X11: XKB (4) - クライアントマップ

クライアントマップ
クライアントマップ (XkbDescRec 構造体の map) では、クライアントのライブラリ側で使用されるような情報が取得できます。
主に、キーコードから KeySym を取得するための情報です。

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;

「キータイプ」「KeySym 配列」「キーコードごとのシンボル情報」「キーコードごとの実修飾子」があります。

modmap は前回紹介しました。
キーコードに対応する、実際の修飾子のマスクが設定されています。
キータイプ
キータイプは、KeySym 変換時に使用されるタイプです。
現在の修飾子に対応する KeySym に変換するための、シフトレベルを決定するために使われます。

例えば、どの修飾子が ON の状態でも、常に一つの KeySym に変換されるキーの場合は、"ONE LEVEL" のタイプ。
Shift 時とそれ以外で、2つの KeySym に変換される場合は、"TWO LEVEL" のタイプ。

というように、一つのキーコードから、修飾子の組み合わせでいくつの KeySym に変換されるかによって、タイプが分かれます。
types 配列には、このキーボードで使用されるすべてのキータイプが含まれています。

size_types と num_types は、types 配列の個数です。
size_types は、メモリ確保されている個数です。
num_types は、実際に有効なデータの個数です。

クライアント側が、関数によって配列のサイズを変更できるので、このように、個数が2つの変数で示されています。
KeySym の取得
syms の一次配列には、キーコードから変換する時に使用される KeySym がセットされています。

キーから KeySym を取得するには、「キーコード」「修飾子の状態」「キーグループ」の3つが必要になります。

まずは、key_sym_map 配列から、キーコードごとの情報を取得します。
その kt_index 配列から、指定グループ時に使用する、キータイプのインデックス値を取得します。

さらに、types[keytype_index] の XkbKeyTypeRec 構造体から、現在の修飾子状態を元に、その修飾子に対応するシフトレベルを決定します。
シフトレベルは、0〜の値です。

syms 配列内には、キーコードごとに、以下のような順で、連続してデータが並んでいます。

<group1>{ [shift0] [shift1] } <group2>{ [shift0] [shift1] }

そのキーのグループ数が2つで、シフトレベルが2つの場合、4個の KeySym が並ぶことになります。
ただし、キーコードごとに、必要なグループ数とシフトレベル数が異なるので、1つのキーコードごとの個数は可変です。

そのため、key_sym_map 配列から、キーコードごとの、syms 配列内での先頭位置 (offset) を取得します。
そこから、現在のグループインデックスと、上記で取得したシフトレベルの値を元に、位置を加算して、実際の KeySym を取得します。
キータイプ
types 配列には、各キータイプの情報が定義されており、最大で XkbMaxKeyTypes (255) 個定義できます。

ただし、先頭の XkbNumRequiredTypes (4) 個には、事前に定義された標準的なキータイプが並びます。
XkbKeyTypeRec 構造体
typedef struct {
    XkbModsRec        mods;
    unsigned char     num_levels;
    unsigned char     map_count;
    XkbKTMapEntryPtr  map;
    XkbModsPtr        preserve;
    Atom              name;
    Atom *            level_names;
} XkbKeyTypeRec, *XkbKeyTypePtr;

typedef struct _XkbMods {
    unsigned char   mask;
    unsigned char   real_mods;
    unsigned short  vmods;
} XkbModsRec, *XkbModsPtr;

typedef struct {
    Bool              active;
    unsigned char     level;
    XkbModsRec        mods;
} XkbKTMapEntryRec, *XkbKTMapEntryPtr;

modsこのキータイプが使用するすべての修飾子
num_levelsシフトレベルの合計数
map_countmap 配列の数
mapmap_count 個の配列。
修飾子の組み合わせによって、シフトレベルを決定するためのデータ。
preserveオプション。NULL の場合あり。
map_count 個の配列で、各 map に対応する。
map を使用してシフトレベルを決定する時に、それぞれのエントリで、消費すべきではない修飾子が指定される。
nameキータイプの名前
level_namesnum_level 個の Atom の配列。
各シフトレベルの名前。
XkbModsRec 構造体
この構造体は、実際の修飾子と仮想修飾子の両方を使って、修飾子のマスクを指定する時に使用されます。

maskreal_mods と、vmods に関連付けられている実際の修飾子を含むマスク。
XKB によって自動計算されるので、クライントが値を設定する際は、指定しなくて良い。
real_modsコア X の実際の修飾子のマスク
vmods仮想修飾子のマスク。
仮想修飾子のインデックスをビット位置とする。
XkbKTMapEntryRec 構造体
activeシフトレベルを決定するときに、mods の修飾子の組み合わせを考慮するか。
False の場合、この map エントリは無視されます。
levelactive が True の場合、現在の修飾子が、mods の修飾子と一致した場合に使用される、シフトレベル (0〜)。
mods修飾子。
AND ではなく = で判定します。

エントリのいずれにも一致しなかった場合は、level = 0 が使用されます。
シンボルマップ
XkbClientMapRec 構造体の key_sym_map は、キーコードをインデックスとした配列です。
syms 配列から KeySym を取得するための情報です。

#define XkbNumKbdGroups  4
#define XkbMaxKbdGroup   (XkbNumKbdGroups-1)

typedef struct {
    unsigned char   kt_index[XkbNumKbdGroups];
    unsigned char   group_info;
    unsigned char   width;
    unsigned short  offset;
} XkbSymMapRec, *XkbSymMapPtr;

kt_index各グループごとの、使用するキータイプ (types 配列) のインデックス
group_info下位 4bit に、このキーのグループ数。
上位 4bit は、グループが範囲外の場合の処理方法のフラグ。

XkbRedirectIntoRange (0x80) : 無効なグループ番号は、(XkbDescRec) ctrls->groups_wrap の下位 4bit をグループインデックスとして使用します。それでも範囲外の場合は、group_index = 0 が使用されます。

XkbClampIntoRange (0x40) : 無効なグループ番号は、最も近い有効なグループ番号に変換されます。1 未満なら 1、最大値より大きい場合は、有効な最大値。

それ以外 : グループ番号は、グループ数で割った余りで、有効なグループ番号に変換されます。
widthこのキーの最大シフトレベル数
offsetクライアントマップの syms 配列内の先頭インデックス。
syms 配列から対応する KeySym を取得する場合、syms[offset + group_index * width + shift_level] となる。
プログラム
クライアントマップから、キータイプの一覧と、シンボルマップを表示します。

$ cc -o run e14-xkb4.c util.c -lXlib

e14-xkb4.c
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include "util.h"

static void _put_types(XkbClientMapPtr p)
{
    int i,j;
    XkbKeyTypePtr pt;
    XkbKTMapEntryPtr pm;
    XkbModsPtr ppr;

    printf("size_types: %d\n"
        "num_types: %d\n",
        p->size_types, p->num_types);

    pt = p->types;

    for(i = 0; i < p->num_types; i++, pt++)
    {
        printf("\n--- [%d] ---\n", i);
        put_atom_name_line("name: ", pt->name);
        
        printf("mods: mask(0x%x) real(0x%x) vmods(0x%x)\n"
        "num_levels: %d\n",
        pt->mods.mask, pt->mods.real_mods, pt->mods.vmods, pt->num_levels);

        for(j = 0; j < pt->num_levels; j++)
        {
            printf("level[%d]: ", j);
            put_atom_name_line(NULL, pt->level_names[j]);
        }

        //map

        pm = pt->map;

        for(j = 0; j < pt->map_count; j++, pm++)
        {
            printf("<map[%d]> active(%d) level(%d) mods[mask(0x%x):real(0x%x):vmods(0x%x)]\n",
                j, pm->active, pm->level,
                pm->mods.mask, pm->mods.real_mods, pm->mods.vmods);
        }

        //preserve

        if(pt->preserve)
        {
            ppr = pt->preserve;
            
            for(j = 0; j < pt->map_count; j++, ppr++)
            {
                if(ppr->mask)
                {
                    printf("<preserve[%d]> mask(0x%x):real(0x%x):vmods(0x%x)\n",
                        j, ppr->mask, ppr->real_mods, ppr->vmods);
                }
            }
        }
    }
}

static void _put_symmap(XkbDescPtr p,int min,int max)
{
    int i,j,group_num;
    XkbSymMapPtr pm;
    KeySym *sym;
    char *name;

    printf("\n==== key_sym_map ====\n\n");

    pm = p->map->key_sym_map + min;

    for(i = min; i <= max; i++, pm++)
    {
        printf("[%d]<%.4s> kt_index(%d,%d,%d,%d)"
        " group_info(0x%x) width(%d) offset(%d)\n",
            i, p->names->keys[i].name,
            pm->kt_index[0], pm->kt_index[1],
            pm->kt_index[2], pm->kt_index[3],
            pm->group_info, pm->width, pm->offset);

        sym = p->map->syms + pm->offset;
        group_num = pm->group_info & 0x0f;

        for(j = group_num * pm->width; j; j--)
        {
            name = XKeysymToString(*(sym++));
            if(name)
                printf(" %s", name);
            else
                printf(" <null>");
        }

        printf("\n");
    }
}

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);

    _put_types(desc->map);

    _put_symmap(desc, desc->min_key_code, desc->max_key_code);

    XkbFreeKeyboard(desc, 0, True);

    XCloseDisplay(disp);

    return 0;
}
実行例
キータイプ
size_types: 24
num_types: 24

--- [0] ---
name: "ONE_LEVEL"
mods: real(0x0) vmods(0x0)
num_levels: 1
level[0]: "Any"

--- [0] ---
name: "ONE_LEVEL"
mods: mask(0x0) real(0x0) vmods(0x0)
num_levels: 1
level[0]: "Any"

--- [1] ---
name: "TWO_LEVEL"
mods: mask(0x1) real(0x1) vmods(0x0)
num_levels: 2
level[0]: "Base"
level[1]: "Shift"
<map[0]> active(1) level(1) mods[mask(0x1):real(0x1):vmods(0x0)]

--- [2] ---
name: "ALPHABETIC"
mods: mask(0x3) real(0x3) vmods(0x0)
num_levels: 2
level[0]: "Base"
level[1]: "Caps"
<map[0]> active(1) level(1) mods[mask(0x1):real(0x1):vmods(0x0)]
<map[1]> active(1) level(1) mods[mask(0x2):real(0x2):vmods(0x0)]

--- [3] ---
name: "KEYPAD"
mods: mask(0x11) real(0x1) vmods(0x1)
num_levels: 2
level[0]: "Base"
level[1]: "Number"
<map[0]> active(1) level(1) mods[mask(0x10):real(0x0):vmods(0x1)]

...

--- [7] ---
name: "CTRL+ALT"
mods: mask(0x8d) real(0x5) vmods(0x6)
num_levels: 5
level[0]: "Base"
level[1]: "Shift"
level[2]: "AltGr"
level[3]: "Shift AltGr"
level[4]: "Ctrl+Alt"
<map[0]> active(1) level(1) mods[mask(0x1):real(0x1):vmods(0x0)]
<map[1]> active(1) level(2) mods[mask(0x80):real(0x0):vmods(0x4)]
<map[2]> active(1) level(3) mods[mask(0x81):real(0x1):vmods(0x4)]
<map[3]> active(1) level(4) mods[mask(0xc):real(0x4):vmods(0x2)]
<preserve[0]> mask(0x1):real(0x1):vmods(0x0)
<preserve[2]> mask(0x1):real(0x1):vmods(0x0)

...

最初の4個は、事前定義されたキータイプです。

ONE_LEVELシフトレベルが1つしかなく、修飾子も全く使用されません。
常に1つの KeySym にしか変換されないキー用です。
TWO_LEVELシフトレベルが2つで、Shift 時と、それ以外です。
map[0] で、実際の修飾子の ShiftMask が ON の場合 (Shift は仮想修飾子には含まれないので、vmods は 0)、level = 1 が指定されます。それ以外は level = 0 になります。
ALPHABETICCapsLock に対応するキー (アルファベット) 用です。
全体として、Shift と Lock (0x02) の修飾子を使います。

Shift のみ ON の場合と、CapsLock のみ ON の場合、level = 1 になります。
この場合、level = 0 なら小文字、level = 1 なら大文字になります。
CapsLock と Shift が両方とも ON の場合は小文字になるので、map でこの状態が指定されていないのは正しいです。
KEYPADテンキーなどのキーパッド用のキーです。
全体として、Shift と NumLock を使います。
(vmods の NumLock [index = 0] は、実修飾子で Mod2Mask になるので、mods.mask は 0x11 になります)

NumLock のみ ON の場合、level = 1 になります。
なお、Shift 時に KeySym が変化する場合は、別のキータイプが使われます。
CTRL+ALT全体として、Shift, Ctrl, Alt [index = 1], LevelThree [index = 2] の修飾子を使います。
mods.mask は、Shift, Ctrl, Mod1, Mod5 となっています。

シフトレベル数は5つで、以下のようになります。
Shift : level 1 (Shift は消費しない)
LevelThree : level 2
Shift+LevelThree : level 3 (Shift は消費しない)
Ctrl+Alt : level4

この場合、preserve の情報があるので、Shift は実際には修飾子として消費されません。
シンボルマップ
[8]<> kt_index(0,0,0,0) group_info(0x0) width(0) offset(1)

[9]<ESC> kt_index(0,0,0,0) group_info(0x1) width(1) offset(1)
 Escape
[10]<AE01> kt_index(1,0,0,0) group_info(0x1) width(2) offset(2)
 1 exclam
...
[24]<AD01> kt_index(2,0,0,0) group_info(0x1) width(2) offset(30)
 q Q
[25]<AD02> kt_index(2,0,0,0) group_info(0x1) width(2) offset(32)
 w W
...
[79]<KP7> kt_index(3,0,0,0) group_info(0x1) width(2) offset(166)
 KP_Home KP_7

このキーボード全体で使われるグループ数は1つなので、常に group_index = 0 が使われます。

keycode 9 (ESC)kt_index[0] = 0 なので、キータイプ index 0 (ONE_LEVE) が使用されます。
group_info の下位 4bit は 1 なので、このキーのグループ数は 1 です。
width = 1 で、シフトレベル数は1つです。

このキーは、修飾子に関係なく、常に ESC キーとなります。
syms[offset(=1)] に、XK_Escape の KeySym があります。
keycode 10 ('1')kt_index[0] = 1 なので、キータイプ index 1 (TWO_LEVEL) が使用されます。
level = 0 の通常時は '1'。
Shift のみ ON の場合は level = 1 になるので、syms[offset(=2) + 1] で、XK_exclam ('!') になります。
keycode 24 ('Q')kt_index[0] = 2 なので、キータイプ index 2 (ALPHABETIC) が使用されます。
keycode 79 (Num 7)テンキーの 7 です。
キータイプ index 3 (KEYPAD) が使用されます。
NumLock OFF の場合と ON の場合で2通りあります。
関数
なお、キーコードから KeySym を取得する関数などは別にあるので、わざわざクライアントマップを参照する必要はほとんどありません。

//キーコードとグループインデックス、シフトレベルから KeySym を取得

KeySym XkbKeycodeToKeysym(Display *dpy, KeyCode kc, unsigned int group, unsigned int level);

//KeySym に関連付けられている実修飾子を取得

unsigned int XkbKeysymToModifiers(Display *dpy, KeySym ks);

//キーコードと修飾子(キーイベント時の state)から KeySym を取得
// mods_rtrn : 消費された修飾子が返る

Bool XkbLookupKeySym(Display *dpy, KeyCode key, unsigned int state, unsigned int *mods_rtrn, KeySym *sym_rtrn);

//XLookupString と同等。KeySym から文字列を取得

int XkbLookupKeyBinding(Display *dpy, KeySym sym, unsigned int state, char *buf, int nbytes, int *extra_rtrn);

//KeySym に修飾子を適用したものを返し、文字列があればそれも返す

int XkbTranslateKeySym(Display *dpy, KeySym *sym_inout, unsigned int mods,
    char *buf, int nbytes, int *extra_rtrn);

//XkbDescRec 構造体を元に、キーコードと修飾子から KeySym を取得

Bool XkbTranslateKeyCode(XkbDescPtr xkb, KeyCode key, unsigned int mods,
    unsigned int *mods_rtrn, KeySym *keysym_rtrn);