Skip to content

Commit

Permalink
X11: Rewrite input handling in terms of XInput and XKB
Browse files Browse the repository at this point in the history
This patch completes the port of the X11 backend from core input handling to
XInput/XKB input handling. In this context the word 'core' refers to the core
X11 protocol in contrast to protocol extensions such as XInput and XKB.

XInput and XKB are very large protocols that extend X11 with features expected
from modern desktop environments such as

- Support for a rich set of input devices such as touchpads.
- Support for multiple attached keyboards, mice, touchpads, tablets, etc.
- Support for rich and interactive keyboard layouts.

# Breaking Changes

- This patch removes all processing of core input events in favor of XInput
  events. The legacy XIM input method protocol is based on filtering and
  injecting core input events. Therefore, this patch also removes support for
  XIM input methods. Applications are encouraged to switch to more modern IM
  protocols such as [IBus]. These protocols can be implemented in application
  space outside of winit. Note that modern toolskits such as QT5 and chromium
  do not support XIM.

  [IBus]: https://en.wikipedia.org/wiki/Intelligent_Input_Bus

- This patch removes support for synthetic keyboard events. This feature
  cannot be implemented correctly:

  - XKB is a complex state machine where key presses and releases can perform
    a rich set of actions. For example:

    - Switching modifiers on and off.
    - Switching between keyboard layouts.
    - Moving the mouse cursor.

    These actions depend on the order the key are pressed and released.
    For example, if a key that switches layouts is released before a
    regular key, then the release of the regular key will produce
    different events than it would otherwise.

  - The winit API does not permit synthetic `ModifierChanged` events. As such,
    an application cannot distinguish between the user deliberately changing
    the active modifiers and synthetic changes. For example, consider an
    application that performs a drag-and-drop operation as long as the Shift
    modifier is active.

  Applications are encouraged to track the state of keys manually in a way
  that is suitable for their application.

# New and Changed Features

- Winit no longer tracks keyboard events if no winit window has the focus except
  that:

  - Raw keyboard events are still being tracked. A future patch might make this
    behavior optional. See rust-windowing#1634.
  - Changes to the keyboard layout are being tracked at all times.

- The backend now has complete support for multiple seats. For each seat it
  tracks the modifier state and the focused window. In the case of
  `KeyboardInput` events, applications can distinguish multiple seats by
  tracking the value of the `device_id` field. In the case of
  `ModifierChanged` events, applications cannot distinguish different seats. A
  future patch might add a `device_id` field to `ModifierChanged` events.

  The following sequence of events is possible:

  1. Key Press: Seat 1, Left Shift
  2. Modifiers Changed: Shift
  3. Key Press: Seat 2, Left Ctrl
  4. Modifiers Changed: Ctrl
  5. Key Press: Seat 1, KeyA, Text: "A" (due to active Shift)
  6. Key Release: Seat 1, Left Shift
  7. Modifiers Changed: None
  8. Key Release: Seat 2, Left Ctrl
  9. Modifiers Changed: None

- Keyboard state and window events are now completely independent of device
  events. Applications can disable device events by modifying the winit
  source code (or in the future with a supported toggle) without incurring
  regressions in other areas.

- Key release events no longer contain a value in the `text` and
  `text_with_all_modifiers` fields.

- Key presses that are part of a compose sequence no longer contain a value in
  the `text` and `text_with_all_modifiers`. Applications that simply want to
  handle text input can therefore listen for key events and append the values
  of the `text` field to the input buffer without having to track any state.

- The `logical_key` field of key events is no longer affected by compose
  sequences. This is in line with how browsers handle compose sequences.

- Aborted compose sequences no longer produce any `text`. An aborted compose
  sequence is a sequence that was not completed correctly. For example,
  consider the following sequence of keysyms:

  1. Multi_key
  2. (
  3. c
  4. (

  `(` is not a valid continuation of the compose sequence starting with
  `[Multi_key, (, c]`. Therefore it aborts the sequence and no `text` is
  produced (not even for the final `(`). This is in line with existing
  practice on linux.

- The `Dead` `Key` is now used exclusively for those keysyms that have
  `_dead_` in their name. This appears to be in line with how browsers handle
  dead keys.

- The value of a `Dead` `Key` is in one of three categories:

  - If the dead key does not correspond to any particular diacritical mark,
    the value is `None`. For example, `dead_greek` (used to input Greek
    characters on a Latin keyboard).
  - If the dead key has a freestanding variant in unicode, the value is
    `Some(c)` with `c` being the freestanding character. For example,
    `dead_circumflex` has the value `Some('^')`.
  - Otherwise the value is `None`. For example, `dead_belowdot`.

- `key_without_modifiers` now respects the effective XKB group. It only
  discards the state of modifiers. This is essential to correctly handle
  keyboard layouts in the GNOME desktop environment which uses XKB groups to
  switch between layouts.

# Implementation Details

- `EventProcessor` no longer uses any interior mutability. In cases where
  there were conflicting borrows, the code has been rewritten to use
  freestanding functions.

- Keyboard state is now tracked exclusively by xkbcommon. The code that
  manually tracked some of this state has been removed.

- The `xkb_state` module has been significantly simplified. The
  `process_key_event` function now computes all effects produced by a key
  press/release ahead of time.

- Almost all XInput events also carry the current XKB state of its seat. We
  use this to track the state of modifiers eagerly and independently of
  keyboard events.
  • Loading branch information
mahkoh committed Dec 2, 2021
1 parent 03f12db commit 1d6adf5
Show file tree
Hide file tree
Showing 12 changed files with 828 additions and 1,248 deletions.
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux
default = ["x11", "wayland"]
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
stdweb = ["std_web", "instant/stdweb"]
x11 = ["x11-dl", "mio", "mio-misc", "percent-encoding", "parking_lot", "xkbcommon-dl/x11"]
x11 = ["x11-dl", "mio", "mio-misc", "percent-encoding", "parking_lot"]
wayland = ["wayland-client", "sctk", "memmap2"]

[dependencies]
Expand Down Expand Up @@ -93,11 +93,11 @@ wayland-client = { version = "0.28", features = [ "dlopen"] , optional = true }
sctk = { package = "smithay-client-toolkit", version = "0.12.3", optional = true }
mio = { version = "0.7", features = ["os-ext"], optional = true }
mio-misc = { version = "1.0", optional = true }
x11-dl = { version = "2.18.5", optional = true }
x11-dl = { version = "2.19.1", optional = true }
percent-encoding = { version = "2.0", optional = true }
parking_lot = { version = "0.11.0", optional = true }
memmap2 = { version = "0.2.1", optional = true }
xkbcommon-dl = { git = "https://github.com/maroider/xkbcommon-dl", rev = "900832888ad6f11011d1369befb344a9aa8a9610" }
xkbcommon-dl = { git = "https://github.com/mahkoh/xkbcommon-dl", rev = "dd6a9033b1e45a2668700f14c2d47e48aeb3194f" }

[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
package = "web-sys"
Expand Down
51 changes: 51 additions & 0 deletions src/platform_impl/linux/common/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,57 @@ pub fn keysym_to_key(keysym: u32) -> Key<'static> {
keysyms::XKB_KEY_SunVideoRaiseBrightness => Key::BrightnessUp,
// XKB_KEY_SunPowerSwitchShift
//
// Dead keys
keysyms::XKB_KEY_dead_greek => Key::Dead(None),
keysyms::XKB_KEY_dead_currency => Key::Dead(None),
keysyms::XKB_KEY_dead_stroke => Key::Dead(None),
keysyms::XKB_KEY_dead_voiced_sound => Key::Dead(None),
keysyms::XKB_KEY_dead_semivoiced_sound => Key::Dead(None),
keysyms::XKB_KEY_dead_lowline => Key::Dead(None),
keysyms::XKB_KEY_dead_aboveverticalline => Key::Dead(None),
keysyms::XKB_KEY_dead_belowverticalline => Key::Dead(None),
keysyms::XKB_KEY_dead_longsolidusoverlay => Key::Dead(None),
keysyms::XKB_KEY_dead_grave => Key::Dead(Some('`')),
keysyms::XKB_KEY_dead_acute => Key::Dead(Some('´')),
keysyms::XKB_KEY_dead_circumflex => Key::Dead(Some('^')),
keysyms::XKB_KEY_dead_tilde => Key::Dead(Some('~')),
keysyms::XKB_KEY_dead_macron => Key::Dead(Some('¯')),
keysyms::XKB_KEY_dead_breve => Key::Dead(Some('˘')),
keysyms::XKB_KEY_dead_abovedot => Key::Dead(Some('˙')),
keysyms::XKB_KEY_dead_diaeresis => Key::Dead(Some('¨')),
keysyms::XKB_KEY_dead_abovering => Key::Dead(Some('°')),
keysyms::XKB_KEY_dead_doubleacute => Key::Dead(Some('˝')),
keysyms::XKB_KEY_dead_caron => Key::Dead(Some('ˇ')),
keysyms::XKB_KEY_dead_cedilla => Key::Dead(Some('¸')),
keysyms::XKB_KEY_dead_ogonek => Key::Dead(Some('˛')),
keysyms::XKB_KEY_dead_iota => Key::Dead(Some('ͺ')),
keysyms::XKB_KEY_dead_belowdot => Key::Dead(None),
keysyms::XKB_KEY_dead_hook => Key::Dead(None),
keysyms::XKB_KEY_dead_horn => Key::Dead(None),
keysyms::XKB_KEY_dead_abovecomma => Key::Dead(None),
keysyms::XKB_KEY_dead_abovereversedcomma => Key::Dead(None),
keysyms::XKB_KEY_dead_doublegrave => Key::Dead(None),
keysyms::XKB_KEY_dead_belowring => Key::Dead(Some('˳')),
keysyms::XKB_KEY_dead_belowmacron => Key::Dead(Some('ˍ')),
keysyms::XKB_KEY_dead_belowcircumflex => Key::Dead(None),
keysyms::XKB_KEY_dead_belowtilde => Key::Dead(Some('˷')),
keysyms::XKB_KEY_dead_belowbreve => Key::Dead(None),
keysyms::XKB_KEY_dead_belowdiaeresis => Key::Dead(None),
keysyms::XKB_KEY_dead_invertedbreve => Key::Dead(None),
keysyms::XKB_KEY_dead_belowcomma => Key::Dead(None),
keysyms::XKB_KEY_dead_a => Key::Dead(Some('a')),
keysyms::XKB_KEY_dead_A => Key::Dead(Some('A')),
keysyms::XKB_KEY_dead_e => Key::Dead(Some('e')),
keysyms::XKB_KEY_dead_E => Key::Dead(Some('E')),
keysyms::XKB_KEY_dead_i => Key::Dead(Some('i')),
keysyms::XKB_KEY_dead_I => Key::Dead(Some('I')),
keysyms::XKB_KEY_dead_o => Key::Dead(Some('o')),
keysyms::XKB_KEY_dead_O => Key::Dead(Some('O')),
keysyms::XKB_KEY_dead_u => Key::Dead(Some('u')),
keysyms::XKB_KEY_dead_U => Key::Dead(Some('U')),
keysyms::XKB_KEY_dead_small_schwa => Key::Dead(Some('ə')),
keysyms::XKB_KEY_dead_capital_schwa => Key::Dead(Some('Ə')),

0 => Key::Unidentified(NativeKeyCode::Unidentified),
_ => Key::Unidentified(NativeKeyCode::XkbSym(keysym)),
}
Expand Down
Loading

0 comments on commit 1d6adf5

Please sign in to comment.