RubyCocoaを使ってQuicksilverプラグインを書く
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でちゃちゃっと コード書きたいよ! という方の参考になればと思います。
参考
- RubyCocoaでQuartzComposer CustomPatch : QuartzComposerのCustomPatchもCocoaのバンドルなので、このときの経験が活かせました。
- PyObjC Plug-ins : 本家による、PyObjCでQuicksilver pluginをつくるための情報。ただし、リンク先があちこちロストしてる ><
- ひ日誌 : RubyCocoaで Quicksilver pluginは書けるのか? : ここが発端。
2008.02.03 追記
RubyCocoaのhisaさんにコメントいただき (!)、もっとよい方法を教えてもらいました。 こちらについても、まとめて別エントリにします。
- Newer: Write a Quicksilver plugin with RubyCocoa
- Older: RHGの逆襲 #1に行ってきた
Comments:3
- hisa 08-02-03 (Sun) 20:28
-
おおすばらしい!
実を言うと僕も作って使ってたのですが、解決できていない問題もあったりして、無精して公開してませんでした。メールでソース(プロジェクト)を送るつもりですので、よろしければ煮て焼いて食うなりなんなりしてください。
初期化のタイミングですが、僕はプラグインのクラスがロードされたタイミングでやってます:
@implementation QSRubyEnablerPlugin + (void)load { static int installed = 0; if (! installed) if (! RBBundleInit("init_ruby_enabler.rb", [self class], self)) installed = 1; } @end
それからアクションは QSActionProvider の派生クラスとして実装しました。
module QSRubyEnablerPluginActionModule def obj2qsobj(obj) obj = OSX::QSObject.objectWithString(obj.to_s) unless obj.is_a? OSX::QSObject obj end def show_large_type(obj) OSX::QSTextActions.provider.showLargeType(obj2qsobj(obj)) end def paste_string(str) pb = OSX::NSPasteboard.generalPasteboard pb.declareTypes_owner([OSX::NSStringPboardType], self) pb.setString_forType(str, OSX::NSStringPboardType) end end class TranslatorAction < OSX::QSActionProvider include OSX include QSRubyEnablerPluginActionModule require 'yahoo-honyaku' def initialize @yahoo_honyaku = YahooHonyaku.new end def translateEJ(obj) translate_qsobject(:ej, obj) end def translateJE(obj) translate_qsobject(:je, obj) end def translateCJ(obj) translate_qsobject(:cj, obj) end def translateJC(obj) translate_qsobject(:jc, obj) end def translateKJ(obj) translate_qsobject(:kj, obj) end def translateJK(obj) translate_qsobject(:jk, obj) end private def translate_qsobject(mode, obj) src = obj.stringValue src.gsub! result = @yahoo_honyaku.translate(mode, src.to_s) show_large_type(result) paste_string(result.to_s) nil rescue Exception => err show_large_type(err.message) NSLog("QSRubyEnablerPluginAction error: %@", err.message) nil end end
- mootoh 08-02-03 (Sun) 22:30
-
hisaさん:
おおおお、ありがとうございます! つくってらしたんですね。 QSActionProviderの派生クラスをRubyで実装するのは、まさにやりたかったところでした。
頂いたプロジェクトソースを元にして、自分なりのまとめを書いてみます。
- hisa 08-02-04 (Mon) 11:54
-
コメント内のソースがぐちゃぐちゃになってたのを直してくれたんですね。どうもありがとう
僕のやつは、ヤフー翻訳を呼び出すアクションを作るのにhpricotが使いたかったのでRubyCocoaをリンクしたみたいな感じになってますが、Rubyでアクション(やその他)を書ける汎用のプラグインがあるとうれしいですよね。
Trackbacks:0
- Trackback URL for this entry
- http://blog.deadbeaf.org/2008/02/03/quicksilver-plugin-with-rubycocoa/trackback/
- Listed below are links to weblogs that reference
- RubyCocoaを使ってQuicksilverプラグインを書く from mootoh.log

