タスクスイッチャまで
起動中のアプリをアイコンボタン風に表示して、そこからタスクを切り替える、というところまでは簡単にできる。
起動中のアプリを保存しておくデータ用のクラスは現在こんな感じ。
@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を使えばいいわけだから。