windows: mouse and keyboard (#8791)

Ezekiel Warren created

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)

Change summary

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   | 520 +++++++++++++++++++--
4 files changed, 605 insertions(+), 85 deletions(-)

Detailed changes

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",
 ]
 
 

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<WindowsPlatformInner>,
 }
 
+/// 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<Callbacks>,
     pub(crate) window_handles: RefCell<HashSet<AnyWindowHandle>>,
     pub(crate) event: HANDLE,
+    pub(crate) settings: RefCell<WindowsPlatformSystemSettings>,
 }
 
 impl Drop for WindowsPlatformInner {
@@ -65,6 +79,57 @@ struct Callbacks {
     validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> 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::<Runnable>();
@@ -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<dyn 'static + FnOnce()>) {
         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()

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
+    }
 }

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<Point<GlobalPixels>>,
     size: Cell<Size<GlobalPixels>>,
@@ -109,75 +143,423 @@ 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_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<Keystroke> {
+        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;
             }
-            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);
+
+            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);
                 }
-                drop(callbacks);
-                return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) };
+                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);
             }
-            WM_DESTROY => {
-                let mut callbacks: std::cell::RefMut<'_, Callbacks> = self.callbacks.borrow_mut();
-                if let Some(callback) = callbacks.close.take() {
-                    callback()
+        }
+        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();
                 }
-                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(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(1);
+                return LRESULT(0);
             }
-            _ => return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) },
         }
-        LRESULT(0)
+        LRESULT(1)
     }
 }
 
@@ -512,6 +894,16 @@ unsafe extern "system" fn wnd_proc(
     r
 }
 
+pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {
+    let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
+    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 {