X11: NetWM (1)

NetWM
Extended Window Manager Hints」は、NetWM とも呼ばれています。
ICCCM を拡張する形で、さらなるデスクトップ関連の定義を行っています。

現在のウィンドウマネージャは、よほど古いウィンドウマネージャでない限りは、こちらの仕様に沿う形で実装されています。

NetWM も含めて、Unix のデスクトップ環境に関する色々な定義は、freedesktop.org が行っています。

>> https://specifications.freedesktop.org/ (各ドキュメントファイル)
>> https://www.freedesktop.org/wiki/Specifications/ (各ドキュメント)
NetWM 実装方法
基本的には、ICCCM と同じように、クライアントウィンドウにプロパティを設定したり、ウィンドウマネージャから ClientMessage イベントが送られてきたりします。

NetWM ではさらに、ルートウィンドウに対して色々なプロパティが設定されており、クライアントがそれを読み込むことで、ウィンドウマネージャに関する色々な情報を取得することができます。

また、ルートウィンドウに対して、XSendEvent 関数で ClientMessage イベントを送ることで、ウィンドウマネージャの動作などを変更することができます。

XSendEvent 関数でルートウィンドウにメッセージを送る時の引数は、以下のようにします。

[XSendEvent]
w           ルートウィンドウ
propagate   False
event_mask  (SubstructureNotifyMask|SubstructureRedirectMask)
event       XClientMessageEvent
  type = ClientMessage
関数、マクロ
ICCCM の場合は、X11/Xutil.h に、プロパティを設定したり読み込む関数や、各定数のマクロが定義されていましたが、NetWM に関しては、そういった定義を行っているヘッダファイルは存在しません。

そのため、プロパティは、XGetWindowProperty() や XChangeProperty() を使って直接読み書きをする必要があり、必要な定数に関しては、自分でマクロや列挙型を定義する必要があります。
_NET_SUPPORTED
まず、現在使用しているウィンドウマネージャが、クライアントが使用したい各 NetWM 機能をサポートしているかどうかを確認する必要があるため、ルートウィンドウから _NET_SUPPORTED プロパティを読み込みます。

type = "ATOM"、format = 32 で、アトムの配列になっています。

この場合は可変数の配列となるので、XGetWindowProperty() での読み込み時は、long_length に適当に大きなサイズを指定しておきます。
実際にプロパティに設定されているサイズ (個数) は、nitems_return に返ります。

クライアントは、このアトムリスト内に存在しない機能は、使わないようにする必要があります。
ウィンドウのリスト
同じように、ルートウィンドウから、_NET_CLIENT_LIST_NET_CLIENT_LIST_STACKING プロパティを読み込むことで、ウィンドウマネージャによって管理されている、すべてのウィンドウのリストを取得することができます。

type = "WINDOW", format = 32 で、ウィンドウ ID (Window) の配列になっています。
これも可変数の配列です。

_NET_CLIENT_LIST最初にマップされた順で、古いウィンドウから順に並んでいます。
_NET_CLIENT_LIST_STACKING重なり順で、背面から前面の順で並んでいます。

ウィンドウマネージャによって、常に現在の状態に更新されます。
プロパティ読み込み時の注意
ウィンドウマネージャが任意のタイミングで現状を更新するようなプロパティを、他のクライアントが読み込む場合、同じプロパティに対して、複数回の XGetWindowProperty() を実行して読み込むようなことは、なるべく避けてください。

また、1度目の XGetWindowProperty() でプロパティの実際のサイズを取得した後に、2度目の XGetWindowProperty() でデータを読み込むようなことも、同様に避けてください。

理由は、複数回のプロパティ取得の間に、プロパティの値が変更される可能性があるからです。

X サーバーがリクエストを処理する順番によっては、複数回のプロパティを取得するリクエストの間に、そのプロパティを変更するリクエストが来る場合があるため、最初のプロパティ取得時と、以降のプロパティ取得時で、値が変化している可能性があります。

例えば、1度目でプロパティのサイズを取得した時は、数は1個だったのに、2度目でデータを読み込んだ時は、個数が2個になっていたりする場合があります。

そのため、他のクライアントが任意のタイミングで値を書き換える可能性があるプロパティの場合は、事前にサイズを取得するようなことはせずに、一度の XGetWindowProperty 関数だけでデータを読み込むべきです。

プロパティを読み込む時点で、他のクライアントによって値が変更される心配がないプロパティの場合は、複数回 XGetWindowProperty() を実行しても問題ありません。
プログラム
_NET_SUPPORTED, _NET_CLIENT_LIST, _NET_CLIENT_LIST_STACKING の値を表示します。

<d08-netwm.c>
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>

/* format=32 のプロパティを読み込み */

static void *_read_prop32(Display *disp,Atom prop,Atom prop_type,int *num_ret)
{
    Atom type;
    int format;
    unsigned long nitem,after;
    unsigned char *buf = NULL;

    if(XGetWindowProperty(disp, DefaultRootWindow(disp),
        prop, 0, 1024, False, prop_type,
        &type, &format, &nitem, &after, &buf) != Success)
        return NULL;

    if(!buf) return NULL;

    if(format != 32)
    {
        XFree(buf);
        return NULL;
    }

    *num_ret = nitem;

    return buf;
}

/* アトムの文字列を表示 */

static void _put_atoms(Display *disp,Atom *buf,int num)
{
    int i;
    char **names;

    names = (char **)malloc(sizeof(char *) * num);

    XGetAtomNames(disp, buf, num, names);
    
    for(i = 0; i < num; i++)
    {
        if(names[i])
        {
            printf("%s\n", names[i]);
            free(names[i]);
        }
    }

    free(names);
}

/* ウィンドウIDを表示 */

static void _put_window(Window *buf,int num)
{
    int i;

    for(i = 0; i < num; i++)
        printf("0x%lx, ", buf[i]);

    printf("\n");
}

int main(int argc,char **argv)
{
    Display *disp;
    void *buf;
    int num;
    
    disp = XOpenDisplay(NULL);
    if(!disp) return 1;

    //_NET_SUPPORTED

    buf = _read_prop32(disp, XInternAtom(disp, "_NET_SUPPORTED", False), XA_ATOM, &num);
    if(buf)
    {
        printf("[_NET_SUPPORTED]\n");
        _put_atoms(disp, (Atom *)buf, num);
        XFree(buf);
    }

    //_NET_CLIENT_LIST

    buf = _read_prop32(disp, XInternAtom(disp, "_NET_CLIENT_LIST", False), XA_WINDOW, &num);
    if(buf)
    {
        printf("\n[_NET_CLIENT_LIST]\n");
        _put_window((Window *)buf, num);
        XFree(buf);
    }

    //_NET_CLIENT_LIST_STACKING

    buf = _read_prop32(disp, XInternAtom(disp, "_NET_CLIENT_LIST_STACKING", False), XA_WINDOW, &num);
    if(buf)
    {
        printf("\n[_NET_CLIENT_LIST_STACKING]\n");
        _put_window((Window *)buf, num);
        XFree(buf);
    }

    XCloseDisplay(disp);

    return 0;
}
解説
ウィンドウの状態を表す _NET_WM_STATE に関しては、そのプロパティ名と、各状態のプロパティがリスト化されています。

_NET_WM_STATE
_NET_WM_STATE_MODAL
_NET_WM_STATE_MAXIMIZED_VERT
_NET_WM_STATE_MAXIMIZED_HORZ
...