Home > RubyCocoaを使ってQuicksilverプラグインを書く

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でちゃちゃっと コード書きたいよ! という方の参考になればと思います。

参考


2008.02.03 追記

RubyCocoaのhisaさんにコメントいただき (!)、もっとよい方法を教えてもらいました。 こちらについても、まとめて別エントリにします。

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でアクション(やその他)を書ける汎用のプラグインがあるとうれしいですよね。

Comment Form
Remember personal info

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

Home > RubyCocoaを使ってQuicksilverプラグインを書く

Feeds

Return to page top