08 November '2007 - 12:26 | 技術動向 Text Input Source Services を RubyCocoa で使う
Psychs 先生が、Text Input Source Services なるものが 「Leopard で追加されたらしい」とさえずっていたので、つい出来心で遊んでみることにした。Leopard で遊ぶということは、RubyCocoa で遊ぶということなので、さくっと ruby で書いてみる。
と思いきや、このモジュール、なんと Carbon だと。もう、いいよ、Carbon は。
で、とりあえず TextInputSources.Private.bridgesupport を手作業で(笑)でっちあげて、いくつか関数を実行して遊んでみた。当然ながら期待どおりにさくっと動いた。前にやった IOKit とかと違ってぜんぜんパラメータもないし、呼ぶだけ系なので楽勝。
たとえば、入力を US (というか ASCII だけれど) に変更するスクリプトは、こんな感じで書ける。
require 'osx/cocoa'
OSX.load_bridge_support_file 'TextInputSources.Private.bridgesupport'
OSX::TISSelectInputSource OSX::TISCopyCurrentASCIICapableKeyboardInputSource()
最初の二行はおまじないなので、じっしつ一行だけ。
たとえば、Terminal とか QuickSilver とか、特定のアプリがフォーカスを得た時にこのスクリプトを呼ぶようにしておけば便利。と思いきや、どうやればそうできるのかが分からない。Psychs 先生に聞いてみたら、どうやら OSX ではすべてのウィンドウのイベントをフックするとか、Windows みたいに簡単にはできないらしい。うーむ。
まあ、それはそれとして、たとえば TISGetInputSourceProperty() を使って、
p OSX::TISGetInputSourceProperty(OSX::TISCopyCurrentKeyboardInputSource(), OSX::KTISPropertyInputSourceLanguages)
みたいなことをやると、たとえば
#<OSX::ObjcPtr:0xd6a24 cptr=0x3b6280 allocated_size=0 encoding=v>
みたいに表示されて、ちゃんと void* なポインタが取れてるのは分かる。なぜか allocated_size はゼロって書いてあるけど、たぶんサイズが分からないからゼロってことにしてあるだけだと思うので気にしない。実際、データは入ってて、読むことはできる。
で、この void* なポインタはキャストして使わないといけない。上の例だと、この void* なポインタは「文字列へのポインタ(CFStringRefs)の配列へのポインタ (CFArrayRef)」にキャストして使う。のだけれど、どうやってキャストするのかが分からない。って、さすがにそれは無理か。
結局、こんな Carbon あがりの半端者をむりやり RubyCocoa で使えるようにしたって、たいして嬉しくならなかったりするらしい。
ちなみに、void* なポインタを、指定されたオブジェクトだと盲目的に信じて無理矢理オブジェクトを生成して返す関数でも作ってみたらどうだろうか。とうぜん、ruby コードなのに SEGV だらけになるんだろうな。
いったい、なんのための ruby かと。冒涜するのもいい加減にしろと。
<retval declared_type='void*' type='^v'/>
となっているところを
<retval declared_type='id' type='@'/>
と変えてみると問題なく動作しました。
Psychs (ウェブサイト) - 10 November '2007 - 15:46
これで動くのは
CoreFoundation の CFStringRef と CFArrayRef が
Cocoa の NSString* と NSArray* (つまりid) と互換だからです。
たとえば、ポインタの指示先は C の struct __CFString としても、
Objective-C の NSString としても扱えるように設計されています。
Psychs (ウェブサイト) - 10 November '2007 - 15:55
じつはこのエントリを書いたあとにいろいろ試していて、id にすれば動くということを発見していたのだけれど、CFStringRef と CFArrayRef 以外の時にも動く理由がよく分からなくて混乱していました。この二つは先生が書いてくれた理由をどっかで読んでたので問題なかったのですが、なんで CFBooleanRef と CFDataRef の時にもちゃんと動くんだろうか?と。
でも、たぶん、こいつらもぜんぶ id 互換ってことでいいんでしょうね。
ただ、id に変更していろいろ試していたら、ひどいバグをみつけて困ってしまっていました。
ぼくの環境では US キーボードで、OSX::KTISPropertyIconRef を取ろうとすると、なんと (void*)1 が帰ってきます orz
なので、id にしていると必ず SEGV を起こせてしまいます。0 じゃなくて 1 ってあたりがいかにもバグっぽいので libffi でも深追いしてみようかと思ったのですが、どこが間違ってるのかよく分からないので、とりあえず talk にでも書いておいた方がいいですかね。
ひろしま - 10 November '2007 - 18:04
http://developer.apple.com/documentation..
に載っています。
CFBooleanRef については、RubyCocoa で NSCFBoolean に変換するので '@' で問題ないはずです。
ただ、IconRef とかは、どこかで NSImage#initWithIconRef で NSImage にする必要がありますが。
とりあえず、ObjcPtr#as_id を trunk に追加しておいたので、'^v' のまま as_id すると NSObject として取り出せるようになりました。名前がいまいちなのと、もうちょっと何とかならないかと思うので、まだ今後変える可能性があります。
うちでは、OSX::KTISPropertyIconRef はかならず 0 を返してくるみたいです。1 が返ってくるのはおかしいですね。PPC っていうことはないですよね? (PPC は結構あやしい)
もしよかったらレポートしておいていただけると助かります。
Psychs (ウェブサイト) - 11 November '2007 - 15:10
ObjcPtr#as_id は便利でいいですね。これなら戻り値が (void*)1 だったら回避するというヒドス kludge を入れることができますしねw しかし、メソッド追加して解決するというのはぜんぜん気づきませんでした。さすが、コミッタは偉い :)
ちなみに、レポートしると言われたのでしておこうかと思ったのですが、よく考えてみると先生の環境で再現しないというのはちょっとおかしい。なので、念のために同じことをするコードを C で書いて実験してみたら、なんと (void*)1 が帰って来てましたよw
つまり、RubyCocoa はシロってことでした。うーむ。
ぼくの環境だけがおかしいのかも知れないので、他の Leopard で追試して、再現するようなら Apple にレポートしておきますw
ちなみに、PPC って何でしょうか?ぼくは、使えない OSX になんか見向きもしなかった真の勝利者たる誉れ高き 2006 Switcher ですが、それが何か?
ひろしま - 11 November '2007 - 20:56
この件について、Laurent と話してみたんですが、TextInputSource.bridgesupport を OS に添付するように Apple にレポートしといてよ、と言われました。もしよかったら、お願いできますか?
ぼくも 2006 Switcher なので、PPC についてはよく知りませんw
Psychs (ウェブサイト) - 12 November '2007 - 10:35
さっそくですが、ObjcPtr#cast_as(encoding) に変更になりました。
ptr.cast_as('@') で as_id 相当の動作になります。
Psychs (ウェブサイト) - 12 November '2007 - 14:00
で、メソッド名は #cast_as() ですか。いいですねぇ、ruby なのにw
# as_id よりも to_id の方がいいんじゃないかと思ってたのだけれど、何にでもできる方がいい :)
ちなみに、(void*)1 問題は、他の環境でも再現できるかどうか、他の人にも試してもらおうと思ったら、OSX.load_bridge_support_file が undefined とか言われて動かないと言われました。もしかしてこれって、Leopard の RubyCocoa には入ってないメソッドなんですかね。ぼくも同じのを使ってるつもりだったのだけれど、いろいろやってるのでよく分からない。
ぼくの環境では何度やっても、入力が US の時だけ (void*)1 で、それ以外の時は NULL が返ってくるので、ぜひ誰かにも再現してもらいたいなあ。再現されたらレポートするので、誰か再現して下さいw
ひろしま - 12 November '2007 - 15:13