Skip to main content

running_process_core/pty/
terminal_input.rs

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
12/// Environment variable name for the trace file path.
13pub const NATIVE_TERMINAL_INPUT_TRACE_PATH_ENV: &str =
14    "RUNNING_PROCESS_NATIVE_TERMINAL_INPUT_TRACE_PATH";
15
16// ── Error type ──
17
18#[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// ── Pure-Rust data types ──
31
32#[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
91// ── Utility functions ──
92
93pub 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// ── Console mode / key translation helpers ──
133
134#[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    // Shift+Enter: send CSI u escape sequence so downstream TUI apps
258    // (e.g. Claude Code) can distinguish Shift+Enter (newline) from
259    // plain Enter (submit).  Format: ESC [ 13 ; 2 u
260    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// ── Worker thread ──
463
464#[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// ── Wait helper ──
547
548#[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
582// ── TerminalInputCore ──
583
584pub 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}