Additional Windows keyboard input fixes (#42294)

John Tur created

- Enable Alt+Numpad input
- For this to be effective, the default keybindings for Alt+{Number}
will need to be unbound. This won't be needed once we gain the ability
to differentiate numpad digit keys from alphanumeric digit keys.
  - Fixes https://github.com/zed-industries/zed/issues/40699
- Fix a number of edge cases with dead keys

Release Notes:

- N/A

Change summary

crates/gpui/src/platform/windows/events.rs | 55 +++++++++++------------
1 file changed, 26 insertions(+), 29 deletions(-)

Detailed changes

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

@@ -1332,21 +1332,13 @@ fn parse_normal_key(
     lparam: LPARAM,
     mut modifiers: Modifiers,
 ) -> Option<(Keystroke, bool)> {
-    let mut key_char = None;
+    let (key_char, prefer_character_input) = process_key(vkey, lparam.hiword());
+
     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)
     })?;
 
-    let prefer_character_input = should_prefer_character_input(vkey, lparam.hiword() & 0xFF);
-
     Some((
         Keystroke {
             modifiers,
@@ -1357,11 +1349,11 @@ fn parse_normal_key(
     ))
 }
 
-fn should_prefer_character_input(vkey: VIRTUAL_KEY, scan_code: u16) -> bool {
+fn process_key(vkey: VIRTUAL_KEY, scan_code: u16) -> (Option<String>, bool) {
     let mut keyboard_state = [0u8; 256];
     unsafe {
         if GetKeyboardState(&mut keyboard_state).is_err() {
-            return false;
+            return (None, false);
         }
     }
 
@@ -1372,21 +1364,25 @@ fn should_prefer_character_input(vkey: VIRTUAL_KEY, scan_code: u16) -> bool {
             scan_code as u32,
             Some(&keyboard_state),
             &mut buffer_c,
-            0x5,
+            0x4,
         )
     };
+
+    if result_c == 0 {
+        return (None, false);
+    }
+
+    let c = &buffer_c[..result_c.unsigned_abs() as usize];
+    let key_char = String::from_utf16(c)
+        .ok()
+        .filter(|s| !s.is_empty() && !s.chars().next().unwrap().is_control());
+
     if result_c < 0 {
-        return false;
+        return (key_char, true);
     }
 
-    let c = &buffer_c[..result_c as usize];
-    if char::decode_utf16(c.iter().copied())
-        .next()
-        .and_then(|ch| ch.ok())
-        .map(|ch| ch.is_control())
-        .unwrap_or(true)
-    {
-        return false;
+    if key_char.is_none() {
+        return (None, false);
     }
 
     // Workaround for some bug that makes the compiler think keyboard_state is still zeroed out
@@ -1395,9 +1391,10 @@ fn should_prefer_character_input(vkey: VIRTUAL_KEY, scan_code: u16) -> bool {
     let alt_down = (keyboard_state[VK_MENU.0 as usize] & 0x80) != 0;
     let win_down = (keyboard_state[VK_LWIN.0 as usize] & 0x80) != 0
         || (keyboard_state[VK_RWIN.0 as usize] & 0x80) != 0;
+
     let has_modifiers = ctrl_down || alt_down || win_down;
     if !has_modifiers {
-        return false;
+        return (key_char, false);
     }
 
     let mut state_no_modifiers = keyboard_state;
@@ -1417,15 +1414,15 @@ fn should_prefer_character_input(vkey: VIRTUAL_KEY, scan_code: u16) -> bool {
             scan_code as u32,
             Some(&state_no_modifiers),
             &mut buffer_c_no_modifiers,
-            0x5,
+            0x4,
         )
     };
-    if result_c_no_modifiers <= 0 {
-        return false;
-    }
 
-    let c_no_modifiers = &buffer_c_no_modifiers[..result_c_no_modifiers as usize];
-    c != c_no_modifiers
+    let c_no_modifiers = &buffer_c_no_modifiers[..result_c_no_modifiers.unsigned_abs() as usize];
+    (
+        key_char,
+        result_c != result_c_no_modifiers || c != c_no_modifiers,
+    )
 }
 
 fn parse_ime_composition_string(ctx: HIMC, comp_type: IME_COMPOSITION_STRING) -> Option<String> {