X11: ICCCM (2) - ウィンドウの表示状態

ウィンドウの表示状態
Xlib におけるウィンドウの表示状態は、「マップされている」か「マップされていない」かの2通りしか選択できません。

ICCCM では、トップレベルウィンドウの表示状態を、以下の3通りで定義します。

WithdrawnState (0)ウィンドウもアイコンも表示されず、タスクバーなどにも表示されない
NormalState (1)ウィンドウが通常状態で表示可能です。
IconicState (3)アイコン化されている状態 (意味は未定義)。
トップレベルウィンドウは非表示だが、代わりに、アイコンのウィンドウまたは Pixmap が表示されるか、WM_ICON_NAME のテキストが表示される。

ウィンドウがマップされている「通常の状態」と、ウィンドウがマップされていない「完全に非表示な状態」か「アイコン化されている状態」の3通りとなります。

「アイコン化」は、各ウィンドウマネージャによって定義が異なります。
ウィンドウの状態を変更する
新しく作成されたウィンドウは、Withdrawn の状態です。
以下を行うことによって、表示状態を変更することができます。

Withdrawn → NormalWM_HINTS プロパティの initial_state を NormalState にして、ウィンドウをマップする
Withdrawn → IconicWM_HINTS プロパティの initial_state を IconicState にして、ウィンドウをマップする
Normal → IconicClientMessage イベントを送信する
Normal → Withdrawn
Iconic → Withdrawn
ウィンドウをアンマップし、XSendEvent() で UnmapNotify イベントを送信する
Iconic → Normalウィンドウをマップする。
この場合、WM_HINTS プロパティの initial_state は関係ない。

Withdrawn からの移行や、Withdrawn への移行は、クライアントのみが行うことができます (ウィンドウマネージャによって行われることはない)。

NetWM では、さらに最小化などの状態が追加されており、ICCCM による純粋な Iconic 状態はあまり使われないので、詳細は省きます。
Withdrawn 状態へ移行する
Withdrawn は、ウィンドウが完全に非表示な状態で、タスクバーにも表示されない状態です。

ウィンドウを非表示にする場合、通常は XUnmapWindow() でウィンドウをアンマップしますが、ICCCM または NetWM においては、ウィンドウをアンマップした後、ウィンドウマネージャに対して、XSendEvent() で UnmapNotify イベントを送る必要があります。

イベントの送信が必要となる理由
たとえば、最小化されている状態で、ウィンドウを完全に非表示にしたい場合 (タイトルバーからも消したい場合)、ウィンドウはすでにアンマップされている状態なので、X サーバーによる UnmapNotify イベントが発生しません。

そのため、すでに最小化によってアンマップされている状態でも、ウィンドウマネージャによる Withdrawn 状態への移行ができるようにするため、クライアントは明示的に UnmapNotify イベントを送る必要があります。

ウィンドウマネージャは、X サーバーが生成した通常の UnmapNotify イベントか、クライアントから送られた UnmapNotify イベントを受け取った場合、Withdrawn 状態への移行を実行します。

そのため、ウィンドウがマップされている状態で、XUnmapWindow() だけを実行した場合でも、通常状態から Withdrawn 状態への移行はできますが、最小化されている状態で XUnmapWindow() しても何も起こらないので、ウィンドウはタスクバーに残ります。
XSendEvent
Status XSendEvent(Display *display, Window w, Bool propagate, long event_mask, XEvent *event_send);

XEvent 構造体のイベントを、特定のクライアントに直接送ることができます。
※この場合、グラブの状態は無視します。

w送信先のウィンドウ
propagateFalse の場合、event_mask のいずれかのイベントマスクを選択している、全てのクライアントに送信される。

True の場合、送信先ウィンドウを作成したクライアントに対して送られる。
送信先ウィンドウが event_mask のいずれも選択していない場合は、(do_not_propagate_mask でマスクが選択されていない) 送信先ウィンドウの最も近い祖先に送られる。
そのようなウィンドウがない場合、イベントは送信されない。
event_maskイベントマスク。
0 の場合、イベントは常に、送信先ウィンドウを作成したクライアントに送られる。
event_send送信するイベント。
send_event は常に True になり、serial と display の値は、この関数によって上書きされます。
戻り値0 以外で成功

ウィンドウマネージャに送る
ウィンドウマネージャに対してイベントを送る場合は、基本的に、以下のように引数を指定します。

w = ルートウィンドウ
propagate = False
event_mask = (SubstructureRedirectMask | SubstructureNotifyMask)

ウィンドウマネージャのクライアントは、ルートウィンドウに対して、上記のイベントマスクを選択することで、各ウィンドウのイベントを処理しています。

そのため、このイベントマスクを選択しているクライアントを指定するということは、ウィンドウマネージャのクライアントを指定することと同じ意味になります。
Withdrawn 用の UnmapNotify イベントを送る
typedef struct {
 int           type;
 unsigned long serial;
 Bool          send_event;
 Display       *display;
 Window        event;
 Window        window;
 Bool          from_configure;
} XUnmapEvent;

XEvent ev;

memset(&ev, 0, sizeof(XEvent));
ev.xunmap.type = UnmapNotify;
ev.xunmap.event = root_window;
ev.xunmap.window = window; //実際にアンマップされたウィンドウ
ev.xunmap.from_configure = False;
//ほかは XSendEvent で設定される

XSendEvent(disp, root_window, False,
    SubstructureRedirectMask | SubstructureNotifyMask, &ev);

ウィンドウをアンマップした後、上記のように UnmapNotify イベントを送ることで、Withdrawn 状態へ移行できます。

XWithdrawWindow
ちなみに、Xlib には、アンマップを行った後、UnmapNotify イベントを送る、便利な関数が存在します。

Status XWithdrawWindow(Display *display, Window w, int screen_number);

ウィンドウをアンマップした後、指定したスクリーン番号のルートウィンドウに、UnmapNotify イベントを送ります。
通常はこの関数を使えば問題ありません。
プログラム
F11 キーをホットキーとして扱います。
F11 キーが押されると、ウィンドウを Normal <-> Withdrawn の状態に切り替えます。

ポインタのボタンを押すと終了します。

Withdrawn 状態では、タスクバーからウィンドウが消えていること、そして、ウィンドウを最小化した状態でも、Withdrawn へ移行できることを確認してください。

$ cc -o run d03-state.c util.c -lX11

<d03-state.c>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include "util.h"

static void _withdrawn(Display *disp,Window win)
{
    XEvent ev;

    XUnmapWindow(disp, win);

    memset(&ev, 0, sizeof(XEvent));

    ev.xunmap.type = UnmapNotify;
    ev.xunmap.event = g_disp.root;
    ev.xunmap.window = win;
    ev.xunmap.from_configure = False;

    XSendEvent(disp, g_disp.root, False,
        SubstructureRedirectMask | SubstructureNotifyMask, &ev);
}

int main(int argc,char **argv)
{
    Display *disp;
    Window win;
    XEvent ev;
    int keycode,fmap;
    
    disp = XOpenDisplay(NULL);
    if(!disp) return 1;

    set_display(disp);

    win = create_test_window(disp,
        ButtonPressMask | KeyPressMask | KeyReleaseMask);

    //F11 キーをパッシブグラブ

    keycode = XKeysymToKeycode(disp, XK_F11);

    XGrabKey(disp, keycode, AnyModifier, g_disp.root,
        False, GrabModeAsync, GrabModeAsync);

    //イベント

    fmap = 1;

    XMapWindow(disp, win);

    while(1)
    {
        XNextEvent(disp, &ev);

        switch(ev.type)
        {
            case KeyPress:
                if(ev.xkey.keycode == keycode)
                {
                    if(fmap)
                        _withdrawn(disp, win);
                    else
                        XMapWindow(disp, win);

                    fmap ^= 1;
                }
                break;
            case ButtonPress:
                goto END;
        }
    }

END:
    XCloseDisplay(disp);

    return 0;
}