feat(input): port keyboard, keybinding and mouse input handling

Ports ZIG/src/input (except macOS keymaps) into ghostty-input:
- key, key_mods (incl. RemapSet), the physical keycode table
- key_encode: legacy, modifyOtherKeys and kitty keyboard protocol
  encoders, function key tables
- Binding: all keybinding actions with their user-visible docs (for
  +list-actions), triggers, sets with sequences, chains, leader keys and
  key tables, and formatting back to config strings
- command palette defaults, mouse, paste safety checks, kitty helpers,
  links, helpgen action docs

Deviations (docs/rewrite/notes/ghostty-input.md): Mods.super is named
super_; case folding uses char::to_lowercase plus Ghostty's special
cases instead of uucode tables; chain parents are trigger paths instead
of pointers; a Zig bug where chained leaves inside sequences corrupted the
shared prefix when formatting is fixed (covered by a test).

Integration left: building encode/paste Options from terminal modes,
and implementing the EntryFormatter trait in ghostty-config.

Test Plan:
- cargo test -p ghostty-input (220 tests; 8 macOS-only skipped)
- cargo clippy -p ghostty-input --all-targets -- -D warnings
This commit is contained in:
ddidderr committed 2026-10-11 21:56:07 +02:00
1 parent 3323c3f525
commit 3afe938b95
21 files changed
+13413 -1

No files matched your search

+132
View File
@@ -0,0 +1,132 @@
//! Mouse input types (ZIG/src/input/mouse.zig).
/// The state of a mouse button.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum ButtonState {
Release,
Press,
}
/// Possible mouse buttons. We only track up to 11 because that's the maximum
/// button input that terminal mouse tracking handles without becoming
/// ambiguous.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum Button {
Unknown = 0,
Left = 1,
Right = 2,
Middle = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9,
Ten = 10,
Eleven = 11,
}
impl Button {
/// The maximum value in this enum. This can be used to create a densely
/// packed array, for example.
pub const MAX: i32 = 11;
}
/// The "momentum" of a mouse scroll event. This matches the macOS events
/// because it is the only reliable source right now of momentum events.
/// This is used to handle "inertial scrolling" (i.e. flicking).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[repr(u8)]
pub enum Momentum {
#[default]
None = 0,
Began = 1,
Stationary = 2,
Changed = 3,
Ended = 4,
Cancelled = 5,
MayBegin = 6,
}
impl Momentum {
pub fn from_int(v: u8) -> Option<Momentum> {
Some(match v {
0 => Momentum::None,
1 => Momentum::Began,
2 => Momentum::Stationary,
3 => Momentum::Changed,
4 => Momentum::Ended,
5 => Momentum::Cancelled,
6 => Momentum::MayBegin,
_ => return None,
})
}
}
/// The pressure stage of a pressure-sensitive input device.
///
/// This currently only supports the stages that macOS supports.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[repr(u8)]
pub enum PressureStage {
/// The input device is unpressed.
#[default]
None = 0,
/// The input device is pressed a normal amount. On macOS trackpads,
/// this is after a "click".
Normal = 1,
/// The input device is pressed a deep amount. On macOS trackpads,
/// this is after a "force click".
Deep = 2,
}
/// The bitmask for mods for scroll events.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ScrollMods {
/// True if this is a high-precision scroll event. For example, Apple
/// devices such as Magic Mouse, trackpads, etc. are high-precision
/// and send very detailed scroll events.
pub precision: bool,
/// The momentum phase (if available, supported) of the scroll event.
/// This is used to handle "inertial scrolling" (i.e. flicking).
pub momentum: Momentum,
}
impl ScrollMods {
/// Packed `u8` representation (bit 0 precision, bits 1..4 momentum).
pub fn int(self) -> u8 {
u8::from(self.precision) | ((self.momentum as u8) << 1)
}
pub fn from_int(v: u8) -> ScrollMods {
ScrollMods {
precision: v & 1 != 0,
momentum: Momentum::from_int((v >> 1) & 0b111).unwrap_or(Momentum::None),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
// For our own understanding
#[test]
fn scroll_mods_layout() {
assert_eq!(ScrollMods::default().int(), 0b0);
assert_eq!(
ScrollMods {
precision: true,
..Default::default()
}
.int(),
0b0000_0001
);
assert_eq!(ScrollMods::from_int(0b0000_0011).momentum, Momentum::Began);
}
}