タスクスイッチャまで
起動中のアプリをアイコンボタン風に表示して、そこからタスクを切り替える、というところまでは簡単にできる。起動中のアプリを保存しておくデータ用のクラスは現在こんな感じ。
@interface SPApp : NSObject { NSString* _name; NSImage* _icon; pid_t _pid; } -(id)initWithRunningApp:(NSRunningApplication*)aApp; @property(readonly) NSString* _name; @property(readonly) NSImage* _icon; @property(readonly) pid_t _pid; @end荻原本によるとインスタンス変数にアンダースコアつけるのはやめよ、ということなんだけど・・・。
で、このクラスにNSRunnnigApplicationのインスタンスからデータをコピーして、表示用のViewに渡す。
-(void)setRunningApp{ NSWorkspace *ws=[NSWorkspace sharedWorkspace]; NSArray* ra=[ws runningApplications]; for(NSRunningApplication* app in ra){ if([app activationPolicy]==NSApplicationActivationPolicyRegular){ NSRect viewFrame=NSMakeRect(0, 0, 10,10); SPApp *sp=[[SPApp alloc] initWithRunningApp:app]; MyView *buttonView=[[MyView alloc] initWithFrame:viewFrame runApp:sp]; [view addSubview:buttonView]; [buttonView release]; [sp release]; } } }クラス名がMyViewなのはご愛嬌。(^^;)
このままだとViewのrectが(0,0,10,10)なんだけど、これはViewの中でサイズを定義しなおしている。その辺はごちゃごちゃしたコードになっているので省略。
で、このViewのmouseUpあたりでこんなふうに書いてタスクスイッチャ機能は完成。
-(void)mouseUp:(NSEvent *)theEvent{ [self set_state:BUTTON_STATE_OVER]; [self setNeedsDisplay:YES]; NSRunningApplication *selectApp=[NSRunningApplication runningApplicationWithProcessIdentifier:[_app _pid]]; [selectApp activateWithOptions:NSApplicationActivateIgnoringOtherApps]; }
で、さらに、どーせなら起動中のアプリが開いているウィンドウの一覧を表示して、ウィンドウを選択してタスクを切り替えるようにしたい、と考えた。
ここまでが前段。
開いているウィンドウの一覧
ユニバーサルアクセスを無効にしたら、「HyperDock」が使えなくなって、ちょっと難儀している。HyperDockはDock上にWinVISTA、WIN7のタスクバー風にウィンドウの一覧を表示するユーティリティ。これがあるとXcodeとInterfaceBuilderでたくさんウィンドウが開いても、すぐに目的のウィンドウをアクティブにできる。HyperDockが使えなくなったのでExposeを使うようになったけれど、全画面を使って「ウィンドウの一覧」を表示するため、アプリの選択をまちがったら、やり直す作業がちょっと煩雑。
そこでHyperDockもどきの機能をつけようとしてみた。
まずはウィンドウの一覧を得る。
あとになってxcatsan師匠が同じことを書いているのに気づいたんだけど、一応、半分くらいは自分でこの方法を考えた。
CGWindowListCopyWindowInfoを使えば全アプリのウィンドウの一覧を表示できる。これはxcatsan師匠が便利なサンプル付きで解説してくれているので知っていた。
で、ウィンドウの親プロセスもPIDで得ることができるので、起動中のアプリのPIDと同じPIDをもっているウィンドウがそのアプリのもの、ということになる。
-(void)rightMouseDown:(NSEvent *)theEvent{ CFStringRef ownerPid; CFIndex i; CFDictionaryRef w; CFStringRef winName; CGRect rect; CGWindowID spot_window_id; CFArrayRef list =CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID); NSMenu *menu = [[[NSMenu alloc] initWithTitle:@"HELLO"] autorelease]; for (i=0; i < CFArrayGetCount(list); i++) { w = CFArrayGetValueAtIndex(list, i); if (CFDictionaryContainsKey(w, kCGWindowWorkspace)) { ownerPid=CFDictionaryGetValue(w, kCGWindowOwnerPID); if ([(NSString*)ownerPid intValue] ==[_app _pid] ) { winName=CFDictionaryGetValue(w, kCGWindowName); if((NSString *)winName !=@""){ CGRectMakeWithDictionaryRepresentation(CFDictionaryGetValue(w, kCGWindowBounds), &rect); CFNumberGetValue(CFDictionaryGetValue(w, kCGWindowNumber),kCGWindowIDCFNumberType, &spot_window_id);本当に全てのウィンドウの一覧なので、
if (CFDictionaryContainsKey(w, kCGWindowWorkspace)) {この条件を挟んで「Workspace上にウィンドウとして表示されている」ものに限定する。で、同じPIDならウィンドウのタイトルと(winName)、表示領域(rect)と、windowID(spot_window_id)を取得する。 で、とりあえずウィンドウを画像として表示してみる実験をした。
CGImageRef cgimage = CGWindowListCreateImage(rect,kCGWindowListOptionIncludingWindow , spot_window_id, kCGWindowImageDefault); NSSize imageSize=NSMakeSize((int)rect.size.width*0.25, (int)rect.size.height*0.25); NSImage* newImage=[[[NSImage alloc ]initWithCGImage:cgimage size:imageSize] autorelease]; [newImage drawAtPoint:NSMakePoint(0, 0) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];CGWindowListCreateImageでえたCGImageRefを使ってinitWithCGImagでNSImageを作っている。その時原寸の4分の1に縮小している。それをウィンドウの(0,0)で描画する。 表示されたときは飛び上がって喜びましたよ。こういう、グラフィックス系の操作をやったのはPalmOSでjpegとbitmapをちょっこと使おうとしたことがあるだけなので。 しかし・・・・残念ながらこれだと、ちがうSpaceにあるアプリのイメージは取得できない。CGWindowListCreateImageのオプションをいろいろいじってみたけれど、せいぜい「アクテイブなWorkspaceの、該当するrectを画像として生成する」ことしかできない。 あれじゃあ、SimpleCapはどういう動作をするんだったけ、と思って試してみたら、やはり非アクティブなWorkspaceに表示されているアプリのキャプチャはできなかった。xcatsan師匠に図々しく問い合せてみたら、これは初めからの仕様ということだった。
メニューにしてみる
画像をどうしても取得できないなら、とりあえずメニューにしておいてウィンドウをアクティブにできるか試す。
NSMenuItem *item=[[NSMenuItem alloc] init]; [item setTitle:(NSString *)winName]; [item setTarget:self]; [item setAction:@selector(select:)]; [item setTag:(NSUInteger) spot_window_id]; [menu addItem:item];もちろんこれはうまくいく。 しかし・・ここからわからない。どーしても「あるアプリのあるウィンドウ」にアクセスする方法がわからない。 要はDockの右クリックから出てくるメニュー、 これと同じことがしたいわけだ。メニューとして表示することまではできた。ではどうやってウィンドウを選択するのか。
結論:できない
普通の方法ではできないことがわかった(^^;)。CGSPrivateを使うのではないか、と当たりをつけて調べてみたら、
CGSSetUniversalOwnerがそれを実現する方法。ただし、これはDockをkillして、それから自分のConectionIDをDockの代わりに登録する、という関数。つまりDockの再発明をする、ということになる。
アホらしくて実際に試していない。それならDockを使えばいいわけだから。
0 件のコメント:
コメントを投稿