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_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_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(handle, wparam, lparam, state_ptr),
|
||||||
WM_SYSKEYUP => handle_syskeyup_msg(wparam, lparam, state_ptr),
|
WM_SYSKEYUP => handle_syskeyup_msg(handle, 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(handle, wparam, lparam, state_ptr),
|
||||||
WM_KEYUP => handle_keyup_msg(wparam, lparam, state_ptr),
|
WM_KEYUP => handle_keyup_msg(handle, wparam, lparam, state_ptr),
|
||||||
WM_CHAR => handle_char_msg(wparam, 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),
|
||||||
|
@ -337,12 +337,13 @@ fn handle_mouse_leave_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize>
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_syskeydown_msg(
|
fn handle_syskeydown_msg(
|
||||||
|
handle: HWND,
|
||||||
wparam: WPARAM,
|
wparam: WPARAM,
|
||||||
lparam: LPARAM,
|
lparam: LPARAM,
|
||||||
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 input = handle_key_event(wparam, lparam, &mut lock, |keystroke| {
|
let input = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| {
|
||||||
PlatformInput::KeyDown(KeyDownEvent {
|
PlatformInput::KeyDown(KeyDownEvent {
|
||||||
keystroke,
|
keystroke,
|
||||||
is_held: lparam.0 & (0x1 << 30) > 0,
|
is_held: lparam.0 & (0x1 << 30) > 0,
|
||||||
|
@ -358,7 +359,6 @@ fn handle_syskeydown_msg(
|
||||||
|
|
||||||
if handled {
|
if handled {
|
||||||
lock.system_key_handled = true;
|
lock.system_key_handled = true;
|
||||||
lock.suppress_next_char_msg = true;
|
|
||||||
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}`
|
||||||
|
@ -368,12 +368,13 @@ fn handle_syskeydown_msg(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_syskeyup_msg(
|
fn handle_syskeyup_msg(
|
||||||
|
handle: HWND,
|
||||||
wparam: WPARAM,
|
wparam: WPARAM,
|
||||||
lparam: LPARAM,
|
lparam: LPARAM,
|
||||||
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 input = handle_key_event(wparam, lparam, &mut lock, |keystroke| {
|
let input = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| {
|
||||||
PlatformInput::KeyUp(KeyUpEvent { keystroke })
|
PlatformInput::KeyUp(KeyUpEvent { keystroke })
|
||||||
})?;
|
})?;
|
||||||
let mut func = lock.callbacks.input.take()?;
|
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:
|
// 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
|
// 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(
|
||||||
|
handle: HWND,
|
||||||
wparam: WPARAM,
|
wparam: WPARAM,
|
||||||
lparam: LPARAM,
|
lparam: LPARAM,
|
||||||
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 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 {
|
PlatformInput::KeyDown(KeyDownEvent {
|
||||||
keystroke,
|
keystroke,
|
||||||
is_held: lparam.0 & (0x1 << 30) > 0,
|
is_held: lparam.0 & (0x1 << 30) > 0,
|
||||||
|
@ -401,32 +403,42 @@ fn handle_keydown_msg(
|
||||||
}) else {
|
}) else {
|
||||||
return Some(1);
|
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);
|
return Some(1);
|
||||||
};
|
};
|
||||||
drop(lock);
|
|
||||||
|
|
||||||
let handled = !func(input).propagate;
|
let handled = !func(input).propagate;
|
||||||
|
|
||||||
let mut lock = state_ptr.state.borrow_mut();
|
state_ptr.state.borrow_mut().callbacks.input = Some(func);
|
||||||
lock.callbacks.input = Some(func);
|
|
||||||
|
|
||||||
if handled {
|
if handled {
|
||||||
lock.suppress_next_char_msg = true;
|
|
||||||
Some(0)
|
Some(0)
|
||||||
} else {
|
} else {
|
||||||
|
translate_message(handle, wparam, lparam);
|
||||||
Some(1)
|
Some(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_keyup_msg(
|
fn handle_keyup_msg(
|
||||||
|
handle: HWND,
|
||||||
wparam: WPARAM,
|
wparam: WPARAM,
|
||||||
lparam: LPARAM,
|
lparam: LPARAM,
|
||||||
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 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 })
|
PlatformInput::KeyUp(KeyUpEvent { keystroke })
|
||||||
}) else {
|
}) else {
|
||||||
return Some(1);
|
return Some(1);
|
||||||
|
@ -1213,7 +1225,23 @@ fn handle_input_language_changed(
|
||||||
Some(0)
|
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>(
|
fn handle_key_event<F>(
|
||||||
|
handle: HWND,
|
||||||
wparam: WPARAM,
|
wparam: WPARAM,
|
||||||
lparam: LPARAM,
|
lparam: LPARAM,
|
||||||
state: &mut WindowsWindowState,
|
state: &mut WindowsWindowState,
|
||||||
|
@ -1222,15 +1250,10 @@ fn handle_key_event<F>(
|
||||||
where
|
where
|
||||||
F: FnOnce(Keystroke) -> PlatformInput,
|
F: FnOnce(Keystroke) -> PlatformInput,
|
||||||
{
|
{
|
||||||
state.suppress_next_char_msg = false;
|
|
||||||
let virtual_key = VIRTUAL_KEY(wparam.loword());
|
let virtual_key = VIRTUAL_KEY(wparam.loword());
|
||||||
let mut modifiers = current_modifiers();
|
let mut modifiers = current_modifiers();
|
||||||
|
|
||||||
match virtual_key {
|
match virtual_key {
|
||||||
VK_PROCESSKEY => {
|
|
||||||
// IME composition
|
|
||||||
None
|
|
||||||
}
|
|
||||||
VK_SHIFT | VK_CONTROL | VK_MENU | VK_LWIN | VK_RWIN => {
|
VK_SHIFT | VK_CONTROL | VK_MENU | VK_LWIN | VK_RWIN => {
|
||||||
if state
|
if state
|
||||||
.last_reported_modifiers
|
.last_reported_modifiers
|
||||||
|
@ -1244,6 +1267,11 @@ where
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
vkey => {
|
vkey => {
|
||||||
|
let vkey = if vkey == VK_PROCESSKEY {
|
||||||
|
VIRTUAL_KEY(unsafe { ImmGetVirtualKey(handle) } as u16)
|
||||||
|
} else {
|
||||||
|
vkey
|
||||||
|
};
|
||||||
let keystroke = parse_normal_key(vkey, lparam, modifiers)?;
|
let keystroke = parse_normal_key(vkey, lparam, modifiers)?;
|
||||||
Some(f(keystroke))
|
Some(f(keystroke))
|
||||||
}
|
}
|
||||||
|
@ -1491,12 +1519,7 @@ 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 lock = state_ptr.state.borrow_mut();
|
let mut input_handler = state_ptr.state.borrow_mut().input_handler.take()?;
|
||||||
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)
|
||||||
|
@ -1510,9 +1533,6 @@ 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);
|
||||||
|
|
|
@ -231,9 +231,6 @@ impl WindowsPlatform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// todo(windows)
|
|
||||||
// crate `windows 0.56` reports true as Err
|
|
||||||
TranslateMessage(&msg).as_bool();
|
|
||||||
DispatchMessageW(&msg);
|
DispatchMessageW(&msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,6 @@ 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,
|
||||||
|
|
||||||
|
@ -103,7 +102,6 @@ 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();
|
||||||
|
@ -123,7 +121,6 @@ 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