terminal_input/
lib.rs

1extern crate ncurses;
2#[macro_use] extern crate const_cstr;
3
4use core::ops::{BitOr, BitAnd};
5use core::convert::TryInto;
6
7mod imp_ncurses;
8
9/// The set of modifier keys (e.g. Ctrl, Alt, and Shift) that were pressed at the time of an event.
10/// Represented as an opaque bitmap to allow for extension with other keys, such as a Meta or
11/// Command key.
12#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13pub struct Modifiers(u8);
14
15impl BitOr for Modifiers {
16    type Output = Modifiers;
17
18    fn bitor(self, other: Modifiers) -> Modifiers {
19        Modifiers(self.0 | other.0)
20    }
21}
22
23impl BitAnd for Modifiers {
24    type Output = Modifiers;
25
26    fn bitand(self, other: Modifiers) -> Modifiers {
27        Modifiers(self.0 & other.0)
28    }
29}
30
31impl Modifiers {
32    pub const NONE: Modifiers = Modifiers(0);
33
34    pub const SHIFT: Modifiers = Modifiers(0b1);
35    pub const ALT: Modifiers = Modifiers(0b10);
36    pub const CTRL: Modifiers = Modifiers(0b100);
37
38    pub const fn remove(self, other: Modifiers) -> Modifiers {
39        Modifiers(self.0 & !other.0)
40    }
41
42    // Intrinsic const fn impls
43    pub const fn bitor(self, other: Modifiers) -> Modifiers {
44        Modifiers(self.0 | other.0)
45    }
46
47    pub const fn bitand(self, other: Modifiers) -> Modifiers {
48        Modifiers(self.0 & other.0)
49    }
50
51    pub const fn eq(&self, other: &Modifiers) -> bool {
52        self.0 == other.0
53    }
54}
55
56/// A single event generated by a terminal. Simple text input, whether arriving via a pipe, a
57/// paste command, or typing, will be represented with KeyPress events. These events are inherently
58/// lossy and have different levels of support on different terminals. Depending on the use case,
59/// certain modifier keys may just never be recorded, key repeats will be indistinguishable from
60/// orignal presses, pastes may not be bracketed, and key releases may never be registered, among
61/// other failures.
62#[derive(Copy, Clone, Debug)]
63pub enum Event {
64    /// A single typing action by the user, input from stdin. Except between PasteBegin and PasteEnd
65    /// events, these typically will not be control characters, as those are heuristically decoded
66    /// into modifier keys combined with printable characters.
67    KeyPress {
68        modifiers: Modifiers,
69        key: KeyInput,
70        /// Whether this keypress comes from holding down a key
71        is_repeat: bool,
72    },
73    /// This is kept as a separate event from KeyPress as it usually does not want to be handled in
74    /// the same way and is supported by very few terminals, making it easy to miss in testing.
75    KeyRelease {
76        modifiers: Modifiers,
77        key: KeyInput,
78    },
79    /// A motion or click of a mouse button. Modifiers typically are only be available on button
80    /// state changes, not mouse motion.
81    Mouse {
82        device_id: u16,
83        modifiers: Modifiers,
84        buttons: ncurses::ll::mmask_t,
85        x: u32,
86        y: u32,
87    },
88    /// An indication that the following events occur purely as result of the user pasting from
89    /// some unknown location that should be conservatively considered malicious. Applications
90    /// should filter out control commands that happen during a paste, only considering the input
91    /// as raw, unescaped text.
92    PasteBegin,
93    /// The marker indicating a return to normal user interaction.
94    PasteEnd,
95    /// The window has been resized and the application may want to rerender to fit the new sizee.
96    Resize {
97        width: u32,
98        height: u32
99    }
100}
101
102#[derive(Copy, Clone, Debug)]
103pub enum KeyInput {
104    Codepoint(char),
105    /// A raw byte, not part of a unicode codepoint. This is generated when invalid UTF-8 is input.
106    Byte(u8),
107    /// A key not inputting a printable character.
108    Special(i32),
109}
110
111pub struct InputStream<'a> {
112    inner: imp_ncurses::InputStream,
113    screen: ncurses::ll::WINDOW,
114    // To prevent concurrency errors: we own all of stdin.
115    _stdin_lock: std::io::StdinLock<'a>,
116}
117
118impl<'a> InputStream<'a> {
119    pub unsafe fn init_with_ncurses(data: std::io::StdinLock<'a>, screen: ncurses::ll::WINDOW) -> InputStream<'a> {
120        InputStream {
121            inner: imp_ncurses::InputStream::init(screen),
122            screen: screen,
123            _stdin_lock: data
124        }
125    }
126
127    // Wait until a new event is received. Note that the `Err` case should not generally be fatal;
128    // this can be generated in some cases by inputs that terminal-input or ncurses is confused by.
129    // In testing, this tends to happen when scrolling sideways on xterm, for example.
130    pub fn next_event(&mut self) -> Result<Event, ()> {
131        self.inner.next_event(self.screen)
132    }
133
134    // Set the time delay after an escape character is received to distinguish between the escape
135    // key and automatic escape sequences.
136    pub fn set_escdelay(&mut self, escdelay: core::time::Duration) {
137        unsafe {
138            ncurses::ll::set_escdelay(escdelay.as_millis().try_into().unwrap_or(i32::MAX));
139        }
140    }
141}