From a3bbcf30b2b94c88a3b59f86260a59cee3d6715d Mon Sep 17 00:00:00 2001 From: IoI_xD Date: Wed, 15 Apr 2026 12:25:51 -0700 Subject: [PATCH 1/4] wayland: dark theme detection --- include/Mw/LowLevel/Wayland.h | 53 ++++++++++++++++- pl/rules.pl | 2 + src/backend/wayland.c | 105 ++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 2 deletions(-) diff --git a/include/Mw/LowLevel/Wayland.h b/include/Mw/LowLevel/Wayland.h index ce1630c..9377bc8 100644 --- a/include/Mw/LowLevel/Wayland.h +++ b/include/Mw/LowLevel/Wayland.h @@ -14,6 +14,7 @@ #include #include #include +#include MWDECL int MwLLWaylandCallInit(void); @@ -35,8 +36,7 @@ typedef struct wayland_call_table { void* lib; void* xkb_lib; void* cairo_lib; -#if WAYLAND_LOAD_OPENGL -#endif + void* dbus_lib; int (*wl_display_dispatch_pending)(struct wl_display* display); void (*wl_display_disconnect)(struct wl_display* display); @@ -125,6 +125,21 @@ typedef struct wayland_call_table { void (*cairo_set_antialias)(cairo_t*, cairo_antialias_t); void (*cairo_set_operator)(cairo_t* cr, cairo_operator_t op); + void (*dbus_error_init)(DBusError* error); + DBusConnection* (*dbus_bus_get)(DBusBusType type, DBusError* error); + dbus_bool_t (*dbus_error_is_set)(const DBusError* error); + void (*dbus_error_free)(DBusError* error); + DBusMessage* (*dbus_message_new_method_call)(const char* bus_name, const char* path, const char* iface, const char* method); + void (*dbus_message_iter_init_append)(DBusMessage* message, DBusMessageIter* iter); + dbus_bool_t (*dbus_message_iter_append_basic)(DBusMessageIter* iter, int type, const void* value); + DBusMessage* (*dbus_connection_send_with_reply_and_block)(DBusConnection* connection, DBusMessage* message, int timeout_milliseconds, DBusError* error); + void (*dbus_message_unref)(DBusMessage* message); + dbus_bool_t (*dbus_message_iter_init)(DBusMessage* message, DBusMessageIter* iter); + int (*dbus_message_iter_get_arg_type)(DBusMessageIter* iter); + void (*dbus_message_iter_recurse)(DBusMessageIter* iter, DBusMessageIter* sub); + void (*dbus_connection_unref)(DBusConnection* connection); + void (*dbus_message_iter_get_basic)(DBusMessageIter* iter, void* value); + } wayland_call_table_t; extern wayland_call_table_t wl_call_tbl; @@ -225,6 +240,34 @@ MwInline int wayland_load_funcs() { CAIRO_FUNC(cairo_pattern_set_filter); #undef CAIRO_FUNC + wl_call_tbl.dbus_lib = MwDynamicOpen("libdbus-1.so"); + if(!wl_call_tbl.dbus_lib) { + fprintf(stderr, "[WARNING] Will not be able to detect dark theme: dbus library (libdbus-1.so) not found.\n"); + return 0; + } + +#define DBUS_FUNC(x) \ + wl_call_tbl.x = MwDynamicSymbol(wl_call_tbl.dbus_lib, #x); \ + if(!x) { \ + fprintf(stderr, "[WARNING] Will not be able to detect dark theme: dbus function %s (libdbus-1.so) not found.\n", #x); \ + return 0; \ + } + + DBUS_FUNC(dbus_error_init); + DBUS_FUNC(dbus_bus_get); + DBUS_FUNC(dbus_error_is_set); + DBUS_FUNC(dbus_error_free); + DBUS_FUNC(dbus_message_new_method_call); + DBUS_FUNC(dbus_message_iter_init_append); + DBUS_FUNC(dbus_message_iter_append_basic); + DBUS_FUNC(dbus_connection_send_with_reply_and_block); + DBUS_FUNC(dbus_message_unref); + DBUS_FUNC(dbus_message_iter_init); + DBUS_FUNC(dbus_message_iter_get_arg_type); + DBUS_FUNC(dbus_message_iter_recurse); + DBUS_FUNC(dbus_connection_unref); + DBUS_FUNC(dbus_message_iter_get_basic); + return 0; } @@ -436,6 +479,12 @@ struct _MwLLWayland { struct zwp_locked_pointer_v1* locked_pointer; MwBool pointer_constrained; + DBusConnection* dbus_conn; + DBusError dbus_err; + DBusMessage* dbus_msg; + DBusMessage* dbus_reply; + DBusMessageIter dbus_args, dbus_variant, dbus_inner_variant; + /* clipboard related stuff. * Note that unlike most interfaces, we don't keep zwp_primary_selection stuff in a wayland_protocol_t because we use wl_data_device as a fallback and want to have it share memory space.*/ diff --git a/pl/rules.pl b/pl/rules.pl index 3da1928..d6f72fb 100644 --- a/pl/rules.pl +++ b/pl/rules.pl @@ -49,6 +49,8 @@ if (grep(/^wayland$/, @backends)) { scan_wayland_protocol("unstable", "pointer-constraints", "-unstable-v1"); scan_wayland_protocol("unstable", "relative-pointer", "-unstable-v1"); + add_cflags("-I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include"); + $gl_libs = "-lGL -lGLU"; } diff --git a/src/backend/wayland.c b/src/backend/wayland.c index 8bb6f3e..e1e8ee4 100644 --- a/src/backend/wayland.c +++ b/src/backend/wayland.c @@ -1829,6 +1829,99 @@ static void widget_setup(MwLL r, MwLL parent, int x, int y, int width, int heigh MwLLEndDraw(r); } } + +static void dark_theme_detection_setup(MwLL handle) { + + if(!wl_call_tbl.dbus_lib) { + return; + } + + wl_call_tbl.dbus_error_init(&handle->wayland.dbus_err); + + handle->wayland.dbus_conn = wl_call_tbl.dbus_bus_get(0 /* DBUS_BUS_SESSION */, &handle->wayland.dbus_err); + if(wl_call_tbl.dbus_error_is_set(&handle->wayland.dbus_err)) { + fprintf(stderr, "[WARNING] Could not detect dark theme: Connection error: %s\n", handle->wayland.dbus_err.message); + wl_call_tbl.dbus_error_free(&handle->wayland.dbus_err); + return; + } + if(!handle->wayland.dbus_conn) { + fprintf(stderr, "[WARNING] Could not detect dark theme: Failed to connect to session bus\n"); + return; + } +} + +static void detect_dark_theme(MwLL handle) { + const char* namespace = "org.freedesktop.appearance"; + const char* key = "color-scheme"; /* 0=no preference, 1=dark, 2=light */ + MwU32 value = 0; + int translated_value = 0; + + if(!handle->wayland.dbus_conn) { + return; + } + + handle->wayland.dbus_msg = wl_call_tbl.dbus_message_new_method_call( + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Settings", + "Read"); + if(!handle->wayland.dbus_msg) { + fprintf(stderr, "[WARNING] Could not detect dark theme: Failed to create message\n"); + return; + } + + wl_call_tbl.dbus_message_iter_init_append(handle->wayland.dbus_msg, &handle->wayland.dbus_args); + wl_call_tbl.dbus_message_iter_append_basic(&handle->wayland.dbus_args, 's', &namespace); + wl_call_tbl.dbus_message_iter_append_basic(&handle->wayland.dbus_args, 's', &key); + + handle->wayland.dbus_reply = wl_call_tbl.dbus_connection_send_with_reply_and_block(handle->wayland.dbus_conn, handle->wayland.dbus_msg, 100, &handle->wayland.dbus_err); + wl_call_tbl.dbus_message_unref(handle->wayland.dbus_msg); + + if(wl_call_tbl.dbus_error_is_set(&handle->wayland.dbus_err)) { + fprintf(stderr, "[WARNING] Could not detect dark theme: Send error: %s\n", handle->wayland.dbus_err.message); + wl_call_tbl.dbus_error_free(&handle->wayland.dbus_err); + return; + } + if(!handle->wayland.dbus_reply) { + fprintf(stderr, "[WARNING] Could not detect dark theme: No reply received\n"); + return; + } + + if(!wl_call_tbl.dbus_message_iter_init(handle->wayland.dbus_reply, &handle->wayland.dbus_args)) { + fprintf(stderr, "[WARNING] Could not detect dark theme: Reply has no arguments\n"); + wl_call_tbl.dbus_message_unref(handle->wayland.dbus_reply); + return; + } + + if(wl_call_tbl.dbus_message_iter_get_arg_type(&handle->wayland.dbus_args) != 'v') { + fprintf(stderr, "[WARNING] Could not detect dark theme: Expected outer variant\n"); + wl_call_tbl.dbus_message_unref(handle->wayland.dbus_reply); + return; + } + wl_call_tbl.dbus_message_iter_recurse(&handle->wayland.dbus_args, &handle->wayland.dbus_variant); + + /* Some portals wrap the value in a second variant */ + if(wl_call_tbl.dbus_message_iter_get_arg_type(&handle->wayland.dbus_variant) == 'v') { + wl_call_tbl.dbus_message_iter_recurse(&handle->wayland.dbus_variant, &handle->wayland.dbus_inner_variant); + wl_call_tbl.dbus_message_iter_get_basic(&handle->wayland.dbus_inner_variant, &value); + } else { + wl_call_tbl.dbus_message_iter_get_basic(&handle->wayland.dbus_variant, &value); + } + + translated_value = (value == 1) ? 1 : 0; + + MwLLDispatch(handle, dark_theme, &translated_value); +} + +static void dark_theme_detection_destroy(MwLL handle) { + if(!wl_call_tbl.dbus_lib) { + return; + } + + wl_call_tbl.dbus_message_unref(handle->wayland.dbus_reply); + wl_call_tbl.dbus_connection_unref(handle->wayland.dbus_conn); +} + static MwLL MwLLCreateImpl(MwLL parent, int x, int y, int width, int height) { MwLL r; r = malloc(sizeof(*r)); @@ -1837,6 +1930,10 @@ static MwLL MwLLCreateImpl(MwLL parent, int x, int y, int width, int height) { widget_setup(r, parent, x, y, width, height, MWLL_WAYLAND_UNKNOWN); + if(!parent) { + dark_theme_detection_setup(r); + } + return r; } @@ -1856,6 +1953,10 @@ static void MwLLDestroyImpl(MwLL handle) { MwLLDestroyCommon(handle); + if(!handle->wayland.dbus_conn) { + dark_theme_detection_destroy(handle); + } + event_loop(handle); /* sleep long enough that any active attempts to wait for the mutex will have given up and we can safely unlock/destroy it */ @@ -2144,6 +2245,10 @@ static int MwLLPendingImpl(MwLL handle) { }; int pending = 0; + if(handle->wayland.dbus_conn) { + detect_dark_theme(handle); + } + if(!handle->wayland.did_initial_resize) { recursive_dispatch_resize(handle); handle->wayland.did_initial_resize = 1; From d05e31b154baea11d14f76c99deb00b682e56f8f Mon Sep 17 00:00:00 2001 From: IoI_xD Date: Wed, 15 Apr 2026 13:00:59 -0700 Subject: [PATCH 2/4] wayland: don't poll for dark theme, that doesn't work --- include/Mw/LowLevel/Wayland.h | 2 ++ src/backend/wayland.c | 49 +++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/include/Mw/LowLevel/Wayland.h b/include/Mw/LowLevel/Wayland.h index 9377bc8..d4ebdef 100644 --- a/include/Mw/LowLevel/Wayland.h +++ b/include/Mw/LowLevel/Wayland.h @@ -479,6 +479,8 @@ struct _MwLLWayland { struct zwp_locked_pointer_v1* locked_pointer; MwBool pointer_constrained; + MwBool dark_theme_detection; + time_t dbus_timer; DBusConnection* dbus_conn; DBusError dbus_err; DBusMessage* dbus_msg; diff --git a/src/backend/wayland.c b/src/backend/wayland.c index e1e8ee4..68207b8 100644 --- a/src/backend/wayland.c +++ b/src/backend/wayland.c @@ -1831,11 +1831,17 @@ static void widget_setup(MwLL r, MwLL parent, int x, int y, int width, int heigh } static void dark_theme_detection_setup(MwLL handle) { + const char* namespace = "org.freedesktop.appearance"; + const char* key = "color-scheme"; /* 0=no preference, 1=dark, 2=light */ + MwU32 value = 0; + int translated_value = 0; if(!wl_call_tbl.dbus_lib) { return; } + time(&handle->wayland.dbus_timer); + wl_call_tbl.dbus_error_init(&handle->wayland.dbus_err); handle->wayland.dbus_conn = wl_call_tbl.dbus_bus_get(0 /* DBUS_BUS_SESSION */, &handle->wayland.dbus_err); @@ -1848,17 +1854,6 @@ static void dark_theme_detection_setup(MwLL handle) { fprintf(stderr, "[WARNING] Could not detect dark theme: Failed to connect to session bus\n"); return; } -} - -static void detect_dark_theme(MwLL handle) { - const char* namespace = "org.freedesktop.appearance"; - const char* key = "color-scheme"; /* 0=no preference, 1=dark, 2=light */ - MwU32 value = 0; - int translated_value = 0; - - if(!handle->wayland.dbus_conn) { - return; - } handle->wayland.dbus_msg = wl_call_tbl.dbus_message_new_method_call( "org.freedesktop.portal.Desktop", @@ -1873,9 +1868,18 @@ static void detect_dark_theme(MwLL handle) { wl_call_tbl.dbus_message_iter_init_append(handle->wayland.dbus_msg, &handle->wayland.dbus_args); wl_call_tbl.dbus_message_iter_append_basic(&handle->wayland.dbus_args, 's', &namespace); wl_call_tbl.dbus_message_iter_append_basic(&handle->wayland.dbus_args, 's', &key); +} + +static void detect_dark_theme(MwLL handle) { + const char* namespace = "org.freedesktop.appearance"; + const char* key = "color-scheme"; /* 0=no preference, 1=dark, 2=light */ + MwU32 value = 0; + + if(!handle->wayland.dbus_conn) { + return; + } handle->wayland.dbus_reply = wl_call_tbl.dbus_connection_send_with_reply_and_block(handle->wayland.dbus_conn, handle->wayland.dbus_msg, 100, &handle->wayland.dbus_err); - wl_call_tbl.dbus_message_unref(handle->wayland.dbus_msg); if(wl_call_tbl.dbus_error_is_set(&handle->wayland.dbus_err)) { fprintf(stderr, "[WARNING] Could not detect dark theme: Send error: %s\n", handle->wayland.dbus_err.message); @@ -1908,18 +1912,16 @@ static void detect_dark_theme(MwLL handle) { wl_call_tbl.dbus_message_iter_get_basic(&handle->wayland.dbus_variant, &value); } - translated_value = (value == 1) ? 1 : 0; - - MwLLDispatch(handle, dark_theme, &translated_value); + MwLLDispatch(handle, dark_theme, &value); } static void dark_theme_detection_destroy(MwLL handle) { if(!wl_call_tbl.dbus_lib) { return; } - - wl_call_tbl.dbus_message_unref(handle->wayland.dbus_reply); - wl_call_tbl.dbus_connection_unref(handle->wayland.dbus_conn); + if(handle->wayland.dbus_msg) wl_call_tbl.dbus_message_unref(handle->wayland.dbus_msg); + if(handle->wayland.dbus_reply) wl_call_tbl.dbus_message_unref(handle->wayland.dbus_reply); + if(handle->wayland.dbus_conn) wl_call_tbl.dbus_connection_unref(handle->wayland.dbus_conn); } static MwLL MwLLCreateImpl(MwLL parent, int x, int y, int width, int height) { @@ -1932,6 +1934,7 @@ static MwLL MwLLCreateImpl(MwLL parent, int x, int y, int width, int height) { if(!parent) { dark_theme_detection_setup(r); + r->wayland.dark_theme_detection = MwTRUE; } return r; @@ -2245,8 +2248,11 @@ static int MwLLPendingImpl(MwLL handle) { }; int pending = 0; - if(handle->wayland.dbus_conn) { - detect_dark_theme(handle); + if(handle->wayland.dark_theme_detection) { + if(handle->wayland.dbus_conn) { + detect_dark_theme(handle); + handle->wayland.dark_theme_detection = MwFALSE; + } } if(!handle->wayland.did_initial_resize) { @@ -2652,6 +2658,9 @@ static void MwLLEndStateChangeImpl(MwLL handle) { widget_setup(handle, handle->wayland.parent, handle->wayland.detach_point.x, handle->wayland.detach_point.y, handle->wayland.ww, handle->wayland.wh, handle->wayland.type_to_be); + if(!handle->wayland.parent) + handle->wayland.dark_theme_detection = MwTRUE; + if(handle->common.user) { MwWidget w = handle->common.user; int i; From 64930db2fc89f2b08bc5e8d9cdcfa1ce565f0079 Mon Sep 17 00:00:00 2001 From: IoI_xD Date: Wed, 15 Apr 2026 13:03:24 -0700 Subject: [PATCH 3/4] color picker example: have button match the background --- examples/basic/colorpicker.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/basic/colorpicker.c b/examples/basic/colorpicker.c index 22e647c..8d0c0f3 100644 --- a/examples/basic/colorpicker.c +++ b/examples/basic/colorpicker.c @@ -35,12 +35,11 @@ int main() { window = MwVaCreateWidget(MwWindowClass, "main", NULL, MwDEFAULT, MwDEFAULT, 640, 480, MwNtitle, "color picker", NULL); - MwSetText(window, MwNbackground, "#000000"); + MwSetText(window, MwNbackground, MwGetInteger(window, MwNdarkTheme) ? MwDefaultBackground : MwDefaultDarkBackground); button = MwVaCreateWidget(MwButtonClass, "button", window, 160, 180, 320, 120, MwNtext, "change window background", NULL); - MwSetText(button, MwNbackground, MwGetInteger(button, MwNdarkTheme) ? MwDefaultDarkBackground : MwDefaultBackground); MwAddUserHandler(button, MwNactivateHandler, color_picker, NULL); From a0c864d85b2bf782e78ef15068b0a293910fc017 Mon Sep 17 00:00:00 2001 From: IoI_xD Date: Wed, 15 Apr 2026 13:58:50 -0700 Subject: [PATCH 4/4] fix wayland crash by having MwLLDestroy flush before actually destroying handle --- src/backend/wayland.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/backend/wayland.c b/src/backend/wayland.c index 68207b8..4350336 100644 --- a/src/backend/wayland.c +++ b/src/backend/wayland.c @@ -1949,6 +1949,11 @@ static void MwLLDestroyImpl(MwLL handle) { }; int select_ret; + event_loop(handle); + // wl_display_cancel_read(handle->wayland.display); + + wl_flush(handle); + if(pthread_mutex_timedlock(&handle->wayland.eventsMutex, &t) != 0) { printf("WARNING: Couldn't call MwLLDestroyImpl due to deadlock.\n"); return; @@ -1956,12 +1961,6 @@ static void MwLLDestroyImpl(MwLL handle) { MwLLDestroyCommon(handle); - if(!handle->wayland.dbus_conn) { - dark_theme_detection_destroy(handle); - } - - event_loop(handle); - /* sleep long enough that any active attempts to wait for the mutex will have given up and we can safely unlock/destroy it */ tv.tv_sec = 0; tv.tv_usec = 1000; @@ -1972,7 +1971,10 @@ static void MwLLDestroyImpl(MwLL handle) { pthread_mutex_unlock(&handle->wayland.eventsMutex); pthread_mutex_destroy(&handle->wayland.eventsMutex); - // framebuffer_destroy(&handle->wayland); + if(!handle->wayland.dbus_conn) { + dark_theme_detection_destroy(handle); + } + buffer_destroy(&handle->wayland.cursor); wl_region_destroy(handle->wayland.region); @@ -2005,7 +2007,12 @@ static void MwLLDestroyImpl(MwLL handle) { shfree(handle->wayland.wl_protocol_map); shfree(handle->wayland.wl_protocol_setup_map); - free(handle); + wl_keyboard_destroy(handle->wayland.keyboard); + wl_pointer_destroy(handle->wayland.pointer); + + wl_flush(handle); + + // free(handle); if(currentlyHeldWidget == handle) { currentlyHeldWidget = NULL; @@ -2252,6 +2259,7 @@ static int MwLLPendingImpl(MwLL handle) { if(handle->wayland.dbus_conn) { detect_dark_theme(handle); handle->wayland.dark_theme_detection = MwFALSE; + MwLLDispatch(handle, draw, NULL); } }