diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 17:08:48 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 17:08:48 +0300 |
| commit | ac6f9f5e7711521455b83ceb843a25e091646d43 (patch) | |
| tree | 0dfd5fc46a605cd4980ef3c9131ddbaa2270c78a /src | |
| parent | d4d13e616cb15ef22479d9cb97922af93317a01a (diff) | |
Report the system double-click interval
wsap_term hardcodes 400 ms in three places to count clicks.
yasl_display_double_click_ms() reads the interval from the system. X11
parses Net/DoubleClickTime from the XSETTINGS owner's property. Wayland
has no protocol for it, so it asks the Settings portal for KDE's
DoubleClickInterval, which KDE sends as a string and which exists only
when the user set it, then GNOME's double-click. X11 without XSETTINGS
asks the portal too. Cocoa reads NSEvent.doubleClickInterval. Missing or
implausible values give YASL_DOUBLE_CLICK_MS_DEFAULT, 400 ms.
The X11 state test covers the XSETTINGS parser in both byte orders and
with truncation. The Xwayland test owns the settings selection. The
desktop test serves both portal keys. The Cocoa path is not built or run
here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/common/backend.c | 7 | ||||
| -rw-r--r-- | src/common/backend.h | 2 | ||||
| -rw-r--r-- | src/platform/cocoa/window.m | 7 | ||||
| -rw-r--r-- | src/platform/freedesktop/dbus.c | 21 | ||||
| -rw-r--r-- | src/platform/freedesktop/dbus.h | 5 | ||||
| -rw-r--r-- | src/platform/wayland/backend.c | 9 | ||||
| -rw-r--r-- | src/platform/x11/window.c | 80 |
7 files changed, 130 insertions, 1 deletions
diff --git a/src/common/backend.c b/src/common/backend.c index d35d243..b15233f 100644 --- a/src/common/backend.c +++ b/src/common/backend.c @@ -110,6 +110,13 @@ int yasl_display_modifiers(const struct yasl_display *d) return d->ops->display_modifiers(d); } +int yasl_display_double_click_ms(const struct yasl_display *d) +{ + int ms = d && d->ops->display_double_click_ms ? d->ops->display_double_click_ms(d) : 0; + /* Reject nonsense from misconfigured settings as well as no answer. */ + return ms > 0 && ms <= 10000 ? ms : YASL_DOUBLE_CLICK_MS_DEFAULT; +} + void yasl_display_copy(struct yasl_display *d, enum yasl_selection sel, const char *text) { if (!d || !d->ops->display_copy || !text || diff --git a/src/common/backend.h b/src/common/backend.h index fd60418..96ce9e7 100644 --- a/src/common/backend.h +++ b/src/common/backend.h @@ -57,6 +57,8 @@ struct yasl_backend_ops { int (*display_dispatch_timeout)(struct yasl_display *d, int timeout_ms); void (*display_flush)(struct yasl_display *d); int (*display_modifiers)(const struct yasl_display *d); + /* Optional; returns zero or less when the system reports no interval. */ + int (*display_double_click_ms)(const struct yasl_display *d); void (*display_copy)(struct yasl_display *d, enum yasl_selection sel, const char *text); void (*display_paste)(struct yasl_display *d, enum yasl_selection sel, struct yasl_surface *target); bool (*display_drag_start)(struct yasl_display *d, struct yasl_surface *origin, const struct yasl_drag_image *image, const struct yasl_drag_listener *l, void *ud); diff --git a/src/platform/cocoa/window.m b/src/platform/cocoa/window.m index 75dce54..1efff59 100644 --- a/src/platform/cocoa/window.m +++ b/src/platform/cocoa/window.m @@ -2047,6 +2047,12 @@ static void impl_display_flush(struct yasl_display *d) cocoa_display_flush((struct cocoa_display *)d); } +static int impl_display_double_click_ms(const struct yasl_display *d) +{ + (void)d; + return (int)lround([NSEvent doubleClickInterval] * 1000.0); +} + static int impl_display_modifiers(const struct yasl_display *d) { return cocoa_display_modifiers((const struct cocoa_display *)d); @@ -2231,6 +2237,7 @@ const struct yasl_backend_ops yasl_cocoa_backend = { .display_dispatch_timeout = impl_display_dispatch_timeout, .display_flush = impl_display_flush, .display_modifiers = impl_display_modifiers, + .display_double_click_ms = impl_display_double_click_ms, .display_copy = impl_display_copy, .display_paste = impl_display_paste, .display_drag_start = impl_display_drag_start, diff --git a/src/platform/freedesktop/dbus.c b/src/platform/freedesktop/dbus.c index abca900..484b987 100644 --- a/src/platform/freedesktop/dbus.c +++ b/src/platform/freedesktop/dbus.c @@ -4,6 +4,7 @@ #if YASL_HAS_DBUS #include <dbus/dbus.h> +#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -116,6 +117,15 @@ static bool iter_int(DBusMessageIter *it, int64_t *out) } DBusBasicValue v; switch (dbus_message_iter_get_arg_type(it)) { + case DBUS_TYPE_STRING: { + dbus_message_iter_get_basic(it, &v); + char *end; + errno = 0; + long long n = strtoll(v.str, &end, 10); + if (errno || end == v.str || *end) return false; + *out = n; + return true; + } case DBUS_TYPE_BYTE: dbus_message_iter_get_basic(it, &v); *out = v.byt; return true; case DBUS_TYPE_BOOLEAN: dbus_message_iter_get_basic(it, &v); *out = v.bool_val; return true; case DBUS_TYPE_INT16: dbus_message_iter_get_basic(it, &v); *out = v.i16; return true; @@ -185,3 +195,14 @@ bool portal_read_int(const char *ns, const char *key, int64_t *out) } #endif + +int portal_double_click_ms(void) +{ + /* kdeglobals holds only values the user changed, so a KDE answer is + * deliberate; the GNOME key can be a default from another portal backend. */ + int64_t ms; + if (portal_read_int("org.kde.kdeglobals.KDE", "DoubleClickInterval", &ms) || + portal_read_int("org.gnome.desktop.peripherals.mouse", "double-click", &ms)) + return ms > 0 && ms <= INT32_MAX ? (int)ms : 0; + return 0; +} diff --git a/src/platform/freedesktop/dbus.h b/src/platform/freedesktop/dbus.h index 3b1662b..fd0aeb1 100644 --- a/src/platform/freedesktop/dbus.h +++ b/src/platform/freedesktop/dbus.h @@ -10,7 +10,10 @@ /* Send org.freedesktop.Notifications.Notify without waiting for a reply. */ bool bus_notify(const char *app, const char *title, const char *body); -/* Read an integer setting through the XDG Settings portal, waiting briefly. */ +/* Read an integer setting through the XDG Settings portal, waiting briefly. + * A string holding a decimal integer also counts, as KDE reports them. */ bool portal_read_int(const char *ns, const char *key, int64_t *out); +/* The KDE or GNOME double-click interval from the portal, or zero. */ +int portal_double_click_ms(void); #endif diff --git a/src/platform/wayland/backend.c b/src/platform/wayland/backend.c index c6272ce..ad9ed4c 100644 --- a/src/platform/wayland/backend.c +++ b/src/platform/wayland/backend.c @@ -1,4 +1,5 @@ #include "platform/wayland/internal.h" +#include "platform/freedesktop/dbus.h" static struct yasl_display *impl_display_open(struct yasl_loop *loop, const struct yasl_display_spec *spec) { @@ -31,6 +32,13 @@ static int impl_display_modifiers(const struct yasl_display *d) return wayland_display_modifiers((const struct wayland_display *)d); } +static int impl_display_double_click_ms(const struct yasl_display *d) +{ + /* Wayland has no protocol for it; desktops publish it as a setting. */ + (void)d; + return portal_double_click_ms(); +} + static void impl_display_copy(struct yasl_display *d, enum yasl_selection sel, const char *text) { wayland_display_copy((struct wayland_display *)d, sel, text); @@ -202,6 +210,7 @@ const struct yasl_backend_ops yasl_wayland_backend = { .display_dispatch_timeout = impl_display_dispatch_timeout, .display_flush = impl_display_flush, .display_modifiers = impl_display_modifiers, + .display_double_click_ms = impl_display_double_click_ms, .display_copy = impl_display_copy, .display_paste = impl_display_paste, .display_drag_start = impl_display_drag_start, diff --git a/src/platform/x11/window.c b/src/platform/x11/window.c index be1ff32..724987f 100644 --- a/src/platform/x11/window.c +++ b/src/platform/x11/window.c @@ -6,6 +6,7 @@ #include "keymap.h" #include "common/utf8.h" #include "common/timing.h" +#include "platform/freedesktop/dbus.h" #include <X11/Xatom.h> #include <X11/XKBlib.h> @@ -27,6 +28,7 @@ #include <limits.h> #include <math.h> #include <poll.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> @@ -1804,6 +1806,83 @@ static bool place(struct yasl_surface *base, struct yasl_vector2i at) return true; } +static uint32_t xsettings_card(const unsigned char *p, int size, bool msb) +{ + uint32_t v = 0; + for (int i = 0; i < size; i++) + v |= (uint32_t)p[i] << (8 * (msb ? size - 1 - i : i)); + return v; +} + +/* Find an integer in an _XSETTINGS_SETTINGS blob: a byte-order byte, three + * pad bytes, a serial, a count, then entries of type, pad, name length, name + * padded to four bytes, serial, and a typed value. */ +static bool xsettings_int(const unsigned char *p, size_t len, const char *name, int32_t *out) +{ + if (len < 12) return false; + bool msb = p[0] == MSBFirst; + uint32_t n = xsettings_card(p + 8, 4, msb); + size_t want = strlen(name); + size_t off = 12; + for (uint32_t i = 0; i < n; i++) { + if (len - off < 4) return false; + unsigned type = p[off]; + size_t name_len = xsettings_card(p + off + 2, 2, msb); + size_t padded = (name_len + 3) & ~(size_t)3; + const unsigned char *entry_name = p + off + 4; + off += 4; + if (len - off < padded + 4) return false; + off += padded + 4; + size_t value_len; + if (type == 0) { + value_len = 4; + } else if (type == 1) { + if (len - off < 4) return false; + size_t str_len = xsettings_card(p + off, 4, msb); + if (str_len > len) return false; + value_len = 4 + ((str_len + 3) & ~(size_t)3); + } else if (type == 2) { + value_len = 8; + } else { + return false; + } + if (len - off < value_len) return false; + if (type == 0 && name_len == want && memcmp(entry_name, name, want) == 0) { + *out = (int32_t)xsettings_card(p + off, 4, msb); + return true; + } + off += value_len; + } + return false; +} + +static int double_click_ms(const struct yasl_display *base) +{ + const struct x11_display *d = (const struct x11_display *)base; + if (d->native) { + char name[32]; + snprintf(name, sizeof(name), "_XSETTINGS_S%d", d->screen); + Atom selection = XInternAtom(d->native, name, True); + Atom property = XInternAtom(d->native, "_XSETTINGS_SETTINGS", True); + Window owner = selection ? XGetSelectionOwner(d->native, selection) : None; + Atom actual; + int format; + unsigned long count, left; + unsigned char *data = NULL; + int32_t ms; + if (owner && property && + XGetWindowProperty(d->native, owner, property, 0, 0x10000, False, property, + &actual, &format, &count, &left, &data) == Success && + data && actual == property && format == 8 && + xsettings_int(data, count, "Net/DoubleClickTime", &ms)) { + XFree(data); + return ms; + } + if (data) XFree(data); + } + return portal_double_click_ms(); +} + static bool bell(struct yasl_surface *base) { struct x11_surface *s = (struct x11_surface *)base; @@ -2059,6 +2138,7 @@ const struct yasl_backend_ops yasl_x11_backend = { .surface_drag_carry = drag_carry, .surface_place_under_pointer = place, .surface_bell = bell, + .display_double_click_ms = double_click_ms, .surface_start_interactive_move = interactive_move, .surface_start_interactive_resize = interactive_resize, .surface_show_window_menu = show_window_menu, |
