Skip to content

Commit f0065a3

Browse files
committed
Improve Windows text input
1 parent 68707ff commit f0065a3

File tree

4 files changed

+111
-150
lines changed

4 files changed

+111
-150
lines changed

crates/gpui/src/platform/windows/events.rs

Lines changed: 14 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub(crate) const WM_GPUI_DOCK_MENU_ACTION: u32 = WM_USER + 4;
2626
pub(crate) const WM_GPUI_FORCE_UPDATE_WINDOW: u32 = WM_USER + 5;
2727
pub(crate) const WM_GPUI_KEYBOARD_LAYOUT_CHANGED: u32 = WM_USER + 6;
2828
pub(crate) const WM_GPUI_GPU_DEVICE_LOST: u32 = WM_USER + 7;
29+
pub(crate) const WM_GPUI_KEYDOWN: u32 = WM_USER + 8;
2930

3031
const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
3132
const AUTO_HIDE_TASKBAR_THICKNESS_PX: i32 = 1;
@@ -92,13 +93,10 @@ impl WindowsWindowInner {
9293
}
9394
WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(handle, wparam, lparam),
9495
WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(handle, wparam, lparam),
95-
WM_SYSKEYDOWN => self.handle_syskeydown_msg(handle, wparam, lparam),
96-
WM_SYSKEYUP => self.handle_syskeyup_msg(handle, wparam, lparam),
97-
WM_SYSCOMMAND => self.handle_system_command(wparam),
98-
WM_KEYDOWN => self.handle_keydown_msg(handle, wparam, lparam),
99-
WM_KEYUP => self.handle_keyup_msg(handle, wparam, lparam),
96+
WM_SYSKEYUP => self.handle_syskeyup_msg(wparam, lparam),
97+
WM_KEYUP => self.handle_keyup_msg(wparam, lparam),
98+
WM_GPUI_KEYDOWN => self.handle_keydown_msg(wparam, lparam),
10099
WM_CHAR => self.handle_char_msg(wparam),
101-
WM_DEADCHAR => self.handle_dead_char_msg(wparam),
102100
WM_IME_STARTCOMPOSITION => self.handle_ime_position(handle),
103101
WM_IME_COMPOSITION => self.handle_ime_composition(handle, lparam),
104102
WM_SETCURSOR => self.handle_set_cursor(handle, lparam),
@@ -327,35 +325,9 @@ impl WindowsWindowInner {
327325
Some(0)
328326
}
329327

330-
fn handle_syskeydown_msg(&self, handle: HWND, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
328+
fn handle_syskeyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
331329
let mut lock = self.state.borrow_mut();
332-
let input = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| {
333-
PlatformInput::KeyDown(KeyDownEvent {
334-
keystroke,
335-
is_held: lparam.0 & (0x1 << 30) > 0,
336-
})
337-
})?;
338-
let mut func = lock.callbacks.input.take()?;
339-
drop(lock);
340-
341-
let handled = !func(input).propagate;
342-
343-
let mut lock = self.state.borrow_mut();
344-
lock.callbacks.input = Some(func);
345-
346-
if handled {
347-
lock.system_key_handled = true;
348-
Some(0)
349-
} else {
350-
// we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
351-
// shortcuts.
352-
None
353-
}
354-
}
355-
356-
fn handle_syskeyup_msg(&self, handle: HWND, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
357-
let mut lock = self.state.borrow_mut();
358-
let input = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| {
330+
let input = handle_key_event(wparam, lparam, &mut lock, |keystroke| {
359331
PlatformInput::KeyUp(KeyUpEvent { keystroke })
360332
})?;
361333
let mut func = lock.callbacks.input.take()?;
@@ -369,9 +341,9 @@ impl WindowsWindowInner {
369341

370342
// It's a known bug that you can't trigger `ctrl-shift-0`. See:
371343
// https://superuser.com/questions/1455762/ctrl-shift-number-key-combination-has-stopped-working-for-a-few-numbers
372-
fn handle_keydown_msg(&self, handle: HWND, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
344+
fn handle_keydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
373345
let mut lock = self.state.borrow_mut();
374-
let Some(input) = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| {
346+
let Some(input) = handle_key_event(wparam, lparam, &mut lock, |keystroke| {
375347
PlatformInput::KeyDown(KeyDownEvent {
376348
keystroke,
377349
is_held: lparam.0 & (0x1 << 30) > 0,
@@ -381,15 +353,6 @@ impl WindowsWindowInner {
381353
};
382354
drop(lock);
383355

384-
let is_composing = self
385-
.with_input_handler(|input_handler| input_handler.marked_text_range())
386-
.flatten()
387-
.is_some();
388-
if is_composing {
389-
translate_message(handle, wparam, lparam);
390-
return Some(0);
391-
}
392-
393356
let Some(mut func) = self.state.borrow_mut().callbacks.input.take() else {
394357
return Some(1);
395358
};
@@ -398,17 +361,12 @@ impl WindowsWindowInner {
398361

399362
self.state.borrow_mut().callbacks.input = Some(func);
400363

401-
if handled {
402-
Some(0)
403-
} else {
404-
translate_message(handle, wparam, lparam);
405-
Some(1)
406-
}
364+
if handled { Some(0) } else { Some(1) }
407365
}
408366

409-
fn handle_keyup_msg(&self, handle: HWND, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
367+
fn handle_keyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
410368
let mut lock = self.state.borrow_mut();
411-
let Some(input) = handle_key_event(handle, wparam, lparam, &mut lock, |keystroke| {
369+
let Some(input) = handle_key_event(wparam, lparam, &mut lock, |keystroke| {
412370
PlatformInput::KeyUp(KeyUpEvent { keystroke })
413371
}) else {
414372
return Some(1);
@@ -434,14 +392,6 @@ impl WindowsWindowInner {
434392
Some(0)
435393
}
436394

437-
fn handle_dead_char_msg(&self, wparam: WPARAM) -> Option<isize> {
438-
let ch = char::from_u32(wparam.0 as u32)?.to_string();
439-
self.with_input_handler(|input_handler| {
440-
input_handler.replace_and_mark_text_in_range(None, &ch, None);
441-
});
442-
None
443-
}
444-
445395
fn handle_mouse_down_msg(
446396
&self,
447397
handle: HWND,
@@ -1127,17 +1077,6 @@ impl WindowsWindowInner {
11271077
Some(0)
11281078
}
11291079

1130-
fn handle_system_command(&self, wparam: WPARAM) -> Option<isize> {
1131-
if wparam.0 == SC_KEYMENU as usize {
1132-
let mut lock = self.state.borrow_mut();
1133-
if lock.system_key_handled {
1134-
lock.system_key_handled = false;
1135-
return Some(0);
1136-
}
1137-
}
1138-
None
1139-
}
1140-
11411080
fn handle_system_theme_changed(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
11421081
// lParam is a pointer to a string that indicates the area containing the system parameter
11431082
// that was changed.
@@ -1281,23 +1220,7 @@ impl WindowsWindowInner {
12811220
}
12821221
}
12831222

1284-
#[inline]
1285-
fn translate_message(handle: HWND, wparam: WPARAM, lparam: LPARAM) {
1286-
let msg = MSG {
1287-
hwnd: handle,
1288-
message: WM_KEYDOWN,
1289-
wParam: wparam,
1290-
lParam: lparam,
1291-
// It seems like leaving the following two parameters empty doesn't break key events, they still work as expected.
1292-
// But if any bugs pop up after this PR, this is probably the place to look first.
1293-
time: 0,
1294-
pt: POINT::default(),
1295-
};
1296-
unsafe { TranslateMessage(&msg).ok().log_err() };
1297-
}
1298-
12991223
fn handle_key_event<F>(
1300-
handle: HWND,
13011224
wparam: WPARAM,
13021225
lparam: LPARAM,
13031226
state: &mut WindowsWindowState,
@@ -1323,10 +1246,7 @@ where
13231246
capslock: current_capslock(),
13241247
}))
13251248
}
1326-
VK_PACKET => {
1327-
translate_message(handle, wparam, lparam);
1328-
None
1329-
}
1249+
VK_PACKET => None,
13301250
VK_CAPITAL => {
13311251
let capslock = current_capslock();
13321252
if state
@@ -1342,11 +1262,6 @@ where
13421262
}))
13431263
}
13441264
vkey => {
1345-
let vkey = if vkey == VK_PROCESSKEY {
1346-
VIRTUAL_KEY(unsafe { ImmGetVirtualKey(handle) } as u16)
1347-
} else {
1348-
vkey
1349-
};
13501265
let keystroke = parse_normal_key(vkey, lparam, modifiers)?;
13511266
Some(f(keystroke))
13521267
}
@@ -1460,25 +1375,11 @@ fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
14601375
unsafe { GetKeyState(vkey.0 as i32) < 0 }
14611376
}
14621377

1463-
fn keyboard_uses_altgr() -> bool {
1464-
use crate::platform::windows::keyboard::WindowsKeyboardLayout;
1465-
WindowsKeyboardLayout::new()
1466-
.map(|layout| layout.uses_altgr())
1467-
.unwrap_or(false)
1468-
}
1469-
14701378
#[inline]
14711379
pub(crate) fn current_modifiers() -> Modifiers {
1472-
let lmenu_pressed = is_virtual_key_pressed(VK_LMENU);
1473-
let rmenu_pressed = is_virtual_key_pressed(VK_RMENU);
1474-
let lcontrol_pressed = is_virtual_key_pressed(VK_LCONTROL);
1475-
1476-
// Only treat right Alt + left Ctrl as AltGr on keyboards that actually use it
1477-
let altgr = keyboard_uses_altgr() && rmenu_pressed && lcontrol_pressed;
1478-
14791380
Modifiers {
1480-
control: is_virtual_key_pressed(VK_CONTROL) && !altgr,
1481-
alt: (lmenu_pressed || rmenu_pressed) && !altgr,
1381+
control: is_virtual_key_pressed(VK_CONTROL),
1382+
alt: is_virtual_key_pressed(VK_MENU),
14821383
shift: is_virtual_key_pressed(VK_SHIFT),
14831384
platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN),
14841385
function: false,

crates/gpui/src/platform/windows/keyboard.rs

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -110,38 +110,6 @@ impl WindowsKeyboardLayout {
110110
name: "unknown".to_string(),
111111
}
112112
}
113-
114-
pub(crate) fn uses_altgr(&self) -> bool {
115-
// Check if this is a known AltGr layout by examining the layout ID
116-
// The layout ID is a hex string like "00000409" (US) or "00000407" (German)
117-
// Extract the language ID (last 4 bytes)
118-
let id_bytes = self.id.as_bytes();
119-
if id_bytes.len() >= 4 {
120-
let lang_id = &id_bytes[id_bytes.len() - 4..];
121-
// List of keyboard layouts that use AltGr (non-exhaustive)
122-
matches!(
123-
lang_id,
124-
b"0407" | // German
125-
b"040C" | // French
126-
b"040A" | // Spanish
127-
b"0415" | // Polish
128-
b"0413" | // Dutch
129-
b"0816" | // Portuguese
130-
b"041D" | // Swedish
131-
b"0414" | // Norwegian
132-
b"040B" | // Finnish
133-
b"041F" | // Turkish
134-
b"0419" | // Russian
135-
b"0405" | // Czech
136-
b"040E" | // Hungarian
137-
b"0424" | // Slovenian
138-
b"041B" | // Slovak
139-
b"0418" // Romanian
140-
)
141-
} else {
142-
false
143-
}
144-
}
145113
}
146114

147115
impl WindowsKeyboardMapper {
@@ -259,7 +227,7 @@ pub(crate) fn generate_key_char(
259227
}
260228

261229
let mut buffer = [0; 8];
262-
let len = unsafe { ToUnicode(vkey.0 as u32, scan_code, Some(&state), &mut buffer, 1 << 2) };
230+
let len = unsafe { ToUnicode(vkey.0 as u32, scan_code, Some(&state), &mut buffer, 0x5) };
263231

264232
match len {
265233
len if len > 0 => String::from_utf16(&buffer[..len as usize])

crates/gpui/src/platform/windows/platform.rs

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,98 @@ impl WindowsPlatform {
272272
}
273273
}
274274

275+
fn translate_accelerator(msg: &MSG) -> Option<()> {
276+
if msg.message != WM_KEYDOWN && msg.message != WM_SYSKEYDOWN {
277+
return None;
278+
}
279+
280+
let vkey = VIRTUAL_KEY(msg.wParam.0 as u16);
281+
let scan_code = ((msg.lParam.0 >> 16) & 0xFF) as u32;
282+
283+
let mut keyboard_state = [0u8; 256];
284+
unsafe {
285+
GetKeyboardState(&mut keyboard_state).ok()?;
286+
}
287+
288+
let mut buffer_c = [0u16; 8];
289+
let result_c = unsafe {
290+
ToUnicode(
291+
vkey.0 as u32,
292+
scan_code,
293+
Some(&keyboard_state),
294+
&mut buffer_c,
295+
0x4,
296+
)
297+
};
298+
299+
if result_c < 0 {
300+
return None;
301+
}
302+
303+
let c = String::from_utf16_lossy(&buffer_c[..result_c as usize]);
304+
305+
let ctrl_down = (keyboard_state[VK_CONTROL.0 as usize] & 0x80) != 0;
306+
let alt_down = (keyboard_state[VK_MENU.0 as usize] & 0x80) != 0;
307+
let win_down = (keyboard_state[VK_LWIN.0 as usize] & 0x80) != 0
308+
|| (keyboard_state[VK_RWIN.0 as usize] & 0x80) != 0;
309+
310+
let has_modifiers = ctrl_down || alt_down || win_down;
311+
312+
if !has_modifiers {
313+
return dispatch_accelerator(msg);
314+
}
315+
316+
if c.chars().next().map_or(true, |ch| ch.is_control()) {
317+
return dispatch_accelerator(msg);
318+
}
319+
320+
let mut state_no_modifiers = keyboard_state;
321+
state_no_modifiers[VK_CONTROL.0 as usize] = 0;
322+
state_no_modifiers[VK_LCONTROL.0 as usize] = 0;
323+
state_no_modifiers[VK_RCONTROL.0 as usize] = 0;
324+
state_no_modifiers[VK_MENU.0 as usize] = 0;
325+
state_no_modifiers[VK_LMENU.0 as usize] = 0;
326+
state_no_modifiers[VK_RMENU.0 as usize] = 0;
327+
state_no_modifiers[VK_LWIN.0 as usize] = 0;
328+
state_no_modifiers[VK_RWIN.0 as usize] = 0;
329+
330+
let mut buffer_c_no_modifiers = [0u16; 8];
331+
let result_c_no_modifers = unsafe {
332+
ToUnicode(
333+
vkey.0 as u32,
334+
scan_code,
335+
Some(&state_no_modifiers),
336+
&mut buffer_c_no_modifiers,
337+
0x4,
338+
)
339+
};
340+
341+
if result_c_no_modifers <= 0 {
342+
return dispatch_accelerator(msg);
343+
}
344+
345+
let c_no_modifiers =
346+
String::from_utf16_lossy(&buffer_c_no_modifiers[..result_c_no_modifers as usize]);
347+
348+
if c != c_no_modifiers {
349+
return None;
350+
}
351+
352+
dispatch_accelerator(msg)
353+
}
354+
355+
fn dispatch_accelerator(msg: &MSG) -> Option<()> {
356+
let result = unsafe {
357+
SendMessageW(
358+
msg.hwnd,
359+
WM_GPUI_KEYDOWN,
360+
Some(msg.wParam),
361+
Some(msg.lParam),
362+
)
363+
};
364+
(result == LRESULT(0)).then_some(())
365+
}
366+
275367
impl Platform for WindowsPlatform {
276368
fn background_executor(&self) -> BackgroundExecutor {
277369
self.background_executor.clone()
@@ -312,7 +404,10 @@ impl Platform for WindowsPlatform {
312404
let mut msg = MSG::default();
313405
unsafe {
314406
while GetMessageW(&mut msg, None, 0, 0).as_bool() {
315-
DispatchMessageW(&msg);
407+
if translate_accelerator(&msg).is_none() {
408+
_ = TranslateMessage(&msg);
409+
DispatchMessageW(&msg);
410+
}
316411
}
317412
}
318413

0 commit comments

Comments
 (0)