## page was renamed from clear/note/wmdev/2011-08-16
= Xクライアントの作成 =
しばらくXクライアントを作って感覚をつかんでみる。
* http://users.actcom.co.il/~choo/lupg/tutorials/xlib-programming/xlib-programming.html
こんなのがあったのでざっと読んでみると、割とWindowsでAPI叩いてプログラム書くのに似ている(むしろ逆なのだろう)。GC(Graphics Context)とかハンドルとかは大体同じような理解で良さそう。
== 「何もしない」やつを作ってみる ==
お約束のウィンドウが出るだけというアレをステップを追いつつ作ってみる。
=== Xサーバへの接続 ===
何はともあれまずXサーバに接続する。必要なのはXサーバの動いているホストのアドレスとディスプレイ番号。ちなみに最初のディスプレイは0番。この辺は普通に使ってるだけでもわかるか。
XOpenDisplay:: Xサーバと接続する
{{{
Display *XOpenDisplay(char *display_name);
}}}
* `name`に"
:"の形式で指定する。`NULL`だと`DISPLAY`環境変数を見に行くらしい
* `Display`型のポインタが帰ってくるので、これを使ってXサーバとやりとりする。失敗すると`NULL`が来る
終了時に接続を切るときは`XCloseDisplay()`を使うらしい。これによって全てのウィンドウと確保したリソースが解放される。プログラムは終了しない。
とりあえず接続して終了するだけならこんな感じか。
{{{#!highlight c numbers=no
Display *d;
d = XOpenDisplay(NULL);
if (!d) {
fprintf(stderr, "failed to connect X server\n");
exit(1);
}
/* ... */
XCloseDisplay(d);
}}}
=== ディスプレイの情報を取得してみる ===
ちょっと逸れて、接続したXサーバから情報を取得してみる。いろいろマクロがある。
{{{#!highlight c
#include
#include
#include
#include
void show(Display *d)
{
int def_screen = DefaultScreen(d);
printf("Server Vendor: %s, release %d\n", ServerVendor(d), VendorRelease(d));
printf("\tProtocol version %d, revision %d\n", ProtocolVersion(d), ProtocolRevision(d));
printf("Found %d screen\n", ScreenCount(d));
printf("Number of default screen: %d\n", def_screen);
printf("Size of default screen: %dx%d\n", DisplayWidth(d, def_screen), DisplayHeight(d, def_screen));
printf("\tDepth of Root Window: %d\n", DefaultDepth(d, def_screen));
}
int main(void)
{
Display *d;
d = XOpenDisplay(NULL);
if (!d) {
fprintf(stderr, "failed to connect X server\n");
exit(1);
} else {
show(d);
}
XCloseDisplay(d);
return 0;
}
}}}
試しに手元で実行。
{{{
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()`というのがあるらしい。イベントループの部分は関数に追い出してみることにする。
{{{#!highlight c
void event_loop(void)
{
XEvent event;
for (;;) {
XNextEvent(d, &event);
switch (event.type) {
case DestroyNotify:
return;
}
}
}
}}}
大体揃ったのでこれまでのコードをまとめる。
{{{#!highlight c
#include
#include
#include
#include
void event_loop(void);
Display *d;
Window w;
int main(int argc, char **argv)
{
d = XOpenDisplay(NULL);
if (!d) {
fprintf(stderr, "failed to open display\n");
exit(1);
}
w = XCreateSimpleWindow(
d, DefaultRootWindow(d), 0, 0, 400, 300, 1,
BlackPixel(d, 0), WhitePixel(d, 0));
XMapWindow(d, w);
XSelectInput(d, w, StructureNotifyMask);
event_loop();
XCloseDisplay(d);
return 0;
}
void event_loop(void)
{
XEvent event;
for (;;) {
XNextEvent(d, &event);
switch (event.type) {
case DestroyNotify:
return;
}
}
}
}}}
== ウィンドウマネージャとやりとりする ==
上記のプログラムをコンパイルすると動くことは動くのだが、いくつか問題が発生する。
=== ウィンドウを閉じるとき ===
クライアント側で特に終了動作を規定していない(例えば、クリックされたら終了、とか)ので、ウィンドウマネージャから閉じてやるなり`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`)はウィンドウマネージャからやってくるらしく、それを受け取らないとこうなるらしい。問答無用で外部からウィンドウが閉じられるのが不味いようなので、ちゃんと通知を受け取って自分で消すことにする。
* http://stackoverflow.com/questions/1157364/intercept-wm-delete-window-on-x11
* http://www.lemoda.net/c/xlib-wmclose/
そのためにはまず`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"となる。一体どういうことかソースを見ると、
{{{#!highlight c start=1934
void
updatetitle(Client *c) {
if(!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name))
gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name);
if(c->name[0] == '\0') /* hack to mark broken clients */
strcpy(c->name, broken);
}
}}}
ということで、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);
}}}
== ひとまず完成 ==
これまでの分をまとめる。
=== プログラム ===
{{{#!highlight c
#include
#include
#include
#include
#include
void event_loop(void);
Display *d;
Window w;
Atom wm_delete_window;
int main(int argc, char **argv)
{
if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
fputs("locale not supported", stderr);
d = XOpenDisplay(NULL);
if (!d) {
fprintf(stderr, "failed to open display\n");
exit(1);
}
w = XCreateSimpleWindow(
d, DefaultRootWindow(d), 0, 0, 400, 300, 1,
BlackPixel(d, 0), WhitePixel(d, 0));
XmbSetWMProperties(d, w, "X11テスト", NULL, argv, argc, NULL, NULL, NULL);
wm_delete_window = XInternAtom(d, "WM_DELETE_WINDOW", False);
XSetWMProtocols(d, w, &wm_delete_window, 1);
XMapWindow(d, w);
XSelectInput(d, w, StructureNotifyMask);
event_loop();
XCloseDisplay(d);
return 0;
}
void event_loop(void)
{
XEvent event;
for (;;) {
XNextEvent(d, &event);
switch (event.type) {
case ClientMessage:
if ((Atom)event.xclient.data.l[0] == wm_delete_window)
XDestroyWindow(d, w);
break;
case DestroyNotify:
return;
}
}
}
}}}
=== スクリーンショット ===
{{attachment:simple_client.png}}
* 白い部分が作成したクライアントのウィンドウ。タイトルが正しく表示されていることも確認できる