From 36cbfbfb9493f6fa2ff1c45fe35feca86af8cf28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Thu, 14 Mar 2024 03:10:22 +0800 Subject: [PATCH] windows: Better keyboard input support (#9180) ### Description Currently, there are some issues with input handling on Windows: #### 1. Direct crash when encountering IME input. https://github.com/zed-industries/zed/assets/14981363/598f7272-1948-4a42-99c5-2ef7b9162a1e #### 2. Handling messages every 1/60 seconds in the main thread. Despite being named "immediate_handle," it's not exactly immediate. ```rust // actually halt here let wait_result = unsafe { DCompositionWaitForCompositorClock(Some(&[self.inner.event]), INFINITE) }; // compositor clock ticked so we should draw a frame if wait_result == 1 { unsafe { invalidate_thread_windows(GetCurrentThreadId()) }; while unsafe { PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE) }.as_bool() ``` #### 3. According to Windows recommendations, character input should be obtained using `WM_CHAR` instead of `WM_KEYDOWN`. Additionally, there are problems with the handling within `WM_CHAR`. ```rust fn handle_char_msg(&self, wparam: WPARAM) -> LRESULT { let mut callbacks = self.callbacks.borrow_mut(); if let Some(callback) = callbacks.input.as_mut() { let modifiers = self.current_modifiers(); let msg_char = wparam.0 as u8 as char; // these are u16 chars, cant treat them as u8 ``` And, we don't handle `WM_SYSKEYDOWN` properly, which leads to `Alt + F4` not working. Release Notes: - N/A --- Cargo.toml | 28 +- crates/gpui/src/platform/windows/platform.rs | 125 ++---- crates/gpui/src/platform/windows/window.rs | 430 +++++++++++-------- 3 files changed, 321 insertions(+), 262 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3e1f3415bb..62b33df587 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -323,7 +323,13 @@ url = "2.2" uuid = { version = "1.1.2", features = ["v4"] } wasmparser = "0.121" wasm-encoder = "0.41" -wasmtime = { version = "18.0", default-features = false, features = ["async", "demangle", "runtime", "cranelift", "component-model"] } +wasmtime = { version = "18.0", default-features = false, features = [ + "async", + "demangle", + "runtime", + "cranelift", + "component-model", +] } wasmtime-wasi = "18.0" which = "6.0.0" wit-component = "0.20" @@ -334,23 +340,21 @@ version = "0.53.0" features = [ "implement", "Wdk_System_SystemServices", - "Win32_Graphics_Gdi", "Win32_Graphics_DirectComposition", - "Win32_UI_Controls", - "Win32_UI_WindowsAndMessaging", - "Win32_UI_Input_KeyboardAndMouse", - "Win32_UI_Shell", - "Win32_System_Com", - "Win32_System_SystemInformation", - "Win32_System_SystemServices", - "Win32_System_Time", + "Win32_Graphics_Gdi", "Win32_Security", "Win32_System_Com", "Win32_System_Com_StructuredStorage", - "Win32_System_Threading", "Win32_System_DataExchange", "Win32_System_Ole", - "Win32_System_Com", + "Win32_System_SystemInformation", + "Win32_System_SystemServices", + "Win32_System_Time", + "Win32_System_Threading", + "Win32_UI_Controls", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", ] [patch.crates-io] diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 34ba80d89b..f302447453 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -3,7 +3,6 @@ use std::{ cell::{Cell, RefCell}, - collections::HashSet, ffi::{c_uint, c_void, OsString}, os::windows::ffi::{OsStrExt, OsStringExt}, path::{Path, PathBuf}, @@ -17,19 +16,23 @@ use async_task::Runnable; use copypasta::{ClipboardContext, ClipboardProvider}; use futures::channel::oneshot::{self, Receiver}; use itertools::Itertools; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; +use smallvec::SmallVec; use time::UtcOffset; use util::{ResultExt, SemanticVersion}; use windows::{ core::{IUnknown, HRESULT, HSTRING, PCWSTR, PWSTR}, Wdk::System::SystemServices::RtlGetVersion, Win32::{ - Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE}, - Graphics::DirectComposition::DCompositionWaitForCompositorClock, + Foundation::{CloseHandle, HANDLE, HWND}, + Graphics::{ + DirectComposition::DCompositionWaitForCompositorClock, + Gdi::{RedrawWindow, HRGN, RDW_INVALIDATE, RDW_UPDATENOW}, + }, System::{ Com::{CoCreateInstance, CreateBindCtx, CLSCTX_ALL}, Ole::{OleInitialize, OleUninitialize}, - Threading::{CreateEventW, GetCurrentThreadId, INFINITE}, + Threading::{CreateEventW, INFINITE}, Time::{GetTimeZoneInformation, TIME_ZONE_ID_INVALID}, }, UI::{ @@ -40,21 +43,21 @@ use windows::{ FOS_ALLOWMULTISELECT, FOS_FILEMUSTEXIST, FOS_PICKFOLDERS, SIGDN_FILESYSPATH, }, WindowsAndMessaging::{ - DispatchMessageW, EnumThreadWindows, LoadImageW, PeekMessageW, PostQuitMessage, - SetCursor, SystemParametersInfoW, TranslateMessage, HCURSOR, IDC_ARROW, IDC_CROSS, - IDC_HAND, IDC_IBEAM, IDC_NO, IDC_SIZENS, IDC_SIZEWE, IMAGE_CURSOR, LR_DEFAULTSIZE, - LR_SHARED, MSG, PM_REMOVE, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, - SW_SHOWDEFAULT, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WM_QUIT, WM_SETTINGCHANGE, + DispatchMessageW, LoadImageW, PeekMessageW, PostQuitMessage, SetCursor, + SystemParametersInfoW, TranslateMessage, HCURSOR, IDC_ARROW, IDC_CROSS, IDC_HAND, + IDC_IBEAM, IDC_NO, IDC_SIZENS, IDC_SIZEWE, IMAGE_CURSOR, LR_DEFAULTSIZE, LR_SHARED, + MSG, PM_REMOVE, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SW_SHOWDEFAULT, + SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WM_QUIT, WM_SETTINGCHANGE, }, }, }, }; use crate::{ - try_get_window_inner, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, - ForegroundExecutor, Keymap, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, - PlatformTextSystem, PlatformWindow, Task, WindowAppearance, WindowOptions, WindowParams, - WindowsDispatcher, WindowsDisplay, WindowsTextSystem, WindowsWindow, + Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, + Keymap, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, + PlatformWindow, Task, WindowAppearance, WindowParams, WindowsDispatcher, WindowsDisplay, + WindowsTextSystem, WindowsWindow, }; pub(crate) struct WindowsPlatform { @@ -72,15 +75,13 @@ pub(crate) struct WindowsPlatformSystemSettings { pub(crate) wheel_scroll_lines: u32, } -type WindowHandleValues = HashSet; - pub(crate) struct WindowsPlatformInner { background_executor: BackgroundExecutor, pub(crate) foreground_executor: ForegroundExecutor, main_receiver: flume::Receiver, text_system: Arc, callbacks: Mutex, - pub(crate) window_handle_values: RefCell, + pub raw_window_handles: RwLock>, pub(crate) event: HANDLE, pub(crate) settings: RefCell, } @@ -167,7 +168,7 @@ impl WindowsPlatform { let foreground_executor = ForegroundExecutor::new(dispatcher); let text_system = Arc::new(WindowsTextSystem::new()); let callbacks = Mutex::new(Callbacks::default()); - let window_handle_values = RefCell::new(HashSet::new()); + let raw_window_handles = RwLock::new(SmallVec::new()); let settings = RefCell::new(WindowsPlatformSystemSettings::new()); let inner = Rc::new(WindowsPlatformInner { background_executor, @@ -175,64 +176,31 @@ impl WindowsPlatform { main_receiver, text_system, callbacks, - window_handle_values, + raw_window_handles, event, settings, }); Self { inner } } - /// runs message handlers that should be processed before dispatching to prevent translating unnecessary messages - /// returns true if message is handled and should not dispatch - fn run_immediate_msg_handlers(&self, msg: &MSG) -> bool { - if msg.message == WM_SETTINGCHANGE { - self.inner.settings.borrow_mut().update_all(); - return true; - } - - if !self - .inner - .window_handle_values - .borrow() - .contains(&msg.hwnd.0) - { - return false; - } - - if let Some(inner) = try_get_window_inner(msg.hwnd) { - inner.handle_immediate_msg(msg.message, msg.wParam, msg.lParam) - } else { - false - } - } - fn run_foreground_tasks(&self) { for runnable in self.inner.main_receiver.drain() { runnable.run(); } } -} -unsafe extern "system" fn invalidate_window_callback(hwnd: HWND, lparam: LPARAM) -> BOOL { - let window_handle_values = unsafe { &*(lparam.0 as *const WindowHandleValues) }; - if !window_handle_values.contains(&hwnd.0) { - return TRUE; + fn redraw_all(&self) { + for handle in self.inner.raw_window_handles.read().iter() { + unsafe { + RedrawWindow( + *handle, + None, + HRGN::default(), + RDW_INVALIDATE | RDW_UPDATENOW, + ); + } + } } - if let Some(inner) = try_get_window_inner(hwnd) { - inner.invalidate_client_area(); - } - TRUE -} - -/// invalidates all windows belonging to a thread causing a paint message to be scheduled -fn invalidate_thread_windows(win32_thread_id: u32, window_handle_values: &WindowHandleValues) { - unsafe { - EnumThreadWindows( - win32_thread_id, - Some(invalidate_window_callback), - LPARAM(window_handle_values as *const _ as isize), - ) - }; } impl Platform for WindowsPlatform { @@ -250,35 +218,34 @@ impl Platform for WindowsPlatform { fn run(&self, on_finish_launching: Box) { on_finish_launching(); + let dispatch_event = self.inner.event; + 'a: loop { let mut msg = MSG::default(); // will be 0 if woken up by self.inner.event or 1 if the compositor clock ticked // SEE: https://learn.microsoft.com/en-us/windows/win32/directcomp/compositor-clock/compositor-clock let wait_result = - unsafe { DCompositionWaitForCompositorClock(Some(&[self.inner.event]), INFINITE) }; + unsafe { DCompositionWaitForCompositorClock(Some(&[dispatch_event]), INFINITE) }; // compositor clock ticked so we should draw a frame if wait_result == 1 { + self.redraw_all(); unsafe { - invalidate_thread_windows( - GetCurrentThreadId(), - &self.inner.window_handle_values.borrow(), - ) - }; + let mut msg = MSG::default(); - while unsafe { PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE) }.as_bool() - { - if msg.message == WM_QUIT { - break 'a; - } - - if !self.run_immediate_msg_handlers(&msg) { - unsafe { TranslateMessage(&msg) }; - unsafe { DispatchMessageW(&msg) }; + while PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE).as_bool() { + if msg.message == WM_QUIT { + break 'a; + } + if msg.message == WM_SETTINGCHANGE { + self.inner.settings.borrow_mut().update_all(); + continue; + } + TranslateMessage(&msg); + DispatchMessageW(&msg); } } } - self.run_foreground_tasks(); } diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index b159a29ca2..42b07a678a 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -40,9 +40,9 @@ use windows::{ TD_INFORMATION_ICON, TD_WARNING_ICON, }, Input::KeyboardAndMouse::{ - GetKeyState, VIRTUAL_KEY, VK_BACK, VK_CONTROL, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, - VK_F24, VK_HOME, VK_INSERT, VK_LEFT, VK_LWIN, VK_MENU, VK_NEXT, VK_PRIOR, - VK_RETURN, VK_RIGHT, VK_RWIN, VK_SHIFT, VK_SPACE, VK_TAB, VK_UP, + GetKeyState, VIRTUAL_KEY, VK_0, VK_A, VK_BACK, VK_CONTROL, VK_DOWN, VK_END, + VK_ESCAPE, VK_F1, VK_F24, VK_HOME, VK_INSERT, VK_LEFT, VK_LWIN, VK_MENU, VK_NEXT, + VK_PRIOR, VK_RETURN, VK_RIGHT, VK_RWIN, VK_SHIFT, VK_TAB, VK_UP, }, Shell::{DragQueryFileW, HDROP}, WindowsAndMessaging::{ @@ -52,9 +52,9 @@ use windows::{ WINDOW_EX_STYLE, WINDOW_LONG_PTR_INDEX, WM_CHAR, WM_CLOSE, WM_DESTROY, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_MOVE, WM_NCCREATE, WM_NCDESTROY, - WM_PAINT, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, - WM_SYSKEYUP, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_OVERLAPPEDWINDOW, - WS_VISIBLE, XBUTTON1, XBUTTON2, + WM_PAINT, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SIZE, WM_SYSKEYDOWN, WM_SYSKEYUP, + WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_OVERLAPPEDWINDOW, WS_VISIBLE, XBUTTON1, + XBUTTON2, }, }, }, @@ -68,25 +68,6 @@ use crate::{ WindowAppearance, WindowParams, WindowsDisplay, WindowsPlatformInner, }; -#[derive(PartialEq)] -pub(crate) enum CallbackResult { - /// handled by system or user callback - Handled { - /// `true` if user callback handled event - by_callback: bool, - }, - Unhandled, -} - -impl CallbackResult { - pub fn is_handled(&self) -> bool { - match self { - Self::Handled { by_callback: _ } => true, - _ => false, - } - } -} - pub(crate) struct WindowsWindowInner { hwnd: HWND, origin: Cell>, @@ -182,15 +163,6 @@ impl WindowsWindowInner { unsafe { InvalidateRect(self.hwnd, None, FALSE) }; } - /// returns true if message is handled and should not dispatch - pub(crate) fn handle_immediate_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> bool { - match msg { - WM_KEYDOWN | WM_SYSKEYDOWN => self.handle_keydown_msg(wparam).is_handled(), - WM_KEYUP | WM_SYSKEYUP => self.handle_keyup_msg(wparam).is_handled(), - _ => false, - } - } - fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { log::debug!("msg: {msg}, wparam: {}, lparam: {}", wparam.0, lparam.0); match msg { @@ -234,9 +206,11 @@ impl WindowsWindowInner { } WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(wparam, lparam), WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(wparam, lparam), - WM_CHAR | WM_SYSCHAR => self.handle_char_msg(wparam), - // These events are handled by the immediate handler - WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP => LRESULT(0), + WM_SYSKEYDOWN => self.handle_syskeydown_msg(msg, wparam, lparam), + WM_SYSKEYUP => self.handle_syskeyup_msg(msg, wparam, lparam), + WM_KEYDOWN => self.handle_keydown_msg(msg, wparam, lparam), + WM_KEYUP => self.handle_keyup_msg(msg, wparam), + WM_CHAR => self.handle_char_msg(msg, wparam, lparam), _ => unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }, } } @@ -302,9 +276,15 @@ impl WindowsWindowInner { if let Some(callback) = callbacks.close.take() { callback() } - let mut window_handles = self.platform_inner.window_handle_values.borrow_mut(); - window_handles.remove(&self.hwnd.0); - if window_handles.is_empty() { + let index = self + .platform_inner + .raw_window_handles + .read() + .iter() + .position(|handle| *handle == self.hwnd) + .unwrap(); + self.platform_inner.raw_window_handles.write().remove(index); + if self.platform_inner.raw_window_handles.read().is_empty() { self.platform_inner .foreground_executor .spawn(async { @@ -345,155 +325,210 @@ impl WindowsWindowInner { LRESULT(1) } - fn parse_key_msg_keystroke(&self, wparam: WPARAM) -> Option { + fn parse_syskeydown_msg_keystroke(&self, wparam: WPARAM) -> Option { + let modifiers = self.current_modifiers(); + if !modifiers.alt { + // on Windows, F10 can trigger this event, not just the alt key + // and we just don't care about F10 + return None; + } + let vk_code = wparam.loword(); - - // 0-9 https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes - if vk_code >= 0x30 && vk_code <= 0x39 { - let modifiers = self.current_modifiers(); - - if modifiers.shift { - return None; - } - - let digit_char = (b'0' + ((vk_code - 0x30) as u8)) as char; - return Some(Keystroke { - modifiers, - key: digit_char.to_string(), - ime_key: Some(digit_char.to_string()), - }); - } - - // A-Z https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes - if vk_code >= 0x41 && vk_code <= 0x5A { - let offset = (vk_code - 0x41) as u8; - let alpha_char = (b'a' + offset) as char; - let alpha_char_upper = (b'A' + offset) as char; - let modifiers = self.current_modifiers(); - return Some(Keystroke { - modifiers, - key: alpha_char.to_string(), - ime_key: Some(if modifiers.shift { - alpha_char_upper.to_string() - } else { - alpha_char.to_string() - }), - }); - } - - if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 { - let offset = vk_code - VK_F1.0; - return Some(Keystroke { - modifiers: self.current_modifiers(), - key: format!("f{}", offset + 1), - ime_key: None, - }); + 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_SPACE => Some(("space", Some(" "))), - VK_TAB => Some(("tab", Some("\t"))), - VK_BACK => Some(("backspace", None)), - VK_RETURN => Some(("enter", None)), - VK_UP => Some(("up", None)), - VK_DOWN => Some(("down", None)), - VK_RIGHT => Some(("right", None)), - VK_LEFT => Some(("left", None)), - VK_HOME => Some(("home", None)), - VK_END => Some(("end", None)), - VK_PRIOR => Some(("pageup", None)), - VK_NEXT => Some(("pagedown", None)), - VK_ESCAPE => Some(("escape", None)), - VK_INSERT => Some(("insert", None)), + 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, ime_key)) = key { + if let Some(key) = key { Some(Keystroke { - modifiers: self.current_modifiers(), + modifiers, key: key.to_string(), - ime_key: ime_key.map(|k| k.to_string()), + ime_key: None, }) } else { None } } - fn handle_keydown_msg(&self, wparam: WPARAM) -> CallbackResult { - let mut callbacks = self.callbacks.borrow_mut(); - let keystroke = self.parse_key_msg_keystroke(wparam); - if let Some(keystroke) = keystroke { - if let Some(callback) = callbacks.input.as_mut() { - let ime_key = keystroke.ime_key.clone(); - let event = KeyDownEvent { - keystroke, - is_held: true, - }; + fn parse_keydown_msg_keystroke(&self, wparam: WPARAM) -> Option { + let vk_code = wparam.loword(); - if callback(PlatformInput::KeyDown(event)) { - CallbackResult::Handled { by_callback: true } - } else if let Some(mut input_handler) = self.input_handler.take() { - if let Some(ime_key) = ime_key { - input_handler.replace_text_in_range(None, &ime_key); - } - self.input_handler.set(Some(input_handler)); - CallbackResult::Handled { by_callback: true } - } else { - CallbackResult::Handled { by_callback: false } - } - } else { - CallbackResult::Handled { by_callback: false } + let modifiers = self.current_modifiers(); + if modifiers.control || modifiers.alt { + let basic_key = basic_vkcode_to_string(vk_code, modifiers); + if basic_key.is_some() { + return basic_key; } - } else { - CallbackResult::Unhandled } - } - fn handle_keyup_msg(&self, wparam: WPARAM) -> CallbackResult { - let mut callbacks = self.callbacks.borrow_mut(); - let keystroke = self.parse_key_msg_keystroke(wparam); - if let Some(keystroke) = keystroke { - if let Some(callback) = callbacks.input.as_mut() { - let event = KeyUpEvent { keystroke }; - let by_callback = callback(PlatformInput::KeyUp(event)); - CallbackResult::Handled { by_callback } - } else { - CallbackResult::Handled { by_callback: false } - } - } else { - CallbackResult::Unhandled - } - } - - fn handle_char_msg(&self, wparam: WPARAM) -> LRESULT { - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.input.as_mut() { - let modifiers = self.current_modifiers(); - let msg_char = wparam.0 as u8 as char; - let keystroke = Keystroke { + if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 { + let offset = vk_code - VK_F1.0; + return Some(Keystroke { modifiers, - key: msg_char.to_string(), - ime_key: Some(msg_char.to_string()), - }; - let ime_key = keystroke.ime_key.clone(); - let event = KeyDownEvent { - keystroke, - is_held: false, - }; - - if callback(PlatformInput::KeyDown(event)) { - return LRESULT(0); - } - - if let Some(mut input_handler) = self.input_handler.take() { - if let Some(ime_key) = ime_key { - input_handler.replace_text_in_range(None, &ime_key); - } - self.input_handler.set(Some(input_handler)); - return LRESULT(0); - } + key: format!("f{}", offset + 1), + ime_key: None, + }); } - return LRESULT(1); + + 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 + } + } + + fn parse_char_msg_keystroke(&self, wparam: WPARAM) -> Option { + let src = [wparam.0 as u16]; + let Ok(first_char) = char::decode_utf16(src).collect::>()[0] else { + return None; + }; + if first_char.is_control() { + None + } else { + Some(Keystroke { + modifiers: self.current_modifiers(), + key: first_char.to_lowercase().to_string(), + ime_key: Some(first_char.to_string()), + }) + } + } + + fn handle_syskeydown_msg(&self, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}` + // shortcuts. + let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else { + return unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) }; + }; + let Some(ref mut func) = self.callbacks.borrow_mut().input else { + return unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) }; + }; + let event = KeyDownEvent { + keystroke, + is_held: lparam.0 & (0x1 << 30) > 0, + }; + if func(PlatformInput::KeyDown(event)) { + self.invalidate_client_area(); + return LRESULT(0); + } + unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) } + } + + fn handle_syskeyup_msg(&self, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}` + // shortcuts. + let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else { + return unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) }; + }; + let Some(ref mut func) = self.callbacks.borrow_mut().input else { + return unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) }; + }; + let event = KeyUpEvent { keystroke }; + if func(PlatformInput::KeyUp(event)) { + self.invalidate_client_area(); + return LRESULT(0); + } + unsafe { DefWindowProcW(self.hwnd, message, wparam, lparam) } + } + + fn handle_keydown_msg(&self, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else { + return LRESULT(1); + }; + let Some(ref mut func) = self.callbacks.borrow_mut().input else { + return LRESULT(1); + }; + let event = KeyDownEvent { + keystroke, + is_held: lparam.0 & (0x1 << 30) > 0, + }; + if func(PlatformInput::KeyDown(event)) { + self.invalidate_client_area(); + return LRESULT(0); + } + LRESULT(1) + } + + fn handle_keyup_msg(&self, message: u32, wparam: WPARAM) -> LRESULT { + let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else { + return LRESULT(1); + }; + let Some(ref mut func) = self.callbacks.borrow_mut().input else { + return LRESULT(1); + }; + let event = KeyUpEvent { keystroke }; + if func(PlatformInput::KeyUp(event)) { + self.invalidate_client_area(); + return LRESULT(0); + } + LRESULT(1) + } + + fn handle_char_msg(&self, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + let Some(keystroke) = self.parse_char_msg_keystroke(wparam) else { + return LRESULT(1); + }; + let mut callbacks = self.callbacks.borrow_mut(); + let Some(ref mut func) = callbacks.input else { + return LRESULT(1); + }; + let ime_key = keystroke.ime_key.clone(); + let event = KeyDownEvent { + keystroke, + is_held: lparam.0 & (0x1 << 30) > 0, + }; + if func(PlatformInput::KeyDown(event)) { + self.invalidate_client_area(); + return LRESULT(0); + } + drop(callbacks); + let Some(ime_char) = ime_key else { + return LRESULT(1); + }; + let Some(mut input_handler) = self.input_handler.take() else { + return LRESULT(1); + }; + input_handler.replace_text_in_range(None, &ime_char); + self.input_handler.set(Some(input_handler)); + self.invalidate_client_area(); + LRESULT(0) } fn handle_mouse_down_msg(&self, button: MouseButton, lparam: LPARAM) -> LRESULT { @@ -671,9 +706,9 @@ impl WindowsWindow { drag_drop_handler, }; platform_inner - .window_handle_values - .borrow_mut() - .insert(wnd.inner.hwnd.0); + .raw_window_handles + .write() + .push(wnd.inner.hwnd); unsafe { ShowWindow(wnd.inner.hwnd, SW_SHOW) }; wnd @@ -1134,5 +1169,58 @@ unsafe fn set_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX, dwnewlong: } } +fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option { + 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 + } + } + } +} + +fn oemkey_vkcode_to_string(code: u16) -> Option { + 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, + } +} + // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;