prettytty/
event.rs

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