aboutsummaryrefslogtreecommitdiff
path: root/src/platform
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-08-01 04:27:50 +0300
committerGitHub <noreply@github.com>2026-08-01 04:27:50 +0300
commitb76adc155d979705a353fd6dc87c702f9dfc6c22 (patch)
treef613254e0cfbfd2b4450a6a5f3ee7c0ba8136976 /src/platform
parent8d190eddda2c74afa3dc043f8ec8020ffd7b5a94 (diff)
fix(input): preserve native key lifecycle across routing (#2142)
refs #2077 Co-authored-by: Jonathan Liebig <jonathan.liebig@gmail.com>
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/windows.rs77
1 files changed, 71 insertions, 6 deletions
diff --git a/src/platform/windows.rs b/src/platform/windows.rs
index 35ecfd82..6cc642eb 100644
--- a/src/platform/windows.rs
+++ b/src/platform/windows.rs
@@ -64,15 +64,33 @@ use super::{ClipboardImage, ForegroundJob, Signal};
const STILL_ACTIVE: u32 = 259;
const FOREGROUND_PROCESS_SNAPSHOT_CACHE_TTL: Duration = Duration::from_millis(250);
-pub(crate) fn encode_windows_conpty_shift_enter(key: crate::input::TerminalKey) -> Option<Vec<u8>> {
+/// Encode native or targeted semantic Win32 input for a compatible ConPTY destination.
+pub(crate) fn encode_windows_conpty_fallback(key: &crate::input::TerminalKey) -> Option<Vec<u8>> {
use crossterm::event::{KeyCode, KeyEventKind, KeyModifiers};
- if key.code != KeyCode::Enter || key.modifiers != KeyModifiers::SHIFT {
- return None;
- }
+ let (virtual_key_code, virtual_scan_code, unicode, control_key_state) =
+ if let Some(record) = key.windows_record() {
+ (
+ record.virtual_key_code,
+ record.virtual_scan_code,
+ record.unicode,
+ record.control_key_state,
+ )
+ } else if key.code == KeyCode::Enter && key.modifiers == KeyModifiers::SHIFT {
+ (13, 28, 13, 16)
+ } else {
+ return None;
+ };
+ let key_down = key.kind != KeyEventKind::Release;
+ let repeat_count = if key_down { key.repeat_count.max(1) } else { 1 };
- let key_down = !matches!(key.kind, KeyEventKind::Release);
- Some(format!("\x1b[13;28;13;{};16;1_", u8::from(key_down)).into_bytes())
+ Some(
+ format!(
+ "\x1b[{virtual_key_code};{virtual_scan_code};{unicode};{};{control_key_state};{repeat_count}_",
+ u8::from(key_down),
+ )
+ .into_bytes(),
+ )
}
#[derive(Debug)]
@@ -1390,6 +1408,53 @@ mod tests {
};
#[test]
+ fn windows_conpty_native_encoder_uses_canonical_phase_and_repeat_count() {
+ let key = crate::input::TerminalKey::new(
+ crossterm::event::KeyCode::Esc,
+ crossterm::event::KeyModifiers::empty(),
+ )
+ .with_windows_record(crate::input::WindowsKeyRecord {
+ key_down: true,
+ repeat_count: 3,
+ virtual_key_code: 27,
+ virtual_scan_code: 1,
+ unicode: 27,
+ control_key_state: 0,
+ });
+
+ assert_eq!(
+ super::encode_windows_conpty_fallback(&key),
+ Some(b"\x1b[27;1;27;1;0;3_".to_vec())
+ );
+ let mut release = key.with_kind(crossterm::event::KeyEventKind::Release);
+ release.repeat_count = 3;
+ assert_eq!(
+ super::encode_windows_conpty_fallback(&release),
+ Some(b"\x1b[27;1;27;0;0;1_".to_vec())
+ );
+ }
+
+ #[test]
+ fn windows_conpty_native_encoder_preserves_semantic_shift_enter_fallback() {
+ let shift_enter = crate::input::TerminalKey::new(
+ crossterm::event::KeyCode::Enter,
+ crossterm::event::KeyModifiers::SHIFT,
+ );
+
+ assert_eq!(
+ super::encode_windows_conpty_fallback(&shift_enter),
+ Some(b"\x1b[13;28;13;1;16;1_".to_vec())
+ );
+ assert_eq!(
+ super::encode_windows_conpty_fallback(&crate::input::TerminalKey::new(
+ crossterm::event::KeyCode::Enter,
+ crossterm::event::KeyModifiers::empty(),
+ )),
+ None
+ );
+ }
+
+ #[test]
fn windows_notification_text_is_null_terminated_and_unicode_safe() {
let mut destination = [u16::MAX; 6];
super::copy_wide_truncated(&mut destination, "abc😀def");