When scaling applies, after a call to SetCursorPos(), a subsequent call to GetCursorPos() may not return the exact coordinates set, which an app may interpret as mouse movement.
-- v2: win32u, server: Use float values to track mouse cursor location.
From: Conor McCarthy cmccarthy@codeweavers.com
Requires display mode emulation for failures to manifest. --- dlls/user32/tests/monitor.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
diff --git a/dlls/user32/tests/monitor.c b/dlls/user32/tests/monitor.c index 206d9505705..7ad3970a646 100644 --- a/dlls/user32/tests/monitor.c +++ b/dlls/user32/tests/monitor.c @@ -3611,6 +3611,8 @@ static void test_monitor_dpi(void) DPI_AWARENESS_CONTEXT old_ctx; float scale_x, scale_y; BOOL ret, is_virtual; + INT x, y; + POINT pt;
if (!pGetDpiForMonitorInternal || !pSetThreadDpiAwarenessContext) { @@ -3722,6 +3724,19 @@ static void test_monitor_dpi(void) ok( fabs( dpi_y - system_dpi * scale_y ) < system_dpi * 0.05, "got MDT_RAW_DPI y %u\n", dpi_y ); }
+ /* Test for cursor position rounding errors. To be meaninful, this requires display + * mode emulation. To enable it, run 'wine control desk.cpl' and set the checkbox. */ + for (x = infos[i].rect.left; x < infos[i].rect.left + 64; ++x) + { + y = x - infos[i].rect.left + infos[i].rect.top; + SetCursorPos(x, y); + GetCursorPos(&pt); + todo_wine_if(pt.x != x) + ok(pt.x == x, "got x %ld, expected %d.\n", pt.x, x); + todo_wine_if(pt.y != y) + ok(pt.y == y, "got y %ld, expected %d.\n", pt.y, y); + } + pSetThreadDpiAwarenessContext( old_ctx ); } }
From: Conor McCarthy cmccarthy@codeweavers.com
When scaling applies, after a call to SetCursorPos(), a subsequent call to GetCursorPos() may not return the exact coordinates set, which an app may interpret as mouse movement. --- dlls/user32/tests/monitor.c | 2 - dlls/win32u/input.c | 35 +++++++----- dlls/win32u/message.c | 24 +++++---- dlls/win32u/sysparams.c | 97 ++++++++++++++++++++++++++++++++++ dlls/win32u/win32u_private.h | 7 +++ include/wine/server_protocol.h | 14 ++--- server/protocol.def | 12 ++--- server/queue.c | 21 ++++---- server/request_handlers.h | 1 + server/request_trace.h | 8 +-- server/user.h | 11 +++- server/window.c | 63 +++++++++++++++------- tools/make_requests | 1 + 13 files changed, 226 insertions(+), 70 deletions(-)
diff --git a/dlls/user32/tests/monitor.c b/dlls/user32/tests/monitor.c index 7ad3970a646..f76971c1b53 100644 --- a/dlls/user32/tests/monitor.c +++ b/dlls/user32/tests/monitor.c @@ -3731,9 +3731,7 @@ static void test_monitor_dpi(void) y = x - infos[i].rect.left + infos[i].rect.top; SetCursorPos(x, y); GetCursorPos(&pt); - todo_wine_if(pt.x != x) ok(pt.x == x, "got x %ld, expected %d.\n", pt.x, x); - todo_wine_if(pt.y != y) ok(pt.y == y, "got y %ld, expected %d.\n", pt.y, y); }
diff --git a/dlls/win32u/input.c b/dlls/win32u/input.c index 252ae78e6a0..6e7f382ad02 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -706,16 +706,18 @@ UINT WINAPI NtUserSendInput( UINT count, INPUT *inputs, int size ) */ BOOL WINAPI NtUserSetCursorPos( INT x, INT y ) { - RECT rect = {x, y, x, y}; + float float_x, float_y; BOOL ret; INT prev_x, prev_y, new_x, new_y;
- rect = map_rect_virt_to_raw( rect, get_thread_dpi() ); + float_x = x; + float_y = y; + map_float_point_virt_to_raw( &float_x, &float_y, get_thread_dpi() ); SERVER_START_REQ( set_cursor ) { req->flags = SET_CURSOR_POS; - req->x = rect.left; - req->y = rect.top; + req->x = float_x; + req->y = float_y; if ((ret = !wine_server_call( req ))) { prev_x = reply->prev_x; @@ -736,28 +738,37 @@ BOOL WINAPI NtUserGetCursorPos( POINT *pt ) { struct object_lock lock = OBJECT_LOCK_INIT; const desktop_shm_t *desktop_shm; + float x = 0.0f, y = 0.0f; BOOL ret = TRUE; DWORD last_change = 0; NTSTATUS status; - RECT rect;
if (!pt) return FALSE;
while ((status = get_shared_desktop( &lock, &desktop_shm )) == STATUS_PENDING) { - pt->x = desktop_shm->cursor.x; - pt->y = desktop_shm->cursor.y; + x = desktop_shm->cursor.x; + y = desktop_shm->cursor.y; last_change = desktop_shm->cursor.last_change; } if (status) return FALSE;
/* query new position from graphics driver if we haven't updated recently */ - if (NtGetTickCount() - last_change > 100) ret = user_driver->pGetCursorPos( pt ); - if (!ret) return FALSE; + if (NtGetTickCount() - last_change > 100) + { + ret = user_driver->pGetCursorPos( pt ); + if (!ret) return FALSE; + /* keep the more accurate float position if the driver's position matches */ + if (pt->x != float_round_to_int( x ) || pt->y != float_round_to_int( y )) + { + x = pt->x; + y = pt->y; + } + }
- SetRect( &rect, pt->x, pt->y, pt->x, pt->y ); - rect = map_rect_raw_to_virt( rect, get_thread_dpi() ); - *pt = *(POINT *)&rect.left; + map_float_point_raw_to_virt( &x, &y, get_thread_dpi() ); + pt->x = float_round_to_int( x ); + pt->y = float_round_to_int( y ); return ret; }
diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index 9e8f9d54af8..203fea7b041 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -2771,7 +2771,7 @@ static BOOL process_hardware_message( MSG *msg, UINT hw_id, const struct hardwar HWND hwnd_filter, UINT first, UINT last, BOOL remove ) { struct ntuser_thread_info *thread_info = NtUserGetThreadInfo(); - RECT rect = {msg->pt.x, msg->pt.y, msg->pt.x, msg->pt.y}; + float x, y; UINT context; BOOL ret = FALSE;
@@ -2780,9 +2780,11 @@ static BOOL process_hardware_message( MSG *msg, UINT hw_id, const struct hardwar
/* hardware messages are always in raw physical coords */ context = set_thread_dpi_awareness_context( NTUSER_DPI_PER_MONITOR_AWARE ); - rect = map_rect_raw_to_virt( rect, get_thread_dpi() ); - msg->pt.x = rect.left; - msg->pt.y = rect.top; + x = msg->pt.x; + y = msg->pt.y; + map_float_point_raw_to_virt( &x, &y, get_thread_dpi() ); + msg->pt.x = float_round_to_int( x ); + msg->pt.y = float_round_to_int( y );
if (msg->message == WM_INPUT || msg->message == WM_INPUT_DEVICE_CHANGE) ret = process_rawinput_message( msg, hw_id, msg_data ); @@ -2855,6 +2857,7 @@ static int peek_message( MSG *msg, const struct peek_message_filter *filter ) unsigned char buffer_init[1024]; size_t buffer_size = sizeof(buffer_init); void *buffer = buffer_init; + float reply_x, reply_y;
if (!first && !last) last = ~0; if (hwnd == HWND_BROADCAST) hwnd = HWND_TOPMOST; @@ -2906,8 +2909,10 @@ static int peek_message( MSG *msg, const struct peek_message_filter *filter ) info.msg.wParam = reply->wparam; info.msg.lParam = reply->lparam; info.msg.time = reply->time; - info.msg.pt.x = reply->x; - info.msg.pt.y = reply->y; + reply_x = reply->x; + reply_y = reply->y; + info.msg.pt.x = float_round_to_int( reply_x ); + info.msg.pt.y = float_round_to_int( reply_y ); hw_id = 0; } else buffer_size = reply->total; @@ -3007,12 +3012,11 @@ static int peek_message( MSG *msg, const struct peek_message_filter *filter ) } else if (info.msg.message == WH_MOUSE_LL && size >= sizeof(msg_data->hardware)) { - RECT rect = {info.msg.pt.x, info.msg.pt.y, info.msg.pt.x, info.msg.pt.y}; MSLLHOOKSTRUCT hook;
- rect = map_rect_raw_to_virt( rect, 0 ); - info.msg.pt.x = rect.left; - info.msg.pt.y = rect.top; + map_float_point_raw_to_virt( &reply_x, &reply_y, 0 ); + info.msg.pt.x = float_round_to_int( reply_x ); + info.msg.pt.y = float_round_to_int( reply_y );
hook.pt = info.msg.pt; hook.mouseData = info.msg.lParam; diff --git a/dlls/win32u/sysparams.c b/dlls/win32u/sysparams.c index 13b49a9f12c..aa8fd8179a6 100644 --- a/dlls/win32u/sysparams.c +++ b/dlls/win32u/sysparams.c @@ -1913,6 +1913,75 @@ static UINT monitor_get_dpi( struct monitor *monitor, MONITOR_DPI_TYPE type, UIN return min( *dpi_x, *dpi_y ); }
+/* display_lock must be held */ +static void map_monitor_float_point( struct monitor *monitor, float *x, float *y, UINT dpi_from, + MONITOR_DPI_TYPE type_from, UINT dpi_to, MONITOR_DPI_TYPE type_to ) +{ + UINT dpi_x, dpi_y; + float r; + + assert( type_from != type_to ); + + if (monitor->source) + { + DEVMODEW current_mode = {.dmSize = sizeof(DEVMODEW)}, *mode_from, *mode_to; + UINT num, den, dpi; + + source_get_current_settings( monitor->source, ¤t_mode ); + + dpi = monitor_get_dpi( monitor, MDT_DEFAULT, &dpi_x, &dpi_y ); + if (!dpi_from) dpi_from = dpi; + if (!dpi_to) dpi_to = dpi; + + if (type_from == MDT_RAW_DPI) + { + monitor_virt_to_raw_ratio( monitor, &den, &num ); + mode_from = &monitor->source->physical; + mode_to = ¤t_mode; + } + else + { + monitor_virt_to_raw_ratio( monitor, &num, &den ); + mode_from = ¤t_mode; + mode_to = &monitor->source->physical; + } + + if (dpi != dpi_from) + { + r = (float)dpi / dpi_from; + *x *= r; + *y *= r; + } + *x -= mode_from->dmPosition.x + mode_from->dmPelsWidth / 2.0f; + *y -= mode_from->dmPosition.y + mode_from->dmPelsHeight / 2.0f; + if (num != den) + { + r = (float)num / den; + *x *= r; + *y *= r; + } + *x += mode_to->dmPosition.x + mode_to->dmPelsWidth / 2.0f; + *y += mode_to->dmPosition.y + mode_to->dmPelsHeight / 2.0f; + if (dpi_to != dpi) + { + r = (float)dpi_to / dpi; + *x *= r; + *y *= r; + } + + return; + } + + if (!dpi_from) dpi_from = monitor_get_dpi( monitor, type_from, &dpi_x, &dpi_y ); + if (!dpi_to) dpi_to = monitor_get_dpi( monitor, type_to, &dpi_x, &dpi_y ); + if (dpi_from != dpi_to) + { + r = (float)dpi_to / dpi_from; + *x *= r; + *y *= r; + } +} + /* display_lock must be held */ static RECT map_monitor_rect( struct monitor *monitor, RECT rect, UINT dpi_from, MONITOR_DPI_TYPE type_from, UINT dpi_to, MONITOR_DPI_TYPE type_to ) @@ -2604,6 +2673,20 @@ static RECT monitors_get_union_rect( UINT dpi, MONITOR_DPI_TYPE type ) return rect; }
+/* map a monitor point from MDT_RAW_DPI to MDT_DEFAULT coordinates */ +void map_float_point_raw_to_virt( float *x, float *y, UINT dpi_to ) +{ + struct monitor *monitor; + RECT pos; + + if (!lock_display_devices( FALSE )) return; + pos.left = pos.right = float_round_to_int( *x ); + pos.top = pos.bottom = float_round_to_int( *y ); + if ((monitor = get_monitor_from_rect( pos, MONITOR_DEFAULTTONEAREST, 0, MDT_RAW_DPI ))) + map_monitor_float_point( monitor, x, y, 0, MDT_RAW_DPI, dpi_to, MDT_DEFAULT ); + unlock_display_devices(); +} + /* map a monitor rect from MDT_RAW_DPI to MDT_DEFAULT coordinates */ RECT map_rect_raw_to_virt( RECT rect, UINT dpi_to ) { @@ -2618,6 +2701,20 @@ RECT map_rect_raw_to_virt( RECT rect, UINT dpi_to ) return rect; }
+/* map a monitor point from MDT_DEFAULT to MDT_RAW_DPI coordinates */ +void map_float_point_virt_to_raw( float *x, float *y, UINT dpi_from ) +{ + struct monitor *monitor; + RECT pos; + + if (!lock_display_devices( FALSE )) return; + pos.left = pos.right = float_round_to_int( *x ); + pos.top = pos.bottom = float_round_to_int( *y ); + if ((monitor = get_monitor_from_rect( pos, MONITOR_DEFAULTTONEAREST, dpi_from, MDT_DEFAULT ))) + map_monitor_float_point( monitor, x, y, dpi_from, MDT_DEFAULT, 0, MDT_RAW_DPI ); + unlock_display_devices(); +} + /* map a monitor rect from MDT_DEFAULT to MDT_RAW_DPI coordinates */ RECT map_rect_virt_to_raw( RECT rect, UINT dpi_from ) { diff --git a/dlls/win32u/win32u_private.h b/dlls/win32u/win32u_private.h index 6eaa7270b01..a0d01c83ef1 100644 --- a/dlls/win32u/win32u_private.h +++ b/dlls/win32u/win32u_private.h @@ -174,7 +174,9 @@ extern POINT map_dpi_point( POINT pt, UINT dpi_from, UINT dpi_to ); extern RECT map_dpi_rect( RECT rect, UINT dpi_from, UINT dpi_to ); extern HRGN map_dpi_region( HRGN region, UINT dpi_from, UINT dpi_to ); extern struct window_rects map_dpi_window_rects( struct window_rects rects, UINT dpi_from, UINT dpi_to ); +extern void map_float_point_raw_to_virt( float *x, float *y, UINT dpi_to ); extern RECT map_rect_raw_to_virt( RECT rect, UINT dpi_to ); +extern void map_float_point_virt_to_raw( float *x, float *y, UINT dpi_from ); extern RECT map_rect_virt_to_raw( RECT rect, UINT dpi_from ); extern struct window_rects map_window_rects_virt_to_raw( struct window_rects rects, UINT dpi_from ); extern POINT point_phys_to_win_dpi( HWND hwnd, POINT pt ); @@ -418,4 +420,9 @@ static inline BOOL intersect_rect( RECT *dst, const RECT *src1, const RECT *src2 return !IsRectEmpty( dst ); }
+static inline int float_round_to_int( float val ) +{ + return val + 0.5f; +} + #endif /* __WINE_WIN32U_PRIVATE */ diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index d9f1099f154..f5076ca9b0a 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -979,8 +979,8 @@ struct obj_locator
struct shared_cursor { - int x; - int y; + float x; + float y; unsigned int last_change; struct rectangle clip; }; @@ -3146,8 +3146,8 @@ struct get_message_reply lparam_t wparam; lparam_t lparam; int type; - int x; - int y; + float x; + float y; unsigned int time; data_size_t total; /* VARARG(data,message_data); */ @@ -5713,8 +5713,8 @@ struct set_cursor_request unsigned int flags; user_handle_t handle; int show_count; - int x; - int y; + float x; + float y; struct rectangle clip; }; struct set_cursor_reply @@ -7040,6 +7040,6 @@ union generic_reply struct d3dkmt_object_open_name_reply d3dkmt_object_open_name_reply; };
-#define SERVER_PROTOCOL_VERSION 921 +#define SERVER_PROTOCOL_VERSION 922
#endif /* __WINE_WINE_SERVER_PROTOCOL_H */ diff --git a/server/protocol.def b/server/protocol.def index 87b8730f92a..9ace253d701 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -995,8 +995,8 @@ struct obj_locator
struct shared_cursor { - int x; /* cursor position */ - int y; + float x; /* cursor position */ + float y; unsigned int last_change; /* time of last position change */ struct rectangle clip; /* cursor clip rectangle */ }; @@ -2400,8 +2400,8 @@ enum message_type lparam_t wparam; /* parameters */ lparam_t lparam; /* parameters */ int type; /* message type */ - int x; /* message x position */ - int y; /* message y position */ + float x; /* message x position */ + float y; /* message y position */ unsigned int time; /* message time */ data_size_t total; /* total size of extra data */ VARARG(data,message_data); /* message data for sent messages */ @@ -3997,8 +3997,8 @@ struct handle_info unsigned int flags; /* flags for fields to set (see below) */ user_handle_t handle; /* handle to the cursor */ int show_count; /* show count increment/decrement */ - int x; /* cursor position */ - int y; + float x; /* cursor position */ + float y; struct rectangle clip; /* cursor clip rectangle */ @REPLY user_handle_t prev_handle; /* previous handle */ diff --git a/server/queue.c b/server/queue.c index 6b48a5dfdc8..71fd90e0680 100644 --- a/server/queue.c +++ b/server/queue.c @@ -85,8 +85,8 @@ struct message unsigned int msg; /* message code */ lparam_t wparam; /* parameters */ lparam_t lparam; /* parameters */ - int x; /* message position */ - int y; + float x; /* message position */ + float y; unsigned int time; /* message time */ void *data; /* message data for sent messages */ unsigned int data_size; /* size of message data */ @@ -540,7 +540,7 @@ static int update_desktop_cursor_window( struct desktop *desktop, user_handle_t return updated; }
-static int update_desktop_cursor_pos( struct desktop *desktop, user_handle_t win, int x, int y ) +static int update_desktop_cursor_pos( struct desktop *desktop, user_handle_t win, float x, float y ) { desktop_shm_t *desktop_shm = desktop->shared; int updated; @@ -577,7 +577,7 @@ static void update_desktop_cursor_handle( struct desktop *desktop, struct thread }
/* set the cursor position and queue the corresponding mouse message */ -static void set_cursor_pos( struct desktop *desktop, int x, int y ) +static void set_cursor_pos( struct desktop *desktop, float x, float y ) { static const struct hw_msg_source source = { IMDT_UNAVAILABLE, IMO_SYSTEM }; const struct rawinput_device *device; @@ -607,7 +607,7 @@ void update_cursor_pos( struct desktop *desktop ) }
/* retrieve default position and time for synthesized messages */ -static void get_message_defaults( struct msg_queue *queue, int *x, int *y, unsigned int *time ) +static void get_message_defaults( struct msg_queue *queue, float *x, float *y, unsigned int *time ) { struct desktop *desktop = queue->input->desktop; desktop_shm_t *desktop_shm = desktop->shared; @@ -623,7 +623,7 @@ void set_clip_rectangle( struct desktop *desktop, const struct rectangle *rect, desktop_shm_t *desktop_shm = desktop->shared; struct rectangle top_rect, new_rect; unsigned int old_flags; - int x, y; + float x, y;
get_virtual_screen_rect( desktop, &top_rect, 1 ); if (rect) @@ -1913,7 +1913,7 @@ static void queue_hardware_message( struct desktop *desktop, struct message *msg msg->lparam &= ~(KF_EXTENDED << 16); break; case QS_MOUSEMOVE: - prepend_cursor_history( msg->x, msg->y, msg->time, msg_data->info ); + prepend_cursor_history( float_round_to_int( msg->x ), float_round_to_int( msg->y ), msg->time, msg_data->info ); /* fallthrough */ case QS_MOUSEBUTTON: if (update_desktop_cursor_pos( desktop, msg->win, msg->x, msg->y )) always_queue = 1; @@ -2217,7 +2217,8 @@ static int queue_mouse_message( struct desktop *desktop, user_handle_t win, cons unsigned int i, time = get_tick_count(), flags; struct hw_msg_source source = { IMDT_MOUSE, origin }; lparam_t wparam = input->mouse.data << 16; - int wait = 0, x, y; + int wait = 0; + float x, y;
static const unsigned int messages[] = { @@ -2275,7 +2276,9 @@ static int queue_mouse_message( struct desktop *desktop, user_handle_t win, cons raw_msg.time = time; raw_msg.message = WM_INPUT; raw_msg.flags = flags; - rawmouse_init( &raw_msg.rawinput, &raw_msg.data.mouse, x - desktop_shm->cursor.x, y - desktop_shm->cursor.y, + rawmouse_init( &raw_msg.rawinput, &raw_msg.data.mouse, + float_round_to_int( x - desktop_shm->cursor.x ), + float_round_to_int( y - desktop_shm->cursor.y ), raw_msg.flags, input->mouse.data, input->mouse.info );
dispatch_rawinput_message( desktop, &raw_msg ); diff --git a/server/request_handlers.h b/server/request_handlers.h index 6bef45d4ff9..0206aded583 100644 --- a/server/request_handlers.h +++ b/server/request_handlers.h @@ -630,6 +630,7 @@ C_ASSERT( sizeof(client_ptr_t) == 8 ); C_ASSERT( sizeof(d3dkmt_handle_t) == 4 ); C_ASSERT( sizeof(data_size_t) == 4 ); C_ASSERT( sizeof(file_pos_t) == 8 ); +C_ASSERT( sizeof(float) == 4 ); C_ASSERT( sizeof(int) == 4 ); C_ASSERT( sizeof(ioctl_code_t) == 4 ); C_ASSERT( sizeof(lparam_t) == 8 ); diff --git a/server/request_trace.h b/server/request_trace.h index 8090f80eb76..1c969e60462 100644 --- a/server/request_trace.h +++ b/server/request_trace.h @@ -1468,8 +1468,8 @@ static void dump_get_message_reply( const struct get_message_reply *req ) dump_uint64( ", wparam=", &req->wparam ); dump_uint64( ", lparam=", &req->lparam ); fprintf( stderr, ", type=%d", req->type ); - fprintf( stderr, ", x=%d", req->x ); - fprintf( stderr, ", y=%d", req->y ); + fprintf( stderr, ", x=%05.2f", req->x ); + fprintf( stderr, ", y=%05.2f", req->y ); fprintf( stderr, ", time=%08x", req->time ); fprintf( stderr, ", total=%u", req->total ); dump_varargs_message_data( ", data=", cur_size ); @@ -3212,8 +3212,8 @@ static void dump_set_cursor_request( const struct set_cursor_request *req ) fprintf( stderr, " flags=%08x", req->flags ); fprintf( stderr, ", handle=%08x", req->handle ); fprintf( stderr, ", show_count=%d", req->show_count ); - fprintf( stderr, ", x=%d", req->x ); - fprintf( stderr, ", y=%d", req->y ); + fprintf( stderr, ", x=%05.2f", req->x ); + fprintf( stderr, ", y=%05.2f", req->y ); dump_rectangle( ", clip=", &req->clip ); }
diff --git a/server/user.h b/server/user.h index 73b10aaf5ac..46a38ae8150 100644 --- a/server/user.h +++ b/server/user.h @@ -168,6 +168,8 @@ extern int rect_in_region( struct region *region, const struct rectangle *rect )
/* window functions */
+extern void map_float_point_raw_to_virt( struct desktop *desktop, float *x, float *y ); +extern void map_float_point_virt_to_raw( struct desktop *desktop, float *x, float *y ); extern struct process *get_top_window_owner( struct desktop *desktop ); extern void get_virtual_screen_rect( struct desktop *desktop, struct rectangle *rect, int is_raw ); extern void post_desktop_message( struct desktop *desktop, unsigned int message, @@ -180,8 +182,8 @@ extern int is_window_visible( user_handle_t window ); extern int is_window_transparent( user_handle_t window ); extern int make_window_active( user_handle_t window ); extern struct thread *get_window_thread( user_handle_t handle ); -extern user_handle_t shallow_window_from_point( struct desktop *desktop, int x, int y ); -extern struct thread *window_thread_from_point( user_handle_t scope, int x, int y ); +extern user_handle_t shallow_window_from_point( struct desktop *desktop, float x, float y ); +extern struct thread *window_thread_from_point( user_handle_t scope, float x, float y ); extern user_handle_t find_window_to_repaint( user_handle_t parent, struct thread *thread ); extern struct window_class *get_window_class( user_handle_t window );
@@ -230,6 +232,11 @@ static inline int point_in_rect( const struct rectangle *rect, int x, int y ) return (x >= rect->left && x < rect->right && y >= rect->top && y < rect->bottom); }
+static inline int float_round_to_int( float val ) +{ + return val + 0.5f; +} + static inline int scale_dpi( int val, unsigned int dpi_from, unsigned int dpi_to ) { if (val >= 0) return (val * dpi_to + (dpi_from / 2)) / dpi_from; diff --git a/server/window.c b/server/window.c index d9a2689ae5a..55ee1dfe3af 100644 --- a/server/window.c +++ b/server/window.c @@ -307,25 +307,49 @@ static struct monitor_info *get_monitor_from_rect( struct winstation *winstation return found ? found : nearest; }
-static void map_point_raw_to_virt( struct desktop *desktop, int *x, int *y ) +static void rect_map_float_point( const struct rectangle *rect_to, const struct rectangle *rect_from, + float *x, float *y ) { int width_from, height_from, width_to, height_to; - struct rectangle rect = {*x, *y, *x + 1, *y + 1}; + + width_to = rect_to->right - rect_to->left; + height_to = rect_to->bottom - rect_to->top; + width_from = rect_from->right - rect_from->left; + height_from = rect_from->bottom - rect_from->top; + + *x -= rect_from->left + width_from / 2.0f; + *x *= (float)width_to / width_from; + *x += rect_to->left + width_to / 2.0f; + + *y -= rect_from->top + height_from / 2.0f; + *y *= (float)height_to / height_from; + *y += rect_to->top + height_to / 2.0f; +} + +void map_float_point_raw_to_virt( struct desktop *desktop, float *x, float *y ) +{ struct monitor_info *monitor; + struct rectangle rect;
- if (!(monitor = get_monitor_from_rect( desktop->winstation, &rect, 1 ))) return; - width_to = monitor->virt.right - monitor->virt.left; - height_to = monitor->virt.bottom - monitor->virt.top; - width_from = monitor->raw.right - monitor->raw.left; - height_from = monitor->raw.bottom - monitor->raw.top; + rect.left = float_round_to_int( *x ); + rect.top = float_round_to_int( *y ); + rect.right = rect.left + 1; + rect.bottom = rect.top + 1; + if ((monitor = get_monitor_from_rect( desktop->winstation, &rect, 1 ))) + rect_map_float_point( &monitor->virt, &monitor->raw, x, y); +}
- *x = *x * 2 - (monitor->raw.left * 2 + width_from); - *x = (*x * width_to * 2 + width_from) / (width_from * 2); - *x = (*x + monitor->virt.left * 2 + width_to) / 2; +void map_float_point_virt_to_raw( struct desktop *desktop, float *x, float *y ) +{ + struct monitor_info *monitor; + struct rectangle rect;
- *y = *y * 2 - (monitor->raw.top * 2 + height_from); - *y = (*y * height_to * 2 + height_from) / (height_from * 2); - *y = (*y + monitor->virt.top * 2 + height_to) / 2; + rect.left = float_round_to_int( *x ); + rect.top = float_round_to_int( *y ); + rect.right = rect.left + 1; + rect.bottom = rect.top + 1; + if ((monitor = get_monitor_from_rect( desktop->winstation, &rect, 0 ))) + rect_map_float_point( &monitor->raw, &monitor->virt, x, y); }
/* get the per-monitor DPI for a window */ @@ -1028,17 +1052,17 @@ static int get_window_children_from_point( struct window *parent, int x, int y, }
/* get handle of root of top-most window containing point (in absolute raw coords) */ -user_handle_t shallow_window_from_point( struct desktop *desktop, int x, int y ) +user_handle_t shallow_window_from_point( struct desktop *desktop, float x, float y ) { struct window *ptr;
if (!desktop->top_window) return 0;
- map_point_raw_to_virt( desktop, &x, &y ); + map_float_point_raw_to_virt( desktop, &x, &y );
LIST_FOR_EACH_ENTRY( ptr, &desktop->top_window->children, struct window, entry ) { - int x_child = x, y_child = y; + int x_child = float_round_to_int( x ), y_child = float_round_to_int( y );
if (!is_point_in_window( ptr, &x_child, &y_child, 0 )) continue; /* skip it */ return ptr->handle; @@ -1047,14 +1071,17 @@ user_handle_t shallow_window_from_point( struct desktop *desktop, int x, int y ) }
/* return thread of top-most window containing point (in absolute raw coords) */ -struct thread *window_thread_from_point( user_handle_t scope, int x, int y ) +struct thread *window_thread_from_point( user_handle_t scope, float float_x, float float_y ) { struct window *win = get_user_object( scope, NTUSER_OBJ_WINDOW ); + int x, y;
if (!win) return NULL;
- map_point_raw_to_virt( win->desktop, &x, &y ); + map_float_point_raw_to_virt( win->desktop, &float_x, &float_y );
+ x = float_round_to_int( float_x ); + y = float_round_to_int( float_y ); screen_to_client( win, &x, &y, 0 ); win = child_window_from_point( win, x, y ); if (!win->thread) return NULL; diff --git a/tools/make_requests b/tools/make_requests index 17e425537b5..c9d4965d4fb 100755 --- a/tools/make_requests +++ b/tools/make_requests @@ -29,6 +29,7 @@ my %formats = "unsigned char" => [ 1, 1, "%02x" ], "unsigned short"=> [ 2, 2, "%04x" ], "unsigned int" => [ 4, 4, "%08x" ], + "float" => [ 4, 4, "%05.2f" ], "data_size_t" => [ 4, 4, "%u" ], "obj_handle_t" => [ 4, 4, "%04x" ], "atom_t" => [ 4, 4, "%04x" ],
On Tue Oct 14 09:36:30 2025 +0000, Jacek Caban wrote:
AFAIR, Windows uses fixed-point in GDI with 4-bit fractional part.
I wanted to avoid any possibility of rounding error, but I think float will avoid that in all cases, and it simplifies calculations. Also it's not necessary to return floats in replies, so I removed that.
This causes an issue with mouse-over-button behaviour, where it acts as if the cursor is no longer over the button. I'll look into it.
I wonder if there is some way to avoid multiple conversions at all? By making the mouse coordinates always stored and transferred in virtual mode coordinates or device coordinates and only converted one way for user-seen messages when needed? While I realize it might be quite not straightforward to do.
On Thu Oct 16 04:31:30 2025 +0000, Paul Gofman wrote:
I wonder if there is some way to avoid multiple conversions at all? By making the mouse coordinates always stored and transferred in virtual mode coordinates or device coordinates and only converted one way for user-seen messages when needed? While I realize it might be quite not straightforward to do.
We must have two-way conversion to allow for `SetCursorPos()`, which is where the issue lies.
Both this version and the old rational version have the mouse-over issue, but I didn't look into it much.
It turns out so long as virt-to-raw and dpi-to-raw-dpi scaling is always up-scaling, which I think will always be true, the test failures (and the Dungeons & Dragons mouse-look issue) are resolved by using float intermediate values when mapping coordinates. This avoids internal rounding errors occurring during multiple scalings in `map_monitor_rect()`. I raised !9183 for it and will close this.
This merge request was closed by Conor McCarthy.