Home > Tags > mac

mac

SafariでMigemoを使えるようにしようとした

SIMBLプラグインをつくる習作として、Safariのページ内検索にMigemoをつかえるようにできないか試行錯誤してみました。 分かったことは、SIMBLプラグインとしてつくるのは難しそう、ということでした。

作戦

Migemoの出力は正規表現です。

例: takai → (タカイ|タカイ|隆一|多階層|他界|高[市石鼾泉井]|たかい|takai|takai)

対して、Safariのページ内検索は1つのキーワードに対する全文検索です。 つまり、探すべきキーワードが1つから複数に変わるということです。これは大きな差。

ならば、元のコードでキーワードを渡して検索している関数を、キーワードの個数ぶんだけ繰り返し呼べば所望の機能が実現できそうだ、と考えました。

どこに手を入れるか

Safariの文字列検索を追う で書いていたように、WebView::searchFor にブレークポイントをはり、デバッガでステップ実行して挙動を調べました。

コードを書き変える

まず、元の searachFor メソッドを _safari_searchFor メソッドに変名してとっておき、 この_safari_searchFor メソッドをMigemoの正規表現から得られる文字列の数だけ呼び出す、という戦略を考えます。 うまくいけば、SIMBLプラグインとして実装する際に Method Swizzling として実現できますね。

ためしに、与えられたキーワードと google の2つで検索するようにしてみました。

Index: mac/WebView/WebViewPrivate.h
===================================================================
--- mac/WebView/WebViewPrivate.h        (revision 32849)
+++ mac/WebView/WebViewPrivate.h        (working copy)
@@ -106,6 +106,9 @@
  */
 - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection;
 
+- (BOOL)_safari_searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection;
+
+
 - (void)setMainFrameDocumentReady:(BOOL)mainFrameDocumentReady;
 - (void)setTabKeyCyclesThroughElements:(BOOL)cyclesElements;
Index: mac/WebView/WebView.mm
===================================================================
--- mac/WebView/WebView.mm      (revision 32849)
+++ mac/WebView/WebView.mm      (working copy)
@@ -3002,6 +3002,14 @@
 
 - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection
 {
+  BOOL ret1 = [self _safari_searchFor:@"google" direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:startInSelection];
+  BOOL ret2 = [self _safari_searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:startInSelection];
+
+  return ret1 and ret2;
+}
+
+- (BOOL)_safari_searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection
+{
  if (_private->closed)
    return NO;
 
@@ -3247,24 +3255,27 @@
 
 - (NSUInteger)markAllMatchesForText:(NSString *)string caseSensitive:(BOOL)caseFlag highlight:(BOOL)highlight limit:(NSUInteger)limit
 {
-    WebFrame *frame = [self mainFrame];+    NSString *strs[2] = {@"google", string};
     unsigned matchCount = 0;
-    do {
-        id <WebDocumentView> view = [[frame frameView] documentView];
-        if ([view conformsToProtocol:@protocol(WebMultipleTextMatches)]) {
-            [(NSView <WebMultipleTextMatches>*)view  setMarkedTextMatchesAreHighlighted:highlight];
-            ASSERT(limit == 0 || matchCount < limit);
-            matchCount += [(NSView <WebMultipleTextMatches>*)view markAllMatchesForText:string caseSensitive:caseFlag limit:limit == 0 ? 0 : limit - matchCount];
 
-            // Stop looking if we've reached the limit. A limit of 0 means no limit.
-            if (limit > 0 && matchCount >= limit)
-                break;
-        }
-        
-        frame = incrementFrame(frame, YES, NO);
-    } while (frame);
-
+    for (int i(0); i<2; i++) {
+        WebFrame *frame = [self mainFrame];
+        do {
+            id <WebDocumentView> view = [[frame frameView] documentView];
+            if ([view conformsToProtocol:@protocol(WebMultipleTextMatches)]) {
+                [(NSView <WebMultipleTextMatches>*)view  setMarkedTextMatchesAreHighlighted:highlight];
+                ASSERT(limit == 0 || matchCount < limit);
+                matchCount += [(NSView <WebMultipleTextMatches>*)view markAllMatchesForText:strs[i] caseSensitive:caseFlag limit:limit == 0 ? 0 : limit - matchCount];
+                
+                // Stop looking if we've reached the limit. A limit of 0 means no limit.
+                if (limit > 0 && matchCount >= limit)
+                    break;
+            }
+            
+            frame = incrementFrame(frame, YES, NO);
+        } while (frame);
+    }
     return matchCount;
 }

じっさいに、マッチしたキーワードをハイライト表示させているのは markAllMatchesForText というメソッドのようだったので、 こちらにも複数キーワード対応するためのハックを仕込んでおきます。

実行結果

result

複数キーワードがハイライト表示されました。やりましたね :) ただし、「次の候補へ」のようにして、次のマッチ文字列に飛ぼうとしても、同じキーワード間でしかジャンプできませんでした。 ><

原因と対策

そもそも、キーワードにマッチした元のテキストやマッチした結果はどこに保存されているのか。 これを調べていくと WebCore の中に入っていくことになります。 WebCoreはKHTML由来のコードで、C++で書かれているライブラリです。 となると、Objective-C みたいに実行時に柔軟なメソッド差し替えはできなくなってしまいます。 今回はSIMBLプラグインの習作が目的だったので、深追いはここまで。

本気でやるならば、C/Migemoと鬼車をWebCoreにリンクして、マッチしたテキストの範囲などを複数キーワードに対応させればよいでしょう。

まとめ

あと少しだったんだけどなあ…

手駒

  • Migemoの実装には、C/Migemoをつかいます。
  • Migemoの出力は正規表現なので、鬼車のCocoa版であるOgreKitをつかいます。
  • Safariをハックするには、WebKitをデバッグビルドするのが便利です。
  • Cocoaアプリを拡張するには、SIMBLプラグインをつくるのが定石です。

夏ライオンにメッセージフィルタリングをつけるハック

@akrさんがつくっている、夏ライオンというTwitterクライアントに、表示されるメッセージを特定の条件でフィルタリングするハックをしてみました。

何ができるか

特定のユーザのメッセージだけを表示したり、

ある言葉が含まれるメッセージだけを表示したりできます。

キモ

  • どこから手をつけるか
  • NSPredicate をつかう

長くなるので、続きは以下で。

Continue reading

Xacti DMX-HD1000

Xacti DMX-HD1000 (黒) を買いました。 Full HD (1080i, 1920×1080 60fps) の H.264/AVC でSDHCカードに撮影でき、重さが280gしかないという優れものです。 ここではMacといっしょに使うという観点で、二日使った感想を書いてみます。

環境:

  • MacBook (2GHz Core 2 Duo, 2GB RAM)
  • Mac OS X 10.5.2
  • iLife ‘06

単なるMP4ファイル

ひとつの撮影は、単にひとつのMP4ファイルとしてSDHCカードに保存されています。 カードリーダーをMacにつなげば、ふつうのファイルコピーだけど映像の取り込みが完了する、というのは、これまで Firewire でDVカメラから映像を取り込んでいたぼくにとっては革命的な手軽さでした。

テープというシーケンシャルアクセスから、ランダムアクセスできるSDカードにメディアが変わったことのインパクトはすごいものです (他の例: FDD→HDD、VHS→HDDレコーダ、などなど)。 他にもまだ、シーケンシャルアクセスでしか使えないものがあれば、そこにはイノベーションのチャンスがある、ということですね。

QuickTimeでの再生

このMP4ファイルはQuickTime Playerで簡単に再生できることもポイントですね。 ただし、Full HD のMP4ファイルはそのままでは再生することができなくて、avc1DecoderというものをインストールするとOKでした。

Full HD のMP4ファイルの再生はさすがにCPUパワーを酷使しますが、再生時にだいたい25〜29fpsは出ていました。ギリギリ再生が追いついている、というところですね。

iMovie で編集

Full HDのMP4ファイルをそのままiMovieに放り込むと、音は聞こえど画像は出ない、という状態になります。価格.comにある情報 に従い、先頭フレームだけをQuickTimeでカットし (先頭でCmd-XすればOK) 、参照ムービーとして保存したものをiMovieでimportすると、ちゃんと編集できました。

iMovieのプロジェクトは、新規作成するときに HD-1080i-30 の形式を選んでおくと、HDの編集ができます。これに最初気づかずにはまりました。

Webカムとして使う

Xacti DMX-1000は、Webカムとしても使えると説明書にあります。 ただし、Windowsでしか使えないよと書かれており、ほんとかいよとMacにUSBでつないでXactiの設定をしてみると、ふつうにQuickTimeの録画ソースとしてXactiを使うことができました。

まとめ

買う前は、ほんとにMacでもちゃんと使えるのか不安でしたが、ちゃんと使えました! 実売で6万円を切っているし、Full HD なビデオカメラが欲しいMacユーザに、とってもおすすめです。


SanDisk UltraII SDHC 8GB SDSDRH-8192-903
サンディスク (2007-10-05)
売り上げランキング: 1509

TimeCapsuleをマウントする

TimeCapsule - Mac mini

TimeCapsuleを買いました。1TBです。 我が家の3台のMacを、TimeMachineで自動バックアップさせてます。 自動バックアップがいかにストレスを減らしてくれるものかは、体験してみないと分からないものですね。

で、いくら3台あるとはいえ、バックアップだけに1TBも使いきりません。 そこで、ふつうのNASとして使おうとAFPでマウントしてみます。 TimeCapsuleはFinderに見えているので、ふつうにマウントできます。

そこでファイルをちょこちょことコピーしてみたところ、パーミッションが変わってしまうことに気づきました。

-rw-r--r--   1 moto  www       10240  3  5 11:28 test.tar
  ↓
-rwxrwxrwx   1 moto  staff       10240  3  5 11:28 test.tar

みたいになります。File modeが0777、グループがマウントしているユーザのグループに、といった具合。

ふつうに使うぶんには困らないのですが、UNIX的な使い方をしている人は注意したほうがいいですね。 回避方法をご存知の方はぜひコメントください。

QSTwitter 1.4

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を実現しました。

コード

CodeRepos : TwitterPlugin

以前のバージョン

  • 1.3 : 2008.02.22
  • 1.2 : 2008.01.23
  • 1.1 : 2007.12.22

関連エントリ

QSTwitter 1.3

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がインストールされた場所にキャッシュしています。 なんという手抜き。

コード

CodeRepos : TwitterPlugin

以前のバージョン

  • 1.2 : 2008.01.23
  • 1.1 : 2007.12.22

関連エントリ

LeopardでのQuartzComposerカスタムプラグインづくり

Ruby会議2008CFPとして、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のCommandLineToolFreeFrameHostを調べてみると、どうやらQuartzComposerのインスペクタから受け取るイベントのハンドラで、これらのメソッドを呼ぶようにしています。なるほど。

インストール場所が変わっている

これまでは、/Library/Graphics/Patch にプラグインを置くのが流儀だったのですが、Leopardからは/Library/Graphics/Quartz\ Composer\ Plug-Ins/ になりました(~/Library以下でもOK)。


カスタムプラグインをつくるという点から、Leopardになって気づいたことをまとめました。次はいよいよ、RubyCocoaでカスタムプラグインづくりです。

関連エントリ

もっとRubyCocoaでQuicksilverプラグインを書く

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に、actionSelectoract:に指定しておけば、ちゃんとactが呼ばれます。

疑問

  • OSX.require_framework とか使わないで、QSActionProviderとかのQS frameworkが使えてるのはなぜ?
  • hisaさんは、この方法だとCPU usageが高くなると懸念されているのですが、僕のところではいまひとつ高くなってるように見えません。why?

まとめ

RubyCocoaで、Quicsilverプラグインを書く方法をまとめました。 これで、あとはRubyのクラスとInfo.plistを書くだけでどんどんプラグインが書けますね。みんながんばれ!

あと、自分でもイマイチだなーと思うことでも、何かしら書いて晒すことが大事なんだなと。そんで改善していければOKなんだ。 hisaさん、ありがとうございました。

Write a Quicksilver plugin with RubyCocoa

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

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さんにコメントいただき (!)、もっとよい方法を教えてもらいました。 こちらについても、まとめて別エントリにします。

Home > Tags > mac