跳转至内容

X 键盘扩展

来自 ArchWiki

X Keyboard Extension,简称 XKB,定义了 X 中键盘码的处理方式,并提供了对内部翻译表的访问。这是在 X 中使用多个键盘布局的基本机制。

本文档描述了如何修改和创建键盘布局。如果您想了解如何配置键盘,请参见 Xorg/Keyboard configuration

警告 在使用 XKB 时,您的 X 服务器可能会挂起或崩溃,或者您的键盘可能会进入无法使用的状态,只能通过终止当前会话来恢复。在进行任何更改之前,请遵循 #注意事项和准备工作,以确保您能够恢复。

注意事项和准备工作

为应对 X 服务器崩溃或键盘进入无法使用的状态的可能性,请做好准备:

  1. 确保您有不使用键盘即可终止会话的方法。(除了电源按钮,拥有远程 killall X 或重启主机的能力可能是不错的主意。)
  2. 确保您已保存所有工作,以防数据丢失。
  3. 如果您使用 GNOME,可以安装 gnome-tweaks,以便使用鼠标恢复设置。例如,当键盘变得无法使用时,用鼠标导航到“Keyboard & Mouse > Additional Layout Options”,然后选中和取消选中所有设置,这将把键盘重置为系统默认行为。
提示 为了便于调试,请停止诸如 xxkb(1) 类的应用程序,这些应用程序会切换键盘布局或以其他方式主动更改 XKB 状态。

获取和设置 XKB 布局

使用规则

查看 /usr/share/X11/xkb/rules/ 中的 *.lst 文件或 XKB 主页,以获取配置规则的灵感。您自己的配置可以放在 /etc/X11/xorg.conf.d/ 中。

例如,您可能想重新映射您的 Caps Lock 键为 Escape

90-custom-kbd.conf
Section "InputClass"
    Identifier "keyboard defaults"
    MatchIsKeyboard "on"

    Option "XKbOptions" "caps:escape"
EndSection
注意 即使您忠实地在 Xorg 配置文件中重建了一个可用的 setxkbmap(1) 命令,也可能由于 Xorg 的 bug 而完全无效。 [1] [2]

使用按键映射

使用 xkbcomp(1)(包 xorg-xkbcomp)来操作 XKB 数据。要获取当前配置,请运行:

$ xkbcomp $DISPLAY output.xkb

要将数据上传回服务器,请运行:

$ xkbcomp input.xkb $DISPLAY

请注意,如果没有 $DISPLAY 参数,xkbcomp(1) 将尝试将 .xkb 文件编译成(通常是无用的).xkm 文件,而不会将任何内容上传到服务器。但是,它会检查语法并报告错误。

布局准备好后,将其保存为 ~/.Xkeymap 并在 ~/.xinitrc 中将其加载到启动项:

~/.xinitrc
...

test -f ~/.Xkeymap && xkbcomp ~/.Xkeymap $DISPLAY

实际文件名无关紧要。请注意,与通过 xorg.conf(5) 进行的标准系统范围配置不同,这是一个每个用户的按键映射。此外,在 X 运行时更改 XKB 配置也没有问题。

XKB 基本信息

XKB 的核心功能非常简单,在处理按键映射之前,有必要了解它是如何工作的。

工具和值

使用 xev(包 xorg-xev)来获取键码并检查您的按键映射工作情况。

$ xev -event keyboard
   KeyPress event, serial 45, synthetic NO, window 0x2200001,
       root 0xad, subw 0x0, time 183176240, (796,109), root:(867,413),
       state 0x1, keycode 21 (keysym 0x2b, plus), same_screen YES,
       XLookupString gives 1 bytes: (2b) "+"
       XmbLookupString gives 1 bytes: (2b) "+"
       XFilterEvent returns: False

请注意键码 21、状态 0x1 和按键符号 0x2b(即 plus)。键码 21 是输入设备提供给 X 的,通常是某种物理键索引。状态代表修饰键,0x01Shift。键码和状态值一起是 X 在 XKeyEvent(3) 结构中发送给应用程序的内容。按键符号和对应的字符串是客户端使用 XLookupString(3) 和相关函数获得的。

状态字段中的位具有预定义名称:ShiftLockControlMod1Mod2Mod3Mod4Mod5,从低到高。因此,Ctrl+Shift 是 0x05,依此类推。客户端应用程序通常只检查它们需要的位,因此一个具有正常键盘输入和 Ctrl+key 快捷键的应用程序通常不会区分 ControlControl+Mod3 状态。

按键符号也是数字。其中许多有名称,在 /usr/include/X11/keysymdef.h 中声明,带有 KP_ 前缀。但是,数字是客户端实际接收到的。按键符号仅在应用程序期望某些特定值时才重要;通常是像箭头、Enter、Backspace、F 键和各种快捷键。对于其他情况,则使用字符串。

键码翻译

XKB 主要在 XLookupString 阶段工作,根据其内部状态(组和状态值)将传入的键码转换为按键符号。

   (keycode, group, state) → keysym

组通常代表“布局”,例如美式英语、法式 AZERTY、俄语、希腊语等。最多可以有 4 个组。

在内部,翻译涉及额外的步骤:

   (keycode [, group]) → type
   (state, type) → level
   (keycode, group, level) → S[keycode][group][level]

其中 S 是翻译表(实际上称为 xkb_symbols,见下文)。

类型用于指示哪些修饰键会影响哪些按键;本质上,这是一种减少 S 的第三维的方法。例如,典型的字母数字键仅受 Shift 影响,因此其类型设置为 TWO_LEVEL,而

   (state, TWO_LEVEL) → level = ((state >> 0) & 0x01) = state & 0x01

是 0 或 1。因此,它是 S[keycode][0..4][0..1] 而不是 S[keycode][0..4][0..256]

按键符号和状态

在 X 的术语中,aCtrl+a 表示相同的按键符号和不同的状态,但 aA 是不同的按键符号。

通常,提供不同按键符号是 XKB 的任务,而状态由各个应用程序稍后处理。

此外,XKB 中的状态具有一定的延迟效应,也就是说,您必须在按下键之前设置好状态。

示例:Ctrl+h 可以在 rxvt(应用程序设置)中配置为执行 Backspace 操作。这样 rxvt 将收到带有 Control 位设置的状态值的 h 按键符号,它将与 Backspace 按键符号明显不同。或者,XKB 可用于使 Ctrl+h 组合生成带有 Control 位设置的 Backspace 按键符号;在这种情况下,只要 Ctrl 键被按下,rxvt 就不会区分物理 Backspace 键和 h 键。使 Ctrl+h 组合生成不带 Control 位设置的 Backspace 按键符号也是 XKB 的任务,但这比 Control+Backspace 难得多。

操作

从上面的表中获得的按键符号也可以触发某些操作。

   (keysym, state) → action

对于 XKB 来说,设置或锁定修饰键位是一种操作,任何 X 服务器交互,如切换控制台、终止服务器、移动指针等也是操作。操作通常不影响按键符号,而生成按键符号则不是操作。

每个(按键符号,状态)对只有一个可能的操作。

编辑布局

从服务器的默认配置开始。尽可能逐步进行更改并进行测试。

xkbcomp(1) 生成的 .xkb 文件是一个简单的文本文件。允许使用 C++ 风格的注释,即 // 直到行尾。部分名称,例如 xkb_keycodes "name-here",此时无关紧要,可以省略。

xkb_keycodes

键码定义。文件的其余部分不使用数字键码,只使用此部分定义的符号键标签。

最好只保留该键盘实际拥有的按键。

标签本身是任意的。它们仅在后面的 xkb_symbols 部分中使用。

xkb_types

此部分在 xkb_symbols 之前,所以看一下,但尽量暂时不要进行更改。标准类型很大程度上依赖于虚拟修饰键,稍后将进行解释。目前,只需找到您需要的类型。从以下开始:ONE_LEVEL、TWO_LEVEL、ALPHABETIC。

ONE_LEVEL 键不受修饰键影响;通常是 Enter、Space、Escape、F 键、Shift/Alt/Ctrl 键等。TWO_LEVEL 和 ALPHABETIC 键根据 Shift 状态产生不同的按键符号。所有字母数字键都属于这些类型。ALPHABETIC 另外还尊重 CapsLock。

类型本身的描述非常简单。该行:

   modifiers= Shift+NumLock+LevelThree;

表示此类型的键仅受 Shift、NumLock 和 LevelThree 位影响。像这样的映射行:

   map[Shift+LevelThree]= Level4;

定义了哪个组合对应哪个级别值。xkbcomp(1) 转储数据时使用“LevelN”,但也可以使用简短且方便得多的“N”。

level_name 行无关紧要,可以忽略。

xkb_compatibility

操作定义(interpret)和键盘 LED(indicator)等。您可以删除您没有或不使用的内容,例如键盘操作、鼠标控制或附加修饰键。

请注意,key+AnyOfOrNone(all) 等同于 key,但 key 更易读。

如果您需要,请检查组切换。如果您有四个组,LockGroup(group=N) 可能有用,否则 ISO_Next_Group/ISO_Prev_Group 就足够了。LatchGroup 对不寻常的设置可能很有用。

xkb_symbols

定义每个键功能的 मुख्य 部分。语法:

   key <LABL> { [ G1L1, G1L2, G1L3, ... ], [ G2L1, G2L2, G2L3, ... ], ... }

<LABL> 是 xkb_keycodes 部分的键标签,GiLj 是组 i 级别 j 的按键符号。每个组中的按键符号数量必须与此类型定义的级别数匹配(如果 xkbcomp(1) 发现不匹配,它会发出警告)。

请参阅 /usr/include/X11/keysymdef.h 获取可能的按键符号列表。除了列出的之外,您还可以使用 Unnnn 来表示十六进制代码为 nnnn 的 Unicode 符号,例如 U0301 表示组合的尖音符。请注意,aU0061 的处理方式不同(例如,大多数应用程序期望 Ctrl+a,而不是 Ctrl+U0061,因为它们的数值不同)。

此处也指定了键类型,方式如下:

   key.type = "T1";
   key <...> { ... };
   key <...> { ... };
   key <...> { ... };
   key.type = "T2";
   key <...> { ... };
   key <...> { ... };

或单独为每个键:

   key <...> { type = "T", [ .... ], [ .... ] };

不同组的键类型可能不同。这有些违反直觉,但实际上有一些有用的应用。要为每个组设置类型,请使用此:

   key <...> { type[1] = "T1", type[2] = "T2", [ ... ], [ ... ] };

您可以使用以下方法设置组的标签:

   name[1] = "EN";     // group 1
   name[2] = "RU";     // group 2
   name[3] = "UA";     // group 3

如果启用了标签,xxkb(1) 将会显示这些。

该部分还包含 modifier_map 行。暂时忽略它们,或者查看下面的虚拟修饰键。

xkb_geometry

完全不相关的部分,描述物理键盘布局。可以删除而不会产生任何后果。

基本示例

首先检查您现有的布局,因为它可能包含许多常见按键的标准定义。

在文本中,“xkb_keycodes { text }”表示“text”应该添加到 xkb_keycodes 部分。在上下文清楚的情况下,部分名称会被省略。

简单的按键分配

启用附加(又称多媒体)键。

   xkb_keycodes {
       <VOL-> = 122;       // check with xev
       <VOL+> = 123;
   }
   
   xkb_symbols {
       key.type = "ONE_LEVEL";
       key <VOL-> { [ XF86AudioLowerVolume ] };
       key <VOL+> { [ XF86AudioRaiseVolume ] };
   }

Caps Lock 映射为 Escape,主要面向 Vim 用户。

   key.type = "ONE_LEVEL";
   key <CAPS> { [ Escape ] };

交换 Ins 和 PrintScreen(如果它们是颠倒的 — 在 Dell 笔记本键盘上会发生)。

   key.type = "ONE_LEVEL";
   key <IN?>  { [    Print ] };
   key <PRSC> { [   Insert ] };

在某些 HP 笔记本键盘上,上述方法无效。相反,必须重新定义键码本身。

   partial xkb_keycodes "insert" {
       alias <I118> = <IN?>;
       <INS>  = 218;
       <I218> = 118;
   };

将 Shift 更改为粘滞键版本。

替换

   key <LFSH> {         [         Shift_L ] };

   key <LFSH> {         [         ISO_Level2_Latch ] };

您可能还需要将以下内容添加到 /usr/share/X11/xkb/compat/basic

   interpret ISO_Level2_Latch+AnyOf(all) {
       useModMapMods=level1;
       action= LatchMods(modifiers=Shift,clearLocks,latchToLock);
   };
   interpret ISO_Level2_Latch+AnyOfOrNone(all) {
       action= LatchMods(modifiers=Shift,clearLocks,latchToLock);
   };

多个布局

对于常规字母数字键,只需在键定义中添加第二个/第三个/第四个 `[ ]` 部分。

   key.type = "ALPHABETIC";
   key <AD01> { [ q, Q ], [ a, A ] };      // QWERTY-AZERTY
   key <AC02> { [        s,        S ],        // two cyrillic layouts
                [    U044B,    U042B ],
                [    U0456,    U0406 ] };

布局切换通过触发 `LockGroup` 操作来完成。

   interpret ISO_Next_Group { action = LockGroup(group=+1); };
   interpret ISO_Prev_Group { action = LockGroup(group=-1); };

通常这意味着将 `ISO_Next_Group` 和 `ISO_Prev_Group` 按键符号放在正确的组/级别位置。请注意,组是循环的,因此如果您有两个组并两次按下 `ISO_Next_Group`,您将返回到开始的组。

使用专用键在两个或多个布局之间循环切换。

   key.type = "ONE_LEVEL";
   key <RWIN> { [ ISO_Next_Group ] }

如果您有多个布局和一些备用键,为每个布局设置专用键可能是一个更好的主意。三个布局的示例:

   key.type = "ONE_LEVEL";
   key <RCTL> { [ ISO_Next_Group ],    // g1: switch to g2
                [ ISO_Prev_Group ],    // g2: switch back to g1
                [ ISO_Prev_Group ] };  // g3: switch to g2
   key <MENU> { [ ISO_Prev_Group ],    // g1: switch to g3
                [ ISO_Next_Group ],    // g2: switch to g3
                [ ISO_Next_Group ] };  // g3: switch back to g1

如果您有四个布局,您很可能必须使用 `ISO_First_Group` 和 `ISO_Last_Group`。

使用一个键实现相同想法,只需利用 TWO_LEVEL 类型即可。

   key.type = "TWO_LEVEL";
   key <MENU> { [ ISO_Next_Group, ISO_Prev_Group ],   
                [ ISO_Prev_Group, ISO_Next_Group ],   
                [ ISO_Prev_Group, ISO_Next_Group ] }; 

这样,菜单键对应组 2,Shift+菜单键对应组 3。要使用 Ctrl 或 Alt 代替 Shift,请分别用 `PC_CONTROL_LEVEL2` 或 `PC_ALT_LEVEL2` 类型替换 `TWO_LEVEL`。

使用两个修饰键(Shift+Shift、Ctrl+Shift 等)切换可以通过使用非 ONE_LEVEL 类型来完成。Shift+Shift 示例:

   key.type = "TWO_LEVEL";
   key <LFSH> { [ Shift_L, ISO_Prev_Group ] };
   key <RTSH> { [ Shift_R, ISO_Next_Group ] };

要锁定组(又称切换;仅在按住键时生效),请使用 `LatchGroup` 操作,通常绑定到 `ISO_Group_Latch` 按键符号。

   key <RCTL> { [ ISO_Group_Latch ] }

在 xkb_compatibility 部分调整 `ISO_Group_Latch` 定义以使用正确的组。

   interpret ISO_Group_Latch { action = LatchGroup(group=3); };

请参阅 /usr/share/X11/xkb/symbols/group 获取更多标准示例。

将 Caps hjkl 映射为 Vim 式方向键

本文或本节需要在语言、wiki 语法或风格方面进行改进。请参阅 Help:Style 获取参考。

原因: 本节的目的不明确。(请在 Talk:X keyboard extension 中讨论)

创建清除按键的修饰键的按键映射是必要的,如果目标键用于键盘快捷键。例如,使用主键盘(Shift+Left)突出显示文本,或在大多数信使中切换聊天(Alt+Down)将不起作用,如果存在额外的 Caps 修饰键。然而,如果用户仅仅通过将按键符号放在 symbols 部分来重新绑定字母键,则必须发送一个附加修饰键。如下重新绑定允许 AHK 的 blind 命令等功能。

types 部分(定义层映射)必须包含一个条目,使得:

  • 当不按下任何修饰键时,使用第一级按键符号(小写字母)。
  • 当仅按下 Shift 时,使用第二级按键符号(大写字母)。
  • 当仅按下 Lock 时,使用第三级按键符号(方向键)。
  • 当按下 Shift 和 Lock 时,也使用第三级按键符号(Shift+方向键)。

将此添加到您的 types 部分底部:

 xkb_types "complete" {
   ...
   type "CUST_CAPSLOCK" {
       modifiers= Shift+Lock; 
       map[Shift] = Level2;            //maps shift and no Lock. Shift+Alt goes here, too, because Alt isn't in modifiers.
       map[Lock] = Level3;
       map[Shift+Lock] = Level3;       //maps shift and Lock. Shift+Lock+Alt goes here, too.
       level_name[Level1]= "Base";
       level_name[Level2]= "Shift";
       level_name[Level3]= "Lock";
   };
 };

现在,通过修改 compatibility 中已有的 LockMods 到 SetMods 的定义,将 Caps 键从锁定(切换)模式改为设置(按下)模式。

(请注意,这意味着您不能像正常一样使用 CapsLock)。

 xkb_compatibility "complete" {
   ...
   interpret Caps_Lock+AnyOfOrNone(all) {
       action= SetMods(modifiers=Lock);
   };
   ...
 };

最后,按如下方式修改您的 symbols 文件。

 xkb_symbols "pc_us_inet(evdev)" {
   ...
   key <AC06> {
       type= "CUST_CAPSLOCK",
       symbols[Group1]= [               h,               H,               Left],
       actions[Group1]= [      NoAction(),      NoAction(),   RedirectKey(keycode=<LEFT>, clearmods=Lock) ]
  };

附加符号

用相同的按键输入更多内容。

组合键

易于设置且对于输入常用 Unicode 字符极其有用。

   key <RALT> { [ Multi_key ] };

Level3

其思想类似于 Alt 或 AltGr 的原始含义:字母数字键通过按住某个修饰键获得额外的字符。

首先,设置修饰键。

   xkb_symbols { 
       key <LWIN> { [ISO_Level3_Shift ] };
       modifier_map Mod5 { ISO_Level3_Shift };
   }

此外,以下内容应该已经在相关部分定义,但以防万一:

   xkb_compatibility {
       interpret ISO_Level3_Shift { action= SetMods(modifiers=Mod5); };
   }
   
   xkb_types {
       type "THREE_LEVEL" {
           modifiers= Shift+Mod5;
           map[Shift]= Level2;
           map[Mod5]= Level3;
           map[Shift+Mod5]= Level3;
           level_name[Level1]= "Base";
           level_name[Level2]= "Shift";
           level_name[Level3]= "Level3";
       };
       type "FOUR_LEVEL" {
           modifiers= Shift+LevelThree;
           map[Shift]= Level2;
           map[LevelThree]= Level3;
           map[Shift+LevelThree]= Level4;
           level_name[Level1]= "Base";
           level_name[Level2]= "Shift";
           level_name[Level3]= "Alt Base";
           level_name[Level4]= "Shift Alt";
       };
   }

请注意,标准定义在 xkb_compatibility 和 xkb_types 中使用 LevelThree 而不是 Mod5。只要上面的 modifier_map 使用 Mod5,就没有实际区别,您最终还是会使用 Mod5 位。

现在,我们来看按键本身,以 Vim 式光标为例。

   key.type = "THREE_LEVEL";
   key <AC06> { [ h, H,  Left ] };
   key <AC07> { [ j, J,  Down ] };
   key <AC08> { [ k, K,    Up ] };
   key <AC09> { [ l, L, Right ] };

正如您通过 xev(1) 可能会发现的,这会产生 Mod5+Left 而不是单纯的 Left。但没关系,因为大多数应用程序会忽略它们不使用的状态位。有关其他解决方案,请查看下面的 Overlays。

Meta, Super 和 Hyper

真实修饰键

一些应用程序(特别是 Emacs)允许有意义地使用更高的状态位。通常假设键盘上除了标准的 Shift、Ctrl 和 Alt 之外,还有 Meta、Super 和 Hyper 等修饰键,它们控制这些位。

从 XKB 的角度来看,这意味着设置 Mod2、Mod3、Mod4 和 Mod5 修饰键位。因为您只需要这些位本身,所以不需要像 Level3 示例那样编辑类型。

   xkb_compatibility {
       interpret Super_L { action = SetMods(modifiers=Mod3); };
   }
   xkb_symbols {
       key <LWIN> { [ Super_L ] };
       modifier_map Mod3 { Super_L };
   }

标准定义在 xkb_compatibility 中使用 Super 修饰键而不是 Mod3。您可以保留它,只需确保 modifier_map 行到位即可。

请记住,ModN 和 Super、Hyper 甚至 Alt 等命名修饰键之间没有严格的对应关系。Mod1 是唯一被广泛使用的;一些应用程序称之为 Meta,一些称之为 Alt。对于其他,请查看特定应用程序如何处理状态位,以及/或查看下面的虚拟修饰键。

按键符号追踪

至少有一个应用程序(openbox)已知会追踪 Meta_[LR]、Super_[LR] 和 Hyper_[LR] 按键符号的 KeyPress/KeyRelease 事件,而不是依赖于状态位。在这种情况下:

   xkb_symbols {
       key <LWIN> { [ Super_L ] };
   }

就足够了,您可以省略 interpretmodifier_map 行。

说到 Openbox,请注意它实际上允许两种方法:“S-h”追踪 Super_[LR] 事件,而“Mod3-h”检查相关状态位。

预设配置

XKB 通常通过指定 XkbTypes/XkbCompat/XkbSymbols,或 XkbModel/XkbLayout (+XkbVariant/XkbOptions),或 XkbKeymap 来配置,通常在 /etc/X11/xorg.conf 或 /etc/X11/xorg.conf.d/*.conf 中,如下所示:

   Option  "XkbModel"    "thinkpad60"                                                                                                  
   Option  "XkbLayout"   "us,sk,de"                                                                                                    
   Option  "XkbVariant"  "altgr-intl,qwerty,"                                                                                          
   Option  "XkbOptions"  "grp:menu_toggle,grp_led:caps"        

这些值通过组合 /usr/share/X11/xkb 中的多个文件来定义完整的 XKB 映射(可以通过 xkbcomp(1) 转储的映射)。实际上,可以使用 setxkbmap -print 获取等效于 xkbcomp(1) 的 .xkb 文件:

$ setxkbmap -model thinkpad60 -layout us,sk,de -variant altgr-intl,qwerty \
       -option -option grp:menu_toggle -option grp_led:caps -print

请注意输出中的 include 语句。每个部分的 文件都从 /usr/share/X11/xkb 下的相关子目录中获取,例如:

   xkb_types { include "complete" };

表示 xkbcomp(1) 将查找 /usr/share/X11/xkb/types/complete。加号表示连接,所以:

   xkb_keycodes { include "evdev+aliases(qwerty)" };

表示:

   xkb_keycodes {
       include "evdev";
       include "aliases(qwerty)";
   };

括号用于从文件中选择命名部分。请查看 /usr/share/X11/xkb/keycodes/aliases 并注意:

   xkb_keycodes "qwerty" { ... };

这是 aliases(qwerty) 引用的部分。最后,冒号允许将布局的部分移到另一个组。

与 XkbTypes/XkbCompat/XkbSymbols/XkbGeometry 值(直接定义相关的 .xkb 文件部分)不同,XkbModel、XkbLayout 和 XkbRules 指的是 /usr/share/X11/xkb/rules/ 下的附加非 xkb 文件,这些文件将 model 和 layout 值与特定的 symbols 和 geometry 匹配。XkbKeymap 指的是完整的按键映射。有关详细描述,请参阅 Ivan Pascal 的页面。

xkbcomp(1) 方法一样,这种类型的配置可以在运行时进行:使用 setxkbmap(1) 而不带 -print 选项。

/usr/share/X11/xkb 中的文件是很好的示例来源,尤其是在处理具有非平凡 XKB 实现的标准键盘功能(例如,键盘/NumLock 处理)时。此外,这些文件是您必须编辑以将更改推向上游的文件。但在这样做之前,请查看 X Keyboard Config Rules

xmodmap

xmodmap 与 XKB 没有直接关系;它使用不同的(XKB 之前的)键码处理思想。特别是,它缺乏组和类型的概念,因此尝试为每个键设置多个按键符号不太可能成功。总的来说,除了最简单的按键映射或指针按钮映射修改之外,应使用 xkbcomp(1)

指示器

如“键盘 LED”。指示器名称用于在 xkb_keycodes 部分中与物理 LED 匹配。否则,它们无关紧要。未匹配到任何 LED 的指示器称为“虚拟”;xkbvleds(1)(包 xorg-xkbutils)可用于检查它们的状态。示例:

   xkb_keycodes {
       indicator 1 = "LED1";       // first physical LED
   }

指示器始终反映 XKB 内部状态的指定部分。两种常见模式是显示修饰键状态:

   xkb_compatibility {
       indicator "LED1" { modifiers = Lock; }; // CapsLock indicator
   }

或当前组:

   xkb_compatibility {
       indicator "LED1" { groups = 0x06; };    // "group 2 or group 3 is active"
   }

值是位掩码。对于组,位 1 是组 1,位 2 是组 2,依此类推。

修饰键和类型

在某些时候,有必要清理 types 部分,和/或引入不常见的类型。

类型和修饰键紧密相连,因此在处理类型描述之前,先处理修饰键位很有意义。

决定您将使用哪些位。总共只有八个,其中 Shift、Control 和 Mod1 在应用程序中被广泛使用,而 Lock(又称 CapsLock)具有预定义的含义,也可能很难覆盖。其余四个,然而,是可以自由使用的。

警告:四个标准类型 ONE_LEVEL、TWO_LEVEL、ALPHABETIC 和 KEYPAD 在 xkbcomp(1) 中受到特殊对待。它们的工作方式可能不同,仅仅因为它们是这样命名的。避免删除它们。如果某些更改未按预期工作,请尝试添加一个新类型。

在标准类型中使用真实修饰键

根据您的基础配置,可能有很多未使用的标准类型,如 EIGHT_LEVEL 或 PC_RCONTROL_LEVEL2。删除它们以避免不必要的工作。

现在,一些标准类型使用虚拟修饰键。如果您决定使用它们,请查看下面的虚拟修饰键并跳过此部分。否则,摆脱它们是一个好主意。查看您需要的类型,然后用相应的真实类型替换它们,或删除相关定义。示例:

   type "KEYPAD" {
       modifiers= Shift+NumLock;
       map[Shift]= Level2;
       map[NumLock]= Level2;
       level_name[Level1]= "Base";
       level_name[Level2]= "Number";
   };

如果您使用 Mod2 作为 NumLock,将类型更改为:

   type "KEYPAD" {
       modifiers= Shift+Mod2;
       map[Shift]= Level2;
       map[Mod2]= Level2;
       level_name[Level1]= "Base";
       level_name[Level2]= "Number";
   };

如果您不打算使用 NumLock 修饰键,请将其更改为:

   type "KEYPAD" {
       modifiers= Shift;
       map[Shift]= Level2;
       level_name[Level1]= "Base";
       level_name[Level2]= "Number";
   };

在 xkb_compatibility 部分也做同样的事情。一旦完成,您应该能够删除文件中的所有“virtual_modifiers”行。

切换单个修饰键位

基本上,您需要的是一个带有相关解释条目的按键符号。以 Mod5 切换为例,使用 LWIN 键,按键符号为 ISO_Level3_Shift

   xkb_compatibility {
       interpret ISO_Level3_Shift { action = SetMods(modifiers=Mod5); };
   }
   
   xkb_symbols {
       key <LWIN> { [ISO_Level3_Shift ] };
   }

除了 SetMods,您还可以使用 LockModsLatchModsSetMods 创建一个常规的“按下时生效”修饰键。LockMods 创建一个像 CapsLock 或 NumLock 这样的“开/关”开关。LatchMods 意味着“直到下次按键生效”即粘滞修饰键。

modifier_map

修饰键映射表将八个修饰键位中的每一个映射到一个(最多 4 个)按键。

   modifier_map Mod1 { Alt_L, Alt_R };

在核心协议中,没有 XKB,它的含义大致相当于:

   interpret Alt_L { action = SetMods(modifiers=Mod1); };
   interpret Alt_R { action = SetMods(modifiers=Mod1); };

XKB 不使用原始意义上的修饰键映射。在 XKB 中,它的唯一功能是映射虚拟修饰键(见下文)。

然而,该表很容易被客户端访问,并且有一个违反直觉的(但广为人知的)技巧:修饰键映射用于告诉哪个 ModX 位是 Alt。因此,最好像上面那样将一个修饰键映射到 Alt_L 或 Alt_R。除非您有非常充分的理由不这样做,否则它应该是 Mod1。

多个键盘

XKB 只能为单个连接的物理键盘设置按键映射。此功能对于多键盘设置非常有用,特别是当这些键盘不同时;例如,一台笔记本电脑连接了一个全尺寸 USB 键盘。

首先,使用 xinput(包 xorg-xinput)获取设备 ID。

   AT Translated Set 2 keyboard                id=11   [slave  keyboard (3)]

现在,

$ xkbcomp -i 11 file.xkb $DISPLAY

或者

$ setxkbmap -device 11 ...

将只为指定的键盘设置按键映射。转储 XKB 配置也有效:

$ xkbcomp -i 11 $DISPLAY file.xkb

请注意 xkbcomp -i11 将无法工作,也不会给出清晰的错误消息。确保 -i 后面有空格。

调试 XKB

当按键不起作用时,首先要检查的是 XKB 内部状态:修饰键、有效组和控制位。这三者都可以用来驱动 LED;使用 xkbvleds(1) 来检查它们:

   indicator "LED1" { modifiers = Lock; };
   indicator "LED2" { groups = 2; };
   indicator "LED3" { controls = audiblebell; };

此外,xkbwatch(1) 显示所有(真实)修饰键及其锁定/粘滞状态。修饰键也由 xev(1) 报告。可以使用 xxkb(1) 来监视有效组,但请确保 two_state 模式已关闭。

如果解释部分工作不正常,请务必检查是否存在重复的“interpret”块。更好的是,尝试注释掉任何与特定按键符号相关的代码。有关解释,请参见第 9.2 节。

通过以下方式下载按键映射以检查服务器实际接收到的内容也有意义:

$ xkbcomp $DISPLAY out.xkb

结果往往与输入文件不同。没有已知的解决方法。

虚拟修饰键

XKB 最令人头疼的部分之一是虚拟修饰键,它们虽然相对次要且大多无用,但在所有标准按键映射中都占有重要位置。这个术语本身就具有极大的误导性,而且大多数文档也帮不上太多忙。

所以,首先要明确:虚拟修饰键不像真实修饰键那样是修饰键。如果非要说,它只是一种给一些真实修饰键命名的 方式。它们不是可以用于级别定义的 16 个额外位。它们是 16 个可能的名称,每个名称指向 8 个修饰键位中的一个(或一些,或没有)。

真实修饰键位称为 Shift、Lock、Control 和 Mod1-Mod5。其中没有 Alt。引入虚拟修饰键是为了允许说诸如:

   #define Alt Mod1

to applications willing to use this information.

It is possible to make a usable layout without defining virtual modifiers at all. Among standard modifiers, only Alt/Meta actually need such treatment, because Shift and Control are real modifiers anyway and NumLock is not normally used as a modifier.

Also, unlike most of the keymap-related things that affect any application using basic Xlib functions, virtual modifiers must be queried explicitly using XKBlib calls. Not all applications actually do that.

定义虚拟修饰符

The mapping between virtual and real modifiers is defined in a rather weird way using keysyms as a medium. Refer to XKBproto for some reasons behind this. Real modifiers M are assigned to a key using

   modifier_map M { <keysym> };

Virtual modifiers V can be assigned to a key using

   interpret <keysym> { virtualMod = V; };

If a virtual modifier V shares at least one keysym with a real modifier M, it is bound to M.

Note that virtual modifier names are not pre-defined and must be declared in xkb_compatibility and xkb_types sections before using them

   xkb_compatibility "complete" {
       virtual_modifiers LevelThree,NumLock,Alt;
   }

Keysym 解释

Virtual modifiers can be used in interpret <keysym> blocks as if they were defined to the respective real modifiers. For a virtual modifier V not bound to any real modifier, this means

   #define V

type declaration, and

   interpret <key> { }
   interpret <key>+V { }

blocks will be treated as duplicates. Only one of them, the last one in the file, will work. xkbcomp(1) usually gives a warning in cases like this.

客户端注意事项

Handling XKB virtual modifiers on the client side requires some non-trivial server interaction. Most applications just do not bother, sticking with 8 real modifiers supplied in XKeyEvent.state.

However, it is possible for an application to obtain virtual modifiers associated with a key press. Gtk, for instance, has [3] which may or may not be used in particular application.

Some others may implement something that looks like virtual modifier support, but actually is not, see the Openbox example in #Keysym tracking. Regarding Alt handling, see #modifier_map.

XKB 控制位

A bunch of bit flags affecting various aspects of XKB functionality. To control them, use {Set,Latch,Lock}Controls actions.

鼠标控制

XKB allows controlling mouse pointer from keyboard. When set up properly, it can be extremely useful. However, its usability depends a lot on particular physical keyboard layout and on user's individual preferences.

From XKB point of view it is relatively simple to implement, one should just trigger relevant actions. Fairly complete implementation can be found in /usr/share/X11/xkb/compat/mousekeys.

Note that the actions will not work unless MouseKeys control bit is set

   interpret Pointer_EnableKeys { action= LockControls(controls=MouseKeys); };

Because most keyboards do not have dedicated mouse control keys, combining MouseKeys and one of the Overlay flags may be a good idea

   interpret Pointer_EnableKeys { action= LockControls(controls=MouseKeys+Overlay1); };

This allows moving pointer control keys to appropriate overlay block

   xkb_keycodes {
       <MUP> = 218;
       <MDWN> = 212;
       <MLFT> = 214;
       <MRHT> = 216;
   }

   xkb_symbols {
       key   <UP> { [    Up ], overlay1 = <MUP> };
       key <LEFT> { [  Left ], overlay1 = <MLFT> };
       key <RGHT> { [ Right ], overlay1 = <MRHT> };
       key <DOWN> { [  Down ], overlay1 = <MDWN> };

       key <MUP>  { [ Pointer_Up ] };
       key <MDWN> { [ Pointer_Down ] };
       key <MLFT> { [ Pointer_Left ] };
       key <MRHT> { [ Pointer_Right ] };
   }

This way it is possible to assign non-mouse actions to the keys used to control mouse, and thus, for example, use modifier keys to generate mouse buttons events.

本地 XKB 文件夹

You can set an X keymap from a local file using the following command

$ xkbcomp keymap.xkb $DISPLAY

where keymap.xkb must have a structure like

keymap.xkb
xkb_keymap {
    xkb_keycodes  { ... };
    xkb_types     { ... };
    xkb_compat    { ... };
    xkb_symbols   { ... };

    // Geometry is completely optional.
    // xkb_geometry  { include "pc(pc104)" };
};

You can use includes from this file, where the inclusion refer to a local folder instead of /usr/share/X11/xkb. You need to use the -I/path/ parameter of xkbcomp(1) for that. Full example

$ xkbcomp -I$HOME/.xkb $HOME/.keymap.xkb $DISPLAY
$HOME/.keymap.xkb
xkb_keymap {
    xkb_keycodes  { include "evdev+aliases(qwerty)" };
    xkb_types     { include "complete" };
    xkb_compat    { include "complete" };
    xkb_symbols   { include "pc+custom+inet(evdev)" };
};

The symbol file must have the same name as specified in the xkb_symbols right above.

$HOME/.xkb/symbols/custom
partial alphanumeric_keys xkb_symbols "custom" { ... };

配置工具

Most desktop environments allow for changing XKB options via their settings managers. Other relevant tools include

  • input-remapper-gitAUR - a GUI tool for key remapping.
  • klfcAUR - Keyboard Layout Files Creator is a CLI utility for generating layouts from a JSON specifiation into various formats.

故障排除

通过 xorg.conf 或 .xinitrc 设置布局无效

If you are able to sucessfully set an XKB layout after starting Xorg, via setxkbmap(1), but not at startup using an Xorg configuration file or ~/.xinitrc (either with xkbcomp(1) using a precompiled XKB map or setxkbmap(1)), then you might have an input method enabled which is overriding XKB. This behavior exists and can be disabled for Fcitx and Fcitx5.

我有一个 USB 键盘,设置在拔掉后会丢失

Using rules instead of static keymap configuration will give you a more flexible and permanent key mapping that does not need to be reloaded manually (or by a script).

参见