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