Home > Tags > 206
206
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 で 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
ApacheLogPatch 0.1
- 2007-07-09 (Mon)
- default
ApacheLogPatch というものをつくりました。 Apacheのログを、Quartz Composer で派手に tail -f するというものです。 パス、リモートホストのアドレス、リファラー、検索キーワードが表示されます。
Apacheの設定ファイルを書き換えたりと動くようにするまでの敷居が妙に高いわりには、動いたときの「おー、ふーん」感がなんともいえませんが、動かしてみようという方はどうぞぜひ。
自宅サーバをMacで動かしている方という、たいへん狭いターゲットを狙いました。 スクリーンセーバーとして動かしておき、自分のサーバに人がどんな検索キーワードできているのか、なんてことを夕食どきにご飯を食べながら眺めてみるのも一興ではないでしょうか。
ビデオとスクリーンショット
ビデオは、本当はDVカメラで撮影したちゃんとしたものがあったのですが、どうもFirewireからの取り込みができず挫折して vnc2swf を使いました。ので、フレームレートが悲惨なことになっており、本当にこれ、動かして楽しいの? みたいなことになってます。楽しいんですよ。
Python版のvnc2swfで録り直しました。だいぶ見れるレベルになったのでは。
- Comments: 0
- Trackbacks: 0
QuartzComposer の CustomPatch で Signal を生成する方法
- 2007-07-05 (Thu)
- default
QuartzComposer の Patch には、Signal という 一瞬だけ True になったあとまたすぐ False になるような Port をもつものがあります。たとえば、StopWatch とか、Counterとか。
では、どのようにして CustomPatch で Signal を生成すればよいか、いろいろ試行錯誤してみたので、メモを残しておきます。
Port
@interface SomePatch : QCPatch { QCBooleanPort *outputSignal; .... }
のように、Boolean な Port を宣言しておきます。
Configuration
まず、CustomPatch の timeMode を 1 にして、常に Patch が実行されるようにしておきます。
+ (int)timeMode { return 1; }
としておけばよいでしょう。
ここらへんの詳細については、QCPatch Configuration に書かれてあります。
Signal を発生させる
1〜2回、execute が実行される間に outputSignal の値が True になっていれば、Signal が発生したとみなされるようです。
- (BOOL)execute:(id)fp8 time:(double)fp12 arguments:(id)fp20 { static int count = 0; // signal hack if (TRUE == [outputSignal booleanValue]) { if (1 == count++) { [outputSignal setBooleanValue:FALSE]; count = 0; } } return YES; }
みたいにしました。[outputSignal setBooleanValue:TRUE] にするのは、どこか別のスレッドにてやっています。
まとめ
俺Patchで Signal が送れるようになれば、データが変わったタイミングを後段のPatchに伝えることができて、なにかと使えそうです。RSS Feed Patch や、Image Downloader も、FeedやImageのダウンロードが終わったタイミングをSignalで通知しているわけで。
おまけ
QuartzComposer の CustomPatch をデバッグするには、NSLog が便利です。 というか、つくった Patch は plugin 形式なので、いつものようにデバッガを直接起動することができないので。
NSLogのログは、 /Library/Logs/Console/”自分の uid”/console.log に出力されるので、tail -f などとして眺めるとグッドです。
- Comments: 0
- Trackbacks: 0
QuartzComposer の CustomPatch をつくるための Xcode template
- 2007-07-03 (Tue)
- default
の0.2が公開されてました。
なにが素晴らしいかって、
- より多くの種類の Port ( Image, Structure, …)
- ビルドしたら、自動的に /Library/Graphics/Patch につくったPatchがインストールされる
というところ。
以前のもの よりも格段に使えるものになっているので、QuartzComposerで俺パッチをつくるぜ! という方はぜひインストールしてみればよいと思います。
- Comments: 0
- Trackbacks: 0
IRCのメッセージをQuartzComposerに表示
- 2007-06-17 (Sun)
- default
SocketReaderPatchの応用例として、IRCで流れるメッセージを、QuartzComposerに表示させてみました。
ダウンロード
インストール
- rbot を Rubygem などでインストールする
- インストールしたrbotのディレクトリ (Rubygemの場合は /opt/local/lib/ruby/gems/1.8/gems/rbot-0.9.10) の /data/rbot/plugins/ に、qc.rb をコピーする
つかいかた
- SocketReaderPatch を使うQuartzComposerのCompositionを開いておく (サーバ側のSocketをlistenしておく)
- 適当なサーバ、チャンネルにrbotを常駐させる (rbotのマニュアルを参照)
これで、QuartzComposerにIRCのログが表示されるようになります。
コード
たったこれだけ。
require 'kconv' class QCPlugin < Plugin PORT = 12345 def initialize super @sock = TCPSocket.new('localhost', PORT) end def listen(m) return unless m.kind_of?(PrivMessage) @sock.print Kconv.toutf8(m.message) end def help(plugin, topic="") "QuartzCompositor from IRC log" end def privmsg(m) #puts m.message end end plugin = QCPlugin.new plugin.register("qc")
その他
元々は、Ruby会議2日目に思いついたネタだったので、かれこれ1週間たってしまいました。長かった。 RubyCococaでやろうとか、dRubyだとか何かと野心的にアプローチをとってみたのですが、けっきょくはSocketでごりごり書くというオーソドックスなスタイルに。
それにしても、SocketReaderPatchをつくるときにもqc.rbのときにも、オブジェクト指向の生産性の高さを実感しました。 既存の振舞いをほんの少しカスタマイズしたいときの継承の威力というか。
- Comments: 0
- Trackbacks: 0
SocketReaderPatch 0.1
- 2007-06-16 (Sat)
- default
Mac OSX の Quartz Composer で、Socketから文字列を読み込んで後段に渡すGeneratorパッチ
を書きました。
晴れた土曜の勢いで書いたのでなにかとバグがあるかもしれませんが、プロトタイプを出すことがたいせつなので公開します。フィードバックいただけるとたいへん喜びます。
2007.06/16 20:34 追記
英語の説明文も追加。QuartzComposer CustomPatch Xcode template 配布サイトに感謝のコメントを残しておいた。
2007.06/16 22:50 追記
ScreenCast を追加しました。
ScreenCastには、 Making Free Screencasts on OS X を参考にして、vnc2swfを使いました。マウスカーソルはでないのだけど、フリーでScreenCastがつくれるのは著しく便利です。
swf → mpeg4 には、QuickTime Proをつかって。なぜかH.264ではエンコードできなかったのでMPEG-4。
- Comments: 0
- Trackbacks: 0
RubyCocoaでQuartzComposerパッチ
- 2007-06-13 (Wed)
- default
という小プロジェクトを今週はやっています。
RubyCocoa, CocoaProgramming, Ruby, QuartzComposer, XCode, dRuby といった様々なテクノロジーをいっきに学んでいて、かなりお腹いっぱいになってきます。
* IRCのメッセージをrbotで拾ってdRubyで投げる
* サービス側のdRubyでメッセージを受け取る
* RubyCocoaでbundleを書く
* QuartzComposerのbundleインターフェイスから、RubyCocoaでつくったクラスを指定する方法が分からなくてめげる ← いまここ
フレームワークは便利なんだけれど、なんというか全体が見えなくてもどかしく、できないことが積もっていくうちにめげ気分になるのが難点です。僕だけ?
- Comments: 0
- Trackbacks: 0
Home > Tags > 206


