X11: XKB (3) - 仮想修飾子

仮想修飾子について
キー修飾子は「Shift, Lock, Ctrl, Mod1〜Mod5」が使われます。

Shift, Ctrl, CapsLock は、そのまま名前通りのキーになるので問題ありませんが、Mod1〜Mod5 は、キーボードのセットによって割り当てが異なるため、コア X のみで、どの修飾子がどのキーに対応するかを判断するには、少し面倒な手順が必要になります。

なお、Mod1〜Mod5 で使われるのは、NumLock, Alt, Windows キーなどです。
修飾子に対応するキーを調べる
例えば、コア X で、Alt がどの修飾子 (Mod1〜Mod5) に対応しているかを調べたい場合は、以下のようにする必要があります。
※X のキーマッピングについては、(X11) キーマッピング を参照してください。

  1. キーマッピングと修飾子マッピングを取得する (XGetKeyboardMapping, XGetModifierMapping)。
    キーマッピング = キーコードに対応する KeySym のリスト。
    修飾子マッピング = 各修飾子に対応するキーコードのリスト。
  2. 修飾子マッピングから、Mod1〜Mod5 に対応する各キーコードを取得。
  3. 上記のキーコードを元に、キーマッピングを参照し、XK_Alt_R または XK_Alt_L の KeySym が含まれているものを調べる。
  4. 存在すれば、その修飾子は Alt キーである。

ただし、キーマッピングや修飾子マッピングは、X サーバーの起動中に変更される可能性があるので、マッピングが変更された場合 (MappingNotify イベントが来た時) は、上記の処理を再度行う必要があります。
仮想修飾子
このように、コア X では、Alt キーや Windows キーなどの修飾子が扱いにくいので、XKB では、この負担軽減のために、仮想修飾子を使うことができます。

仮想修飾子は、名前付きで16個まで定義でき、それぞれに、「Shift, Lock, Ctrl, Mod1〜Mod5」の任意のセットを設定することができます。
仮想修飾子の情報
仮想修飾子は、XKB の設定によって、あらかじめ定義されています。
名前
各仮想修飾子の名前は、前回行った、XkbDescRec 構造体の names->vmods (Atom) から取得することができます。

[0] "NumLock"
[1] "Alt"
[2] "LevelThree"
[3] "LevelFive"
[4] "Meta"
[5] "Super"
[6] "Hyper"
[7] "ScrollLock"

この名前を元に、特定の仮想修飾子のインデックス (0〜15) を取得することができます。
上記の場合、Alt キーを使いたいなら、index 1 であることを記憶しておきます。
仮想修飾子と実際の修飾子の関連付け
仮想修飾子には、X で使われる実際の修飾子のマスクが関連付けられています。

この情報は、サーバーマップ (XkbDescRec 構造体の server) の vmods にあります。

#define XkbNumVirtualMods 16

typedef struct {
    unsigned short     num_acts;
    unsigned short     size_acts;
    XkbAction *        acts;
    XkbBehavior *      behaviors;
    unsigned short *   key_acts;
    unsigned char *    explicit;
    unsigned char      vmods[XkbNumVirtualMods];
    unsigned short *   vmodmap;
} XkbServerMapRec, *XkbServerMapPtr;

vmods には、各仮想修飾子に対応する、実際の修飾子のマスクが設定されています。

[0] "NumLock" 0x10 (Mod2Mask)
[1] "Alt" 0x08 (Mod1Mask)
[2] "LevelThree" 0x80 (Mod5Mask)
[3] "LevelFive"  0x20 (Mod3Mask)
[4] "Meta" 0x08 (Mod1Mask)
[5] "Super" 0x40 (Mod4Mask)
[6] "Hyper" 0x40 (Mod4Mask)
[7] "ScrollLock" 0

※ScrollLock が実際のキーボードに存在していたとしても、XKB の設定で修飾子として指定されていなければ、キーは動作しません。ここでは、Mod1〜Mod5 まですべて使用されているので、ScrollLock 修飾子が設定できる余地がありません。

この情報によって、Alt (Meta) が Mod1Mask に対応している、というようなことが判定できます。
キーコードに対応する仮想修飾子
サーバーマップの vmodmap には、各キーコードに対応する、仮想修飾子のマスクが設定されています。
キーコードをインデックスとした、unsigned short の配列になっています。

例えば、左 Alt・右 Alt に対応するキーコードには、それぞれ、0x12 ((1<<1) | (1<<4)) が設定されます。
上記の例では、Alt (index = 1) と Meta (index = 4) が、両方とも Mod1 としての扱いになるので、そのインデックスをビット位置としたマスクになります。

配列数
なお、vmodmap の配列数がどこにも定義されていませんが、キーコードをインデックスとして扱う配列データの場合、キーコードは XkbDescRec 構造体の min_key_code から max_key_code の範囲まで存在するので、array[max_key_code] までの値が取得できるようにするため、(max_key_code + 1) 個は確実に存在します。
基本的には 8〜255 の範囲となるので、256 個です。

※min_key_code 未満のキーコードは使用されませんが、配列上にはその分のデータも存在します。
キーコードに対応する実際の修飾子
各キーコードに対応する、実際の修飾子を取得したい場合は、クライアントマップ (XkbDescRec 構造体の map) の modmap を参照します。

typedef struct {
    unsigned char   size_types;
    unsigned char   num_types;
    XkbKeyTypePtr   types;
    unsigned short  size_syms;
    unsigned short  num_syms;
    KeySym *        syms;
    XkbSymMapPtr    key_sym_map;
    unsigned char * modmap;
} XkbClientMapRec, *XkbClientMapPtr;

modmap は、キーコードをインデックスとした配列で、そのキーに対応する、実際の修飾子のマスクが設定されています。
配列の数に関しては、vmodmap と同様です。

なお、サーバーマップの修飾子情報が変更されると、このデータも自動的に更新されます。
(XkbDescRec 構造体ですでに値を取得している場合は、データの再取得が必要)
プログラム
仮想修飾子の情報を表示します。

$ cc -o run e13-xkb3.c util.c -lXlib

<e13-xkb3.c>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include "util.h"

static void _put_vmods(XkbDescPtr p)
{
    int i;

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

    for(i = 0; i < XkbNumVirtualMods; i++)
    {
        if(p->names->vmods[i])
        {
            printf("[%d] \"", i);
            put_atom_name(p->names->vmods[i]);
            printf("\" 0x%02x\n", p->server->vmods[i]);
        }
    }
}

static void _put_vmodmap(XkbDescPtr p)
{
    int i;

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

    for(i = p->min_key_code; i <= p->max_key_code; i++)
    {
        if(p->server->vmodmap[i])
        {
            printf("[%d] %.4s (0x%04x)\n",
                i, p->names->keys[i].name, p->server->vmodmap[i]);
        }
    }
}

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

    set_display(disp);

    //XKB 初期化

    if(init_xkb(disp, NULL))
        return 1;

    //データ取得

    desc = XkbGetKeyboard(disp, XkbAllComponentsMask, XkbUseCoreKbd);

    printf("keycode: %d - %d\n",
        desc->min_key_code, desc->max_key_code);

    _put_vmods(desc);
    _put_vmodmap(desc);

    XkbFreeKeyboard(desc, 0, True);

    XCloseDisplay(disp);

    return 0;
}
実行例
keycode: 8 - 255
--- vmods ---
[0] "NumLock" 0x10
[1] "Alt" 0x08
[2] "LevelThree" 0x80
[3] "LevelFive" 0x20
[4] "Meta" 0x08
[5] "Super" 0x40
[6] "Hyper" 0x40
[7] "ScrollLock" 0x00
--- vmodmap ---
[64] LALT (0x0012)
[77] NMLK (0x0001)
[92] LVL3 (0x0004)
[108] RALT (0x0012)
[133] LWIN (0x0020)
[134] RWIN (0x0020)
[203] LVL5 (0x0008)
[204] ALT (0x0002)
[205] META (0x0010)
[206] SUPR (0x0020)
[207] HYPR (0x0040)

キーコード 77 の "NMLK" キーは NumLock なので、vmodmap では、仮想修飾子の index = 0 (0x0001) が ON になっています。
"LALT" が左 Alt、"RALT" が右 Alt。
"LWIN" が左 Windows キー、"RWIN" が 右 Windows キーで、仮想修飾子の index = 5 (Super) が ON です。