X11: ウィンドウの状態変化イベント

ウィンドウの状態変化イベント
ウィンドウの状態が変化した時、以下のイベントが来ます。

MapNotifyウィンドウが画面に表示された時
UnmapNotifyウィンドウが画面から表示されなくなった時
CreateNotify子ウィンドウが作成された時
DestroyNotifyウィンドウが破棄された時
ConfigureNotifyウィンドウの位置・サイズ・スタック順が変更された時
CirculateNotifyウィンドウのスタック位置が循環された時
GravityNotify親のウィンドウサイズが変更され、子の位置が変化した時
ReparentNotify親ウィンドウが別のウィンドウに変更された時
VisibilityNotify画面上の可視状態が変化した時

必要なイベントマスクは、VisibilityNotify の場合は VisibilityChangeMask
それ以外は、自身のウィンドウに関する状態変化なら StructureNotifyMask
子ウィンドウに関する状態変化なら SubstructureNotifyMask です。

CreateNotify は、親ウィンドウの SubstructureNotifyMask でしか選択できません (自身のウィンドウ作成時のイベントは生成できない)。
イベント
ConfigureNotify
typedef struct {
 int           type;
 unsigned long serial;
 Bool          send_event;
 Display       *display;
 Window        event;
 Window        window;
 int           x, y;
 int           width, height;
 int           border_width;
 Window        above;
 Bool          override_redirect;
} XConfigureEvent;

ウィンドウの位置・サイズ・境界幅・スタック順 (ウィンドウの表示順) が変更された時。

eventイベントが送られたウィンドウ (自身か親)
window実際に変化したウィンドウ
x,ysend_event が False の場合、親ウィンドウの原点を基準とした相対位置。
True の場合、ルートウィンドウの原点を基準とした位置。
width,heightウィンドウの中身のサイズ
border_widthウィンドウの境界線の幅
aboveこのウィンドウの下に位置する兄弟ウィンドウ。
None の場合、window は、兄弟ウィンドウに関してスタックの一番下にあります。
それ以外の場合、window は、above の兄弟ウィンドウの上に配置されます。
override_redirectウィンドウの属性から設定される。
VisibilityNotify
typedef struct {
 int           type;
 unsigned long serial;
 Bool          send_event;
 Display       *display;
 Window        window;
 int           state;
} XVisibilityEvent;

画面上のウィンドウの可視状態が変化した時。

[state]
VisibilityUnobscured (0)部分的または完全に隠れている or 非表示状態から、表示可能で全体が見えている状態に変化した時。
VisibilityPartiallyObscured (1)全体が見えている or 非表示状態から、表示可能で一部が隠れている状態に変化した時。
VisibilityFullyObscured (2)表示可能で全体が見えている or 一部が隠れている or 非表示状態から、表示可能で全体が隠れた状態に変化した時。
プログラム
各イベントを表示します。
ポインタボタンを押すと終了します。

<17-configure.c>
#include <stdio.h>
#include <X11/Xlib.h>

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

    //ウィンドウ作成

    attr.background_pixel = 0;
    attr.event_mask = ButtonPressMask | StructureNotifyMask
     | VisibilityChangeMask;

    win = XCreateWindow(disp, DefaultRootWindow(disp),
        0, 0, 200, 200, 0,
        CopyFromParent, CopyFromParent, CopyFromParent,
        CWBackPixel | CWEventMask, &attr);

    XMapWindow(disp, win);

    //イベント

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

        switch(ev.type)
        {
            case ConfigureNotify:
                printf("[Configure] event(0x%lx) window(0x%lx) send_event(%d) x(%d) y(%d)\n"
                    "  width(%d) height(%d) border_width(%d) above(0x%lx)\n",
                    ev.xconfigure.event, ev.xconfigure.window, ev.xconfigure.send_event,
                    ev.xconfigure.x, ev.xconfigure.y,
                    ev.xconfigure.width, ev.xconfigure.height,
                    ev.xconfigure.border_width, ev.xconfigure.above);
                break;
            case MapNotify:
                printf("[Map]\n");
                break;
            case UnmapNotify:
                printf("[Unmap]\n");
                break;
            case VisibilityNotify:
                printf("[Visibility] state(%d)\n", ev.xvisibility.state);
                break;
            case ReparentNotify:
                printf("[Reparent] parent(0x%lx) x(%d) y(%d)\n",
                    ev.xreparent.parent, ev.xreparent.x, ev.xreparent.y);
                break;
            case ButtonPress:
                goto END;
        }
    }

END:
    XCloseDisplay(disp);

    return 0;
}
起動時のイベント
[Reparent] parent(0x404114) x(0) y(0)
[Configure] event(0x1c00001) window(0x1c00001) send_event(0) x(1) y(20)
  width(200) height(200) border_width(0) above(0x404140)
[Configure] event(0x1c00001) window(0x1c00001) send_event(1) x(1) y(816)
  width(200) height(200) border_width(0) above(0x0)
[Map]
[Visibility] state(0)

起動時は、まず、XMapWindow() でウィンドウを表示しようとした時、ウィンドウマネージャがルートウィンドウで SubstructureRedirectMask イベントマスクを選択しているので、ウィンドウはマップされず、代わりに、ウィンドウマネージャのクライアントに MapRequest イベントが送信されます。

ウィンドウマネージャがそのイベントを受け取ると、そのウィンドウの override_redirect が True でない場合、ウィンドウ装飾としてフレームウィンドウを作成し、対象のウィンドウをフレームウィンドウの子として変更します。

それにより、ウィンドウに ReparentNotify イベントが来ます。
parent = 0x404114 が、新しい親ウィンドウ (フレームウィンドウ) になります。

その後、ウィンドウマネージャがウィンドウの初期位置とサイズを調整し、ConfigureNotify イベントが送られてきます。

この時の x,y の位置は、フレームウィンドウの左上を基準とした、中身のウィンドウの相対位置になります。

その後、send_event が True となっている ConfigureNotify イベントが送られてきます。
この時の x,y の位置は、ルートウィンドウの左上を基準とした、中身のウィンドウの位置になります。

send_event が True の場合、XSendEvent 関数によって、他のクライアントから直接イベントが送られてきたことを意味します。

ルートウィンドウ上でウィンドウの位置が移動した場合、フレームウィンドウの位置は変化しますが、中身のウィンドウは子ウィンドウのため、位置は変化しないので、通常の方法では、中身のウィンドウに、ウィンドウ位置が変化したイベントを通知することができません。
そのため、ルート上のウィンドウ位置が移動した時は、ウィンドウマネージャが XSendEvent() で直接イベントを送る形になっています。
これにより、ウィンドウ位置が移動したことを判断することができます。

その後、ウィンドウマネージャがフレームウィンドウをマップすることで、子である中身のウィンドウも表示され、MapNotify イベントが来ます。

その直後に VisibilityNotify イベントが来て、state が VisibilityUnobscured (0) になっています。
これは、非表示の状態から、ウィンドウ全体が見える形で画面上に表示されたことを意味します。