イベントループの改良 関連: [[clear/wm_devel/2012-11-12]] これまで以下のようにイベントループを書いていた。 {{{#!highlight c volatile sig_atomic_t running = 1; /* 実行継続フラグ */ xcb_connection_t *conn; /* Xサーバとの接続 */ /* ... */ /* SIGTERMハンドラ */ void handle_sigterm(int sig) { running = 0; } /* ... */ /* イベントループ */ xcb_generic_event_t *event; while (running && (event = xcb_wait_for_event(conn))) { /* イベント処理(とエラーハンドリング)を行う */ } }}} `xcb_wait_for_event`は、イベントが無い場合は来るまでブロックし続ける。そのため、WMがkillされて継続フラグが0になった場合でも、何かイベントが来ない限りそこで止まってしまい、WMがすぐに終了しないという問題がある。 そこで、`xcb_poll_for_event`を用いてイベントループを次のように書き直した。 {{{#!highlight c /* イベントループ */ xcb_generic_event_t *event; int xfd = xcb_get_file_descriptor(conn); fd_set fd; FD_ZERO(&fd); while (running) { if ((event = xcb_poll_for_event(conn))) { イベント処理(とエラーハンドリング)を行う } else { /* エラーが発生していないか確認 */ if (xcb_connection_has_error(conn)) { Xサーバとの接続でエラー発生。終了 } /* 出力バッファをflushしてリクエストをXサーバに送信する ここでやっておくと、あちこちでxcb_flushしなくても良くなる */ xcb_flush(conn); /* 何か来るまで待つ */ FD_SET(xfd, &fd); if (select(xfd + 1, &fd, NULL, NULL, NULL) < 0) { if (errno == EINTR) { /* Linuxのselect(2)のmanによるとエラー発生時のfdの中身は未定義になるらしい FreeBSDではfdは変更されないとmanに書いてあった */ FD_ZERO(&fd); } else { selectに失敗。エラーを出力して終了 } } } } }}} `xcb_poll_for_event`は、イベントが無い場合即座にNULLを返すので、killされた場合もループがすぐ回って即座に終了するようになる。しかし、これだけだとイベントが届かない限り全力でループが回り続けるので、CPU資源を食いつぶしてしまう。そこで、イベントが無かった場合は`select`でXサーバから何かがやってくるのを待つ。`select`中に何らかのシグナルを受け取った場合(killされたときは`SIGTERM`)は`select`が-1を返し、`errno`が`EINTR`となるので、イベントループの実行を続ける<>。そうでない場合は本当に`select`が失敗しているのでエラーを吐いて終了する。 awesomeやi3(共にXCBで書かれている)は、libevを使ってイベントループを実装している。