Home > Tags > RubyCocoa
RubyCocoa
QCGainer : Quartz Composer Gainer plugin
- 2008-06-06 (Fri)
- default
RubyCocoaでQuartzComposer プラグインをつくれるようになった ので、さっそくつくってみました。 みんな大好きGainer の入力を QuartzComposer からとれるようにしました。
ソースコード
github:qc_gainer に。MITライセンスです。 Funnel, osc を使っています。
短かいので、コードをはっておきます。ごさんこうに。
# # qc_gainer.rb # # Created by mootoh on 6/6/08. # Copyright (c) 2008 deadbeaf.org. All rights reserved. # require 'osx/cocoa' OSX.require_framework 'QuartzComposer' require 'funnel' class QCGainer < OSX::QCPlugIn def self.executionMode 2 end def self.timeMode 1 end def initialize @initialized = false @gio = Funnel::Gainer.new(Funnel::Gainer::MODE1) @ain = [0,0,0,0] @din = [0,0,0,0] @aot = [0,0,0,0] @dot = [0,0,0,0] end def startExecution(context) Thread.new do sleep 0.1 4.times { |i| addInputPortWithType_forKey_withAttributes(OSX::QCPortTypeNumber, "aot_" + i.to_s, nil) } 4.times { |i| addInputPortWithType_forKey_withAttributes(OSX::QCPortTypeNumber, "dot_" + i.to_s, nil) } 4.times { |i| addOutputPortWithType_forKey_withAttributes(OSX::QCPortTypeNumber, "ain_" + i.to_s, nil) } 4.times { |i| addOutputPortWithType_forKey_withAttributes(OSX::QCPortTypeNumber, "din_" + i.to_s, nil) } end true end def execute_atTime_withArguments(context, time, args) unless @initialized 4.times do |i| @gio.ain(i).on Funnel::PortEvent::CHANGE do |event| @ain[i] = event.target.value end @gio.din(i).on Funnel::PortEvent::CHANGE do |event| @din[i] = event.target.value end end @initialized = false end 4.times do |i| setValue_forOutputKey(@ain[i], "ain_" + i.to_s) setValue_forOutputKey(@din[i], "din_" + i.to_s) end true end def stopExecution(context) true end end
使い方
- Gainer I/O をMacとつなげる
- Funnelサーバを起動
- Quartz Composerを起動
- Pluginを配置
デモ
ビデオに撮るのがめんどうなので、Ruby会議 に持っていきます。
まとめ
こんなふうに、ありものをくっつけてささっと何かをつくるというとき、glue言語としてRubyはグッドですね。
Happy Physical Hacking !
- Comments: 2
- Trackbacks: 1
Leopard で Quartz Composer カスタム Plugin を RubyCocoa でつくる
- 2008-06-06 (Fri)
- default
RubyCocoaで、QuartzComposerのカスタムプラグインをつくることができました on Leopard。 LeopardでのQuartzComposerカスタムプラグインづくりの続きです。あれから4ヶ月。
サンプル: qcplugin_rb
サンプルとして、2入力をとってその間に “Ruby” を挿入して後段に流すというプラグインを書きました。 githubにアップしてあります。MITライセンスです。
ソースコード: github:qcplugin_rb
スクリーンショット
ポイント
- Info.plistの書きかた
- 入出力ポートの追加方法
Info.plistの書きかた
1.まず、Info.plist の NSPrincipalClass エントリに、RubyCocoaのBundleを初期化するObjective-Cのクラスを指定します。 RubyCocoaでMacのプラグインをつくるときの共通tipsですね。
2.それと、実際のプラグインの動作を記述するクラスを QCPlugInClasses に列挙します。QCPlugInクラスを継承したやつですね。
<key>NSPrincipalClass</key> <string>RBLoader</string> <-- 1 <key>QCPlugInClasses</key> <array> <string>QCRubyPlugin</string> <-- 2 </array>
入出力ポートの追加方法
LeopardのQuartzComposerから、ポートの指定は Objective-C 2.0 の property 機能を使って行うようになりました。 これをRubyCocoaからどう使うか分からなかったので、QCPlugInクラスのメソッドであるaddInputPortWithType:forKey:withAttributes: や addOutputPortWithType:forKey:withAttributes: を使ってなんとかします。
では、どこにこれらのメソッドを書くか。QuartzComposerの制約で、executeメソッド内に書いちゃダメよ、とあるので困ります。 サンプルコードを漁ってみたところ、別スレッドを立ててそこで呼び出すのはOKぽい。 そこで、RubyのThreadをつかってやってみたところ、うまくいきました。ただし、ちょっとdelayを設けてやらないとQuartzComposerが死にます。なぜだ。
もっとうまいやり方をご存知のかたがいれば、ぜひ教えてください。 m(_ _)m
ひっかかりどころ
バッドノウハウがいくつかありますね。
- s/QCPlugin/QCPlugIn/ なのです。見落しやすすぎる。
- ポート追加のdelay。
- メソッド名。execute が execute_atTime_withArguments になったりします。気づかずにいると、いつまでたってもメソッドが呼ばれずに悲しくなる。
- イチからXcodeのプロジェクトをつくるよりも、サンプルを元に書き換えていくとはまりにくい。
おまけ
こう書くとすんなり動くようになったみたいですが、現実は泥のようなデバッグでした。 RubyCocoaのフレームワークをデバッガで追ったり、QuartzComposerの謎の挙動に悩まされたり。 フレームワークをつかった開発でのデバッグについては得るところがあったので、別に書くことにします。
関連エントリ
まとめ
Happy Visual Hacking !
- Comments: 0
- Trackbacks: 1
QSTwitter 1.4
- 2008-03-01 (Sat)
- default
QuicksilverからTwitterに投稿するプラグイン、QSTwitterを1.4にアップデートしました。 ダウンロードはこちら。 まだ若干バギーです :) が、 つかってみてくださいね。
変更点
Triggerで一発ポスト !
後述のTrigger設定をすることにより、ショートカット一発でTwitterにポストできるようにしました。
これまで:
- QS起動
- テキストモードに移行 (ピリオド入力)
- テキスト入力
- タブキーでActionに移動
- postと入力 (ここを抜かして、’Large Type’になるミスが多発していました)
- ENTER でポスト
1.4:
- Triggerのショートカット入力
- テキスト入力
- ENTER でポスト !
ステップ数で2倍、体感速度およびストレスでさらに倍程度速くなりました。
Trigger のセットアップ
カタログをつくったあと、図のような手順を踏みます。ショートカットキーはお好きなものを。ターゲットのとこをブランクにするのがコツかと。

参考: わかばマークのMacの備忘録 : Quicksilver/ Proxy Objects について
スクリーンショット

注意事項
1.3と同様です。
中のつくりについて
QSTwitterというダミーfollowingユーザをつくり、ここにreplyするとpublic timelineに発言するようなフェイクをつくることでTriggerを実現しました。
コード
以前のバージョン
関連エントリ
- Comments: 1
- Trackbacks: 0
QSTwitter 1.3
- 2008-02-22 (Fri)
- default
QuicksilverからTwitterに投稿するプラグイン、QSTwitterを1.3にアップデートしました。 ダウンロードはこちら。 若干バギーです :)
変更点
Friendの補完
自分がfollowしているひと (= Friend) をカタログに保持し、 Quicksilverから補完入力できるようにしました。 アイコンが表示されるのがキュート。
Reply Action
補完入力したFriendに対して、reply Action でメッセージを送れるようにしました。 自動的に、@だれそれがメッセージの先頭につきます。
HTTP プロキシ
環境変数に、http_proxyが設定されている場合に、そのプロキシサーバを使うようにしました。
Action名の変更
これまでは、TwitterというActionでメッセージを送っていましたが、 postというActionに名前を変えました。
スクリーンショット

スクリーンキャスト
百聞は一見にしかずということで。
注意事項
- Mac OS X 10.5.2 でしか確認していません。Leopardが必須です。RubyCocoaがインストールされているTigerでも、ひょっとしたらビルドできるかも。
- JSONのRubyライブラリが必要です。sudo gem install jsonなどとしてインストールしてください。
- インストールした直後、Quicksilverが固まります。これは、Friendすべてをダウンロードしてカタログ化しているためです。
- 初回のカタログが生成されたあと、Quicksilverがクラッシュしたり暴走したりします。Quicksilverを再起動すると、ちゃんと動くようです。 (調査中)
中のつくりについて
これまではObjective-Cで書いていたのですが、RubyCocoaで書き直しました。 メリットとしては、以下のようなものがあります。
- JSONが簡単に扱える
- HTTP POST via プロキシができる (NSURLConnectionではなかなかうまくいかない)
- デバッグがラク ( /reload と postすると、Rubyスクリプトが再読み込みされるようになってる)
また、コードを見てもらえると分かるのですが、カタログ化のためにダウンロードしたFriendのJSONを、 Marshal.dumpでPluginがインストールされた場所にキャッシュしています。 なんという手抜き。
コード
以前のバージョン
関連エントリ
- Comments: 0
- Trackbacks: 1
LeopardでのQuartzComposerカスタムプラグインづくり
- 2008-02-19 (Tue)
- default
Ruby会議2008のCFPとして、RubyCocoaでインタラクティブにMacのプラグインをつくるよ、みたいなものを書いて出してみました。 出してから、そういえばLeopardになってからQuartzComposerで遊んでないなあと気づき、ちょっと触ってみたらえらく変わっていたので、今日調べたことをカスタムプラグインをつくるという観点でメモっときます。
カスタムプラグインがオフィシャルに
最大の変更点はこれです。 Appleの丁寧なドキュメント Introduction to Quartz Composer Custom Patch Programming Guide を読めば、たちまちつくれるようになるのではないかと。サンプルも充実してて、/Developer/Examples/Quartz\ Composer/Plugins/にごろごろ転がってます。 これまで、kineme.net:Xcode Template for Custom Quartz Composer Patches を使い、非公開のAPIでプログラミングしていたのに対して大きな進歩です。
Port指定にはpropertyをつかう
Objective-C 2.0で導入された、propertyという機能をつかって入出力Portの指定をコードでやるようになっています。 これまでは、インスタンス変数名の先頭にinput/outputがあれば、それがPortになっていました。
Portの増減が動的にできる
RubyCocoaでpropertyを使う方法を知らないのでどうしようかなあと思ってたのですが、 addinputPortWithTypeとかaddOutputPortWithTypeというメソッドを使えば動的にPortが加えられます。
注意すべきことは、これらのメソッドはプラグインのexecuteメソッドに入れてはいけないことです (例外があがってQuartzComposerが止まってしまいます)。んじゃどうするかということで、ExampleのCommandLineToolとFreeFrameHostを調べてみると、どうやらQuartzComposerのインスペクタから受け取るイベントのハンドラで、これらのメソッドを呼ぶようにしています。なるほど。
インストール場所が変わっている
これまでは、/Library/Graphics/Patch にプラグインを置くのが流儀だったのですが、Leopardからは/Library/Graphics/Quartz\ Composer\ Plug-Ins/ になりました(~/Library以下でもOK)。
カスタムプラグインをつくるという点から、Leopardになって気づいたことをまとめました。次はいよいよ、RubyCocoaでカスタムプラグインづくりです。
関連エントリ
- Comments: 0
- Trackbacks: 0
もっとRubyCocoaでQuicksilverプラグインを書く
- 2008-02-06 (Wed)
- default
RubyCocoaを使ってQuicksilverプラグインを書く の続編です。 前回書いたあと、hisaさんからアドバイスをもらい、RubyCocoaでプラグインを書くことができるようになりました。
ポイントは以下の2つ。
- NSPrincipalClass
- RubyActionClass < OSX::QSActionProvider
コードは、CodeReposのものをupdateしておきました。 hisaさんコードがたくさん入っていますが、煮るなり焼くなり好きにせよとのことなので、前回と同じ修正BSDライセンスとします。
NSPrincipalClass
Quicksilver プラグインに限らず、Cocoaで何らかのBundleを書く際には、Info.plistの NSPrincipalClass にクラス名を指定しておくと、そのクラスの +load() メソッドが呼ばれてプラグインが組み込まれるようです。 なので、ここでRBBundleInit()を呼んでRubyCocoaを初期化すると。とてもスマート。 前回はこのことを知らずに、無理矢理なハックをしていたのでした。
RCLoader.m:
ここでは、NSPrincipalClassにRCLoaderというクラスを指定しています。
@implementation RCLoader + (void)load { static bool installed = 0; if (! installed) { if (! RBBundleInit("load_ruby.rb", [self class], self)) { installed = true; } } }
load_ruby.rb:
そんで、RubyCocoaの初期化の中で、必要な.rbを読み込んでおく、と。
def load_ruby_programs(bundle, logger) path = bundle.resourcePath.fileSystemRepresentation rbfiles = Dir.entries(path).select {|x| /\.rb\z/ =~ x} rbfiles -= [ File.basename(__FILE__) ] rbfiles.each do |path| require( File.basename(path) ) end end OSX.init_for_bundle do |bundle, param, logger| load_ruby_programs(bundle, logger) end
RubyActionClass < OSX::QSActionProvider
前回説明したように、QuicksilverのActionを記述するには、QSActionProviderを継承したクラスでメソッドを用意します。 今回は、RubyのクラスでQSActionProviderを実装します。RubyCocoaのパワーがすごすぎる。
class RubyAction < OSX::QSActionProvider def act(arg) val = arg.stringValue OSX::QSObject.objectWithString('Hello world, ' + val) end end
あとは、このRubyActionクラスを、Info.plistのactionClassに、actionSelectorをact:に指定しておけば、ちゃんとactが呼ばれます。
疑問
- OSX.require_framework とか使わないで、QSActionProviderとかのQS frameworkが使えてるのはなぜ?
- hisaさんは、この方法だとCPU usageが高くなると懸念されているのですが、僕のところではいまひとつ高くなってるように見えません。why?
まとめ
RubyCocoaで、Quicsilverプラグインを書く方法をまとめました。 これで、あとはRubyのクラスとInfo.plistを書くだけでどんどんプラグインが書けますね。みんながんばれ!
あと、自分でもイマイチだなーと思うことでも、何かしら書いて晒すことが大事なんだなと。そんで改善していければOKなんだ。 hisaさん、ありがとうございました。
- Comments: 10
- Trackbacks: 1
Write a Quicksilver plugin with RubyCocoa
- 2008-02-03 (Sun)
- default
I wrote a Quicksilver plugin with RubyCocoa, that adds “hello world” to the passed string.
- Xcode project codes : in CodeRepos
- License : revised BSD
It should work on Tiger/Leopard if RubyCocoa is installed.
How does it work
As I mentioned in QuartzComposer CustomPatch with RubyCocoa, we just call RBBundleInit() function in plugin initializaiton phase to write some plugin with RubyCocoa. But wait, where should be the initializaiton code in Quicksilver plugin ?
We write actual plugin behavior in the class that inherits QSActionProvider, so I tried to call RBBundleInit() in the init() method in that class… however, that resulted in crashing Quicksilver :(
Then I called RBBundleInit() at only first time in performActionOnObject() of the actual Action class, and made references between Objective-C and Ruby class instances.
After that, I implemented an actual action behavior in Ruby class, and delegates from Objective-C to Ruby method, thats’ all. This is ugly, confusing, … I know, but it does work well :)
Code Snippets
ActionProvider in Objective-C:
(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 side:
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
Future works
- Better initializaiton. It should be in constractor of some class)
- Inherits QSActionProvider by Ruby class (more pure Ruby)
- Use ns_import ?
It should not be “With RubyCocoa”, but rather “By RubyCocoa”.
Conclusion
I made a start point to write a Quicksilver plugin by RubyCocoa. It is timing of initialization that is to be considered about writing some bundle in RubyCocoa. I made it in this article somehow. This article is for someone who wants to create Quicksilver plugin by Ruby, not learning unfamiliar Objective-C.
Reference
- QuartzComposer CustomPatch by RubyCocoa
- PyObjC Plug-ins : Article about writing Quicksilver plugin by PyObjC (a bit obsolete…)
- Comments: 0
- Trackbacks: 2
RubyCocoaを使ってQuicksilverプラグインを書く
- 2008-02-03 (Sun)
- default
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さんにコメントいただき (!)、もっとよい方法を教えてもらいました。 こちらについても、まとめて別エントリにします。
- Comments: 3
- Trackbacks: 0
RubyCocoa で QuartzComposer CustomPatch (2)
- 2007-07-15 (Sun)
- default
とりあえず、できました。
スクリーンショット
サンプル
input, output ともにStringのPortを1つずつもち、入力にある”Objective-C”を”Ruby”に正規表現で置換するというだけのサンプルを書きました。
RubyPatch (binary & source) rev3 (i386 Binary)
RubyCocoaをUniversal Binary でビルドしていなかったため、いまのところIntel Mac だけで動きます。
RubyCocoa の svn trunk、 MacBook Kuroで動作確認をしています。
何をしているか
- Patchがロードされるとき、すなわち “registerNodesWithManager” が呼ばれるときに、RBBundleInitを呼んでRubyCocoaの準備をする
- Patchの動作を記述するObjective-CのクラスをProxyとして、実際の動作を記述するRubyクラスのインスタンスを保持させる
- Rubyクラスは、Objective-CクラスからPortをもらってくる
- Patchを実行するイベントがきたら、Rubyクラスのインスタンスに対して処理を移譲
といったところ。
実際のコードは、RubyQC::Trac にあるのでご覧ください。
このアプローチの問題点
- いちいち全メソッドを Objective-Cのクラス → Rubyのクラス に投げるよう書くのがめんどう
- Portの記述がObjective-Cのクラスにしか書けない (みたい) なので、Portを加えたり減らしたりするたびにObjective-C/Rubyコードともに変更しないといけない。わすれそう。
OSX::QCPatchクラスをRubyクラスで継承できて、さらにPortの情報もRubyクラスで書けるようになれば、これらの問題は解決しそうです。
課題
QCPatch.execute は、3つの引数をとるのですが、Rubyクラスのメソッドにどうやって3引数を渡すのか分からない…
18:03 追記
MLで、LimeChatの中の人に教えてもらいました。
objc_method :execute_time_arguments, %w|char id double id| def execute_time_arguments(fp8, fp12, fp20) ... end
のようにすればいいよ、とのことで、ばっちり動きました。ありがとうございます。
とはいえ、CustomPatch がRubyで書けるようになったのは大きな進歩です。ApacheReaderPatch とかも、ずいぶんと簡単に書けそう。
- Comments: 0
- Trackbacks: 0
RubyCocoa で QuartzComposer CustomPatch をつくりたい
- 2007-07-14 (Sat)
- default
先に今の状態 : できそうな糸口がつかめたところで挫折 :(
SocketReaderPatch、ApacheLogPatch とQuartzComposerのカスタムパッチをつくってきたのですが、ここらでRubyで書くかと思い立ちました。 バベル案内 と ホワイの(感動的)Rubyガイド を読んで、こんなにもRubyが世界に受け入れらているのかと感動したことと、いくつかCustomPatchをつくってみて、CustomPatchj作成の概観がつかめてきたから。
どうやるか
RubyCocoaのsvn trunkを使います。 最近のリビジョンでは、QuartzComposer.frameworkが使えるようになっているのです。
>> require 'osx/cocoa' r=> true >> OSX.require_framework 'QuartzComposer' => true >> OSX::QCStructure => OSX::QCStructure >> OSX::QCPatch => OSX::QCPatch
ひゃっほう。
で、いつもどおりQuartzComposer CustomPatch の Xcode template をつかって、Xcodeプロジェクトをはじめます。
RubyCocoa.frameworkをリンクしておき、RubyPatchPrincipla.m を
#import <rubyCocoa/RubyCocoa.h> #imporut "RubyPatchPrincipal.h" @implementation RubyPatchPlugin + (void)registerNodesWithManager:(GFNodeManager*)fp8 { static bool loaded = false; if (!loaded) { if (RBBundleInit("/tmp/custom_patch.rb", self, nil)) { NSLog(@"[RubyPatchPlugin.registerNodesWithManager] RBBundleInit failed"); } else { loaded = true; } } Class helperClass = NSClassFromString(@"RubyPatch"); [fp8 registerNodeWithClass:helperClass]; } @end
のように書きました。 RubyCocoaのbundle生成用APIを用いています。
loadされるRubyスクリプトはこういうもの。
require 'osx/cocoa' OSX.require_framework 'QuartzComposer' class RubyPatch < OSX::QCPatch #attr_accessor :inputFoo, :outputBar def RubyPatch.logger=(l) @@logger = l end def RubyPatch.executionMode @@logger.info("executionMode") 2 end def RubyPatch.allowsSubpatches @@logger.info("allowsSubpatches") 0 # in objc.h, NO is defined as 0 end def RubyPatch.timeMode @@logger.info("timeMode") 1 end def init @@logger.info("init") @inputFoo = OSX::QCBooleanPort.alloc.init end def initWithIdentifier(fp8) @@logger.info("initWithIdentifier") init self end def setup(fp8) @@logger.info("setup %s, %s", fp8, fp8.class) fp8 end def cleanup(fp8) @@logger.info("cleanup") end def enable(fp8) @@logger.info("enable") end def disable(fp8) @@logger.info("disable") end def execute(fp8, time, arg) @@logger.info("execute") true; end end OSX.init_for_bundle do |bundle, param, logger| logger.info("init bundle=%s param=%s", bundle, param) RubyPatch.logger = logger end
templateが生成するObjective-Cのコードを、Rubyに書き下したものですね。
実行
ビルドもすんなり通り、さっそく実行してみます。
アウチ。 なんか例外が投げられてしまいます。 出しているログを眺めてみると…
正常なもの (Objective-Cで書いたカスタムパッチ)
2007-07-14 14:35:03.568 Quartz Composer[15580] RubyPatch.plugin (Quartz Composer): init bundle=NSBundle (loaded) param= 2007-07-14 14:35:03.708 Quartz Composer[15580] initWithIdentifier 2007-07-14 14:35:03.708 Quartz Composer[15580] executionMode 2007-07-14 14:35:03.708 Quartz Composer[15580] timeMode 2007-07-14 14:35:03.710 Quartz Composer[15580] allowsSubpatches 2007-07-14 14:35:03.741 Quartz Composer[15580] allowsSubpatches 2007-07-14 14:35:03.741 Quartz Composer[15580] allowsSubpatches 2007-07-14 14:35:03.741 Quartz Composer[15580] allowsSubpatches 2007-07-14 14:35:03.785 Quartz Composer[15580] setup
だめなもの (RubyCocoaで書いた)
2007-07-14 14:36:11.300 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): init bundle=NSBundle (loaded) param= 2007-07-14 14:36:11.301 Quartz Composer[15602] assert passed ! 2007-07-14 14:36:11.342 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): initWithIdentifier 2007-07-14 14:36:11.342 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): init 2007-07-14 14:36:11.345 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): allowsSubpatches 2007-07-14 14:36:11.377 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): allowsSubpatches 2007-07-14 14:36:11.378 Quartz Composer[15602] RubyPatch.plugin (Quartz Composer): allowsSubpatches
よく見てみると、RubyCocoaで書いた方は、initWithIdentifier のあとに executionMode, timeMode が呼ばれることなく allowsSubpatches が呼ばれています。 executionMode, timeMode が分からないので、QuartzComposer としてはこのパッチをどの種類にして良いか分からず、レンダリングできませんよーという例外がきてるっぽい。
問題
ではなぜ executionMode, timeMode が呼ばれないか、考えてみます。
QCPatchのヘッダによると、executionMode, timeMode はそれぞれ int を返すメソッドのようです。 ところが、RubyCocoaで用意した executionMode, timeMode は返り値の型を指定していません。 ちょっと調べたところ、RubyCocoaではRubyのメソッドが整数型を返すとき、Objective-Cの世界にはNSDecimalNumberの型を返すようになっているようです。
その結果、executionMode, timeMode は別のメソッドとして認識されてしまい、QCPatchのメソッドをオーバーライドできず、呼ばれていないのではないかと推測しました。
解決案
- intで返すようにRubyCocoaでなんとかする
- QCPatchをRubyクラスで継承せずにObjective-Cのクラスで継承させ、そのメンバにRubyクラスのdelegateのオブジェクトをもたせて、実際の処理はdelegateオブジェクトに振る
2が手っ取り早そうですが、いまひとつかっこいくないなぁ。
別の問題
CustomPatchのテンプレートによると、QCPatchを継承したクラスのメンバに QCPortクラスのインスタンス (ex. inputEnable, outputString など) を書くことで、パッチのinput/outputポートを指定できるような仕組みになっています。
じゃあ、
attr_accessor :inputEnable, :outputString ...
みたいにすればいいのか、というとどうもそうでもないみたいで…
やはり解決案2を使うしかないのかもです。
- Comments: 0
- Trackbacks: 0
Home > Tags > RubyCocoa



