mirror of
https://github.com/herdrdev/herdr.git
synced 2026-11-06 00:06:18 +00:00
fix: preserve windows accent-key shortcuts (#5021)
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user