diff --git a/glfw/x11_init.c b/glfw/x11_init.c index 5b5455ac3..25e22f215 100644 --- a/glfw/x11_init.c +++ b/glfw/x11_init.c @@ -148,6 +148,75 @@ static void detectEWMH(void) XFree(supportedAtoms); } +static void +read_xi_scroll_devices(void) { +#define xi _glfw.x11.xi + xi.num_scroll_devices = 0; + // Require XI2.1 or later for smooth scrolling + if (!xi.available || xi.major < 2 || (xi.major == 2 && xi.minor < 1) || !xi.LIBINPUT_SCROLL_METHOD_ENABLED) return; +#undef xi + int deviceCount; + XIDeviceInfo* devices = XIQueryDevice(_glfw.x11.display, XIAllDevices, &deviceCount); + if (!devices) return; + for (int i = 0; i < deviceCount; i++) { + XIDeviceInfo* device = &devices[i]; + if (device->use != XISlavePointer || !device->enabled) continue; + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char *data = NULL; + bool is_highres = false; + + XIGetProperty(_glfw.x11.display, device->deviceid, _glfw.x11.xi.LIBINPUT_SCROLL_METHOD_ENABLED, 0, 2, False, XA_INTEGER, &actual_type, &actual_format, &nitems, &bytes_after, &data); + if (data) { + if (nitems > 1) is_highres = data[0] || data[1]; + XFree(data); + } + for (int j = 0; j < device->num_classes; j++) { + if (device->classes[j]->type != XIScrollClass) continue; + XIScrollClassInfo* scroll = (XIScrollClassInfo*)device->classes[j]; + XIScrollDevice *d = NULL; + for (unsigned k = 0; k < _glfw.x11.xi.num_scroll_devices; k++) { + XIScrollDevice *t = _glfw.x11.xi.scroll_devices + k; + if (t->deviceid == device->deviceid && t->sourceid == scroll->sourceid) { d = t; break; } + } + if (!d) { + if (_glfw.x11.xi.num_scroll_devices >= arraysz(_glfw.x11.xi.scroll_devices)) continue; + d = &_glfw.x11.xi.scroll_devices[_glfw.x11.xi.num_scroll_devices++]; + *d = (XIScrollDevice){ + .is_highres=is_highres, .deviceid=device->deviceid, .sourceid=scroll->sourceid, + }; + memcpy(d->name, device->name, MIN(sizeof(d->name)-1, strlen(device->name))); + } + if (d->num_valuators >= arraysz(d->valuators)) continue; + XIScrollValuator *v = d->valuators + d->num_valuators++; + *v = (XIScrollValuator){ + .number=scroll->number, .is_vertical=scroll->scroll_type == XIScrollTypeVertical, + .increment=scroll->increment, + }; + } + for (int j = 0; j < device->num_classes; j++) { + if (device->classes[j]->type != XIValuatorClass) continue; + XIValuatorClassInfo* vi = (XIValuatorClassInfo*)device->classes[j]; + XIScrollDevice *d = NULL; + for (unsigned k = 0; k < _glfw.x11.xi.num_scroll_devices; k++) { + XIScrollDevice *t = _glfw.x11.xi.scroll_devices + k; + if (t->deviceid == device->deviceid && t->sourceid == vi->sourceid) { d = t; break; } + } + if (!d) continue; + XIScrollValuator *v = NULL; + for (unsigned k = 0; k < d->num_valuators; k++) { + XIScrollValuator *t = d->valuators + k; + if (t->number == vi->number) { v = t; break; } + } + if (!v) continue; + v->value = vi->value; v->mode = vi->mode; v->resolution = vi->resolution; + v->min = vi->min; v->max = vi->max; + } + } + XIFreeDeviceInfo(devices); +} + // Look for and initialize supported X11 extensions // static bool initExtensions(void) @@ -193,62 +262,6 @@ static bool initExtensions(void) &_glfw.x11.xi.minor) == Success) { _glfw.x11.xi.available = true; - - // Detect smooth scrolling support once globally - _glfw.x11.xi.smoothScroll.available = false; - _glfw.x11.xi.smoothScroll.verticalAxis = -1; - _glfw.x11.xi.smoothScroll.horizontalAxis = -1; - _glfw.x11.xi.smoothScroll.verticalIncrement = 0.0; - _glfw.x11.xi.smoothScroll.horizontalIncrement = 0.0; - - // Require XI2.1 or later for smooth scrolling - if (_glfw.x11.xi.major >= 2 && (_glfw.x11.xi.major > 2 || _glfw.x11.xi.minor >= 1)) - { - if (XIQueryDevice && XIFreeDeviceInfo) - { - int deviceCount; - XIDeviceInfo* devices = XIQueryDevice(_glfw.x11.display, XIAllMasterDevices, &deviceCount); - if (devices) - { - for (int i = 0; i < deviceCount; i++) - { - XIDeviceInfo* device = &devices[i]; - - // Only process master pointer devices - if (device->use != XIMasterPointer) - continue; - - for (int j = 0; j < device->num_classes; j++) - { - if (device->classes[j]->type != XIScrollClass) - continue; - - XIScrollClassInfo* scroll = (XIScrollClassInfo*)device->classes[j]; - - if (scroll->scroll_type == XIScrollTypeVertical) - { - _glfw.x11.xi.smoothScroll.verticalAxis = scroll->number; - _glfw.x11.xi.smoothScroll.verticalIncrement = scroll->increment; - } - else if (scroll->scroll_type == XIScrollTypeHorizontal) - { - _glfw.x11.xi.smoothScroll.horizontalAxis = scroll->number; - _glfw.x11.xi.smoothScroll.horizontalIncrement = scroll->increment; - } - } - } - - XIFreeDeviceInfo(devices); - - // Enable smooth scrolling if we found at least one scroll axis - if (_glfw.x11.xi.smoothScroll.verticalAxis >= 0 || - _glfw.x11.xi.smoothScroll.horizontalAxis >= 0) - { - _glfw.x11.xi.smoothScroll.available = true; - } - } - } - } } } } @@ -496,6 +509,9 @@ static bool initExtensions(void) _glfw.x11.MOTIF_WM_HINTS = XInternAtom(_glfw.x11.display, "_MOTIF_WM_HINTS", False); + _glfw.x11.xi.LIBINPUT_SCROLL_METHOD_ENABLED = XInternAtom(_glfw.x11.display, "libinput Scroll Method Enabled", False); + read_xi_scroll_devices(); + // The compositing manager selection name contains the screen number { char name[32]; diff --git a/glfw/x11_platform.h b/glfw/x11_platform.h index 78354d67a..73c58ca58 100644 --- a/glfw/x11_platform.h +++ b/glfw/x11_platform.h @@ -234,6 +234,18 @@ typedef struct AtomArray { size_t sz, capacity; } AtomArray; +typedef struct XIScrollValuator { + double increment, value, min, max; int number, resolution, mode; bool is_vertical; +} XIScrollValuator; + +typedef struct XIScrollDevice { + bool is_highres; + int deviceid, sourceid; + XIScrollValuator valuators[8]; + unsigned num_valuators; + char name[32]; +} XIScrollDevice; + // X11-specific global data // typedef struct _GLFWlibraryX11 @@ -411,14 +423,9 @@ typedef struct _GLFWlibraryX11 PFN_XIQueryDevice QueryDevice; PFN_XIFreeDeviceInfo FreeDeviceInfo; PFN_XIGetProperty GetProperty; - // Smooth scrolling support - struct { - bool available; - int verticalAxis; - int horizontalAxis; - double verticalIncrement; - double horizontalIncrement; - } smoothScroll; + XIScrollDevice scroll_devices[16]; + unsigned num_scroll_devices; + Atom LIBINPUT_SCROLL_METHOD_ENABLED; } xi; struct { diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 04883c88d..32d45597e 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -499,18 +499,12 @@ static void disableRawMouseMotion(_GLFWwindow* window UNUSED) // static void enableSmoothScrolling(_GLFWwindow* window) { - if (!_glfw.x11.xi.smoothScroll.available) - return; - - // Initialize scroll valuator tracking for this window - window->x11.smoothScroll.verticalValue = 0.0; - window->x11.smoothScroll.horizontalValue = 0.0; - + if (!_glfw.x11.xi.num_scroll_devices) return; // Select XI_Motion events on the window XIEventMask em; unsigned char mask[XIMaskLen(XI_Motion)] = { 0 }; - em.deviceid = XIAllMasterDevices; + em.deviceid = XIAllDevices; em.mask_len = sizeof(mask); em.mask = mask; XISetMask(mask, XI_Motion); @@ -1343,71 +1337,57 @@ static void processEvent(XEvent *event) else if (event->xcookie.evtype == XI_Motion) { XIDeviceEvent* de = (XIDeviceEvent*)event->xcookie.data; - + XIScrollDevice *d = NULL; + for (unsigned i = 0; i < _glfw.x11.xi.num_scroll_devices; i++) { + XIScrollDevice *t = &_glfw.x11.xi.scroll_devices[i]; + if (t->deviceid == de->deviceid && t->sourceid == de->sourceid) { + d = t; break; + } + } // Find the window for this event _GLFWwindow* window = NULL; - if (XFindContext(_glfw.x11.display, de->event, _glfw.x11.context, - (XPointer*)&window) == 0 && - _glfw.x11.xi.smoothScroll.available) - { - double xOffset = 0.0; - double yOffset = 0.0; - bool hasScroll = false; - + if (d && de->valuators.mask_len && XFindContext( + _glfw.x11.display, de->event, _glfw.x11.context, (XPointer*)&window) == 0) { + double xOffset = 0, yOffset = 0; // Process valuators to detect scroll events - if (de->valuators.mask_len) - { - const double* values = de->valuators.values; - - for (int i = 0; i < de->valuators.mask_len * 8; i++) - { - if (!XIMaskIsSet(de->valuators.mask, i)) - continue; - - if (i == _glfw.x11.xi.smoothScroll.verticalAxis) - { - double delta = *values - window->x11.smoothScroll.verticalValue; - window->x11.smoothScroll.verticalValue = *values; - - if (_glfw.x11.xi.smoothScroll.verticalIncrement != 0.0) - { - yOffset = -delta / _glfw.x11.xi.smoothScroll.verticalIncrement; - hasScroll = true; - } + GLFWOffsetType type = GLFW_SCROLL_OFFEST_HIGHRES; + unsigned vidx = 0; + for (int i = 0; i < de->valuators.mask_len * 8; i++) { + if (!XIMaskIsSet(de->valuators.mask, i)) continue; + const double value = de->valuators.values[vidx++]; + XIScrollValuator *v = NULL; + for (unsigned k = 0; k < d->num_valuators; k++) { + XIScrollValuator *t = d->valuators + k; + if (t->number == i) { v = t; break; } + } + if (!v) continue; + double delta = value - v->value; + v->value = value; + if (v->is_vertical) delta *= -1; + double *off = v->is_vertical ? &yOffset : &xOffset; + *off = delta; + if (!d->is_highres) { + if (v->increment == 120.) type = GLFW_SCROLL_OFFEST_V120; + else { + type = GLFW_SCROLL_OFFSET_LINES; + if (v->increment != 0) *off /= v->increment; } - else if (i == _glfw.x11.xi.smoothScroll.horizontalAxis) - { - double delta = *values - window->x11.smoothScroll.horizontalValue; - window->x11.smoothScroll.horizontalValue = *values; - - if (_glfw.x11.xi.smoothScroll.horizontalIncrement != 0.0) - { - xOffset = delta / _glfw.x11.xi.smoothScroll.horizontalIncrement; - hasScroll = true; - } - } - - values++; } } - - if (hasScroll) - { + if (xOffset != 0 || yOffset != 0) { // Get keyboard modifiers int mods = translateState(de->mods.effective); - // Scale offsets by content scale _glfwInputScroll(window, &(GLFWScrollEvent){ .keyboard_modifiers = mods, - .x_offset = xOffset * _glfw.x11.contentScaleX, - .y_offset = yOffset * _glfw.x11.contentScaleY, + .x_offset = xOffset * (type == GLFW_SCROLL_OFFEST_HIGHRES ? _glfw.x11.contentScaleX : 1), + .y_offset = yOffset * (type == GLFW_SCROLL_OFFEST_HIGHRES ? _glfw.x11.contentScaleY : 1), .unscaled = {.x = xOffset, .y = yOffset}, - .offset_type = GLFW_SCROLL_OFFEST_HIGHRES + .offset_type = type, }); } } } - XFreeEventData(_glfw.x11.display, &event->xcookie); } } @@ -1544,22 +1524,22 @@ static void processEvent(XEvent *event) // Only use these if smooth scrolling is not available else if (event->xbutton.button == Button4) { - if (!_glfw.x11.xi.smoothScroll.available) + if (!_glfw.x11.xi.num_scroll_devices) _glfwInputScroll(window, &(GLFWScrollEvent){.keyboard_modifiers=mods, .y_offset=1, .unscaled.y=1}); } else if (event->xbutton.button == Button5) { - if (!_glfw.x11.xi.smoothScroll.available) + if (!_glfw.x11.xi.num_scroll_devices) _glfwInputScroll(window, &(GLFWScrollEvent){.keyboard_modifiers=mods, .y_offset=-1, .unscaled.y=-1}); } else if (event->xbutton.button == Button6) { - if (!_glfw.x11.xi.smoothScroll.available) + if (!_glfw.x11.xi.num_scroll_devices) _glfwInputScroll(window, &(GLFWScrollEvent){.keyboard_modifiers=mods, .x_offset=1, .unscaled.x=1}); } else if (event->xbutton.button == Button7) { - if (!_glfw.x11.xi.smoothScroll.available) + if (!_glfw.x11.xi.num_scroll_devices) _glfwInputScroll(window, &(GLFWScrollEvent){.keyboard_modifiers=mods, .x_offset=-1, .unscaled.x=-1}); }