MacOSX で Window マネージャー的なことをやりたい using CGS

次。 w3mimg と sevil を作ったわけだけど、これらは割と似たようなことがやりたいのだったりする。つまり既に動いてるプロセスのウィンドウの情報が欲しい。 sevil の場合はウィンドウを書き換えたい、と。ちなみにこのへんから特にあやしいのでウソがあったらすんません。あとこの内容は Apple 的に激しく非推奨らしいのでそのへん適当に。

まず情報取得の方法ですが、2種類あるかなと。一つは Core Graphics Services という API を使うこと。これはなんか Apple の ML でその API は使うな! とか言ってるのを見かけたようなものですが(リンク失念)、高速に動いて、特に設定を変えることなく動きます。欠点はいつまでサポートされるかとか昔の OS で動くかとか不透明すぎること。あとドキュメントが存在しないこと。

まぁそれさえ妥協すれば割と簡単に使える、例えば画面にあるウィンドウ一覧はこんな感じ。

#include <Carbon/Carbon.h>

typedef void* CGSValue;

int _CGSDefaultConnection();
void CGSGetOnScreenWindowCount(int cid, int wid, int* cnt);
void CGSGetOnScreenWindowList(int cid, int wid, int cnt, int* ids, int* acnt);
void CGSGetWindowProperty(int cid, int wid, CGSValue key, CGSValue* val);
CGSValue CGSCreateCStringNoCopy(char* str);
void CGSGetScreenRectForWindow(int cid, int wid, CGRect* rect);
char* CGSCStringValue(CGSValue str);

int main() {
    int cid, cnt, *ids;
    int i;

    cid = _CGSDefaultConnection();
    CGSGetOnScreenWindowCount(cid, 0, &cnt);
    ids = (int*)malloc(sizeof(int)*cnt);
    CGSGetOnScreenWindowList(cid, 0, cnt, ids, &cnt);
    for (i = 0; i < cnt; i++) {
        CGSValue windowTitle;
        char* title;
        CGSGetWindowProperty(cid, ids[i],
                             CGSCreateCStringNoCopy("kCGSWindowTitle"),
                             &windowTitle);
        title = CGSCStringValue(windowTitle);

        if (title && *title) {
            CGRect rect;
            CGSGetScreenRectForWindow(cid, ids[i], &rect);
            int x = (int)rect.origin.x;
            int y = (int)rect.origin.y;
            int w = (int)rect.size.width;
            int h = (int)rect.size.height;

            printf("%s\t(%d,%d) (%d,%d)\n", title, x, y, w, h);
        }
    }
    free(ids);
    return 0;
}

CGSGetOnScreenWindowList を CGSGetWindowList にすれば画面に無いのも取れるし、ウィンドウがどのプロセスに属しているか、とか、あとウィンドウのレベル(背景だとか普通だとかメニューだとか)とかの属性も取れる。詳細は適当にヘッダでも探すと良い。

ただまぁ、この API は書き換え系の命令が全部エラーで帰ってくる。っていうのは X でもそうだけど、 Window Manager みたいにヨソの Window を自在に操れる存在は一つのプロセスしか許されてないみたい。そして OSX は Dock がその役割を担っているので、ヤツを殺して自分で完全な Window Manager を書く気が無いと手が出しにくい。

ただ、 VirtueDesktop は Apple Event で Dock と通信することで機能を一部実現しているみたい。実際ソースの中の DocCommunicator を適当にリンクしてやると、 Dock を殺さずにウィンドウの上下を入れ替える関数 (CGSExt*) とかが使えたりする。

ただあんまり追ってないのでよくわかってない。暇な時に深追いしてみるテーマとしてはなかなか面白そう。 VirtueDesktop も DesktopManager も mach_inject.c とかそいう萌え萌えなタイトルのコードが入ってるんですよね。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h