summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/guide.md6
-rw-r--r--include/yasl/yasl.h14
-rw-r--r--src/common/backend.c7
-rw-r--r--src/common/backend.h2
-rw-r--r--src/platform/cocoa/window.m7
-rw-r--r--src/platform/freedesktop/dbus.c21
-rw-r--r--src/platform/freedesktop/dbus.h5
-rw-r--r--src/platform/wayland/backend.c9
-rw-r--r--src/platform/x11/window.c80
-rw-r--r--tests/common/test_desktop.c53
-rw-r--r--tests/x11/test_input.c32
-rw-r--r--tests/x11/test_state.c55
12 files changed, 278 insertions, 13 deletions
diff --git a/docs/guide.md b/docs/guide.md
index 7f02ab7..2926011 100644
--- a/docs/guide.md
+++ b/docs/guide.md
@@ -222,7 +222,11 @@ with `device` distinguishing mouse, touch, pen, and eraser. `POINTER_DOWN` and
or touch contact. Other types leave `button` meaningless. Every pointer event
also carries `buttons`, a `YASL_BUTTON_BIT()` mask of the buttons held after
that event, tracked from the queue's down and up events. Use it to tell a drag
-from a hover. `YASL_EVENT_INPUT_RESET` clears the mask. On Wayland,
+from a hover. Count clicks against `yasl_display_double_click_ms()`, the
+system interval: XSETTINGS on X11, the KDE or GNOME setting through the
+Settings portal on Wayland, and the system value on Cocoa. It falls back to
+`YASL_DOUBLE_CLICK_MS_DEFAULT` (400 ms). The query can wait briefly on Linux, so
+read it when a window gains focus. `YASL_EVENT_INPUT_RESET` clears the mask. On Wayland,
`POINTER_LEAVE` also clears it, because the compositor sends leave during a
press only when it takes the pointer away. X11 and Cocoa keep the press across
leave and still deliver the release. `POINTER_MOTION` reports `delta`, the device-pixel movement since
diff --git a/include/yasl/yasl.h b/include/yasl/yasl.h
index 973c39d..7c9bfbd 100644
--- a/include/yasl/yasl.h
+++ b/include/yasl/yasl.h
@@ -568,6 +568,20 @@ YASL_API void yasl_display_flush(struct yasl_display *d);
* For an individual event use its own mods field instead.
*/
YASL_API int yasl_display_modifiers(const struct yasl_display *d);
+/** Double-click interval used when the system reports none. */
+#define YASL_DOUBLE_CLICK_MS_DEFAULT 400
+/**
+ * Read the system's double-click interval.
+ * @param d Display, or NULL.
+ * @return Maximum milliseconds between the clicks of a double click, or
+ * YASL_DOUBLE_CLICK_MS_DEFAULT when d is NULL or the system reports none.
+ * X11 reads the XSETTINGS Net/DoubleClickTime value. Wayland, and X11 without
+ * XSETTINGS, read the KDE or GNOME setting through the XDG Settings portal,
+ * which needs D-Bus and can wait briefly. Cocoa reads the system interval.
+ * The user can change it at any time; read it again when a window gains focus
+ * rather than on every click.
+ */
+YASL_API int yasl_display_double_click_ms(const struct yasl_display *d);
/* Unsupported/invalid selections are no-ops, never clipboard aliases. */
/**
* Offer a copy of a NUL-terminated UTF-8 string as a selection.
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,
diff --git a/tests/common/test_desktop.c b/tests/common/test_desktop.c
index f65a9cd..89760fe 100644
--- a/tests/common/test_desktop.c
+++ b/tests/common/test_desktop.c
@@ -1,5 +1,6 @@
#define _GNU_SOURCE
#include "common/backend.h"
+#include "platform/freedesktop/dbus.h"
#include "yasl/desktop.h"
#include <assert.h>
@@ -142,6 +143,8 @@ static void test_open_uri(const char *dir)
static void test_appearance_fallback(void)
{
+ assert(yasl_display_double_click_ms(NULL) == YASL_DOUBLE_CLICK_MS_DEFAULT);
+ assert(portal_double_click_ms() == 0);
struct yasl_display display = {0};
assert(yasl_display_appearance(NULL) == 0);
unsetenv("GTK_THEME");
@@ -160,6 +163,8 @@ static void test_appearance_fallback(void)
}
#if YASL_HAS_DBUS
+static dbus_int32_t fake_slot = -1;
+
/* A fake notification server and Settings portal on a private bus. */
struct fake_bus {
DBusConnection *conn;
@@ -167,6 +172,7 @@ struct fake_bus {
char app[64], title[64], body[64], entry[64];
int notifications;
bool read_one;
+ bool kde;
_Atomic bool stop;
};
@@ -175,14 +181,28 @@ static void reply_setting(DBusConnection *c, DBusMessage *msg, bool nested)
const char *ns = NULL, *key = NULL;
dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &ns, DBUS_TYPE_STRING, &key,
DBUS_TYPE_INVALID);
- dbus_uint32_t value;
- if (ns && key && strcmp(ns, "org.freedesktop.appearance") == 0 &&
- strcmp(key, "color-scheme") == 0) {
- value = 1;
- } else if (ns && key && strcmp(ns, "org.freedesktop.appearance") == 0 &&
- strcmp(key, "contrast") == 0) {
- value = 1;
+ struct fake_bus *fb = dbus_connection_get_data(c, fake_slot);
+ dbus_uint32_t value = 1;
+ dbus_int32_t gnome_ms = 333;
+ const char *kde_ms = "250";
+ int type = DBUS_TYPE_UINT32;
+ const void *data = &value;
+ if (!ns || !key) {
+ type = DBUS_TYPE_INVALID;
+ } else if (strcmp(ns, "org.freedesktop.appearance") == 0 &&
+ (strcmp(key, "color-scheme") == 0 || strcmp(key, "contrast") == 0)) {
+ } else if (strcmp(ns, "org.gnome.desktop.peripherals.mouse") == 0 &&
+ strcmp(key, "double-click") == 0) {
+ type = DBUS_TYPE_INT32;
+ data = &gnome_ms;
+ } else if (fb->kde && strcmp(ns, "org.kde.kdeglobals.KDE") == 0 &&
+ strcmp(key, "DoubleClickInterval") == 0) {
+ type = DBUS_TYPE_STRING;
+ data = &kde_ms;
} else {
+ type = DBUS_TYPE_INVALID;
+ }
+ if (type == DBUS_TYPE_INVALID) {
DBusMessage *err = dbus_message_new_error(msg,
"org.freedesktop.portal.Error.NotFound", "Requested setting not found");
dbus_connection_send(c, err, NULL);
@@ -191,16 +211,17 @@ static void reply_setting(DBusConnection *c, DBusMessage *msg, bool nested)
}
DBusMessage *reply = dbus_message_new_method_return(msg);
DBusMessageIter it, outer, inner;
+ const char signature[2] = {(char)type, '\0'};
dbus_message_iter_init_append(reply, &it);
if (nested) {
dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, "v", &outer);
- dbus_message_iter_open_container(&outer, DBUS_TYPE_VARIANT, "u", &inner);
- dbus_message_iter_append_basic(&inner, DBUS_TYPE_UINT32, &value);
+ dbus_message_iter_open_container(&outer, DBUS_TYPE_VARIANT, signature, &inner);
+ dbus_message_iter_append_basic(&inner, type, data);
dbus_message_iter_close_container(&outer, &inner);
dbus_message_iter_close_container(&it, &outer);
} else {
- dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, "u", &outer);
- dbus_message_iter_append_basic(&outer, DBUS_TYPE_UINT32, &value);
+ dbus_message_iter_open_container(&it, DBUS_TYPE_VARIANT, signature, &outer);
+ dbus_message_iter_append_basic(&outer, type, data);
dbus_message_iter_close_container(&it, &outer);
}
dbus_connection_send(c, reply, NULL);
@@ -280,6 +301,8 @@ static void test_bus(const char *address)
pthread_mutex_init(&fb.lock, NULL);
fb.conn = dbus_connection_open_private(address, &err);
assert(fb.conn && dbus_bus_register(fb.conn, &err));
+ assert(dbus_connection_allocate_data_slot(&fake_slot));
+ assert(dbus_connection_set_data(fb.conn, fake_slot, &fb, NULL));
assert(dbus_bus_request_name(fb.conn, "org.freedesktop.Notifications", 0, &err) ==
DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
assert(dbus_bus_request_name(fb.conn, "org.freedesktop.portal.Desktop", 0, &err) ==
@@ -311,10 +334,18 @@ static void test_bus(const char *address)
assert(yasl_display_appearance(&display) == want);
unsetenv("GTK_THEME");
+ /* A KDE value, sent as a string, wins over the GNOME key. */
+ assert(portal_double_click_ms() == 333);
+ fb.kde = true;
+ assert(portal_double_click_ms() == 250);
+ fb.read_one = true;
+ assert(portal_double_click_ms() == 250);
+
fb.stop = true;
pthread_join(thread, NULL);
dbus_connection_close(fb.conn);
dbus_connection_unref(fb.conn);
+ dbus_connection_free_data_slot(&fake_slot);
pthread_mutex_destroy(&fb.lock);
}
#endif
diff --git a/tests/x11/test_input.c b/tests/x11/test_input.c
index bbcbf45..95943d1 100644
--- a/tests/x11/test_input.c
+++ b/tests/x11/test_input.c
@@ -65,6 +65,37 @@ static void check_selection_encoding(struct x11_display *d, struct x11_surface *
}
}
+/* Own the XSETTINGS selection with one Net/DoubleClickTime value. */
+static void check_double_click(struct x11_display *d)
+{
+ /* No settings owner and no portal: the default applies. */
+ unsetenv("DBUS_SESSION_BUS_ADDRESS");
+ unsetenv("XDG_RUNTIME_DIR");
+ assert(yasl_display_double_click_ms(&d->base) == YASL_DOUBLE_CLICK_MS_DEFAULT);
+
+ char name[32];
+ snprintf(name, sizeof(name), "_XSETTINGS_S%d", d->screen);
+ Atom selection = XInternAtom(d->native, name, False);
+ Atom property = XInternAtom(d->native, "_XSETTINGS_SETTINGS", False);
+ Window owner = XCreateSimpleWindow(d->native, DefaultRootWindow(d->native),
+ 0, 0, 1, 1, 0, 0, 0);
+ XSetSelectionOwner(d->native, selection, owner, CurrentTime);
+ static const unsigned char blob[] = {
+ LSBFirst, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 19, 0, 'N', 'e', 't', '/', 'D', 'o', 'u', 'b', 'l', 'e',
+ 'C', 'l', 'i', 'c', 'k', 'T', 'i', 'm', 'e', 0, 0, 0, 0, 0,
+ 250, 0, 0, 0,
+ };
+ XChangeProperty(d->native, owner, property, property, 8, PropModeReplace,
+ blob, sizeof(blob));
+ XSync(d->native, False);
+ assert(yasl_display_double_click_ms(&d->base) == 250);
+
+ XDestroyWindow(d->native, owner);
+ XSync(d->native, False);
+ assert(yasl_display_double_click_ms(&d->base) == YASL_DOUBLE_CLICK_MS_DEFAULT);
+}
+
int main(void)
{
struct yasl_display_spec ds = {.backend = YASL_BACKEND_X11};
@@ -117,6 +148,7 @@ int main(void)
XFree(saved);
s->ic = saved_ic;
check_selection_encoding(d, s);
+ check_double_click(d);
/* Test CSD interactive actions, window menu, and IME caret */
assert(yasl_display_caps(base) & YASL_CAP_CSD);
diff --git a/tests/x11/test_state.c b/tests/x11/test_state.c
index abdda95..abc0851 100644
--- a/tests/x11/test_state.c
+++ b/tests/x11/test_state.c
@@ -36,8 +36,63 @@ static void state_changed(struct yasl_surface *surface, uint32_t state, void *ud
notifications++;
}
+/* Build an XSETTINGS blob in either byte order. */
+struct blob {
+ unsigned char bytes[256];
+ size_t len;
+ bool msb;
+};
+
+static void put(struct blob *b, uint32_t v, int size)
+{
+ for (int i = 0; i < size; i++)
+ b->bytes[b->len++] = (unsigned char)(v >> (8 * (b->msb ? size - 1 - i : i)));
+}
+
+static void put_name(struct blob *b, unsigned type, const char *name)
+{
+ size_t n = strlen(name);
+ b->bytes[b->len++] = (unsigned char)type;
+ b->bytes[b->len++] = 0;
+ put(b, (uint32_t)n, 2);
+ memcpy(b->bytes + b->len, name, n);
+ b->len += (n + 3) & ~(size_t)3;
+ put(b, 7, 4); /* Last-change serial. */
+}
+
+static void test_xsettings(void)
+{
+ for (int order = 0; order < 2; order++) {
+ struct blob b = {.msb = order == 1};
+ b.bytes[0] = b.msb ? MSBFirst : LSBFirst;
+ b.len = 4;
+ put(&b, 1, 4);
+ put(&b, 4, 4);
+ put_name(&b, 1, "Net/ThemeName");
+ put(&b, 5, 4);
+ memcpy(b.bytes + b.len, "Adwai", 5);
+ b.len += 8;
+ put_name(&b, 2, "Net/Color");
+ put(&b, 0x1111, 2); put(&b, 0x2222, 2); put(&b, 0x3333, 2); put(&b, 0xffff, 2);
+ put_name(&b, 0, "Net/DoubleClickTimeX");
+ put(&b, 999, 4);
+ put_name(&b, 0, "Net/DoubleClickTime");
+ put(&b, 250, 4);
+
+ int32_t ms = 0;
+ assert(xsettings_int(b.bytes, b.len, "Net/DoubleClickTime", &ms) && ms == 250);
+ assert(!xsettings_int(b.bytes, b.len, "Net/DoubleClick", &ms));
+ /* A string with that name is not an integer setting. */
+ assert(!xsettings_int(b.bytes, b.len, "Net/ThemeName", &ms));
+ /* The value is last, so any truncation fails cleanly. */
+ for (size_t len = 0; len < b.len; len++)
+ assert(!xsettings_int(b.bytes, len, "Net/DoubleClickTime", &ms));
+ }
+}
+
int main(void)
{
+ test_xsettings();
struct x11_display display = {.base.ops = &yasl_x11_backend,
.net_state = 10, .fullscreen = 20, .maximized_h = 21, .maximized_v = 22};
display.native = (Display *)&display;