ページ

2008年6月14日土曜日

メニューのキャプチャ(その6)サブメニュー(2)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

サブメニューのキャプチャ画像を作ってみる。複数のウィンドウのキャプチャには CGWindowListCreateImageFromArray( ) が使える。使い方は以前画面キャプチャその18で紹介した。

主要なコードを下記へ示す。

AppController.m

  CGWindowID *windowIDs = calloc(20, sizeof(CGWindowID));

for (i=0; i < CFArrayGetCount(list); i++) {
w = CFArrayGetValueAtIndex(list, i);
CFNumberGetValue(CFDictionaryGetValue(w, kCGWindowLayer),
kCFNumberIntType, &layer);
CFNumberGetValue(CFDictionaryGetValue(w, kCGWindowNumber),
kCGWindowIDCFNumberType, &window_id);

if (layer == kCGPopUpMenuWindowLevel) {
windowIDs[widx++] = window_id;
}
}

if (widx > 0) {
NSString* path = [NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* filename = [path stringByAppendingPathComponent:@"cocoa_days_menu.png"];

CFArrayRef windowIDsArray = CFArrayCreate(kCFAllocatorDefault, (const void**)windowIDs, widx, NULL);
CGImageRef cgimage = CGWindowListCreateImageFromArray(CGRectNull, windowIDsArray, kCGWindowImageDefault);

NSBitmapImageRep *bitmap_rep = [[[NSBitmapImageRep alloc] initWithCGImage:cgimage] autorelease];
NSImage *image = [[[NSImage alloc] init] autorelease];
[image addRepresentation:bitmap_rep];

NSData* data = [bitmap_rep representationUsingType:NSPNGFileType
properties:[NSDictionary dictionary]];
[data writeToFile:filename atomically:YES];
}

free(windowIDs);


タイマー発火直後に layer==kCGPopUpMenuWindowLevel (101) のウィンドウを探し配列 windowIDs へ格納しておく。そしてこの配列を CFArrayRef に変換し、CGWindowListCreateImageFromArray ( )へ渡す。

実行結果。うまくいった。



3階層でもOK。


ソース:MenuCapture-03.zip

2008年6月13日金曜日

メニューのキャプチャ(その5)サブメニュー(1)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

サブメニュー(階層メニュー)の場合はどうなるのか。WindowListアプリを使って調べてみた。

下記のようにメニューを開き、タイマーを仕掛けてみる。


サブメニューもウィンドウの1つとして現れた。両方ともレイヤーは 101。Number(=WindowID)が違っているのがわかる。30611 の Dock はサブメニュー、30610 の Appleはメインメニューを表している。通常 WindowIDは順番に振られると思うので、これを使えば階層関係がわかる。



メニュー単体は前回のサンプルでキャプチャできた。では、階層化メニューを一つの画像にキャプチャするにはどうしたらよいだろうか。

2008年6月12日木曜日

メニューのキャプチャ(その4)コンテキストメニュー

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

コンテキストメニューはどうか。WindowListアプリを使って調べてみよう。

下のようにコンテキストメニューを開きタイマーを仕掛ける。


結果は次の通り。前回までのメニューと同じ。レイヤーは 101で、名前が "ContextMenu" になっている。


キャプチャしてみた(前回のサンプルを使用)。

2008年6月11日水曜日

メニューのキャプチャ(その3)画像作成

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

メニューのキャプチャ画像を作る。前回までのプログラムに CGWindowListCreateImage( )を加えてキャプチャ画像を作る。

これも以前の検証のコードを流用するとすぐできる。

AppController.m

   w = CFArrayGetValueAtIndex(list, i);
CFNumberGetValue(CFDictionaryGetValue(w, kCGWindowLayer),
kCFNumberIntType, &layer);
CFNumberGetValue(CFDictionaryGetValue(w, kCGWindowNumber),
kCGWindowIDCFNumberType, &window_id);

if (layer == kCGPopUpMenuWindowLevel) {
NSString* path = [NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* filename = [path stringByAppendingPathComponent:@"cocoa_days_menu.png"];

CGImageRef cgimage = CGWindowListCreateImage(CGRectZero, kCGWindowListOptionIncludingWindow, window_id, kCGWindowImageDefault);
NSBitmapImageRep *bitmap_rep = [[[NSBitmapImageRep alloc] initWithCGImage:cgimage] autorelease];
NSImage *image = [[[NSImage alloc] init] autorelease];
[image addRepresentation:bitmap_rep];

NSData* data = [bitmap_rep representationUsingType:NSPNGFileType
properties:[NSDictionary dictionary]];
[data writeToFile:filename atomically:YES];
}
}



実行後、タイマーを起動させ、メニューを開いて少し待つと cocoa_days_menu.png がデスクトップに作成される。



こんな感じ。



メニューのキャプチャも容易にできることがわかった。

ソース:MenuCapture-02.zip

2008年6月10日火曜日

メニューのキャプチャ(その2)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

実際にメニューのキャプチャをやってみよう。まずはタイマーと CGWindowListCopyWindowInfo( ) を使って目的のウィンドウを特定する。

サンプルアプリを用意した。ボタンを押すと5秒後にウィンドウの一覧を CGWindowListCopyWindowInfo( ) で取得し、kCGWindowLayer = kCGPopUpMenuWindowLevel (101) のものだけログへ出力する。


ソース:MenuCapture-01.zip

実行時の様子。


ウィンドウ情報が得られた。

kCGWindowAlpha = 1;
kCGWindowBounds = {
Height = 189;
Width = 219;
X = 45;
Y = 22;
};
kCGWindowIsOnscreen = 1;
kCGWindowLayer = 101;
kCGWindowMemoryUsage = 234136;
kCGWindowName = "\Uf8ff";
kCGWindowNumber = 112307;
kCGWindowOwnerName = MenuCapture;
kCGWindowOwnerPID = 36446;
kCGWindowSharingState = 1;
kCGWindowStoreType = 2;
}



ソースコードはこんな感じ。今までのものをそのまま流用しただけ。
AppController.m
- (void)fire:(NSTimer*)theTimer
{
static int t = 5;
if (t < 0) {
t = 5;
}
[_text setStringValue:[NSString stringWithFormat:@"%d", t]];
t--;
if (t < 0) {
[theTimer invalidate];
[_text setStringValue:[NSString stringWithFormat:@"%d", 0]];

UInt32 option = kCGWindowListOptionOnScreenOnly;
CFArrayRef list =CGWindowListCopyWindowInfo(option, kCGNullWindowID);
CFDictionaryRef w;
CFIndex i;
int layer;

for (i=0; i < CFArrayGetCount(list); i++) {
w = CFArrayGetValueAtIndex(list, i);
CFNumberGetValue(CFDictionaryGetValue(w, kCGWindowLayer),
kCFNumberIntType, &layer);

if (layer == kCGPopUpMenuWindowLevel) {
NSLog(@"%@", w);
}
}
}
}


- - - -
次回は得た情報を元にキャプチャ画像を作成する。

2008年6月9日月曜日

メニューのキャプチャ(その1)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

SimpleCapではメニューを簡単にキャプチャしたい。その為の調査を少しずつ行っていく。

まずはメニューがそのように扱われているのか、以前作成したウィンドウリストアプリ(WindowList)で調べてみる。WindowList は画面に表示されているウィンドウのリストを表示するもの。


この以前のプログラムにタイマー機能を持たせてメニューを開いている時のウィンドウリストを見てみよう。


タイマーを仕掛け、メニューを開いてみる。Finder のメニューを開いてみた。


すると WindowList に Layer=101 でメニューが現れた。OwnerNameから Finderのメニューであることがわかる。実際(画像の右側が切れているが)PIDは Finderと同じものを示している。Nameはアップルマークになっている。


この Layer=101 は NSWindowのレベルを示していると思われ、実際ヘッダファイルを見ると定義があった。
CGWindow.h

/* Definitions of older constant values as calls */
#define kCGBaseWindowLevel CGWindowLevelForKey(kCGBaseWindowLevelKey) /* INT32_MIN */
#define kCGMinimumWindowLevel CGWindowLevelForKey(kCGMinimumWindowLevelKey) /* (kCGBaseWindowLevel + 1) */
#define kCGDesktopWindowLevel CGWindowLevelForKey(kCGDesktopWindowLevelKey) /* kCGMinimumWindowLevel */
#define kCGDesktopIconWindowLevel CGWindowLevelForKey(kCGDesktopIconWindowLevelKey) /* kCGMinimumWindowLevel + 20 */
#define kCGBackstopMenuLevel CGWindowLevelForKey(kCGBackstopMenuLevelKey) /* -20 */
#define kCGNormalWindowLevel CGWindowLevelForKey(kCGNormalWindowLevelKey) /* 0 */
#define kCGFloatingWindowLevel CGWindowLevelForKey(kCGFloatingWindowLevelKey) /* 3 */
#define kCGTornOffMenuWindowLevel CGWindowLevelForKey(kCGTornOffMenuWindowLevelKey) /* 3 */
#define kCGDockWindowLevel CGWindowLevelForKey(kCGDockWindowLevelKey) /* 20 */
#define kCGMainMenuWindowLevel CGWindowLevelForKey(kCGMainMenuWindowLevelKey) /* 24 */
#define kCGStatusWindowLevel CGWindowLevelForKey(kCGStatusWindowLevelKey) /* 25 */
#define kCGModalPanelWindowLevel CGWindowLevelForKey(kCGModalPanelWindowLevelKey) /* 8 */
#define kCGPopUpMenuWindowLevel CGWindowLevelForKey(kCGPopUpMenuWindowLevelKey) /* 101 */
#define kCGDraggingWindowLevel CGWindowLevelForKey(kCGDraggingWindowLevelKey) /* 500 */
#define kCGScreenSaverWindowLevel CGWindowLevelForKey(kCGScreenSaverWindowLevelKey) /* 1000 */
#define kCGCursorWindowLevel CGWindowLevelForKey(kCGCursorWindowLevelKey) /* 2000 */
#define kCGOverlayWindowLevel CGWindowLevelForKey(kCGOverlayWindowLevelKey) /* 102 */
#define kCGHelpWindowLevel CGWindowLevelForKey(kCGHelpWindowLevelKey) /* 200 */
#define kCGUtilityWindowLevel CGWindowLevelForKey(kCGUtilityWindowLevelKey) /* 19 */


メニューのレベルは、kCGPopUpMenuWindowLevel (101) で定義されている。ということは Layer=101 (kCGPopUpMenuWindowLevel) を狙い撃ちすればメニューがキャプチャできるかもしれない。


ついでにコンテキストメニューも調べておこう。

タイマーを仕掛けコンテキストメニューを出して見る。


出た。Layer=101で名前は「ContextMenu」。



ソース:WindowList-5.zip

2008年6月8日日曜日

SimpleCap (6) 選択範囲の移動を軽く

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

普段は intel製の iMacで開発している。会社で使っている PowerBook G4 (PowerPC) で SimpleCap を使ってみたところ範囲選択の移動がちょっとモッサリしてた。そこで少しでも軽くする為に余計な描画を取る事にした。

通常は Knobとボタンが表示される。


移動中は下のようになる。