サイズ: 9863
コメント:
|
サイズ: 10137
コメント:
|
削除された箇所はこのように表示されます。 | 追加された箇所はこのように表示されます。 |
行 220: | 行 220: |
これまでの分をまとめる。 === プログラム === |
|
行 280: | 行 282: |
=== スクリーンショット === 白い部分が作成したクライアントのウィンドウ。タイトルが正しく表示されていることも確認できる。 {{attachment:simple_client.png}} |
Xクライアントの作成
しばらくXクライアントを作って感覚をつかんでみる。
こんなのがあったのでざっと読んでみると、割とWindowsでAPI叩いてプログラム書くのに似ている(むしろ逆なのだろう)。GC(Graphics Context)とかハンドルとかは大体同じような理解で良さそう。
「何もしない」やつを作ってみる
お約束のウィンドウが出るだけというアレをステップを追いつつ作ってみる。
Xサーバへの接続
何はともあれまずXサーバに接続する。必要なのはXサーバの動いているホストのアドレスとディスプレイ番号。ちなみに最初のディスプレイは0番。この辺は普通に使ってるだけでもわかるか。
- XOpenDisplay
- Xサーバと接続する
Display *XOpenDisplay(char *display_name);
nameに"<address>:<displaynumber>"の形式で指定する。NULLだとDISPLAY環境変数を見に行くらしい
Display型のポインタが帰ってくるので、これを使ってXサーバとやりとりする。失敗するとNULLが来る
終了時に接続を切るときは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 #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"となる。一体どういうことかソースを見ると、
ということで、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 }
スクリーンショット
白い部分が作成したクライアントのウィンドウ。タイトルが正しく表示されていることも確認できる。