ログイン
編集不可のページディスカッション情報添付ファイル

2011-08-16 22:46:02時点のリビジョン6

メッセージを消す
clear/wm_devel/2011-08-16

MMA

Xクライアントの作成

しばらくXクライアントを作って感覚をつかんでみる。

こんなのがあったのでざっと読んでみると、割とWindowsでAPI叩いてプログラム書くのに似ている(むしろ逆なのだろう)。GC(Graphics Context)とかハンドルとかは大体同じような理解で良さそう。

「何もしない」やつを作ってみる

お約束のウィンドウが出るだけというアレをステップを追いつつ作ってみる。

Xサーバへの接続

何はともあれまずXサーバに接続する。必要なのはXサーバの動いているホストのアドレスとディスプレイ番号。ちなみに最初のディスプレイは0番。この辺は普通に使ってるだけでもわかるか。

XOpenDisplay
Xサーバと接続する

Display *XOpenDisplay(char *display_name);

終了時に接続を切るときはXCloseDisplay()を使うらしい。これによって全てのウィンドウと確保したリソースが解放される。プログラムは終了しない。

とりあえず接続して終了するだけならこんな感じか。

Display *d;
d = XOpenDisplay(NULL);
if (!d) {
    fprintf(stderr, "failed to connect X server\n");
    exit(1);
}

/* ... */

XCloseDisplay(d);

ディスプレイの情報を取得してみる

ちょっと逸れて、接続したXサーバから情報を取得してみる。いろいろマクロがある。

   1 #include <X11/Xlib.h>
   2 #include <X11/Xutil.h>
   3 #include <stdio.h>
   4 #include <stdlib.h>
   5 
   6 void show(Display *d)
   7 {
   8     int def_screen = DefaultScreen(d);
   9 
  10     printf("Server Vendor: %s, release %d\n", ServerVendor(d), VendorRelease(d));
  11     printf("\tProtocol version %d, revision %d\n", ProtocolVersion(d), ProtocolRevision(d));
  12     printf("Found %d screen\n", ScreenCount(d));
  13     printf("Number of default screen: %d\n", def_screen);
  14     printf("Size of default screen: %dx%d\n", DisplayWidth(d, def_screen), DisplayHeight(d, def_screen));
  15     printf("\tDepth of Root Window: %d\n", DefaultDepth(d, def_screen));
  16 }
  17 
  18 int main(void)
  19 {
  20     Display *d;
  21 
  22     d = XOpenDisplay(NULL);
  23     if (!d) {
  24         fprintf(stderr, "failed to connect X server\n");
  25         exit(1);
  26     } else {
  27         show(d);
  28     }
  29 
  30     XCloseDisplay(d);
  31 
  32     return 0;
  33 }

試しに手元で実行。

Server Vendor: The X.Org Foundation, release 10707000
        Protocol version 11, revision 0
Found 1 screen
Number of default screen: 0
Size of default screen: 1920x1080
        Depth of Root Window: 24

ウィンドウの生成

Windowsだとウィンドウクラスを登録してCreateWindow(Ex)して…となるが、Xlibの場合XCreateSimpleWindow()というのがあった。

w = XCreateSimpleWindow(
        d, DefaultRootWindow(d), 0, 0, 400, 300, 1,
        BlackPixel(d, 0), WhitePixel(d, 0));

生成したウィンドウを表示する。

XMapWindow(d, w);

イベントの処理

まずどんな種類のイベントを受け取るかのマスクを指定する。とりあえず、ウィンドウの破棄(DestroyNotify)を受け取りたいので、StructureNotifyMaskを立てておく。

XSelectInput(d, w, StructureNotifyMask);

続いてイベントループに入る。WindowsだとGetMessage()やらPeekMessage()を使ったお決まりのパターンがあるけれど、XlibではXNextEvent()というのがあるらしい。イベントループの部分は関数に追い出してみることにする。

   1 void event_loop(void)
   2 {
   3     XEvent event;
   4 
   5     for (;;) {
   6         XNextEvent(d, &event);
   7 
   8         switch (event.type) {
   9         case DestroyNotify:
  10             return;
  11         }
  12     }
  13 }

大体揃ったのでこれまでのコードをまとめる。

   1 #include <X11/Xlib.h>
   2 #include <X11/Xutil.h>
   3 #include <stdio.h>
   4 #include <stdlib.h>
   5 
   6 void event_loop(void);
   7 
   8 Display *d;
   9 Window w;
  10 
  11 int main(int argc, char **argv)
  12 {
  13     d = XOpenDisplay(NULL);
  14     if (!d) {
  15         fprintf(stderr, "failed to open display\n");
  16         exit(1);
  17     }
  18     w = XCreateSimpleWindow(
  19             d, DefaultRootWindow(d), 0, 0, 400, 300, 1,
  20             BlackPixel(d, 0), WhitePixel(d, 0));
  21     XMapWindow(d, w);
  22 
  23     XSelectInput(d, w, StructureNotifyMask);
  24     event_loop();
  25     XCloseDisplay(d);
  26 
  27     return 0;
  28 }
  29 
  30 void event_loop(void)
  31 {
  32     XEvent event;
  33 
  34     for (;;) {
  35         XNextEvent(d, &event);
  36 
  37         switch (event.type) {
  38         case DestroyNotify:
  39             return;
  40         }
  41     }
  42 }

ウィンドウマネージャとやりとりする

上記のプログラムをコンパイルすると動くことは動くのだが、いくつか問題が発生する。

ウィンドウを閉じるとき

クライアント側で特に終了動作を規定していない(例えば、クリックされたら終了、とか)ので、ウィンドウマネージャから閉じてやるなりxkillを使うなりしてウィンドウを閉じてやる必要がある。しかし、実際にそうすると

XIO:  fatal IO error 35 (Resource temporarily unavailable) on X server ":0.0"
      after 16 requests (16 known processed) with 0 events remaining.

きゃー。調べてみると、ウィンドウを閉じようとしている際の通知(WM_DELETE_WINDOW)はウィンドウマネージャからやってくるらしく、それを受け取らないとこうなるらしい。問答無用で外部からウィンドウが閉じられるのが不味いようなので、ちゃんと通知を受け取って自分で消すことにする。

そのためにはまずWM_DELETE_WINDOWを受け取れるようにしなければならない。何もしないと来ない。WM_PROTOCOLSプロパティを設定すればいい。WM_DELETE_WINDOWに対するアトム値を作っておいて、それを用いて判別する。

Atom wm_delete_window;
wm_delete_window = XInternAtom(d, "WM_DELETE_WINDOW", False);
XSetWMProtocols(d, w, &wm_delete_window, 1);

こうしておくと、ウィンドウが閉じられそうになったとき、上記のアトム値を含む形でClientMessageというイベントが来るので、イベントループに処理を書き加えておく。

for (;;) {
    XNextEvent(d, &event);

    switch (event.type) {
    case ClientMessage:
        /* event.xclient.dataにはメッセージ固有のデータが入っていて、`l[0]`がメッセージのアトム値を表している */
        if ((Atom)event.xclient.data.l[0] == wm_delete_window)
            XDestroyWindow(d, w);
        break;

    case DestroyNotify:
        return;
    }
}

これでひとまずOK。

タイトル文字列を設定する

上記のプログラムをdwm上で動かすと、バーに出てくるタイトルが"broken"となる。一体どういうことかソースを見ると、

1934 void
1935 updatetitle(Client *c) {
1936         if(!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name))
1937                 gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name);
1938         if(c->name[0] == '\0') /* hack to mark broken clients */
1939                 strcpy(c->name, broken);
1940 }

ということで、dwmはWM_NAME(アプリケーション名を表すプロパティ)が空のクライアントをbrokenと見なすらしいので設定しておく。せっかくなので日本語を使ってみる。

ロケールの設定。メッセージ以外dwm 5.9から拝借。

if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
    fputs("locale not supported", stderr);

続いてWM_NAMEを設定する。

XmbSetWMProperties(d, w, "X11テスト", NULL, argv, argc, NULL, NULL, NULL);

ひとまず完成

   1 #include <X11/Xlib.h>
   2 #include <X11/Xutil.h>
   3 #include <stdio.h>
   4 #include <stdlib.h>
   5 #include <locale.h>
   6 
   7 void event_loop(void);
   8 
   9 Display *d;
  10 Window w;
  11 
  12 Atom wm_delete_window;
  13 
  14 int main(int argc, char **argv)
  15 {
  16     if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
  17         fputs("locale not supported", stderr);
  18 
  19     d = XOpenDisplay(NULL);
  20     if (!d) {
  21         fprintf(stderr, "failed to open display\n");
  22         exit(1);
  23     }
  24     w = XCreateSimpleWindow(
  25             d, DefaultRootWindow(d), 0, 0, 400, 300, 1,
  26             BlackPixel(d, 0), WhitePixel(d, 0));
  27     XmbSetWMProperties(d, w, "X11テスト", NULL, argv, argc, NULL, NULL, NULL);
  28 
  29     wm_delete_window = XInternAtom(d, "WM_DELETE_WINDOW", False);
  30     XSetWMProtocols(d, w, &wm_delete_window, 1);
  31 
  32     XMapWindow(d, w);
  33 
  34     XSelectInput(d, w, StructureNotifyMask);
  35     event_loop();
  36     XCloseDisplay(d);
  37 
  38     return 0;
  39 }
  40 
  41 void event_loop(void)
  42 {
  43     XEvent event;
  44 
  45     for (;;) {
  46         XNextEvent(d, &event);
  47 
  48         switch (event.type) {
  49         case ClientMessage:
  50             if ((Atom)event.xclient.data.l[0] == wm_delete_window)
  51                 XDestroyWindow(d, w);
  52             break;
  53 
  54         case DestroyNotify:
  55             return;
  56         }
  57     }
  58 }