summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-13 17:08:48 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-13 17:08:48 +0300
commitac6f9f5e7711521455b83ceb843a25e091646d43 (patch)
tree0dfd5fc46a605cd4980ef3c9131ddbaa2270c78a /tests
parentd4d13e616cb15ef22479d9cb97922af93317a01a (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 'tests')
-rw-r--r--tests/common/test_desktop.c53
-rw-r--r--tests/x11/test_input.c32
-rw-r--r--tests/x11/test_state.c55
3 files changed, 129 insertions, 11 deletions
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;