2011年1月23日日曜日

MacRuby・Spaces.appその6・デスクトップいっぱいの透明ウィンドウを作る

MacRubyを使用して自分で使いたいアプリを作っている。目的はただひとつ、

デスクトップでマウスホイールをころころしてSpacesのworkspaceを切り替える

こと。

これまでに
1,Spacesの切替を自作アプリの中から行う
2,workspaceが切り替わったことをSpaces.appに通知する

ところまでやってみた。初心者にしては上等じゃねえか、と自分で言ってみる。残りは

3,デスクトップのマウスイベントを取得する
4,ホイールの回転でworkspaceを切り替える

そこで本日は3、のデスクトップのマウスイベント。脳内方針としては

a,透明なデスクトップ大のウィンドウを非アクティブで表示する
b,そいつでマウスイベントをもらう

てな感じ。当然、ステータスバーにメニューを表示するアプリとなる。とは言え、ばかでかいウィンドウを1枚持っているだけのアプリなので、「常駐もの」で連想するような「システムに負荷をかける」ことにはならないはず。

本日はステータスバーにメニューを表示させ、透明な巨大ウィンドウ(iMac27inchなもんで)を作成するところまで進んだ。

「cocoa 透明ウィンドウ」でググルと

Cocoaはやっぱり! 出張版 3. ウィンドウの形状変更と透明化

このサイトが一番初めに出てくるのでとりあえずここに書いてある通りにやってみる。デベロッパードキュメントのサンプルの解説とリファクタリング、という感じで大変わかりやすいけれど、やはり情報が古いのは否めない。(2002年作成のページ)

一般的に透明ウィンドウの作成はNSWindowクラスのサブクラスを作ってinitWithContentRectでウィンドウのスタイルマスクを指定する、となっているけれど、The Invisible Designerさんのこのアーティクルによると、OSX10.6以降は

[window setStyleMask:0];

という便利なメソッドができたということで、サブクラスをつくらずControllerから簡単に透明化ができる。
def applicationDidFinishLaunching(note)
#ウィンドウの透明化
 my_window.setStyleMask(0 & NSNonactivatingPanelMask)
 my_window.setAlphaValue(1.0)
 my_window.setOpaque(false)
 my_window.setBackgroundColor(NSColor.clearColor)
 
#フルスクリーンにする
 main_screen = NSScreen.mainScreen
 fullscreen_frame =NSRect.new(main_screen.frame.origin,main_screen.frame.size)
 my_window.setFrame(fullscreen_frame,display:false)
        
#マウスイベントは拾えるようにする
 my_window.setIgnoresMouseEvents(false) 
        
#全部のworkspaceで表示する
 my_window.setCanBeVisibleOnAllSpaces(true)
        
#ここで初めてウィンドウを表示する 
 my_window.orderBack(self)

#これ以降はステータスバー関係
 status_bar = NSStatusBar.systemStatusBar
 status_item = status_bar.statusItemWithLength(NSVariableStatusItemLength)
 
 status_item.setMenu sbMenu
 status_item.setTitle "Spin"
 status_item.setToolTip "Spin"
 
end
たったこれだけ書くのに、今日の丸1日かかりましたよ。(^^;)楽しんだけど。InterfaceBuilderでWindowのVisible at Launchオプションを外して、あれこれ準備が整ってからorderBack(self)とする方法は前記The Invisible Designerさんのこのアーティクルによる。(それまでは黒い四角が表示されて首をひねりまくった)

しかし実はこのまま実行すると、「透明なウィンドウがデスクトップを覆い尽くして他のアプリが選択できない」状態となる。setIgnoresMouseEvents(false) としているため。でもこれを書かないとマウスイベントが拾えない。info.plistにBGOnlyを記入してもだめ。

夕方からずーっとこれに悩んで、解決したのがもう寝酒を飲もうかという9時でございました。素人はねー。

結局、いつものxcatsan師匠のこのページで問題解決のヒントをもらった。

画面キャプチャその1 - 画面全体を黒くする

解説されているソースの中に、

[_fullscreen_window setLevel:NSScreenSaverWindowLevel + 1];
とあって、ははあ、ウィンドウの表示にもレベルってのがあるのか、と知る。で、さっそく「my_window.setlevel(NSScreenSaverWindowLevel + 1)」と書いて実行してみてびっくり。後知恵で考えれば、これはほとんど一番上のレベルに表示しているんですね。その辺のところは

ザリガニが見ていた...。 マウスイベントを処理するCocoaアプリケーションにしてみる

を読んでやっと気づいた。じゃあ、普通のウィンドウより下のレベルに表示したらいいのかな、ということでデベロッパドキュメントを調べてみたら、こんな記述があった。

enum _CGCommonWindowLevelKey {
   kCGBaseWindowLevelKey = 0,
   kCGMinimumWindowLevelKey,
   kCGDesktopWindowLevelKey,
   kCGBackstopMenuLevelKey,
   kCGNormalWindowLevelKey,
   kCGFloatingWindowLevelKey,
   kCGTornOffMenuWindowLevelKey,
   kCGDockWindowLevelKey,
   kCGMainMenuWindowLevelKey,
   kCGStatusWindowLevelKey,
   kCGModalPanelWindowLevelKey,
   kCGPopUpMenuWindowLevelKey,
   kCGDraggingWindowLevelKey,
   kCGScreenSaverWindowLevelKey,
   kCGMaximumWindowLevelKey,
   kCGOverlayWindowLevelKey,
   kCGHelpWindowLevelKey,
   kCGUtilityWindowLevelKey,
   kCGDesktopIconWindowLevelKey,
   kCGCursorWindowLevelKey,
   kCGNumberOfWindowLevelKeys
};

NSNormalWindowLevelはkCGNormalWindowLevelなので、-2くらいしても大丈夫そう。というわけで試しに

my_window.setLevel(NSNormalWindowLevel - 1)
としたところ、考えていたとおりの動きとなった。つまり
1,自アプリ起動後に直前までアクティブだったウィンドウがアクティブになる
2,どのアプリをクリックしてもすぐアクティになる
3,デスクトップをクリックするとFinderがアクティブになる
4,デスクトップでマウスホイールを回すと、自アプリがそのイベントを受取る

よし、これであとはViewにSpacesを切り替えるメソッドを書くだけだ。
workspaceが切り替わった、というNotificationをスローするだけでworkspaceを切り替えることができる、ということが、昨日の一連の実験で判明したのでまずはその辺りから。視覚効果付きのworkspace切替は、Prefarenceを付けてからの実装となりそう。

0 件のコメント:

コメントを投稿