Skip to main content

uncurses/ansi/
kitty.rs

1//! Progressive keyboard enhancement protocol.
2//!
3//! ## Category
4//!
5//! This module emits CSI `u` queries and setters for enhanced keyboard reporting:
6//! requesting active flags, setting/add/removing flags, and pushing/popping a
7//! keyboard flag stack.
8//!
9//! ## CSI format
10//!
11//! The protocol uses private CSI prefixes before the final `u`:
12//!
13//! ```text
14//! ESC [ = flags ; mode u     set/add/remove flags
15//! ESC [ > flags u            push stack frame
16//! ESC [ < count u            pop stack frame(s)
17//! ESC [ ? u                  query active flags
18//! ```
19//!
20//! ## Mode interaction
21//!
22//! These controls manage their own keyboard-reporting state and are independent
23//! of modifyOtherKeys controls in [`crate::ansi::xterm`].
24
25use std::io::{self, Write};
26
27bitflags::bitflags! {
28    /// Bitflags for progressive keyboard reporting.
29    ///
30    /// The numeric value of the flag set is written as the `flags` parameter in
31    /// CSI `u` requests such as `ESC [ = <flags> ; <mode> u`.
32    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
33    pub struct KittyKeyboardFlags: u8 {
34        /// Flag bit `1`: request disambiguated escape-coded keys.
35        const DISAMBIGUATE_ESCAPE_CODES   = 0b0000_0001;
36        /// Flag bit `2`: request press, repeat, and release event-type reporting.
37        const REPORT_EVENT_TYPES          = 0b0000_0010;
38        /// Flag bit `4`: request shifted and base alternate key values.
39        const REPORT_ALTERNATE_KEYS       = 0b0000_0100;
40        /// Flag bit `8`: request escape-sequence reports for all keys.
41        const REPORT_ALL_KEYS_AS_ESCAPE   = 0b0000_1000;
42        /// Flag bit `16`: request associated text for key events.
43        const REPORT_ASSOCIATED_TEXT      = 0b0001_0000;
44        /// All defined keyboard enhancement flags combined.
45        const ALL = Self::DISAMBIGUATE_ESCAPE_CODES.bits()
46                  | Self::REPORT_EVENT_TYPES.bits()
47                  | Self::REPORT_ALTERNATE_KEYS.bits()
48                  | Self::REPORT_ALL_KEYS_AS_ESCAPE.bits()
49                  | Self::REPORT_ASSOCIATED_TEXT.bits();
50    }
51}
52
53/// Request active keyboard enhancement flags: exact bytes `ESC [ ? u` (`b"\x1b[?u"`).
54///
55/// A compatible terminal replies with CSI `? <flags> u`.
56pub const REQUEST_KITTY_KEYBOARD: &[u8] = b"\x1b[?u";
57
58/// Write [`REQUEST_KITTY_KEYBOARD`], the `ESC [ ? u` active-flag query.
59pub fn write_request_kitty_keyboard<W: Write>(w: &mut W) -> io::Result<()> {
60    w.write_all(REQUEST_KITTY_KEYBOARD)
61}
62
63/// Operation mode used by [`write_set_kitty_keyboard`] for CSI `= flags ; mode u`.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65#[repr(u8)]
66pub enum KittyKeyboardMode {
67    /// Replace the active keyboard flags with exactly the supplied flag set; parameter value `1`.
68    Set = 1,
69    /// Add the supplied keyboard flags to the active set; parameter value `2`.
70    Add = 2,
71    /// Remove the supplied keyboard flags from the active set; parameter value `3`.
72    Remove = 3,
73}
74
75/// Set, add, or remove keyboard enhancement flags with `ESC [ = <flags> ; <mode> u`.
76///
77/// `flags.bits()` supplies the decimal flag mask; [`KittyKeyboardMode`] supplies the operation parameter.
78pub fn write_set_kitty_keyboard<W: Write>(
79    w: &mut W,
80    flags: KittyKeyboardFlags,
81    mode: KittyKeyboardMode,
82) -> io::Result<()> {
83    write!(w, "\x1b[={};{}u", flags.bits(), mode as u8)
84}
85
86/// Push a keyboard enhancement stack frame with `ESC [ > <flags> u`.
87///
88/// The decimal flag mask is taken from `flags.bits()`.
89pub fn write_push_kitty_keyboard<W: Write>(w: &mut W, flags: KittyKeyboardFlags) -> io::Result<()> {
90    write!(w, "\x1b[>{}u", flags.bits())
91}
92
93/// Pop keyboard enhancement stack frames with `ESC [ < <count> u`.
94///
95/// `count <= 1` emits the short form `ESC [ < u`; larger counts include the decimal count.
96pub fn write_pop_kitty_keyboard<W: Write>(w: &mut W, count: u16) -> io::Result<()> {
97    if count <= 1 {
98        w.write_all(b"\x1b[<u")
99    } else {
100        write!(w, "\x1b[<{count}u")
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_push() {
110        let mut buf = Vec::new();
111        let flags =
112            KittyKeyboardFlags::DISAMBIGUATE_ESCAPE_CODES | KittyKeyboardFlags::REPORT_EVENT_TYPES;
113        write_push_kitty_keyboard(&mut buf, flags).unwrap();
114        assert_eq!(buf, b"\x1b[>3u");
115    }
116
117    #[test]
118    fn test_pop_default() {
119        let mut buf = Vec::new();
120        write_pop_kitty_keyboard(&mut buf, 1).unwrap();
121        assert_eq!(buf, b"\x1b[<u");
122    }
123
124    #[test]
125    fn test_set() {
126        let mut buf = Vec::new();
127        let flags = KittyKeyboardFlags::DISAMBIGUATE_ESCAPE_CODES;
128        write_set_kitty_keyboard(&mut buf, flags, KittyKeyboardMode::Add).unwrap();
129        assert_eq!(buf, b"\x1b[=1;2u");
130    }
131}