prettytty/
event.rs

1//! Support for keyboard, mouse, and query response events.
2//!
3//! # Background
4//!
5//! Prettytty's [`Scan`] trait reads so-called [`Token`]s from terminal input,
6//! largely to distinguish between runs of UTF-8 encoded text and ANSI escape
7//! sequences. To avoid the overhead of heap allocation for every single token,
8//! their payloads draw on the scanner's internal buffer. That necessitates
9//! processing a token's payload before reading the next.
10//!
11//! By contrast, this module's [`Event`]s are higher-level abstractions, which
12//! often stem from entire ANSI escape sequences. They also are self-contained,
13//! i.e., not restricted by explicit lifetimes, yet lightweight enough to
14//! implement [`Copy`]. They include key, mouse, window, and response-to-query
15//! events. Since not all valid ANSI escape sequences map to this module's
16//! events, [`ScanEvent::read_event`]  method returns [`TokenOrEvent`], with
17//! tokens serving as fallback. Consistent with how hardware terminals treat
18//! unknown escape sequences, an application may simply ignore such tokens.
19//!
20//! Tokens should suffice for applications that only need to process key presses
21//! for letters, numbers, and common symbols—or the occasional ANSI escape
22//! sequence. Applications that need to process modifier keys, mouse input,
23//! and ANSI escape sequences should prefer events.
24//!
25//! ## Keyboard Input
26//!
27//! Unfortunately, there are several conventions for encoding keyboard and mouse
28//! input from terminals. Legacy encodings for keyboard events share some
29//! patterns, such as
30//! <code>CSI&nbsp;<em>P<sub>key</sub></em>&nbsp;;&nbsp;<em>P<sub>mod</sub></em>&nbsp;~</code>
31//! or <code>SS3&nbsp;<em>key</em></code>. But they also differ for PC, VT-220,
32//! VT-52, Sun, and HP keyboards, as covered in
33//! [xterm](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Special-Keyboard-Keys)'s
34//! documentation and highlighted by [this
35//! table](https://invisible-island.net/xterm/xterm-function-keys.html). Worse,
36//! legacy encodings are incomplete and ambiguous. For example, there is no way
37//! to distinguish between <kbd>ctrl-i</kbd> and <kbd>shift-ctrl-i</kbd>.
38//! <kbd>alt-c</kbd> may really be <kbd>esc</kbd> and <kbd>c</kbd> typed too
39//! quickly after each other. <kbd>alt-c</kbd> also overlaps with the first byte
40//! in the UTF-8 encoding of many extended Latin characters such as é.
41//!
42//! There are a number of attempts to fix these short-comings:
43//!
44//!   * [Fixterms](http://www.leonerd.org.uk/hacks/fixterms/)
45//!   * [kitty's terminal protocol
46//!     extensions](https://sw.kovidgoyal.net/kitty/protocol-extensions/#keyboard-handling),
47//!     which build on fixterms
48//!   * [xterm's
49//!     modifyOtherKeys](https://invisible-island.net/xterm/modified-keys.html)
50//!     enables a partially overlapping encoding
51//!   * [`win32-input-mode`](https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md),
52//!     which encodes the Windows-specific `KEY_EVENT_RECORD` structure
53//!
54//! Currently, prettytty supports common legacy encodings as well as the
55//! encoding based on <code>CSI⋯u</code> shared between fixterms, kitty, and
56//! xterm. No configuration is necessary, all of them are recognized by default.
57//! Prettytty does *not* yet support kitty's progressive enhancements. While
58//! many of the enhancements are useful, their encoding is rather awkward,
59//! distinguishing between semicolons and colons. While that may be consistent
60//! with the letter of some ISO standards, it also is wilfully different from
61//! all other ANSI escape sequences and well-established terminal practice.
62//!
63//!
64//! # The `event` Feature
65//!
66//! This module is entirely optional and requires that the `event` feature is
67//! enabled.
68
69// Some background on encoding [mouse
70// events](https://leonerds-code.blogspot.com/2012/04/wide-mouse-support-in-libvterm.html)
71
72use crate::cmd::{ModeStatus, RequestColor};
73use crate::Token;
74
75/// A token or an event.
76#[derive(Clone, Debug, PartialEq, Eq)]
77pub enum TokenOrEvent<'a> {
78    /// An event.
79    Event(Event),
80    /// A token, which is guaranteed not to be a text token.
81    Token(Token<'a>),
82}
83
84impl TokenOrEvent<'_> {
85    /// Determine whether this is a token.
86    pub const fn is_token(&self) -> bool {
87        matches!(*self, TokenOrEvent::Token(_))
88    }
89
90    /// Determine whether this is an event.
91    pub const fn is_event(&self) -> bool {
92        matches!(*self, TokenOrEvent::Event(_))
93    }
94
95    /// Drop any token, returning events only.
96    pub fn as_event(&self) -> Option<Event> {
97        match *self {
98            TokenOrEvent::Event(event) => Some(event),
99            _ => None,
100        }
101    }
102
103    /// Map an event, dropping the token.
104    pub fn map_event<R, F>(&self, op: F) -> Option<R>
105    where
106        F: FnOnce(Event) -> R,
107    {
108        match *self {
109            TokenOrEvent::Event(event) => Some(op(event)),
110            _ => None,
111        }
112    }
113
114    /// Map either token or event to a common (result) type.
115    ///
116    /// This method consumes the token-or-event instance.
117    pub fn map_either<R, Fe, Ft>(self, map_event: Fe, map_token: Ft) -> Option<R>
118    where
119        Fe: FnOnce(Event) -> Option<R>,
120        Ft: FnOnce(Token<'_>) -> Option<R>,
121    {
122        match self {
123            TokenOrEvent::Event(event) => map_event(event),
124            TokenOrEvent::Token(token) => map_token(token),
125        }
126    }
127}
128
129/// An input event.
130#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131pub enum Event {
132    /// A key press, repeat, or release.
133    Key(KeyEvent),
134    /// A mouse move, button press, mouse drag, or button release.
135    Mouse(MouseEvent),
136    /// A window event.
137    Window(WindowEvent),
138    /// A cursor event.
139    Cursor(CursorEvent),
140    /// A color event.
141    Color(ColorEvent),
142    /// A terminal mode event.
143    Mode(ModeStatus),
144}
145
146// -------------------------------------------------------------------------------------
147
148/// A mouse event.
149///
150/// This struct captures mouse movements along with keyboard modifiers and
151/// button presses. Since every mouse event includes a screen coordinate, this
152/// struct includes both coordinates inline.
153#[derive(Clone, Copy, Debug, PartialEq, Eq)]
154pub struct MouseEvent {
155    pub kind: MouseEventKind,
156    pub modifiers: Modifiers,
157    pub column: u16,
158    pub row: u16,
159}
160
161/// The kind of mouse event.
162///
163/// What [`Press`](MouseEventKind::Press), [`Drag`](MouseEventKind::Drag), and
164/// [`Release`](MouseEventKind::Release) are to mice,
165/// [`Press`](KeyboardEventKind::Press), [`Repeat`](MouseEventKind::Repeat), and
166/// [`Release`](MouseEventKind::Release) are to keyboards.
167#[derive(Clone, Copy, Debug, PartialEq, Eq)]
168pub enum MouseEventKind {
169    Press(MouseButton),
170    Drag(MouseButton),
171    Release(MouseButton),
172    Move,
173}
174
175/// The mouse button.
176#[derive(Clone, Copy, Debug, PartialEq, Eq)]
177pub enum MouseButton {
178    Left,
179    Middle,
180    Right,
181}
182
183// -------------------------------------------------------------------------------------
184
185/// A key event.
186#[derive(Clone, Copy, Debug, PartialEq, Eq)]
187pub struct KeyEvent {
188    pub kind: KeyEventKind,
189    pub modifiers: Modifiers,
190    pub key: Key,
191}
192
193/// The kind of key event.
194///
195/// What [`Press`](MouseEventKind::Press), [`Drag`](MouseEventKind::Drag), and
196/// [`Release`](MouseEventKind::Release) are to mice,
197/// [`Press`](KeyboardEventKind::Press), [`Repeat`](MouseEventKind::Repeat), and
198/// [`Release`](MouseEventKind::Release) are to keyboards.
199#[derive(Clone, Copy, Debug, PartialEq, Eq)]
200pub enum KeyEventKind {
201    Press,
202    Repeat,
203    Release,
204}
205
206/// A logical modifier of keys and mouse button presses.
207///
208/// This enumeration abstracts over the concrete [modifier key](ModifierKey),
209/// which may be on the left or right side of the keyboard, and instead includes
210/// variants for the logical modifier only. Contemporary keyboard generally
211/// include the following modifier keys:
212///
213///   * <kbd>shift</kbd>
214///   * <kbd>alt</kbd> or<kbd>option</kbd>
215///   * <kbd>control</kbd>
216///   * <kbd>Windows</kbd>, <kbd>Linux</kbd>, or <kbd>command</kbd>
217///   * <kbd>caps lock</kbd>
218///   * possibly <kbd>num lock</kbd>
219///
220/// Influential past keyboards, such as the [space-cadet
221/// keyboard](https://en.wikipedia.org/wiki/Space-cadet_keyboard), further
222/// included the <kbd>super</kbd>, <kbd>hyper</kbd>, and <kbd>meta</kbd>
223/// modifiers.
224///
225/// Terminal emulators usually agree only on the names of the first three
226/// modifiers, (1) <kbd>shift</kbd>, (2) <kbd>alt</kbd>/<kbd>option</kbd>, and
227/// (3) <kbd>control</kbd>. Amongst further modifiers, xterm labels the fourth
228/// one <kbd>meta</kbd>, whereas kitty calls that modifier <kbd>super</kbd> and
229/// the sixth modifier <kbd>meta</kbd>. Given these divergent names, prettytty
230/// uses a neutral term, <kbd>command</kbd>, for the fourth modifier.
231///
232/// The [`Modifier::Keypad`] variant is a virtual modifier, i.e., it has no
233/// physical key. It is used to distinguish keys, such as <kbd>/</kbd> or
234/// <kbd>=</kbd>, that appear even on a 60% or 65% keyboard from equally
235/// labelled keys abutting the numeric pad, which only appears on 96% or 100%
236/// keyboards.
237#[derive(Clone, Copy, Debug, PartialEq, Eq)]
238#[non_exhaustive]
239pub enum Modifier {
240    /// The <kbd>shift</kbd> key.
241    Shift = 0x01,
242    /// The <kbd>alt</kbd> or <kbd>option</kbd> key.
243    Option = 0x02,
244    /// The <kbd>control</kbd> key.
245    Control = 0x04,
246    /// The <kbd>Windows</kbd>, <kbd>Linux</kbd>, or <kbd>command</kbd> key.
247    /// Xterm labels this modifier as <kbd>meta</kbd> and Kitty labels it as
248    /// <kbd>super</kbd>
249    Command = 0x08,
250    /// A first extra modifier, labelled <kbd>hyper</kbd> by Kitty.
251    Extra1 = 0x10,
252    /// A second extra modifier, labelled <kbd>meta</kbd> by Kitty.
253    Extra2 = 0x20,
254    /// The <kbd>caps lock</kbd> key.
255    CapsLock = 0x40,
256    /// The <kbd>number lock</kbd> key.
257    NumLock = 0x80,
258    /// A virtual modifier indicating a keypad key.
259    Keypad = 0x100,
260}
261
262impl Modifier {
263    const fn successor(&self) -> Option<Self> {
264        use Modifier::*;
265
266        Some(match *self {
267            Shift => Option,
268            Option => Control,
269            Control => Command,
270            Command => Extra1,
271            Extra1 => Extra2,
272            Extra2 => CapsLock,
273            CapsLock => NumLock,
274            NumLock => Keypad,
275            Keypad => return None,
276        })
277    }
278}
279
280impl From<Modifier> for Modifiers {
281    fn from(value: Modifier) -> Self {
282        Self(value as u16)
283    }
284}
285
286impl<M: Into<Modifiers>> core::ops::Add<M> for Modifier {
287    type Output = Modifiers;
288
289    fn add(self, rhs: M) -> Self::Output {
290        Modifiers::combine(self as u16, rhs.into().0)
291    }
292}
293
294impl<M: Into<Modifiers>> core::ops::Add<M> for Modifiers {
295    type Output = Modifiers;
296
297    fn add(self, rhs: M) -> Self::Output {
298        Self::combine(self.0, rhs.into().0)
299    }
300}
301
302impl<M: Into<Modifiers>> core::ops::AddAssign<M> for Modifiers {
303    fn add_assign(&mut self, rhs: M) {
304        *self = Self::combine(self.0, rhs.into().0)
305    }
306}
307
308impl<M: Into<Modifiers>> core::ops::Sub<M> for Modifier {
309    type Output = Modifiers;
310
311    fn sub(self, rhs: M) -> Self::Output {
312        Modifiers(self as u16 & !rhs.into().0)
313    }
314}
315
316impl<M: Into<Modifiers>> core::ops::Sub<M> for Modifiers {
317    type Output = Modifiers;
318
319    fn sub(self, rhs: M) -> Self::Output {
320        Self(self.0 & !rhs.into().0)
321    }
322}
323
324impl<M: Into<Modifiers>> core::ops::SubAssign<M> for Modifiers {
325    fn sub_assign(&mut self, rhs: M) {
326        *self = Self(self.0 & !rhs.into().0)
327    }
328}
329
330/// A key's logical modifiers.
331///
332/// This struct combines zero or more logical modifiers. In fact, it defaults to
333/// zero modifiers.
334#[derive(Clone, Copy, Default, PartialEq, Eq)]
335pub struct Modifiers(u16);
336
337impl Modifiers {
338    fn combine(left: u16, right: u16) -> Self {
339        Self(left | right)
340    }
341
342    /// Decode an ANSI escape parameter.
343    pub fn from_escape(code: u16) -> Option<Self> {
344        let code = code.saturating_sub(1);
345        if code <= (u8::MAX as u16) {
346            Some(Self(code))
347        } else {
348            None
349        }
350    }
351
352    /// Determine whether there are no active modifiers.
353    pub const fn is_empty(&self) -> bool {
354        self.0 == 0
355    }
356
357    /// Get the number of active modifiers.
358    pub const fn len(&self) -> usize {
359        self.0.count_ones() as usize
360    }
361
362    /// Determine whether the given modifier is enabled.
363    pub fn has(&self, modifier: Modifier) -> bool {
364        self.0 & modifier as u16 != 0
365    }
366
367    /// Get an iterator over the active modifiers.
368    pub fn modifiers(&self) -> ModifierIter {
369        ModifierIter {
370            modifiers: *self,
371            cursor: None,
372            remaining: self.len(),
373        }
374    }
375}
376
377impl core::fmt::Debug for Modifiers {
378    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
379        f.debug_set().entries(self.modifiers()).finish()
380    }
381}
382
383/// An iterator over modifiers.
384#[derive(Debug)]
385pub struct ModifierIter {
386    modifiers: Modifiers,
387    cursor: Option<Modifier>,
388    remaining: usize,
389}
390
391impl Iterator for ModifierIter {
392    type Item = Modifier;
393
394    fn next(&mut self) -> Option<Self::Item> {
395        loop {
396            let modifier = match self.cursor {
397                None => Modifier::Shift,
398                Some(Modifier::NumLock) => return None,
399                Some(modifier) => modifier
400                    .successor()
401                    .expect("with no-successor case already handled, successor must exist"),
402            };
403            self.cursor = Some(modifier);
404
405            if self.modifiers.has(modifier) {
406                self.remaining -= 1;
407                return Some(modifier);
408            }
409        }
410    }
411
412    fn size_hint(&self) -> (usize, Option<usize>) {
413        (self.remaining, Some(self.remaining))
414    }
415}
416
417impl ExactSizeIterator for ModifierIter {
418    fn len(&self) -> usize {
419        self.remaining
420    }
421}
422
423impl core::iter::FusedIterator for ModifierIter {}
424
425// -------------------------------------------------------------------------------------
426
427/// A key.
428#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
429#[non_exhaustive]
430pub enum Key {
431    Escape,
432    Enter,
433    Tab,
434    Backspace,
435    Insert,
436    Delete,
437    Left,
438    Right,
439    Up,
440    Down,
441    PageUp,
442    PageDown,
443    Home,
444    End,
445    CapsLock,
446    ScrollLock,
447    NumLock,
448    PrintScreen,
449    Pause,
450    Menu,
451    KeypadBegin,
452    F(u8),
453    Media(MediaKey),
454    Mod(ModifierKey),
455    Char(char),
456}
457
458impl Key {
459    /// Get the key corresponding to SS3/CSI-1 and the given byte.
460    pub fn with_ss3(byte: u8) -> Option<Self> {
461        use Key::*;
462
463        let key = match byte {
464            b' ' => Char(' '),
465            b'A' => Up,
466            b'B' => Down,
467            b'C' => Right,
468            b'D' => Left,
469            b'E' => KeypadBegin,
470            b'F' => End,
471            b'H' => Home,
472            b'I' => Tab,
473            b'M' => Enter,
474            b'P' => F(1),
475            b'Q' => F(2),
476            b'R' => F(3),
477            b'S' => F(4),
478            b'X' => Char('='),
479            b'j' => Char('*'),
480            b'k' => Char('+'),
481            b'l' => Char(','),
482            b'm' => Char('-'),
483            b'n' => Char('.'),
484            b'o' => Char('/'),
485            b'p' => Char('0'),
486            b'q' => Char('1'),
487            b'r' => Char('2'),
488            b's' => Char('3'),
489            b't' => Char('4'),
490            b'u' => Char('5'),
491            b'v' => Char('6'),
492            b'w' => Char('7'),
493            b'x' => Char('8'),
494            b'y' => Char('9'),
495            _ => return None,
496        };
497
498        Some(key)
499    }
500
501    /// Map a function key code to a key.
502    ///
503    /// For a DECFNK escape sequence, that is, CSI *Pcode* ; *Pmod* ~, this
504    /// method maps the code parameter to a key.
505    pub fn with_function_key(code: u8) -> Option<Self> {
506        use Key::*;
507
508        // https://www.xfree86.org/current/ctlseqs.html
509        // https://vt100.net/docs/vt510-rm/DECFNK.html
510        let key = match code {
511            1 => Home,
512            2 => Insert,
513            3 => Delete,
514            4 => End,
515            5 => PageUp,
516            6 => PageDown,
517            7 => Left, // kitty incorrectly assigns Home
518            8 => Down, // kitty incorrectly assigns End
519            9 => Up,
520            10 => Right,
521            11..=15 => F(code - 10), // F1..=F5
522            17..=21 => F(code - 11), // F6..=F10
523            23..=26 => F(code - 12), // F11..=F14
524            28..=29 => F(code - 13), // F15..=F16
525            31..=34 => F(code - 14), // F17..=F20
526            _ => return None,
527        };
528
529        Some(key)
530    }
531
532    /// Map the CSI/u key code to a key.
533    pub fn with_csi_u(code: u16) -> Option<(Self, Modifiers)> {
534        use Key::*;
535
536        let key = match code {
537            9 => Tab,
538            13 => Enter,
539            27 => Escape,
540            127 => Backspace,
541
542            57_358 => CapsLock,
543            57_359 => ScrollLock,
544            57_360 => NumLock,
545            57_361 => PrintScreen,
546            57_362 => Pause,
547            57_363 => Menu,
548            57_376..=57_398 => F((code - 57_376) as u8 + 13),
549            // Keypad keys
550            57_399..=57_408 => Char((b'0' + (code - 57_399) as u8) as char),
551            57_409 => Char('.'),
552            57_410 => Char('/'),
553            57_411 => Char('*'),
554            57_412 => Char('-'),
555            57_413 => Char('+'),
556            57_414 => Enter,
557            57_415 => Char('='),
558            57_416 => Char(','),
559            57_417 => Left,
560            57_418 => Right,
561            57_419 => Up,
562            57_420 => Down,
563            57_421 => PageUp,
564            57_422 => PageDown,
565            57_423 => Home,
566            57_424 => End,
567            57_425 => Insert,
568            57_426 => Delete,
569            57_427 => KeypadBegin,
570            // Media and modifier keys
571            57_428..=57_440 => Media(MediaKey::with_csi_u(code)?),
572            57_441..=57_452 => Mod(ModifierKey::with_csi_u(code)?),
573            _ => return None,
574        };
575
576        let modifiers = if (57_399..=57_427).contains(&code) {
577            self::Modifier::Keypad.into()
578        } else {
579            Modifiers::default()
580        };
581
582        Some((key, modifiers))
583    }
584}
585
586/// A media key.
587#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
588pub enum MediaKey {
589    Play,
590    Pause,
591    PlayPause,
592    Reverse,
593    Stop,
594    FastForward,
595    Rewind,
596    NextTrack,
597    PreviousTrack,
598    Record,
599    LowerVolume,
600    RaiseVolume,
601    MuteVolume,
602}
603
604impl MediaKey {
605    /// Map the CSI/u key code to a media key.
606    pub fn with_csi_u(code: u16) -> Option<Self> {
607        use MediaKey::*;
608
609        let key = match code {
610            57_428 => Play,
611            57_429 => Pause,
612            57_430 => PlayPause,
613            57_431 => Reverse,
614            57_432 => Stop,
615            57_433 => FastForward,
616            57_434 => Rewind,
617            57_435 => NextTrack,
618            57_436 => PreviousTrack,
619            57_437 => Record,
620            57_438 => LowerVolume,
621            57_439 => RaiseVolume,
622            57_440 => MuteVolume,
623            _ => return None,
624        };
625
626        Some(key)
627    }
628}
629
630/// A physical modifier key.
631///
632/// This enumeration comprises physical keys instead of logical [`Modifier`]s.
633#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
634pub enum ModifierKey {
635    LeftShift,
636    LeftControl,
637    LeftOption,
638    LeftCommand,
639    LeftExtra1,
640    LeftExtra2,
641    RightShift,
642    RightControl,
643    RightOption,
644    RightCommand,
645    RightExtra1,
646    RightExtra2,
647}
648
649impl ModifierKey {
650    /// Map the CSI/u key code to a modifier key.
651    pub fn with_csi_u(code: u16) -> Option<Self> {
652        use ModifierKey::*;
653
654        let key = match code {
655            57_441 => LeftShift,
656            57_442 => LeftControl,
657            57_443 => LeftOption,
658            57_444 => LeftCommand,
659            57_445 => LeftExtra1,
660            57_446 => LeftExtra2,
661            57_447 => RightShift,
662            57_448 => RightControl,
663            57_449 => RightOption,
664            57_450 => RightCommand,
665            57_451 => RightExtra1,
666            57_452 => RightExtra2,
667            _ => return None,
668        };
669
670        Some(key)
671    }
672
673    /// Convert this modifier key into a logical modifier flag.
674    pub fn as_modifier(&self) -> Modifier {
675        use ModifierKey::*;
676
677        match *self {
678            LeftShift => Modifier::Shift,
679            LeftControl => Modifier::Control,
680            LeftOption => Modifier::Option,
681            LeftCommand => Modifier::Command,
682            LeftExtra1 => Modifier::Extra1,
683            LeftExtra2 => Modifier::Extra2,
684            RightShift => Modifier::Shift,
685            RightControl => Modifier::Control,
686            RightOption => Modifier::Option,
687            RightCommand => Modifier::Command,
688            RightExtra1 => Modifier::Extra1,
689            RightExtra2 => Modifier::Extra2,
690        }
691    }
692}
693
694/// An event to indicate a change in terminal size.
695#[derive(Clone, Copy, Debug, PartialEq, Eq)]
696pub struct WindowEvent {
697    pub columns: u16,
698    pub rows: u16,
699}
700
701/// An event to indicate a cursor position.
702#[derive(Clone, Copy, Debug, PartialEq, Eq)]
703pub struct CursorEvent {
704    pub column: u16,
705    pub row: u16,
706}
707
708/// An event to indicate a concrete color.
709#[derive(Clone, Copy, Debug, PartialEq, Eq)]
710pub struct ColorEvent {
711    pub kind: RequestColor,
712    /// The value has three components, with each component comprising a
713    /// magnitude and a maximum value (which is either 0xff, 0xffff, 0xffffff,
714    /// or 0xffff_ffff).
715    pub value: [(u16, u16); 3],
716}