Skip to main content

running_process_core/pty/
terminal_input.rs

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