2011年2月15日火曜日

Cocoa ObjC タスクスイッチャもどき(2)アプリのウィンドウ一覧を得る

タスクスイッチャまで
起動中のアプリをアイコンボタン風に表示して、そこからタスクを切り替える、というところまでは簡単にできる。

起動中のアプリを保存しておくデータ用のクラスは現在こんな感じ。

@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 件のコメント:

コメントを投稿