Skip to content

Commit 06cfea6

Browse files
committed
wayland: Improve pointer confinement reliability
If the pointer should be confined, keep trying until a confine/lock signal is received. This considerably improves locking/confinement reliability on compositors where confining can be a racy operation, or may not take effect until the pointer is actually in the confinement region. A pointer lock is used to special-case 1x1 confinement regions, as otherwise, the pointer can still exhibit jitter at the subpixel level, particularly on scaled desktops.
1 parent 9ff0438 commit 06cfea6

File tree

3 files changed

+73
-56
lines changed

3 files changed

+73
-56
lines changed

src/video/wayland/SDL_waylandevents.c

Lines changed: 52 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -592,16 +592,9 @@ static void pointer_dispatch_absolute_motion(SDL_WaylandSeat *seat)
592592
seat->pointer.last_motion.x = (int)SDL_floorf(sx);
593593
seat->pointer.last_motion.y = (int)SDL_floorf(sy);
594594

595-
/* Pointer confinement regions are created only when the pointer actually enters the region via
596-
* a motion event received from the compositor.
597-
*/
598-
if (!SDL_RectEmpty(&window->mouse_rect) && !seat->pointer.confined_pointer) {
599-
SDL_Rect scaled_mouse_rect;
600-
Wayland_GetScaledMouseRect(window, &scaled_mouse_rect);
601-
602-
if (SDL_PointInRect(&seat->pointer.last_motion, &scaled_mouse_rect)) {
603-
Wayland_SeatUpdatePointerGrab(seat);
604-
}
595+
// If the pointer should be confined, but wasn't for some reason, keep trying until it is.
596+
if (!SDL_RectEmpty(&window->mouse_rect) && !seat->pointer.is_confined) {
597+
Wayland_SeatUpdatePointerGrab(seat);
605598
}
606599

607600
if (window->hit_test) {
@@ -802,7 +795,7 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
802795

803796
static bool Wayland_ProcessHitTest(SDL_WaylandSeat *seat, Uint32 serial)
804797
{
805-
// Locked in relative mode, do nothing.
798+
// Pointer is immobilized, do nothing.
806799
if (seat->pointer.locked_pointer) {
807800
return false;
808801
}
@@ -1259,29 +1252,33 @@ static const struct zwp_relative_pointer_v1_listener relative_pointer_listener =
12591252
relative_pointer_handle_relative_motion,
12601253
};
12611254

1262-
static void locked_pointer_locked(void *data,
1263-
struct zwp_locked_pointer_v1 *locked_pointer)
1255+
static void locked_pointer_locked(void *data, struct zwp_locked_pointer_v1 *locked_pointer)
12641256
{
1257+
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
1258+
seat->pointer.is_confined = true;
12651259
}
12661260

1267-
static void locked_pointer_unlocked(void *data,
1268-
struct zwp_locked_pointer_v1 *locked_pointer)
1261+
static void locked_pointer_unlocked(void *data, struct zwp_locked_pointer_v1 *locked_pointer)
12691262
{
1263+
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
1264+
seat->pointer.is_confined = false;
12701265
}
12711266

12721267
static const struct zwp_locked_pointer_v1_listener locked_pointer_listener = {
12731268
locked_pointer_locked,
12741269
locked_pointer_unlocked,
12751270
};
12761271

1277-
static void confined_pointer_confined(void *data,
1278-
struct zwp_confined_pointer_v1 *confined_pointer)
1272+
static void confined_pointer_confined(void *data, struct zwp_confined_pointer_v1 *confined_pointer)
12791273
{
1274+
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
1275+
seat->pointer.is_confined = true;
12801276
}
12811277

1282-
static void confined_pointer_unconfined(void *data,
1283-
struct zwp_confined_pointer_v1 *confined_pointer)
1278+
static void confined_pointer_unconfined(void *data, struct zwp_confined_pointer_v1 *confined_pointer)
12841279
{
1280+
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
1281+
seat->pointer.is_confined = false;
12851282
}
12861283

12871284
static const struct zwp_confined_pointer_v1_listener confined_pointer_listener = {
@@ -3664,17 +3661,18 @@ void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat)
36643661
SDL_Rect scaled_mouse_rect;
36653662
Wayland_GetScaledMouseRect(window, &scaled_mouse_rect);
36663663

3664+
confine_rect = wl_compositor_create_region(display->compositor);
3665+
wl_region_add(confine_rect,
3666+
scaled_mouse_rect.x,
3667+
scaled_mouse_rect.y,
3668+
scaled_mouse_rect.w,
3669+
scaled_mouse_rect.h);
3670+
36673671
/* Some compositors will only confine the pointer to an arbitrary region if the pointer
3668-
* is already within the confinement area when it is created.
3672+
* is already within the confinement area when it is created. Warp the pointer to the
3673+
* closest point within the confinement zone if outside.
36693674
*/
3670-
if (SDL_PointInRect(&seat->pointer.last_motion, &scaled_mouse_rect)) {
3671-
confine_rect = wl_compositor_create_region(display->compositor);
3672-
wl_region_add(confine_rect,
3673-
scaled_mouse_rect.x,
3674-
scaled_mouse_rect.y,
3675-
scaled_mouse_rect.w,
3676-
scaled_mouse_rect.h);
3677-
} else {
3675+
if (!SDL_PointInRect(&seat->pointer.last_motion, &scaled_mouse_rect)) {
36783676
/* Warp the pointer to the closest point within the confinement zone if outside,
36793677
* The confinement region will be created when a true position event is received.
36803678
*/
@@ -3698,16 +3696,32 @@ void Wayland_SeatUpdatePointerGrab(SDL_WaylandSeat *seat)
36983696
}
36993697

37003698
if (confine_rect || (window->flags & SDL_WINDOW_MOUSE_GRABBED)) {
3701-
seat->pointer.confined_pointer =
3702-
zwp_pointer_constraints_v1_confine_pointer(display->pointer_constraints,
3703-
w->surface,
3704-
seat->pointer.wl_pointer,
3705-
confine_rect,
3706-
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
3707-
zwp_confined_pointer_v1_add_listener(seat->pointer.confined_pointer,
3708-
&confined_pointer_listener,
3709-
window);
3710-
3699+
if (window->mouse_rect.w != 1 && window->mouse_rect.h != 1) {
3700+
seat->pointer.confined_pointer =
3701+
zwp_pointer_constraints_v1_confine_pointer(display->pointer_constraints,
3702+
w->surface,
3703+
seat->pointer.wl_pointer,
3704+
confine_rect,
3705+
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
3706+
zwp_confined_pointer_v1_add_listener(seat->pointer.confined_pointer,
3707+
&confined_pointer_listener,
3708+
seat);
3709+
} else {
3710+
/* Use a lock for 1x1 confinement regions, as the pointer can exhibit subpixel motion otherwise.
3711+
* A null region is used since the warp *should* have placed the pointer where we want it, but
3712+
* better to lock it slightly off than let the pointer escape, as confining to a specific region
3713+
* seems to be a racy operation on some compositors.
3714+
*/
3715+
seat->pointer.locked_pointer =
3716+
zwp_pointer_constraints_v1_lock_pointer(display->pointer_constraints,
3717+
w->surface,
3718+
seat->pointer.wl_pointer,
3719+
NULL,
3720+
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
3721+
zwp_locked_pointer_v1_add_listener(seat->pointer.locked_pointer,
3722+
&locked_pointer_listener,
3723+
seat);
3724+
}
37113725
if (confine_rect) {
37123726
wl_region_destroy(confine_rect);
37133727
}

src/video/wayland/SDL_waylandevents_c.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ typedef struct SDL_WaylandSeat
129129
Uint32 enter_serial;
130130
SDL_MouseButtonFlags buttons_pressed;
131131
SDL_Point last_motion;
132+
bool is_confined;
132133

133134
SDL_MouseID sdl_id;
134135

src/video/wayland/SDL_waylandmouse.c

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -839,37 +839,39 @@ void Wayland_SeatWarpMouse(SDL_WaylandSeat *seat, SDL_WindowData *window, float
839839
const wl_fixed_t f_y = wl_fixed_from_double(SDL_clamp(y / window->pointer_scale.y, 0, window->current.logical_height));
840840
wp_pointer_warp_v1_warp_pointer(d->wp_pointer_warp_v1, window->surface, seat->pointer.wl_pointer, f_x, f_y, seat->pointer.enter_serial);
841841
} else {
842-
bool toggle_lock = !seat->pointer.locked_pointer;
843842
bool update_grabs = false;
844843

844+
// Pointers can only have one confinement type active on a surface at one time.
845+
if (seat->pointer.confined_pointer) {
846+
zwp_confined_pointer_v1_destroy(seat->pointer.confined_pointer);
847+
seat->pointer.confined_pointer = NULL;
848+
update_grabs = true;
849+
}
850+
if (seat->pointer.locked_pointer) {
851+
zwp_locked_pointer_v1_destroy(seat->pointer.locked_pointer);
852+
seat->pointer.locked_pointer = NULL;
853+
update_grabs = true;
854+
}
855+
845856
/* The pointer confinement protocol allows setting a hint to warp the pointer,
846857
* but only when the pointer is locked.
847858
*
848859
* Lock the pointer, set the position hint, unlock, and hope for the best.
849860
*/
850-
if (toggle_lock) {
851-
if (seat->pointer.confined_pointer) {
852-
zwp_confined_pointer_v1_destroy(seat->pointer.confined_pointer);
853-
seat->pointer.confined_pointer = NULL;
854-
update_grabs = true;
855-
}
856-
seat->pointer.locked_pointer = zwp_pointer_constraints_v1_lock_pointer(d->pointer_constraints, window->surface,
857-
seat->pointer.wl_pointer, NULL,
858-
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT);
859-
}
861+
struct zwp_locked_pointer_v1 *warp_lock =
862+
zwp_pointer_constraints_v1_lock_pointer(d->pointer_constraints, window->surface,
863+
seat->pointer.wl_pointer, NULL,
864+
ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT);
860865

861866
const wl_fixed_t f_x = wl_fixed_from_double(x / window->pointer_scale.x);
862867
const wl_fixed_t f_y = wl_fixed_from_double(y / window->pointer_scale.y);
863-
zwp_locked_pointer_v1_set_cursor_position_hint(seat->pointer.locked_pointer, f_x, f_y);
868+
zwp_locked_pointer_v1_set_cursor_position_hint(warp_lock, f_x, f_y);
864869
wl_surface_commit(window->surface);
865870

866-
if (toggle_lock) {
867-
zwp_locked_pointer_v1_destroy(seat->pointer.locked_pointer);
868-
seat->pointer.locked_pointer = NULL;
871+
zwp_locked_pointer_v1_destroy(warp_lock);
869872

870-
if (update_grabs) {
871-
Wayland_SeatUpdatePointerGrab(seat);
872-
}
873+
if (update_grabs) {
874+
Wayland_SeatUpdatePointerGrab(seat);
873875
}
874876

875877
/* NOTE: There is a pending warp event under discussion that should replace this when available.

0 commit comments

Comments
 (0)