X11: キーマッピング

キーマッピング
Xlib 内部には、キーコード (物理キー番号) に対応する KeySym のリストと、X のキー修飾子 (Shift, Lock, Control, Mod1〜Mod5) に対応するキーコードのリストがあります。

X サーバーは、これらのデータを接続開始時やマッピング変更時にクライアントに送って、Xlib に保存させます。

Xlib は、このマッピングデータを使って、キーが押された時にキーコードから KeySym に変換したり、修飾キーが押された時に、対応する修飾子を ON にします。
プログラム
まずは、現在のキーマッピングを表示してみます。

<15-keymap.c>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>

int main(int argc,char **argv)
{
    Display *disp;
    KeySym *buf,*ptr,ksym;
    XModifierKeymap *mmap;
    KeyCode *kptr;
    char *name;
    int i,j,cnt,min,max,num;
    
    disp = XOpenDisplay(NULL);
    if(!disp) return 1;

    XDisplayKeycodes(disp, &min, &max);

    printf("min: %d, max: %d\n", min, max);

    //キーマップ

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

    cnt = max - min + 1;

    buf = ptr = XGetKeyboardMapping(disp, min, cnt, &num);

    for(i = 0; i < cnt; i++)
    {
        printf("[%d]", min + i);
        
        for(j = 0; j < num; j++, ptr++)
        {
            if(*ptr == NoSymbol)
                printf(" -");
            else if(*ptr == XK_VoidSymbol)
                printf(" <void>");
            else
            {
                name = XKeysymToString(*ptr);

                printf(" %s", (name)? name: "<?>");
            }
        }

        printf("\n");
    }

    //修飾子マップ

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

    mmap = XGetModifierMapping(disp);

    kptr = mmap->modifiermap;

    for(i = 0; i < 8; i++)
    {
        printf("[%d]", i);
        
        for(j = 0; j < mmap->max_keypermod; j++, kptr++)
        {
            printf(" %d", *kptr);

            if(*kptr)
            {
                ptr = buf + (*kptr - min) * num;
                ksym = (ptr[0] == NoSymbol)? ptr[1]: ptr[0];
                
                if(ksym != NoSymbol)
                {
                    name = XKeysymToString(ksym);
                    if(name) printf("(%s)", name);
                }
            }
        }

        printf("\n");
    }

    //

    XFree(buf);
    XFreeModifiermap(mmap);

    XCloseDisplay(disp);

    return 0;
}

※Xlib には、キーコードから KeySym を取得する XKeycodeToKeysym 関数が存在しますが、現在非推奨となっているため、キーマッピングのデータから KeySym を取得しています。
関数を使って取得したい場合は、代わりに XKB 拡張機能を使ってください。
実行結果
min: 8, max: 255

--- key map ---
[8] - - - - - - -
[9] Escape - Escape - - - -
[10] 1 exclam 1 exclam - - -
[11] 2 quotedbl 2 quotedbl - - -
...

--- modifier map ---
[0] 50(Shift_L) 62(Shift_R) 0 0
[1] 66(Eisu_toggle) 0 0 0
[2] 37(Control_L) 105(Control_R) 0 0
[3] 64(Alt_L) 108(Alt_R) 204(Alt_L) 205(Meta_L)
[4] 77(Num_Lock) 0 0 0
[5] 203(ISO_Level5_Shift) 0 0 0
[6] 133(Super_L) 134(Super_R) 206(Super_L) 207(Hyper_L)
[7] 92(ISO_Level3_Shift) 0 0 0
関数
キーコードの範囲を取得
void XDisplayKeycodes(Display *display, int *min_keycodes_return, int *max_keycodes_return);

指定 Display でサポートされているキーコードの最小値と最大値を取得します。
最小値は8以上で、最大値は 255 以下です。
キーマッピングを取得
KeySym *XGetKeyboardMapping(Display *display, KeyCode first_keycode,
    int keycode_count, int *keysyms_per_keycode_return);

first_keycode から keycode_count 個のキーコードに対応する、KeySym の配列を取得します。
keysyms_per_keycode_return に、1つのキーコードごとの KeySym の個数が入ります。

確保された KeySym の配列が返ります。
first_keycode のキーコードから、(keyms_per_keycode_return x keycode_count) 個分のデータとなります。
使用後は XFree() で解放します。
KeySym の名前を取得
char *XKeysymToString(KeySym keysym);

KeySym の名前 (マクロの "XK_" を除外した名前) を返します。
返された文字列は静的領域にあるため、解放などはしないでください。
指定された KeySym が定義されていない場合、NULL を返します。
修飾子のマッピング
XModifierKeymap *XGetModifierMapping(Display *display);

XFreeModifiermap(XModifierKeymap *modmap);

typedef struct {
  int max_keypermod;     /* 1つの修飾子の KeyCode 数 */
  KeyCode *modifiermap;  /* max_keypermod x 修飾子8個 */
} XModifierKeymap;

XGetModifierMapping() で、修飾子のマッピングデータを取得します。
XFreeModifiermap() で、返された構造体を解放します。
キーマッピング
まず、XDisplayKeycodes() で、キーコードの最小値・最大値を取得します。
基本的に、8 と 255 です。

その後、XGetKeyboardMapping() でキーマッピングを取得します。
返された配列には、1つのキーコードに対して、複数 (固定数) の KeySym のリストがあります。
修飾子の状態によって、何番目の KeySym が使用されるかが決まります。
KeySym リスト
NoSymbol (0) は、未使用・未定義であることを表します。
リストの全てが NoSymbol であれば、使用されないキーです。

上記の結果例では、1つのキーコードに対して7個のリストがありますが、キーイベントによってキーコードから KeySym を取得する場合は、最初の4つのみを使います。

グループ
KeySym リストの1・2番目は「グループ1」時のキーで、3・4番目は「グループ2」時のキーを示します。

グループは、XK_Mode_switch が指定されたキーによって切り替えられます。

Shift などのキーとは別に、これを修飾子として割り当てると、修飾子が ON ならグループ2、OFF ならグループ1の KeySym が使われます。
グループによる区別がなければ、グループ1とグループ2は同じ値になります。

基本的に、グループ内の1番目は、通常時のキーで、グループ内の2番目は、+Shift 時のキーや、テンキーのキーです。
アルファベットの場合、1番目は小文字で、2番目は大文字です。

CapsLock、ShiftLock、NumLock
  • キーマッピング内で XK_Caps_Lock が指定されており、修飾子のマッピングで、Lock 修飾子にそのキーコードが割り当てられている場合、Lock 修飾子 (LockMask) は CapsLock として解釈されます。

    # キー
    [66] Eisu_toggle Caps_Lock Eisu_toggle Caps_Lock - - -
    # 修飾子
    [1] 66(Eisu_toggle) 0 0 0

    この場合、keycode 66 のグループ1・2の1番目が英数キーで、2番目が XK_Caps_Lock です。
    +Shift でなければ XK_Eisu_toggle で、+Shift の場合は XK_Caps_Lock になります。

    修飾子の [1] (Lock) にキーコード 66 が割り当てられており、そこに XK_Caps_Lock があるので、このキーは CapsLock として扱われます。

  • XK_Shift_Lock がキーマッピング内にあり、Lock 修飾子にそのキーコードが割り当てられている場合、Lock 修飾子は ShiftLock として解釈されます。

    # キー
    [50] Shift_L - Shift_L - - - -
    [62] Shift_R - Shift_R - - - -
    
    # 修飾子
    [0] 50(Shift_L) 62(Shift_R) 0 0

    この場合、XK_Shift_Lock が定義されていないので、ShiftLock としては扱われません。

  • Lock 修飾子が、CapsLock と ShiftLock の両方として解釈できる場合は、CapsLock として解釈されます。

  • XK_Num_Lock がキーマッピング内にあり、Mod1〜Mod5 のいずれかの修飾子に割り当てられている場合、その修飾子は numlock 修飾子と呼ばれ、テンキーは XK_KP_* の KeySym となります。

    # キー
    [77] Num_Lock - Num_Lock - - - -
    # 修飾子
    [4] 77(Num_Lock) 0 0 0

    この場合、XK_Num_Lock は Mod2 (Mod1〜Mod5 は 3〜7 番目) に割り当てられています。
キーコードから KeySym への変換
修飾子の状態により、以下の順で KeySym が取得されます。

  1. NumLock が ON で、2番目の KeySym が XK_KP_* の場合 (テンキー)。
    Shift が ON、または、ShiftLock として解釈される Lock が ON の場合は、1番目の KeySym が使用され、それ以外は2番目の KeySym が使用されます。
  2. Shift と Lock が両方とも OFF の場合、1番目の KeySym が使用されます。
  3. CapsLock として解釈される Lock が ON で、Shift が OFF の場合 (アルファベットは大文字、記号は通常)。
    1番目の KeySym が使用されますが、その KeySym が小文字のアルファベットの場合は、大文字の KeySym が使用されます。
  4. CapsLock として解釈される Lock が ON で、Shift が ON の場合 (アルファベットは小文字、記号は +Shift)。
    2番目の KeySym が使用されますが、その KeySym が大文字のアルファベットの場合は、小文字の KeySym が使用されます。
  5. Shift が ON、または ShiftLock として解釈される Lock が ON か、その両方の場合、2番目の KeySym が使用されます。
修飾子マッピング
X の Shift, Lock, Ctrl, Mod1〜Mod5 の各修飾子には、それぞれ複数のキーを割り当てることができます。

Shift や Ctrl には、左右に2つのキーがあるため、2つのキーを割り当てることになります。
X では、各修飾子に、最大8個のキーを割り当てることができます。

XGetModifierMapping() で、修飾子のマッピングを取得することができます。

XModifierKeymap 構造体の modifiermap は、キーコードの配列になっており、[0] Shift [1] Lock [2] Ctrl [3-7] Mod1〜Mod5 の順で、全8個の修飾子に対応する、各 max_keypermod 個分のキーコードが含まれています。
値が 0 の場合は、キーコードなしとなります。

[0] 50(Shift_L) 62(Shift_R) 0 0
[1] 66(Eisu_toggle) 0 0 0
[2] 37(Control_L) 105(Control_R) 0 0
[3] 64(Alt_L) 108(Alt_R) 204(Alt_L) 205(Meta_L)
[4] 77(Num_Lock) 0 0 0
[5] 203(ISO_Level5_Shift) 0 0 0
[6] 133(Super_L) 134(Super_R) 206(Super_L) 207(Hyper_L)
[7] 92(ISO_Level3_Shift) 0 0 0
キーマッピングの変更時
キーマッピングが変更された場合、Xlib 内部のデータを更新する必要があるため、X サーバーはすべてのクライアントに MappingNotify イベントを報告します。
(このイベントは常に送信されます)

XMappingEvent 構造体の request が、MappingKeyboard または MappingModifier の場合は、XRefreshKeyboardMapping(XMappingEvent *) を呼び出してキーマッピングを更新します。

typedef struct {
 int           type;
 unsigned long serial;
 Bool          send_event;
 Display       *display;
 Window        window;
 int           request;
 int           first_keycode;
 int           count;
} XMappingEvent;

case MappingNotify:
    if(ev.xmapping.request == MappingKeyboard
        || ev.xmapping.request == MappingModifier)
    {
        XRefreshKeyboardMapping((XMappingEvent *)&ev);
    }
    break;