X11: NetWM (7) - ウィンドウタイプ/状態

_NET_WM_WINDOW_TYPE
ウィンドウをマップする前に、クライアントウィンドウに _NET_WM_WINDOW_TYPE プロパティを設定すると、そのウィンドウの機能のタイプを指定することができます。
(type = "ATOM", format = 32)

タイプはアトムで示され、配列で複数指定できます。最初の値が、一番優先度が高くなります。

このプロパティによって、タイプごとにウィンドウ装飾が変わったり、ウィンドウの重ね順が変わったりします。

※override_redirect = True のポップアップウィンドウなどに対しても、適切なものを設定する必要があります。

_NET_WM_WINDOW_TYPE_NORMAL通常のトップレベルウィンドウ。
_NET_WM_WINDOW_TYPE, WM_TRANSIENT_FOR のどちらのプロパティも設定されていないウィンドウは、このタイプとみなされなければなりません。
_NET_WM_WINDOW_TYPE がない override_redirect ウィンドウは、WM_TRANSIENT_FOR が設定されているかどうかに関係なく、このタイプとして解釈される必要があります。
_NET_WM_WINDOW_TYPE_DESKTOPデスクトップ機能。
スクリーンと同じサイズの、デスクトップアイコンを含む単一のウィンドウを含めることができ、ルートウィンドウに対してパッシブグラブを設定することなく、デスクトップを完全に制御できるようになります。
_NET_WM_WINDOW_TYPE_DOCKドックまたはパネルの機能。
ウィンドウマネージャーは、通常、このようなウィンドウを、他のすべてのウィンドウの上に置きます。
_NET_WM_WINDOW_TYPE_TOOLBAR
_NET_WM_WINDOW_TYPE_MENU
ツールバーと、メニューウィンドウ。
このタイプのウィンドウには、WM_TRANSIENT_FOR プロパティが設定されている場合があります。
_NET_WM_WINDOW_TYPE_MENU は、アプリケーションウィンドウから切り離されたウィンドウで設定する必要があることに注意してください。
通常、override_redirect ウィンドウで使用されます。
_NET_WM_WINDOW_TYPE_UTILITYパレットやツールボックスなどの、小さなユーティリティウィンドウ。
これは、メインアプリケーションから切り離されたツールバーには対応しないため、TOOLBAR タイプとは異なります。
これは TRANSIENT なダイアログではないため、DIALOG タイプとは異なります。
ユーザーは作業中、このウィンドウを開いたままにする可能性があります。
このタイプのウィンドウには、WM_TRANSIENT_FOR プロパティが設定されている場合があります。
_NET_WM_WINDOW_TYPE_SPLASHアプリケーションの起動時に表示される、スプラッシュスクリーン。
_NET_WM_WINDOW_TYPE_DIALOGダイアログウィンドウ。
_NET_WM_WINDOW_TYPE プロパティがない場合は、WM_TRANSIENT_FOR プロパティが設定されているウィンドウを、このタイプとみなされなければなりません。
WM_TRANSIENT_FOR があり、_NET_WM_WINDOW_TYPE がない、override_redirect ウィンドウは、_NET_WM_WINDOW_TYPE_NORMAL として解釈される必要があります。
_NET_WM_WINDOW_TYPE_DROPDOWN_MENUドロップダウンメニュー。
ユーザーがメニューバーをクリックしたときに、通常表示される種類のメニューであることを示します。
このプロパティは通常、override_redirect ウィンドウで使用されます。
_NET_WM_WINDOW_TYPE_POPUP_MENUポップアップメニュー。
ユーザーがオブジェクトを右クリックしたときに、通常表示される種類のメニューです。
このプロパティは通常、override_redirect ウィンドウで使用されます。
_NET_WM_WINDOW_TYPE_TOOLTIPツールヒント。
マウスカーソルが、オブジェクト上でしばらく待機した後に、通常表示される短い説明テキストであることを示します。
このプロパティは通常、override_redirect ウィンドウで使用されます。
_NET_WM_WINDOW_TYPE_NOTIFICATION通知。
「ラップトップの電源が切れています」などの情報テキストが表示されるバブル。
このプロパティは通常、override_redirect ウィンドウで使用されます。
_NET_WM_WINDOW_TYPE_COMBOコンボボックスによってポップアップされるウィンドウ。
このプロパティは通常、override_redirect ウィンドウで使用されます。
_NET_WM_WINDOW_TYPE_DNDウィンドウがドラッグされていることを示します。
ウィンドウに、ある場所から別の場所にドラッグされているオブジェクトの表現が含まれている場合、クライアントはこのヒントを設定する必要があります。
例としては、あるファイルマネージャーウィンドウから、別のファイルマネージャーウィンドウにドラッグされているアイコンを含むウィンドウが挙げられます。
このプロパティは通常、override_redirect ウィンドウで使用されます。
_NET_WM_STATE
クライアントウィンドウの _NET_WM_STATE プロパティには、現在のウィンドウの状態を示すアトムの配列がセットされています。
(type = "ATOM", format = 32)

  • ウィンドウをマップする前に、_NET_WM_STATE プロパティに値をセットすると、マップ時にその状態が適用されます。
  • ウィンドウがマップされている時に状態を変更したい場合は、プロパティを変更するのではなく、ClientMessage を送信する必要があります。
  • ウィンドウマネージャは、ウィンドウの状態が変化した時、常に _NET_WM_STATE プロパティを更新します。
  • ウィンドウマネージャは、ウィンドウが完全に非表示 (Withdrawn 状態) になるたびに、_NET_WM_STATE プロパティを削除します。
  • ウィンドウマネージャによって、独自の状態を追加しても良い。その場合、"_NET" で始まらないこと。

_NET_WM_STATE_MODALモーダルダイアログボックス。
(表示されている間、他のウィンドウがアクティブにならない)

WM_TRANSIENT_FOR の値が、別のトップレベルウィンドウに設定されている場合、ダイアログはそのウィンドウに対してのみ、モーダルになります。
WM_TRANSIENT_FOR の値が None またはルートウィンドウに設定されている場合、ダイアログはそのウィンドウのグループに対して、モーダルになります。
_NET_WM_STATE_STICKY仮想デスクトップがスクロールしている場合でも、画面上のウィンドウの位置を固定しておくべきであることを示します。
_NET_WM_STATE_MAXIMIZED_HORZ
_NET_WM_STATE_MAXIMIZED_VERT
ウィンドウが垂直/水平に最大化されていることを示します。
_NET_WM_STATE_SHADEDウィンドウがシェーディング (巻き上げ) されていることを示します。
ウィンドウのタイトルバーだけが表示されている状態になります。
_NET_WM_STATE_SKIP_TASKBARウィンドウをタスクバーに含めるべきではないことを示します。
このヒントは、アプリケーションによって要求される必要があります。
_NET_WM_WINDOW_TYPE によって、ウィンドウの正確な性質を伝えている場合、アプリケーションはこのヒントを設定すべきではありません。
_NET_WM_STATE_SKIP_PAGERウィンドウをページャに含めるべきではないことを示します。
このヒントは、アプリケーションによって要求される必要があります。
_NET_WM_WINDOW_TYPE によって、ウィンドウの正確な性質を伝えている場合、アプリケーションはこのヒントを設定すべきではありません。
_NET_WM_STATE_HIDDENデスクトップ/ビューポートがアクティブで、ウィンドウがスクリーンの範囲内にあるが、ウィンドウは画面に表示されないことを示すために、ウィンドウマネージャーによって設定される必要があります。

一般的に、最小化されたウィンドウは、_NET_WM_STATE_HIDDEN 状態になる必要があります。
ページャーや同様のアプリケーションでは、WM_STATE ではなく、_NET_WM_STATE_HIDDEN を使用して、ミニチュア表現において、ウィンドウを表示するかどうかを決定する必要があります。

※アプリケーションが _NET_WM_STATE_HIDDEN 状態を切り替えるように要求した場合、_NET_WM_STATE_HIDDEN は独立した状態ではなく、最小化などの他の側面の機能であるため、ウィンドウマネージャーはおそらく、その要求を無視する必要があります。
_NET_WM_STATE_FULLSCREENウィンドウが画面全体に表示され、ウィンドウ装飾がないことを示します。
さらに、ウィンドウマネージャーは、フルスクリーンから通常のウィンドウに戻った後に、元のジオメトリを復元させます。
_NET_WM_STATE_ABOVE他のウィンドウより前面に配置されることを示します
_NET_WM_STATE_BELOW他のウィンドウより背面に配置されることを示します。
主にユーザー側で指定されることを目的としているため、アプリケーション側では使用しないでください。
_NET_WM_STATE_DEMANDS_ATTENTIONウィンドウ内またはウィンドウで、何らかのアクションが発生したことを示します。
たとえば、ウィンドウのアクティブ化を要求したが、ウィンドウマネージャーがそれを拒否した場合に、ウィンドウマネージャーによって設定されたり、アプリケーションが何らかの作業を終了した場合に設定される場合があります。

この状態は、クライアントとウィンドウマネージャーの両方によって設定される場合があります。
ウィンドウが必要な注意を受けた (通常は、ウィンドウがアクティブ化された) と、ウィンドウマネージャーが判断した場合、ウィンドウマネージャーによって設定が解除される必要があります。
_NET_WM_STATE_FOCUSEDウィンドウの装飾フレームが、アクティブな状態 (入力フォーカスがある状態) として描画されるかどうかを示します。
クライアントは、これを読み取り専用のヒントと見なす必要があります。
マップ時に設定したり、ClientMessage を介して、変更したりすることはできません。

通常、_NET_ACTIVE_WINDOW で指定されているウィンドウには、このヒントが含まれますが、アクティブなウィンドウと強い関連性があり、ユーザーによって、アクティブなウィンドウと一体であると見なされる場合は、他のウィンドウにも、同様のヒントが設定される場合があります。
トップレベルにキーボードフォーカスがあるときに、内部要素の外観を変更する必要があるクライアントは、_NET_SUPPORTED でこのアトムが利用可能かどうかを確認し、利用可能な場合は、FocusIn イベントによるフォーカスの状態よりも _NET_WM_STATE_FOCUSED の値を優先する必要があります。
そうすることで、ウィンドウの装飾と一致する形で、アクティブ状態を描画できます。
マップ時の状態変更
マップされている時にウィンドウの状態を変更したい場合は、XSendEvent() でルートウィンドウに ClientMessage を送信する必要があります。
XSendEvent 時の引数は、NetWM (1) で説明しています。

[ClientMessage]
window = 対象のウィンドウ
message_type = (Atom) _NET_WM_STATE
format = 32
data.l[0] = action
data.l[1] = 1つ目のAtom
data.l[2] = 2つ目のAtom (なければ 0)
data.l[3] = 通常時は 1、ページャ時は 2

//action
#define _NET_WM_STATE_REMOVE   0  //削除
#define _NET_WM_STATE_ADD      1  //追加/セット
#define _NET_WM_STATE_TOGGLE   2  //反転

同時に2つの状態を変更することができるため、水平と垂直の最大化を同時に行うことができます。
状態が変化したことを判断する
例えば、ウィンドウ装飾の「最小化」「最大化」ボタンが押された時など、ユーザー操作によってウィンドウ状態が変化したことを監視したい場合は、PropertyNotify イベントを受信して (PropertyChangeMask)、ウィンドウの _NET_WM_STATE プロパティが変化したかどうかを判断する必要があります。

typedef struct {
 int           type;
 unsigned long serial;
 Bool          send_event;
 Display       *display;
 Window        window;
 Atom atom;
 Time time;
 int state;
} XPropertyEvent;

atom は、変化したプロパティの名前です。
state は、PropertyNewValue の場合は新しい値に変更され、PropertyDelete の場合はプロパティが削除されました。

ウィンドウが Withdrawn 状態になった時、プロパティは削除される場合があります。
_NET_WM_ALLOWED_ACTIONS
クライアントウィンドウの _NET_WM_ALLOWED_ACTIONS プロパティには、そのウィンドウが現在操作できるアクションのアトムリストがセットされています。
(type = "ATOM", format = 32)

そのウィンドウに対して、指定されたアクションが現在使用可能であるかどうかに応じて、ウィンドウマネージャが自動でプロパティを更新します。
また、タスクバー、ページャーなどのクライアントによって、現在使用可能なアクションが設定されます。

※プログラム側で使用したい機能を選択するようなものではなく、各操作が実行できるかどうかの判断を行うために使います。

_NET_WM_ACTION_MOVEウィンドウが画面上で移動できる
_NET_WM_ACTION_RESIZEウィンドウのサイズを変更できる。
※WM_NORMAL_HINTS の最小サイズと最大サイズが同じ場合、サイズは変更不可能。
_NET_WM_ACTION_MINIMIZEウィンドウをアイコン化 (最小化) できる
_NET_WM_ACTION_SHADEウィンドウに影を付けることができる
_NET_WM_ACTION_STICKウィンドウの貼り付け状態が切り替えられる (_NET_WM_STATE_STICKY と同様)。
この状態は、デスクトップではなくビューポートに関係していることに注意してください。
_NET_WM_ACTION_MAXIMIZE_HORZウィンドウを水平方向に最大化できる
_NET_WM_ACTION_MAXIMIZE_VERTウィンドウを垂直方向に最大化できる
_NET_WM_ACTION_FULLSCREENウィンドウを全画面状態にできる
_NET_WM_ACTION_CHANGE_DESKTOPウィンドウをデスクトップ間で移動できる
_NET_WM_ACTION_CLOSEウィンドウが閉じられる可能性がある。
(クライアントによって _NET_CLOSE_WINDOW メッセージが送信される可能性がある)
_NET_WM_ACTION_ABOVE最前面に配置される可能性がある (_NET_WM_STATE_ABOVE の変更に応答する)
_NET_WM_ACTION_BELOW最背面に配置される可能性がある (_NET_WM_STATE_BELOW の変更に応答する)
プログラム
初期状態で最大化にします。

左クリックで、最大化の状態を切り替えます。
右クリックで、全画面の状態を切り替えます。

閉じるボタンで終了します。

$ cc -o run d14-winstate.c util.c -lX11

<d14-winstate.c>
#include <stdio.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include "util.h"

enum
{
    _ACTION_ADD = 0,
    _ACTION_DELETE,
    _ACTION_TOGGLE
};

/* プロパティのアトムリストを出力 */

static void _put_atom_list(Window win,Atom atom,const char *name,int state)
{
    Atom *buf;
    int num;

    if(state == PropertyDelete)
    {
        printf("[%s] <delete>\n", name);
        return;
    }

    buf = read_prop32(win, atom, XA_ATOM, &num);
    if(!buf) return;

    printf("[%s] (%d) ", name, num);

    put_atoms_name((Atom *)buf, num);

    XFree(buf);
}

/* ウィンドウの状態を変更 */

static void _change_state(Window win,Atom prop,int action,Atom state1,Atom state2)
{
    XClientMessageEvent ev;

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

    ev.type = ClientMessage;
    ev.message_type = prop;
    ev.window = win;
    ev.format = 32;
    ev.data.l[0] = action;
    ev.data.l[1] = state1;
    ev.data.l[2] = state2;
    ev.data.l[3] = 1;

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

int main(int argc,char **argv)
{
    Display *disp;
    Window win;
    XEvent ev;
    Atom atoms[5];
    
    disp = XOpenDisplay(NULL);
    if(!disp) return 1;

    set_display(disp);

    atoms[0] = GET_ATOM("_NET_WM_STATE");
    atoms[1] = GET_ATOM("_NET_WM_ALLOWED_ACTIONS");
    atoms[2] = GET_ATOM("_NET_WM_STATE_FULLSCREEN");
    atoms[3] = GET_ATOM("_NET_WM_STATE_MAXIMIZED_HORZ");
    atoms[4] = GET_ATOM("_NET_WM_STATE_MAXIMIZED_VERT");

    win = create_test_window2(disp, 200, 200, 0,
        ButtonPressMask | PropertyChangeMask | StructureNotifyMask);

    //初期状態で最大化

    XChangeProperty(disp, win, atoms[0],
        XA_ATOM, 32, PropModeReplace, (unsigned char *)&atoms[3], 2);

    //イベント

    XMapWindow(disp, win);

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

        if(event_quit(&ev)) break;

        switch(ev.type)
        {
            case ButtonPress:
                if(ev.xbutton.button == Button1)
                {
                    printf("* toggle MAXIMIZE\n");
                    _change_state(win, atoms[0], _ACTION_TOGGLE, atoms[3], atoms[4]);
                }
                else if(ev.xbutton.button == Button3)
                {
                    printf("* toggle FULLSCREEN\n");
                    _change_state(win, atoms[0], _ACTION_TOGGLE, atoms[2], None);
                }
                break;
            case MapNotify:
                printf("* Map\n");
                break;
            case PropertyNotify:
                if(ev.xproperty.atom == atoms[0])
                {
                    _put_atom_list(win, atoms[0], "_NET_WM_STATE", ev.xproperty.state);
                }
                else if(ev.xproperty.atom == atoms[1])
                {
                    _put_atom_list(win, atoms[1], "_NET_WM_ALLOWED_ACTIONS",
                        ev.xproperty.state);
                }
                break;
        }
    }

    XCloseDisplay(disp);

    return 0;
}
解説
※ウィンドウにプロパティは存在するが、値がない場合は、終端に 1byte の 0 がある状態で、バッファが返ります。個数は 0 になります。

ウィンドウマネージャによって動作は異なります。
以下は、OpenBox の場合の動作です。
マップするまでの間
[_NET_WM_STATE] (2) _NET_WM_STATE_MAXIMIZED_HORZ, _NET_WM_STATE_MAXIMIZED_VERT, 
[_NET_WM_STATE] (2) _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_MAXIMIZED_HORZ, 
[_NET_WM_STATE] (2) _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_MAXIMIZED_HORZ, 
[_NET_WM_ALLOWED_ACTIONS] (12) _NET_WM_ACTION_CHANGE_DESKTOP, _NET_WM_ACTION_SHADE,
 _NET_WM_ACTION_CLOSE, _NET_WM_ACTION_MOVE, _NET_WM_ACTION_MINIMIZE, _NET_WM_ACTION_RESIZE,
 _NET_WM_ACTION_FULLSCREEN, _NET_WM_ACTION_MAXIMIZE_HORZ, _NET_WM_ACTION_MAXIMIZE_VERT,
 _NET_WM_ACTION_ABOVE, _NET_WM_ACTION_BELOW, _OB_WM_ACTION_UNDECORATE, 
[_NET_WM_ALLOWED_ACTIONS] (12) _NET_WM_ACTION_CHANGE_DESKTOP, _NET_WM_ACTION_SHADE,
 _NET_WM_ACTION_CLOSE, _NET_WM_ACTION_MOVE, _NET_WM_ACTION_MINIMIZE, _NET_WM_ACTION_RESIZE,
 _NET_WM_ACTION_FULLSCREEN, _NET_WM_ACTION_MAXIMIZE_HORZ, _NET_WM_ACTION_MAXIMIZE_VERT,
 _NET_WM_ACTION_ABOVE, _NET_WM_ACTION_BELOW, _OB_WM_ACTION_UNDECORATE, 
* Map

まず、マップする前にプロパティを設定しているので、最初の _NET_WM_STATE は、クライアントのプロパティ変更によるものです。

次に来る _NET_WM_STATE は、XMapWindow() 後、MapRequest イベントを受け取ったウィンドウマネージャによってプロパティが変更された結果です。
プロパティの値が調整された結果、順番が逆になっています。

同じ値で再び _NET_WM_STATE が来ている原因はわかりませんが、ウィンドウマネージャによるものでしょう。

その後、ウィンドウマネージャが、_NET_WM_ALLOWED_ACTIONS に現在操作可能なアクションを設定しています。
最大化している状態なのに _NET_WM_ACTION_MAXIMIZE_{HORZ,VERT} がありますが、これは、最大化を取り消して元に戻す操作ができるという意味になります。

なお、_OB_WM_ACTION_UNDECORATE で、OpenBox 独自の状態が追加されています。これは、装飾を非表示に出来る状態です。
最大化を元に戻した場合
[_NET_WM_STATE] (0) 
[_NET_WM_ALLOWED_ACTIONS] (12) _NET_WM_ACTION_CHANGE_DESKTOP, _NET_WM_ACTION_SHADE,
 _NET_WM_ACTION_CLOSE, _NET_WM_ACTION_MOVE, _NET_WM_ACTION_MINIMIZE, _NET_WM_ACTION_RESIZE,
 _NET_WM_ACTION_FULLSCREEN, _NET_WM_ACTION_MAXIMIZE_HORZ, _NET_WM_ACTION_MAXIMIZE_VERT,
 _NET_WM_ACTION_ABOVE, _NET_WM_ACTION_BELOW, _OB_WM_ACTION_UNDECORATE,

通常の表示状態に戻ったため、_NET_WM_STATE は、値なしになっています。
最小化して、元に戻した場合
# 最小化
[_NET_WM_STATE] (1) _NET_WM_STATE_HIDDEN,
# 元に戻す
[_NET_WM_STATE] (0) 
* Map

最小化時、_NET_WM_STATE は、_NET_WM_STATE_HIDDEN になっています。
全画面化した場合
右クリックで全画面化して、再び右クリックで全画面化を戻した場合。

# 全画面化
* toggle FULLSCREEN
[_NET_WM_STATE] (1) _NET_WM_STATE_FULLSCREEN, 
[_NET_WM_ALLOWED_ACTIONS] (4) _NET_WM_ACTION_CHANGE_DESKTOP, _NET_WM_ACTION_CLOSE,
 _NET_WM_ACTION_MINIMIZE, _NET_WM_ACTION_FULLSCREEN, 
[_NET_WM_STATE] (1) _NET_WM_STATE_FULLSCREEN, 

# 元に戻す
* toggle FULLSCREEN
[_NET_WM_STATE] (0) 
[_NET_WM_ALLOWED_ACTIONS] (12) _NET_WM_ACTION_CHANGE_DESKTOP, _NET_WM_ACTION_SHADE,
 _NET_WM_ACTION_CLOSE, _NET_WM_ACTION_MOVE, _NET_WM_ACTION_MINIMIZE, _NET_WM_ACTION_RESIZE,
 _NET_WM_ACTION_FULLSCREEN, _NET_WM_ACTION_MAXIMIZE_HORZ, _NET_WM_ACTION_MAXIMIZE_VERT,
 _NET_WM_ACTION_ABOVE, _NET_WM_ACTION_BELOW, _OB_WM_ACTION_UNDECORATE, 
[_NET_WM_STATE] (0)

全画面化した場合、_NET_WM_ALLOWED_ACTIONS の数が減っていることに注目してください。

この状態では、最大化を行ったり、最大化を元に戻すアクションはできないため、除外されています。
また、巻き上げ・位置移動・リサイズ・最前面化・最背面化などもできない状態になっています。
ほか
ほかの状態変化に関しては、ウィンドウのタイトルバーを右クリックするなどして表示したメニュー上で行える場合があります。