きりかノート 3冊め

おあそびプログラミング

InputSwitcher で「書類ごとに異なる入力ソースを使用」が復活!

http://d.hatena.ne.jp/Psychs/20071108/1194528979#c1194621054の長い説明。

Psychsさん作の InputSwitcher(11/10 時点での最新は 0.2)がとてもとても便利。これは、アプリケーションごとの InputMethod の状態を保存してくれるユーティリティ。

背景

Leopard では、InputMethod の状態がシステムでひとつになっている。Tiger 以前では、システム環境設定 > 言語環境に「書類ごとに異なる入力ソースを使用」と「すべての書類で同じ入力ソースを使用」のラジオボタンがあり、どちらかを選ぶことができたのだけど、Leopard では後者のみが強制されるようになってしまったのだ。

これがどういうことかというと、アプリケーションを切り替えても日本語入力や英数入力の状態がそのままになってるということ(いろいろ省略)。で、これが原因となって

  • メール書く→Quicksilver で他の作業をしよう→入力が反映されない(日本語入力になっているままだから)→ムキー!!
  • ブラウザで調べもの→ターミナルに戻ってプログラミング→でf(日本語のまま)→むきーっ!!

という悲劇が(少なくとも自分のとこで)起きていたわけ。

InputSwitcher 登場

InputSwitcher は、 制御アプリ(サーバ InputSwitcher.app)と SIMBL プラグイン(クライアント InputSwitcherClient.bundle)で構成されている。

  • サーバ:アプリごとの InputMethod の状態を記憶、必要なら InputMethod を変更する
  • クライアント:アプリのアクティブ/非アクティブの状態の変化を監視、変更があれば変更のあったことをサーバに通知する

という仕組みになっている。動作を具体的に説明すると、

  1. GyazMail でメールを書いている。入力は「かな」
  2. ターミナル.app に切り替えてプログラミングしようとする。
    1. ここで GyazMail が非アクティブになるので、クライアントはサーバに「GyazMail:非アクティブ」との通知を送る
    2. サーバは通知を受け取って「GyazMail:最後の入力状態は「かな」」を記憶する
  3. ターミナル.app でプログラミング作業。入力は「英数」
  4. ちょっとメールチェック、GyazMail に切り替え
    1. クライアントはサーバに「GyazMail:アクティブ」との通知を送る
    2. サーバは通知を受け取って GyazMailの最後の入力状態は「かな」だったことを思い出す。そして、システムの入力状態を「かな」に変更

のようになる。もちろん、ターミナル.app 側でのアクティブ/非アクティブ時にも、同じように動作している。簡単のため省略。

これで、アプリケーションごとに入力方法が切り替わるようになるのだ。

0.1 (最初のバージョン)のクライアント初期化処理

クライアントはアプリケーションのアクティブ/非アクティブの変更を監視(というか NSApplication の通知がくるのを待ってる)するために、オブジェクトをひとつ作成する。

このオブジェクトの生成をアプリケーションの起動後にどこかで実施する必要があるのだけれど、0.1 では

  • NSObject と NSWindow を poseAs: したクラスを作成
  • このクラスの -awakeFromNib メソッドでクライアントオブジェクトの生成を行う

という手続きになっていた。このやり方は、

  • ターゲットのアプリケーションで .nib のロードが発生する
  • その .nib には NSObject(のサブクラス)または NSWindow (のサブクラス)のインスタンスがあり、awakeFromNib がオーバーライドされていない

という条件を満たす必要がある。そうでないと、そのアプリケーションでは InputSwitcher が有効に働かない。

ところで SIMBL には、プラグインの Info.plist の NSPrincipalClass で指定されたクラスに +insltall というクラスメソッドがあると、そのメソッドをプラグインのロード時(= 対象アプリケーションの起動時)に呼び出す、という機能がある。これがプラグインの初期処理に使えるよ、という話でした。たしか、SIMBL 0.5 あたりで導入された機能だと思う> +install。SIMBL 0.2 の頃にはなかったはず。

余談

アプリケーションのアクティブ/非アクティブのシステムグローバルな通知って、無いのだね。ひさしぶりに Notification Watcher 立ち上げてチェックしてみたものの、それらしいものは見つからない。起動/終了は通知飛ぶのに。TSM 関連っぽい通知はすごい数が飛んでいた。

InputSwitcher に感動したので SIMBL の作者に donate してみた。ちょっとスジが違う気がしなくもない。

Quicksilver だと…(2007/11/11 追記)

Quicksilver だと効かないことに気づく。あー、アプリケーション本体はアクティブにならないまま動作するからか。

コードを svn のリポジトリ から取ってきて調べてみると、コマンドパネル(なんて呼ぶんだ?)の表示/表示終了のときにそれぞれ "InterfaceActivated" / "InterfaceDeactivated" という通知が発行されていることがわかった。

で、試行錯誤の結果 +[PSInputSwitcherHook install] に以下の処理を追加した。

 if ([appid isEqualToString:@"com.blacktree.Quicksilver"]) {
[c addObserver:NSApp selector:@selector(activateIgnoringOtherApps:)
name:@"InterfaceActivated" object:nil];
[c addObserver:i selector:@selector(appDidBecomeActive:)
name:@"InterfaceActivated" object:nil];
[c addObserver:i selector:@selector(appWillResignActive:)
name:@"InterfaceDeactivated" object:nil];
[c removeObserver:i
name:@"NSApplicationWillResignActiveNotification" object:nil];
}
  • コマンドパネル表示のときにアプリケーションをアクティブにする
  • コマンドパネル表示のときにサーバにアクティブの通知(アプリケーションが Quicksilver のまま パネル非表示→再表示となったときのため)
  • コマンドパネルの表示終了時に入力モードを記憶させる
  • アプリケーションの非アクティブ時の通知はしない

というもの。activateIgnoringOtherApps: の引数は id でなく BOOL なのでちょっとマズイ。コマンドパネル表示時にメニューバーのアプリケーションが切り替わるのが、うざいっちゃうざいものの、日本語入力になってるよりは全然いい。