win32: account for mouse wheel speed setting

Also adds a method to toggle this behavior during runtime.
This commit is contained in:
Tony 2025-08-23 20:38:56 +08:00 committed by GitHub
parent b13b39aa0b
commit 317d62fb93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 96 additions and 18 deletions

View file

@ -42,22 +42,23 @@ use windows_sys::Win32::UI::Input::{
use windows_sys::Win32::UI::WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetClientRect, GetCursorPos,
GetMenu, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, PostMessageW,
RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos, TranslateMessage,
CREATESTRUCTW, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG,
MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PM_REMOVE, PT_TOUCH, QS_ALLINPUT, RI_MOUSE_HWHEEL,
RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE,
SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT,
WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT,
WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE,
WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION,
WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS,
WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL,
WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY,
WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN,
WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND,
WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING,
WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW,
WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE,
RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos, SystemParametersInfoW,
TranslateMessage, CREATESTRUCTW, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO,
MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PM_REMOVE, PT_TOUCH, QS_ALLINPUT,
RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED,
SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE,
SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT,
WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT, WM_CAPTURECHANGED, WM_CLOSE,
WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO,
WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION,
WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN,
WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE,
WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN,
WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS,
WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH,
WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW,
WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP,
WS_VISIBLE,
};
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource};
@ -92,6 +93,13 @@ use crate::window::{InitData, Window};
use crate::window_state::{CursorFlags, ImeState, WindowFlags, WindowState};
use crate::{raw_input, util};
// This is defined in `winuser.h` as a macro that expands to `UINT_MAX`
const WHEEL_PAGESCROLL: u32 = u32::MAX;
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa#:~:text=SPI_GETWHEELSCROLLLINES
const DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA: isize = 3;
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa#:~:text=SPI_GETWHEELSCROLLCHARS
const DEFAULT_SCROLL_CHARACTERS_PER_WHEEL_DELTA: isize = 3;
pub(crate) struct WindowData {
pub window_state: Arc<Mutex<WindowState>>,
pub event_loop_runner: Rc<EventLoopRunner>,
@ -1636,9 +1644,26 @@ unsafe fn public_window_callback_inner(
update_modifiers(window, userdata);
let scroll_lines_multiplier = if userdata.window_state_lock().use_system_wheel_speed {
let mut scroll_lines = DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA;
let _ = SystemParametersInfoW(
SPI_GETWHEELSCROLLLINES,
0,
&mut scroll_lines as *mut isize as *mut c_void,
0,
);
if scroll_lines as u32 == WHEEL_PAGESCROLL {
// TODO: figure out how to handle page scrolls
scroll_lines = DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA;
}
scroll_lines
} else {
1
};
userdata.send_window_event(window, WindowEvent::MouseWheel {
device_id: None,
delta: LineDelta(0.0, value),
delta: LineDelta(0.0, value * scroll_lines_multiplier as f32),
phase: TouchPhase::Moved,
});
@ -1653,9 +1678,23 @@ unsafe fn public_window_callback_inner(
update_modifiers(window, userdata);
let scroll_characters_multiplier =
if userdata.window_state_lock().use_system_wheel_speed {
let mut scroll_characters = DEFAULT_SCROLL_CHARACTERS_PER_WHEEL_DELTA;
let _ = SystemParametersInfoW(
SPI_GETWHEELSCROLLCHARS,
0,
&mut scroll_characters as *mut isize as *mut c_void,
0,
);
scroll_characters
} else {
1
};
userdata.send_window_event(window, WindowEvent::MouseWheel {
device_id: None,
delta: LineDelta(value, 0.0),
delta: LineDelta(value * scroll_characters_multiplier as f32, 0.0),
phase: TouchPhase::Moved,
});

View file

@ -285,6 +285,15 @@ pub trait WindowExtWindows {
/// Supported starting with Windows 11 Build 22000.
fn set_corner_preference(&self, preference: CornerPreference);
/// Sets if the reported [`winit_core::event::WindowEvent::MouseWheel`] event
/// should account for scroll speed system settings.
///
/// The default scroll speed on Windows is 3 lines/characters per scroll,
/// this will be 1 if you set it to false.
///
/// The default is `true`.
fn set_use_system_scroll_speed(&self, should_use: bool);
/// Get the raw window handle for this [`Window`] without checking for thread affinity.
///
/// Window handles in Win32 have a property called "thread affinity" that ties them to their
@ -398,6 +407,11 @@ impl WindowExtWindows for dyn CoreWindow + '_ {
window.set_corner_preference(preference)
}
fn set_use_system_scroll_speed(&self, should_use: bool) {
let window = self.cast_ref::<Window>().unwrap();
window.set_use_system_scroll_speed(should_use)
}
unsafe fn window_handle_any_thread(
&self,
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
@ -459,6 +473,7 @@ pub struct WindowAttributesWindows {
pub(crate) title_background_color: Option<Color>,
pub(crate) title_text_color: Option<Color>,
pub(crate) corner_preference: Option<CornerPreference>,
pub(crate) use_system_wheel_speed: bool,
}
impl Default for WindowAttributesWindows {
@ -478,6 +493,7 @@ impl Default for WindowAttributesWindows {
title_background_color: None,
title_text_color: None,
corner_preference: None,
use_system_wheel_speed: true,
}
}
}
@ -610,6 +626,18 @@ impl WindowAttributesWindows {
self.corner_preference = Some(corners);
self
}
/// Sets if the reported [`winit_core::event::WindowEvent::MouseWheel`] event
/// should account for scroll speed system settings.
///
/// The default scroll speed on Windows is 3 lines/characters per scroll,
/// this will be 1 if you set it to false.
///
/// The default is `true`.
pub fn with_use_system_scroll_speed(mut self, should_use: bool) -> Self {
self.use_system_wheel_speed = should_use;
self
}
}
impl PlatformWindowAttributes for WindowAttributesWindows {

View file

@ -344,6 +344,11 @@ impl Window {
}
}
#[inline]
pub fn set_use_system_scroll_speed(&self, should_use: bool) {
self.window_state_lock().use_system_wheel_speed = should_use;
}
fn set_icon(&self, mut new_icon: Icon, icon_type: IconType) {
if let Some(icon) = new_icon.cast_ref::<RgbaIcon>() {
let icon = match WinIcon::from_rgba(icon) {
@ -1303,6 +1308,7 @@ impl InitData<'_> {
win.set_skip_taskbar(self.win_attributes.skip_taskbar);
win.set_window_icon(self.attributes.window_icon.clone());
win.set_taskbar_icon(self.win_attributes.taskbar_icon.clone());
win.set_use_system_scroll_speed(self.win_attributes.use_system_wheel_speed);
let attributes = self.attributes.clone();

View file

@ -60,6 +60,8 @@ pub(crate) struct WindowState {
pub dragging: bool,
pub skip_taskbar: bool,
pub use_system_wheel_speed: bool,
}
#[derive(Clone)]
@ -187,6 +189,8 @@ impl WindowState {
dragging: false,
skip_taskbar: false,
use_system_wheel_speed: true,
}
}

View file

@ -256,3 +256,4 @@ changelog entry.
- On macOS, fixed redundant `SurfaceResized` event at window creation.
- On macOS, don't panic on monitors with unknown bit-depths.
- On macOS, fixed crash when closing the window on macOS 26+.
- On Windows, account for mouse wheel lines per scroll setting for `WindowEvent::MouseWheel`.