fix: preserve windows accent-key shortcuts (#5021)

This commit is contained in:
JJ Liebig
2026-10-07 16:48:40 +02:00
committed by GitHub
parent a124eed73c
commit 4e624cd50e
3 changed files with 51 additions and 11 deletions
+26 -3
View File
@@ -791,11 +791,26 @@ pub(crate) struct TestWindowsInput(WindowsInputTranslator);
#[cfg(all(test, windows))]
impl TestWindowsInput {
pub(crate) fn key(
pub(crate) fn key_in_layout(
&mut self,
record: crate::input::WindowsKeyRecord,
layout: windows_sys::Win32::UI::Input::KeyboardAndMouse::HKL,
) -> Vec<crate::protocol::ClientInputEvent> {
self.0.translate(WindowsInputRecord::Key(record))
let oem_char = (is_oem_virtual_key(record.virtual_key_code) && record.unicode == 0)
.then(|| {
crate::platform::resolve_base_printable_key_in_layout(
record.virtual_key_code,
record.virtual_scan_code,
layout,
)
})
.flatten();
self.0
.mapper
.translate_key_with_oem_char(record, oem_char)
.into_iter()
.flat_map(|item| self.0.pump.process(item))
.collect()
}
pub(crate) fn idle(&mut self) -> Vec<crate::protocol::ClientInputEvent> {
@@ -869,6 +884,14 @@ impl WindowsInputMapper {
}
fn translate_key(&mut self, key: WindowsKeyRecord) -> Vec<PlatformInputItem> {
self.translate_key_with_oem_char(key, resolve_ctrl_oem_char(key))
}
fn translate_key_with_oem_char(
&mut self,
key: WindowsKeyRecord,
oem_char: Option<char>,
) -> Vec<PlatformInputItem> {
if !self.key_record_can_emit_event(key) {
return Vec::new();
}
@@ -905,7 +928,7 @@ impl WindowsInputMapper {
};
}
let events = self.translate_semantic_key_events(key, resolve_ctrl_oem_char(key));
let events = self.translate_semantic_key_events(key, oem_char);
let items = if let Some(bytes) = self.paste_payload_bytes_for_key(key) {
vec![PlatformInputItem::PasteAwareKey {
win32_paste_bytes: bytes.clone(),
+9 -4
View File
@@ -1464,7 +1464,7 @@ mod windows_records {
}
/// The press and release records Windows produces for `vk` under `chord`,
/// or None for dead keys and multi-unit output.
/// or None for multi-unit output.
fn key_records(
layout: HKL,
vk: u16,
@@ -1515,6 +1515,8 @@ mod windows_records {
let unicode = match written {
0 => 0,
1 => buf[0],
// A dead key has not committed a character yet.
n if n < 0 => 0,
_ => return None,
};
let press = crate::input::WindowsKeyRecord {
@@ -1551,8 +1553,11 @@ mod windows_records {
herdr: &mut HerdrPath,
input: &mut crate::client::input::windows_vti::TestWindowsInput,
record: crate::input::WindowsKeyRecord,
layout: HKL,
) -> Option<Vec<u8>> {
let mut events = input.key(record);
// The generated record belongs to this layout, independently of the
// foreground window's layout while the test runs.
let mut events = input.key_in_layout(record, layout);
events.extend(input.idle());
let outcome = herdr.state.handle_client_events(&events);
herdr.deliver(vec![outcome])
@@ -1584,8 +1589,8 @@ mod windows_records {
continue;
};
let expected = [win32_input_mode(press), win32_input_mode(release)].concat();
let pressed = deliver_record(&mut herdr, &mut input, press);
let released = deliver_record(&mut herdr, &mut input, release);
let pressed = deliver_record(&mut herdr, &mut input, press, layout);
let released = deliver_record(&mut herdr, &mut input, release, layout);
let page_key_scrolls_herdr = chord.name == "plain"
&& matches!(key_name.as_str(), "pageup" | "pagedown")
&& herdr.runtime.plain_page_keys_use_host_scrollback() == Some(true);
+16 -4
View File
@@ -644,11 +644,21 @@ fn standard_windows_path(path: &std::path::Path) -> Option<PathBuf> {
/// Resolves against the current foreground layout because asynchronous console
/// records do not retain the layout that was active when the key was pressed.
pub(crate) fn resolve_base_printable_key(vk: u16, scan: u16) -> Option<char> {
// SAFETY: the foreground window and its layout are owned by Win32.
let layout = unsafe {
let thread_id = GetWindowThreadProcessId(GetForegroundWindow(), null_mut());
GetKeyboardLayout(thread_id)
};
resolve_base_printable_key_in_layout(vk, scan, layout)
}
pub(crate) fn resolve_base_printable_key_in_layout(
vk: u16,
scan: u16,
layout: windows_sys::Win32::UI::Input::KeyboardAndMouse::HKL,
) -> Option<char> {
// SAFETY: Win32 owns the handles; the fixed buffers match the API lengths.
unsafe {
let thread_id = GetWindowThreadProcessId(GetForegroundWindow(), null_mut());
let layout = GetKeyboardLayout(thread_id);
let key_state = [0u8; 256];
let mut output = [0u16; 2];
let written = ToUnicodeEx(
@@ -660,7 +670,9 @@ pub(crate) fn resolve_base_printable_key(vk: u16, scan: u16) -> Option<char> {
0x4,
layout,
);
let units = output.get(..usize::try_from(written).ok()?)?;
// A negative result identifies a dead key; its spacing accent is still
// the key's identity. Flag 0x4 above keeps composition state unchanged.
let units = output.get(..usize::try_from(written.unsigned_abs()).ok()?)?;
let mut chars = char::decode_utf16(units.iter().copied());
let ch = chars.next()?.ok()?;
(chars.next().is_none() && !ch.is_control()).then_some(ch)