From b560f8ab17fbf6f3000a94f758edf418e531f772 Mon Sep 17 00:00:00 2001
From: Frank Praznik <[EMAIL REDACTED]>
Date: Tue, 17 Mar 2026 12:34:59 -0400
Subject: [PATCH] wayland: Scale custom cursors with the pointer scale
When emulating display modes or using display scaling, custom cursors need to be scaled, or they can appear too large or small relative to the window size.
(cherry picked from commit 3d21b3bc680a9c49f62f6dfc219e2d81fc787ed5)
---
src/video/wayland/SDL_waylandmouse.c | 42 +++++++++++++++++++++++----
src/video/wayland/SDL_waylandmouse.h | 2 ++
src/video/wayland/SDL_waylandwindow.c | 8 +++++
3 files changed, 46 insertions(+), 6 deletions(-)
diff --git a/src/video/wayland/SDL_waylandmouse.c b/src/video/wayland/SDL_waylandmouse.c
index ec54994d87c2d..e367abc384907 100644
--- a/src/video/wayland/SDL_waylandmouse.c
+++ b/src/video/wayland/SDL_waylandmouse.c
@@ -1103,12 +1103,14 @@ static void Wayland_CursorStateSetCursor(SDL_WaylandCursorState *state, const Wa
dst_height = dst_width;
} else {
- // If viewports aren't available, the scale is always 1.0.
- state->scale = viddata->viewporter && focus ? focus->scale_factor : 1.0;
- dst_width = cursor_data->cursor_data.custom.width;
- dst_height = cursor_data->cursor_data.custom.height;
- hot_x = cursor_data->cursor_data.custom.hot_x;
- hot_y = cursor_data->cursor_data.custom.hot_y;
+ /* If viewports aren't available, the scale is always 1.0.
+ * The dimensions are scaled by the pointer scale, so custom cursors will be scaled relative to the window size.
+ */
+ state->scale = viddata->viewporter && focus ? SDL_min(focus->pointer_scale.x, focus->pointer_scale.y) : 1.0;
+ dst_width = SDL_max((int)SDL_lround((double)cursor_data->cursor_data.custom.width / state->scale), 1);
+ dst_height = SDL_max((int)SDL_lround((double)cursor_data->cursor_data.custom.height / state->scale), 1);
+ hot_x = (int)SDL_lround((double)cursor_data->cursor_data.custom.hot_x / state->scale);
+ hot_y = (int)SDL_lround((double)cursor_data->cursor_data.custom.hot_y / state->scale);
}
state->current_cursor = cursor_data;
@@ -1180,6 +1182,34 @@ static void Wayland_CursorStateResetCursor(SDL_WaylandCursorState *state)
state->current_frame = -1;
}
+void Wayland_DisplayUpdatePointerFocusedScale(SDL_WindowData *updated_window)
+{
+ SDL_VideoData *viddata = updated_window->waylandData;
+ SDL_WaylandSeat *seat;
+ const double new_scale = SDL_min(updated_window->pointer_scale.x, updated_window->pointer_scale.y);
+
+ wl_list_for_each (seat, &viddata->seat_list, link) {
+ if (seat->pointer.focus == updated_window) {
+ SDL_WaylandCursorState *state = &seat->pointer.cursor_state;
+ if (state->current_cursor && !state->current_cursor->is_system_cursor && state->scale != new_scale) {
+ Wayland_CursorStateResetCursor(state);
+ Wayland_SeatUpdatePointerCursor(seat);
+ }
+ }
+
+ SDL_WaylandPenTool *tool;
+ wl_list_for_each (tool, &seat->tablet.tool_list, link) {
+ if (tool->focus == updated_window) {
+ SDL_WaylandCursorState *state = &tool->cursor_state;
+ if (state->current_cursor && !state->current_cursor->is_system_cursor && state->scale != new_scale) {
+ Wayland_CursorStateResetCursor(&tool->cursor_state);
+ Wayland_TabletToolUpdateCursor(tool);
+ }
+ }
+ }
+ }
+}
+
static bool Wayland_ShowCursor(SDL_Cursor *cursor)
{
SDL_VideoDevice *vd = SDL_GetVideoDevice();
diff --git a/src/video/wayland/SDL_waylandmouse.h b/src/video/wayland/SDL_waylandmouse.h
index 481cae10739eb..4da54358b595c 100644
--- a/src/video/wayland/SDL_waylandmouse.h
+++ b/src/video/wayland/SDL_waylandmouse.h
@@ -27,6 +27,8 @@
extern void Wayland_InitMouse(SDL_VideoData *data);
extern void Wayland_FiniMouse(SDL_VideoData *data);
extern void Wayland_SeatUpdatePointerCursor(SDL_WaylandSeat *seat);
+extern void Wayland_SeatResetCursor(SDL_WaylandSeat *seat);
+extern void Wayland_DisplayUpdatePointerFocusedScale(SDL_WindowData *updated_window);
extern void Wayland_TabletToolUpdateCursor(SDL_WaylandPenTool *tool);
extern void Wayland_SeatWarpMouse(SDL_WaylandSeat *seat, SDL_WindowData *window, float x, float y);
extern void Wayland_CursorStateSetFrameCallback(SDL_WaylandCursorState *state, void *userdata);
diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c
index 4e5446a666b22..d22a790061b66 100644
--- a/src/video/wayland/SDL_waylandwindow.c
+++ b/src/video/wayland/SDL_waylandwindow.c
@@ -30,6 +30,7 @@
#include "../../core/unix/SDL_appid.h"
#include "../SDL_egl_c.h"
#include "SDL_waylandevents_c.h"
+#include "SDL_waylandmouse.h"
#include "SDL_waylandwindow.h"
#include "SDL_waylandvideo.h"
#include "../../SDL_hints_c.h"
@@ -305,6 +306,8 @@ static void ConfigureWindowGeometry(SDL_Window *window)
{
SDL_WindowData *data = window->internal;
const double scale_factor = GetWindowScale(window);
+ const double prev_pointer_scale_x = data->pointer_scale.x;
+ const double prev_pointer_scale_y = data->pointer_scale.y;
const int old_pixel_width = data->current.pixel_width;
const int old_pixel_height = data->current.pixel_height;
int window_width, window_height;
@@ -429,6 +432,11 @@ static void ConfigureWindowGeometry(SDL_Window *window)
}
}
+ // Update the scale for any focused cursors.
+ if (prev_pointer_scale_x != data->pointer_scale.x || prev_pointer_scale_y != data->pointer_scale.y) {
+ Wayland_DisplayUpdatePointerFocusedScale(data);
+ }
+
/* Update the min/max dimensions, primarily if the state was changed, and for non-resizable
* xdg-toplevel windows where the limits should match the window size.
*/