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