From 67e513044185ff27d4f466900995341d79c6c012 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Mon, 4 Aug 2025 19:43:42 -0400 Subject: [PATCH] x11: Check axis labels when searching for relative axes Prefer axes with the 'Rel X'/'Rel Y' labels, followed by 'Abs X'/'Abs Y', and only fall back to the old behavior of using the first two enumerated axes if no others are found. Fixes a FIXME when determining which axes to use for relative motion. --- src/video/x11/SDL_x11mouse.h | 1 + src/video/x11/SDL_x11xinput2.c | 129 +++++++++++++++++++++------------ 2 files changed, 83 insertions(+), 47 deletions(-) diff --git a/src/video/x11/SDL_x11mouse.h b/src/video/x11/SDL_x11mouse.h index 2a2973c3d7..db6f00c4eb 100644 --- a/src/video/x11/SDL_x11mouse.h +++ b/src/video/x11/SDL_x11mouse.h @@ -26,6 +26,7 @@ typedef struct SDL_XInput2DeviceInfo { int device_id; + int number[2]; bool relative[2]; double minval[2]; double maxval[2]; diff --git a/src/video/x11/SDL_x11xinput2.c b/src/video/x11/SDL_x11xinput2.c index d78c0be5e2..5da1bb791a 100644 --- a/src/video/x11/SDL_x11xinput2.c +++ b/src/video/x11/SDL_x11xinput2.c @@ -45,6 +45,11 @@ static bool xinput2_multitouch_supported; * this extension */ static int xinput2_opcode; +static Atom xinput2_rel_x_atom; +static Atom xinput2_rel_y_atom; +static Atom xinput2_abs_x_atom; +static Atom xinput2_abs_y_atom; + #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2_SUPPORTS_SCROLLINFO typedef struct { @@ -67,23 +72,40 @@ static int scrollable_device_count; static bool xinput2_scrolling_supported; #endif -static void parse_valuators(const double *input_values, const unsigned char *mask, int mask_len, - double *output_values, int output_values_len) +static void parse_relative_valuators(SDL_XInput2DeviceInfo *devinfo, const XIRawEvent *rawev) { - int i = 0, z = 0; - int top = mask_len * 8; - if (top > MAX_AXIS) { - top = MAX_AXIS; + double processed_coords[2] = { 0.0, 0.0 }; + int values_i = 0, found = 0; + + for (int i = 0; i < rawev->valuators.mask_len * 8 && found < 2; ++i) { + if (!XIMaskIsSet(rawev->valuators.mask, i)) { + continue; + } + + for (int j = 0; j < 2; ++j) { + if (devinfo->number[j] == i) { + const double current_val = rawev->valuators.values[values_i]; + + if (devinfo->relative[j]) { + processed_coords[j] = current_val; + } else { + processed_coords[j] = devinfo->prev_coords[j] - current_val; // convert absolute to relative + } + + devinfo->prev_coords[j] = current_val; + ++found; + + break; + } + } + + ++values_i; } - SDL_memset(output_values, 0, output_values_len * sizeof(double)); - for (; i < top && z < output_values_len; i++) { - if (XIMaskIsSet(mask, i)) { - const int value = (int)*input_values; - output_values[z] = value; - input_values++; - } - z++; + // Relative mouse motion is delivered to the window with keyboard focus + SDL_Mouse *mouse = SDL_GetMouse(); + if (mouse->relative_mode && SDL_GetKeyboardFocus()) { + SDL_SendMouseMotion(rawev->time, mouse->focus, (SDL_MouseID)rawev->sourceid, true, (float)processed_coords[0], (float)processed_coords[1]); } } @@ -260,6 +282,12 @@ bool X11_InitXinput2(SDL_VideoDevice *_this) xinput2_multitouch_supported = xinput2_version_atleast(version, 2, 2); #endif + // Populate the atoms for finding relative axes + xinput2_rel_x_atom = X11_XInternAtom(data->display, "Rel X", False); + xinput2_rel_y_atom = X11_XInternAtom(data->display, "Rel Y", False); + xinput2_abs_x_atom = X11_XInternAtom(data->display, "Abs X", False); + xinput2_abs_y_atom = X11_XInternAtom(data->display, "Abs Y", False); + // Enable raw motion events for this display SDL_zero(eventmask); SDL_zeroa(mask); @@ -345,7 +373,6 @@ static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata, SDL_XInput2DeviceInfo *prev = NULL; SDL_XInput2DeviceInfo *devinfo; XIDeviceInfo *xidevinfo; - int axis = 0; int i; for (devinfo = videodata->mouse_device_info; devinfo; devinfo = devinfo->next) { @@ -375,18 +402,49 @@ static SDL_XInput2DeviceInfo *xinput2_get_device_info(SDL_VideoData *videodata, devinfo->device_id = device_id; - /* !!! FIXME: this is sort of hacky because we only care about the first two axes we see, but any given - !!! FIXME: axis could be relative or absolute, and they might not even be the X and Y axes! - !!! FIXME: But we go on, for now. Maybe we need a more robust mouse API in SDL3... */ + /* Search for relative axes with the following priority: + * - Labelled 'Rel X'/'Rel Y' + * - Labelled 'Abs X'/'Abs Y' + * - The first two axes found + */ + bool have_rel_x = false, have_rel_y = false; + bool have_abs_x = false, have_abs_y = false; + int axis_index = 0; for (i = 0; i < xidevinfo->num_classes; i++) { const XIValuatorClassInfo *v = (const XIValuatorClassInfo *)xidevinfo->classes[i]; if (v->type == XIValuatorClass) { - devinfo->relative[axis] = (v->mode == XIModeRelative); - devinfo->minval[axis] = v->min; - devinfo->maxval[axis] = v->max; - if (++axis >= 2) { + if (v->label == xinput2_rel_x_atom || (v->label == xinput2_abs_x_atom && !have_rel_x) || + (axis_index == 0 && !have_rel_x && !have_abs_x)) { + devinfo->number[0] = v->number; + devinfo->relative[0] = (v->mode == XIModeRelative); + devinfo->minval[0] = v->min; + devinfo->maxval[0] = v->max; + + if (v->label == xinput2_rel_x_atom) { + have_rel_x = true; + } else if (v->label == xinput2_abs_x_atom) { + have_abs_x = true; + } + } else if (v->label == xinput2_rel_y_atom || (v->label == xinput2_abs_y_atom && !have_rel_y) || + (axis_index == 1 && !have_rel_y && !have_abs_y)) { + devinfo->number[1] = v->number; + devinfo->relative[1] = (v->mode == XIModeRelative); + devinfo->minval[1] = v->min; + devinfo->maxval[1] = v->max; + + if (v->label == xinput2_rel_y_atom) { + have_rel_y = true; + } else if (v->label == xinput2_abs_y_atom) { + have_abs_y = true; + } + } + + // If two relative axes were found, nothing more to do. + if (have_rel_x && have_rel_y) { break; } + + ++axis_index; } } @@ -437,41 +495,18 @@ void X11_HandleXinput2Event(SDL_VideoDevice *_this, XGenericEventCookie *cookie) { const XIRawEvent *rawev = (const XIRawEvent *)cookie->data; const bool is_pen = X11_FindPenByDeviceID(rawev->sourceid) != NULL; - SDL_Mouse *mouse = SDL_GetMouse(); - SDL_XInput2DeviceInfo *devinfo; - double coords[2]; - double processed_coords[2]; - int i; - Uint64 timestamp = X11_GetEventTimestamp(rawev->time); videodata->global_mouse_changed = true; if (is_pen) { break; // Pens check for XI_Motion instead } - devinfo = xinput2_get_device_info(videodata, rawev->deviceid); + SDL_XInput2DeviceInfo *devinfo = xinput2_get_device_info(videodata, rawev->deviceid); if (!devinfo) { break; // oh well. } - parse_valuators(rawev->raw_values, rawev->valuators.mask, - rawev->valuators.mask_len, coords, 2); - - for (i = 0; i < 2; i++) { - if (devinfo->relative[i]) { - processed_coords[i] = coords[i]; - } else { - processed_coords[i] = devinfo->prev_coords[i] - coords[i]; // convert absolute to relative - } - } - - // Relative mouse motion is delivered to the window with keyboard focus - if (mouse->relative_mode && SDL_GetKeyboardFocus()) { - SDL_SendMouseMotion(timestamp, mouse->focus, (SDL_MouseID)rawev->sourceid, true, (float)processed_coords[0], (float)processed_coords[1]); - } - - devinfo->prev_coords[0] = coords[0]; - devinfo->prev_coords[1] = coords[1]; + parse_relative_valuators(devinfo, rawev); } break; case XI_KeyPress: