1use std::collections::VecDeque;
2#[cfg(windows)]
3use std::fs::OpenOptions;
4#[cfg(windows)]
5use std::io::Write;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::{Arc, Condvar, Mutex};
8use std::thread;
9use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
10
11use thiserror::Error;
12
13pub const NATIVE_TERMINAL_INPUT_TRACE_PATH_ENV: &str =
15 "RUNNING_PROCESS_NATIVE_TERMINAL_INPUT_TRACE_PATH";
16
17#[derive(Debug, Error)]
21pub enum TerminalInputError {
22 #[error("terminal input is closed")]
24 Closed,
25 #[error("no terminal input available before timeout")]
27 Timeout,
28 #[error("terminal input I/O error: {0}")]
30 Io(#[from] std::io::Error),
31 #[error("terminal input error: {0}")]
33 Other(String),
34}
35
36#[derive(Clone)]
40pub struct TerminalInputEventRecord {
41 pub data: Vec<u8>,
43 pub submit: bool,
45 pub shift: bool,
47 pub ctrl: bool,
49 pub alt: bool,
51 pub virtual_key_code: u16,
53 pub repeat_count: u16,
55}
56
57pub struct TerminalInputState {
59 pub events: VecDeque<TerminalInputEventRecord>,
61 pub closed: bool,
63}
64
65#[cfg(windows)]
66pub struct ActiveTerminalInputCapture {
68 pub input_handle: usize,
70 pub original_mode: u32,
72 pub active_mode: u32,
74}
75
76#[cfg(windows)]
77#[derive(Debug, PartialEq)]
79pub enum TerminalInputWaitOutcome {
80 Event(TerminalInputEventRecord),
82 Closed,
84 Timeout,
86}
87
88#[cfg(windows)]
89impl std::fmt::Debug for TerminalInputEventRecord {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 f.debug_struct("TerminalInputEventRecord")
92 .field("data", &self.data)
93 .field("submit", &self.submit)
94 .field("shift", &self.shift)
95 .field("ctrl", &self.ctrl)
96 .field("alt", &self.alt)
97 .field("virtual_key_code", &self.virtual_key_code)
98 .field("repeat_count", &self.repeat_count)
99 .finish()
100 }
101}
102
103#[cfg(windows)]
104impl PartialEq for TerminalInputEventRecord {
105 fn eq(&self, other: &Self) -> bool {
106 self.data == other.data
107 && self.submit == other.submit
108 && self.shift == other.shift
109 && self.ctrl == other.ctrl
110 && self.alt == other.alt
111 && self.virtual_key_code == other.virtual_key_code
112 && self.repeat_count == other.repeat_count
113 }
114}
115
116pub fn unix_now_seconds() -> f64 {
120 SystemTime::now()
121 .duration_since(UNIX_EPOCH)
122 .map(|duration| duration.as_secs_f64())
123 .unwrap_or(0.0)
124}
125
126#[cfg(windows)]
127pub fn native_terminal_input_trace_target() -> Option<String> {
129 std::env::var(NATIVE_TERMINAL_INPUT_TRACE_PATH_ENV)
130 .ok()
131 .map(|value| value.trim().to_string())
132 .filter(|value| !value.is_empty())
133}
134
135#[cfg(windows)]
136pub fn append_native_terminal_input_trace_line(line: &str) {
138 let Some(target) = native_terminal_input_trace_target() else {
139 return;
140 };
141 if target == "-" {
142 eprintln!("{line}");
143 return;
144 }
145 let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&target) else {
146 return;
147 };
148 let _ = writeln!(file, "{line}");
149}
150
151#[cfg(windows)]
152pub fn format_terminal_input_bytes(data: &[u8]) -> String {
154 if data.is_empty() {
155 return "[]".to_string();
156 }
157 let parts: Vec<String> = data.iter().map(|byte| format!("{byte:02x}")).collect();
158 format!("[{}]", parts.join(" "))
159}
160
161#[cfg(windows)]
164pub fn native_terminal_input_mode(original_mode: u32) -> u32 {
166 use winapi::um::wincon::{
167 ENABLE_ECHO_INPUT, ENABLE_EXTENDED_FLAGS, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT,
168 ENABLE_QUICK_EDIT_MODE, ENABLE_WINDOW_INPUT,
169 };
170
171 (original_mode | ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT)
172 & !(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_QUICK_EDIT_MODE)
173}
174
175#[cfg(windows)]
176pub fn terminal_input_modifier_parameter(shift: bool, alt: bool, ctrl: bool) -> Option<u8> {
178 let value = 1 + u8::from(shift) + (u8::from(alt) * 2) + (u8::from(ctrl) * 4);
179 (value > 1).then_some(value)
180}
181
182#[cfg(windows)]
183pub fn repeat_terminal_input_bytes(chunk: &[u8], repeat_count: u16) -> Vec<u8> {
185 let repeat = usize::from(repeat_count.max(1));
186 let mut output = Vec::with_capacity(chunk.len() * repeat);
187 for _ in 0..repeat {
188 output.extend_from_slice(chunk);
189 }
190 output
191}
192
193#[cfg(windows)]
194pub fn repeated_modified_sequence(base: &[u8], modifier: Option<u8>, repeat_count: u16) -> Vec<u8> {
196 if let Some(value) = modifier {
197 let base_text = std::str::from_utf8(base).expect("VT sequence literal must be utf-8");
198 let body = base_text
199 .strip_prefix("\x1b[")
200 .expect("VT sequence literal must start with CSI");
201 let sequence = format!("\x1b[1;{value}{body}");
202 repeat_terminal_input_bytes(sequence.as_bytes(), repeat_count)
203 } else {
204 repeat_terminal_input_bytes(base, repeat_count)
205 }
206}
207
208#[cfg(windows)]
209pub fn repeated_tilde_sequence(number: u8, modifier: Option<u8>, repeat_count: u16) -> Vec<u8> {
211 if let Some(value) = modifier {
212 let sequence = format!("\x1b[{number};{value}~");
213 repeat_terminal_input_bytes(sequence.as_bytes(), repeat_count)
214 } else {
215 let sequence = format!("\x1b[{number}~");
216 repeat_terminal_input_bytes(sequence.as_bytes(), repeat_count)
217 }
218}
219
220#[cfg(windows)]
221pub fn control_character_for_unicode(unicode: u16) -> Option<u8> {
223 let upper = char::from_u32(u32::from(unicode))?.to_ascii_uppercase();
224 match upper {
225 '@' | ' ' => Some(0x00),
226 'A'..='Z' => Some((upper as u8) - b'@'),
227 '[' => Some(0x1B),
228 '\\' => Some(0x1C),
229 ']' => Some(0x1D),
230 '^' => Some(0x1E),
231 '_' => Some(0x1F),
232 _ => None,
233 }
234}
235
236#[cfg(windows)]
237pub fn trace_translated_console_key_event(
239 record: &winapi::um::wincontypes::KEY_EVENT_RECORD,
240 event: TerminalInputEventRecord,
241) -> TerminalInputEventRecord {
242 append_native_terminal_input_trace_line(&format!(
243 "[{:.6}] native_terminal_input raw bKeyDown={} vk={:#06x} scan={:#06x} unicode={:#06x} control={:#010x} repeat={} translated bytes={} submit={} shift={} ctrl={} alt={}",
244 unix_now_seconds(),
245 record.bKeyDown,
246 record.wVirtualKeyCode,
247 record.wVirtualScanCode,
248 unsafe { *record.uChar.UnicodeChar() },
249 record.dwControlKeyState,
250 record.wRepeatCount.max(1),
251 format_terminal_input_bytes(&event.data),
252 event.submit,
253 event.shift,
254 event.ctrl,
255 event.alt,
256 ));
257 event
258}
259
260#[cfg(windows)]
261pub fn translate_console_key_event(
263 record: &winapi::um::wincontypes::KEY_EVENT_RECORD,
264) -> Option<TerminalInputEventRecord> {
265 use winapi::um::wincontypes::{
266 LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED, SHIFT_PRESSED,
267 };
268 use winapi::um::winuser::{
269 VK_BACK, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_HOME, VK_INSERT, VK_LEFT, VK_NEXT,
270 VK_PRIOR, VK_RETURN, VK_RIGHT, VK_TAB, VK_UP,
271 };
272
273 if record.bKeyDown == 0 {
274 append_native_terminal_input_trace_line(&format!(
275 "[{:.6}] native_terminal_input raw bKeyDown=0 vk={:#06x} scan={:#06x} unicode={:#06x} control={:#010x} repeat={} translated=ignored",
276 unix_now_seconds(),
277 record.wVirtualKeyCode,
278 record.wVirtualScanCode,
279 unsafe { *record.uChar.UnicodeChar() },
280 record.dwControlKeyState,
281 record.wRepeatCount,
282 ));
283 return None;
284 }
285
286 let repeat_count = record.wRepeatCount.max(1);
287 let modifiers = record.dwControlKeyState;
288 let shift = modifiers & SHIFT_PRESSED != 0;
289 let alt = modifiers & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0;
290 let ctrl = modifiers & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0;
291 let virtual_key_code = record.wVirtualKeyCode;
292 let unicode = unsafe { *record.uChar.UnicodeChar() };
293
294 if shift && !ctrl && !alt && virtual_key_code as i32 == VK_RETURN {
298 return Some(trace_translated_console_key_event(
299 record,
300 TerminalInputEventRecord {
301 data: repeat_terminal_input_bytes(b"\x1b[13;2u", repeat_count),
302 submit: false,
303 shift,
304 ctrl,
305 alt,
306 virtual_key_code,
307 repeat_count,
308 },
309 ));
310 }
311
312 let mut data = if ctrl {
313 control_character_for_unicode(unicode)
314 .map(|byte| repeat_terminal_input_bytes(&[byte], repeat_count))
315 .unwrap_or_default()
316 } else {
317 Vec::new()
318 };
319
320 if data.is_empty() && unicode != 0 {
321 if let Some(character) = char::from_u32(u32::from(unicode)) {
322 let text: String = std::iter::repeat_n(character, usize::from(repeat_count)).collect();
323 data = text.into_bytes();
324 }
325 }
326
327 if data.is_empty() {
328 let modifier = terminal_input_modifier_parameter(shift, alt, ctrl);
329 let sequence = match virtual_key_code as i32 {
330 VK_BACK => Some(b"\x08".as_slice()),
331 VK_TAB if shift => Some(b"\x1b[Z".as_slice()),
332 VK_TAB => Some(b"\t".as_slice()),
333 VK_RETURN => Some(b"\r".as_slice()),
334 VK_ESCAPE => Some(b"\x1b".as_slice()),
335 VK_UP => {
336 return Some(trace_translated_console_key_event(
337 record,
338 TerminalInputEventRecord {
339 data: repeated_modified_sequence(b"\x1b[A", modifier, repeat_count),
340 submit: false,
341 shift,
342 ctrl,
343 alt,
344 virtual_key_code,
345 repeat_count,
346 },
347 ));
348 }
349 VK_DOWN => {
350 return Some(trace_translated_console_key_event(
351 record,
352 TerminalInputEventRecord {
353 data: repeated_modified_sequence(b"\x1b[B", modifier, repeat_count),
354 submit: false,
355 shift,
356 ctrl,
357 alt,
358 virtual_key_code,
359 repeat_count,
360 },
361 ));
362 }
363 VK_RIGHT => {
364 return Some(trace_translated_console_key_event(
365 record,
366 TerminalInputEventRecord {
367 data: repeated_modified_sequence(b"\x1b[C", modifier, repeat_count),
368 submit: false,
369 shift,
370 ctrl,
371 alt,
372 virtual_key_code,
373 repeat_count,
374 },
375 ));
376 }
377 VK_LEFT => {
378 return Some(trace_translated_console_key_event(
379 record,
380 TerminalInputEventRecord {
381 data: repeated_modified_sequence(b"\x1b[D", modifier, repeat_count),
382 submit: false,
383 shift,
384 ctrl,
385 alt,
386 virtual_key_code,
387 repeat_count,
388 },
389 ));
390 }
391 VK_HOME => {
392 return Some(trace_translated_console_key_event(
393 record,
394 TerminalInputEventRecord {
395 data: repeated_modified_sequence(b"\x1b[H", modifier, repeat_count),
396 submit: false,
397 shift,
398 ctrl,
399 alt,
400 virtual_key_code,
401 repeat_count,
402 },
403 ));
404 }
405 VK_END => {
406 return Some(trace_translated_console_key_event(
407 record,
408 TerminalInputEventRecord {
409 data: repeated_modified_sequence(b"\x1b[F", modifier, repeat_count),
410 submit: false,
411 shift,
412 ctrl,
413 alt,
414 virtual_key_code,
415 repeat_count,
416 },
417 ));
418 }
419 VK_INSERT => {
420 return Some(trace_translated_console_key_event(
421 record,
422 TerminalInputEventRecord {
423 data: repeated_tilde_sequence(2, modifier, repeat_count),
424 submit: false,
425 shift,
426 ctrl,
427 alt,
428 virtual_key_code,
429 repeat_count,
430 },
431 ));
432 }
433 VK_DELETE => {
434 return Some(trace_translated_console_key_event(
435 record,
436 TerminalInputEventRecord {
437 data: repeated_tilde_sequence(3, modifier, repeat_count),
438 submit: false,
439 shift,
440 ctrl,
441 alt,
442 virtual_key_code,
443 repeat_count,
444 },
445 ));
446 }
447 VK_PRIOR => {
448 return Some(trace_translated_console_key_event(
449 record,
450 TerminalInputEventRecord {
451 data: repeated_tilde_sequence(5, modifier, repeat_count),
452 submit: false,
453 shift,
454 ctrl,
455 alt,
456 virtual_key_code,
457 repeat_count,
458 },
459 ));
460 }
461 VK_NEXT => {
462 return Some(trace_translated_console_key_event(
463 record,
464 TerminalInputEventRecord {
465 data: repeated_tilde_sequence(6, modifier, repeat_count),
466 submit: false,
467 shift,
468 ctrl,
469 alt,
470 virtual_key_code,
471 repeat_count,
472 },
473 ));
474 }
475 _ => None,
476 };
477 data = sequence.map(|chunk| repeat_terminal_input_bytes(chunk, repeat_count))?;
478 }
479
480 if alt && !data.starts_with(b"\x1b[") && !data.starts_with(b"\x1bO") {
481 let mut prefixed = Vec::with_capacity(data.len() + 1);
482 prefixed.push(0x1B);
483 prefixed.extend_from_slice(&data);
484 data = prefixed;
485 }
486
487 let event = TerminalInputEventRecord {
488 data,
489 submit: virtual_key_code as i32 == VK_RETURN && !shift,
490 shift,
491 ctrl,
492 alt,
493 virtual_key_code,
494 repeat_count,
495 };
496 Some(trace_translated_console_key_event(record, event))
497}
498
499#[cfg(windows)]
502pub fn native_terminal_input_worker(
504 input_handle: usize,
505 state: Arc<Mutex<TerminalInputState>>,
506 condvar: Arc<Condvar>,
507 stop: Arc<AtomicBool>,
508 capturing: Arc<AtomicBool>,
509) {
510 use winapi::shared::minwindef::DWORD;
511 use winapi::shared::winerror::WAIT_TIMEOUT;
512 use winapi::um::consoleapi::ReadConsoleInputW;
513 use winapi::um::synchapi::WaitForSingleObject;
514 use winapi::um::winbase::WAIT_OBJECT_0;
515 use winapi::um::wincontypes::{INPUT_RECORD, KEY_EVENT};
516 use winapi::um::winnt::HANDLE;
517
518 let handle = input_handle as HANDLE;
519 let mut records: [INPUT_RECORD; 512] = unsafe { std::mem::zeroed() };
520 append_native_terminal_input_trace_line(&format!(
521 "[{:.6}] native_terminal_input worker_start handle={input_handle}",
522 unix_now_seconds(),
523 ));
524
525 while !stop.load(Ordering::Acquire) {
526 let wait_result = unsafe { WaitForSingleObject(handle, 50) };
527 match wait_result {
528 WAIT_OBJECT_0 => {
529 let mut read_count: DWORD = 0;
530 let ok = unsafe {
531 ReadConsoleInputW(
532 handle,
533 records.as_mut_ptr(),
534 records.len() as DWORD,
535 &mut read_count,
536 )
537 };
538 if ok == 0 {
539 append_native_terminal_input_trace_line(&format!(
540 "[{:.6}] native_terminal_input read_console_input_failed handle={input_handle}",
541 unix_now_seconds(),
542 ));
543 break;
544 }
545 let mut batch = Vec::new();
546 for record in records.iter().take(read_count as usize) {
547 if record.EventType != KEY_EVENT {
548 continue;
549 }
550 let key_event = unsafe { record.Event.KeyEvent() };
551 if let Some(event) = translate_console_key_event(key_event) {
552 batch.push(event);
553 }
554 }
555 if !batch.is_empty() {
556 let mut guard = state.lock().expect("terminal input mutex poisoned");
557 guard.events.extend(batch);
558 drop(guard);
559 condvar.notify_all();
560 }
561 }
562 WAIT_TIMEOUT => continue,
563 _ => {
564 append_native_terminal_input_trace_line(&format!(
565 "[{:.6}] native_terminal_input wait_result={wait_result} handle={input_handle}",
566 unix_now_seconds(),
567 ));
568 break;
569 }
570 }
571 }
572
573 capturing.store(false, Ordering::Release);
574 let mut guard = state.lock().expect("terminal input mutex poisoned");
575 guard.closed = true;
576 condvar.notify_all();
577 drop(guard);
578 append_native_terminal_input_trace_line(&format!(
579 "[{:.6}] native_terminal_input worker_stop handle={input_handle}",
580 unix_now_seconds(),
581 ));
582}
583
584#[cfg(windows)]
587pub fn wait_for_terminal_input_event(
589 state: &Arc<Mutex<TerminalInputState>>,
590 condvar: &Arc<Condvar>,
591 timeout: Option<Duration>,
592) -> TerminalInputWaitOutcome {
593 let deadline = timeout.map(|limit| Instant::now() + limit);
594 let mut guard = state.lock().expect("terminal input mutex poisoned");
595 loop {
596 if let Some(event) = guard.events.pop_front() {
597 return TerminalInputWaitOutcome::Event(event);
598 }
599 if guard.closed {
600 return TerminalInputWaitOutcome::Closed;
601 }
602 match deadline {
603 Some(deadline) => {
604 let now = Instant::now();
605 if now >= deadline {
606 return TerminalInputWaitOutcome::Timeout;
607 }
608 let wait = deadline.saturating_duration_since(now);
609 let result = condvar
610 .wait_timeout(guard, wait)
611 .expect("terminal input mutex poisoned");
612 guard = result.0;
613 }
614 None => {
615 guard = condvar.wait(guard).expect("terminal input mutex poisoned");
616 }
617 }
618 }
619}
620
621pub struct TerminalInputCore {
625 pub state: Arc<Mutex<TerminalInputState>>,
627 pub condvar: Arc<Condvar>,
629 pub stop: Arc<AtomicBool>,
631 pub capturing: Arc<AtomicBool>,
633 pub worker: Mutex<Option<thread::JoinHandle<()>>>,
635 #[cfg(windows)]
636 pub console: Mutex<Option<ActiveTerminalInputCapture>>,
638}
639
640impl Default for TerminalInputCore {
641 fn default() -> Self {
642 Self::new()
643 }
644}
645
646impl TerminalInputCore {
647 pub fn new() -> Self {
649 Self {
650 state: Arc::new(Mutex::new(TerminalInputState {
651 events: VecDeque::new(),
652 closed: true,
653 })),
654 condvar: Arc::new(Condvar::new()),
655 stop: Arc::new(AtomicBool::new(false)),
656 capturing: Arc::new(AtomicBool::new(false)),
657 worker: Mutex::new(None),
658 #[cfg(windows)]
659 console: Mutex::new(None),
660 }
661 }
662
663 pub fn next_event(&self) -> Option<TerminalInputEventRecord> {
665 self.state
666 .lock()
667 .expect("terminal input mutex poisoned")
668 .events
669 .pop_front()
670 }
671
672 pub fn available(&self) -> bool {
674 !self
675 .state
676 .lock()
677 .expect("terminal input mutex poisoned")
678 .events
679 .is_empty()
680 }
681
682 pub fn capturing(&self) -> bool {
684 self.capturing.load(Ordering::Acquire)
685 }
686
687 pub fn original_console_mode(&self) -> Option<u32> {
689 #[cfg(windows)]
690 {
691 return self
692 .console
693 .lock()
694 .expect("terminal input console mutex poisoned")
695 .as_ref()
696 .map(|capture| capture.original_mode);
697 }
698
699 #[cfg(not(windows))]
700 {
701 None
702 }
703 }
704
705 pub fn active_console_mode(&self) -> Option<u32> {
707 #[cfg(windows)]
708 {
709 return self
710 .console
711 .lock()
712 .expect("terminal input console mutex poisoned")
713 .as_ref()
714 .map(|capture| capture.active_mode);
715 }
716
717 #[cfg(not(windows))]
718 {
719 None
720 }
721 }
722
723 pub fn wait_for_event(
725 &self,
726 timeout: Option<f64>,
727 ) -> Result<TerminalInputEventRecord, TerminalInputError> {
728 let state = Arc::clone(&self.state);
729 let condvar = Arc::clone(&self.condvar);
730 let deadline = timeout.map(|secs| Instant::now() + Duration::from_secs_f64(secs));
731 let mut guard = state.lock().expect("terminal input mutex poisoned");
732 loop {
733 if let Some(event) = guard.events.pop_front() {
734 return Ok(event);
735 }
736 if guard.closed {
737 return Err(TerminalInputError::Closed);
738 }
739 match deadline {
740 Some(deadline) => {
741 let now = Instant::now();
742 if now >= deadline {
743 return Err(TerminalInputError::Timeout);
744 }
745 let wait = deadline.saturating_duration_since(now);
746 let result = condvar
747 .wait_timeout(guard, wait)
748 .expect("terminal input mutex poisoned");
749 guard = result.0;
750 }
751 None => {
752 guard = condvar.wait(guard).expect("terminal input mutex poisoned");
753 }
754 }
755 }
756 }
757
758 pub fn drain_events(&self) -> Vec<TerminalInputEventRecord> {
760 let mut guard = self.state.lock().expect("terminal input mutex poisoned");
761 guard.events.drain(..).collect()
762 }
763
764 pub fn stop_impl(&self) -> Result<(), std::io::Error> {
766 self.stop.store(true, Ordering::Release);
767 #[cfg(windows)]
768 append_native_terminal_input_trace_line(&format!(
769 "[{:.6}] native_terminal_input stop_requested",
770 unix_now_seconds(),
771 ));
772 if let Some(worker) = self
773 .worker
774 .lock()
775 .expect("terminal input worker mutex poisoned")
776 .take()
777 {
778 let _ = worker.join();
779 }
780 self.capturing.store(false, Ordering::Release);
781
782 #[cfg(windows)]
783 let restore_result = {
784 use winapi::um::consoleapi::SetConsoleMode;
785 use winapi::um::winnt::HANDLE;
786
787 let console = self
788 .console
789 .lock()
790 .expect("terminal input console mutex poisoned")
791 .take();
792 console.map(|capture| unsafe {
793 SetConsoleMode(capture.input_handle as HANDLE, capture.original_mode)
794 })
795 };
796
797 let mut guard = self.state.lock().expect("terminal input mutex poisoned");
798 guard.closed = true;
799 self.condvar.notify_all();
800 drop(guard);
801
802 #[cfg(windows)]
803 if let Some(result) = restore_result {
804 if result == 0 {
805 return Err(std::io::Error::last_os_error());
806 }
807 }
808 Ok(())
809 }
810
811 #[cfg(windows)]
812 pub fn start_impl(&self) -> Result<(), std::io::Error> {
814 use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
815 use winapi::um::handleapi::INVALID_HANDLE_VALUE;
816 use winapi::um::processenv::GetStdHandle;
817 use winapi::um::winbase::STD_INPUT_HANDLE;
818
819 let mut worker_guard = self
820 .worker
821 .lock()
822 .expect("terminal input worker mutex poisoned");
823 if worker_guard.is_some() {
824 return Ok(());
825 }
826
827 let input_handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
828 if input_handle.is_null() || input_handle == INVALID_HANDLE_VALUE {
829 return Err(std::io::Error::last_os_error());
830 }
831
832 let mut original_mode = 0u32;
833 let got_mode = unsafe { GetConsoleMode(input_handle, &mut original_mode) };
834 if got_mode == 0 {
835 return Err(std::io::Error::new(
836 std::io::ErrorKind::Unsupported,
837 "TerminalInputCore requires an attached Windows console stdin",
838 ));
839 }
840
841 let active_mode = native_terminal_input_mode(original_mode);
842 let set_mode = unsafe { SetConsoleMode(input_handle, active_mode) };
843 if set_mode == 0 {
844 return Err(std::io::Error::last_os_error());
845 }
846 append_native_terminal_input_trace_line(&format!(
847 "[{:.6}] native_terminal_input start handle={} original_mode={:#010x} active_mode={:#010x}",
848 unix_now_seconds(),
849 input_handle as usize,
850 original_mode,
851 active_mode,
852 ));
853
854 self.stop.store(false, Ordering::Release);
855 self.capturing.store(true, Ordering::Release);
856 {
857 let mut state = self.state.lock().expect("terminal input mutex poisoned");
858 state.events.clear();
859 state.closed = false;
860 }
861 *self
862 .console
863 .lock()
864 .expect("terminal input console mutex poisoned") = Some(ActiveTerminalInputCapture {
865 input_handle: input_handle as usize,
866 original_mode,
867 active_mode,
868 });
869
870 let state = Arc::clone(&self.state);
871 let condvar = Arc::clone(&self.condvar);
872 let stop = Arc::clone(&self.stop);
873 let capturing = Arc::clone(&self.capturing);
874 let input_handle_raw = input_handle as usize;
875 *worker_guard = Some(thread::spawn(move || {
876 native_terminal_input_worker(input_handle_raw, state, condvar, stop, capturing);
877 }));
878 Ok(())
879 }
880}
881
882impl Drop for TerminalInputCore {
883 fn drop(&mut self) {
884 let _ = self.stop_impl();
885 }
886}