diff --git a/crates/gpui/src/platform/windows.rs b/crates/gpui/src/platform/windows.rs index 2b35a8e942..f8ea897fb8 100644 --- a/crates/gpui/src/platform/windows.rs +++ b/crates/gpui/src/platform/windows.rs @@ -1,14 +1,18 @@ mod direct_write; mod dispatcher; mod display; +mod events; mod platform; +mod system_settings; mod util; mod window; pub(crate) use direct_write::*; pub(crate) use dispatcher::*; pub(crate) use display::*; +pub(crate) use events::*; pub(crate) use platform::*; +pub(crate) use system_settings::*; pub(crate) use util::*; pub(crate) use window::*; diff --git a/crates/gpui/src/platform/windows/display.rs b/crates/gpui/src/platform/windows/display.rs index 151d0b7151..17d0a5b8ae 100644 --- a/crates/gpui/src/platform/windows/display.rs +++ b/crates/gpui/src/platform/windows/display.rs @@ -9,7 +9,7 @@ use windows::{ use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Point, Size}; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub(crate) struct WindowsDisplay { pub handle: HMONITOR, pub display_id: DisplayId, diff --git a/crates/gpui/src/platform/windows/events.rs b/crates/gpui/src/platform/windows/events.rs new file mode 100644 index 0000000000..7ae1607210 --- /dev/null +++ b/crates/gpui/src/platform/windows/events.rs @@ -0,0 +1,1274 @@ +use std::rc::Rc; + +use ::util::ResultExt; +use anyhow::Context; +use windows::Win32::{ + Foundation::*, + Graphics::Gdi::*, + System::SystemServices::*, + UI::{ + HiDpi::*, + Input::{Ime::*, KeyboardAndMouse::*}, + WindowsAndMessaging::*, + }, +}; + +use crate::*; + +pub(crate) const CURSOR_STYLE_CHANGED: u32 = WM_USER + 1; +pub(crate) const MOUSE_WHEEL_SETTINGS_CHANGED: u32 = WM_USER + 2; +pub(crate) const MOUSE_WHEEL_SETTINGS_SCROLL_CHARS_CHANGED: isize = 1; +pub(crate) const MOUSE_WHEEL_SETTINGS_SCROLL_LINES_CHANGED: isize = 2; +pub(crate) const CLOSE_ONE_WINDOW: u32 = WM_USER + 3; +const SIZE_MOVE_LOOP_TIMER_ID: usize = 1; + +pub(crate) fn handle_msg( + handle: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> LRESULT { + let handled = match msg { + WM_ACTIVATE => handle_activate_msg(handle, wparam, state_ptr), + WM_CREATE => handle_create_msg(handle, state_ptr), + WM_MOVE => handle_move_msg(handle, lparam, state_ptr), + WM_SIZE => handle_size_msg(lparam, state_ptr), + WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => handle_size_move_loop(handle), + WM_EXITSIZEMOVE | WM_EXITMENULOOP => handle_size_move_loop_exit(handle), + WM_TIMER => handle_timer_msg(handle, wparam, state_ptr), + WM_NCCALCSIZE => handle_calc_client_size(handle, wparam, lparam, state_ptr), + WM_DPICHANGED => handle_dpi_changed_msg(handle, wparam, lparam, state_ptr), + WM_NCHITTEST => handle_hit_test_msg(handle, msg, wparam, lparam, state_ptr), + WM_PAINT => handle_paint_msg(handle, state_ptr), + WM_CLOSE => handle_close_msg(state_ptr), + WM_DESTROY => handle_destroy_msg(handle, state_ptr), + WM_MOUSEMOVE => handle_mouse_move_msg(lparam, wparam, state_ptr), + WM_NCMOUSEMOVE => handle_nc_mouse_move_msg(handle, lparam, state_ptr), + WM_NCLBUTTONDOWN => { + handle_nc_mouse_down_msg(handle, MouseButton::Left, wparam, lparam, state_ptr) + } + WM_NCRBUTTONDOWN => { + handle_nc_mouse_down_msg(handle, MouseButton::Right, wparam, lparam, state_ptr) + } + WM_NCMBUTTONDOWN => { + handle_nc_mouse_down_msg(handle, MouseButton::Middle, wparam, lparam, state_ptr) + } + WM_NCLBUTTONUP => { + handle_nc_mouse_up_msg(handle, MouseButton::Left, wparam, lparam, state_ptr) + } + WM_NCRBUTTONUP => { + handle_nc_mouse_up_msg(handle, MouseButton::Right, wparam, lparam, state_ptr) + } + WM_NCMBUTTONUP => { + handle_nc_mouse_up_msg(handle, MouseButton::Middle, wparam, lparam, state_ptr) + } + WM_LBUTTONDOWN => handle_mouse_down_msg(MouseButton::Left, lparam, state_ptr), + WM_RBUTTONDOWN => handle_mouse_down_msg(MouseButton::Right, lparam, state_ptr), + WM_MBUTTONDOWN => handle_mouse_down_msg(MouseButton::Middle, lparam, state_ptr), + WM_XBUTTONDOWN => handle_xbutton_msg(wparam, lparam, handle_mouse_down_msg, state_ptr), + WM_LBUTTONUP => handle_mouse_up_msg(MouseButton::Left, lparam, state_ptr), + WM_RBUTTONUP => handle_mouse_up_msg(MouseButton::Right, lparam, state_ptr), + WM_MBUTTONUP => handle_mouse_up_msg(MouseButton::Middle, lparam, state_ptr), + WM_XBUTTONUP => handle_xbutton_msg(wparam, lparam, handle_mouse_up_msg, state_ptr), + WM_MOUSEWHEEL => handle_mouse_wheel_msg(handle, wparam, lparam, state_ptr), + WM_MOUSEHWHEEL => handle_mouse_horizontal_wheel_msg(handle, wparam, lparam, state_ptr), + WM_SYSKEYDOWN => handle_syskeydown_msg(handle, wparam, lparam, state_ptr), + WM_SYSKEYUP => handle_syskeyup_msg(handle, wparam, state_ptr), + WM_KEYDOWN => handle_keydown_msg(handle, wparam, lparam, state_ptr), + WM_KEYUP => handle_keyup_msg(handle, wparam, state_ptr), + WM_CHAR => handle_char_msg(handle, wparam, lparam, state_ptr), + WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr), + WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr), + WM_SETCURSOR => handle_set_cursor(lparam, state_ptr), + CURSOR_STYLE_CHANGED => handle_cursor_changed(lparam, state_ptr), + MOUSE_WHEEL_SETTINGS_CHANGED => handle_mouse_wheel_settings_msg(wparam, lparam, state_ptr), + _ => None, + }; + if let Some(n) = handled { + LRESULT(n) + } else { + unsafe { DefWindowProcW(handle, msg, wparam, lparam) } + } +} + +fn handle_move_msg( + handle: HWND, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + let x = lparam.signed_loword() as i32; + let y = lparam.signed_hiword() as i32; + let mut lock = state_ptr.state.borrow_mut(); + lock.origin = point(x.into(), y.into()); + let size = lock.physical_size; + let center_x = x + size.width.0 / 2; + let center_y = y + size.height.0 / 2; + let monitor_bounds = lock.display.bounds(); + if center_x < monitor_bounds.left().0 + || center_x > monitor_bounds.right().0 + || center_y < monitor_bounds.top().0 + || center_y > monitor_bounds.bottom().0 + { + // center of the window may have moved to another monitor + let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) }; + if !monitor.is_invalid() && lock.display.handle != monitor { + // we will get the same monitor if we only have one + lock.display = WindowsDisplay::new_with_handle(monitor); + } + } + if let Some(mut callback) = lock.callbacks.moved.take() { + drop(lock); + callback(); + state_ptr.state.borrow_mut().callbacks.moved = Some(callback); + } + Some(0) +} + +fn handle_size_msg(lparam: LPARAM, state_ptr: Rc) -> Option { + let width = lparam.loword().max(1) as i32; + let height = lparam.hiword().max(1) as i32; + let new_physical_size = size(width.into(), height.into()); + let mut lock = state_ptr.state.borrow_mut(); + let scale_factor = lock.scale_factor; + lock.physical_size = new_physical_size; + lock.renderer.update_drawable_size(Size { + width: width as f64, + height: height as f64, + }); + if let Some(mut callback) = lock.callbacks.resize.take() { + drop(lock); + let logical_size = logical_size(new_physical_size, scale_factor); + callback(logical_size, scale_factor); + state_ptr.state.borrow_mut().callbacks.resize = Some(callback); + } + Some(0) +} + +fn handle_size_move_loop(handle: HWND) -> Option { + unsafe { + let ret = SetTimer(handle, SIZE_MOVE_LOOP_TIMER_ID, USER_TIMER_MINIMUM, None); + if ret == 0 { + log::error!( + "unable to create timer: {}", + std::io::Error::last_os_error() + ); + } + } + None +} + +fn handle_size_move_loop_exit(handle: HWND) -> Option { + unsafe { + KillTimer(handle, SIZE_MOVE_LOOP_TIMER_ID).log_err(); + } + None +} + +fn handle_timer_msg( + handle: HWND, + wparam: WPARAM, + state_ptr: Rc, +) -> Option { + if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID { + for runnable in state_ptr.main_receiver.drain() { + runnable.run(); + } + handle_paint_msg(handle, state_ptr) + } else { + None + } +} + +fn handle_paint_msg(handle: HWND, state_ptr: Rc) -> Option { + let mut paint_struct = PAINTSTRUCT::default(); + let _hdc = unsafe { BeginPaint(handle, &mut paint_struct) }; + let mut lock = state_ptr.state.borrow_mut(); + if let Some(mut request_frame) = lock.callbacks.request_frame.take() { + drop(lock); + request_frame(); + state_ptr.state.borrow_mut().callbacks.request_frame = Some(request_frame); + } + unsafe { EndPaint(handle, &paint_struct) }; + Some(0) +} + +fn handle_close_msg(state_ptr: Rc) -> Option { + let mut lock = state_ptr.state.borrow_mut(); + if let Some(mut callback) = lock.callbacks.should_close.take() { + drop(lock); + let should_close = callback(); + state_ptr.state.borrow_mut().callbacks.should_close = Some(callback); + if should_close { + None + } else { + Some(0) + } + } else { + None + } +} + +fn handle_destroy_msg(handle: HWND, state_ptr: Rc) -> Option { + let callback = { + let mut lock = state_ptr.state.borrow_mut(); + lock.callbacks.close.take() + }; + if let Some(callback) = callback { + callback(); + } + unsafe { + PostMessageW(None, CLOSE_ONE_WINDOW, None, LPARAM(handle.0)).log_err(); + } + Some(0) +} + +fn handle_mouse_move_msg( + lparam: LPARAM, + wparam: WPARAM, + state_ptr: Rc, +) -> Option { + let mut lock = state_ptr.state.borrow_mut(); + if let Some(mut callback) = lock.callbacks.input.take() { + let scale_factor = lock.scale_factor; + drop(lock); + 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 x = lparam.signed_loword() as f32; + let y = lparam.signed_hiword() as f32; + let event = MouseMoveEvent { + position: logical_point(x, y, scale_factor), + pressed_button, + modifiers: current_modifiers(), + }; + let result = if callback(PlatformInput::MouseMove(event)).default_prevented { + Some(0) + } else { + Some(1) + }; + state_ptr.state.borrow_mut().callbacks.input = Some(callback); + return result; + } + Some(1) +} + +fn handle_syskeydown_msg( + handle: HWND, + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}` + // shortcuts. + let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else { + return None; + }; + let mut lock = state_ptr.state.borrow_mut(); + let Some(mut func) = lock.callbacks.input.take() else { + return None; + }; + drop(lock); + let event = KeyDownEvent { + keystroke, + is_held: lparam.0 & (0x1 << 30) > 0, + }; + let result = if func(PlatformInput::KeyDown(event)).default_prevented { + invalidate_client_area(handle); + Some(0) + } else { + None + }; + state_ptr.state.borrow_mut().callbacks.input = Some(func); + + result +} + +fn handle_syskeyup_msg( + handle: HWND, + wparam: WPARAM, + state_ptr: Rc, +) -> Option { + // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}` + // shortcuts. + let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else { + return None; + }; + let mut lock = state_ptr.state.borrow_mut(); + let Some(mut func) = lock.callbacks.input.take() else { + return None; + }; + drop(lock); + let event = KeyUpEvent { keystroke }; + let result = if func(PlatformInput::KeyUp(event)).default_prevented { + invalidate_client_area(handle); + Some(0) + } else { + Some(1) + }; + state_ptr.state.borrow_mut().callbacks.input = Some(func); + + result +} + +fn handle_keydown_msg( + handle: HWND, + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + let Some(keystroke) = parse_keydown_msg_keystroke(wparam) else { + return Some(1); + }; + let mut lock = state_ptr.state.borrow_mut(); + let Some(mut func) = lock.callbacks.input.take() else { + return Some(1); + }; + drop(lock); + let event = KeyDownEvent { + keystroke, + is_held: lparam.0 & (0x1 << 30) > 0, + }; + let result = if func(PlatformInput::KeyDown(event)).default_prevented { + invalidate_client_area(handle); + Some(0) + } else { + Some(1) + }; + state_ptr.state.borrow_mut().callbacks.input = Some(func); + + result +} + +fn handle_keyup_msg( + handle: HWND, + wparam: WPARAM, + state_ptr: Rc, +) -> Option { + let Some(keystroke) = parse_keydown_msg_keystroke(wparam) else { + return Some(1); + }; + let mut lock = state_ptr.state.borrow_mut(); + let Some(mut func) = lock.callbacks.input.take() else { + return Some(1); + }; + drop(lock); + let event = KeyUpEvent { keystroke }; + let result = if func(PlatformInput::KeyUp(event)).default_prevented { + invalidate_client_area(handle); + Some(0) + } else { + Some(1) + }; + state_ptr.state.borrow_mut().callbacks.input = Some(func); + + result +} + +fn handle_char_msg( + handle: HWND, + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + let Some(keystroke) = parse_char_msg_keystroke(wparam) else { + return Some(1); + }; + let mut lock = state_ptr.state.borrow_mut(); + let Some(mut func) = lock.callbacks.input.take() else { + return Some(1); + }; + drop(lock); + let ime_key = keystroke.ime_key.clone(); + let event = KeyDownEvent { + keystroke, + is_held: lparam.0 & (0x1 << 30) > 0, + }; + + let dispatch_event_result = func(PlatformInput::KeyDown(event)); + let mut lock = state_ptr.state.borrow_mut(); + lock.callbacks.input = Some(func); + if dispatch_event_result.default_prevented || !dispatch_event_result.propagate { + invalidate_client_area(handle); + return Some(0); + } + let Some(ime_char) = ime_key else { + return Some(1); + }; + let Some(mut input_handler) = lock.input_handler.take() else { + return Some(1); + }; + drop(lock); + input_handler.replace_text_in_range(None, &ime_char); + invalidate_client_area(handle); + state_ptr.state.borrow_mut().input_handler = Some(input_handler); + + Some(0) +} + +fn handle_mouse_down_msg( + button: MouseButton, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + let mut lock = state_ptr.state.borrow_mut(); + if let Some(mut callback) = lock.callbacks.input.take() { + let x = lparam.signed_loword() as f32; + let y = lparam.signed_hiword() as f32; + let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32)); + let click_count = lock.click_state.update(button, physical_point); + let scale_factor = lock.scale_factor; + drop(lock); + + let event = MouseDownEvent { + button, + position: logical_point(x, y, scale_factor), + modifiers: current_modifiers(), + click_count, + first_mouse: false, + }; + let result = if callback(PlatformInput::MouseDown(event)).default_prevented { + Some(0) + } else { + Some(1) + }; + state_ptr.state.borrow_mut().callbacks.input = Some(callback); + + result + } else { + Some(1) + } +} + +fn handle_mouse_up_msg( + button: MouseButton, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + let mut lock = state_ptr.state.borrow_mut(); + if let Some(mut callback) = lock.callbacks.input.take() { + let x = lparam.signed_loword() as f32; + let y = lparam.signed_hiword() as f32; + let click_count = lock.click_state.current_count; + let scale_factor = lock.scale_factor; + drop(lock); + + let event = MouseUpEvent { + button, + position: logical_point(x, y, scale_factor), + modifiers: current_modifiers(), + click_count, + }; + let result = if callback(PlatformInput::MouseUp(event)).default_prevented { + Some(0) + } else { + Some(1) + }; + state_ptr.state.borrow_mut().callbacks.input = Some(callback); + + result + } else { + Some(1) + } +} + +fn handle_xbutton_msg( + wparam: WPARAM, + lparam: LPARAM, + handler: impl Fn(MouseButton, LPARAM, Rc) -> Option, + state_ptr: Rc, +) -> Option { + let nav_dir = match wparam.hiword() { + XBUTTON1 => NavigationDirection::Back, + XBUTTON2 => NavigationDirection::Forward, + _ => return Some(1), + }; + handler(MouseButton::Navigate(nav_dir), lparam, state_ptr) +} + +fn handle_mouse_wheel_msg( + handle: HWND, + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + let mut lock = state_ptr.state.borrow_mut(); + if let Some(mut callback) = lock.callbacks.input.take() { + let scale_factor = lock.scale_factor; + let wheel_scroll_lines = lock.mouse_wheel_settings.wheel_scroll_lines; + drop(lock); + let wheel_distance = + (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_lines as f32; + let mut cursor_point = POINT { + x: lparam.signed_loword().into(), + y: lparam.signed_hiword().into(), + }; + unsafe { ScreenToClient(handle, &mut cursor_point) }; + let event = ScrollWheelEvent { + position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), + delta: ScrollDelta::Lines(Point { + x: 0.0, + y: wheel_distance, + }), + modifiers: current_modifiers(), + touch_phase: TouchPhase::Moved, + }; + let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented { + Some(0) + } else { + Some(1) + }; + state_ptr.state.borrow_mut().callbacks.input = Some(callback); + + result + } else { + Some(1) + } +} + +fn handle_mouse_horizontal_wheel_msg( + handle: HWND, + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + let mut lock = state_ptr.state.borrow_mut(); + if let Some(mut callback) = lock.callbacks.input.take() { + let scale_factor = lock.scale_factor; + let wheel_scroll_chars = lock.mouse_wheel_settings.wheel_scroll_chars; + drop(lock); + let wheel_distance = + (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_chars as f32; + let mut cursor_point = POINT { + x: lparam.signed_loword().into(), + y: lparam.signed_hiword().into(), + }; + unsafe { ScreenToClient(handle, &mut cursor_point) }; + let event = ScrollWheelEvent { + position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), + delta: ScrollDelta::Lines(Point { + x: wheel_distance, + y: 0.0, + }), + modifiers: current_modifiers(), + touch_phase: TouchPhase::Moved, + }; + let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented { + Some(0) + } else { + Some(1) + }; + state_ptr.state.borrow_mut().callbacks.input = Some(callback); + + result + } else { + Some(1) + } +} + +fn handle_ime_position(handle: HWND, state_ptr: Rc) -> Option { + unsafe { + let mut lock = state_ptr.state.borrow_mut(); + let ctx = ImmGetContext(handle); + let Some(mut input_handler) = lock.input_handler.take() else { + return Some(1); + }; + let scale_factor = lock.scale_factor; + drop(lock); + + let caret_range = input_handler.selected_text_range().unwrap_or_default(); + let caret_position = input_handler.bounds_for_range(caret_range).unwrap(); + state_ptr.state.borrow_mut().input_handler = Some(input_handler); + let config = CANDIDATEFORM { + dwStyle: CFS_CANDIDATEPOS, + // logical to physical + ptCurrentPos: POINT { + x: (caret_position.origin.x.0 * scale_factor) as i32, + y: (caret_position.origin.y.0 * scale_factor) as i32 + + ((caret_position.size.height.0 * scale_factor) as i32 / 2), + }, + ..Default::default() + }; + ImmSetCandidateWindow(ctx, &config as _); + ImmReleaseContext(handle, ctx); + Some(0) + } +} + +fn handle_ime_composition( + handle: HWND, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + let mut ime_input = None; + if lparam.0 as u32 & GCS_COMPSTR.0 > 0 { + let Some((string, string_len)) = parse_ime_compostion_string(handle) else { + return None; + }; + let mut lock = state_ptr.state.borrow_mut(); + let Some(mut input_handler) = lock.input_handler.take() else { + return None; + }; + drop(lock); + input_handler.replace_and_mark_text_in_range(None, string.as_str(), Some(0..string_len)); + state_ptr.state.borrow_mut().input_handler = Some(input_handler); + ime_input = Some(string); + } + if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 { + let Some(ref comp_string) = ime_input else { + return None; + }; + let caret_pos = retrieve_composition_cursor_position(handle); + let mut lock = state_ptr.state.borrow_mut(); + let Some(mut input_handler) = lock.input_handler.take() else { + return None; + }; + drop(lock); + input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos)); + state_ptr.state.borrow_mut().input_handler = Some(input_handler); + } + if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 { + let Some(comp_result) = parse_ime_compostion_result(handle) else { + return None; + }; + let mut lock = state_ptr.state.borrow_mut(); + let Some(mut input_handler) = lock.input_handler.take() else { + return Some(1); + }; + drop(lock); + input_handler.replace_text_in_range(None, &comp_result); + state_ptr.state.borrow_mut().input_handler = Some(input_handler); + invalidate_client_area(handle); + return Some(0); + } + // currently, we don't care other stuff + None +} + +/// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize +fn handle_calc_client_size( + handle: HWND, + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + if !state_ptr.hide_title_bar || state_ptr.state.borrow().is_fullscreen() { + return None; + } + + if wparam.0 == 0 { + return None; + } + + let dpi = unsafe { GetDpiForWindow(handle) }; + + let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) }; + let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) }; + let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) }; + + // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure + let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS; + let mut requested_client_rect = unsafe { &mut ((*params).rgrc) }; + + requested_client_rect[0].right -= frame_x + padding; + requested_client_rect[0].left += frame_x + padding; + requested_client_rect[0].bottom -= frame_y + padding; + + Some(0) +} + +fn handle_activate_msg( + handle: HWND, + wparam: WPARAM, + state_ptr: Rc, +) -> Option { + let activated = wparam.loword() > 0; + if state_ptr.hide_title_bar { + if let Some(titlebar_rect) = state_ptr.state.borrow().get_titlebar_rect().log_err() { + unsafe { InvalidateRect(handle, Some(&titlebar_rect), FALSE) }; + } + } + let this = state_ptr.clone(); + state_ptr + .executor + .spawn(async move { + let mut lock = this.state.borrow_mut(); + if let Some(mut cb) = lock.callbacks.active_status_change.take() { + drop(lock); + cb(activated); + this.state.borrow_mut().callbacks.active_status_change = Some(cb); + } + }) + .detach(); + + None +} + +fn handle_create_msg(handle: HWND, state_ptr: Rc) -> Option { + let mut size_rect = RECT::default(); + unsafe { GetWindowRect(handle, &mut size_rect).log_err() }; + + let width = size_rect.right - size_rect.left; + let height = size_rect.bottom - size_rect.top; + + if state_ptr.hide_title_bar { + unsafe { + SetWindowPos( + handle, + None, + size_rect.left, + size_rect.top, + width, + height, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE, + ) + .log_err() + }; + } + + Some(0) +} + +fn handle_dpi_changed_msg( + handle: HWND, + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + let new_dpi = wparam.loword() as f32; + state_ptr.state.borrow_mut().scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32; + + let rect = unsafe { &*(lparam.0 as *const RECT) }; + let width = rect.right - rect.left; + let height = rect.bottom - rect.top; + // this will emit `WM_SIZE` and `WM_MOVE` right here + // even before this function returns + // the new size is handled in `WM_SIZE` + unsafe { + SetWindowPos( + handle, + None, + rect.left, + rect.top, + width, + height, + SWP_NOZORDER | SWP_NOACTIVATE, + ) + .context("unable to set window position after dpi has changed") + .log_err(); + } + invalidate_client_area(handle); + + Some(0) +} + +fn handle_hit_test_msg( + handle: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + if !state_ptr.hide_title_bar { + return None; + } + + // default handler for resize areas + let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) }; + if matches!( + hit.0 as u32, + HTNOWHERE + | HTRIGHT + | HTLEFT + | HTTOPLEFT + | HTTOP + | HTTOPRIGHT + | HTBOTTOMRIGHT + | HTBOTTOM + | HTBOTTOMLEFT + ) { + return Some(hit.0); + } + + if state_ptr.state.borrow().is_fullscreen() { + return Some(HTCLIENT as _); + } + + let dpi = unsafe { GetDpiForWindow(handle) }; + let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) }; + let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) }; + + let mut cursor_point = POINT { + x: lparam.signed_loword().into(), + y: lparam.signed_hiword().into(), + }; + unsafe { ScreenToClient(handle, &mut cursor_point) }; + if cursor_point.y > 0 && cursor_point.y < frame_y + padding { + return Some(HTTOP as _); + } + + let titlebar_rect = state_ptr.state.borrow().get_titlebar_rect(); + if let Ok(titlebar_rect) = titlebar_rect { + if cursor_point.y < titlebar_rect.bottom { + let caption_btn_width = (state_ptr.state.borrow().caption_button_width().0 + * state_ptr.state.borrow().scale_factor) as i32; + if cursor_point.x >= titlebar_rect.right - caption_btn_width { + return Some(HTCLOSE as _); + } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 { + return Some(HTMAXBUTTON as _); + } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 { + return Some(HTMINBUTTON as _); + } + + return Some(HTCAPTION as _); + } + } + + Some(HTCLIENT as _) +} + +fn handle_nc_mouse_move_msg( + handle: HWND, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + if !state_ptr.hide_title_bar { + return None; + } + + let mut lock = state_ptr.state.borrow_mut(); + if let Some(mut callback) = lock.callbacks.input.take() { + let scale_factor = lock.scale_factor; + drop(lock); + let mut cursor_point = POINT { + x: lparam.signed_loword().into(), + y: lparam.signed_hiword().into(), + }; + unsafe { ScreenToClient(handle, &mut cursor_point) }; + let event = MouseMoveEvent { + position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), + pressed_button: None, + modifiers: current_modifiers(), + }; + let result = if callback(PlatformInput::MouseMove(event)).default_prevented { + Some(0) + } else { + Some(1) + }; + state_ptr.state.borrow_mut().callbacks.input = Some(callback); + + result + } else { + None + } +} + +fn handle_nc_mouse_down_msg( + handle: HWND, + button: MouseButton, + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + if !state_ptr.hide_title_bar { + return None; + } + + let mut lock = state_ptr.state.borrow_mut(); + let result = if let Some(mut callback) = lock.callbacks.input.take() { + let scale_factor = lock.scale_factor; + let mut cursor_point = POINT { + x: lparam.signed_loword().into(), + y: lparam.signed_hiword().into(), + }; + unsafe { ScreenToClient(handle, &mut cursor_point) }; + let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y)); + let click_count = lock.click_state.update(button, physical_point); + drop(lock); + let event = MouseDownEvent { + button, + position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), + modifiers: current_modifiers(), + click_count, + first_mouse: false, + }; + let result = if callback(PlatformInput::MouseDown(event)).default_prevented { + Some(0) + } else { + None + }; + state_ptr.state.borrow_mut().callbacks.input = Some(callback); + + result + } else { + None + }; + + // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc + result.or_else(|| matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0)) +} + +fn handle_nc_mouse_up_msg( + handle: HWND, + button: MouseButton, + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + if !state_ptr.hide_title_bar { + return None; + } + + let mut lock = state_ptr.state.borrow_mut(); + if let Some(mut callback) = lock.callbacks.input.take() { + let scale_factor = lock.scale_factor; + drop(lock); + let mut cursor_point = POINT { + x: lparam.signed_loword().into(), + y: lparam.signed_hiword().into(), + }; + unsafe { ScreenToClient(handle, &mut cursor_point) }; + let event = MouseUpEvent { + button, + position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), + modifiers: current_modifiers(), + click_count: 1, + }; + let result = if callback(PlatformInput::MouseUp(event)).default_prevented { + Some(0) + } else { + None + }; + state_ptr.state.borrow_mut().callbacks.input = Some(callback); + if result.is_some() { + return result; + } + } else { + drop(lock); + } + + if button == MouseButton::Left { + match wparam.0 as u32 { + HTMINBUTTON => unsafe { + ShowWindowAsync(handle, SW_MINIMIZE); + }, + HTMAXBUTTON => unsafe { + if state_ptr.state.borrow().is_maximized() { + ShowWindowAsync(handle, SW_NORMAL); + } else { + ShowWindowAsync(handle, SW_MAXIMIZE); + } + }, + HTCLOSE => unsafe { + PostMessageW(handle, WM_CLOSE, WPARAM::default(), LPARAM::default()).log_err(); + }, + _ => return None, + }; + return Some(0); + } + + None +} + +fn handle_cursor_changed(lparam: LPARAM, state_ptr: Rc) -> Option { + state_ptr.state.borrow_mut().current_cursor = HCURSOR(lparam.0); + Some(0) +} + +fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc) -> Option { + if matches!( + lparam.loword() as u32, + HTLEFT | HTRIGHT | HTTOP | HTTOPLEFT | HTTOPRIGHT | HTBOTTOM | HTBOTTOMLEFT | HTBOTTOMRIGHT + ) { + return None; + } + unsafe { SetCursor(state_ptr.state.borrow().current_cursor) }; + Some(1) +} + +fn handle_mouse_wheel_settings_msg( + wparam: WPARAM, + lparam: LPARAM, + state_ptr: Rc, +) -> Option { + match lparam.0 { + 1 => { + state_ptr + .state + .borrow_mut() + .mouse_wheel_settings + .wheel_scroll_chars = wparam.0 as u32 + } + 2 => { + state_ptr + .state + .borrow_mut() + .mouse_wheel_settings + .wheel_scroll_lines = wparam.0 as u32 + } + _ => unreachable!(), + } + Some(0) +} + +fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option { + let modifiers = 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(); + 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 + } +} + +fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option { + let vk_code = wparam.loword(); + + let modifiers = 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; + } + } + + if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 { + let offset = vk_code - VK_F1.0; + return Some(Keystroke { + modifiers, + key: format!("f{}", offset + 1), + ime_key: None, + }); + } + + 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(Keystroke { + modifiers, + key: key.to_string(), + ime_key: None, + }) + } else { + None + } +} + +fn parse_char_msg_keystroke(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 { + 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() { + modifiers.shift = false; + } + let key = match first_char { + ' ' => "space".to_string(), + first_char => first_char.to_lowercase().to_string(), + }; + Some(Keystroke { + modifiers, + key, + ime_key: Some(first_char.to_string()), + }) + } +} + +/// mark window client rect to be re-drawn +/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect +pub(crate) fn invalidate_client_area(handle: HWND) { + unsafe { InvalidateRect(handle, None, FALSE) }; +} + +fn parse_ime_compostion_string(handle: HWND) -> Option<(String, usize)> { + unsafe { + let ctx = ImmGetContext(handle); + let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0); + let result = if string_len >= 0 { + let mut buffer = vec![0u8; string_len as usize + 2]; + ImmGetCompositionStringW( + ctx, + GCS_COMPSTR, + Some(buffer.as_mut_ptr() as _), + string_len as _, + ); + let wstring = std::slice::from_raw_parts::( + buffer.as_mut_ptr().cast::(), + string_len as usize / 2, + ); + let string = String::from_utf16_lossy(wstring); + Some((string, string_len as usize / 2)) + } else { + None + }; + ImmReleaseContext(handle, ctx); + result + } +} + +fn retrieve_composition_cursor_position(handle: HWND) -> usize { + unsafe { + let ctx = ImmGetContext(handle); + let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0); + ImmReleaseContext(handle, ctx); + ret as usize + } +} + +fn parse_ime_compostion_result(handle: HWND) -> Option { + unsafe { + let ctx = ImmGetContext(handle); + let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0); + let result = if string_len >= 0 { + let mut buffer = vec![0u8; string_len as usize + 2]; + ImmGetCompositionStringW( + ctx, + GCS_RESULTSTR, + Some(buffer.as_mut_ptr() as _), + string_len as _, + ); + let wstring = std::slice::from_raw_parts::( + buffer.as_mut_ptr().cast::(), + string_len as usize / 2, + ); + let string = String::from_utf16_lossy(wstring); + Some(string) + } else { + None + }; + ImmReleaseContext(handle, ctx); + result + } +} + +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, + } +} + +#[inline] +fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool { + unsafe { GetKeyState(vkey.0 as i32) < 0 } +} + +#[inline] +fn current_modifiers() -> Modifiers { + Modifiers { + control: is_virtual_key_pressed(VK_CONTROL), + alt: is_virtual_key_pressed(VK_MENU), + shift: is_virtual_key_pressed(VK_SHIFT), + platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN), + function: false, + } +} diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 7f8ec438a3..f6d53c5aa1 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -3,8 +3,7 @@ use std::{ cell::{Cell, RefCell}, - ffi::{c_uint, c_void, OsString}, - iter::once, + ffi::{c_void, OsString}, mem::transmute, os::windows::ffi::{OsStrExt, OsStringExt}, path::{Path, PathBuf}, @@ -18,7 +17,7 @@ use async_task::Runnable; use copypasta::{ClipboardContext, ClipboardProvider}; use futures::channel::oneshot::{self, Receiver}; use itertools::Itertools; -use parking_lot::{Mutex, RwLock}; +use parking_lot::RwLock; use semantic_version::SemanticVersion; use smallvec::SmallVec; use time::UtcOffset; @@ -39,56 +38,26 @@ use windows::{ use crate::*; 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, + state: RefCell, + raw_window_handles: RwLock>, + // The below members will never change throughout the entire lifecycle of the app. + icon: HICON, main_receiver: flume::Receiver, + background_executor: BackgroundExecutor, + foreground_executor: ForegroundExecutor, text_system: Arc, - callbacks: Mutex, - pub raw_window_handles: RwLock>, - pub(crate) dispatch_event: OwnedHandle, - pub(crate) settings: RefCell, - pub icon: HICON, - // NOTE: standard cursor handles don't need to close. - pub(crate) current_cursor: Cell, + dispatch_event: OwnedHandle, } -impl WindowsPlatformInner { - pub(crate) fn try_get_windows_inner_from_hwnd( - &self, - hwnd: HWND, - ) -> Option> { - self.raw_window_handles - .read() - .iter() - .find(|entry| *entry == &hwnd) - .and_then(|hwnd| try_get_window_inner(*hwnd)) - } - - #[inline] - pub fn run_foreground_tasks(&self) { - for runnable in self.main_receiver.drain() { - runnable.run(); - } - } +pub(crate) struct WindowsPlatformState { + callbacks: PlatformCallbacks, + pub(crate) settings: WindowsPlatformSystemSettings, + // NOTE: standard cursor handles don't need to close. + pub(crate) current_cursor: HCURSOR, } #[derive(Default)] -struct Callbacks { +struct PlatformCallbacks { open_urls: Option)>>, quit: Option>, reopen: Option>, @@ -97,53 +66,16 @@ struct Callbacks { validate_app_menu_command: Option bool>>, } -enum WindowsMessageWaitResult { - ForegroundExecution, - WindowsMessage(MSG), - Error, -} - -impl WindowsPlatformSystemSettings { +impl WindowsPlatformState { fn new() -> Self { - let mut settings = Self::default(); - settings.update_all(); - settings - } + let callbacks = PlatformCallbacks::default(); + let settings = WindowsPlatformSystemSettings::new(); + let current_cursor = load_cursor(CursorStyle::Arrow); - 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; + Self { + callbacks, + settings, + current_cursor, } } } @@ -166,33 +98,31 @@ impl WindowsPlatform { log::info!("Using cosmic text system."); Arc::new(CosmicTextSystem::new()) as Arc }; - let callbacks = Mutex::new(Callbacks::default()); - let raw_window_handles = RwLock::new(SmallVec::new()); - let settings = RefCell::new(WindowsPlatformSystemSettings::new()); let icon = load_icon().unwrap_or_default(); - let current_cursor = Cell::new(load_cursor(CursorStyle::Arrow)); - let inner = Rc::new(WindowsPlatformInner { + let state = RefCell::new(WindowsPlatformState::new()); + let raw_window_handles = RwLock::new(SmallVec::new()); + + Self { + state, + raw_window_handles, + icon, + main_receiver, background_executor, foreground_executor, - main_receiver, text_system, - callbacks, - raw_window_handles, dispatch_event, - settings, - icon, - current_cursor, - }); - Self { inner } + } } #[inline] fn run_foreground_tasks(&self) { - self.inner.run_foreground_tasks(); + for runnable in self.main_receiver.drain() { + runnable.run(); + } } fn redraw_all(&self) { - for handle in self.inner.raw_window_handles.read().iter() { + for handle in self.raw_window_handles.read().iter() { unsafe { RedrawWindow( *handle, @@ -203,24 +133,75 @@ impl WindowsPlatform { } } } + + pub fn try_get_windows_inner_from_hwnd(&self, hwnd: HWND) -> Option> { + self.raw_window_handles + .read() + .iter() + .find(|entry| *entry == &hwnd) + .and_then(|hwnd| try_get_window_inner(*hwnd)) + } + + #[inline] + fn post_message(&self, message: u32, wparam: WPARAM, lparam: LPARAM) { + self.raw_window_handles + .read() + .iter() + .for_each(|handle| unsafe { + PostMessageW(*handle, message, wparam, lparam).log_err(); + }); + } + + fn close_one_window(&self, target_window: HWND) -> bool { + let mut lock = self.raw_window_handles.write(); + let index = lock + .iter() + .position(|handle| *handle == target_window) + .unwrap(); + lock.remove(index); + + lock.is_empty() + } + + fn update_system_settings(&self) { + let mut lock = self.state.borrow_mut(); + // mouse wheel + { + let (scroll_chars, scroll_lines) = lock.settings.mouse_wheel_settings.update(); + if let Some(scroll_chars) = scroll_chars { + self.post_message( + MOUSE_WHEEL_SETTINGS_CHANGED, + WPARAM(scroll_chars as usize), + LPARAM(MOUSE_WHEEL_SETTINGS_SCROLL_CHARS_CHANGED), + ); + } + if let Some(scroll_lines) = scroll_lines { + self.post_message( + MOUSE_WHEEL_SETTINGS_CHANGED, + WPARAM(scroll_lines as usize), + LPARAM(MOUSE_WHEEL_SETTINGS_SCROLL_LINES_CHANGED), + ); + } + } + } } impl Platform for WindowsPlatform { fn background_executor(&self) -> BackgroundExecutor { - self.inner.background_executor.clone() + self.background_executor.clone() } fn foreground_executor(&self) -> ForegroundExecutor { - self.inner.foreground_executor.clone() + self.foreground_executor.clone() } fn text_system(&self) -> Arc { - self.inner.text_system.clone() + self.text_system.clone() } fn run(&self, on_finish_launching: Box) { on_finish_launching(); - let dispatch_event = self.inner.dispatch_event.to_raw(); + let dispatch_event = self.dispatch_event.to_raw(); let vsync_event = create_event().unwrap(); let timer_stop_event = create_event().unwrap(); let raw_timer_stop_event = timer_stop_event.to_raw(); @@ -248,16 +229,20 @@ impl Platform for WindowsPlatform { WAIT_EVENT(2) => { let mut msg = MSG::default(); unsafe { - while PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE).as_bool() { - if msg.message == WM_QUIT { - break 'a; + while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() { + match msg.message { + WM_QUIT => break 'a, + CLOSE_ONE_WINDOW => { + if self.close_one_window(HWND(msg.lParam.0)) { + break 'a; + } + } + WM_SETTINGCHANGE => self.update_system_settings(), + _ => { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } } - if msg.message == WM_SETTINGCHANGE { - self.inner.settings.borrow_mut().update_all(); - continue; - } - TranslateMessage(&msg); - DispatchMessageW(&msg); } } @@ -272,9 +257,8 @@ impl Platform for WindowsPlatform { } end_vsync_timer(raw_timer_stop_event); - let mut callbacks = self.inner.callbacks.lock(); - if let Some(callback) = callbacks.quit.as_mut() { - callback() + if let Some(ref mut callback) = self.state.borrow_mut().callbacks.quit { + callback(); } } @@ -340,17 +324,12 @@ impl Platform for WindowsPlatform { } fn primary_display(&self) -> Option> { - if let Some(display) = WindowsDisplay::primary_monitor() { - Some(Rc::new(display) as Rc) - } else { - None - } + WindowsDisplay::primary_monitor().map(|display| Rc::new(display) as Rc) } fn active_window(&self) -> Option { let active_window_hwnd = unsafe { GetActiveWindow() }; - self.inner - .try_get_windows_inner_from_hwnd(active_window_hwnd) + self.try_get_windows_inner_from_hwnd(active_window_hwnd) .map(|inner| inner.handle) } @@ -359,7 +338,21 @@ impl Platform for WindowsPlatform { handle: AnyWindowHandle, options: WindowParams, ) -> Box { - Box::new(WindowsWindow::new(self.inner.clone(), handle, options)) + let lock = self.state.borrow(); + let window = WindowsWindow::new( + handle, + options, + self.icon, + self.foreground_executor.clone(), + self.main_receiver.clone(), + lock.settings.mouse_wheel_settings, + lock.current_cursor, + ); + drop(lock); + let handle = window.get_raw_handle(); + self.raw_window_handles.write().push(handle); + + Box::new(window) } // todo(windows) @@ -379,9 +372,8 @@ impl Platform for WindowsPlatform { .detach(); } - // todo(windows) fn on_open_urls(&self, callback: Box)>) { - self.inner.callbacks.lock().open_urls = Some(callback); + self.state.borrow_mut().callbacks.open_urls = Some(callback); } fn prompt_for_paths(&self, options: PathPromptOptions) -> Receiver>> { @@ -501,26 +493,26 @@ impl Platform for WindowsPlatform { } fn on_quit(&self, callback: Box) { - self.inner.callbacks.lock().quit = Some(callback); + self.state.borrow_mut().callbacks.quit = Some(callback); } fn on_reopen(&self, callback: Box) { - self.inner.callbacks.lock().reopen = Some(callback); + self.state.borrow_mut().callbacks.reopen = Some(callback); } // todo(windows) fn set_menus(&self, menus: Vec, keymap: &Keymap) {} fn on_app_menu_action(&self, callback: Box) { - self.inner.callbacks.lock().app_menu_action = Some(callback); + self.state.borrow_mut().callbacks.app_menu_action = Some(callback); } fn on_will_open_app_menu(&self, callback: Box) { - self.inner.callbacks.lock().will_open_app_menu = Some(callback); + self.state.borrow_mut().callbacks.will_open_app_menu = Some(callback); } fn on_validate_app_menu_command(&self, callback: Box bool>) { - self.inner.callbacks.lock().validate_app_menu_command = Some(callback); + self.state.borrow_mut().callbacks.validate_app_menu_command = Some(callback); } fn os_name(&self) -> &'static str { @@ -667,7 +659,9 @@ impl Platform for WindowsPlatform { } fn set_cursor_style(&self, style: CursorStyle) { - self.inner.current_cursor.set(load_cursor(style)); + let hcursor = load_cursor(style); + self.post_message(CURSOR_STYLE_CHANGED, WPARAM(0), LPARAM(hcursor.0)); + self.state.borrow_mut().current_cursor = hcursor; } // todo(windows) @@ -699,10 +693,10 @@ impl Platform for WindowsPlatform { fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task> { let mut password = password.to_vec(); - let mut username = username.encode_utf16().chain(once(0)).collect_vec(); + let mut username = username.encode_utf16().chain(Some(0)).collect_vec(); let mut target_name = windows_credentials_target_name(url) .encode_utf16() - .chain(once(0)) + .chain(Some(0)) .collect_vec(); self.foreground_executor().spawn(async move { let credentials = CREDENTIALW { @@ -724,7 +718,7 @@ impl Platform for WindowsPlatform { fn read_credentials(&self, url: &str) -> Task)>>> { let mut target_name = windows_credentials_target_name(url) .encode_utf16() - .chain(once(0)) + .chain(Some(0)) .collect_vec(); self.foreground_executor().spawn(async move { let mut credentials: *mut CREDENTIALW = std::ptr::null_mut(); @@ -757,7 +751,7 @@ impl Platform for WindowsPlatform { fn delete_credentials(&self, url: &str) -> Task> { let mut target_name = windows_credentials_target_name(url) .encode_utf16() - .chain(once(0)) + .chain(Some(0)) .collect_vec(); self.foreground_executor().spawn(async move { unsafe { CredDeleteW(PCWSTR::from_raw(target_name.as_ptr()), CRED_TYPE_GENERIC, 0)? }; diff --git a/crates/gpui/src/platform/windows/system_settings.rs b/crates/gpui/src/platform/windows/system_settings.rs new file mode 100644 index 0000000000..af670b468a --- /dev/null +++ b/crates/gpui/src/platform/windows/system_settings.rs @@ -0,0 +1,81 @@ +use std::ffi::{c_uint, c_void}; + +use util::ResultExt; +use windows::Win32::UI::WindowsAndMessaging::{ + SystemParametersInfoW, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, + SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, +}; + +/// 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 { + pub(crate) mouse_wheel_settings: MouseWheelSettings, +} + +#[derive(Default, Debug, Clone, Copy)] +pub(crate) struct MouseWheelSettings { + /// SEE: SPI_GETWHEELSCROLLCHARS + pub(crate) wheel_scroll_chars: u32, + /// SEE: SPI_GETWHEELSCROLLLINES + pub(crate) wheel_scroll_lines: u32, +} + +impl WindowsPlatformSystemSettings { + pub(crate) fn new() -> Self { + let mut settings = Self::default(); + settings.init(); + settings + } + + fn init(&mut self) { + self.mouse_wheel_settings.update(); + } +} + +impl MouseWheelSettings { + pub(crate) fn update(&mut self) -> (Option, Option) { + ( + self.update_wheel_scroll_chars(), + self.update_wheel_scroll_lines(), + ) + } + + fn update_wheel_scroll_chars(&mut self) -> Option { + 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 { + self.wheel_scroll_chars = value; + Some(value) + } else { + None + } + } + + fn update_wheel_scroll_lines(&mut self) -> Option { + 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 { + self.wheel_scroll_lines = value; + Some(value) + } else { + None + } + } +} diff --git a/crates/gpui/src/platform/windows/util.rs b/crates/gpui/src/platform/windows/util.rs index 5cb5c6c64e..e97652e9b9 100644 --- a/crates/gpui/src/platform/windows/util.rs +++ b/crates/gpui/src/platform/windows/util.rs @@ -74,6 +74,7 @@ pub(crate) unsafe fn set_window_long( } } +#[derive(Debug, Clone)] pub(crate) struct OwnedHandle(HANDLE); impl OwnedHandle { @@ -135,3 +136,19 @@ pub(crate) fn load_cursor(style: CursorStyle) -> HCURSOR { ) }) } + +#[inline] +pub(crate) fn logical_size(physical_size: Size, scale_factor: f32) -> Size { + Size { + width: px(physical_size.width.0 as f32 / scale_factor), + height: px(physical_size.height.0 as f32 / scale_factor), + } +} + +#[inline] +pub(crate) fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point { + Point { + x: px(x / scale_factor), + y: px(y / scale_factor), + } +} diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index 7c0738c4a1..b16adf6126 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -1,8 +1,7 @@ #![deny(unsafe_op_in_unsafe_fn)] use std::{ - cell::{Cell, RefCell}, - iter::once, + cell::RefCell, num::NonZeroIsize, path::PathBuf, rc::{Rc, Weak}, @@ -13,197 +12,122 @@ use std::{ use ::util::ResultExt; use anyhow::Context; -use blade_graphics as gpu; +use async_task::Runnable; use futures::channel::oneshot::{self, Receiver}; use itertools::Itertools; use raw_window_handle as rwh; use smallvec::SmallVec; -use std::result::Result; use windows::{ core::*, Win32::{ Foundation::*, Graphics::Gdi::*, System::{Com::*, LibraryLoader::*, Ole::*, SystemServices::*}, - UI::{ - Controls::*, - HiDpi::*, - Input::{Ime::*, KeyboardAndMouse::*}, - Shell::*, - WindowsAndMessaging::*, - }, + UI::{Controls::*, HiDpi::*, Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*}, }, }; -use crate::platform::blade::{BladeRenderer, BladeSurfaceConfig}; +use crate::platform::blade::BladeRenderer; use crate::*; -pub(crate) struct WindowsWindowInner { +pub(crate) struct WindowsWindow(pub Rc); + +pub struct WindowsWindowState { + pub origin: Point, + pub physical_size: Size, + pub scale_factor: f32, + + pub callbacks: Callbacks, + pub input_handler: Option, + + pub renderer: BladeRenderer, + + pub click_state: ClickState, + pub mouse_wheel_settings: MouseWheelSettings, + pub current_cursor: HCURSOR, + + pub display: WindowsDisplay, + fullscreen: Option, hwnd: HWND, - origin: Cell>, - physical_size: Cell>, - scale_factor: Cell, - input_handler: Cell>, - renderer: RefCell, - callbacks: RefCell, - platform_inner: Rc, - pub(crate) handle: AnyWindowHandle, - hide_title_bar: bool, - display: RefCell>, - click_state: RefCell, - fullscreen: Cell>, } -impl WindowsWindowInner { +pub(crate) struct WindowsWindowStatePtr { + hwnd: HWND, + pub(crate) state: RefCell, + pub(crate) handle: AnyWindowHandle, + pub(crate) hide_title_bar: bool, + pub(crate) executor: ForegroundExecutor, + pub(crate) main_receiver: flume::Receiver, +} + +impl WindowsWindowState { fn new( hwnd: HWND, - cs: &CREATESTRUCTW, - platform_inner: Rc, - handle: AnyWindowHandle, - hide_title_bar: bool, - display: Rc, transparent: bool, + cs: &CREATESTRUCTW, + mouse_wheel_settings: MouseWheelSettings, + current_cursor: HCURSOR, + display: WindowsDisplay, ) -> Self { - let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32; - let origin = Cell::new(Point { - x: DevicePixels(cs.x), - y: DevicePixels(cs.y), - }); - let physical_size = Cell::new(Size { - width: DevicePixels(cs.cx), - height: DevicePixels(cs.cy), - }); - let scale_factor = Cell::new(monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32); - let input_handler = Cell::new(None); - struct RawWindow { - hwnd: isize, - } - impl rwh::HasWindowHandle for RawWindow { - fn window_handle(&self) -> Result, rwh::HandleError> { - Ok(unsafe { - let hwnd = NonZeroIsize::new_unchecked(self.hwnd); - let mut handle = rwh::Win32WindowHandle::new(hwnd); - let hinstance = get_window_long(HWND(self.hwnd), GWLP_HINSTANCE); - handle.hinstance = NonZeroIsize::new(hinstance); - rwh::WindowHandle::borrow_raw(handle.into()) - }) - } - } - impl rwh::HasDisplayHandle for RawWindow { - fn display_handle(&self) -> Result, rwh::HandleError> { - let handle = rwh::WindowsDisplayHandle::new(); - Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) }) - } - } - - let raw = RawWindow { hwnd: hwnd.0 }; - let gpu = Arc::new( - unsafe { - gpu::Context::init_windowed( - &raw, - gpu::ContextDesc { - validation: false, - capture: false, - overlay: false, - }, - ) - } - .unwrap(), - ); - let config = BladeSurfaceConfig { - size: gpu::Extent::default(), - transparent, + let origin = point(cs.x.into(), cs.y.into()); + let physical_size = size(cs.cx.into(), cs.cy.into()); + let scale_factor = { + let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32; + monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32 }; - let renderer = RefCell::new(BladeRenderer::new(gpu, config)); - let callbacks = RefCell::new(Callbacks::default()); - let display = RefCell::new(display); - let click_state = RefCell::new(ClickState::new()); - let fullscreen = Cell::new(None); + let renderer = windows_renderer::windows_renderer(hwnd, transparent); + let callbacks = Callbacks::default(); + let input_handler = None; + let click_state = ClickState::new(); + let fullscreen = None; + Self { - hwnd, origin, physical_size, scale_factor, + callbacks, input_handler, renderer, - callbacks, - platform_inner, - handle, - hide_title_bar, - display, click_state, + mouse_wheel_settings, + current_cursor, + display, fullscreen, + hwnd, } } - fn is_maximized(&self) -> bool { + #[inline] + pub(crate) fn is_fullscreen(&self) -> bool { + self.fullscreen.is_some() + } + + pub(crate) fn is_maximized(&self) -> bool { !self.is_fullscreen() && unsafe { IsZoomed(self.hwnd) }.as_bool() } - fn is_minimized(&self) -> bool { - unsafe { IsIconic(self.hwnd) }.as_bool() - } - - fn is_fullscreen(&self) -> bool { - let fullscreen = self.fullscreen.take(); - let is_fullscreen = fullscreen.is_some(); - self.fullscreen.set(fullscreen); - is_fullscreen - } - - async fn toggle_fullscreen(self: Rc) { - let StyleAndBounds { - style, - x, - y, - cx, - cy, - } = if let Some(state) = self.fullscreen.take() { - state - } else { - let style = WINDOW_STYLE(unsafe { get_window_long(self.hwnd, GWL_STYLE) } as _); - let mut rc = RECT::default(); - unsafe { GetWindowRect(self.hwnd, &mut rc) }.log_err(); - self.fullscreen.set(Some(StyleAndBounds { - style, - x: rc.left, - y: rc.top, - cx: rc.right - rc.left, - cy: rc.bottom - rc.top, - })); - let style = style - & !(WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CAPTION); - let bounds = self.display.borrow().clone().bounds(); - StyleAndBounds { - style, - x: bounds.left().0, - y: bounds.top().0, - cx: bounds.size.width.0, - cy: bounds.size.height.0, - } - }; - unsafe { set_window_long(self.hwnd, GWL_STYLE, style.0 as isize) }; - unsafe { - SetWindowPos( - self.hwnd, - HWND::default(), - x, - y, - cx, - cy, - SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER, - ) + fn bounds(&self) -> Bounds { + Bounds { + origin: self.origin, + size: self.physical_size, } - .log_err(); } - pub(crate) fn title_bar_padding(&self) -> Pixels { + /// get the logical size of the app's drawable area. + /// + /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as + /// whether the mouse collides with other elements of GPUI). + fn content_size(&self) -> Size { + logical_size(self.physical_size, self.scale_factor) + } + + fn title_bar_padding(&self) -> Pixels { // using USER_DEFAULT_SCREEN_DPI because GPUI handles the scale with the scale factor let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI) }; px(padding as f32) } - pub(crate) fn title_bar_top_offset(&self) -> Pixels { + fn title_bar_top_offset(&self) -> Pixels { if self.is_maximized() { self.title_bar_padding() * 2 } else { @@ -211,7 +135,7 @@ impl WindowsWindowInner { } } - pub(crate) fn title_bar_height(&self) -> Pixels { + fn title_bar_height(&self) -> Pixels { // todo(windows) this is hard set to match the ui title bar // in the future the ui title bar component will report the size px(32.) + self.title_bar_top_offset() @@ -223,1032 +147,76 @@ impl WindowsWindowInner { px(36.) } - fn get_titlebar_rect(&self) -> anyhow::Result { + pub(crate) fn get_titlebar_rect(&self) -> anyhow::Result { let height = self.title_bar_height(); let mut rect = RECT::default(); unsafe { GetClientRect(self.hwnd, &mut rect) }?; - rect.bottom = rect.top + ((height.0 * self.scale_factor.get()).round() as i32); + rect.bottom = rect.top + ((height.0 * self.scale_factor).round() as i32); Ok(rect) } +} - fn is_virtual_key_pressed(&self, vkey: VIRTUAL_KEY) -> bool { - unsafe { GetKeyState(vkey.0 as i32) < 0 } +impl WindowsWindowStatePtr { + fn new(context: &WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Rc { + let state = RefCell::new(WindowsWindowState::new( + hwnd, + context.transparent, + cs, + context.mouse_wheel_settings, + context.current_cursor, + context.display, + )); + + Rc::new(Self { + state, + hwnd, + handle: context.handle, + hide_title_bar: context.hide_title_bar, + executor: context.executor.clone(), + main_receiver: context.main_receiver.clone(), + }) } - 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), - platform: self.is_virtual_key_pressed(VK_LWIN) || self.is_virtual_key_pressed(VK_RWIN), - function: false, - } - } - - /// mark window client rect to be re-drawn - /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect - pub(crate) fn invalidate_client_area(&self) { - unsafe { InvalidateRect(self.hwnd, None, FALSE) }; - } - - fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { - let handled = match msg { - WM_ACTIVATE => self.handle_activate_msg(wparam), - WM_CREATE => self.handle_create_msg(lparam), - WM_MOVE => self.handle_move_msg(lparam), - WM_SIZE => self.handle_size_msg(lparam), - WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => self.handle_size_move_loop(), - WM_EXITSIZEMOVE | WM_EXITMENULOOP => self.handle_size_move_loop_exit(), - WM_TIMER => self.handle_timer_msg(wparam), - WM_NCCALCSIZE => self.handle_calc_client_size(wparam, lparam), - WM_DPICHANGED => self.handle_dpi_changed_msg(wparam, lparam), - WM_NCHITTEST => self.handle_hit_test_msg(msg, wparam, lparam), - WM_PAINT => self.handle_paint_msg(), - WM_CLOSE => self.handle_close_msg(), - WM_DESTROY => self.handle_destroy_msg(), - WM_MOUSEMOVE => self.handle_mouse_move_msg(lparam, wparam), - WM_NCMOUSEMOVE => self.handle_nc_mouse_move_msg(lparam), - WM_NCLBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Left, wparam, lparam), - WM_NCRBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Right, wparam, lparam), - WM_NCMBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Middle, wparam, lparam), - WM_NCLBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Left, wparam, lparam), - WM_NCRBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Right, wparam, lparam), - WM_NCMBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Middle, wparam, lparam), - 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 => self.handle_xbutton_msg(wparam, lparam, Self::handle_mouse_down_msg), - 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 => self.handle_xbutton_msg(wparam, lparam, Self::handle_mouse_up_msg), - WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(wparam, lparam), - WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(wparam, lparam), - WM_SYSKEYDOWN => self.handle_syskeydown_msg(wparam, lparam), - WM_SYSKEYUP => self.handle_syskeyup_msg(wparam), - 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), - WM_IME_STARTCOMPOSITION => self.handle_ime_position(), - WM_IME_COMPOSITION => self.handle_ime_composition(lparam), - WM_SETCURSOR => self.handle_set_cursor(lparam), - _ => None, - }; - if let Some(n) = handled { - LRESULT(n) - } else { - unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) } - } - } - - fn handle_move_msg(&self, lparam: LPARAM) -> Option { - let x = lparam.signed_loword() as i32; - let y = lparam.signed_hiword() as i32; - self.origin.set(Point { - x: DevicePixels(x), - y: DevicePixels(y), - }); - let size = self.physical_size.get(); - let center_x = x + size.width.0 / 2; - let center_y = y + size.height.0 / 2; - let monitor_bounds = self.display.borrow().bounds(); - if center_x < monitor_bounds.left().0 - || center_x > monitor_bounds.right().0 - || center_y < monitor_bounds.top().0 - || center_y > monitor_bounds.bottom().0 - { - // center of the window may have moved to another monitor - let monitor = unsafe { MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONULL) }; - if !monitor.is_invalid() && self.display.borrow().handle != monitor { - // we will get the same monitor if we only have one - (*self.display.borrow_mut()) = Rc::new(WindowsDisplay::new_with_handle(monitor)); - } - } - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.moved.as_mut() { - callback() - } - Some(0) - } - - fn handle_size_msg(&self, lparam: LPARAM) -> Option { - let width = lparam.loword().max(1) as i32; - let height = lparam.hiword().max(1) as i32; - let scale_factor = self.scale_factor.get(); - let new_physical_size = Size { - width: DevicePixels(width), - height: DevicePixels(height), - }; - self.physical_size.set(new_physical_size); - self.renderer.borrow_mut().update_drawable_size(Size { - width: width as f64, - height: height as f64, - }); - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.resize.as_mut() { - let logical_size = logical_size(new_physical_size, scale_factor); - callback(logical_size, scale_factor); - } - Some(0) - } - - fn handle_size_move_loop(&self) -> Option { - unsafe { - let ret = SetTimer(self.hwnd, SIZE_MOVE_LOOP_TIMER_ID, USER_TIMER_MINIMUM, None); - if ret == 0 { - log::error!( - "unable to create timer: {}", - std::io::Error::last_os_error() - ); - } - } - None - } - - fn handle_size_move_loop_exit(&self) -> Option { - unsafe { - KillTimer(self.hwnd, SIZE_MOVE_LOOP_TIMER_ID).log_err(); - } - None - } - - fn handle_timer_msg(&self, wparam: WPARAM) -> Option { - if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID { - self.platform_inner.run_foreground_tasks(); - self.handle_paint_msg(); - return Some(0); - } - None - } - - fn handle_paint_msg(&self) -> Option { - let mut paint_struct = PAINTSTRUCT::default(); - let _hdc = unsafe { BeginPaint(self.hwnd, &mut paint_struct) }; - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(request_frame) = callbacks.request_frame.as_mut() { - request_frame(); - } - unsafe { EndPaint(self.hwnd, &paint_struct) }; - Some(0) - } - - fn handle_close_msg(&self) -> Option { - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.should_close.as_mut() { - if callback() { - return Some(0); - } - } - None - } - - fn handle_destroy_msg(&self) -> Option { - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.close.take() { - callback() - } - 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 { - unsafe { PostQuitMessage(0) }; - }) - .detach(); - } - Some(1) - } - - fn handle_mouse_move_msg(&self, lparam: LPARAM, wparam: WPARAM) -> Option { - 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 x = lparam.signed_loword() as f32; - let y = lparam.signed_hiword() as f32; - let scale_factor = self.scale_factor.get(); - let event = MouseMoveEvent { - position: logical_point(x, y, scale_factor), - pressed_button, - modifiers: self.current_modifiers(), - }; - if callback(PlatformInput::MouseMove(event)).default_prevented { - return Some(0); - } - } - Some(1) - } - - 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(); - 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 - } - } - - fn parse_keydown_msg_keystroke(&self, wparam: WPARAM) -> Option { - let vk_code = wparam.loword(); - - 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; - } - } - - if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 { - let offset = vk_code - VK_F1.0; - return Some(Keystroke { - modifiers, - key: format!("f{}", offset + 1), - ime_key: None, - }); - } - - 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(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 { - let mut modifiers = self.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() { - modifiers.shift = false; - } - let key = match first_char { - ' ' => "space".to_string(), - first_char => first_char.to_lowercase().to_string(), - }; - Some(Keystroke { - modifiers, - key, - ime_key: Some(first_char.to_string()), - }) - } - } - - fn handle_syskeydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option { - // 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 None; - }; - let Some(ref mut func) = self.callbacks.borrow_mut().input else { - return None; - }; - let event = KeyDownEvent { - keystroke, - is_held: lparam.0 & (0x1 << 30) > 0, - }; - if func(PlatformInput::KeyDown(event)).default_prevented { - self.invalidate_client_area(); - return Some(0); - } - None - } - - fn handle_syskeyup_msg(&self, wparam: WPARAM) -> Option { - // 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 None; - }; - let Some(ref mut func) = self.callbacks.borrow_mut().input else { - return None; - }; - let event = KeyUpEvent { keystroke }; - if func(PlatformInput::KeyUp(event)).default_prevented { - self.invalidate_client_area(); - return Some(0); - } - None - } - - fn handle_keydown_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option { - let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else { - return Some(1); - }; - let Some(ref mut func) = self.callbacks.borrow_mut().input else { - return Some(1); - }; - let event = KeyDownEvent { - keystroke, - is_held: lparam.0 & (0x1 << 30) > 0, - }; - if func(PlatformInput::KeyDown(event)).default_prevented { - self.invalidate_client_area(); - return Some(0); - } - Some(1) - } - - fn handle_keyup_msg(&self, _msg: u32, wparam: WPARAM) -> Option { - let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else { - return Some(1); - }; - let Some(ref mut func) = self.callbacks.borrow_mut().input else { - return Some(1); - }; - let event = KeyUpEvent { keystroke }; - if func(PlatformInput::KeyUp(event)).default_prevented { - self.invalidate_client_area(); - return Some(0); - } - Some(1) - } - - fn handle_char_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option { - let Some(keystroke) = self.parse_char_msg_keystroke(wparam) else { - return Some(1); - }; - let mut callbacks = self.callbacks.borrow_mut(); - let Some(ref mut func) = callbacks.input else { - return Some(1); - }; - let ime_key = keystroke.ime_key.clone(); - let event = KeyDownEvent { - keystroke, - is_held: lparam.0 & (0x1 << 30) > 0, - }; - - let dispatch_event_result = func(PlatformInput::KeyDown(event)); - if dispatch_event_result.default_prevented || !dispatch_event_result.propagate { - self.invalidate_client_area(); - return Some(0); - } - drop(callbacks); - let Some(ime_char) = ime_key else { - return Some(1); - }; - let Some(mut input_handler) = self.input_handler.take() else { - return Some(1); - }; - input_handler.replace_text_in_range(None, &ime_char); - self.input_handler.set(Some(input_handler)); - self.invalidate_client_area(); - Some(0) - } - - fn handle_mouse_down_msg(&self, button: MouseButton, lparam: LPARAM) -> Option { - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.input.as_mut() { - let x = lparam.signed_loword() as f32; - let y = lparam.signed_hiword() as f32; - let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32)); - let click_count = self.click_state.borrow_mut().update(button, physical_point); - let scale_factor = self.scale_factor.get(); - let event = MouseDownEvent { - button, - position: logical_point(x, y, scale_factor), - modifiers: self.current_modifiers(), - click_count, - first_mouse: false, - }; - if callback(PlatformInput::MouseDown(event)).default_prevented { - return Some(0); - } - } - Some(1) - } - - fn handle_mouse_up_msg(&self, button: MouseButton, lparam: LPARAM) -> Option { - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.input.as_mut() { - let x = lparam.signed_loword() as f32; - let y = lparam.signed_hiword() as f32; - let click_count = self.click_state.borrow().current_count; - let scale_factor = self.scale_factor.get(); - let event = MouseUpEvent { - button, - position: logical_point(x, y, scale_factor), - modifiers: self.current_modifiers(), - click_count, - }; - if callback(PlatformInput::MouseUp(event)).default_prevented { - return Some(0); - } - } - Some(1) - } - - fn handle_xbutton_msg( - &self, - wparam: WPARAM, - lparam: LPARAM, - handler: impl Fn(&Self, MouseButton, LPARAM) -> Option, - ) -> Option { - let nav_dir = match wparam.hiword() { - XBUTTON1 => NavigationDirection::Back, - XBUTTON2 => NavigationDirection::Forward, - _ => return Some(1), - }; - handler(self, MouseButton::Navigate(nav_dir), lparam) - } - - fn handle_mouse_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option { - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.input.as_mut() { - let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) - * self.platform_inner.settings.borrow().wheel_scroll_lines as f32; - let mut cursor_point = POINT { - x: lparam.signed_loword().into(), - y: lparam.signed_hiword().into(), - }; - unsafe { ScreenToClient(self.hwnd, &mut cursor_point) }; - let scale_factor = self.scale_factor.get(); - let event = crate::ScrollWheelEvent { - position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), - delta: ScrollDelta::Lines(Point { - x: 0.0, - y: wheel_distance, - }), - modifiers: self.current_modifiers(), - touch_phase: TouchPhase::Moved, - }; - callback(PlatformInput::ScrollWheel(event)); - return Some(0); - } - Some(1) - } - - fn handle_mouse_horizontal_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option { - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.input.as_mut() { - let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) - * self.platform_inner.settings.borrow().wheel_scroll_chars as f32; - let mut cursor_point = POINT { - x: lparam.signed_loword().into(), - y: lparam.signed_hiword().into(), - }; - unsafe { ScreenToClient(self.hwnd, &mut cursor_point) }; - let scale_factor = self.scale_factor.get(); - let event = crate::ScrollWheelEvent { - position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), - delta: ScrollDelta::Lines(Point { - x: wheel_distance, - y: 0.0, - }), - modifiers: self.current_modifiers(), - touch_phase: TouchPhase::Moved, - }; - if callback(PlatformInput::ScrollWheel(event)).default_prevented { - return Some(0); - } - } - Some(1) - } - - fn handle_ime_position(&self) -> Option { - unsafe { - let ctx = ImmGetContext(self.hwnd); - let Some(mut input_handler) = self.input_handler.take() else { - return Some(1); - }; - let caret_range = input_handler.selected_text_range().unwrap_or_default(); - let caret_position = input_handler.bounds_for_range(caret_range).unwrap(); - self.input_handler.set(Some(input_handler)); - let scale_factor = self.scale_factor.get(); - let config = CANDIDATEFORM { - dwStyle: CFS_CANDIDATEPOS, - // logical to physical - ptCurrentPos: POINT { - x: (caret_position.origin.x.0 * scale_factor) as i32, - y: (caret_position.origin.y.0 * scale_factor) as i32 - + ((caret_position.size.height.0 * scale_factor) as i32 / 2), - }, - ..Default::default() - }; - ImmSetCandidateWindow(ctx, &config as _); - ImmReleaseContext(self.hwnd, ctx); - Some(0) - } - } - - fn parse_ime_compostion_string(&self) -> Option<(String, usize)> { - unsafe { - let ctx = ImmGetContext(self.hwnd); - let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0); - let result = if string_len >= 0 { - let mut buffer = vec![0u8; string_len as usize + 2]; - ImmGetCompositionStringW( - ctx, - GCS_COMPSTR, - Some(buffer.as_mut_ptr() as _), - string_len as _, - ); - let wstring = std::slice::from_raw_parts::( - buffer.as_mut_ptr().cast::(), - string_len as usize / 2, - ); - let string = String::from_utf16_lossy(wstring); - Some((string, string_len as usize / 2)) - } else { - None - }; - ImmReleaseContext(self.hwnd, ctx); - result - } - } - - fn retrieve_composition_cursor_position(&self) -> usize { - unsafe { - let ctx = ImmGetContext(self.hwnd); - let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0); - ImmReleaseContext(self.hwnd, ctx); - ret as usize - } - } - - fn parse_ime_compostion_result(&self) -> Option { - unsafe { - let ctx = ImmGetContext(self.hwnd); - let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0); - let result = if string_len >= 0 { - let mut buffer = vec![0u8; string_len as usize + 2]; - ImmGetCompositionStringW( - ctx, - GCS_RESULTSTR, - Some(buffer.as_mut_ptr() as _), - string_len as _, - ); - let wstring = std::slice::from_raw_parts::( - buffer.as_mut_ptr().cast::(), - string_len as usize / 2, - ); - let string = String::from_utf16_lossy(wstring); - Some(string) - } else { - None - }; - ImmReleaseContext(self.hwnd, ctx); - result - } - } - - fn handle_ime_composition(&self, lparam: LPARAM) -> Option { - let mut ime_input = None; - if lparam.0 as u32 & GCS_COMPSTR.0 > 0 { - let Some((string, string_len)) = self.parse_ime_compostion_string() else { - return None; - }; - let Some(mut input_handler) = self.input_handler.take() else { - return None; - }; - input_handler.replace_and_mark_text_in_range( - None, - string.as_str(), - Some(0..string_len), - ); - self.input_handler.set(Some(input_handler)); - ime_input = Some(string); - } - if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 { - let Some(ref comp_string) = ime_input else { - return None; - }; - let caret_pos = self.retrieve_composition_cursor_position(); - let Some(mut input_handler) = self.input_handler.take() else { - return None; - }; - input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos)); - self.input_handler.set(Some(input_handler)); - } - if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 { - let Some(comp_result) = self.parse_ime_compostion_result() else { - return None; - }; - let Some(mut input_handler) = self.input_handler.take() else { - return Some(1); - }; - input_handler.replace_text_in_range(None, &comp_result); - self.input_handler.set(Some(input_handler)); - self.invalidate_client_area(); - return Some(0); - } - // currently, we don't care other stuff - None - } - - fn handle_drag_drop(&self, input: PlatformInput) { - let mut callbacks = self.callbacks.borrow_mut(); - let Some(ref mut func) = callbacks.input else { - return; - }; - func(input); - } - - /// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize - fn handle_calc_client_size(&self, wparam: WPARAM, lparam: LPARAM) -> Option { - if !self.hide_title_bar || self.is_fullscreen() { - return None; - } - - if wparam.0 == 0 { - return None; - } - - let dpi = unsafe { GetDpiForWindow(self.hwnd) }; - - let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) }; - let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) }; - let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) }; - - // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure - let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS; - let mut requested_client_rect = unsafe { &mut ((*params).rgrc) }; - - requested_client_rect[0].right -= frame_x + padding; - requested_client_rect[0].left += frame_x + padding; - requested_client_rect[0].bottom -= frame_y + padding; - - Some(0) - } - - fn handle_activate_msg(&self, wparam: WPARAM) -> Option { - if self.hide_title_bar { - if let Some(titlebar_rect) = self.get_titlebar_rect().log_err() { - unsafe { InvalidateRect(self.hwnd, Some(&titlebar_rect), FALSE) }; - } - } - let activated = wparam.loword() > 0; - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(mut cb) = callbacks.active_status_change.as_mut() { - cb(activated); - } - None - } - - fn handle_create_msg(&self, _lparam: LPARAM) -> Option { - let mut size_rect = RECT::default(); - unsafe { GetWindowRect(self.hwnd, &mut size_rect).log_err() }; - - let width = size_rect.right - size_rect.left; - let height = size_rect.bottom - size_rect.top; - - self.physical_size.set(Size { - width: DevicePixels(width), - height: DevicePixels(height), - }); - - if self.hide_title_bar { - // Inform the application of the frame change to force redrawing with the new - // client area that is extended into the title bar - unsafe { - SetWindowPos( - self.hwnd, - HWND::default(), - size_rect.left, - size_rect.top, - width, - height, - SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE, - ) - .log_err() - }; - } - - Some(0) - } - - fn handle_dpi_changed_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option { - let new_dpi = wparam.loword() as f32; - let scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32; - self.scale_factor.set(scale_factor); - let rect = unsafe { &*(lparam.0 as *const RECT) }; - let width = rect.right - rect.left; - let height = rect.bottom - rect.top; - // this will emit `WM_SIZE` and `WM_MOVE` right here - // even before this function returns - // the new size is handled in `WM_SIZE` - unsafe { - SetWindowPos( - self.hwnd, - None, - rect.left, - rect.top, - width, - height, - SWP_NOZORDER | SWP_NOACTIVATE, - ) - .context("unable to set window position after dpi has changed") - .log_err(); - } - self.invalidate_client_area(); - Some(0) - } - - fn handle_hit_test_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option { - if !self.hide_title_bar { - return None; - } - - // default handler for resize areas - let hit = unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }; - if matches!( - hit.0 as u32, - HTNOWHERE - | HTRIGHT - | HTLEFT - | HTTOPLEFT - | HTTOP - | HTTOPRIGHT - | HTBOTTOMRIGHT - | HTBOTTOM - | HTBOTTOMLEFT - ) { - return Some(hit.0); - } - - if self.is_fullscreen() { - return Some(HTCLIENT as _); - } - - let dpi = unsafe { GetDpiForWindow(self.hwnd) }; - let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) }; - let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) }; - - let mut cursor_point = POINT { - x: lparam.signed_loword().into(), - y: lparam.signed_hiword().into(), - }; - unsafe { ScreenToClient(self.hwnd, &mut cursor_point) }; - if cursor_point.y > 0 && cursor_point.y < frame_y + padding { - return Some(HTTOP as _); - } - - let titlebar_rect = self.get_titlebar_rect(); - if let Ok(titlebar_rect) = titlebar_rect { - if cursor_point.y < titlebar_rect.bottom { - let caption_btn_width = - (self.caption_button_width().0 * self.scale_factor.get()) as i32; - if cursor_point.x >= titlebar_rect.right - caption_btn_width { - return Some(HTCLOSE as _); - } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 { - return Some(HTMAXBUTTON as _); - } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 { - return Some(HTMINBUTTON as _); - } - - return Some(HTCAPTION as _); - } - } - - Some(HTCLIENT as _) - } - - fn handle_nc_mouse_move_msg(&self, lparam: LPARAM) -> Option { - if !self.hide_title_bar { - return None; - } - - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.input.as_mut() { - let mut cursor_point = POINT { - x: lparam.signed_loword().into(), - y: lparam.signed_hiword().into(), - }; - unsafe { ScreenToClient(self.hwnd, &mut cursor_point) }; - let scale_factor = self.scale_factor.get(); - let event = MouseMoveEvent { - position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), - pressed_button: None, - modifiers: self.current_modifiers(), - }; - if callback(PlatformInput::MouseMove(event)).default_prevented { - return Some(0); - } - } - None - } - - fn handle_nc_mouse_down_msg( - &self, - button: MouseButton, - wparam: WPARAM, - lparam: LPARAM, - ) -> Option { - if !self.hide_title_bar { - return None; - } - - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.input.as_mut() { - let mut cursor_point = POINT { - x: lparam.signed_loword().into(), - y: lparam.signed_hiword().into(), - }; - unsafe { ScreenToClient(self.hwnd, &mut cursor_point) }; - let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y)); - let click_count = self.click_state.borrow_mut().update(button, physical_point); - let scale_factor = self.scale_factor.get(); - let event = MouseDownEvent { - button, - position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), - modifiers: self.current_modifiers(), - click_count, - first_mouse: false, - }; - if callback(PlatformInput::MouseDown(event)).default_prevented { - return Some(0); - } - } - - // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc - matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0) - } - - fn handle_nc_mouse_up_msg( - &self, - button: MouseButton, - wparam: WPARAM, - lparam: LPARAM, - ) -> Option { - if !self.hide_title_bar { - return None; - } - - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callback) = callbacks.input.as_mut() { - let mut cursor_point = POINT { - x: lparam.signed_loword().into(), - y: lparam.signed_hiword().into(), - }; - unsafe { ScreenToClient(self.hwnd, &mut cursor_point) }; - let scale_factor = self.scale_factor.get(); - let event = MouseUpEvent { - button, - position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), - modifiers: self.current_modifiers(), - click_count: 1, - }; - if callback(PlatformInput::MouseUp(event)).default_prevented { - return Some(0); - } - } - drop(callbacks); - - if button == MouseButton::Left { - match wparam.0 as u32 { - HTMINBUTTON => unsafe { - ShowWindowAsync(self.hwnd, SW_MINIMIZE); - }, - HTMAXBUTTON => unsafe { - if self.is_maximized() { - ShowWindowAsync(self.hwnd, SW_NORMAL); - } else { - ShowWindowAsync(self.hwnd, SW_MAXIMIZE); - } - }, - HTCLOSE => unsafe { - PostMessageW(self.hwnd, WM_CLOSE, WPARAM::default(), LPARAM::default()) - .log_err(); - }, - _ => return None, - }; - return Some(0); - } - - None - } - - fn handle_set_cursor(&self, lparam: LPARAM) -> Option { - if matches!( - lparam.loword() as u32, - HTLEFT - | HTRIGHT - | HTTOP - | HTTOPLEFT - | HTTOPRIGHT - | HTBOTTOM - | HTBOTTOMLEFT - | HTBOTTOMRIGHT - ) { - return None; - } - unsafe { SetCursor(self.platform_inner.current_cursor.get()) }; - Some(1) + fn is_minimized(&self) -> bool { + unsafe { IsIconic(self.hwnd) }.as_bool() } } #[derive(Default)] -struct Callbacks { - request_frame: Option>, - input: Option DispatchEventResult>>, - active_status_change: Option>, - resize: Option, f32)>>, - moved: Option>, - should_close: Option bool>>, - close: Option>, - appearance_changed: Option>, -} - -pub(crate) struct WindowsWindow { - inner: Rc, - drag_drop_handler: IDropTarget, +pub(crate) struct Callbacks { + pub(crate) request_frame: Option>, + pub(crate) input: Option DispatchEventResult>>, + pub(crate) active_status_change: Option>, + pub(crate) resize: Option, f32)>>, + pub(crate) moved: Option>, + pub(crate) should_close: Option bool>>, + pub(crate) close: Option>, + pub(crate) appearance_changed: Option>, } struct WindowCreateContext { - inner: Option>, - platform_inner: Rc, + inner: Option>, handle: AnyWindowHandle, hide_title_bar: bool, - display: Rc, + display: WindowsDisplay, transparent: bool, + executor: ForegroundExecutor, + main_receiver: flume::Receiver, + mouse_wheel_settings: MouseWheelSettings, + current_cursor: HCURSOR, } impl WindowsWindow { pub(crate) fn new( - platform_inner: Rc, handle: AnyWindowHandle, options: WindowParams, + icon: HICON, + executor: ForegroundExecutor, + main_receiver: flume::Receiver, + mouse_wheel_settings: MouseWheelSettings, + current_cursor: HCURSOR, ) -> Self { - let classname = register_wnd_class(platform_inner.icon); + let classname = register_wnd_class(icon); let hide_title_bar = options .titlebar .as_ref() @@ -1272,16 +240,19 @@ impl WindowsWindow { let hinstance = get_module_handle(); let mut context = WindowCreateContext { inner: None, - platform_inner: platform_inner.clone(), handle, hide_title_bar, // todo(windows) move window to target monitor // options.display_id - display: Rc::new(WindowsDisplay::primary_monitor().unwrap()), + display: WindowsDisplay::primary_monitor().unwrap(), transparent: options.window_background != WindowBackgroundAppearance::Opaque, + executor, + main_receiver, + mouse_wheel_settings, + current_cursor, }; let lpparam = Some(&context as *const _ as *const _); - unsafe { + let raw_hwnd = unsafe { CreateWindowExW( WS_EX_APPWINDOW, classname, @@ -1297,34 +268,20 @@ impl WindowsWindow { lpparam, ) }; - let drag_drop_handler = { - let inner = context.inner.as_ref().unwrap(); - let handler = WindowsDragDropHandler(Rc::clone(inner)); - let drag_drop_handler: IDropTarget = handler.into(); - unsafe { - RegisterDragDrop(inner.hwnd, &drag_drop_handler) - .expect("unable to register drag-drop event") - }; - drag_drop_handler - }; - let wnd = Self { - inner: context.inner.unwrap(), - drag_drop_handler, - }; - platform_inner - .raw_window_handles - .write() - .push(wnd.inner.hwnd); + let state_ptr = Rc::clone(context.inner.as_ref().unwrap()); + register_drag_drop(state_ptr.clone()); + let wnd = Self(state_ptr); + + unsafe { ShowWindow(raw_hwnd, SW_SHOW) }; - unsafe { ShowWindow(wnd.inner.hwnd, SW_SHOW) }; wnd } } impl rwh::HasWindowHandle for WindowsWindow { - fn window_handle(&self) -> Result, rwh::HandleError> { + fn window_handle(&self) -> std::result::Result, rwh::HandleError> { let raw = - rwh::Win32WindowHandle::new(unsafe { NonZeroIsize::new_unchecked(self.inner.hwnd.0) }) + rwh::Win32WindowHandle::new(unsafe { NonZeroIsize::new_unchecked(self.0.hwnd.0) }) .into(); Ok(unsafe { rwh::WindowHandle::borrow_raw(raw) }) } @@ -1332,34 +289,40 @@ impl rwh::HasWindowHandle for WindowsWindow { // todo(windows) impl rwh::HasDisplayHandle for WindowsWindow { - fn display_handle(&self) -> Result, rwh::HandleError> { + fn display_handle(&self) -> std::result::Result, rwh::HandleError> { unimplemented!() } } impl Drop for WindowsWindow { fn drop(&mut self) { - unsafe { - let _ = RevokeDragDrop(self.inner.hwnd); - self.inner.renderer.borrow_mut().destroy(); - } + self.0.state.borrow_mut().renderer.destroy(); + // clone this `Rc` to prevent early release of the pointer + let this = self.0.clone(); + self.0 + .executor + .spawn(async move { + let handle = this.hwnd; + unsafe { + RevokeDragDrop(handle).log_err(); + DestroyWindow(handle).log_err(); + } + }) + .detach(); } } impl PlatformWindow for WindowsWindow { fn bounds(&self) -> Bounds { - Bounds { - origin: self.inner.origin.get(), - size: self.inner.physical_size.get(), - } + self.0.state.borrow().bounds() } fn is_maximized(&self) -> bool { - self.inner.is_maximized() + self.0.state.borrow().is_maximized() } fn is_minimized(&self) -> bool { - self.inner.is_minimized() + self.0.is_minimized() } /// get the logical size of the app's drawable area. @@ -1367,14 +330,11 @@ impl PlatformWindow for WindowsWindow { /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as /// whether the mouse collides with other elements of GPUI). fn content_size(&self) -> Size { - logical_size( - self.inner.physical_size.get(), - self.inner.scale_factor.get(), - ) + self.0.state.borrow().content_size() } fn scale_factor(&self) -> f32 { - self.inner.scale_factor.get() + self.0.state.borrow().scale_factor } // todo(windows) @@ -1383,23 +343,20 @@ impl PlatformWindow for WindowsWindow { } fn display(&self) -> Rc { - self.inner.display.borrow().clone() + Rc::new(self.0.state.borrow().display) } fn mouse_position(&self) -> Point { + let scale_factor = self.scale_factor(); let point = unsafe { let mut point: POINT = std::mem::zeroed(); GetCursorPos(&mut point) .context("unable to get cursor position") .log_err(); - ScreenToClient(self.inner.hwnd, &mut point); + ScreenToClient(self.0.hwnd, &mut point); point }; - logical_point( - point.x as f32, - point.y as f32, - self.inner.scale_factor.get(), - ) + logical_point(point.x as f32, point.y as f32, scale_factor) } // todo(windows) @@ -1407,14 +364,12 @@ impl PlatformWindow for WindowsWindow { Modifiers::none() } - // todo(windows) fn set_input_handler(&mut self, input_handler: PlatformInputHandler) { - self.inner.input_handler.set(Some(input_handler)); + self.0.state.borrow_mut().input_handler = Some(input_handler); } - // todo(windows) fn take_input_handler(&mut self) -> Option { - self.inner.input_handler.take() + self.0.state.borrow_mut().input_handler.take() } fn prompt( @@ -1431,10 +386,9 @@ impl PlatformWindow for WindowsWindow { None => None, }; let answers = answers.iter().map(|s| s.to_string()).collect::>(); - let handle = self.inner.hwnd; - self.inner - .platform_inner - .foreground_executor + let handle = self.0.hwnd; + self.0 + .executor .spawn(async move { unsafe { let mut config; @@ -1459,17 +413,17 @@ impl PlatformWindow for WindowsWindow { }; config.pszWindowTitle = title; config.Anonymous1.pszMainIcon = main_icon; - let instruction = msg.encode_utf16().chain(once(0)).collect_vec(); + let instruction = msg.encode_utf16().chain(Some(0)).collect_vec(); config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr()); let hints_encoded; if let Some(ref hints) = detail_string { - hints_encoded = hints.encode_utf16().chain(once(0)).collect_vec(); + hints_encoded = hints.encode_utf16().chain(Some(0)).collect_vec(); config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr()); }; let mut buttons = Vec::new(); let mut btn_encoded = Vec::new(); for (index, btn_string) in answers.iter().enumerate() { - let encoded = btn_string.encode_utf16().chain(once(0)).collect_vec(); + let encoded = btn_string.encode_utf16().chain(Some(0)).collect_vec(); buttons.push(TASKDIALOG_BUTTON { nButtonID: index as _, pszButtonText: PCWSTR::from_raw(encoded.as_ptr()), @@ -1493,18 +447,18 @@ impl PlatformWindow for WindowsWindow { } fn activate(&self) { - unsafe { SetActiveWindow(self.inner.hwnd) }; - unsafe { SetFocus(self.inner.hwnd) }; - unsafe { SetForegroundWindow(self.inner.hwnd) }; + let hwnd = self.0.hwnd; + unsafe { SetActiveWindow(hwnd) }; + unsafe { SetFocus(hwnd) }; + unsafe { SetForegroundWindow(hwnd) }; } fn is_active(&self) -> bool { - self.inner.hwnd == unsafe { GetActiveWindow() } + self.0.hwnd == unsafe { GetActiveWindow() } } - // todo(windows) fn set_title(&mut self, title: &str) { - unsafe { SetWindowTextW(self.inner.hwnd, &HSTRING::from(title)) } + unsafe { SetWindowTextW(self.0.hwnd, &HSTRING::from(title)) } .inspect_err(|e| log::error!("Set title failed: {e}")) .ok(); } @@ -1512,9 +466,10 @@ impl PlatformWindow for WindowsWindow { fn set_app_id(&mut self, _app_id: &str) {} fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) { - self.inner - .renderer + self.0 + .state .borrow_mut() + .renderer .update_transparency(background_appearance != WindowBackgroundAppearance::Opaque); } @@ -1525,82 +480,134 @@ impl PlatformWindow for WindowsWindow { fn show_character_palette(&self) {} fn minimize(&self) { - unsafe { ShowWindowAsync(self.inner.hwnd, SW_MINIMIZE) }; + unsafe { ShowWindowAsync(self.0.hwnd, SW_MINIMIZE) }; } fn zoom(&self) { - unsafe { ShowWindowAsync(self.inner.hwnd, SW_MAXIMIZE) }; + unsafe { ShowWindowAsync(self.0.hwnd, SW_MAXIMIZE) }; } fn toggle_fullscreen(&self) { - self.inner - .platform_inner - .foreground_executor - .spawn(self.inner.clone().toggle_fullscreen()) + let state_ptr = self.0.clone(); + self.0 + .executor + .spawn(async move { + let mut lock = state_ptr.state.borrow_mut(); + let StyleAndBounds { + style, + x, + y, + cx, + cy, + } = if let Some(state) = lock.fullscreen.take() { + state + } else { + let style = + WINDOW_STYLE(unsafe { get_window_long(state_ptr.hwnd, GWL_STYLE) } as _); + let mut rc = RECT::default(); + unsafe { GetWindowRect(state_ptr.hwnd, &mut rc) }.log_err(); + let _ = lock.fullscreen.insert(StyleAndBounds { + style, + x: rc.left, + y: rc.top, + cx: rc.right - rc.left, + cy: rc.bottom - rc.top, + }); + let style = style + & !(WS_THICKFRAME + | WS_SYSMENU + | WS_MAXIMIZEBOX + | WS_MINIMIZEBOX + | WS_CAPTION); + let bounds = lock.display.bounds(); + StyleAndBounds { + style, + x: bounds.left().0, + y: bounds.top().0, + cx: bounds.size.width.0, + cy: bounds.size.height.0, + } + }; + drop(lock); + unsafe { set_window_long(state_ptr.hwnd, GWL_STYLE, style.0 as isize) }; + unsafe { + SetWindowPos( + state_ptr.hwnd, + HWND::default(), + x, + y, + cx, + cy, + SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER, + ) + } + .log_err(); + }) .detach(); } fn is_fullscreen(&self) -> bool { - self.inner.is_fullscreen() + self.0.state.borrow().is_fullscreen() } - // todo(windows) fn on_request_frame(&self, callback: Box) { - self.inner.callbacks.borrow_mut().request_frame = Some(callback); + self.0.state.borrow_mut().callbacks.request_frame = Some(callback); } - // todo(windows) fn on_input(&self, callback: Box DispatchEventResult>) { - self.inner.callbacks.borrow_mut().input = Some(callback); + self.0.state.borrow_mut().callbacks.input = Some(callback); } - // todo(windows) fn on_active_status_change(&self, callback: Box) { - self.inner.callbacks.borrow_mut().active_status_change = Some(callback); + self.0.state.borrow_mut().callbacks.active_status_change = Some(callback); } - // todo(windows) fn on_resize(&self, callback: Box, f32)>) { - self.inner.callbacks.borrow_mut().resize = Some(callback); + self.0.state.borrow_mut().callbacks.resize = Some(callback); } - // todo(windows) fn on_moved(&self, callback: Box) { - self.inner.callbacks.borrow_mut().moved = Some(callback); + self.0.state.borrow_mut().callbacks.moved = Some(callback); } - // todo(windows) fn on_should_close(&self, callback: Box bool>) { - self.inner.callbacks.borrow_mut().should_close = Some(callback); + self.0.state.borrow_mut().callbacks.should_close = Some(callback); } - // todo(windows) fn on_close(&self, callback: Box) { - self.inner.callbacks.borrow_mut().close = Some(callback); + self.0.state.borrow_mut().callbacks.close = Some(callback); } - // todo(windows) fn on_appearance_changed(&self, callback: Box) { - self.inner.callbacks.borrow_mut().appearance_changed = Some(callback); + self.0.state.borrow_mut().callbacks.appearance_changed = Some(callback); } - // todo(windows) fn draw(&self, scene: &Scene) { - self.inner.renderer.borrow_mut().draw(scene) + self.0.state.borrow_mut().renderer.draw(scene) } - // todo(windows) fn sprite_atlas(&self) -> Arc { - self.inner.renderer.borrow().sprite_atlas().clone() + self.0.state.borrow().renderer.sprite_atlas().clone() } fn get_raw_handle(&self) -> HWND { - self.inner.hwnd + self.0.hwnd } } #[implement(IDropTarget)] -struct WindowsDragDropHandler(pub Rc); +struct WindowsDragDropHandler(pub Rc); + +impl WindowsDragDropHandler { + fn handle_drag_drop(&self, input: PlatformInput) { + let mut lock = self.0.state.borrow_mut(); + if let Some(mut func) = lock.callbacks.input.take() { + drop(lock); + func(input); + self.0.state.borrow_mut().callbacks.input = Some(func); + } + } +} #[allow(non_snake_case)] impl IDropTarget_Impl for WindowsDragDropHandler { @@ -1653,7 +660,7 @@ impl IDropTarget_Impl for WindowsDragDropHandler { ReleaseStgMedium(&mut idata); let mut cursor_position = POINT { x: pt.x, y: pt.y }; ScreenToClient(self.0.hwnd, &mut cursor_position); - let scale_factor = self.0.scale_factor.get(); + let scale_factor = self.0.state.borrow().scale_factor; let input = PlatformInput::FileDrop(FileDropEvent::Entered { position: logical_point( cursor_position.x as f32, @@ -1662,7 +669,7 @@ impl IDropTarget_Impl for WindowsDragDropHandler { ), paths: ExternalPaths(paths), }); - self.0.handle_drag_drop(input); + self.handle_drag_drop(input); } else { *pdweffect = DROPEFFECT_NONE; } @@ -1680,7 +687,7 @@ impl IDropTarget_Impl for WindowsDragDropHandler { unsafe { ScreenToClient(self.0.hwnd, &mut cursor_position); } - let scale_factor = self.0.scale_factor.get(); + let scale_factor = self.0.state.borrow().scale_factor; let input = PlatformInput::FileDrop(FileDropEvent::Pending { position: logical_point( cursor_position.x as f32, @@ -1688,14 +695,14 @@ impl IDropTarget_Impl for WindowsDragDropHandler { scale_factor, ), }); - self.0.handle_drag_drop(input); + self.handle_drag_drop(input); Ok(()) } fn DragLeave(&self) -> windows::core::Result<()> { let input = PlatformInput::FileDrop(FileDropEvent::Exited); - self.0.handle_drag_drop(input); + self.handle_drag_drop(input); Ok(()) } @@ -1711,7 +718,7 @@ impl IDropTarget_Impl for WindowsDragDropHandler { unsafe { ScreenToClient(self.0.hwnd, &mut cursor_position); } - let scale_factor = self.0.scale_factor.get(); + let scale_factor = self.0.state.borrow().scale_factor; let input = PlatformInput::FileDrop(FileDropEvent::Submit { position: logical_point( cursor_position.x as f32, @@ -1719,18 +726,18 @@ impl IDropTarget_Impl for WindowsDragDropHandler { scale_factor, ), }); - self.0.handle_drag_drop(input); + self.handle_drag_drop(input); Ok(()) } } #[derive(Debug)] -struct ClickState { +pub(crate) struct ClickState { button: MouseButton, last_click: Instant, last_position: Point, - current_count: usize, + pub(crate) current_count: usize, } impl ClickState { @@ -1767,6 +774,14 @@ impl ClickState { } } +struct StyleAndBounds { + style: WINDOW_STYLE, + x: i32, + y: i32, + cx: i32, + cy: i32, +} + fn register_wnd_class(icon_handle: HICON) -> PCWSTR { const CLASS_NAME: PCWSTR = w!("Zed::Window"); @@ -1797,27 +812,19 @@ unsafe extern "system" fn wnd_proc( let cs = unsafe { &*cs }; let ctx = cs.lpCreateParams as *mut WindowCreateContext; let ctx = unsafe { &mut *ctx }; - let inner = Rc::new(WindowsWindowInner::new( - hwnd, - cs, - ctx.platform_inner.clone(), - ctx.handle, - ctx.hide_title_bar, - ctx.display.clone(), - ctx.transparent, - )); - let weak = Box::new(Rc::downgrade(&inner)); + let state_ptr = WindowsWindowStatePtr::new(ctx, hwnd, cs); + let weak = Box::new(Rc::downgrade(&state_ptr)); unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) }; - ctx.inner = Some(inner); + ctx.inner = Some(state_ptr); return LRESULT(1); } - let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak; + let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak; if ptr.is_null() { return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }; } let inner = unsafe { &*ptr }; - let r = if let Some(inner) = inner.upgrade() { - inner.handle_msg(msg, wparam, lparam) + let r = if let Some(state) = inner.upgrade() { + handle_msg(hwnd, msg, wparam, lparam, state) } else { unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) } }; @@ -1828,12 +835,12 @@ unsafe extern "system" fn wnd_proc( r } -pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option> { +pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option> { if hwnd == HWND(0) { return None; } - let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak; + let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak; if !ptr.is_null() { let inner = unsafe { &*ptr }; inner.upgrade() @@ -1842,83 +849,6 @@ pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option> } } -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, - } -} - -#[inline] -fn logical_size(physical_size: Size, scale_factor: f32) -> Size { - Size { - width: px(physical_size.width.0 as f32 / scale_factor), - height: px(physical_size.height.0 as f32 / scale_factor), - } -} - -#[inline] -fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point { - Point { - x: px(x / scale_factor), - y: px(y / scale_factor), - } -} - -struct StyleAndBounds { - style: WINDOW_STYLE, - x: i32, - y: i32, - cx: i32, - cy: i32, -} - fn get_module_handle() -> HMODULE { unsafe { let mut h_module = std::mem::zeroed(); @@ -1933,13 +863,84 @@ fn get_module_handle() -> HMODULE { } } +fn register_drag_drop(state_ptr: Rc) { + let window_handle = state_ptr.hwnd; + let handler = WindowsDragDropHandler(state_ptr); + // The lifetime of `IDropTarget` is handled by Windows, it wont release untill + // we call `RevokeDragDrop`. + // So, it's safe to drop it here. + let drag_drop_handler: IDropTarget = handler.into(); + unsafe { + RegisterDragDrop(window_handle, &drag_drop_handler) + .expect("unable to register drag-drop event") + }; +} + // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF; // https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500); // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics const DOUBLE_CLICK_SPATIAL_TOLERANCE: i32 = 4; -const SIZE_MOVE_LOOP_TIMER_ID: usize = 1; + +mod windows_renderer { + use std::{num::NonZeroIsize, sync::Arc}; + + use blade_graphics as gpu; + use raw_window_handle as rwh; + use windows::Win32::{Foundation::HWND, UI::WindowsAndMessaging::GWLP_HINSTANCE}; + + use crate::{ + get_window_long, + platform::blade::{BladeRenderer, BladeSurfaceConfig}, + }; + + pub(super) fn windows_renderer(hwnd: HWND, transparent: bool) -> BladeRenderer { + let raw = RawWindow { hwnd: hwnd.0 }; + let gpu: Arc = Arc::new( + unsafe { + gpu::Context::init_windowed( + &raw, + gpu::ContextDesc { + validation: false, + capture: false, + overlay: false, + }, + ) + } + .unwrap(), + ); + let config = BladeSurfaceConfig { + size: gpu::Extent::default(), + transparent, + }; + + BladeRenderer::new(gpu, config) + } + + struct RawWindow { + hwnd: isize, + } + + impl rwh::HasWindowHandle for RawWindow { + fn window_handle(&self) -> Result, rwh::HandleError> { + Ok(unsafe { + let hwnd = NonZeroIsize::new_unchecked(self.hwnd); + let mut handle = rwh::Win32WindowHandle::new(hwnd); + let hinstance = get_window_long(HWND(self.hwnd), GWLP_HINSTANCE); + handle.hinstance = NonZeroIsize::new(hinstance); + rwh::WindowHandle::borrow_raw(handle.into()) + }) + } + } + + impl rwh::HasDisplayHandle for RawWindow { + fn display_handle(&self) -> Result, rwh::HandleError> { + let handle = rwh::WindowsDisplayHandle::new(); + Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) }) + } + } +} #[cfg(test)] mod tests {