X11: ポインタの設定

ポインタボタンのマッピング
ポインタには、物理ボタン番号と、論理ボタン番号があります。

物理ボタン番号は、ポインタデバイスのボタン番号で、論理ボタン番号は、X のイベントなどで使われるボタン番号です。

X では、物理ボタンと論理ボタンのマッピングを変更することができるので、左ボタンと右ボタンを入れ替えるといったことができます。

「$ xmodmap -pp」で、現在のボタンマッピングを表示することができます。

ただし、Xlib で扱えるポインタボタンは、最大で 10 個までです。
左・右・中ボタン、ホイール (上下左右) を除くと、追加で3個のボタンにしか対応できません。
(XInput の拡張機能を使う場合は別)
ボタンマッピングの変更
int XSetPointerMapping(Display *display, unsigned char map[], int nmap);

ポインタボタンのマッピングを変更します。
成功すると、X サーバーは MappingNotify イベントを生成します。

※X サーバーが起動している間は永続的に維持され、すべてのクライアントにおいて適用されます。

map各物理ボタンに対応する、論理ボタンの配列。
[0] は物理ボタン1となり、値は論理ボタン番号を指定する。
値が 0 の場合、ボタンを無効にする。
配列内で同じ論理ボタン値を持つことはできません。
nmap配列の数。
XGetPointerMapping() が返す長さと同じであること。そうでない場合は、BadValue エラーが発生します。
戻り値MappingSuccess で成功。
変更されるボタンのいずれかが論理的に押されている状態の場合は MappingBusy を返し、マッピングは変更しない。
ボタンマッピングの取得
int XGetPointerMapping(Display *display, unsigned char map_return[], int nmap);

map_return に、現在のマッピング状態を返します。
戻り値は、実際にポインタ上にある物理ボタンの数を返します。
プログラム
起動時に現在のボタンマッピングを表示し、左ボタンを右ボタンを入れ替えます。
ボタンを押した時、ボタン番号が変わっているのを確認してください。

そのままだと、X サーバーの起動中、設定が永続的に適用されてしまうので、終了時に元に戻します。
必ず Enter キーで終了してください。

<12-btnmap.c>
#include <stdlib.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>

unsigned char *g_buf = 0;
int g_num = 0, g_left = -1, g_right = -1;

/* 左ボタンと右ボタンを入れ替え */

static void _set_mapping(Display *disp)
{
    unsigned char tmp;
    int ret;

    if(g_num == -1)
        return;
    else if(g_left == -1 || g_right == -1)
    {
        printf("unfound left or right button\n");
        g_num = -1;
        return;
    }

    tmp = g_buf[g_left];
    g_buf[g_left] = g_buf[g_right];
    g_buf[g_right] = tmp;

    ret = XSetPointerMapping(disp, g_buf, g_num);

    if(ret == MappingSuccess)
        printf("Mapping: success\n");
    else if(ret == MappingBusy)
    {
        printf("Mapping: busy\n");
        g_num = -1;
    }
}

/* 起動時 */

static void _mapping(Display *disp)
{
    int i;

    g_num = XGetPointerMapping(disp, 0, 0);

    g_buf = (unsigned char *)malloc(g_num);

    //現在の値

    XGetPointerMapping(disp, g_buf, g_num);

    printf("-- current --\n");

    for(i = 0; i < g_num; i++)
    {
        printf("[%d] %d\n", i, g_buf[i]);

        if(g_buf[i] == Button1)
            g_left = i;
        else if(g_buf[i] == Button3)
            g_right = i;
    }

    printf("-------------\n");

    //左ボタンと右ボタンを入れ替え

    _set_mapping(disp);
}

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

    //ウィンドウ作成

    attr.background_pixel = 0;
    attr.event_mask = ButtonPressMask | ButtonReleaseMask | KeyPressMask;

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

    XMapWindow(disp, win);

    //マッピング

    _mapping(disp);

    //イベント

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

        switch(ev.type)
        {
            case ButtonPress:
            case ButtonRelease:
                printf("* [%s] button(%u)\n",
                    (ev.type == ButtonPress)? "Press": "Release",
                    ev.xbutton.button);

                fflush(stdout);
                break;
            case MappingNotify:
                printf("- [Mapping] request(%d)\n", ev.xmapping.request);
                fflush(stdout);
                break;
            case KeyPress:
                key = XLookupKeysym((XKeyEvent *)&ev, 0);
                if(key == XK_Return || key == XK_KP_Enter) goto END;
                break;
        }
    }

    //
END:
    _set_mapping(disp);
    free(g_buf);

    XCloseDisplay(disp);

    return 0;
}

左か右のボタンを押した状態でプログラムを起動すると、MappingBusy でマッピングの変更に失敗します。
また、左ボタンと右ボタンの値が見つからない場合は、マッピングを変更しません。
ほか
他にも、XChangePointerControl() で、ポインタの移動速度を変更できます。

また、ポインタの位置を、現在のウィンドウから、任意のウィンドウの指定位置に移動させたい場合は、XWarpPointer() を使います。