2006年 11月 04日

Ruby/Cocoa でステータスアイテムを追加してみる

メニューバーの右のほうのアイコンとかのことをステータスアイテムというらしい。Windows でいうところのタスクトレイ?

require 'osx/cocoa'

include OSX

class Test < NSObject

	def applicationDidFinishLaunching(aNotification)
		menu = NSMenu.alloc.initWithTitle "TEST"
		menu.setDelegate self
		menu.setAutoenablesItems false

		item = menu.addItemWithTitle "TEST", :action, "menuclicked_", :keyEquivalent, ""
		item.setEnabled true

		# new image and resize
		img = NSWorkspace.sharedWorkspace.iconForFileType "unknown"
		size = NSSize.new
		size.width = 16
		size.height = 16
		img.setSize size

		# create status item
		bar = NSStatusBar.systemStatusBar
		item = bar.statusItemWithLength -1
		item.setTitle "Test"
		item.setToolTip "TESTEST"
		item.setImage img
		item.setHighlightMode true
		item.setMenu menu
	end

	def menuclicked(sender)
		p sender
		true
	end
end


app = NSApplication.sharedApplication
app.setDelegate(Test.alloc.init)
p app

trap('SIGINT') { exit }
app.run

ちょーかんたん!!

Ruby/Cocoa menu

growlremote で飛んで来たやつに URL があったらメニューに追加していくのを作ろうと思ったんだけど、どうも DRb と app.run を組み合わせると BUS Error で落ちる。
delegate 版の growlremote も不定期に落ちるのでどうしよもないなぁ(Growl.rb に依存しているほう使ってる)。うーん

openmenu


http://lab.lowreal.net/trac/browser/c/mac/openmenu.rb
仕方ないのでプロセス分離した。なのでこれは単にあるディレクトリ以下のファイルを見てメニューを作り、クリックするとそのファイルを開くというだけのもの。
ただし古いファイルは表示せず、無言でファイルを削除する。こわいね!

でもって

./growlserver.rb -c ~/openmenu

で growlremote を起動しておくと webloc を作りまくるのでいい感じ


ポイントは、setMenu で直接メニューを表示させないで、アイテムのクリックを拾ってから popUpStatusItemMenu でメニューを表示させるところかしら。こうするとメニューを表示するまえに処理を挟めるので動的にメニューを表示させられる。AirMac のメニューみたいな。

2006年 11月 03日

Creammonkey いぢりで Cocoa Webkit をまなぶ

--- /Volumes/Creammonkey/Source/CMController.m	2006-07-05 23:28:50.000000000 +0900
+++ CMController.m	2006-11-03 02:49:19.000000000 +0900
@@ -8,6 +8,13 @@
 #import "CMUserScript.h"
 
 @implementation CMController
++ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector { return NO; }
+- (NSString*) helloHackWebkit:(NSString*) arg
+{
+	return [@"Hello" stringByAppendingString: arg];
+}
+
+
 - (NSArray*) scripts
 {
 	return scripts_;
@@ -233,12 +240,18 @@
 		[webView stringByEvaluatingJavaScriptFromString: @"document.body.__creammonkeyed__ = true;"];
     }
 	
+	if (isDebug) [self reloadUserScripts: nil];
+		
+	WebScriptObject* scriptObject= [webView windowScriptObject];
+	[scriptObject setValue:self forKey:@"__creammonkey_obj"];
+	
     // Eval!
 	NSArray* ary = [self matchedScripts: url];
 	int i;
 	for (i = 0; i < [ary count]; i++) {
         [webView stringByEvaluatingJavaScriptFromString: [ary objectAtIndex: i]];
 	}
+	[scriptObject setValue:nil forKey:@"__creammonkey_obj"];
 }
 
 #pragma mark Action
@@ -252,6 +265,13 @@
     [self saveScriptsConfig];
 }
 
+- (IBAction) toggleDebug: (id) sender
+{
+	isDebug = !isDebug;
+	[sender setState: isDebug ? NSOnState : NSOffState];
+
+}
+
 - (IBAction) uninstallSelected: (id) sender
 {
 	CMUserScript* script = [[scriptsController selectedObjects] objectAtIndex: 0];
@@ -305,6 +325,8 @@
 	scripts_ = nil;    
     targetPages_ = [[NSMutableSet alloc] init];
 	
+	isDebug = FALSE;
+	
 	[NSBundle loadNibNamed: @"Menu.nib" owner: self];
 	
 	return self;

Objctive-C 側のメソッドを JS 側からよんでみる。うえのように書き換えた上で

alert(__creammonkey_obj.helloHackWebkit_("test"));

とかやると Hello test が alert される。

GM_xmlhttprequest を作りたくてどうやるか調べ途中。うえの書き換えだと、eval 後に使ったやつは nil をセットしているけど、これでちゃんと消えてくれるのか (サイト側からそれにアクセスできないのか) が気になる。試せよって感じだけど……

あと上のはファイル分けたりするのがめんどうくさいから self を渡しているけど、専用のクラス/インスタンスを作ってそれを渡すべき。例えば __creammonkey_obj.orderFrontAboutPanel_() とかやると確実に落ちる。

Objctive-C と JS とのデータのやりとりはてきとうにやってくれるっぽい。デフォルトのメソッド名変換は Ruby/Cocoa の正式名と同じ感じ。

2006年 11月 02日

Creammonkey のデバッグ改造版をつくった

[Creammonkey 0.8]( http://blog.8-p.info/articles/2006/12/03/creammonkey-0-8 ) がリリースされたため、そちらをご利用ください :)

Creammonkey という Safari で userjs を使えるようにするものがあるのですが、userjs を書いている時いちいちメニューから Reload All User Scripts をするのが面倒くさいので改造してみました。

MIT ライセンスらしいのでビルドしたやつを公開。なにぶん Xcode 使ったのも初めてなのでこれでいいのかよくわかりません。ソースを公開したいときとか diff とりたいときってどこまでやればいいのかわからない。

Creammonkey-Debug.zip Original Copyright (c) 2006 KATO Kazuyoshi

Debug というメニュー項目が追加されるので、それをチェックしておけばページリロードのたびに userjs もリロードされます。

Creammonkey Debug mode

diff -ur /Volumes/Creammonkey/Source/CMController.h Creammonkey/CMController.h
--- /Volumes/Creammonkey/Source/CMController.h	2006-03-22 01:07:05.000000000 +0900
+++ Creammonkey/CMController.h	2006-11-02 00:27:29.000000000 +0900
@@ -12,9 +12,12 @@
 	NSMutableArray* scripts_;
 	NSString* scriptDir_;
     NSMutableSet* targetPages_;
+	
+	BOOL isDebug;
 }
 
 - (IBAction) toggleScriptEnable: (id) sender;
+- (IBAction) toggleDebug: (id) sender;
 - (IBAction) uninstallSelected: (id) sender;
 - (IBAction) orderFrontAboutPanel: (id) sender;
 - (IBAction) reloadUserScripts: (id) sender;
diff -ur /Volumes/Creammonkey/Source/CMController.m Creammonkey/CMController.m
--- /Volumes/Creammonkey/Source/CMController.m	2006-07-05 23:28:50.000000000 +0900
+++ Creammonkey/CMController.m	2006-11-02 00:34:43.000000000 +0900
@@ -233,6 +233,9 @@
 		[webView stringByEvaluatingJavaScriptFromString: @"document.body.__creammonkeyed__ = true;"];
     }
 	
+	if (isDebug)
+		[self reloadUserScripts: nil];
+	
     // Eval!
 	NSArray* ary = [self matchedScripts: url];
 	int i;
@@ -248,8 +251,14 @@
     
     [script setEnabled: [sender state] != NSOnState];
     [sender setState: [script isEnabled] ? NSOnState : NSOffState];
-    
-    [self saveScriptsConfig];
+}
+
+- (IBAction) toggleDebug: (id) sender
+{
+	isDebug = !isDebug;
+	[sender setState: isDebug ? NSOnState : NSOffState];
+	
+	[self saveScriptsConfig];
 }
 
 - (IBAction) uninstallSelected: (id) sender
@@ -305,6 +314,8 @@
 	scripts_ = nil;    
     targetPages_ = [[NSMutableSet alloc] init];
 	
+	isDebug = FALSE;
+	
 	[NSBundle loadNibNamed: @"Menu.nib" owner: self];
 	
 	return self;
diff -ur /Volumes/Creammonkey/Source/English.lproj/Menu.nib/classes.nib Creammonkey/English.lproj/Menu.nib/classes.nib
--- /Volumes/Creammonkey/Source/English.lproj/Menu.nib/classes.nib	2006-03-22 01:07:18.000000000 +0900
+++ Creammonkey/English.lproj/Menu.nib/classes.nib	2006-11-02 00:38:41.000000000 +0900
@@ -4,6 +4,7 @@
             ACTIONS = {
                 orderFrontAboutPanel = id; 
                 reloadUserScripts = id; 
+                toggleDebug = id; 
                 toggleScriptEnable = id; 
                 uninstallSelected = id; 
             }; 
@@ -12,7 +13,13 @@
             OUTLETS = {menu = NSMenu; scriptsController = NSArrayController; }; 
             SUPERCLASS = NSObject; 
         }, 
-        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }
+        {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, 
+        {
+            ACTIONS = {toggleDebug = id; }; 
+            CLASS = NSArrayController; 
+            LANGUAGE = ObjC; 
+            SUPERCLASS = NSObjectController; 
+        }
     ); 
     IBVersion = 1; 
 }
¥ No newline at end of file

diff 取りにくいなぁ……このパッチあててもだめかも。手元では動いているけど……

やった変更:

  • Interface Builder で Debug 項目を作成
  • FileOwner の Action に toggleDebug: を追加
  • その二つを connect
  • CMController.{h,m} を編集

これでメニューの Debug にチェックがついているときのみ毎回リロードするようになる。別の言語ファイルとかよくわからんので放置した。

http://lowreal.net/blog/2006/11/02/1

objc メッセージ

そういえばメソッドチェインってできないなぁ

カーソル位置の色取得 2

http://subtech.g.hatena.ne.jp/cho45/20061030/1162142940
は遅くてアレなので書き直した
http://lab.lowreal.net/trac/browser/c/mac/cpmousecolor.m

コンパイル済 (Intel)み
http://svn.lab.lowreal.net/lowreal/c/mac/cpmousecolor

CGDisplayAddressForPosition で座標位置の色のアドレスを取得して、unsigned int にキャスト。AARRGGBB になっているので、あとは AA を消して # をくっつける文字列処理

Universal Binary にしようと思ったけど、エンディアンの違いでおかしいのでコードも変えないとだめみたいだ。

CGDisplayBitsPerPixel 使ってちゃんと色数とったほうがいい。32bit 限定

DAAP via SSH

Leopard では dns-sd コマンドをつかってできます: http://subtech.g.hatena.ne.jp/cho45/20090406/1239021992

http://subtech.g.hatena.ne.jp/cho45/20061027/1161910002
で mDNSProxyResponderPosix がどこにあるのかわからないと書いたけど http://developer.apple.com/networking/bonjour/download/ にあった。

Bonjour Source Code v107.6 をダウンロードして以下のことをした

cd mDNSResponder-107.6/mDNSPosix
make os=tiger
cd build/prod
cp mDNSProxyResponderPosix ~/bin
mDNSProxyResponderPosix 127.0.0.1 squeal "shareName" _daap._tcp. 3689 

はてなサービスを超高速移動するブックマークレット

javascript:location='http://%s.hatena.ne.jp/'+location.pathname.split("/")[1]+'/';