wayland: Handle all mouse state in a frame

The hybrid handling can still result in cases where a final event is dropped when the pointer leaves a surface. The spec says that all pointer events should be handled within a frame, so, do so.
This commit is contained in:
Frank Praznik
2026-01-05 13:43:35 -05:00
parent 2bb463921f
commit d102022c95
2 changed files with 132 additions and 80 deletions

View File

@@ -780,25 +780,35 @@ static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
uint32_t time, wl_fixed_t sx, wl_fixed_t sy) uint32_t time, wl_fixed_t sx, wl_fixed_t sy)
{ {
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data; SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
const Uint64 timestamp = Wayland_GetPointerTimestamp(seat, time);
seat->pointer.pending_frame.have_absolute = true;
seat->pointer.pending_frame.absolute.sx = sx; seat->pointer.pending_frame.absolute.sx = sx;
seat->pointer.pending_frame.absolute.sy = sy; seat->pointer.pending_frame.absolute.sy = sy;
if (wl_pointer_get_version(seat->pointer.wl_pointer) >= WL_POINTER_FRAME_SINCE_VERSION) {
seat->pointer.pending_frame.have_absolute = true;
/* The relative pointer timestamp is higher resolution than the default millisecond timestamp, /* The relative pointer timestamp is higher resolution than the default millisecond timestamp,
* but lower than the highres timestamp. Use the best timer available for this frame, but still * but lower than the highres timestamp. Use the best timer available for this frame,
* process the pending millisecond timestamp to update the offset value for other events.
*/ */
const Uint64 timestamp = Wayland_GetPointerTimestamp(seat, time);
if (!seat->pointer.pending_frame.have_relative || seat->pointer.timestamps) { if (!seat->pointer.pending_frame.have_relative || seat->pointer.timestamps) {
seat->pointer.pending_frame.timestamp_ns = timestamp; seat->pointer.pending_frame.timestamp_ns = timestamp;
} }
} else {
if (wl_pointer_get_version(seat->pointer.wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION) { seat->pointer.pending_frame.timestamp_ns = timestamp;
pointer_dispatch_absolute_motion(seat); pointer_dispatch_absolute_motion(seat);
} }
} }
static void pointer_dispatch_enter(SDL_WaylandSeat *seat)
{
SDL_WindowData *window = seat->pointer.pending_frame.enter_window;
seat->pointer.focus = window;
++window->pointer_focus_count;
SDL_SetMouseFocus(window->sdlwindow);
}
static void pointer_handle_enter(void *data, struct wl_pointer *pointer, static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
uint32_t serial, struct wl_surface *surface, uint32_t serial, struct wl_surface *surface,
wl_fixed_t sx_w, wl_fixed_t sy_w) wl_fixed_t sx_w, wl_fixed_t sy_w)
@@ -815,30 +825,21 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
} }
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data; SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
seat->pointer.focus = window; seat->pointer.pending_frame.enter_window = window;
seat->pointer.enter_serial = serial; seat->pointer.enter_serial = serial;
++window->pointer_focus_count;
SDL_SetMouseFocus(window->sdlwindow);
/* In the case of e.g. a pointer confine warp, we may receive an enter /* In the case of e.g. a pointer confine warp, we may receive an enter
* event with no following motion event, but with the new coordinates * event with no following motion event, but with the new coordinates
* as part of the enter event. * as part of the enter event.
*
* If another event with a real timestamp is part of this frame, use it.
* Otherwise, set it to 0 to use the current system timer.
*/ */
if (!seat->pointer.pending_frame.have_absolute &&
!seat->pointer.pending_frame.have_relative &&
!seat->pointer.pending_frame.have_axis) {
seat->pointer.pending_frame.timestamp_ns = 0;
}
seat->pointer.pending_frame.absolute.sx = sx_w; seat->pointer.pending_frame.absolute.sx = sx_w;
seat->pointer.pending_frame.absolute.sy = sy_w; seat->pointer.pending_frame.absolute.sy = sy_w;
if (wl_pointer_get_version(seat->pointer.wl_pointer) >= WL_POINTER_FRAME_SINCE_VERSION) {
seat->pointer.pending_frame.have_absolute = true; seat->pointer.pending_frame.have_absolute = true;
seat->pointer.pending_frame.have_enter = true; } else {
seat->pointer.pending_frame.timestamp_ns = 0;
if (wl_pointer_get_version(seat->pointer.wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION) { pointer_dispatch_enter(seat);
pointer_dispatch_absolute_motion(seat); pointer_dispatch_absolute_motion(seat);
// Update the pointer grab state. // Update the pointer grab state.
@@ -856,26 +857,16 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
} }
} }
static void pointer_handle_leave(void *data, struct wl_pointer *pointer, static void pointer_dispatch_leave(SDL_WaylandSeat *seat)
uint32_t serial, struct wl_surface *surface)
{ {
if (!surface) { SDL_WindowData *window = seat->pointer.pending_frame.leave_window;
// Leave event for a destroyed surface.
return;
}
SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(surface);
if (!window) {
// Not a surface owned by SDL.
return;
}
if (window) {
// Clear the capture flag and raise all buttons // Clear the capture flag and raise all buttons
window->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE; window->sdlwindow->flags &= ~SDL_WINDOW_MOUSE_CAPTURE;
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
seat->pointer.focus = NULL; seat->pointer.focus = NULL;
for (int i = 1; seat->pointer.buttons_pressed; ++i) { for (Uint8 i = 1; seat->pointer.buttons_pressed; ++i) {
if (seat->pointer.buttons_pressed & SDL_BUTTON_MASK(i)) { if (seat->pointer.buttons_pressed & SDL_BUTTON_MASK(i)) {
SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, i, false); SDL_SendMouseButton(0, window->sdlwindow, seat->pointer.sdl_id, i, false);
seat->pointer.buttons_pressed &= ~SDL_BUTTON_MASK(i); seat->pointer.buttons_pressed &= ~SDL_BUTTON_MASK(i);
@@ -890,10 +881,31 @@ static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
if (!--window->pointer_focus_count && had_focus && !window->active_touch_count) { if (!--window->pointer_focus_count && had_focus && !window->active_touch_count) {
SDL_SetMouseFocus(NULL); SDL_SetMouseFocus(NULL);
} }
}
}
static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
uint32_t serial, struct wl_surface *surface)
{
if (!surface) {
// Leave event for a destroyed surface.
return;
}
SDL_WindowData *window = Wayland_GetWindowDataForOwnedSurface(surface);
if (!window) {
// Not a surface owned by SDL.
return;
}
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
seat->pointer.pending_frame.leave_window = window;
if (wl_pointer_get_version(seat->pointer.wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION && window == seat->pointer.focus) {
pointer_dispatch_leave(seat);
Wayland_SeatUpdatePointerGrab(seat); Wayland_SeatUpdatePointerGrab(seat);
Wayland_SeatUpdatePointerCursor(seat); Wayland_SeatUpdatePointerCursor(seat);
} }
}
static bool Wayland_ProcessHitTest(SDL_WaylandSeat *seat, Uint32 serial) static bool Wayland_ProcessHitTest(SDL_WaylandSeat *seat, Uint32 serial)
{ {
@@ -978,47 +990,26 @@ static bool Wayland_ProcessHitTest(SDL_WaylandSeat *seat, Uint32 serial)
return false; return false;
} }
static void pointer_handle_button_common(SDL_WaylandSeat *seat, uint32_t serial, static void pointer_dispatch_button(SDL_WaylandSeat *seat, Uint8 sdl_button, bool down)
Uint64 nsTimestamp, uint32_t button, uint32_t state_w)
{ {
SDL_WindowData *window = seat->pointer.focus; SDL_WindowData *window = seat->pointer.focus;
enum wl_pointer_button_state state = state_w;
Uint8 sdl_button;
const bool down = (state != 0);
switch (button) {
case BTN_LEFT:
sdl_button = SDL_BUTTON_LEFT;
break;
case BTN_MIDDLE:
sdl_button = SDL_BUTTON_MIDDLE;
break;
case BTN_RIGHT:
sdl_button = SDL_BUTTON_RIGHT;
break;
default:
sdl_button = SDL_BUTTON_X1 + (button - BTN_SIDE);
break;
}
if (window) { if (window) {
bool ignore_click = false; bool ignore_click = false;
if (state) { if (down) {
Wayland_UpdateImplicitGrabSerial(seat, serial);
seat->pointer.buttons_pressed |= SDL_BUTTON_MASK(sdl_button); seat->pointer.buttons_pressed |= SDL_BUTTON_MASK(sdl_button);
} else { } else {
seat->pointer.buttons_pressed &= ~(SDL_BUTTON_MASK(sdl_button)); seat->pointer.buttons_pressed &= ~SDL_BUTTON_MASK(sdl_button);
} }
if (sdl_button == SDL_BUTTON_LEFT && Wayland_ProcessHitTest(seat, serial)) { if (sdl_button == SDL_BUTTON_LEFT && Wayland_ProcessHitTest(seat, seat->last_implicit_grab_serial)) {
return; // don't pass this event on to app. return; // don't pass this event on to app.
} }
// Possibly ignore this click if it was to gain focus. // Possibly ignore this click if it was to gain focus.
if (window->last_focus_event_time_ns) { if (window->last_focus_event_time_ns) {
if (state == WL_POINTER_BUTTON_STATE_PRESSED && if (down && (SDL_GetTicksNS() - window->last_focus_event_time_ns) < WAYLAND_FOCUS_CLICK_TIMEOUT_NS) {
(SDL_GetTicksNS() - window->last_focus_event_time_ns) < WAYLAND_FOCUS_CLICK_TIMEOUT_NS) {
ignore_click = !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, false); ignore_click = !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, false);
} }
@@ -1041,7 +1032,7 @@ static void pointer_handle_button_common(SDL_WaylandSeat *seat, uint32_t serial,
} }
if (!ignore_click) { if (!ignore_click) {
SDL_SendMouseButton(nsTimestamp, window->sdlwindow, seat->pointer.sdl_id, sdl_button, down); SDL_SendMouseButton(seat->pointer.pending_frame.timestamp_ns, window->sdlwindow, seat->pointer.sdl_id, sdl_button, down);
} }
} }
} }
@@ -1050,7 +1041,38 @@ static void pointer_handle_button(void *data, struct wl_pointer *pointer, uint32
uint32_t time, uint32_t button, uint32_t state_w) uint32_t time, uint32_t button, uint32_t state_w)
{ {
SDL_WaylandSeat *seat = data; SDL_WaylandSeat *seat = data;
pointer_handle_button_common(seat, serial, Wayland_GetPointerTimestamp(seat, time), button, state_w); Uint8 sdl_button;
switch (button) {
case BTN_LEFT:
sdl_button = SDL_BUTTON_LEFT;
break;
case BTN_MIDDLE:
sdl_button = SDL_BUTTON_MIDDLE;
break;
case BTN_RIGHT:
sdl_button = SDL_BUTTON_RIGHT;
break;
default:
sdl_button = SDL_BUTTON_X1 + (button - BTN_SIDE);
break;
}
if (state_w) {
Wayland_UpdateImplicitGrabSerial(seat, serial);
}
seat->pointer.pending_frame.timestamp_ns = Wayland_GetPointerTimestamp(seat, time);;
if (wl_seat_get_version(seat->wl_seat) >= WL_POINTER_FRAME_SINCE_VERSION) {
if (state_w) {
seat->pointer.pending_frame.buttons_pressed |= SDL_BUTTON_MASK(sdl_button);
} else {
seat->pointer.pending_frame.buttons_released |= SDL_BUTTON_MASK(sdl_button);
}
} else {
pointer_dispatch_button(seat, sdl_button, state_w != 0);
}
} }
static void pointer_handle_axis_common_v1(SDL_WaylandSeat *seat, static void pointer_handle_axis_common_v1(SDL_WaylandSeat *seat,
@@ -1251,10 +1273,18 @@ static void pointer_handle_frame(void *data, struct wl_pointer *pointer)
{ {
SDL_WaylandSeat *seat = data; SDL_WaylandSeat *seat = data;
if (seat->pointer.pending_frame.enter_window) {
if (seat->pointer.pending_frame.leave_window == seat->pointer.focus) {
// Leaving the previous surface before entering a new surface.
pointer_dispatch_leave(seat);
}
pointer_dispatch_enter(seat);
}
if (seat->pointer.pending_frame.have_absolute) { if (seat->pointer.pending_frame.have_absolute) {
pointer_dispatch_absolute_motion(seat); pointer_dispatch_absolute_motion(seat);
if (seat->pointer.pending_frame.have_enter) { if (seat->pointer.pending_frame.enter_window) {
// Update the pointer grab state. // Update the pointer grab state.
Wayland_SeatUpdatePointerGrab(seat); Wayland_SeatUpdatePointerGrab(seat);
@@ -1274,10 +1304,28 @@ static void pointer_handle_frame(void *data, struct wl_pointer *pointer)
pointer_dispatch_relative_motion(seat); pointer_dispatch_relative_motion(seat);
} }
for (Uint8 i = 1; seat->pointer.pending_frame.buttons_pressed || seat->pointer.pending_frame.buttons_released; ++i) {
const Uint32 mask = SDL_BUTTON_MASK(i);
if (seat->pointer.pending_frame.buttons_pressed & mask) {
pointer_dispatch_button(seat, i, true);
seat->pointer.pending_frame.buttons_pressed &= ~mask;
}
if (seat->pointer.pending_frame.buttons_released & mask) {
pointer_dispatch_button(seat, i, false);
seat->pointer.pending_frame.buttons_released &= ~mask;
}
}
if (seat->pointer.pending_frame.have_axis) { if (seat->pointer.pending_frame.have_axis) {
pointer_dispatch_axis(seat); pointer_dispatch_axis(seat);
} }
if (seat->pointer.pending_frame.leave_window == seat->pointer.focus) {
pointer_dispatch_leave(seat);
Wayland_SeatUpdatePointerGrab(seat);
Wayland_SeatUpdatePointerCursor(seat);
}
SDL_zero(seat->pointer.pending_frame); SDL_zero(seat->pointer.pending_frame);
} }
@@ -1297,7 +1345,6 @@ static void pointer_handle_axis_discrete(void *data, struct wl_pointer *pointer,
uint32_t axis, int32_t discrete) uint32_t axis, int32_t discrete)
{ {
SDL_WaylandSeat *seat = data; SDL_WaylandSeat *seat = data;
pointer_handle_axis_common(seat, SDL_WAYLAND_AXIS_EVENT_DISCRETE, axis, wl_fixed_from_int(discrete)); pointer_handle_axis_common(seat, SDL_WAYLAND_AXIS_EVENT_DISCRETE, axis, wl_fixed_from_int(discrete));
} }
@@ -1305,7 +1352,6 @@ static void pointer_handle_axis_value120(void *data, struct wl_pointer *pointer,
uint32_t axis, int32_t value120) uint32_t axis, int32_t value120)
{ {
SDL_WaylandSeat *seat = data; SDL_WaylandSeat *seat = data;
pointer_handle_axis_common(seat, SDL_WAYLAND_AXIS_EVENT_VALUE120, axis, wl_fixed_from_int(value120)); pointer_handle_axis_common(seat, SDL_WAYLAND_AXIS_EVENT_VALUE120, axis, wl_fixed_from_int(value120));
} }
@@ -1335,14 +1381,15 @@ static void relative_pointer_handle_relative_motion(void *data,
SDL_WaylandSeat *seat = data; SDL_WaylandSeat *seat = data;
// Relative pointer event times are in microsecond granularity. // Relative pointer event times are in microsecond granularity.
seat->pointer.pending_frame.have_relative = true;
seat->pointer.pending_frame.relative.dx = dx; seat->pointer.pending_frame.relative.dx = dx;
seat->pointer.pending_frame.relative.dy = dy; seat->pointer.pending_frame.relative.dy = dy;
seat->pointer.pending_frame.relative.dx_unaccel = dx_unaccel; seat->pointer.pending_frame.relative.dx_unaccel = dx_unaccel;
seat->pointer.pending_frame.relative.dy_unaccel = dy_unaccel; seat->pointer.pending_frame.relative.dy_unaccel = dy_unaccel;
seat->pointer.pending_frame.timestamp_ns = Wayland_AdjustEventTimestampBase(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo)); seat->pointer.pending_frame.timestamp_ns = Wayland_AdjustEventTimestampBase(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo));
if (wl_pointer_get_version(seat->pointer.wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION) { if (wl_pointer_get_version(seat->pointer.wl_pointer) >= WL_POINTER_FRAME_SINCE_VERSION) {
seat->pointer.pending_frame.have_relative = true;
} else {
pointer_dispatch_relative_motion(seat); pointer_dispatch_relative_motion(seat);
} }
} }

View File

@@ -205,7 +205,9 @@ typedef struct SDL_WaylandSeat
bool have_absolute; bool have_absolute;
bool have_relative; bool have_relative;
bool have_axis; bool have_axis;
bool have_enter;
Uint32 buttons_pressed;
Uint32 buttons_released;
struct struct
{ {
@@ -232,6 +234,9 @@ typedef struct SDL_WaylandSeat
SDL_MouseWheelDirection direction; SDL_MouseWheelDirection direction;
} axis; } axis;
SDL_WindowData *enter_window;
SDL_WindowData *leave_window;
// Event timestamp in nanoseconds // Event timestamp in nanoseconds
Uint64 timestamp_ns; Uint64 timestamp_ns;
} pending_frame; } pending_frame;