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}