1 //! This is a WINDOWS specific implementation for input related action.
2
3 use std::{char, collections::VecDeque, io, sync::Mutex};
4
5 use crossterm_winapi::{
6 ButtonState, Console, ConsoleMode, EventFlags, Handle, InputEventType, KeyEventRecord,
7 MouseEvent, ScreenBuffer,
8 };
9 use winapi::um::{
10 wincon::{
11 LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED, SHIFT_PRESSED,
12 },
13 winnt::INT,
14 winuser::{
15 VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F10, VK_F11, VK_F12,
16 VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_HOME, VK_INSERT, VK_LEFT,
17 VK_MENU, VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_UP,
18 },
19 };
20
21 use lazy_static::lazy_static;
22
23 use crate::input::{input::Input, InputEvent, KeyEvent, MouseButton};
24 use crate::utils::Result;
25
26 const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008;
27
28 lazy_static! {
29 static ref ORIGINAL_CONSOLE_MODE: Mutex<Option<u32>> = Mutex::new(None);
30 }
31
32 /// Initializes the default console color. It will will be skipped if it has already been initialized.
init_original_console_mode(original_mode: u32)33 fn init_original_console_mode(original_mode: u32) {
34 let mut lock = ORIGINAL_CONSOLE_MODE.lock().unwrap();
35
36 if lock.is_none() {
37 *lock = Some(original_mode);
38 }
39 }
40
41 /// Returns the original console color, make sure to call `init_console_color` before calling this function. Otherwise this function will panic.
original_console_mode() -> u3242 fn original_console_mode() -> u32 {
43 // safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()`
44 ORIGINAL_CONSOLE_MODE
45 .lock()
46 .unwrap()
47 .expect("Original console mode not set")
48 }
49
50 pub(crate) struct WindowsInput;
51
52 impl WindowsInput {
new() -> WindowsInput53 pub fn new() -> WindowsInput {
54 WindowsInput
55 }
56 }
57
58 impl Input for WindowsInput {
read_char(&self) -> Result<char>59 fn read_char(&self) -> Result<char> {
60 // _getwch is without echo and _getwche is with echo
61 let pressed_char = unsafe { _getwche() };
62
63 // we could return error but maybe option to keep listening until valid character is inputted.
64 if pressed_char == 0 || pressed_char == 0xe0 {
65 Err(io::Error::new(
66 io::ErrorKind::Other,
67 "Given input char is not a valid char, mostly occurs when pressing special keys",
68 ))?;
69 }
70
71 let ch = char::from_u32(pressed_char as u32).ok_or_else(|| {
72 io::Error::new(io::ErrorKind::Other, "Could not parse given input to char")
73 })?;
74
75 Ok(ch)
76 }
77
read_async(&self) -> AsyncReader78 fn read_async(&self) -> AsyncReader {
79 let handle = Handle::current_in_handle().expect("failed to create console input handle");
80 let console = Console::from(handle);
81 AsyncReader::new(console, None)
82 }
83
read_until_async(&self, delimiter: u8) -> AsyncReader84 fn read_until_async(&self, delimiter: u8) -> AsyncReader {
85 let handle = Handle::current_in_handle().expect("failed to create console input handle");
86 let console = Console::from(handle);
87 AsyncReader::new(console, Some(delimiter))
88 }
89
read_sync(&self) -> SyncReader90 fn read_sync(&self) -> SyncReader {
91 SyncReader
92 }
93
enable_mouse_mode(&self) -> Result<()>94 fn enable_mouse_mode(&self) -> Result<()> {
95 let mode = ConsoleMode::from(Handle::current_in_handle()?);
96
97 init_original_console_mode(mode.mode()?);
98 mode.set_mode(ENABLE_MOUSE_MODE)?;
99
100 Ok(())
101 }
102
disable_mouse_mode(&self) -> Result<()>103 fn disable_mouse_mode(&self) -> Result<()> {
104 let mode = ConsoleMode::from(Handle::current_in_handle()?);
105 mode.set_mode(original_console_mode())?;
106 Ok(())
107 }
108 }
109
110 /// A synchronous input reader (blocking).
111 ///
112 /// `SyncReader` implements the [`Iterator`](https://doc.rust-lang.org/std/iter/index.html#iterator)
113 /// trait. Documentation says:
114 ///
115 /// > An iterator has a method, `next`, which when called, returns an `Option<Item>`. `next` will return
116 /// > `Some(Item)` as long as there are elements, and once they've all been exhausted, will return `None`
117 /// > to indicate that iteration is finished. Individual iterators may choose to resume iteration, and
118 /// > so calling `next` again may or may not eventually start returning `Some(Item)` again at some point.
119 ///
120 /// `SyncReader` is an individual iterator and it doesn't use `None` to indicate that the iteration is
121 /// finished. You can expect additional `Some(InputEvent)` after calling `next` even if you have already
122 /// received `None`. Unfortunately, `None` means that an error occurred, but you're free to call `next`
123 /// again. This behavior will be changed in the future to avoid errors consumption.
124 ///
125 /// # Notes
126 ///
127 /// * It requires enabled raw mode (see the
128 /// [`crossterm_screen`](https://docs.rs/crossterm_screen/) crate documentation to learn more).
129 /// * See the [`AsyncReader`](struct.AsyncReader.html) if you want a non blocking reader.
130 ///
131 /// # Examples
132 ///
133 /// ```no_run
134 /// use std::{thread, time::Duration};
135 ///
136 /// use crossterm::{screen::RawScreen, input::{input, InputEvent, KeyEvent}};
137 ///
138 /// fn main() {
139 /// println!("Press 'ESC' to quit.");
140 ///
141 /// // Enable raw mode and keep the `_raw` around otherwise the raw mode will be disabled
142 /// let _raw = RawScreen::into_raw_mode();
143 ///
144 /// // Create an input from our screen
145 /// let input = input();
146 ///
147 /// // Create a sync reader
148 /// let mut reader = input.read_sync();
149 ///
150 /// loop {
151 /// if let Some(event) = reader.next() { // Blocking call
152 /// match event {
153 /// InputEvent::Keyboard(KeyEvent::Esc) => {
154 /// println!("Program closing ...");
155 /// break;
156 /// }
157 /// InputEvent::Mouse(event) => { /* Mouse event */ }
158 /// _ => { /* Other events */ }
159 /// }
160 /// }
161 /// thread::sleep(Duration::from_millis(50));
162 /// }
163 /// } // `_raw` dropped <- raw mode disabled
164 /// ```
165 pub struct SyncReader;
166
167 impl Iterator for SyncReader {
168 type Item = InputEvent;
169
170 /// Tries to read the next input event (blocking).
171 ///
172 /// `None` doesn't mean that the iteration is finished. See the
173 /// [`SyncReader`](struct.SyncReader.html) documentation for more information.
next(&mut self) -> Option<Self::Item>174 fn next(&mut self) -> Option<Self::Item> {
175 // This synces the behaviour with the unix::SyncReader (& documentation) where
176 // None is returned in case of error.
177 read_single_event().unwrap_or(None)
178 }
179 }
180
181 /// An asynchronous input reader (not blocking).
182 ///
183 /// `AsyncReader` implements the [`Iterator`](https://doc.rust-lang.org/std/iter/index.html#iterator)
184 /// trait. Documentation says:
185 ///
186 /// > An iterator has a method, `next`, which when called, returns an `Option<Item>`. `next` will return
187 /// > `Some(Item)` as long as there are elements, and once they've all been exhausted, will return `None`
188 /// > to indicate that iteration is finished. Individual iterators may choose to resume iteration, and
189 /// > so calling `next` again may or may not eventually start returning `Some(Item)` again at some point.
190 ///
191 /// `AsyncReader` is an individual iterator and it doesn't use `None` to indicate that the iteration is
192 /// finished. You can expect additional `Some(InputEvent)` after calling `next` even if you have already
193 /// received `None`.
194 ///
195 /// # Notes
196 ///
197 /// * It requires enabled raw mode (see the
198 /// [`crossterm_screen`](https://docs.rs/crossterm_screen/) crate documentation to learn more).
199 /// * A thread is spawned to read the input.
200 /// * The reading thread is cleaned up when you drop the `AsyncReader`.
201 /// * See the [`SyncReader`](struct.SyncReader.html) if you want a blocking,
202 /// or a less resource hungry reader.
203 ///
204 /// # Examples
205 ///
206 /// ```no_run
207 /// use std::{thread, time::Duration};
208 ///
209 /// use crossterm::{screen::RawScreen, input::{input, InputEvent, KeyEvent}};
210 ///
211 /// fn main() {
212 /// println!("Press 'ESC' to quit.");
213 ///
214 /// // Enable raw mode and keep the `_raw` around otherwise the raw mode will be disabled
215 /// let _raw = RawScreen::into_raw_mode();
216 ///
217 /// // Create an input from our screen
218 /// let input = input();
219 ///
220 /// // Create an async reader
221 /// let mut reader = input.read_async();
222 ///
223 /// loop {
224 /// if let Some(event) = reader.next() { // Not a blocking call
225 /// match event {
226 /// InputEvent::Keyboard(KeyEvent::Esc) => {
227 /// println!("Program closing ...");
228 /// break;
229 /// }
230 /// InputEvent::Mouse(event) => { /* Mouse event */ }
231 /// _ => { /* Other events */ }
232 /// }
233 /// }
234 /// thread::sleep(Duration::from_millis(50));
235 /// }
236 /// } // `reader` dropped <- thread cleaned up, `_raw` dropped <- raw mode disabled
237 /// ```
238 pub struct AsyncReader {
239 console: Console,
240 buffer: VecDeque<InputEvent>,
241 delimiter: Option<u8>,
242 }
243
244 impl AsyncReader {
245 // TODO Should the new() really be public?
246 /// Creates a new `AsyncReader`.
247 ///
248 /// # Notes
249 ///
250 /// * A thread is spawned to read the input.
251 /// * The reading thread is cleaned up when you drop the `AsyncReader`.
new(console: Console, delimiter: Option<u8>) -> AsyncReader252 pub fn new(console: Console, delimiter: Option<u8>) -> AsyncReader {
253 AsyncReader {
254 console,
255 buffer: VecDeque::new(),
256 delimiter,
257 }
258 }
259
stop(&mut self)260 pub fn stop(&mut self) {}
261 }
262
263 impl Iterator for AsyncReader {
264 type Item = InputEvent;
265
266 /// Tries to read the next input event (not blocking).
267 ///
268 /// `None` doesn't mean that the iteration is finished. See the
269 /// [`AsyncReader`](struct.AsyncReader.html) documentation for more information.
next(&mut self) -> Option<Self::Item>270 fn next(&mut self) -> Option<Self::Item> {
271 loop {
272 if self.buffer.is_empty() {
273 let (_, events) = read_input_events(&self.console).expect("read failed");
274
275 if events.is_empty() {
276 return None;
277 }
278
279 self.buffer.extend(events);
280 }
281
282 if let Some(delimiter) = self.delimiter {
283 while let Some(e) = self.buffer.pop_front() {
284 if let InputEvent::Keyboard(KeyEvent::Char(key)) = e {
285 if (key as u8) == delimiter {
286 return Some(e);
287 }
288 }
289 }
290
291 continue;
292 }
293
294 return self.buffer.pop_front();
295 }
296 }
297 }
298
299 extern "C" {
_getwche() -> INT300 fn _getwche() -> INT;
301 }
302
read_single_event() -> Result<Option<InputEvent>>303 fn read_single_event() -> Result<Option<InputEvent>> {
304 let console = Console::from(Handle::current_in_handle()?);
305
306 let input = console.read_single_input_event()?;
307
308 match input.event_type {
309 InputEventType::KeyEvent => {
310 handle_key_event(unsafe { KeyEventRecord::from(*input.event.KeyEvent()) })
311 }
312 InputEventType::MouseEvent => {
313 handle_mouse_event(unsafe { MouseEvent::from(*input.event.MouseEvent()) })
314 }
315 // NOTE (@imdaveho): ignore below
316 InputEventType::WindowBufferSizeEvent => return Ok(None), // TODO implement terminal resize event
317 InputEventType::FocusEvent => Ok(None),
318 InputEventType::MenuEvent => Ok(None),
319 }
320 }
321
322 /// partially inspired by: https://github.com/retep998/wio-rs/blob/master/src/console.rs#L130
read_input_events(console: &Console) -> Result<(u32, Vec<InputEvent>)>323 fn read_input_events(console: &Console) -> Result<(u32, Vec<InputEvent>)> {
324 let result = console.read_console_input()?;
325
326 let mut input_events = Vec::with_capacity(result.0 as usize);
327
328 for input in result.1 {
329 match input.event_type {
330 InputEventType::KeyEvent => {
331 if let Ok(Some(event)) =
332 handle_key_event(unsafe { KeyEventRecord::from(*input.event.KeyEvent()) })
333 {
334 input_events.push(event)
335 }
336 }
337 InputEventType::MouseEvent => {
338 if let Ok(Some(event)) =
339 handle_mouse_event(unsafe { MouseEvent::from(*input.event.MouseEvent()) })
340 {
341 input_events.push(event)
342 }
343 }
344 // NOTE (@imdaveho): ignore below
345 InputEventType::WindowBufferSizeEvent => (), // TODO implement terminal resize event
346 InputEventType::FocusEvent => (),
347 InputEventType::MenuEvent => (),
348 }
349 }
350
351 return Ok((result.0, input_events));
352 }
353
handle_mouse_event(mouse_event: MouseEvent) -> Result<Option<InputEvent>>354 fn handle_mouse_event(mouse_event: MouseEvent) -> Result<Option<InputEvent>> {
355 if let Ok(Some(event)) = parse_mouse_event_record(&mouse_event) {
356 return Ok(Some(InputEvent::Mouse(event)));
357 }
358 Ok(None)
359 }
360
handle_key_event(key_event: KeyEventRecord) -> Result<Option<InputEvent>>361 fn handle_key_event(key_event: KeyEventRecord) -> Result<Option<InputEvent>> {
362 if key_event.key_down {
363 if let Some(event) = parse_key_event_record(&key_event) {
364 return Ok(Some(InputEvent::Keyboard(event)));
365 }
366 }
367
368 return Ok(None);
369 }
370
parse_key_event_record(key_event: &KeyEventRecord) -> Option<KeyEvent>371 fn parse_key_event_record(key_event: &KeyEventRecord) -> Option<KeyEvent> {
372 let key_code = key_event.virtual_key_code as i32;
373 match key_code {
374 VK_SHIFT | VK_CONTROL | VK_MENU => None,
375 VK_BACK => Some(KeyEvent::Backspace),
376 VK_ESCAPE => Some(KeyEvent::Esc),
377 VK_RETURN => Some(KeyEvent::Enter),
378 VK_F1 | VK_F2 | VK_F3 | VK_F4 | VK_F5 | VK_F6 | VK_F7 | VK_F8 | VK_F9 | VK_F10 | VK_F11
379 | VK_F12 => Some(KeyEvent::F((key_event.virtual_key_code - 111) as u8)),
380 VK_LEFT | VK_UP | VK_RIGHT | VK_DOWN => {
381 // Modifier Keys (Ctrl, Shift) Support
382 let key_state = &key_event.control_key_state;
383 let ctrl_pressed = key_state.has_state(RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED);
384 let shift_pressed = key_state.has_state(SHIFT_PRESSED);
385
386 let event = match key_code {
387 VK_LEFT => {
388 if ctrl_pressed {
389 Some(KeyEvent::CtrlLeft)
390 } else if shift_pressed {
391 Some(KeyEvent::ShiftLeft)
392 } else {
393 Some(KeyEvent::Left)
394 }
395 }
396 VK_UP => {
397 if ctrl_pressed {
398 Some(KeyEvent::CtrlUp)
399 } else if shift_pressed {
400 Some(KeyEvent::ShiftUp)
401 } else {
402 Some(KeyEvent::Up)
403 }
404 }
405 VK_RIGHT => {
406 if ctrl_pressed {
407 Some(KeyEvent::CtrlRight)
408 } else if shift_pressed {
409 Some(KeyEvent::ShiftRight)
410 } else {
411 Some(KeyEvent::Right)
412 }
413 }
414 VK_DOWN => {
415 if ctrl_pressed {
416 Some(KeyEvent::CtrlDown)
417 } else if shift_pressed {
418 Some(KeyEvent::ShiftDown)
419 } else {
420 Some(KeyEvent::Down)
421 }
422 }
423 _ => None,
424 };
425
426 event
427 }
428 VK_PRIOR | VK_NEXT => {
429 if key_code == VK_PRIOR {
430 Some(KeyEvent::PageUp)
431 } else if key_code == VK_NEXT {
432 Some(KeyEvent::PageDown)
433 } else {
434 None
435 }
436 }
437 VK_END | VK_HOME => {
438 if key_code == VK_HOME {
439 Some(KeyEvent::Home)
440 } else if key_code == VK_END {
441 Some(KeyEvent::End)
442 } else {
443 None
444 }
445 }
446 VK_DELETE => Some(KeyEvent::Delete),
447 VK_INSERT => Some(KeyEvent::Insert),
448 _ => {
449 // Modifier Keys (Ctrl, Alt, Shift) Support
450 let character_raw = { (unsafe { *key_event.u_char.UnicodeChar() } as u16) };
451
452 if character_raw < 255 {
453 let character = character_raw as u8 as char;
454
455 let key_state = &key_event.control_key_state;
456
457 if key_state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) {
458 // If the ALT key is held down, pressing the A key produces ALT+A, which the system does not treat as a character at all, but rather as a system command.
459 // The pressed command is stored in `virtual_key_code`.
460 let command = key_event.virtual_key_code as u8 as char;
461
462 if (command).is_alphabetic() {
463 Some(KeyEvent::Alt(command))
464 } else {
465 None
466 }
467 } else if key_state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) {
468 match character_raw as u8 {
469 c @ b'\x01'..=b'\x1A' => {
470 Some(KeyEvent::Ctrl((c as u8 - 0x1 + b'a') as char))
471 }
472 c @ b'\x1C'..=b'\x1F' => {
473 Some(KeyEvent::Ctrl((c as u8 - 0x1C + b'4') as char))
474 }
475 _ => None,
476 }
477 } else if key_state.has_state(SHIFT_PRESSED) && character == '\t' {
478 Some(KeyEvent::BackTab)
479 } else {
480 if character == '\t' {
481 Some(KeyEvent::Tab)
482 } else {
483 // Shift + key press, essentially the same as single key press
484 // Separating to be explicit about the Shift press.
485 Some(KeyEvent::Char(character))
486 }
487 }
488 } else {
489 None
490 }
491 }
492 }
493 }
494
parse_mouse_event_record(event: &MouseEvent) -> Result<Option<crate::input::MouseEvent>>495 fn parse_mouse_event_record(event: &MouseEvent) -> Result<Option<crate::input::MouseEvent>> {
496 // NOTE (@imdaveho): xterm emulation takes the digits of the coords and passes them
497 // individually as bytes into a buffer; the below cxbs and cybs replicates that and
498 // mimicks the behavior; additionally, in xterm, mouse move is only handled when a
499 // mouse button is held down (ie. mouse drag)
500
501 let window_size = ScreenBuffer::current()?.info()?.terminal_window();
502
503 let xpos = event.mouse_position.x;
504 let mut ypos = event.mouse_position.y;
505
506 // The 'y' position of a mouse event is not relative to the window but absolute to screen buffer.
507 // This means that when the mouse cursor is at the top left it will be x: 0, y: 2295 (e.g. y = number of cells counting from the absolute buffer height) instead of relative x: 0, y: 0 to the window.
508
509 ypos = ypos - window_size.top;
510
511 Ok(match event.event_flags {
512 EventFlags::PressOrRelease => {
513 // Single click
514 match event.button_state {
515 ButtonState::Release => {
516 Some(crate::input::MouseEvent::Release(xpos as u16, ypos as u16))
517 }
518 ButtonState::FromLeft1stButtonPressed => {
519 // left click
520 Some(crate::input::MouseEvent::Press(
521 MouseButton::Left,
522 xpos as u16,
523 ypos as u16,
524 ))
525 }
526 ButtonState::RightmostButtonPressed => {
527 // right click
528 Some(crate::input::MouseEvent::Press(
529 MouseButton::Right,
530 xpos as u16,
531 ypos as u16,
532 ))
533 }
534 ButtonState::FromLeft2ndButtonPressed => {
535 // middle click
536 Some(crate::input::MouseEvent::Press(
537 MouseButton::Middle,
538 xpos as u16,
539 ypos as u16,
540 ))
541 }
542 _ => None,
543 }
544 }
545 EventFlags::MouseMoved => {
546 // Click + Move
547 // NOTE (@imdaveho) only register when mouse is not released
548 if event.button_state != ButtonState::Release {
549 Some(crate::input::MouseEvent::Hold(xpos as u16, ypos as u16))
550 } else {
551 None
552 }
553 }
554 EventFlags::MouseWheeled => {
555 // Vertical scroll
556 // NOTE (@imdaveho) from https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
557 // if `button_state` is negative then the wheel was rotated backward, toward the user.
558 if event.button_state != ButtonState::Negative {
559 Some(crate::input::MouseEvent::Press(
560 MouseButton::WheelUp,
561 xpos as u16,
562 ypos as u16,
563 ))
564 } else {
565 Some(crate::input::MouseEvent::Press(
566 MouseButton::WheelDown,
567 xpos as u16,
568 ypos as u16,
569 ))
570 }
571 }
572 EventFlags::DoubleClick => None, // NOTE (@imdaveho): double click not supported by unix terminals
573 EventFlags::MouseHwheeled => None, // NOTE (@imdaveho): horizontal scroll not supported by unix terminals
574 // TODO: Handle Ctrl + Mouse, Alt + Mouse, etc.
575 })
576 }
577