X Window System(X11)のプログラムを正常に終了させる方法

X Window System(X11)で作ったプログラムを閉じた際に、以下のようなエラーメッセージが出た経験があるかと思います。

```
XIO:  fatal IO error 11 (Resource temporarily unavailable) on X server ":0"
      after 543 requests (11 known processed) with 0 events remaining.
```

これを出さずに正しく終了する方法についてまとめたいと思います。

#エラーメッセージを消す
##エラーメッセージの意味
そもそもどうしてこのようなエラーメッセージが出るかというと、__ウィンドウを閉じる際に発行されるメッセージ(WM_DELETE_WINDOW)を受け取らずにウィンドウが閉じられたから__です。
そのため、__WM_DELETE_WINDOWを受け取ったら正しく閉じる__ようにすることが必要となります。

##エラーメッセージが出ない記述方法
1. `Atom wm_delete_window = XInternAtom(d, "WM_DELETE_WINDOW", False);`<br>WM_DELETE_WINDOWのアトムを生成します。
2. `XSetWMProtocols(d, w, &wm_delete_window, 1);`<br>
WM_PROTOCOLSのプロパティに、生成したアトムをセットします。
3. イベントループ内のswitch文(event.typeで分岐)に以下のようにして、終了処理を記述します。

```c
case ClientMessage:
    if (event.xclient.data.l[0] == wm_delete_window) {
        /*終了処理*/
    }
    break;
```

#正しい終了処理
これでとりあえず、エラーメッセージは出なくなりましたので、次に、正しい終了処理の記述方法について書いていきたいと思います。
##大まかな手順
終了処理では、以下のような手順を踏むと良いと考えられます。

1.  ウィンドウを破棄する
2.  ウィンドウが破棄された際に発行される通知を受け取ったら、以下の手順を行う
  3.  ディスプレイとの接続を切断し、全ての資源を破棄する
  3.  プログラムを正常終了させる

##正しい終了処理の記述方法

1. `XSelectInput(d,w,/*(前略)*/|StructureNotifyMask);`<br>イベントマスクを指定する際に、`StructureNotifyMask`を加えます。
3. `XDestroyWindow(d,w);`<br>終了処理を行いたい箇所に加えます(ウィンドウを破棄し、`DestroyNotify`イベントを呼び出します)。
2. イベントループ内のswitch文(event.typeで分岐)に以下のようにして、終了処理を記述します。

```c
case DestroyNotify:
    XSetCloseDownMode(d,DestroyAll); //ディスプレイとの切断時に、全ての資源を破棄するよう設定
    XCloseDisplay(d); //ディスプレイとの接続を切断
    return 0; //プログラムを正常終了
```

###注意点
`StructureNotifyMask`をイベントマスクに指定すると、`DestroyNotify`イベント以外にも、いくつかイベントが通知されるようになるため、これらについての処理も記述せねばなりません(このあたりはあまり詳しくないので説明を省略します)。

#正しい終了処理を行っているプログラムの例
Ctrl+Eを押すと終了するだけのプログラムです(それ以上の処理は徹底的に省いてあります)。

```example.c
#include <stdio.h>
#include <X11/Xutil.h>

#define D_W 365 //ウィンドウの幅
#define D_H D_W //ウィンドウの高さ

/*メイン関数*/
int main()
{
    Display                    * const d = XOpenDisplay(NULL);
    const unsigned long            black = BlackPixel(d,0); //黒ピクセル
    const unsigned long            white = WhitePixel(d,0); //白ピクセル
    const Window                       w = XCreateSimpleWindow(d,RootWindow(d,0),0,0, D_W, D_H,1,black,white); //ウィンドウ
    Atom                wm_delete_window = XInternAtom(d, "WM_DELETE_WINDOW", False); //WM_DELETE_WINDOWのアトム
    Bool                       ctrl_flag = False; //Ctrlボタンが押されているかどうか
    KeySym                           sym; //キーシンボル
    XEvent                         event; //イベント

    XSetWMProtocols(d, w, &wm_delete_window, 1); //WM_DELETE_WINDOWのアトムをWM_PROTOCOLSに設定(ウィンドウが閉じられたら知らせるように設定)

    XMapWindow(d,w); //ディスプレイとウィンドウを関連付ける
    XSelectInput(d,w,KeyPressMask|KeyReleaseMask|StructureNotifyMask); //ウィンドウのイベントタイプをセット

    /*イベントループ*/
    while(True){
        XNextEvent(d,&event); //イベント取得
        /*イベントタイプによって分岐*/
        switch (event.type){
            /*キーが押されたとき*/
            case KeyPress:
                sym = XLookupKeysym(&event.xkey, 0); //キーシンボルを取得
                if(sym==XK_Control_L || sym==XK_Control_R){ //Ctrlキーが押されたとき
                    ctrl_flag =True; //Ctrlフラグを立てる
                } else if(ctrl_flag){ //Ctrlキーが押されているとき
                    /*キーシンボルで分岐*/
                    switch (sym){
                        /*『e』が押されたとき*/
                        case XK_e:
                            XDestroyWindow(d,w); //ウィンドウを破棄
                            break;
                        /*それ以外のとき*/
                        default:
                            break;
                    }
                }
                break;
            /*キーから指が離れたとき*/
            case KeyRelease:
                sym = XLookupKeysym(&event.xkey, 0); //キーシンボルを取得
                if(sym==XK_Control_L || sym==XK_Control_R){ //Ctrlキーから指が離れたとき
                    ctrl_flag = False; //Ctrlフラグを折る
                }
                break;
            /*クライアントメッセージがきたとき*/
            case ClientMessage:
                /*eventのアトムがWM_DELETE_WINDOWのアトムと一致するとき(ウィンドウが閉じられたとき)*/
                if (event.xclient.data.l[0] == wm_delete_window) {
                    XDestroyWindow(d,w); //ウィンドウを破棄
                }
                break;
            /*ウィンドウが破棄されたとき*/
            case DestroyNotify:
                XSetCloseDownMode(d,DestroyAll); //ディスプレイとの切断時に、全ての資源を破棄するよう設定
                XCloseDisplay(d); //ディスプレイとの接続を切断
                return 0; //プログラムを正常終了
            /*キーボードやポインタのマッピングが変更されたとき*/
            case MappingNotify:
                XRefreshKeyboardMapping(&event.xmapping); //キーボード情報の更新
                break;
            /*リサイズされたとき*/
            case ConfigureNotify:
                XClearWindow(d,w); //ウィンドウをクリア
                break;
            /*ウィンドウがディスプレイにマップされたとき*/
            case MapNotify:
                printf("ウィンドウがディスプレイにマップされました。\n");
                break;
            /*ウィンドウがディスプレイからアンマップされたとき*/
            case UnmapNotify:
                printf("ウィンドウがディスプレイからアンマップされました。\n");
                break;
            /*ウィンドウの親が変更されたとき*/
            case ReparentNotify:
                printf("ウィンドウの親が変更されました。\n");
                break;
            /*それ以外のとき*/
            default:
                fprintf(stderr,"想定外のイベントを取得しました(イベントタイプ : %d)。\n", event.type); //エラーメッセージを表示
                break;
        }
    }
}
```

#参考文献
* [clear/wm_devel/2011-08-16 - 電気通信大学MMA](https://wiki.mma.club.uec.ac.jp/clear/wm_devel/2011-08-16)

#関連記事
* [gtk - グラフィック系のライブラリを用いたC言語のプログラムをコンパイルする際のCMakeLists.txtの記述について(gccのコマンド付き) - Qiita](http://qiita.com/SUZUKI_Masaya/items/c3ed8ae693beed1a926b)