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}