X11: クライアント間の通信

ウィンドウマネージャ
X サーバー自体には、ウィンドウを作成して、単純に表示したり非表示にするような機能しかないので、例えば、最小化や最大化といったような、デスクトップに関する詳細なウィンドウ操作を行うことはできません。

そのため、ウィンドウ装飾を付けたり、ドラッグでウィンドウを移動させたり、リサイズしたりするような動作を実装するのは、「ウィンドウマネージャ」の役目となります。

X11 では、Openbox, Xfwm (Xfce), Mutter (GNOME), KWin (KDE) などのウィンドウマネージャが存在します。
ユーザーは、好きなウィンドウマネージャを選んで、デスクトップを構築することができます。

ウィンドウマネージャ自体は、通常のクライアントと同じです。
ルートウィンドウに対してイベントマスクを選択したり、パッシブグラブを設定したりすることで、自身のクライアントで必要なイベントを受信して、処理します。
プロパティ
ウィンドウマネージャに限らず、クライアントが他のクライアントとの通信を行いたい場合は、ウィンドウの「プロパティ」とイベントを使用します。
(後に説明する「セレクション」も、クライアント間の通信用に使われます)

X ウィンドウには、プロパティと呼ばれる、名前と値がペアになったデータを複数設定することができ、それを他のクライアントが読み込んだり、他のクライアントによって値を設定させることで、異なるクライアント間での値のやりとりを行うことができます。

プロパティのデータは、X サーバーが保持しています。
アトム
プロパティの名前は、本質的には文字列ですが、X 上で扱う場合は、文字列を関連付けた XID の数値で指定する必要があります。
Atom として定義された型は、プロパティの名前などで使われます。

文字列からアトムを取得
Atom XInternAtom(Display *display, char *atom_name, Bool only_if_exists);

Status XInternAtoms(Display *display, char **names, int count, Bool only_if_exists, Atom *atoms_return);

指定文字列に関連付けられている既存のアトムを取得したり、文字列から新しいアトムを生成する場合は、XInternAtom, XInternAtoms 関数を使います。

only_if_exists 引数は、True の場合、既存のアトムのみを取得し、存在していない場合は None を返します。
False の場合、指定文字列に関連付けられているアトムがない場合は、新しく作成します。

XInternAtoms 関数は、複数の文字列から複数のアトムを取得します。
多数のアトムを取得したい場合は、XInternAtom() を個別に実行するよりも効率が良くなります。
すべてのアトムが返された場合は 0 以外、一つでも取得できなかったアトムがある場合は 0 が返ります。

なお、X サーバーには、事前に定義されたアトムが存在しており、<X11/Xatom.h> のインクルードファイルで、各アトムの数値が定義されています。
これ以外のアトムは、上記の関数で取得する必要があります。

アトムから文字列を取得
char *XGetAtomName(Display *display, Atom atom);
Status XGetAtomNames(Display *display, Atom *atoms, int count, char **names_return);

同様に、指定アトムに関連付けられている文字列を取得したい場合は、上記の関数を使います。

XGetAtomName で返された文字列は、XFree() で解放します。

XGetAtomNames の場合は、count 個の char * の配列を用意して、そのポインタを渡します。
各配列の位置に、それぞれ確保された文字列のポインタがセットされるので、配列内の各文字列を XFree() で解放する必要があります。
プロパティの変更
void XChangeProperty(Display *display, Window w, Atom property, Atom type,
    int format, int mode, unsigned char *data, int nelements);

任意のウィンドウのプロパティの値を変更したい場合は、XChangeProperty 関数を使います。

プロパティが変更されると、w のウィンドウで PropertyNotify イベントが生成されます。

propertyプロパティの名前を、アトムで指定します
typeプロパティの値のタイプを、アトムで指定します
formatデータのフォーマット (一つのデータのサイズ)。
8, 16, 32 で、それぞれ char, short, long の配列となります。
modePropModeReplace: 以前の値を破棄して、新しいデータに置き換える。
PropModePrepend: 既存のデータの前に挿入。
PropModeAppend: 既存のデータの後に追加。
dataセットするデータのポインタ
nelementsデータの個数

※64bit OS では、format = 32 の場合、long 型となるので、データは 64bit の整数として扱う必要があります。
X サーバー上では 32bit の数値として扱われますが、クライアント上では 64bit の数値になるので、注意してください。
送信時の最大サイズ
プロパティの値は、イメージデータなどをセットする場合、データサイズが大きくなる場合があります。

しかし、X サーバーにリクエストを送信する際、一つのリクエストで送信できるサイズは制限されているので、XChangeProperty() でプロパティの値を変更する際に、一定のサイズを超える場合は、データを分割して送る必要があります。
(最初に PropModeReplace で置き換えて、残りを PropModeAppend で追加する)

long XMaxRequestSize(Display *display);
long XExtendedMaxRequestSize(Display *display);

一つのリクエストで送れる最大サイズは、XMaxRequestSize() で取得することができます。
X.Org の場合、65535 になっています (バイト単位だと、約 256 KB)。
戻り値は4バイト単位の値になっているので、バイト単位にする場合は、4を掛けます。

なお、X の拡張機能として、これよりも大きなサイズを送れる場合があり、XExtendedMaxRequestSize() で、拡張機能を使用した場合の最大サイズを取得することができます (4バイト単位)。
戻り値が 0 の場合、この拡張機能に対応していません。

X.Org の Xlib 実装の場合は、XExtendedMaxRequestSize() で 4194303 (約 16 MB) の値が返りますが、XChangeProperty() のソースコードを見てみると、4バイト単位で 65535 の長さにしか対応しておらず、それ以上のサイズの場合は、プロパティに値が設定されません (自動で分割処理をするような機能はない)。

そのため、XChangeProperty() でプロパティを変更する場合、XMaxRequestSize() で取得したサイズを超える場合は、複数回 XChangeProperty() を呼び出して、分割処理をする必要があります。
プロパティ名のリストを取得
Atom *XListProperties(Display *display, Window w, int *num_prop_return);

ウィンドウに現在設定されている、すべてのプロパティの名前のアトムを取得できます。
戻り値は XFree() で解放します。プロパティがない場合は NULL が返ります。
プロパティの読み込み
int XGetWindowProperty(Display *display, Window w, Atom property, long long_offset, long long_length,
    Bool delete, Atom req_type, Atom *actual_type_return, int *actual_format_return,
    unsigned long *nitems_return, unsigned long *bytes_after_return, unsigned char **prop_return);

ウィンドウからプロパティを読み込みます。

propertyプロパティの名前のアトム
long_offsetプロパティ内のオフセット位置を、4 byte 単位で指定する
long_length読み込むデータの長さを、4 byte 単位で指定する。
実際より大きな値を指定しても良い。
deleteTrue の場合、すべて読み込んだ後にプロパティを削除する
req_typeプロパティ値のタイプを示すアトム。
AnyPropertyType を指定した場合、任意のタイプ。
actual_type_return実際のプロパティ値のタイプが返る
actual_format_return実際のプロパティのフォーマットが返る (8,16,32)
nitems_return実際に読み込んだ数が返る。
指定した長さより少ない場合がある。
フォーマットごとの個数になります (8 = 1byte, 16 = 2byte, 32 = 4byte)。
bytes_after_returnプロパティの残りのバイト数が返る (常にバイト単位)。
指定オフセットから指定長さを読み込んだ後、その後に、どれだけのサイズのデータが残っているか。
prop_return確保されたバッファのポインタが返る。ここにデータが格納されている。
XFree() で解放する。
フォーマットごとに、「8 = char *」「16 = short *」「32 = long *」に型変換して読み込む。

なお、文字列データを取得した際に、終端が常にヌル文字になるように、確保されたバッファの終端には、1バイト分が余分に確保されており、そこに 0 がセットされています (実際のデータのサイズには影響しない)。
戻り値Success で成功

  • プロパティがウィンドウに存在しない場合、actual_type_return = None、actual_format_return = 0, bytes_after_return = 0, nitems_return = 0 となります。この場合、delete 引数は無視されます。
  • プロパティは存在するが、値のタイプが一致しない場合、actual_type_return = 実際のタイプ、actual_format_return = 実際のプロパティ形式、bytes_after_return = プロパティ長、nitems_return = 0 になります。また、delete 引数は無視されます。

64bit OS の場合、データの扱いが少しややこしくなるので、注意してください。
使い方
まず、読み込む先頭位置 (long_offset) と読み込む長さ (long_length) は、プロパティのフォーマットが何であれ、常に 4 byte 単位の値で指定する必要があります。

また、プロパティのセット時 (XChangeProperty) 時に指定された値のタイプと、読み込み時のタイプ (req_type) は一致する必要があります。
req_type が AnyPropertyType の場合は、すべてのタイプが読み込み可能です。

ウィンドウに指定プロパティが存在して、タイプが一致する場合は、各引数で渡したポインタに値が返ります。

なお、プロパティの現在のサイズを取得したい場合は、以下のように、long_offset と long_length を 0 にして、req_type に AnyPropertyType を指定します。

XGetWindowProperty(display, window, prop, 0, 0,
    False, AnyPropertyType, &type, &format, &nitems, &after, &buf);

bytes_after_return に、先頭からの残りバイト数が返るので、これがプロパティのサイズになります (X サーバー上におけるサイズ)。

データの読み込み
戻り値が Success の場合、prop_return に確保されたバッファのポインタが返るので、そのポインタからデータを読み込みます。

actual_format_return にプロパティのフォーマット値 (8,16,32) が返るので、データはその単位の配列となります。
それぞれ、「8 = 1byte」「16 = 2byte」「32 = (32bit OS なら 4byte、64bit OS なら 8byte)」単位の配列になります。

format = 32 の場合は、long 型の配列になることに注意してください。
サーバー内部では、format = 32 のデータは 32 bit 整数として扱われていますが、サーバーからクライアントに値を送信する際に、long 型に変換されます。

nitems_return に、実際に読み込んだデータの長さ (フォーマット単位) が返ります。
この場合、「8 = char」「16 = short」「32 = long」として、その個数が返ります。

bytes_after_return には、読み込んだ位置以降に存在する、残りのプロパティデータの長さが、バイト単位で返ります。
この場合は、フォーマットに関係なく、常にバイト数となります。
(format = 32 の場合、1つのデータは常に 4byte として扱われます)

format = 32
サーバー内部では、format = 32 のプロパティデータは、32bit 整数として扱われているので、オフセット位置や長さを指定する際は、1つのデータを 4 byte として計算する必要があります。

しかし、サーバーからクライアントに値を送信する際は、long 型の整数に変換されるので、64bit OS の場合、クライアント側では 64bit の整数として扱う必要があります。
プログラム
ウィンドウに "_TEST_ATOM_" という名前のプロパティを設定し、format = 32 の数値として 123 をセットします。

ウィンドウのマップ時に、現在設定されているプロパティの名前のリストを表示し、また、"_TEST_ATOM_" プロパティから値を読み込んで表示します。

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

$ cc -o run d01-property.c util.c -lX11

<d01-property.c>
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include "util.h"

/* ウィンドウのプロパティのリストを出力 */

static void _put_property(Display *disp,Window win)
{
    Atom *prop;
    char **names;
    int i,num;

    prop = XListProperties(disp, win, &num);
    if(prop)
    {
        names = (char **)malloc(sizeof(char *) * num);
    
        XGetAtomNames(disp, prop, num, names);
        
        for(i = 0; i < num; i++)
        {
            if(names[i])
            {
                printf("%s\n", names[i]);
                XFree(names[i]);
            }
        }

        printf("\n");

        free(names);
        
        XFree(prop);
    }
}

/* プロパティ読み込み */

static void _read_prop(Display *disp,Window win,Atom atom)
{
    Atom type;
    int format;
    unsigned long nitem,after;
    unsigned char *buf;

    if(XGetWindowProperty(disp, win, atom, 0, 1, False, atom,
        &type, &format, &nitem, &after, &buf) == Success)
    {
        printf("read property: %ld\n", *((long *)buf));
    
        XFree(buf);
    }
}                    

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

    printf("MaxRequestSize: %ld\n", XMaxRequestSize(disp));
    printf("ExtendedMaxRequestSize: %ld\n\n", XExtendedMaxRequestSize(disp));

    //ウィンドウ作成

    win = create_test_window(disp, ButtonPressMask | StructureNotifyMask);

    //プロパティをセット

    atom = XInternAtom(disp, "_TEST_ATOM_", False);

    ldat = 123;
    XChangeProperty(disp, win, atom, atom, 32, PropModeReplace, (unsigned char *)&ldat, 1);

    //イベント

    XMapWindow(disp, win);

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

        switch(ev.type)
        {
            case MapNotify:
                _put_property(disp, win);
                _read_prop(disp, win, atom);
                break;
            case ButtonPress:
                goto END;
        }
    }

END:
    XCloseDisplay(disp);

    return 0;
}
結果
MaxRequestSize: 65535
ExtendedMaxRequestSize: 4194303

WM_STATE
_NET_WM_DESKTOP
_NET_WM_ALLOWED_ACTIONS
_KDE_NET_WM_FRAME_STRUT
_NET_FRAME_EXTENTS
_NET_WM_STATE
_NET_WM_ICON
_OB_APP_TYPE
_OB_APP_TITLE
_OB_APP_GROUP_CLASS
_OB_APP_GROUP_NAME
_OB_APP_CLASS
_OB_APP_NAME
_OB_APP_ROLE
_NET_WM_VISIBLE_ICON_NAME
_NET_WM_VISIBLE_NAME
_TEST_ATOM_

read property: 123

ウィンドウがマップされた時点で、ウィンドウマネージャによって各プロパティが設定されているので、それが表示されます。
プログラムによってセットされた "_TEST_ATOM_" もあります。

プロパティ名やタイプは、任意の文字列で定義したアトムを使用することができます。
ウィンドウマネージャなどでは、特定の名前のアトムを使用することになっているので、それらのアトムを使って、ウィンドウの属性を変更したりすることができます。