windows: Fix keystroke (#30753)
Closes #22656 Part of #29144, this PR completely rewrites the key handling logic on Windows, making it much more consistent with how things work on macOS. However, one remaining issue is that on Windows, we should be using `Ctrl+Shift+4` instead of `Ctrl+$`. That part is expected to be addressed in #29144. Release Notes: - N/A
This commit is contained in:
parent
f021b401f4
commit
58ba833792
4 changed files with 287 additions and 280 deletions
|
@ -56,6 +56,7 @@ impl Keystroke {
|
||||||
/// This method assumes that `self` was typed and `target' is in the keymap, and checks
|
/// This method assumes that `self` was typed and `target' is in the keymap, and checks
|
||||||
/// both possibilities for self against the target.
|
/// both possibilities for self against the target.
|
||||||
pub(crate) fn should_match(&self, target: &Keystroke) -> bool {
|
pub(crate) fn should_match(&self, target: &Keystroke) -> bool {
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
if let Some(key_char) = self
|
if let Some(key_char) = self
|
||||||
.key_char
|
.key_char
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -72,6 +73,18 @@ impl Keystroke {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
if let Some(key_char) = self
|
||||||
|
.key_char
|
||||||
|
.as_ref()
|
||||||
|
.filter(|key_char| key_char != &&self.key)
|
||||||
|
{
|
||||||
|
// On Windows, if key_char is set, then the typed keystroke produced the key_char
|
||||||
|
if &target.key == key_char && target.modifiers == Modifiers::none() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
target.modifiers == self.modifiers && target.key == self.key
|
target.modifiers == self.modifiers && target.key == self.key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,11 +84,11 @@ pub(crate) fn handle_msg(
|
||||||
WM_MOUSEWHEEL => handle_mouse_wheel_msg(handle, wparam, lparam, state_ptr),
|
WM_MOUSEWHEEL => handle_mouse_wheel_msg(handle, wparam, lparam, state_ptr),
|
||||||
WM_MOUSEHWHEEL => handle_mouse_horizontal_wheel_msg(handle, wparam, lparam, state_ptr),
|
WM_MOUSEHWHEEL => handle_mouse_horizontal_wheel_msg(handle, wparam, lparam, state_ptr),
|
||||||
WM_SYSKEYDOWN => handle_syskeydown_msg(wparam, lparam, state_ptr),
|
WM_SYSKEYDOWN => handle_syskeydown_msg(wparam, lparam, state_ptr),
|
||||||
WM_SYSKEYUP => handle_syskeyup_msg(wparam, state_ptr),
|
WM_SYSKEYUP => handle_syskeyup_msg(wparam, lparam, state_ptr),
|
||||||
WM_SYSCOMMAND => handle_system_command(wparam, state_ptr),
|
WM_SYSCOMMAND => handle_system_command(wparam, state_ptr),
|
||||||
WM_KEYDOWN => handle_keydown_msg(wparam, lparam, state_ptr),
|
WM_KEYDOWN => handle_keydown_msg(wparam, lparam, state_ptr),
|
||||||
WM_KEYUP => handle_keyup_msg(wparam, state_ptr),
|
WM_KEYUP => handle_keyup_msg(wparam, lparam, state_ptr),
|
||||||
WM_CHAR => handle_char_msg(wparam, lparam, state_ptr),
|
WM_CHAR => handle_char_msg(wparam, state_ptr),
|
||||||
WM_DEADCHAR => handle_dead_char_msg(wparam, state_ptr),
|
WM_DEADCHAR => handle_dead_char_msg(wparam, state_ptr),
|
||||||
WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr),
|
WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr),
|
||||||
WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr),
|
WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr),
|
||||||
|
@ -344,170 +344,120 @@ fn handle_syskeydown_msg(
|
||||||
state_ptr: Rc<WindowsWindowStatePtr>,
|
state_ptr: Rc<WindowsWindowStatePtr>,
|
||||||
) -> Option<isize> {
|
) -> Option<isize> {
|
||||||
let mut lock = state_ptr.state.borrow_mut();
|
let mut lock = state_ptr.state.borrow_mut();
|
||||||
let vkey = wparam.loword();
|
let input = handle_key_event(wparam, lparam, &mut lock, |keystroke| {
|
||||||
let input = if is_modifier(VIRTUAL_KEY(vkey)) {
|
|
||||||
let modifiers = current_modifiers();
|
|
||||||
if let Some(prev_modifiers) = lock.last_reported_modifiers {
|
|
||||||
if prev_modifiers == modifiers {
|
|
||||||
return Some(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lock.last_reported_modifiers = Some(modifiers);
|
|
||||||
PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers })
|
|
||||||
} else {
|
|
||||||
let keystroke = parse_syskeydown_msg_keystroke(wparam)?;
|
|
||||||
PlatformInput::KeyDown(KeyDownEvent {
|
PlatformInput::KeyDown(KeyDownEvent {
|
||||||
keystroke,
|
keystroke,
|
||||||
is_held: lparam.0 & (0x1 << 30) > 0,
|
is_held: lparam.0 & (0x1 << 30) > 0,
|
||||||
})
|
})
|
||||||
};
|
})?;
|
||||||
let mut func = lock.callbacks.input.take()?;
|
let mut func = lock.callbacks.input.take()?;
|
||||||
drop(lock);
|
drop(lock);
|
||||||
let result = if !func(input).propagate {
|
|
||||||
state_ptr.state.borrow_mut().system_key_handled = true;
|
|
||||||
Some(0)
|
|
||||||
} else {
|
|
||||||
// we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
|
|
||||||
// shortcuts.
|
|
||||||
None
|
|
||||||
};
|
|
||||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
|
||||||
|
|
||||||
result
|
let handled = !func(input).propagate;
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_syskeyup_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
|
|
||||||
let mut lock = state_ptr.state.borrow_mut();
|
let mut lock = state_ptr.state.borrow_mut();
|
||||||
let vkey = wparam.loword();
|
lock.callbacks.input = Some(func);
|
||||||
let input = if is_modifier(VIRTUAL_KEY(vkey)) {
|
|
||||||
let modifiers = current_modifiers();
|
if handled {
|
||||||
if let Some(prev_modifiers) = lock.last_reported_modifiers {
|
lock.system_key_handled = true;
|
||||||
if prev_modifiers == modifiers {
|
lock.suppress_next_char_msg = true;
|
||||||
return Some(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lock.last_reported_modifiers = Some(modifiers);
|
|
||||||
PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers })
|
|
||||||
} else {
|
|
||||||
let keystroke = parse_syskeydown_msg_keystroke(wparam)?;
|
|
||||||
PlatformInput::KeyUp(KeyUpEvent { keystroke })
|
|
||||||
};
|
|
||||||
let mut func = lock.callbacks.input.take()?;
|
|
||||||
drop(lock);
|
|
||||||
let result = if !func(input).propagate {
|
|
||||||
Some(0)
|
Some(0)
|
||||||
} else {
|
} else {
|
||||||
// we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
|
// we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
|
||||||
// shortcuts.
|
// shortcuts.
|
||||||
None
|
None
|
||||||
};
|
}
|
||||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_syskeyup_msg(
|
||||||
|
wparam: WPARAM,
|
||||||
|
lparam: LPARAM,
|
||||||
|
state_ptr: Rc<WindowsWindowStatePtr>,
|
||||||
|
) -> Option<isize> {
|
||||||
|
let mut lock = state_ptr.state.borrow_mut();
|
||||||
|
let input = handle_key_event(wparam, lparam, &mut lock, |keystroke| {
|
||||||
|
PlatformInput::KeyUp(KeyUpEvent { keystroke })
|
||||||
|
})?;
|
||||||
|
let mut func = lock.callbacks.input.take()?;
|
||||||
|
drop(lock);
|
||||||
|
func(input);
|
||||||
|
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
||||||
|
|
||||||
|
// Always return 0 to indicate that the message was handled, so we could properly handle `ModifiersChanged` event.
|
||||||
|
Some(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's a known bug that you can't trigger `ctrl-shift-0`. See:
|
||||||
|
// https://superuser.com/questions/1455762/ctrl-shift-number-key-combination-has-stopped-working-for-a-few-numbers
|
||||||
fn handle_keydown_msg(
|
fn handle_keydown_msg(
|
||||||
wparam: WPARAM,
|
wparam: WPARAM,
|
||||||
lparam: LPARAM,
|
lparam: LPARAM,
|
||||||
state_ptr: Rc<WindowsWindowStatePtr>,
|
state_ptr: Rc<WindowsWindowStatePtr>,
|
||||||
) -> Option<isize> {
|
) -> Option<isize> {
|
||||||
let Some(keystroke_or_modifier) = parse_keystroke_from_vkey(wparam, false) else {
|
|
||||||
return Some(1);
|
|
||||||
};
|
|
||||||
let mut lock = state_ptr.state.borrow_mut();
|
let mut lock = state_ptr.state.borrow_mut();
|
||||||
|
let Some(input) = handle_key_event(wparam, lparam, &mut lock, |keystroke| {
|
||||||
let event = match keystroke_or_modifier {
|
PlatformInput::KeyDown(KeyDownEvent {
|
||||||
KeystrokeOrModifier::Keystroke(keystroke) => PlatformInput::KeyDown(KeyDownEvent {
|
|
||||||
keystroke,
|
keystroke,
|
||||||
is_held: lparam.0 & (0x1 << 30) > 0,
|
is_held: lparam.0 & (0x1 << 30) > 0,
|
||||||
}),
|
})
|
||||||
KeystrokeOrModifier::Modifier(modifiers) => {
|
}) else {
|
||||||
if let Some(prev_modifiers) = lock.last_reported_modifiers {
|
return Some(1);
|
||||||
if prev_modifiers == modifiers {
|
|
||||||
return Some(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lock.last_reported_modifiers = Some(modifiers);
|
|
||||||
PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers })
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(mut func) = lock.callbacks.input.take() else {
|
let Some(mut func) = lock.callbacks.input.take() else {
|
||||||
return Some(1);
|
return Some(1);
|
||||||
};
|
};
|
||||||
drop(lock);
|
drop(lock);
|
||||||
|
|
||||||
let result = if func(event).default_prevented {
|
let handled = !func(input).propagate;
|
||||||
Some(0)
|
|
||||||
} else {
|
|
||||||
Some(1)
|
|
||||||
};
|
|
||||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_keyup_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
|
|
||||||
let Some(keystroke_or_modifier) = parse_keystroke_from_vkey(wparam, true) else {
|
|
||||||
return Some(1);
|
|
||||||
};
|
|
||||||
let mut lock = state_ptr.state.borrow_mut();
|
let mut lock = state_ptr.state.borrow_mut();
|
||||||
|
lock.callbacks.input = Some(func);
|
||||||
|
|
||||||
let event = match keystroke_or_modifier {
|
if handled {
|
||||||
KeystrokeOrModifier::Keystroke(keystroke) => PlatformInput::KeyUp(KeyUpEvent { keystroke }),
|
lock.suppress_next_char_msg = true;
|
||||||
KeystrokeOrModifier::Modifier(modifiers) => {
|
|
||||||
if let Some(prev_modifiers) = lock.last_reported_modifiers {
|
|
||||||
if prev_modifiers == modifiers {
|
|
||||||
return Some(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lock.last_reported_modifiers = Some(modifiers);
|
|
||||||
PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers })
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let Some(mut func) = lock.callbacks.input.take() else {
|
|
||||||
return Some(1);
|
|
||||||
};
|
|
||||||
drop(lock);
|
|
||||||
|
|
||||||
let result = if func(event).default_prevented {
|
|
||||||
Some(0)
|
Some(0)
|
||||||
} else {
|
} else {
|
||||||
Some(1)
|
Some(1)
|
||||||
};
|
}
|
||||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_char_msg(
|
fn handle_keyup_msg(
|
||||||
wparam: WPARAM,
|
wparam: WPARAM,
|
||||||
lparam: LPARAM,
|
lparam: LPARAM,
|
||||||
state_ptr: Rc<WindowsWindowStatePtr>,
|
state_ptr: Rc<WindowsWindowStatePtr>,
|
||||||
) -> Option<isize> {
|
) -> Option<isize> {
|
||||||
let Some(keystroke) = parse_char_msg_keystroke(wparam) else {
|
let mut lock = state_ptr.state.borrow_mut();
|
||||||
|
let Some(input) = handle_key_event(wparam, lparam, &mut lock, |keystroke| {
|
||||||
|
PlatformInput::KeyUp(KeyUpEvent { keystroke })
|
||||||
|
}) else {
|
||||||
return Some(1);
|
return Some(1);
|
||||||
};
|
};
|
||||||
let mut lock = state_ptr.state.borrow_mut();
|
|
||||||
let Some(mut func) = lock.callbacks.input.take() else {
|
let Some(mut func) = lock.callbacks.input.take() else {
|
||||||
return Some(1);
|
return Some(1);
|
||||||
};
|
};
|
||||||
drop(lock);
|
drop(lock);
|
||||||
let key_char = keystroke.key_char.clone();
|
|
||||||
let event = KeyDownEvent {
|
let result = if func(input).default_prevented {
|
||||||
keystroke,
|
Some(0)
|
||||||
is_held: lparam.0 & (0x1 << 30) > 0,
|
} else {
|
||||||
|
Some(1)
|
||||||
};
|
};
|
||||||
let dispatch_event_result = func(PlatformInput::KeyDown(event));
|
|
||||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
||||||
|
|
||||||
if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
|
result
|
||||||
return Some(0);
|
|
||||||
}
|
}
|
||||||
let Some(ime_char) = key_char else {
|
|
||||||
|
fn handle_char_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
|
||||||
|
let Some(input) = char::from_u32(wparam.0 as u32)
|
||||||
|
.filter(|c| !c.is_control())
|
||||||
|
.map(String::from)
|
||||||
|
else {
|
||||||
return Some(1);
|
return Some(1);
|
||||||
};
|
};
|
||||||
with_input_handler(&state_ptr, |input_handler| {
|
with_input_handler(&state_ptr, |input_handler| {
|
||||||
input_handler.replace_text_in_range(None, &ime_char);
|
input_handler.replace_text_in_range(None, &input);
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(0)
|
Some(0)
|
||||||
|
@ -1297,152 +1247,117 @@ fn handle_input_language_changed(
|
||||||
Some(0)
|
Some(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
|
fn handle_key_event<F>(
|
||||||
let modifiers = current_modifiers();
|
wparam: WPARAM,
|
||||||
let vk_code = wparam.loword();
|
lparam: LPARAM,
|
||||||
|
state: &mut WindowsWindowState,
|
||||||
|
f: F,
|
||||||
|
) -> Option<PlatformInput>
|
||||||
|
where
|
||||||
|
F: FnOnce(Keystroke) -> PlatformInput,
|
||||||
|
{
|
||||||
|
state.suppress_next_char_msg = false;
|
||||||
|
let virtual_key = VIRTUAL_KEY(wparam.loword());
|
||||||
|
let mut modifiers = current_modifiers();
|
||||||
|
|
||||||
// on Windows, F10 can trigger this event, not just the alt key,
|
match virtual_key {
|
||||||
// so when F10 was pressed, handle only it
|
VK_PROCESSKEY => {
|
||||||
if !modifiers.alt {
|
// IME composition
|
||||||
if vk_code == VK_F10.0 {
|
None
|
||||||
let offset = vk_code - VK_F1.0;
|
}
|
||||||
return Some(Keystroke {
|
VK_SHIFT | VK_CONTROL | VK_MENU | VK_LWIN | VK_RWIN => {
|
||||||
modifiers,
|
if state
|
||||||
key: format!("f{}", offset + 1),
|
.last_reported_modifiers
|
||||||
key_char: None,
|
.is_some_and(|prev_modifiers| prev_modifiers == modifiers)
|
||||||
});
|
{
|
||||||
} else {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
state.last_reported_modifiers = Some(modifiers);
|
||||||
|
Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
|
||||||
let key = match VIRTUAL_KEY(vk_code) {
|
|
||||||
VK_BACK => "backspace",
|
|
||||||
VK_RETURN => "enter",
|
|
||||||
VK_TAB => "tab",
|
|
||||||
VK_UP => "up",
|
|
||||||
VK_DOWN => "down",
|
|
||||||
VK_RIGHT => "right",
|
|
||||||
VK_LEFT => "left",
|
|
||||||
VK_HOME => "home",
|
|
||||||
VK_END => "end",
|
|
||||||
VK_PRIOR => "pageup",
|
|
||||||
VK_NEXT => "pagedown",
|
|
||||||
VK_BROWSER_BACK => "back",
|
|
||||||
VK_BROWSER_FORWARD => "forward",
|
|
||||||
VK_ESCAPE => "escape",
|
|
||||||
VK_INSERT => "insert",
|
|
||||||
VK_DELETE => "delete",
|
|
||||||
VK_APPS => "menu",
|
|
||||||
_ => {
|
|
||||||
let basic_key = basic_vkcode_to_string(vk_code, modifiers);
|
|
||||||
if basic_key.is_some() {
|
|
||||||
return basic_key;
|
|
||||||
} else {
|
|
||||||
if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
|
|
||||||
let offset = vk_code - VK_F1.0;
|
|
||||||
return Some(Keystroke {
|
|
||||||
modifiers,
|
modifiers,
|
||||||
key: format!("f{}", offset + 1),
|
|
||||||
key_char: None,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
Some(Keystroke {
|
|
||||||
modifiers,
|
|
||||||
key,
|
|
||||||
key_char: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
enum KeystrokeOrModifier {
|
|
||||||
Keystroke(Keystroke),
|
|
||||||
Modifier(Modifiers),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_keystroke_from_vkey(wparam: WPARAM, is_keyup: bool) -> Option<KeystrokeOrModifier> {
|
|
||||||
let vk_code = wparam.loword();
|
|
||||||
|
|
||||||
let modifiers = current_modifiers();
|
|
||||||
|
|
||||||
let key = match VIRTUAL_KEY(vk_code) {
|
|
||||||
VK_BACK => "backspace",
|
|
||||||
VK_RETURN => "enter",
|
|
||||||
VK_TAB => "tab",
|
|
||||||
VK_UP => "up",
|
|
||||||
VK_DOWN => "down",
|
|
||||||
VK_RIGHT => "right",
|
|
||||||
VK_LEFT => "left",
|
|
||||||
VK_HOME => "home",
|
|
||||||
VK_END => "end",
|
|
||||||
VK_PRIOR => "pageup",
|
|
||||||
VK_NEXT => "pagedown",
|
|
||||||
VK_BROWSER_BACK => "back",
|
|
||||||
VK_BROWSER_FORWARD => "forward",
|
|
||||||
VK_ESCAPE => "escape",
|
|
||||||
VK_INSERT => "insert",
|
|
||||||
VK_DELETE => "delete",
|
|
||||||
VK_APPS => "menu",
|
|
||||||
_ => {
|
|
||||||
if is_modifier(VIRTUAL_KEY(vk_code)) {
|
|
||||||
return Some(KeystrokeOrModifier::Modifier(modifiers));
|
|
||||||
}
|
|
||||||
|
|
||||||
if modifiers.control || modifiers.alt || is_keyup {
|
|
||||||
let basic_key = basic_vkcode_to_string(vk_code, modifiers);
|
|
||||||
if let Some(basic_key) = basic_key {
|
|
||||||
return Some(KeystrokeOrModifier::Keystroke(basic_key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
|
|
||||||
let offset = vk_code - VK_F1.0;
|
|
||||||
return Some(KeystrokeOrModifier::Keystroke(Keystroke {
|
|
||||||
modifiers,
|
|
||||||
key: format!("f{}", offset + 1),
|
|
||||||
key_char: None,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
Some(KeystrokeOrModifier::Keystroke(Keystroke {
|
|
||||||
modifiers,
|
|
||||||
key,
|
|
||||||
key_char: None,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
vkey => {
|
||||||
fn parse_char_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
|
let keystroke = parse_normal_key(vkey, lparam, modifiers)?;
|
||||||
let first_char = char::from_u32((wparam.0 as u16).into())?;
|
Some(f(keystroke))
|
||||||
if first_char.is_control() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let mut modifiers = current_modifiers();
|
|
||||||
// for characters that use 'shift' to type it is expected that the
|
|
||||||
// shift is not reported if the uppercase/lowercase are the same and instead only the key is reported
|
|
||||||
if first_char.to_ascii_uppercase() == first_char.to_ascii_lowercase() {
|
|
||||||
modifiers.shift = false;
|
|
||||||
}
|
}
|
||||||
let key = match first_char {
|
}
|
||||||
' ' => "space".to_string(),
|
}
|
||||||
first_char => first_char.to_lowercase().to_string(),
|
|
||||||
};
|
fn parse_immutable(vkey: VIRTUAL_KEY) -> Option<String> {
|
||||||
|
Some(
|
||||||
|
match vkey {
|
||||||
|
VK_SPACE => "space",
|
||||||
|
VK_BACK => "backspace",
|
||||||
|
VK_RETURN => "enter",
|
||||||
|
VK_TAB => "tab",
|
||||||
|
VK_UP => "up",
|
||||||
|
VK_DOWN => "down",
|
||||||
|
VK_RIGHT => "right",
|
||||||
|
VK_LEFT => "left",
|
||||||
|
VK_HOME => "home",
|
||||||
|
VK_END => "end",
|
||||||
|
VK_PRIOR => "pageup",
|
||||||
|
VK_NEXT => "pagedown",
|
||||||
|
VK_BROWSER_BACK => "back",
|
||||||
|
VK_BROWSER_FORWARD => "forward",
|
||||||
|
VK_ESCAPE => "escape",
|
||||||
|
VK_INSERT => "insert",
|
||||||
|
VK_DELETE => "delete",
|
||||||
|
VK_APPS => "menu",
|
||||||
|
VK_F1 => "f1",
|
||||||
|
VK_F2 => "f2",
|
||||||
|
VK_F3 => "f3",
|
||||||
|
VK_F4 => "f4",
|
||||||
|
VK_F5 => "f5",
|
||||||
|
VK_F6 => "f6",
|
||||||
|
VK_F7 => "f7",
|
||||||
|
VK_F8 => "f8",
|
||||||
|
VK_F9 => "f9",
|
||||||
|
VK_F10 => "f10",
|
||||||
|
VK_F11 => "f11",
|
||||||
|
VK_F12 => "f12",
|
||||||
|
VK_F13 => "f13",
|
||||||
|
VK_F14 => "f14",
|
||||||
|
VK_F15 => "f15",
|
||||||
|
VK_F16 => "f16",
|
||||||
|
VK_F17 => "f17",
|
||||||
|
VK_F18 => "f18",
|
||||||
|
VK_F19 => "f19",
|
||||||
|
VK_F20 => "f20",
|
||||||
|
VK_F21 => "f21",
|
||||||
|
VK_F22 => "f22",
|
||||||
|
VK_F23 => "f23",
|
||||||
|
VK_F24 => "f24",
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_normal_key(
|
||||||
|
vkey: VIRTUAL_KEY,
|
||||||
|
lparam: LPARAM,
|
||||||
|
mut modifiers: Modifiers,
|
||||||
|
) -> Option<Keystroke> {
|
||||||
|
let mut key_char = None;
|
||||||
|
let key = parse_immutable(vkey).or_else(|| {
|
||||||
|
let scan_code = lparam.hiword() & 0xFF;
|
||||||
|
key_char = generate_key_char(
|
||||||
|
vkey,
|
||||||
|
scan_code as u32,
|
||||||
|
modifiers.control,
|
||||||
|
modifiers.shift,
|
||||||
|
modifiers.alt,
|
||||||
|
);
|
||||||
|
get_keystroke_key(vkey, scan_code as u32, &mut modifiers)
|
||||||
|
})?;
|
||||||
Some(Keystroke {
|
Some(Keystroke {
|
||||||
modifiers,
|
modifiers,
|
||||||
key,
|
key,
|
||||||
key_char: Some(first_char.to_string()),
|
key_char,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_ime_compostion_string(ctx: HIMC) -> Option<String> {
|
fn parse_ime_compostion_string(ctx: HIMC) -> Option<String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -1494,40 +1409,11 @@ fn parse_ime_compostion_result(ctx: HIMC) -> Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
|
|
||||||
let mapped_code = unsafe { MapVirtualKeyW(code as u32, MAPVK_VK_TO_CHAR) };
|
|
||||||
|
|
||||||
let key = match mapped_code {
|
|
||||||
0 => None,
|
|
||||||
raw_code => char::from_u32(raw_code),
|
|
||||||
}?
|
|
||||||
.to_ascii_lowercase();
|
|
||||||
|
|
||||||
let key = if matches!(code as u32, 112..=135) {
|
|
||||||
format!("f{key}")
|
|
||||||
} else {
|
|
||||||
key.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(Keystroke {
|
|
||||||
modifiers,
|
|
||||||
key,
|
|
||||||
key_char: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
|
fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
|
||||||
unsafe { GetKeyState(vkey.0 as i32) < 0 }
|
unsafe { GetKeyState(vkey.0 as i32) < 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_modifier(virtual_key: VIRTUAL_KEY) -> bool {
|
|
||||||
matches!(
|
|
||||||
virtual_key,
|
|
||||||
VK_CONTROL | VK_MENU | VK_SHIFT | VK_LWIN | VK_RWIN
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn current_modifiers() -> Modifiers {
|
pub(crate) fn current_modifiers() -> Modifiers {
|
||||||
Modifiers {
|
Modifiers {
|
||||||
|
@ -1639,7 +1525,12 @@ fn with_input_handler<F, R>(state_ptr: &Rc<WindowsWindowStatePtr>, f: F) -> Opti
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut PlatformInputHandler) -> R,
|
F: FnOnce(&mut PlatformInputHandler) -> R,
|
||||||
{
|
{
|
||||||
let mut input_handler = state_ptr.state.borrow_mut().input_handler.take()?;
|
let mut lock = state_ptr.state.borrow_mut();
|
||||||
|
if lock.suppress_next_char_msg {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut input_handler = lock.input_handler.take()?;
|
||||||
|
drop(lock);
|
||||||
let result = f(&mut input_handler);
|
let result = f(&mut input_handler);
|
||||||
state_ptr.state.borrow_mut().input_handler = Some(input_handler);
|
state_ptr.state.borrow_mut().input_handler = Some(input_handler);
|
||||||
Some(result)
|
Some(result)
|
||||||
|
@ -1653,6 +1544,9 @@ where
|
||||||
F: FnOnce(&mut PlatformInputHandler, f32) -> Option<R>,
|
F: FnOnce(&mut PlatformInputHandler, f32) -> Option<R>,
|
||||||
{
|
{
|
||||||
let mut lock = state_ptr.state.borrow_mut();
|
let mut lock = state_ptr.state.borrow_mut();
|
||||||
|
if lock.suppress_next_char_msg {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let mut input_handler = lock.input_handler.take()?;
|
let mut input_handler = lock.input_handler.take()?;
|
||||||
let scale_factor = lock.scale_factor;
|
let scale_factor = lock.scale_factor;
|
||||||
drop(lock);
|
drop(lock);
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use windows::Win32::UI::{
|
use windows::Win32::UI::{
|
||||||
Input::KeyboardAndMouse::GetKeyboardLayoutNameW, WindowsAndMessaging::KL_NAMELENGTH,
|
Input::KeyboardAndMouse::{
|
||||||
|
GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MapVirtualKeyW, ToUnicode, VIRTUAL_KEY, VK_0,
|
||||||
|
VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_ABNT_C1, VK_CONTROL, VK_MENU,
|
||||||
|
VK_OEM_1, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_OEM_102,
|
||||||
|
VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT,
|
||||||
|
},
|
||||||
|
WindowsAndMessaging::KL_NAMELENGTH,
|
||||||
};
|
};
|
||||||
use windows_core::HSTRING;
|
use windows_core::HSTRING;
|
||||||
|
|
||||||
use crate::PlatformKeyboardLayout;
|
use crate::{Modifiers, PlatformKeyboardLayout};
|
||||||
|
|
||||||
pub(crate) struct WindowsKeyboardLayout {
|
pub(crate) struct WindowsKeyboardLayout {
|
||||||
id: String,
|
id: String,
|
||||||
|
@ -41,3 +47,94 @@ impl WindowsKeyboardLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_keystroke_key(
|
||||||
|
vkey: VIRTUAL_KEY,
|
||||||
|
scan_code: u32,
|
||||||
|
modifiers: &mut Modifiers,
|
||||||
|
) -> Option<String> {
|
||||||
|
if modifiers.shift && need_to_convert_to_shifted_key(vkey) {
|
||||||
|
get_shifted_key(vkey, scan_code).inspect(|_| {
|
||||||
|
modifiers.shift = false;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
get_key_from_vkey(vkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key_from_vkey(vkey: VIRTUAL_KEY) -> Option<String> {
|
||||||
|
let key_data = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_CHAR) };
|
||||||
|
if key_data == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The high word contains dead key flag, the low word contains the character
|
||||||
|
let key = char::from_u32(key_data & 0xFFFF)?;
|
||||||
|
|
||||||
|
Some(key.to_ascii_lowercase().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn need_to_convert_to_shifted_key(vkey: VIRTUAL_KEY) -> bool {
|
||||||
|
matches!(
|
||||||
|
vkey,
|
||||||
|
VK_OEM_3
|
||||||
|
| VK_OEM_MINUS
|
||||||
|
| VK_OEM_PLUS
|
||||||
|
| VK_OEM_4
|
||||||
|
| VK_OEM_5
|
||||||
|
| VK_OEM_6
|
||||||
|
| VK_OEM_1
|
||||||
|
| VK_OEM_7
|
||||||
|
| VK_OEM_COMMA
|
||||||
|
| VK_OEM_PERIOD
|
||||||
|
| VK_OEM_2
|
||||||
|
| VK_OEM_102
|
||||||
|
| VK_OEM_8
|
||||||
|
| VK_ABNT_C1
|
||||||
|
| VK_0
|
||||||
|
| VK_1
|
||||||
|
| VK_2
|
||||||
|
| VK_3
|
||||||
|
| VK_4
|
||||||
|
| VK_5
|
||||||
|
| VK_6
|
||||||
|
| VK_7
|
||||||
|
| VK_8
|
||||||
|
| VK_9
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shifted_key(vkey: VIRTUAL_KEY, scan_code: u32) -> Option<String> {
|
||||||
|
generate_key_char(vkey, scan_code, false, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn generate_key_char(
|
||||||
|
vkey: VIRTUAL_KEY,
|
||||||
|
scan_code: u32,
|
||||||
|
control: bool,
|
||||||
|
shift: bool,
|
||||||
|
alt: bool,
|
||||||
|
) -> Option<String> {
|
||||||
|
let mut state = [0; 256];
|
||||||
|
if control {
|
||||||
|
state[VK_CONTROL.0 as usize] = 0x80;
|
||||||
|
}
|
||||||
|
if shift {
|
||||||
|
state[VK_SHIFT.0 as usize] = 0x80;
|
||||||
|
}
|
||||||
|
if alt {
|
||||||
|
state[VK_MENU.0 as usize] = 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buffer = [0; 8];
|
||||||
|
let len = unsafe { ToUnicode(vkey.0 as u32, scan_code, Some(&state), &mut buffer, 1 << 2) };
|
||||||
|
|
||||||
|
if len > 0 {
|
||||||
|
let candidate = String::from_utf16_lossy(&buffer[..len as usize]);
|
||||||
|
if !candidate.is_empty() && !candidate.chars().next().unwrap().is_control() {
|
||||||
|
return Some(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ pub struct WindowsWindowState {
|
||||||
pub callbacks: Callbacks,
|
pub callbacks: Callbacks,
|
||||||
pub input_handler: Option<PlatformInputHandler>,
|
pub input_handler: Option<PlatformInputHandler>,
|
||||||
pub last_reported_modifiers: Option<Modifiers>,
|
pub last_reported_modifiers: Option<Modifiers>,
|
||||||
|
pub suppress_next_char_msg: bool,
|
||||||
pub system_key_handled: bool,
|
pub system_key_handled: bool,
|
||||||
pub hovered: bool,
|
pub hovered: bool,
|
||||||
|
|
||||||
|
@ -102,6 +103,7 @@ impl WindowsWindowState {
|
||||||
let callbacks = Callbacks::default();
|
let callbacks = Callbacks::default();
|
||||||
let input_handler = None;
|
let input_handler = None;
|
||||||
let last_reported_modifiers = None;
|
let last_reported_modifiers = None;
|
||||||
|
let suppress_next_char_msg = false;
|
||||||
let system_key_handled = false;
|
let system_key_handled = false;
|
||||||
let hovered = false;
|
let hovered = false;
|
||||||
let click_state = ClickState::new();
|
let click_state = ClickState::new();
|
||||||
|
@ -121,6 +123,7 @@ impl WindowsWindowState {
|
||||||
callbacks,
|
callbacks,
|
||||||
input_handler,
|
input_handler,
|
||||||
last_reported_modifiers,
|
last_reported_modifiers,
|
||||||
|
suppress_next_char_msg,
|
||||||
system_key_handled,
|
system_key_handled,
|
||||||
hovered,
|
hovered,
|
||||||
renderer,
|
renderer,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue