From e8f4d2a68e7307795f3b6cf9f772b322bbfdb78f Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 9 Feb 2018 21:19:14 -0500 Subject: round out kbd-xmodmap --- public/kbd-xmodmap.md | 181 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 148 insertions(+), 33 deletions(-) diff --git a/public/kbd-xmodmap.md b/public/kbd-xmodmap.md index 5dbccc1..80012d8 100644 --- a/public/kbd-xmodmap.md +++ b/public/kbd-xmodmap.md @@ -1,7 +1,7 @@ GNU/Linux Keyboard Maps: xmodmap ================================ --- -date: "2017-10-01" +date: "2018-02-09" --- The modmap subsystem is part of the core [X11 protocol][xproto]. @@ -35,29 +35,31 @@ Conceptual overview There are 3 fundamental tasks that the modmap subsystem performs: - 1. `keyboard: map keycode -> keysym` - 2. `keyboard: map keysym -> modifier bit` WAIT? - 3. `pointer: map physical button -> logical button` + 1. `keyboard: map keycode -> keysym` (client-side) + 2. `keyboard: map keycode -> modifier bitmask` (server-side) + 3. `pointer: map physical button -> logical button` (server-side) You're thinking: "Great, so the X server does these things for us!" -Nope! It exposes those mappings, and leaves the actual -transformations up to the client. Generally, the actual -transformations are performed automatically inside of libX11/libxcb. - -Vocab: ------- - - - keycode: A numeric ID for a hardware button; this is as close - the the hardware as X11 modmaps let us get. These are - conceptually identical to Linux kernel keycodes, but the numbers - don't match up. Xorg keycodes are typically linux_keycode+8. - - - keysym: A 29-bit integer code that is meaningful to - applications. A mapping of these to symbolic names is defined - in and augmented by - . See: XStringToKeysym() and - XKeysymToString(). We will generally use the symbolic name in - the modmap file. The symbolic names are case-sensitive. +Nope! Not entirely, anyway. It does the keycode->modifier lookup, +and the mouse-button lookup, but the keycode->keysym lookup must be +done client-side by querying the mapping stored on the server. +Generally, this is done automatically inside of libX11/libxcb, and the +actual client application code doesn't need to worry about it. + +So, what's the difference between a keycode and a keysym, and how's +the modifier bitmask work? + + - keycode: A numeric ID for a hardware button; this is as close the + the hardware as X11 modmaps let us get. These are conceptually + identical to Linux kernel keycodes, but the numbers don't match + up. Xorg keycodes are typically `linux_keycode+8`. + + - keysym: A 29-bit integer code that is meaningful to applications. + A mapping of these to symbolic names is defined in + `` and augmented by `/usr/share/X11/XKeysymDB`. + See: `XStringToKeysym()` and `XKeysymToString()`. We will + generally use the symbolic name in the modmap file. The symbolic + names are case-sensitive. - Modifier state: An 8-bit bitmask of modifier keys (names are case-insensitive): @@ -71,12 +73,112 @@ Vocab: 1 << 6 : mod4 1 << 7 : mod5 -Commands by task: ------------------ +With that knowledge, and the libX11/libxcb API docs, you can probably +figure out how to interact with the modmap subsystem from C, but who +does that? Everyone just uses the `xmodmap(1)` command. + +The X11 protocol +---------------- + +As I said, the modifier and button lookup is handled server-side; each +of the [input events][] ({Key,Button}{Press,Release}, and +MotionNotify) and [pointer window events][] ({Enter,Leave}Notify) +include a bitmask of active keyboard modifiers and pointer buttons. +Each are given an 8-bit bitmask---hence 8 key modifiers. For some +reason, only up to Button5 is included in the bitmask; the upper 3 +bits are always zero; but the Button{Press,Release} events will +happily deliver events for up to Button255! + +[input events]: https://www.x.org/releases/current/doc/xproto/x11protocol.html#events:input +[pointer window events]: https://www.x.org/releases/current/doc/xproto/x11protocol.html#events:pointer_window + +The X11 protocol has 6 request types for dealing with these 3 +mappings; an accessor and a mutator pair for each. Since the 2 of the +mappings are done server-side, of these, most clients will only use +GetKeyboardMapping. Anyway, let's look at those 6 requests, grouped +by the mappings that they work with (pardon the Java-like pseudo-code +syntax for indicating logical argument and return types): + + 1. `keyboard: map keycode -> keysym` + + - [GetKeyboardMapping][] :: `List -> Map>` + - [ChangeKeyboardMapping][] :: `Map> -> ()` + + `GetKeyboardMapping` returns the keycode->keysym mappings for the + requested keycodes; this way clients can choose to look up only + the keycodes that they need to handle (the ones that got sent to + them). Each keycode gets a list of keysyms; which keysym they + should use from that list depends on which modifiers are pressed. + `ChangeKeyboardMapping` changes the mapping for the given + keycodes; not all keycodes must be given, any keycodes that aren't + included in the request aren't changed. + + 2. `keyboard: map keycode -> modifier bitmask` + + - [GetModifierMapping][] :: `() -> Map>` + - [SetModifierMapping][] :: `Map> -> ()` -The `xmodmap` command has its own little quirky syntax. There are 8 -commands that it recognizes. Let's look at those, grouped by the 3 -tasks that the modmap subsystem performs: + The modifiers mapping is a lot smaller than the keysym mapping; + you must operate on the entire mapping at once. For each modifier + bit, there's a list of keycodes that will cause that modifier bit + to be flipped in the events that are delivered while it is + pressed. + + 3. `pointer: map physical button -> logical button` + + - [GetPointerMapping][] `() -> List` (indexed by `physicalButton-1`) + - [SetPointerMapping][] `List -> ()` (indexed by `physicalButton-1`) + + Like the modifier mapping, the button mapping is expected to be + small, most mice only have 5-7 buttons (left, middle, right, + scroll up, scroll down, scroll left, scroll right---that's right, + X11 handles scroll events as button presses), though some fancy + gaming mice have more than that, but not much more. + +[ChangeKeyboardMapping]: https://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:ChangeKeyboardMapping +[SetModifierMapping]: https://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:SetModifierMapping +[SetPointerMapping]: https://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:SetPointerMapping + +[GetKeyboardMapping]: https://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:GetKeyboardMapping +[GetModifierMapping]: https://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:GetModifierMapping +[GetPointerMapping]: https://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:GetPointerMapping + +I mentioned earlier that the keycode->keysym mapping isn't actually +done by the X server, and is done in the client; whenever a client +receives a key event or pointer button event, it must do a +`Get*Mapping` request to see what that translates to. Of course, +doing a that for every keystroke would be crazy; but at the same time, +the each client is expected to know about changes to the mappings that +happen at run-time. So, each of the "set"/"change" commands generate +a [MappingNotify][] event that is sent to all clients, so they know +when they must dump their cache of mappings. + +[MappingNotify]: https://www.x.org/releases/current/doc/xproto/x11protocol.html#events:MappingNotify + +For completeness, if you are looking at this as background for +understanding XKB, I should also mention: + + - [GetKeyboardControl][] + - [ChangeKeyboardControl][] + - [GetPointerControl][] + - [ChangePointerControl][] + +[GetKeyboardControl]: https://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:GetKeyboardControl +[ChangeKeyboardControl]: https://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:ChangeKeyboardControl +[GetPointerControl]: https://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:GetPointerControl +[ChangePointerControl]: https://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:ChangePointerControl + +The `xmodmap` command +--------------------- + +The `xmodmap` command reads a configuration file and modifies the maps +in the X server to match. The `xmodmap` config file has its own +little quirky syntax. For one, the comment character is `!` (and +comments may only start at the *beginning* of the line, but that's +fairly common). + +There are 8 commands that `xmodmap` recognizes. Let's look at those, +grouped by the 3 tasks that the modmap subsystem performs: 1. `keyboard: map keycode -> keysym` @@ -95,25 +197,38 @@ tasks that the modmap subsystem performs: Finds an otherwise unused keycode, and has it map to the specified keysyms. - 2. `keyboard: map keysym -> modifier bit` + 2. `keyboard: map keycode -> modifier bitmask` - `clear MODIFIER` - `add MODIFIERNAME = KEYSYMS...` - - `remove MODIFIER = KEYSYMS...` + - `remove MODIFIERNAME = KEYSYMS...` + + Wait, the modmap subsystem maps *keycodes* to modifiers, but the + commands take *keysyms*? Yup! When executing one of these + commands, it first looks up those keysyms in the keyboard map to + translate them in to a set of keycodes, then associates those + keycodes with that modifier. But how does it look up + keysym->keycode; the protocol only supports querying + keycode->keysym? It + [loops](https://cgit.freedesktop.org/xorg/app/xmodmap/tree/handle.c?h=xmodmap-1.0.9#n59) + over *every* keycode finding all the matches. 3. `pointer: map physical button -> logical button` - `pointer = default` - - This is equivalent to `pointer = 1 2 3 4 5 6...` where the + + This is equivalent to `pointer = 1 2 3 4 5 6...` where the list is as long as the number of buttons that there are. - `pointer = NUMBERS...` - TODO + `pointer = A B C D...` sets the physical button 1 to logical + button A, physical button 2 to logical button B, and so on. + Setting a physical button to logical button 0 disables that + button. Appendix: -========= +--------- I use this snippet in my Emacs configuration to make editing xmodmap files nicer: -- cgit v1.1-4-g5e80