ページ

2008年6月20日金曜日

SimpleCap (7) メニューキャプチャ組み込み

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

SimpleCap へ今までのメニューキャプチャの検証結果を反映した。"Menu" で機能が利用できる。もちろんこのキャプチャも SimpleCap で行った。


今はまだソースレベルだが、メニューバーを含まないモードも用意した。



Spotlightもこんな感じ。

2008年6月19日木曜日

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

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

griffin-stewie さんより、Dockのメニューがあるとの指摘を受けた。早速 WindowListを調査してみる。

WindowListのタイマーを仕掛けてDockのメニューを開いておく。


出た。layer=101で普通のメニューであることがわかる。特徴としては Owner nameが"Dock"になっていること。



前回のサンプルでキャプチャしてみる。案の定、メニューバーが入っている。



処理としてはコンテキストメニューと同じなので、OwnerName=="Dock"を利用してフラグを立てた後、処理を合流させてやる。
AppController.m

 // (2) search normal menus
for (i=0; i < CFArrayGetCount(window_list); i++) {
   :
NSString *owner_name = (NSString*)CFDictionaryGetValue(window, kCGWindowOwnerName);
if ([owner_name isEqualToString:@"Dock"]) {
is_dockmenu = YES;
}
   :
// (3) calcurate rect
if (is_contextmenu || is_dockmenu) {
rect_all = CGRectNull;
   :



さて再チャレンジしてみよう。

できた。


ソース:MenuCapture-08.zip

- - - -
今度こそメニューが制覇できただろうか。

メニューのキャプチャ(その11)タイトルバーのメニュー

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

メニューがまだあった。タイトルバーでコマンドキーを押すと出てくるやつ。


前回までのサンプルでキャプチャすると無関係なメニューバーまで入ってしまう。


WindowList で見ると普通のメニューそのもの(一番上のもの)


これはどちらかというとウィンドウキャプチャのオプションとして捉えるべきかもしれない。
今回は対応せずウィンドウキャプチャのところで考えよう。

2008年6月18日水曜日

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

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

前回まででメニューキャプチャも完成と思いきや Spotlight のキャプチャがうまくできない。またもや WindowList で原因を調査してみよう。

下図のように Spotlight を使っている状態でウィンドウ情報を取って見る。


Spotlight は2つ現れていて、表の中の一番上のウィンドウがメニューバーのアイコンを表している。表の下から2番目の Spotlightが結果表示のウィンドウを表している。これを見ると Layerは23で通常のメニュー(kCGPopUpMenuWindowLevel=101)と異なっている。


なるほど。だから今までのコードではうまくキャプチャできないのか。Layer=23がヘッダファイルに無いか調べてみる。
CGWindowLevel.h

#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 */

#define kCGAssistiveTechHighWindowLevel CGWindowLevelForKey(kCGAssistiveTechHighWindowLevelKey) /* 1500 */


あ、23が見当たらない。。

まあいいか。定義は無いが Layer=23を狙い撃ちしてみよう。下記コードを追加する。
   } else if (layer == 23) {
[menu_windows addObject:[NSNumber numberWithInt:window_id]];
CGRectMakeWithDictionaryRepresentation(CFDictionaryGetValue(window, kCGWindowBounds), &rect);
if (CGRectEqualToRect(rect_all,CGRectZero)) {
rect_all = rect;
} else {
rect_all = CGRectUnion(rect, rect_all);
}
is_spotlight = YES;
}

layer=23 の場合もキャプチャ対象とした上でフラグ is_spotlight を立てておく。

その上でその後のステータスバーの処理へ合流させる。
  } else if (is_status_bar || is_spotlight) {
        :



これだけでOK。結果はこんな感じ。


ソース:MenuCapture-07.zip
- - - -
これでメニューは制覇できただろうか。

2008年6月17日火曜日

メニューのキャプチャ(その9)ステータスバー

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

前回までのサンプルでステータスバーのメニューをキャプチャするとメニュー全体が含まれてしまう。


この場合は右上のステータスバーだけをキャプチャに含めたい。さてどうするか。

下図のようにステータスバーのメニューを出した状態で WindowListを調べてみた。



上2つが開いているメニューとサブメニューを表す。ステータスバーは Layer=25 ( kCGStatusWindowLevel ) で表される、ここでは4つのウィンドウが該当する。上から順番に次のようになる(名称は OwnerName)。

 SystemUIServer ステータスバーのアイコン群を表す(図では Spaces から「湖」※ユーザ名までが範囲)
 SimpleCap  これはステータスバー左のカメラのアイコンを表す
 Spotlight   これは一番右の Spotlightアイコンを表す
 SystemUIServer  最後のものは透明なウィンドウでキャプチャには無関係。 

開いているウィンドウがステータスバーに所属するかどうかを判断する材料がないか、WindowListをじっと眺めて共通項を探してみる。

すると OwnerPIDが使えそうなのがわかった。今回の場合、メニュー2つと、ステータスバー(SystemUIServer)の OwnerPID が一致しているのがわかる。これを判断に使ってみよう。

で、結果から先に話すとうまくいった。こんな感じ。


悪くない。

サンプル:MenuCapture-06.zip

コードが長いので今回は細かい解説を割愛するが、アルゴリズムは次のようになっている。番号はソースコード上のコメントに対応している。

 (1) ステータスーバーのウィンドウ情報を最初に専用の配列へ保管しておく。
 (2) メニューウィンドウをすべて配列に保管する。
   この時、(1)のウィンドウと OnwerPIDが同じものがあるかどうか調べておく。
 (3) 次の3通りのケースについてキャプチャ範囲を調整する
   [a] Context Menu
   [b] ステータスバーのメニュー
   [c] 通常のメニュー
 (4) (3)のサイズがスクリーンをはみ出した場合、スクリーンに収まるように補正する

 最後にキャプチャ画像を作成する。

アルゴリズムが泥臭いせいもあってコードはわかりづらいかもしれない。



念のため通常メニューの動作確認。問題なし。


コンテキストメニューも大丈夫。



- - - -
それにしても WindowListは役に立つ。これはこれでちゃんとしたツールに仕上げておきたいな。

2008年6月16日月曜日

メニューのキャプチャ(その8)メニューバーを含める(2)

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

前回メニューバーを入れるようにしたがコンテキストメニューのキャプチャでも入るようになってしまった。コンテキストメニューの場合はメニューバーを入れないようにする。


コンテキストメニューかどうかはウィンドウ名でしか判別できないようなのでこれで判断する。ちょっと泥臭いが手を加える。
AppController.m

  int menubar_window_id;

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) {
NSString *window_name = (NSString*)CFDictionaryGetValue(w, kCGWindowName);
if ([window_name isEqualToString:@"ContextMenu"]) {
is_contextmenu = YES;
}
CGRectMakeWithDictionaryRepresentation(CFDictionaryGetValue(w, kCGWindowBounds), &rect);
if (CGRectEqualToRect(rect_all,CGRectZero)) {
rect_all = rect;
} else {
rect_all = CGRectUnion(rect, rect_all);
}
windowIDs[widx++] = window_id;
} else if (layer == kCGMainMenuWindowLevel) {
menubar_window_id = window_id;
}
}
if (!is_contextmenu) {
windowIDs[widx++] = menubar_window_id;
rect_all = CGRectUnion(CGRectZero, rect_all);
rect_all.size.width += 20;
rect_all.size.height += 25;
} else {
rect_all = CGRectNull;
}


これでOK。


ソース:MenuCapture-05.zip

2008年6月15日日曜日

メニューのキャプチャ(その7)メニューバーを含める

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

メニューのキャプチャはできたが、画面一番上のメニューバー部分が入っていない。せっかくなのでメニューバーもキャプチャ画像へ入れたい。やてみよう。


まずメニューバーがどんなウィンドウかを WindowList で調べる。


この中の "Shared Menubar" が目的のウィンドウのようだ。layer=24を CGWindowLevel.h で調べると定義が見つかった。
CGWindowLevel.h

#define kCGMainMenuWindowLevel  CGWindowLevelForKey(kCGMainMenuWindowLevelKey) /* 24 */


これを狙い撃ちしてキャプチャ画像へ入れてみる。前回のコードに kCGMainMenuWindowLevel のケースも window_id に含めるコードを加える。
AppController.m
 if (layer == kCGMainMenuWindowLevel) {
windowIDs[widx++] = window_id;
}


ただメニューバーはスクリーンと同じ横幅を持っているので大きすぎる。できれば目的のメニューの幅だけ切り取れるようにしたい。そこでメニューの大きさを取っておく。サブメニューを含めることを考えてメニューの大きさを CGRectUnion で加えてすべて含む領域を作成する。

CGRect rect = CGRectZero;
CGRect rect_all = CGRectZero;
:
if (layer == kCGPopUpMenuWindowLevel) {
CGRectMakeWithDictionaryRepresentation(CFDictionaryGetValue(w, kCGWindowBounds), &rect);
rect_all = CGRectUnion(rect, rect_all);
windowIDs[widx++] = window_id;
}


初期値を CGRectZero にしておくとうまい具合にメニューバーも含まれる。

結果はこう。うまくいった。


深いサブメニューもこの通り。



なお kCGWindowBounds で取得できる CGRect はメニューの影を含んでいない。この為、サイズを最後に補正してある。
rect_all.size.width += 20;
rect_all.size.height += 25;

安易な方法だが実用上はこれで十分だろう。

ソース:MenuCapture-04.zip