From 36c48318065d61812f4c7d682cdaf63b9ebd6cf7 Mon Sep 17 00:00:00 2001 From: Ezekiel Warren Date: Tue, 5 Mar 2024 08:35:07 -0800 Subject: [PATCH] windows: mouse and keyboard (#8791) Windows mouse and keyboard working! I also tweaked the message loop so that it didn't get stuck. The peek message loop was almost never returning for me during testing. Release Notes: - Added windows mouse and keyboard support ![windows-mouse-and-keyboard](https://github.com/zed-industries/zed/assets/1284289/08578fbf-0cb2-4e44-bab1-3c4f0291ea4b) --- Cargo.toml | 4 + crates/gpui/src/platform/windows/platform.rs | 148 ++++- crates/gpui/src/platform/windows/util.rs | 18 + crates/gpui/src/platform/windows/window.rs | 534 ++++++++++++++++--- 4 files changed, 612 insertions(+), 92 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ca534a9ee..e0887458f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -314,8 +314,12 @@ version = "0.53.0" features = [ "Win32_Graphics_Gdi", "Win32_UI_WindowsAndMessaging", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_System_SystemServices", "Win32_Security", "Win32_System_Threading", + "Win32_System_DataExchange", + "Win32_System_Ole", ] diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index be3d789813..26fda0be37 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -4,6 +4,7 @@ use std::{ cell::RefCell, collections::HashSet, + ffi::{c_uint, c_void}, path::{Path, PathBuf}, rc::Rc, sync::Arc, @@ -15,27 +16,39 @@ use async_task::Runnable; use futures::channel::oneshot::Receiver; use parking_lot::Mutex; use time::UtcOffset; -use util::SemanticVersion; +use util::{ResultExt, SemanticVersion}; use windows::Win32::{ - Foundation::{CloseHandle, HANDLE, HWND}, + Foundation::{CloseHandle, GetLastError, HANDLE, HWND, WAIT_EVENT}, System::Threading::{CreateEventW, INFINITE}, UI::WindowsAndMessaging::{ - DispatchMessageW, MsgWaitForMultipleObjects, PeekMessageW, PostQuitMessage, - TranslateMessage, MSG, PM_REMOVE, QS_ALLINPUT, WM_QUIT, + DispatchMessageW, GetMessageW, MsgWaitForMultipleObjects, PostQuitMessage, + SystemParametersInfoW, TranslateMessage, MSG, QS_ALLINPUT, SPI_GETWHEELSCROLLCHARS, + SPI_GETWHEELSCROLLLINES, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WM_QUIT, WM_SETTINGCHANGE, }, }; use crate::{ - Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, - Keymap, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, - PlatformWindow, Task, WindowAppearance, WindowOptions, WindowsDispatcher, WindowsDisplay, - WindowsTextSystem, WindowsWindow, + try_get_window_inner, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, + ForegroundExecutor, Keymap, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, + PlatformTextSystem, PlatformWindow, Task, WindowAppearance, WindowOptions, WindowsDispatcher, + WindowsDisplay, WindowsTextSystem, WindowsWindow, }; pub(crate) struct WindowsPlatform { inner: Rc, } +/// Windows settings pulled from SystemParametersInfo +/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow +#[derive(Default, Debug)] +pub(crate) struct WindowsPlatformSystemSettings { + /// SEE: SPI_GETWHEELSCROLLCHARS + pub(crate) wheel_scroll_chars: u32, + + /// SEE: SPI_GETWHEELSCROLLLINES + pub(crate) wheel_scroll_lines: u32, +} + pub(crate) struct WindowsPlatformInner { background_executor: BackgroundExecutor, pub(crate) foreground_executor: ForegroundExecutor, @@ -44,6 +57,7 @@ pub(crate) struct WindowsPlatformInner { callbacks: Mutex, pub(crate) window_handles: RefCell>, pub(crate) event: HANDLE, + pub(crate) settings: RefCell, } impl Drop for WindowsPlatformInner { @@ -65,6 +79,57 @@ struct Callbacks { validate_app_menu_command: Option bool>>, } +enum WindowsMessageWaitResult { + ForegroundExecution, + WindowsMessage(MSG), + Error, +} + +impl WindowsPlatformSystemSettings { + fn new() -> Self { + let mut settings = Self::default(); + settings.update_all(); + settings + } + + pub(crate) fn update_all(&mut self) { + self.update_wheel_scroll_lines(); + self.update_wheel_scroll_chars(); + } + + pub(crate) fn update_wheel_scroll_lines(&mut self) { + let mut value = c_uint::default(); + let result = unsafe { + SystemParametersInfoW( + SPI_GETWHEELSCROLLLINES, + 0, + Some((&mut value) as *mut c_uint as *mut c_void), + SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(), + ) + }; + + if result.log_err() != None { + self.wheel_scroll_lines = value; + } + } + + pub(crate) fn update_wheel_scroll_chars(&mut self) { + let mut value = c_uint::default(); + let result = unsafe { + SystemParametersInfoW( + SPI_GETWHEELSCROLLCHARS, + 0, + Some((&mut value) as *mut c_uint as *mut c_void), + SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(), + ) + }; + + if result.log_err() != None { + self.wheel_scroll_chars = value; + } + } +} + impl WindowsPlatform { pub(crate) fn new() -> Self { let (main_sender, main_receiver) = flume::unbounded::(); @@ -75,6 +140,7 @@ impl WindowsPlatform { let text_system = Arc::new(WindowsTextSystem::new()); let callbacks = Mutex::new(Callbacks::default()); let window_handles = RefCell::new(HashSet::new()); + let settings = RefCell::new(WindowsPlatformSystemSettings::new()); let inner = Rc::new(WindowsPlatformInner { background_executor, foreground_executor, @@ -83,9 +149,44 @@ impl WindowsPlatform { callbacks, 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 let Some(inner) = try_get_window_inner(msg.hwnd) { + inner.handle_immediate_msg(msg.message, msg.wParam, msg.lParam) + } else { + false + } + } + + fn wait_message(&self) -> WindowsMessageWaitResult { + let wait_result = unsafe { + MsgWaitForMultipleObjects(Some(&[self.inner.event]), false, INFINITE, QS_ALLINPUT) + }; + + match wait_result { + WAIT_EVENT(0) => WindowsMessageWaitResult::ForegroundExecution, + WAIT_EVENT(1) => { + let mut msg = MSG::default(); + unsafe { GetMessageW(&mut msg, HWND::default(), 0, 0) }; + WindowsMessageWaitResult::WindowsMessage(msg) + } + _ => { + log::error!("unhandled windows wait message: {}", wait_result.0); + WindowsMessageWaitResult::Error + } + } + } } impl Platform for WindowsPlatform { @@ -103,22 +204,27 @@ impl Platform for WindowsPlatform { fn run(&self, on_finish_launching: Box) { on_finish_launching(); - 'a: loop { - unsafe { - MsgWaitForMultipleObjects(Some(&[self.inner.event]), false, INFINITE, QS_ALLINPUT) - }; - 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; + loop { + match self.wait_message() { + WindowsMessageWaitResult::ForegroundExecution => { + for runnable in self.inner.main_receiver.drain() { + runnable.run(); + } } - unsafe { TranslateMessage(&msg) }; - unsafe { DispatchMessageW(&msg) }; - } - while let Ok(runnable) = self.inner.main_receiver.try_recv() { - runnable.run(); + WindowsMessageWaitResult::WindowsMessage(msg) => { + if msg.message == WM_QUIT { + break; + } + + if !self.run_immediate_msg_handlers(&msg) { + unsafe { TranslateMessage(&msg) }; + unsafe { DispatchMessageW(&msg) }; + } + } + WindowsMessageWaitResult::Error => {} } } + let mut callbacks = self.inner.callbacks.lock(); if let Some(callback) = callbacks.quit.as_mut() { callback() diff --git a/crates/gpui/src/platform/windows/util.rs b/crates/gpui/src/platform/windows/util.rs index bba6f132ab..3a3ebccb64 100644 --- a/crates/gpui/src/platform/windows/util.rs +++ b/crates/gpui/src/platform/windows/util.rs @@ -3,6 +3,8 @@ use windows::Win32::Foundation::{LPARAM, WPARAM}; pub(crate) trait HiLoWord { fn hiword(&self) -> u16; fn loword(&self) -> u16; + fn signed_hiword(&self) -> i16; + fn signed_loword(&self) -> i16; } impl HiLoWord for WPARAM { @@ -13,6 +15,14 @@ impl HiLoWord for WPARAM { fn loword(&self) -> u16 { (self.0 & 0xFFFF) as u16 } + + fn signed_hiword(&self) -> i16 { + ((self.0 >> 16) & 0xFFFF) as i16 + } + + fn signed_loword(&self) -> i16 { + (self.0 & 0xFFFF) as i16 + } } impl HiLoWord for LPARAM { @@ -23,4 +33,12 @@ impl HiLoWord for LPARAM { fn loword(&self) -> u16 { (self.0 & 0xFFFF) as u16 } + + fn signed_hiword(&self) -> i16 { + ((self.0 >> 16) & 0xFFFF) as i16 + } + + fn signed_loword(&self) -> i16 { + (self.0 & 0xFFFF) as i16 + } } diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index f43ac7cec8..162db74a31 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -18,24 +18,58 @@ use windows::{ core::{w, HSTRING, PCWSTR}, Win32::{ Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM}, - UI::WindowsAndMessaging::{ - CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, LoadCursorW, PostQuitMessage, - RegisterClassW, SetWindowLongPtrW, SetWindowTextW, ShowWindow, CREATESTRUCTW, - CW_USEDEFAULT, GWLP_USERDATA, HMENU, IDC_ARROW, SW_MAXIMIZE, SW_SHOW, WINDOW_EX_STYLE, - WINDOW_LONG_PTR_INDEX, WM_CLOSE, WM_DESTROY, WM_MOVE, WM_NCCREATE, WM_NCDESTROY, - WM_PAINT, WM_SIZE, WNDCLASSW, WS_OVERLAPPEDWINDOW, WS_VISIBLE, + System::SystemServices::{ + MK_LBUTTON, MK_MBUTTON, MK_RBUTTON, MK_XBUTTON1, MK_XBUTTON2, MODIFIERKEYS_FLAGS, + }, + UI::{ + 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, + }, + WindowsAndMessaging::{ + CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, LoadCursorW, PostQuitMessage, + RegisterClassW, SetWindowLongPtrW, SetWindowTextW, ShowWindow, CREATESTRUCTW, + CW_USEDEFAULT, GWLP_USERDATA, HMENU, IDC_ARROW, SW_MAXIMIZE, SW_SHOW, WHEEL_DELTA, + 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, + }, }, }, }; use crate::{ - platform::blade::BladeRenderer, AnyWindowHandle, Bounds, GlobalPixels, HiLoWord, Modifiers, - Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, - Point, PromptLevel, Scene, Size, WindowAppearance, WindowBounds, WindowOptions, WindowsDisplay, - WindowsPlatformInner, + platform::blade::BladeRenderer, AnyWindowHandle, Bounds, GlobalPixels, HiLoWord, KeyDownEvent, + KeyUpEvent, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + NavigationDirection, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, + PlatformInputHandler, PlatformWindow, Point, PromptLevel, Scene, ScrollDelta, Size, TouchPhase, + WindowAppearance, WindowBounds, WindowOptions, WindowsDisplay, WindowsPlatformInner, }; -struct WindowsWindowInner { +#[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>, size: Cell>, @@ -109,76 +143,424 @@ impl WindowsWindowInner { } } + fn is_virtual_key_pressed(&self, vkey: VIRTUAL_KEY) -> bool { + unsafe { GetKeyState(vkey.0 as i32) < 0 } + } + + fn current_modifiers(&self) -> Modifiers { + Modifiers { + control: self.is_virtual_key_pressed(VK_CONTROL), + alt: self.is_virtual_key_pressed(VK_MENU), + shift: self.is_virtual_key_pressed(VK_SHIFT), + command: self.is_virtual_key_pressed(VK_LWIN) || self.is_virtual_key_pressed(VK_RWIN), + function: 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 { - WM_MOVE => { - let x = lparam.loword() as f64; - let y = lparam.hiword() as f64; - self.origin.set(Point::new(x.into(), y.into())); - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.moved.as_mut() { - callback() + WM_MOVE => self.handle_move_msg(lparam), + WM_SIZE => self.handle_size_msg(lparam), + WM_PAINT => self.handle_paint_msg(), + WM_CLOSE => self.handle_close_msg(msg, wparam, lparam), + WM_DESTROY => self.handle_destroy_msg(), + WM_MOUSEMOVE => self.handle_mouse_move_msg(lparam, wparam), + WM_LBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Left, lparam), + WM_RBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Right, lparam), + WM_MBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Middle, lparam), + WM_XBUTTONDOWN => { + let nav_dir = match wparam.hiword() { + XBUTTON1 => Some(NavigationDirection::Forward), + XBUTTON2 => Some(NavigationDirection::Back), + _ => None, + }; + + if let Some(nav_dir) = nav_dir { + self.handle_mouse_down_msg(MouseButton::Navigate(nav_dir), lparam) + } else { + LRESULT(1) } } - WM_SIZE => { - // todo!("windows"): handle maximized or minimized - let width = lparam.loword().max(1) as f64; - let height = lparam.hiword().max(1) as f64; - self.renderer - .borrow_mut() - .update_drawable_size(Size { width, height }); - let width = width.into(); - let height = height.into(); - self.size.set(Size { width, height }); - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.resize.as_mut() { - callback( - Size { - width: Pixels(width.0), - height: Pixels(height.0), - }, - 1.0, - ) + WM_LBUTTONUP => self.handle_mouse_up_msg(MouseButton::Left, lparam), + WM_RBUTTONUP => self.handle_mouse_up_msg(MouseButton::Right, lparam), + WM_MBUTTONUP => self.handle_mouse_up_msg(MouseButton::Middle, lparam), + WM_XBUTTONUP => { + let nav_dir = match wparam.hiword() { + XBUTTON1 => Some(NavigationDirection::Back), + XBUTTON2 => Some(NavigationDirection::Forward), + _ => None, + }; + + if let Some(nav_dir) = nav_dir { + self.handle_mouse_up_msg(MouseButton::Navigate(nav_dir), lparam) + } else { + LRESULT(1) } } - WM_PAINT => { - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.request_frame.as_mut() { - callback() - } - } - WM_CLOSE => { - let mut callbacks: std::cell::RefMut<'_, Callbacks> = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.should_close.as_mut() { - if callback() { - return LRESULT(0); - } - } - drop(callbacks); - return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }; - } - WM_DESTROY => { - let mut callbacks: std::cell::RefMut<'_, Callbacks> = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.close.take() { - callback() - } - let mut window_handles = self.platform_inner.window_handles.borrow_mut(); - window_handles.remove(&self.handle); - if window_handles.is_empty() { - self.platform_inner - .foreground_executor - .spawn(async { - unsafe { PostQuitMessage(0) }; - }) - .detach(); - } - return LRESULT(1); - } - _ => return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }, + 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), + _ => unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }, + } + } + + fn handle_move_msg(&self, lparam: LPARAM) -> LRESULT { + let x = lparam.signed_loword() as f64; + let y = lparam.signed_hiword() as f64; + self.origin.set(Point::new(x.into(), y.into())); + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.moved.as_mut() { + callback() } LRESULT(0) } + + fn handle_size_msg(&self, lparam: LPARAM) -> LRESULT { + let width = lparam.loword().max(1) as f64; + let height = lparam.hiword().max(1) as f64; + self.renderer + .borrow_mut() + .update_drawable_size(Size { width, height }); + let width = width.into(); + let height = height.into(); + self.size.set(Size { width, height }); + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.resize.as_mut() { + callback( + Size { + width: Pixels(width.0), + height: Pixels(height.0), + }, + 1.0, + ) + } + LRESULT(0) + } + + fn handle_paint_msg(&self) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.request_frame.as_mut() { + callback() + } + LRESULT(0) + } + + fn handle_close_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.should_close.as_mut() { + if callback() { + return LRESULT(0); + } + } + drop(callbacks); + unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) } + } + + fn handle_destroy_msg(&self) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.close.take() { + callback() + } + let mut window_handles = self.platform_inner.window_handles.borrow_mut(); + window_handles.remove(&self.handle); + if window_handles.is_empty() { + self.platform_inner + .foreground_executor + .spawn(async { + unsafe { PostQuitMessage(0) }; + }) + .detach(); + } + LRESULT(1) + } + + fn handle_mouse_move_msg(&self, lparam: LPARAM, wparam: WPARAM) -> LRESULT { + let x = Pixels::from(lparam.signed_loword() as f32); + let y = Pixels::from(lparam.signed_hiword() as f32); + self.mouse_position.set(Point { x, y }); + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.input.as_mut() { + let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) { + flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left), + flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right), + flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle), + flags if flags.contains(MK_XBUTTON1) => { + Some(MouseButton::Navigate(NavigationDirection::Back)) + } + flags if flags.contains(MK_XBUTTON2) => { + Some(MouseButton::Navigate(NavigationDirection::Forward)) + } + _ => None, + }; + let event = MouseMoveEvent { + position: Point { x, y }, + pressed_button, + modifiers: self.current_modifiers(), + }; + if callback(PlatformInput::MouseMove(event)) { + return LRESULT(0); + } + } + LRESULT(1) + } + + fn parse_key_msg_keystroke(&self, wparam: WPARAM) -> Option { + 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 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)), + _ => None, + }; + + if let Some((key, ime_key)) = key { + Some(Keystroke { + modifiers: self.current_modifiers(), + key: key.to_string(), + ime_key: ime_key.map(|k| k.to_string()), + }) + } 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, + }; + + if callback(PlatformInput::KeyDown(event)) { + if let Some(request_frame) = callbacks.request_frame.as_mut() { + request_frame(); + } + 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)); + if let Some(request_frame) = callbacks.request_frame.as_mut() { + request_frame(); + } + CallbackResult::Handled { by_callback: true } + } else { + CallbackResult::Handled { by_callback: false } + } + } else { + CallbackResult::Handled { by_callback: false } + } + } 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 }; + CallbackResult::Handled { + by_callback: callback(PlatformInput::KeyUp(event)), + } + } 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 { + 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); + } + } + return LRESULT(1); + } + + fn handle_mouse_down_msg(&self, button: MouseButton, lparam: LPARAM) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.input.as_mut() { + let x = Pixels::from(lparam.signed_loword() as f32); + let y = Pixels::from(lparam.signed_hiword() as f32); + let event = MouseDownEvent { + button, + position: Point { x, y }, + modifiers: self.current_modifiers(), + click_count: 1, + }; + if callback(PlatformInput::MouseDown(event)) { + return LRESULT(0); + } + } + LRESULT(1) + } + + fn handle_mouse_up_msg(&self, button: MouseButton, lparam: LPARAM) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.input.as_mut() { + let x = Pixels::from(lparam.signed_loword() as f32); + let y = Pixels::from(lparam.signed_hiword() as f32); + let event = MouseUpEvent { + button, + position: Point { x, y }, + modifiers: self.current_modifiers(), + click_count: 1, + }; + if callback(PlatformInput::MouseUp(event)) { + return LRESULT(0); + } + } + LRESULT(1) + } + + fn handle_mouse_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.input.as_mut() { + let x = Pixels::from(lparam.signed_loword() as f32); + let y = Pixels::from(lparam.signed_hiword() as f32); + let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) + * self.platform_inner.settings.borrow().wheel_scroll_lines as f32; + let event = crate::ScrollWheelEvent { + position: Point { x, y }, + delta: ScrollDelta::Lines(Point { + x: 0.0, + y: wheel_distance, + }), + modifiers: self.current_modifiers(), + touch_phase: TouchPhase::Moved, + }; + if callback(PlatformInput::ScrollWheel(event)) { + if let Some(request_frame) = callbacks.request_frame.as_mut() { + request_frame(); + } + return LRESULT(0); + } + } + LRESULT(1) + } + + fn handle_mouse_horizontal_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.input.as_mut() { + let x = Pixels::from(lparam.signed_loword() as f32); + let y = Pixels::from(lparam.signed_hiword() as f32); + let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) + * self.platform_inner.settings.borrow().wheel_scroll_chars as f32; + let event = crate::ScrollWheelEvent { + position: Point { x, y }, + delta: ScrollDelta::Lines(Point { + x: wheel_distance, + y: 0.0, + }), + modifiers: self.current_modifiers(), + touch_phase: TouchPhase::Moved, + }; + if callback(PlatformInput::ScrollWheel(event)) { + if let Some(request_frame) = callbacks.request_frame.as_mut() { + request_frame(); + } + return LRESULT(0); + } + } + LRESULT(1) + } } #[derive(Default)] @@ -512,6 +894,16 @@ unsafe extern "system" fn wnd_proc( r } +pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option> { + let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak; + if !ptr.is_null() { + let inner = unsafe { &*ptr }; + inner.upgrade() + } else { + None + } +} + unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize { #[cfg(target_pointer_width = "64")] unsafe {