wayland: Send motion events in a frame

Pointer events in seat version 5 and higher should be grouped and sent together when a pointer frame event occurs. Store pending pointer motion events and dispatch them together when a frame event is received.

This helps avoid spurious pointer motion events that some compositors generate with no associated frame (e.g. from some tablet events).
This commit is contained in:
Frank Praznik
2025-05-23 11:32:15 -04:00
parent e9535fa026
commit 9ff0438863
2 changed files with 198 additions and 104 deletions

View File

@@ -237,14 +237,14 @@ static Uint64 Wayland_GetKeyboardTimestamp(SDL_WaylandSeat *seat, Uint32 wl_time
static Uint64 Wayland_GetPointerTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms)
{
const Uint64 adjustedTimestampMS = Wayland_EventTimestampMSToNS(wl_timestamp_ms);
return Wayland_AdjustEventTimestampBase(seat->pointer.timestamps ? seat->pointer.highres_timestamp_ns : adjustedTimestampMS);
const Uint64 adjustedTimestampNS = Wayland_EventTimestampMSToNS(wl_timestamp_ms);
return Wayland_AdjustEventTimestampBase(seat->pointer.timestamps ? seat->pointer.highres_timestamp_ns : adjustedTimestampNS);
}
Uint64 Wayland_GetTouchTimestamp(SDL_WaylandSeat *seat, Uint32 wl_timestamp_ms)
{
const Uint64 adjustedTimestampMS = Wayland_EventTimestampMSToNS(wl_timestamp_ms);
return Wayland_AdjustEventTimestampBase(seat->touch.timestamps ? seat->touch.highres_timestamp_ns : adjustedTimestampMS);
const Uint64 adjustedTimestampNS = Wayland_EventTimestampMSToNS(wl_timestamp_ms);
return Wayland_AdjustEventTimestampBase(seat->touch.timestamps ? seat->touch.highres_timestamp_ns : adjustedTimestampNS);
}
static void input_timestamp_listener(void *data, struct zwp_input_timestamps_v1 *zwp_input_timestamps_v1,
@@ -579,15 +579,15 @@ void Wayland_PumpEvents(SDL_VideoDevice *_this)
}
}
static void pointer_handle_motion_common(SDL_WaylandSeat *seat, Uint64 nsTimestamp, wl_fixed_t sx_w, wl_fixed_t sy_w)
static void pointer_dispatch_absolute_motion(SDL_WaylandSeat *seat)
{
SDL_WindowData *window_data = seat->pointer.focus;
SDL_Window *window = window_data ? window_data->sdlwindow : NULL;
if (window_data) {
const float sx = (float)(wl_fixed_to_double(sx_w) * window_data->pointer_scale.x);
const float sy = (float)(wl_fixed_to_double(sy_w) * window_data->pointer_scale.y);
SDL_SendMouseMotion(nsTimestamp, window_data->sdlwindow, seat->pointer.sdl_id, false, sx, sy);
const float sx = (float)(wl_fixed_to_double(seat->pointer.pending_frame.absolute.sx) * window_data->pointer_scale.x);
const float sy = (float)(wl_fixed_to_double(seat->pointer.pending_frame.absolute.sy) * window_data->pointer_scale.y);
SDL_SendMouseMotion(seat->pointer.pending_frame.timestamp_ns, window_data->sdlwindow, seat->pointer.sdl_id, false, sx, sy);
seat->pointer.last_motion.x = (int)SDL_floorf(sx);
seat->pointer.last_motion.y = (int)SDL_floorf(sy);
@@ -682,10 +682,26 @@ static void pointer_handle_motion_common(SDL_WaylandSeat *seat, Uint64 nsTimesta
}
static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w)
uint32_t time, wl_fixed_t sx, wl_fixed_t sy)
{
SDL_WaylandSeat *seat = (SDL_WaylandSeat *)data;
pointer_handle_motion_common(seat, Wayland_GetPointerTimestamp(seat, time), sx_w, sy_w);
seat->pointer.pending_frame.have_absolute = true;
seat->pointer.pending_frame.absolute.sx = sx;
seat->pointer.pending_frame.absolute.sy = sy;
/* 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
* 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) {
seat->pointer.pending_frame.timestamp_ns = timestamp;
}
if (wl_pointer_get_version(seat->pointer.wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION) {
pointer_dispatch_absolute_motion(seat);
}
}
static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
@@ -713,23 +729,36 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
* event with no following motion event, but with the new coordinates
* as part of the enter event.
*
* FIXME: This causes a movement event with an anomalous timestamp when
* the cursor enters the window.
* 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.
*/
pointer_handle_motion_common(seat, 0, sx_w, sy_w);
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.sy = sy_w;
// Update the pointer grab state.
Wayland_SeatUpdatePointerGrab(seat);
seat->pointer.pending_frame.have_absolute = true;
seat->pointer.pending_frame.have_enter = true;
/* If the cursor was changed while our window didn't have pointer
* focus, we might need to trigger another call to
* wl_pointer_set_cursor() for the new cursor to be displayed.
*
* This will also update the cursor if a second pointer entered a
* window that already has focus, as the focus change sequence
* won't be run.
*/
Wayland_SeatUpdateCursor(seat);
if (wl_pointer_get_version(seat->pointer.wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION) {
pointer_dispatch_absolute_motion(seat);
// Update the pointer grab state.
Wayland_SeatUpdatePointerGrab(seat);
/* If the cursor was changed while our window didn't have pointer
* focus, we might need to trigger another call to
* wl_pointer_set_cursor() for the new cursor to be displayed.
*
* This will also update the cursor if a second pointer entered a
* window that already has focus, as the focus change sequence
* won't be run.
*/
Wayland_SeatUpdateCursor(seat);
}
}
static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
@@ -969,65 +998,67 @@ static void pointer_handle_axis_common(SDL_WaylandSeat *seat, enum SDL_WaylandAx
const enum wl_pointer_axis a = axis;
if (seat->pointer.focus) {
seat->pointer.pending_frame.have_axis = true;
switch (a) {
case WL_POINTER_AXIS_VERTICAL_SCROLL:
switch (type) {
case AXIS_EVENT_VALUE120:
case SDL_WAYLAND_AXIS_EVENT_VALUE120:
/*
* High resolution scroll event. The spec doesn't state that axis_value120
* events are limited to one per frame, so the values are accumulated.
*/
if (seat->pointer.current_axis_info.y_axis_type != AXIS_EVENT_VALUE120) {
seat->pointer.current_axis_info.y_axis_type = AXIS_EVENT_VALUE120;
seat->pointer.current_axis_info.y = 0.0f;
if (seat->pointer.pending_frame.axis.y_axis_type != SDL_WAYLAND_AXIS_EVENT_VALUE120) {
seat->pointer.pending_frame.axis.y_axis_type = SDL_WAYLAND_AXIS_EVENT_VALUE120;
seat->pointer.pending_frame.axis.y = 0.0f;
}
seat->pointer.current_axis_info.y += 0 - (float)wl_fixed_to_double(value);
seat->pointer.pending_frame.axis.y += 0 - (float)wl_fixed_to_double(value);
break;
case AXIS_EVENT_DISCRETE:
case SDL_WAYLAND_AXIS_EVENT_DISCRETE:
/*
* This is a discrete axis event, so we process it and set the
* flag to ignore future continuous axis events in this frame.
*/
if (seat->pointer.current_axis_info.y_axis_type != AXIS_EVENT_DISCRETE) {
seat->pointer.current_axis_info.y_axis_type = AXIS_EVENT_DISCRETE;
seat->pointer.current_axis_info.y = 0 - (float)wl_fixed_to_double(value);
if (seat->pointer.pending_frame.axis.y_axis_type != SDL_WAYLAND_AXIS_EVENT_DISCRETE) {
seat->pointer.pending_frame.axis.y_axis_type = SDL_WAYLAND_AXIS_EVENT_DISCRETE;
seat->pointer.pending_frame.axis.y = 0 - (float)wl_fixed_to_double(value);
}
break;
case AXIS_EVENT_CONTINUOUS:
case SDL_WAYLAND_AXIS_EVENT_CONTINUOUS:
// Only process continuous events if no discrete events have been received.
if (seat->pointer.current_axis_info.y_axis_type == AXIS_EVENT_CONTINUOUS) {
seat->pointer.current_axis_info.y = 0 - (float)wl_fixed_to_double(value);
if (seat->pointer.pending_frame.axis.y_axis_type == SDL_WAYLAND_AXIS_EVENT_CONTINUOUS) {
seat->pointer.pending_frame.axis.y = 0 - (float)wl_fixed_to_double(value);
}
break;
}
break;
case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
switch (type) {
case AXIS_EVENT_VALUE120:
case SDL_WAYLAND_AXIS_EVENT_VALUE120:
/*
* High resolution scroll event. The spec doesn't state that axis_value120
* events are limited to one per frame, so the values are accumulated.
*/
if (seat->pointer.current_axis_info.x_axis_type != AXIS_EVENT_VALUE120) {
seat->pointer.current_axis_info.x_axis_type = AXIS_EVENT_VALUE120;
seat->pointer.current_axis_info.x = 0.0f;
if (seat->pointer.pending_frame.axis.x_axis_type != SDL_WAYLAND_AXIS_EVENT_VALUE120) {
seat->pointer.pending_frame.axis.x_axis_type = SDL_WAYLAND_AXIS_EVENT_VALUE120;
seat->pointer.pending_frame.axis.x = 0.0f;
}
seat->pointer.current_axis_info.x += (float)wl_fixed_to_double(value);
seat->pointer.pending_frame.axis.x += (float)wl_fixed_to_double(value);
break;
case AXIS_EVENT_DISCRETE:
case SDL_WAYLAND_AXIS_EVENT_DISCRETE:
/*
* This is a discrete axis event, so we process it and set the
* flag to ignore future continuous axis events in this frame.
*/
if (seat->pointer.current_axis_info.x_axis_type != AXIS_EVENT_DISCRETE) {
seat->pointer.current_axis_info.x_axis_type = AXIS_EVENT_DISCRETE;
seat->pointer.current_axis_info.x = (float)wl_fixed_to_double(value);
if (seat->pointer.pending_frame.axis.x_axis_type != SDL_WAYLAND_AXIS_EVENT_DISCRETE) {
seat->pointer.pending_frame.axis.x_axis_type = SDL_WAYLAND_AXIS_EVENT_DISCRETE;
seat->pointer.pending_frame.axis.x = (float)wl_fixed_to_double(value);
}
break;
case AXIS_EVENT_CONTINUOUS:
case SDL_WAYLAND_AXIS_EVENT_CONTINUOUS:
// Only process continuous events if no discrete events have been received.
if (seat->pointer.current_axis_info.x_axis_type == AXIS_EVENT_CONTINUOUS) {
seat->pointer.current_axis_info.x = (float)wl_fixed_to_double(value);
if (seat->pointer.pending_frame.axis.x_axis_type == SDL_WAYLAND_AXIS_EVENT_CONTINUOUS) {
seat->pointer.pending_frame.axis.x = (float)wl_fixed_to_double(value);
}
break;
}
@@ -1043,8 +1074,8 @@ static void pointer_handle_axis(void *data, struct wl_pointer *pointer,
const Uint64 nsTimestamp = Wayland_GetPointerTimestamp(seat, time);
if (wl_seat_get_version(seat->wl_seat) >= WL_POINTER_FRAME_SINCE_VERSION) {
seat->pointer.current_axis_info.timestamp_ns = nsTimestamp;
pointer_handle_axis_common(seat, AXIS_EVENT_CONTINUOUS, axis, value);
seat->pointer.pending_frame.timestamp_ns = nsTimestamp;
pointer_handle_axis_common(seat, SDL_WAYLAND_AXIS_EVENT_CONTINUOUS, axis, value);
} else {
pointer_handle_axis_common_v1(seat, nsTimestamp, axis, value);
}
@@ -1059,58 +1090,103 @@ static void pointer_handle_axis_relative_direction(void *data, struct wl_pointer
}
switch (axis_relative_direction) {
case WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL:
seat->pointer.current_axis_info.direction = SDL_MOUSEWHEEL_NORMAL;
seat->pointer.pending_frame.axis.direction = SDL_MOUSEWHEEL_NORMAL;
break;
case WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED:
seat->pointer.current_axis_info.direction = SDL_MOUSEWHEEL_FLIPPED;
seat->pointer.pending_frame.axis.direction = SDL_MOUSEWHEEL_FLIPPED;
break;
}
}
static void pointer_handle_frame(void *data, struct wl_pointer *pointer)
static void pointer_dispatch_relative_motion(SDL_WaylandSeat *seat)
{
SDL_WaylandSeat *seat = data;
SDL_WindowData *window = seat->pointer.focus;
float x, y;
SDL_MouseWheelDirection direction = seat->pointer.current_axis_info.direction;
SDL_Mouse *mouse = SDL_GetMouse();
switch (seat->pointer.current_axis_info.x_axis_type) {
case AXIS_EVENT_CONTINUOUS:
x = seat->pointer.current_axis_info.x / WAYLAND_WHEEL_AXIS_UNIT;
double dx;
double dy;
if (mouse->InputTransform || !mouse->enable_relative_system_scale) {
dx = wl_fixed_to_double(seat->pointer.pending_frame.relative.dx_unaccel);
dy = wl_fixed_to_double(seat->pointer.pending_frame.relative.dy_unaccel);
} else {
dx = wl_fixed_to_double(seat->pointer.pending_frame.relative.dx) * window->pointer_scale.x;
dy = wl_fixed_to_double(seat->pointer.pending_frame.relative.dy) * window->pointer_scale.y;
}
SDL_SendMouseMotion(seat->pointer.pending_frame.timestamp_ns, window->sdlwindow, seat->pointer.sdl_id, true, (float)dx, (float)dy);
}
static void pointer_dispatch_axis(SDL_WaylandSeat *seat)
{
float x, y;
SDL_MouseWheelDirection direction = seat->pointer.pending_frame.axis.direction;
switch (seat->pointer.pending_frame.axis.x_axis_type) {
case SDL_WAYLAND_AXIS_EVENT_CONTINUOUS:
x = seat->pointer.pending_frame.axis.x / WAYLAND_WHEEL_AXIS_UNIT;
break;
case AXIS_EVENT_DISCRETE:
x = seat->pointer.current_axis_info.x;
case SDL_WAYLAND_AXIS_EVENT_DISCRETE:
x = seat->pointer.pending_frame.axis.x;
break;
case AXIS_EVENT_VALUE120:
x = seat->pointer.current_axis_info.x / 120.0f;
case SDL_WAYLAND_AXIS_EVENT_VALUE120:
x = seat->pointer.pending_frame.axis.x / 120.0f;
break;
default:
x = 0.0f;
break;
}
switch (seat->pointer.current_axis_info.y_axis_type) {
case AXIS_EVENT_CONTINUOUS:
y = seat->pointer.current_axis_info.y / WAYLAND_WHEEL_AXIS_UNIT;
switch (seat->pointer.pending_frame.axis.y_axis_type) {
case SDL_WAYLAND_AXIS_EVENT_CONTINUOUS:
y = seat->pointer.pending_frame.axis.y / WAYLAND_WHEEL_AXIS_UNIT;
break;
case AXIS_EVENT_DISCRETE:
y = seat->pointer.current_axis_info.y;
case SDL_WAYLAND_AXIS_EVENT_DISCRETE:
y = seat->pointer.pending_frame.axis.y;
break;
case AXIS_EVENT_VALUE120:
y = seat->pointer.current_axis_info.y / 120.0f;
case SDL_WAYLAND_AXIS_EVENT_VALUE120:
y = seat->pointer.pending_frame.axis.y / 120.0f;
break;
default:
y = 0.0f;
break;
}
// clear pointer.current_axis_info for next frame
SDL_memset(&seat->pointer.current_axis_info, 0, sizeof(seat->pointer.current_axis_info));
SDL_SendMouseWheel(seat->pointer.pending_frame.timestamp_ns,
seat->pointer.focus->sdlwindow, seat->pointer.sdl_id, x, y, direction);
}
if (x != 0.0f || y != 0.0f) {
SDL_SendMouseWheel(seat->pointer.current_axis_info.timestamp_ns,
window->sdlwindow, seat->pointer.sdl_id, x, y, direction);
static void pointer_handle_frame(void *data, struct wl_pointer *pointer)
{
SDL_WaylandSeat *seat = data;
if (seat->pointer.pending_frame.have_absolute) {
pointer_dispatch_absolute_motion(seat);
if (seat->pointer.pending_frame.have_enter) {
// Update the pointer grab state.
Wayland_SeatUpdatePointerGrab(seat);
/* If the cursor was changed while our window didn't have pointer
* focus, we might need to trigger another call to
* wl_pointer_set_cursor() for the new cursor to be displayed.
*
* This will also update the cursor if a second pointer entered a
* window that already has focus, as the focus change sequence
* won't be run.
*/
Wayland_SeatUpdateCursor(seat);
}
}
if (seat->pointer.pending_frame.have_relative) {
pointer_dispatch_relative_motion(seat);
}
if (seat->pointer.pending_frame.have_axis) {
pointer_dispatch_axis(seat);
}
SDL_zero(seat->pointer.pending_frame);
}
static void pointer_handle_axis_source(void *data, struct wl_pointer *pointer,
@@ -1130,7 +1206,7 @@ static void pointer_handle_axis_discrete(void *data, struct wl_pointer *pointer,
{
SDL_WaylandSeat *seat = data;
pointer_handle_axis_common(seat, 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));
}
static void pointer_handle_axis_value120(void *data, struct wl_pointer *pointer,
@@ -1138,7 +1214,7 @@ static void pointer_handle_axis_value120(void *data, struct wl_pointer *pointer,
{
SDL_WaylandSeat *seat = data;
pointer_handle_axis_common(seat, 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));
}
static const struct wl_pointer_listener pointer_listener = {
@@ -1159,29 +1235,24 @@ static void relative_pointer_handle_relative_motion(void *data,
struct zwp_relative_pointer_v1 *pointer,
uint32_t time_hi,
uint32_t time_lo,
wl_fixed_t dx_w,
wl_fixed_t dy_w,
wl_fixed_t dx_unaccel_w,
wl_fixed_t dy_unaccel_w)
wl_fixed_t dx,
wl_fixed_t dy,
wl_fixed_t dx_unaccel,
wl_fixed_t dy_unaccel)
{
SDL_WaylandSeat *seat = data;
SDL_WindowData *window = seat->pointer.focus;
SDL_Mouse *mouse = SDL_GetMouse();
// Relative pointer event times are in microsecond granularity.
const Uint64 timestamp = Wayland_AdjustEventTimestampBase(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo));
seat->pointer.pending_frame.have_relative = true;
seat->pointer.pending_frame.relative.dx = dx;
seat->pointer.pending_frame.relative.dy = dy;
seat->pointer.pending_frame.relative.dx_unaccel = dx;
seat->pointer.pending_frame.relative.dy_unaccel = dy;
seat->pointer.pending_frame.timestamp_ns = Wayland_AdjustEventTimestampBase(SDL_US_TO_NS(((Uint64)time_hi << 32) | (Uint64)time_lo));
double dx;
double dy;
if (mouse->InputTransform || !mouse->enable_relative_system_scale) {
dx = wl_fixed_to_double(dx_unaccel_w);
dy = wl_fixed_to_double(dy_unaccel_w);
} else {
dx = wl_fixed_to_double(dx_w) * window->pointer_scale.x;
dy = wl_fixed_to_double(dy_w) * window->pointer_scale.y;
if (wl_pointer_get_version(seat->pointer.wl_pointer) < WL_POINTER_FRAME_SINCE_VERSION) {
pointer_dispatch_relative_motion(seat);
}
SDL_SendMouseMotion(timestamp, window->sdlwindow, seat->pointer.sdl_id, true, (float)dx, (float)dy);
}
static const struct zwp_relative_pointer_v1_listener relative_pointer_listener = {
@@ -2214,7 +2285,7 @@ static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum w
if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && !seat->pointer.wl_pointer) {
seat->pointer.wl_pointer = wl_seat_get_pointer(wl_seat);
SDL_memset(&seat->pointer.current_axis_info, 0, sizeof(seat->pointer.current_axis_info));
SDL_memset(&seat->pointer.pending_frame.axis, 0, sizeof(seat->pointer.pending_frame.axis));
Wayland_SeatCreateCursorShape(seat);

View File

@@ -35,9 +35,9 @@
enum SDL_WaylandAxisEvent
{
AXIS_EVENT_CONTINUOUS = 0,
AXIS_EVENT_DISCRETE,
AXIS_EVENT_VALUE120
SDL_WAYLAND_AXIS_EVENT_CONTINUOUS = 0,
SDL_WAYLAND_AXIS_EVENT_DISCRETE,
SDL_WAYLAND_AXIS_EVENT_VALUE120
};
typedef struct
@@ -135,16 +135,39 @@ typedef struct SDL_WaylandSeat
// Information about axis events on the current frame
struct
{
enum SDL_WaylandAxisEvent x_axis_type;
float x;
bool have_absolute;
bool have_relative;
bool have_axis;
bool have_enter;
enum SDL_WaylandAxisEvent y_axis_type;
float y;
struct
{
wl_fixed_t sx;
wl_fixed_t sy;
} absolute;
struct
{
wl_fixed_t dx;
wl_fixed_t dy;
wl_fixed_t dx_unaccel;
wl_fixed_t dy_unaccel;
} relative;
struct
{
enum SDL_WaylandAxisEvent x_axis_type;
float x;
enum SDL_WaylandAxisEvent y_axis_type;
float y;
SDL_MouseWheelDirection direction;
} axis;
// Event timestamp in nanoseconds
Uint64 timestamp_ns;
SDL_MouseWheelDirection direction;
} current_axis_info;
} pending_frame;
// Cursor state
struct