windows: Only call TranslateMessage
when we can't handle the event (#32166)
This PR improves key handling on Windows by moving the `TranslateMessage` call from the message loop to after we handled `WM_KEYDOWN`. This brings Windows behavior more in line with macOS and gives us finer control over key events. As a result, Vim keybindings now work properly even when an IME is active. The trade-off is that it might introduce a slight delay in text input. Release Notes: - N/A
This commit is contained in:
parent
738cfdff84
commit
5b9d3ea097
3 changed files with 48 additions and 34 deletions
|
@ -83,11 +83,11 @@ pub(crate) fn handle_msg(
|
|||
WM_XBUTTONUP => handle_xbutton_msg(handle, wparam, lparam, handle_mouse_up_msg, 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_SYSKEYDOWN => handle_syskeydown_msg(wparam, lparam, state_ptr),
|
||||
WM_SYSKEYUP => handle_syskeyup_msg(wparam, lparam, state_ptr),
|
||||
WM_SYSKEYDOWN => handle_syskeydown_msg(handle, wparam, lparam, state_ptr),
|
||||
WM_SYSKEYUP => handle_syskeyup_msg(handle, wparam, lparam, state_ptr),
|
||||
WM_SYSCOMMAND => handle_system_command(wparam, state_ptr),
|
||||
WM_KEYDOWN => handle_keydown_msg(wparam, lparam, state_ptr),
|
||||
WM_KEYUP => handle_keyup_msg(wparam, lparam, state_ptr),
|
||||
WM_KEYDOWN => handle_keydown_msg(handle, wparam, lparam, state_ptr),
|
||||
WM_KEYUP => handle_keyup_msg(handle, wparam, lparam, state_ptr),
|
||||
WM_CHAR => handle_char_msg(wparam, state_ptr),
|
||||
WM_DEADCHAR => handle_dead_char_msg(wparam, state_ptr),
|
||||
WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr),
|
||||
|
@ -337,12 +337,13 @@ fn handle_mouse_leave_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize>
|
|||
}
|
||||
|
||||
fn handle_syskeydown_msg(
|
||||
handle: HWND,
|
||||
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| {
|
||||
let input = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| {
|
||||
PlatformInput::KeyDown(KeyDownEvent {
|
||||
keystroke,
|
||||
is_held: lparam.0 & (0x1 << 30) > 0,
|
||||
|
@ -358,7 +359,6 @@ fn handle_syskeydown_msg(
|
|||
|
||||
if handled {
|
||||
lock.system_key_handled = true;
|
||||
lock.suppress_next_char_msg = true;
|
||||
Some(0)
|
||||
} else {
|
||||
// we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
|
||||
|
@ -368,12 +368,13 @@ fn handle_syskeydown_msg(
|
|||
}
|
||||
|
||||
fn handle_syskeyup_msg(
|
||||
handle: HWND,
|
||||
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| {
|
||||
let input = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| {
|
||||
PlatformInput::KeyUp(KeyUpEvent { keystroke })
|
||||
})?;
|
||||
let mut func = lock.callbacks.input.take()?;
|
||||
|
@ -388,12 +389,13 @@ fn handle_syskeyup_msg(
|
|||
// 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(
|
||||
handle: HWND,
|
||||
wparam: WPARAM,
|
||||
lparam: LPARAM,
|
||||
state_ptr: Rc<WindowsWindowStatePtr>,
|
||||
) -> Option<isize> {
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
let Some(input) = handle_key_event(wparam, lparam, &mut lock, |keystroke| {
|
||||
let Some(input) = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| {
|
||||
PlatformInput::KeyDown(KeyDownEvent {
|
||||
keystroke,
|
||||
is_held: lparam.0 & (0x1 << 30) > 0,
|
||||
|
@ -401,32 +403,42 @@ fn handle_keydown_msg(
|
|||
}) else {
|
||||
return Some(1);
|
||||
};
|
||||
drop(lock);
|
||||
|
||||
let Some(mut func) = lock.callbacks.input.take() else {
|
||||
let is_composing = with_input_handler(&state_ptr, |input_handler| {
|
||||
input_handler.marked_text_range()
|
||||
})
|
||||
.flatten()
|
||||
.is_some();
|
||||
if is_composing {
|
||||
translate_message(handle, wparam, lparam);
|
||||
return Some(0);
|
||||
}
|
||||
|
||||
let Some(mut func) = state_ptr.state.borrow_mut().callbacks.input.take() else {
|
||||
return Some(1);
|
||||
};
|
||||
drop(lock);
|
||||
|
||||
let handled = !func(input).propagate;
|
||||
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
lock.callbacks.input = Some(func);
|
||||
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
||||
|
||||
if handled {
|
||||
lock.suppress_next_char_msg = true;
|
||||
Some(0)
|
||||
} else {
|
||||
translate_message(handle, wparam, lparam);
|
||||
Some(1)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_keyup_msg(
|
||||
handle: HWND,
|
||||
wparam: WPARAM,
|
||||
lparam: LPARAM,
|
||||
state_ptr: Rc<WindowsWindowStatePtr>,
|
||||
) -> Option<isize> {
|
||||
let mut lock = state_ptr.state.borrow_mut();
|
||||
let Some(input) = handle_key_event(wparam, lparam, &mut lock, |keystroke| {
|
||||
let Some(input) = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| {
|
||||
PlatformInput::KeyUp(KeyUpEvent { keystroke })
|
||||
}) else {
|
||||
return Some(1);
|
||||
|
@ -1213,7 +1225,23 @@ fn handle_input_language_changed(
|
|||
Some(0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn translate_message(handle: HWND, wparam: WPARAM, lparam: LPARAM) {
|
||||
let msg = MSG {
|
||||
hwnd: handle,
|
||||
message: WM_KEYDOWN,
|
||||
wParam: wparam,
|
||||
lParam: lparam,
|
||||
// It seems like leaving the following two parameters empty doesn't break key events, they still work as expected.
|
||||
// But if any bugs pop up after this PR, this is probably the place to look first.
|
||||
time: 0,
|
||||
pt: POINT::default(),
|
||||
};
|
||||
unsafe { TranslateMessage(&msg).ok().log_err() };
|
||||
}
|
||||
|
||||
fn handle_key_event<F>(
|
||||
handle: HWND,
|
||||
wparam: WPARAM,
|
||||
lparam: LPARAM,
|
||||
state: &mut WindowsWindowState,
|
||||
|
@ -1222,15 +1250,10 @@ fn handle_key_event<F>(
|
|||
where
|
||||
F: FnOnce(Keystroke) -> PlatformInput,
|
||||
{
|
||||
state.suppress_next_char_msg = false;
|
||||
let virtual_key = VIRTUAL_KEY(wparam.loword());
|
||||
let mut modifiers = current_modifiers();
|
||||
|
||||
match virtual_key {
|
||||
VK_PROCESSKEY => {
|
||||
// IME composition
|
||||
None
|
||||
}
|
||||
VK_SHIFT | VK_CONTROL | VK_MENU | VK_LWIN | VK_RWIN => {
|
||||
if state
|
||||
.last_reported_modifiers
|
||||
|
@ -1244,6 +1267,11 @@ where
|
|||
}))
|
||||
}
|
||||
vkey => {
|
||||
let vkey = if vkey == VK_PROCESSKEY {
|
||||
VIRTUAL_KEY(unsafe { ImmGetVirtualKey(handle) } as u16)
|
||||
} else {
|
||||
vkey
|
||||
};
|
||||
let keystroke = parse_normal_key(vkey, lparam, modifiers)?;
|
||||
Some(f(keystroke))
|
||||
}
|
||||
|
@ -1491,12 +1519,7 @@ fn with_input_handler<F, R>(state_ptr: &Rc<WindowsWindowStatePtr>, f: F) -> Opti
|
|||
where
|
||||
F: FnOnce(&mut PlatformInputHandler) -> R,
|
||||
{
|
||||
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 mut input_handler = state_ptr.state.borrow_mut().input_handler.take()?;
|
||||
let result = f(&mut input_handler);
|
||||
state_ptr.state.borrow_mut().input_handler = Some(input_handler);
|
||||
Some(result)
|
||||
|
@ -1510,9 +1533,6 @@ where
|
|||
F: FnOnce(&mut PlatformInputHandler, f32) -> Option<R>,
|
||||
{
|
||||
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 scale_factor = lock.scale_factor;
|
||||
drop(lock);
|
||||
|
|
|
@ -231,9 +231,6 @@ impl WindowsPlatform {
|
|||
}
|
||||
}
|
||||
_ => {
|
||||
// todo(windows)
|
||||
// crate `windows 0.56` reports true as Err
|
||||
TranslateMessage(&msg).as_bool();
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,6 @@ pub struct WindowsWindowState {
|
|||
pub callbacks: Callbacks,
|
||||
pub input_handler: Option<PlatformInputHandler>,
|
||||
pub last_reported_modifiers: Option<Modifiers>,
|
||||
pub suppress_next_char_msg: bool,
|
||||
pub system_key_handled: bool,
|
||||
pub hovered: bool,
|
||||
|
||||
|
@ -103,7 +102,6 @@ impl WindowsWindowState {
|
|||
let callbacks = Callbacks::default();
|
||||
let input_handler = None;
|
||||
let last_reported_modifiers = None;
|
||||
let suppress_next_char_msg = false;
|
||||
let system_key_handled = false;
|
||||
let hovered = false;
|
||||
let click_state = ClickState::new();
|
||||
|
@ -123,7 +121,6 @@ impl WindowsWindowState {
|
|||
callbacks,
|
||||
input_handler,
|
||||
last_reported_modifiers,
|
||||
suppress_next_char_msg,
|
||||
system_key_handled,
|
||||
hovered,
|
||||
renderer,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue