windows: Fix Zed/GPUI misinterpreting keycodes + Refactor (#12814)

agamcsama , Marshall Bowers , and Mikayla Maki created

Release Notes:

- Fixed Zed/GPUI misinterpreting keycodes on non-US keyboards
([#12811](https://github.com/zed-industries/zed/issues/12811)).

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>

Change summary

crates/gpui/src/platform/windows/events.rs | 207 +++++++++--------------
1 file changed, 82 insertions(+), 125 deletions(-)

Detailed changes

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

@@ -1082,37 +1082,30 @@ fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
     }
 
     let vk_code = wparam.loword();
-    let basic_key = basic_vkcode_to_string(vk_code, modifiers);
-    if basic_key.is_some() {
-        return basic_key;
-    }
 
     let key = match VIRTUAL_KEY(vk_code) {
-        VK_BACK => Some("backspace"),
-        VK_RETURN => Some("enter"),
-        VK_TAB => Some("tab"),
-        VK_UP => Some("up"),
-        VK_DOWN => Some("down"),
-        VK_RIGHT => Some("right"),
-        VK_LEFT => Some("left"),
-        VK_HOME => Some("home"),
-        VK_END => Some("end"),
-        VK_PRIOR => Some("pageup"),
-        VK_NEXT => Some("pagedown"),
-        VK_ESCAPE => Some("escape"),
-        VK_INSERT => Some("insert"),
-        _ => None,
-    };
-
-    if let Some(key) = key {
-        Some(Keystroke {
-            modifiers,
-            key: key.to_string(),
-            ime_key: None,
-        })
-    } else {
-        None
+        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_ESCAPE => "escape",
+        VK_INSERT => "insert",
+        _ => return basic_vkcode_to_string(vk_code, modifiers),
     }
+    .to_owned();
+
+    Some(Keystroke {
+        modifiers,
+        key: key,
+        ime_key: None,
+    })
 }
 
 enum KeystrokeOrModifier {
@@ -1125,67 +1118,62 @@ fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option<KeystrokeOrModifier> {
 
     let modifiers = current_modifiers();
 
-    if is_modifier(VIRTUAL_KEY(vk_code)) {
-        return Some(KeystrokeOrModifier::Modifier(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_ESCAPE => "escape",
+        VK_INSERT => "insert",
+        VK_DELETE => "delete",
+        _ => {
+            if is_modifier(VIRTUAL_KEY(vk_code)) {
+                return Some(KeystrokeOrModifier::Modifier(modifiers));
+            }
 
-    if modifiers.control || modifiers.alt {
-        let basic_key = basic_vkcode_to_string(vk_code, modifiers);
-        if let Some(basic_key) = basic_key {
-            return Some(KeystrokeOrModifier::Keystroke(basic_key));
-        }
-    }
+            if modifiers.control || modifiers.alt {
+                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),
-            ime_key: None,
-        }));
+            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),
+                    ime_key: None,
+                }));
+            };
+            return None;
+        }
     }
+    .to_owned();
 
-    let key = match VIRTUAL_KEY(vk_code) {
-        VK_BACK => Some("backspace"),
-        VK_RETURN => Some("enter"),
-        VK_TAB => Some("tab"),
-        VK_UP => Some("up"),
-        VK_DOWN => Some("down"),
-        VK_RIGHT => Some("right"),
-        VK_LEFT => Some("left"),
-        VK_HOME => Some("home"),
-        VK_END => Some("end"),
-        VK_PRIOR => Some("pageup"),
-        VK_NEXT => Some("pagedown"),
-        VK_ESCAPE => Some("escape"),
-        VK_INSERT => Some("insert"),
-        VK_DELETE => Some("delete"),
-        _ => None,
-    };
-
-    if let Some(key) = key {
-        Some(KeystrokeOrModifier::Keystroke(Keystroke {
-            modifiers,
-            key: key.to_string(),
-            ime_key: None,
-        }))
-    } else {
-        None
-    }
+    Some(KeystrokeOrModifier::Keystroke(Keystroke {
+        modifiers,
+        key: key,
+        ime_key: None,
+    }))
 }
 
 fn parse_char_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
-    let src = [wparam.0 as u16];
-    let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
-        return None;
-    };
+    let first_char = char::from_u32((wparam.0 as u16).into())?;
     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_lowercase().to_string() == first_char.to_uppercase().to_string() {
+        if first_char.to_ascii_uppercase() == first_char.to_ascii_lowercase() {
             modifiers.shift = false;
         }
         let key = match first_char {
@@ -1262,56 +1250,25 @@ fn parse_ime_compostion_result(handle: HWND) -> Option<String> {
 }
 
 fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
-    match code {
-        // VK_0 - VK_9
-        48..=57 => Some(Keystroke {
-            modifiers,
-            key: format!("{}", code - VK_0.0),
-            ime_key: None,
-        }),
-        // VK_A - VK_Z
-        65..=90 => Some(Keystroke {
-            modifiers,
-            key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
-            ime_key: None,
-        }),
-        // VK_F1 - VK_F24
-        112..=135 => Some(Keystroke {
-            modifiers,
-            key: format!("f{}", code - VK_F1.0 + 1),
-            ime_key: None,
-        }),
-        // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
-        _ => {
-            if let Some(key) = oemkey_vkcode_to_string(code) {
-                Some(Keystroke {
-                    modifiers,
-                    key,
-                    ime_key: None,
-                })
-            } else {
-                None
-            }
-        }
-    }
-}
+    let mapped_code = unsafe { MapVirtualKeyW(code as u32, MAPVK_VK_TO_CHAR) };
 
-fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
-    match code {
-        186 => Some(";".to_string()), // VK_OEM_1
-        187 => Some("=".to_string()), // VK_OEM_PLUS
-        188 => Some(",".to_string()), // VK_OEM_COMMA
-        189 => Some("-".to_string()), // VK_OEM_MINUS
-        190 => Some(".".to_string()), // VK_OEM_PERIOD
-        // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
-        191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
-        192 => Some("`".to_string()),       // VK_OEM_3
-        219 => Some("[".to_string()),       // VK_OEM_4
-        220 => Some("\\".to_string()),      // VK_OEM_5
-        221 => Some("]".to_string()),       // VK_OEM_6
-        222 => Some("'".to_string()),       // VK_OEM_7
-        _ => None,
-    }
+    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,
+        ime_key: None,
+    })
 }
 
 #[inline]