RubyCocoaを使って、Quicksilverのプラグインを書いてみました。
受けとった文字列に、”hello world” をくっつけるだけのものです。
プロジェクト一式のコードは、CodeReposに。
ライセンスは修正BSDで。
RubyCocoaがインストールされていれば、TigerでもLeopardでも動くと思います。
仕組み
RubyCocoaでQuartzComposer CustomPatchのときに書いたように、
CocoaアプリじゃなくてプラグインをRubyCocoaで書くには、RBBundleInit() という関数をプラグインの初期化の際に呼んであげればよいわけです。
ところが、Quicksilverプラグインの初期化ってどこなんだろうという。
QSActionProviderを継承したクラスで実際のプラグインの動作 (Action) を記述するので、このクラスのinit()で RBBundleInit() を呼べばいいかなーと考えてやってみたところ、Quicksilverをクラッシュさせてしまいます。こまった。
しかたないので、Actionを記述する performActionOnObject() の中で、RBBundleInit()を最初の一度だけ呼ぶようにしました。その中で、Objective-CからRubyクラスのインスタンスを参照できるようにしておきます。
あとは、RubyのクラスでActionを実装し、Objective-CからRubyのメソッドを呼んで移譲します。これでできあがり。
かなり強引で美しくないのですが目的は果たせるかな、というダーティハックです。
コード片
Objective-CのActionProvider:
(QSObject *)performActionOnObject:(QSObject *)dObject{
// initialize RubyCocoa
static bool loaded = false;
if (!loaded) {
if (RBBundleInit("qs_action.rb", [self class], self)) {
NSLog(@"[RubyCocoaPluginAction.performActionOnObject] RBBundleInit failed"
);
abort();
}
loaded = true;
}
// delegate actual action to Ruby class
QSObject *ret = [QSObject objectWithString:[rb_ act:dObject]];
return ret;
}
RubyCocoa側:
class Action
def initialize(logger)
@logger = logger
end
# write something great :)
# - arg : QSObject
def act(arg)
val = arg.stringValue
@logger.info(val)
'Hello world, ' + val
end
end # Action
require 'osx/cocoa'
OSX.init_for_bundle do |bdl, owner, log|
# bdl - the bundle related with the 2nd argument of RBBundleInit
# owner - the 3rd argument of RBBundleInit as optional data
# log - logger for this block
act = Action.new(log)
owner.setInstance act
end
あと、やるとしたら
- もうちょっときれいに初期化する (何らかのクラスのコンストラクタが望ましい)
- QSActionProviderをRubyのクラスで継承する (よりRubyだけで書けるように)
- ns_importとかつかってみる?
「RubyCocoaを使って」 よりも、「RubyCocoaで」、スマートにプラグインを書けるようにしたいものです。
まぁ、まずは第一歩ということでひとつ。
まとめ
RubyCocoaを使って、Quicksilverプラグインをつくるためのとっかかりについて書きました。
RubyCocoaでバンドルを書くときに悩むのは初期化のタイミングだと思います。
今回はここを無理矢理解決しました。
Objective-Cを学んでる時間があったら、慣れ親しんでるRubyでちゃちゃっと
コード書きたいよ! という方の参考になればと思います。
参考
2008.02.03 追記
RubyCocoaのhisaさんにコメントいただき (!)、もっとよい方法を教えてもらいました。
こちらについても、まとめて別エントリにします。