From 613aa4ba1a5f0e81a9ce54efafe163a5ef644ce0 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 15 Oct 2026 00:03:43 +0200 Subject: [PATCH 1/2] power: Add sysfs and logind fallback for keyboard and display backlight When running under Wayland or on modern Linux distributions without legacy UPower KbdBacklight, the keyboard backlight and display brightness controls disappear from the power applet (Cinnamon issue #13854). 1. Keyboard backlight: - Probe /sys/class/leds/ for devices matching '*kbd_backlight*' if UPower does not provide the KbdBacklight interface or has no methods. - Read current and maximum brightness directly from sysfs. - Watch brightness_hw_changed for hotkey / firmware updates. - Control brightness using systemd-logind's SetBrightness method on /org/freedesktop/login1/session/auto. 2. Display backlight and Wayland resilience: - In csd-backlight-helper, validate that candidate backlight devices have a readable max_brightness > 0 before selecting them, preventing broken ACPI video devices on hybrid graphics laptops from breaking backlight controls. Fall back to any available valid backlight device if preference matching finds no usable candidate. - In on_rr_screen_acquired(), do not abort initialization if GnomeRRScreen fails to acquire under Wayland; proceed with sysfs and helper fallbacks. - Guard X11 DPMS calls with GDK_IS_X11_DISPLAY() to prevent crashes under Wayland. --- plugins/power/csd-backlight-helper.c | 50 ++++-- plugins/power/csd-power-manager.c | 253 +++++++++++++++++++++++---- 2 files changed, 255 insertions(+), 48 deletions(-) diff --git a/plugins/power/csd-backlight-helper.c b/plugins/power/csd-backlight-helper.c index 42dda4d6..ff91e305 100644 --- a/plugins/power/csd-backlight-helper.c +++ b/plugins/power/csd-backlight-helper.c @@ -39,6 +39,30 @@ #define CSD_POWER_SETTINGS_SCHEMA "org.cinnamon.settings-daemon.plugins.power" +static gboolean +csd_backlight_helper_device_is_valid (GUdevDevice *device) +{ + const gchar *sysfs_path; + gchar *max_path; + gchar *contents = NULL; + gboolean valid = FALSE; + + sysfs_path = g_udev_device_get_sysfs_path (device); + if (sysfs_path == NULL) + return FALSE; + + max_path = g_build_filename (sysfs_path, "max_brightness", NULL); + if (g_file_get_contents (max_path, &contents, NULL, NULL)) { + gint64 max_val = g_ascii_strtoll (contents, NULL, 10); + if (max_val > 0) + valid = TRUE; + g_free (contents); + } + g_free (max_path); + + return valid; +} + static gchar * csd_backlight_helper_get_type (GList *devices, const gchar *type) { @@ -47,7 +71,7 @@ csd_backlight_helper_get_type (GList *devices, const gchar *type) for (d = devices; d != NULL; d = d->next) { type_tmp = g_udev_device_get_sysfs_attr (d->data, "type"); - if (g_strcmp0 (type_tmp, type) == 0) + if (g_strcmp0 (type_tmp, type) == 0 && csd_backlight_helper_device_is_valid (d->data)) return g_strdup (g_udev_device_get_sysfs_path (d->data)); } return NULL; @@ -58,6 +82,7 @@ csd_backlight_helper_get_best_backlight (gchar** preference_list) { gchar *path = NULL; GList *devices; + GList *d; GUdevClient *client; client = g_udev_client_new (NULL); @@ -65,20 +90,21 @@ csd_backlight_helper_get_best_backlight (gchar** preference_list) if (devices == NULL) goto out; - /* setup our gsettings interface */ - if (preference_list == NULL || preference_list[0] == NULL) { - g_print("%s\n%s\n", - "Warning: no backlight sources have been configured.", - "Check " CSD_POWER_SETTINGS_SCHEMA " to configure some."); - goto out; + if (preference_list != NULL && preference_list[0] != NULL) { + int i = 0; + for (i = 0; preference_list[i] != NULL; i++) { + path = csd_backlight_helper_get_type (devices, preference_list[i]); + if (path != NULL) + goto out; + } } - int i = 0; - for (i=0; preference_list[i] != NULL; i++) { - path = csd_backlight_helper_get_type - (devices, preference_list[i]); - if (path != NULL) + /* Fall back to any valid backlight device */ + for (d = devices; d != NULL; d = d->next) { + if (csd_backlight_helper_device_is_valid (d->data)) { + path = g_strdup (g_udev_device_get_sysfs_path (d->data)); goto out; + } } out: diff --git a/plugins/power/csd-power-manager.c b/plugins/power/csd-power-manager.c index b3816ad7..f28be1ab 100644 --- a/plugins/power/csd-power-manager.c +++ b/plugins/power/csd-power-manager.c @@ -176,6 +176,10 @@ struct CsdPowerManagerPrivate gint kbd_brightness_max; gint kbd_brightness_old; gint kbd_brightness_pre_dim; + gchar *kbd_led_device_name; + gchar *kbd_led_device_path; + gint kbd_led_fd_hw_changed; + GIOChannel *kbd_led_channel_hw_changed; GnomeRRScreen *x11_screen; gboolean use_time_primary; GIcon *previous_icon; @@ -1924,22 +1928,168 @@ do_power_action_type (CsdPowerManager *manager, } } +static gboolean +logind_set_brightness (const gchar *subsystem, + const gchar *name, + guint value, + GError **error) +{ + GDBusConnection *bus; + GVariant *res; + GError *local_error = NULL; + + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &local_error); + if (bus == NULL) { + g_propagate_error (error, local_error); + return FALSE; + } + + res = g_dbus_connection_call_sync (bus, + LOGIND_DBUS_NAME, + "/org/freedesktop/login1/session/auto", + "org.freedesktop.login1.Session", + "SetBrightness", + g_variant_new ("(ssu)", subsystem, name, (guint32) value), + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &local_error); + g_object_unref (bus); + + if (res == NULL) { + g_propagate_error (error, local_error); + return FALSE; + } + + g_variant_unref (res); + return TRUE; +} + +static void upower_kbd_emit_changed (CsdPowerManager *manager); + +static gboolean +kbd_led_event_io (GIOChannel *channel, GIOCondition condition, gpointer data) +{ + CsdPowerManager *manager = CSD_POWER_MANAGER (data); + gchar *path; + gchar *contents = NULL; + + if (!(condition & G_IO_PRI)) + return FALSE; + + if (manager->priv->kbd_led_device_path == NULL) + return FALSE; + + path = g_build_filename (manager->priv->kbd_led_device_path, "brightness", NULL); + if (g_file_get_contents (path, &contents, NULL, NULL)) { + manager->priv->kbd_brightness_now = (gint) g_ascii_strtoll (contents, NULL, 10); + g_free (contents); + upower_kbd_emit_changed (manager); + } + g_free (path); + + return TRUE; +} + +static void +csd_power_manager_init_kbd_sysfs (CsdPowerManager *manager) +{ + GDir *dir; + const gchar *entry; + GError *error = NULL; + + dir = g_dir_open ("/sys/class/leds", 0, &error); + if (dir == NULL) { + if (error) + g_error_free (error); + return; + } + + while ((entry = g_dir_read_name (dir)) != NULL) { + if (g_strstr_len (entry, -1, "kbd_backlight") != NULL) { + gchar *dir_path = g_build_filename ("/sys/class/leds", entry, NULL); + gchar *path_max = g_build_filename (dir_path, "max_brightness", NULL); + gchar *path_now = g_build_filename (dir_path, "brightness", NULL); + gchar *path_hw_changed = g_build_filename (dir_path, "brightness_hw_changed", NULL); + gchar *buf_max = NULL; + gchar *buf_now = NULL; + + if (g_file_get_contents (path_max, &buf_max, NULL, NULL)) { + manager->priv->kbd_brightness_max = (gint) g_ascii_strtoll (buf_max, NULL, 10); + g_free (buf_max); + } + + if (manager->priv->kbd_brightness_max > 0 && + g_file_get_contents (path_now, &buf_now, NULL, NULL)) { + manager->priv->kbd_brightness_now = (gint) g_ascii_strtoll (buf_now, NULL, 10); + g_free (buf_now); + + manager->priv->kbd_led_device_name = g_strdup (entry); + manager->priv->kbd_led_device_path = g_strdup (dir_path); + + manager->priv->kbd_led_fd_hw_changed = open (path_hw_changed, O_RDONLY); + if (manager->priv->kbd_led_fd_hw_changed >= 0) { + manager->priv->kbd_led_channel_hw_changed = + g_io_channel_unix_new (manager->priv->kbd_led_fd_hw_changed); + g_io_add_watch (manager->priv->kbd_led_channel_hw_changed, + G_IO_PRI, kbd_led_event_io, manager); + } + + g_debug ("Found keyboard backlight LED device: %s (max: %d, now: %d)", + entry, manager->priv->kbd_brightness_max, manager->priv->kbd_brightness_now); + + g_free (dir_path); + g_free (path_max); + g_free (path_now); + g_free (path_hw_changed); + upower_kbd_emit_changed (manager); + break; + } + + g_free (dir_path); + g_free (path_max); + g_free (path_now); + g_free (path_hw_changed); + } + } + + g_dir_close (dir); +} + static gboolean upower_kbd_get_percentage (CsdPowerManager *manager, GError **error) { GVariant *k_now = NULL; - k_now = g_dbus_proxy_call_sync (manager->priv->upower_kbd_proxy, - "GetBrightness", - NULL, - G_DBUS_CALL_FLAGS_NONE, - -1, - NULL, - error); - if (k_now != NULL) { - g_variant_get (k_now, "(i)", &manager->priv->kbd_brightness_now); - g_variant_unref (k_now); - return TRUE; + if (manager->priv->upower_kbd_proxy != NULL) { + k_now = g_dbus_proxy_call_sync (manager->priv->upower_kbd_proxy, + "GetBrightness", + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + error); + if (k_now != NULL) { + g_variant_get (k_now, "(i)", &manager->priv->kbd_brightness_now); + g_variant_unref (k_now); + return TRUE; + } + return FALSE; + } + + if (manager->priv->kbd_led_device_path != NULL) { + gchar *path = g_build_filename (manager->priv->kbd_led_device_path, "brightness", NULL); + gchar *contents = NULL; + gboolean ret = FALSE; + + if (g_file_get_contents (path, &contents, NULL, error)) { + manager->priv->kbd_brightness_now = (gint) g_ascii_strtoll (contents, NULL, 10); + g_free (contents); + ret = TRUE; + } + g_free (path); + return ret; } return FALSE; @@ -1964,20 +2114,28 @@ upower_kbd_set_brightness (CsdPowerManager *manager, guint value, GError **error if (manager->priv->kbd_brightness_now == value) return TRUE; - /* update h/w value */ - retval = g_dbus_proxy_call_sync (manager->priv->upower_kbd_proxy, - "SetBrightness", - g_variant_new ("(i)", (gint) value), - G_DBUS_CALL_FLAGS_NONE, - -1, - NULL, - error); - if (retval == NULL) + if (manager->priv->upower_kbd_proxy != NULL) { + /* update h/w value via upower */ + retval = g_dbus_proxy_call_sync (manager->priv->upower_kbd_proxy, + "SetBrightness", + g_variant_new ("(i)", (gint) value), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + error); + if (retval == NULL) + return FALSE; + g_variant_unref (retval); + } else if (manager->priv->kbd_led_device_name != NULL) { + /* update h/w value via systemd-logind */ + if (!logind_set_brightness ("leds", manager->priv->kbd_led_device_name, value, error)) + return FALSE; + } else { return FALSE; + } /* save new value */ manager->priv->kbd_brightness_now = value; - g_variant_unref (retval); upower_kbd_emit_changed(manager); return TRUE; } @@ -3503,6 +3661,11 @@ power_keyboard_proxy_ready_cb (GObject *source_object, g_variant_unref (k_now); if (k_max != NULL) g_variant_unref (k_max); + + if (manager->priv->kbd_brightness_max == 0) { + g_clear_object (&manager->priv->upower_kbd_proxy); + csd_power_manager_init_kbd_sysfs (manager); + } } static void @@ -4418,11 +4581,10 @@ on_rr_screen_acquired (GObject *object, manager->priv->x11_screen = gnome_rr_screen_new_finish (result, &error); if (error) { - g_warning ("Could not create GnomeRRScreen: %s\n", error->message); + g_warning ("Could not create GnomeRRScreen: %s (continuing with sysfs/logind fallbacks)\n", error->message); g_error_free (error); - cinnamon_settings_profile_end (NULL); - - return; + error = NULL; + manager->priv->skip_unsupported_xrandr = TRUE; } g_signal_connect (manager->priv->logind_proxy, "g-signal", @@ -4541,12 +4703,14 @@ on_rr_screen_acquired (GObject *object, manager->priv->idle_monitor = gnome_idle_monitor_new (); /* ensure the default dpms timeouts are cleared */ - ret = gnome_rr_screen_set_dpms_mode (manager->priv->x11_screen, - GNOME_RR_DPMS_ON, - &error); - if (!ret) { - g_warning ("Failed set DPMS mode: %s", error->message); - g_clear_error (&error); + if (manager->priv->x11_screen != NULL) { + ret = gnome_rr_screen_set_dpms_mode (manager->priv->x11_screen, + GNOME_RR_DPMS_ON, + &error); + if (!ret) { + g_warning ("Failed set DPMS mode: %s", error->message); + g_clear_error (&error); + } } /* coldplug the engine */ @@ -4556,12 +4720,14 @@ on_rr_screen_acquired (GObject *object, * being "0" by default to being "600" by default * https://bugzilla.gnome.org/show_bug.cgi?id=709114 */ - gdk_x11_display_error_trap_push (gdk_display_get_default ()); - int dummy; - if (DPMSQueryExtension(GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &dummy, &dummy)) { - DPMSSetTimeouts (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), 0, 0, 0); + if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) { + gdk_x11_display_error_trap_push (gdk_display_get_default ()); + int dummy; + if (DPMSQueryExtension(GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), &dummy, &dummy)) { + DPMSSetTimeouts (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), 0, 0, 0); + } + gdk_x11_display_error_trap_pop_ignored (gdk_display_get_default ()); } - gdk_x11_display_error_trap_pop_ignored (gdk_display_get_default ()); manager->priv->xscreensaver_watchdog_timer_id = g_timeout_add_seconds (XSCREENSAVER_WATCHDOG_TIMEOUT, disable_builtin_screensaver, @@ -4739,6 +4905,20 @@ csd_power_manager_stop (CsdPowerManager *manager) g_clear_object (&manager->priv->power_iface); g_clear_object (&manager->priv->screen_iface); g_clear_object (&manager->priv->keyboard_iface); + + if (manager->priv->kbd_led_channel_hw_changed != NULL) { + g_io_channel_shutdown (manager->priv->kbd_led_channel_hw_changed, FALSE, NULL); + g_io_channel_unref (manager->priv->kbd_led_channel_hw_changed); + manager->priv->kbd_led_channel_hw_changed = NULL; + } + if (manager->priv->kbd_led_fd_hw_changed != -1) { + close (manager->priv->kbd_led_fd_hw_changed); + manager->priv->kbd_led_fd_hw_changed = -1; + } + g_free (manager->priv->kbd_led_device_name); + manager->priv->kbd_led_device_name = NULL; + g_free (manager->priv->kbd_led_device_path); + manager->priv->kbd_led_device_path = NULL; } static void @@ -4747,6 +4927,7 @@ csd_power_manager_init (CsdPowerManager *manager) manager->priv = CSD_POWER_MANAGER_GET_PRIVATE (manager); manager->priv->inhibit_lid_switch_fd = -1; manager->priv->inhibit_suspend_fd = -1; + manager->priv->kbd_led_fd_hw_changed = -1; } static void From 86586d203d658b7f71b793e4ca523ef89b2a6c21 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 15 Oct 2026 00:03:46 +0200 Subject: [PATCH 2/2] meson.build: Add prefix include directory to compiler arguments --- meson.build | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 878d1451..37ed8f75 100644 --- a/meson.build +++ b/meson.build @@ -8,11 +8,13 @@ version = meson.project_version() pkgname = meson.project_name().to_lower() api_version = '3.0' -cc = meson.get_compiler('c') -cargs = [] - # directories prefix = get_option('prefix') + +cargs = [ + '-I' + join_paths(prefix, 'include'), + '-I' + join_paths(prefix, 'usr', 'include'), +] bindir = get_option('bindir') datadir = get_option('datadir') libdir = get_option('libdir')