Files
oak-gpui/crates/gpui/src/platform/windows/events.rs
T
张小白 95e246ac1c windows: Better dispatcher (#11485)
This PR leverages a more modern Windows API to implement
`WindowsDispatcher`, aligning its implementation more closely with that
of the `macOS` platform. The following improvements have been made:

1. Similar to `macOS`, there is no longer a need to use `sender` and
`receiver` to dispatch a `Runnable` on the main thread.
2. There is also no longer a need to use an `Event` for synchronization.
3. Consistent with #7506 and #11269, `Runnable` is now executed with
high priority.

However, this PR raises the minimum Windows version requirement of
`GPUI` to Windows 10, specifically Windows 10 Fall Creators Update
(10.0.16299). However, the `alacritty_terminal` dependency in Zed relies
on `conPTY` on Windows, an API introduced in the Windows 10 Fall
Creators Update. Therefore, the impact of this PR on Zed should be
minimal. I'd like to hear your voices about this PR, especially about
the minimum Windows version bumping.

Release Notes:

- N/A
2024-05-09 14:24:57 -07:00

1275 lines
41 KiB
Rust

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<WindowsWindowStatePtr>,
) -> 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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>) -> Option<isize> {
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<isize> {
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<isize> {
unsafe {
KillTimer(handle, SIZE_MOVE_LOOP_TIMER_ID).log_err();
}
None
}
fn handle_timer_msg(
handle: HWND,
wparam: WPARAM,
state_ptr: Rc<WindowsWindowStatePtr>,
) -> Option<isize> {
if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
handle_paint_msg(handle, state_ptr)
} else {
None
}
}
fn handle_paint_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
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<WindowsWindowStatePtr>) -> Option<isize> {
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<WindowsWindowStatePtr>) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
// 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<WindowsWindowStatePtr>,
) -> Option<isize> {
// 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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>) -> Option<isize>,
state_ptr: Rc<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>) -> Option<isize> {
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 Some(caret_range) = input_handler.selected_text_range() else {
state_ptr.state.borrow_mut().input_handler = Some(input_handler);
return Some(0);
};
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<WindowsWindowStatePtr>) -> Option<isize> {
state_ptr.state.borrow_mut().current_cursor = HCURSOR(lparam.0);
Some(0)
}
fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
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<WindowsWindowStatePtr>,
) -> Option<isize> {
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<Keystroke> {
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<Keystroke> {
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<Keystroke> {
let src = [wparam.0 as u16];
let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[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::<u16>(
buffer.as_mut_ptr().cast::<u16>(),
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<String> {
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::<u16>(
buffer.as_mut_ptr().cast::<u16>(),
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<Keystroke> {
match code {
// VK_0 - VK_9
48..=57 => Some(Keystroke {
modifiers,
key: format!("{}", code - VK_0.0),
ime_key: None,
}),
// VK_A - VK_Z
65..=90 => Some(Keystroke {
modifiers,
key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
ime_key: None,
}),
// VK_F1 - VK_F24
112..=135 => Some(Keystroke {
modifiers,
key: format!("f{}", code - VK_F1.0 + 1),
ime_key: None,
}),
// OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
_ => {
if let Some(key) = oemkey_vkcode_to_string(code) {
Some(Keystroke {
modifiers,
key,
ime_key: None,
})
} else {
None
}
}
}
}
fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
match code {
186 => Some(";".to_string()), // VK_OEM_1
187 => Some("=".to_string()), // VK_OEM_PLUS
188 => Some(",".to_string()), // VK_OEM_COMMA
189 => Some("-".to_string()), // VK_OEM_MINUS
190 => Some(".".to_string()), // VK_OEM_PERIOD
// https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
192 => Some("`".to_string()), // VK_OEM_3
219 => Some("[".to_string()), // VK_OEM_4
220 => Some("\\".to_string()), // VK_OEM_5
221 => Some("]".to_string()), // VK_OEM_6
222 => Some("'".to_string()), // VK_OEM_7
_ => None,
}
}
#[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,
}
}