diff --git a/src/DBus.vala b/src/DBus.vala index 5541df51c..70aac6bfb 100644 --- a/src/DBus.vala +++ b/src/DBus.vala @@ -29,6 +29,18 @@ public class Gala.DBus { on_name_lost ); + Bus.own_name ( + SESSION, "io.elementary.gala.BrightnessManager", NONE, null, + (connection, name) => { + try { + connection.register_object ("/io/elementary/gala/BrightnessManager", new BrightnessManager (wm.get_display ())); + } catch (Error e) { + warning (e.message); + } + }, + on_name_lost + ); + Bus.own_name ( SESSION, "org.pantheon.gala", NONE, null, (connection, name) => { diff --git a/src/Misc/Brightness/BrightnessManager.vala b/src/Misc/Brightness/BrightnessManager.vala new file mode 100644 index 000000000..befe9f63e --- /dev/null +++ b/src/Misc/Brightness/BrightnessManager.vala @@ -0,0 +1,120 @@ +/* + * Copyright 2026 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +[DBus (name = "io.elementary.gala.BrightnessManager")] +public class Gala.BrightnessManager : Object { + public signal void monitors_changed (); + public signal void monitor_brightness_changed (int index, double brightness); + + [DBus (visible = false)] + public Meta.Display display { private get; construct; } + + private Gee.ArrayList supported_monitors; + + [DBus (visible = false)] + public BrightnessManager (Meta.Display display) { + Object (display: display); + } + + construct { + supported_monitors = new Gee.ArrayList (); + + var monitor_manager = display.get_context ().get_backend ().get_monitor_manager (); + monitor_manager.monitors_changed.connect (update_monitors); + update_monitors (); + } + + private void update_monitors () { + supported_monitors.clear (); + + var monitor_manager = display.get_context ().get_backend ().get_monitor_manager (); + unowned var logical_monitors = monitor_manager.get_logical_monitors (); + + foreach (var logical_monitor in logical_monitors) { + bool has_backlight = false; + foreach (var monitor in logical_monitor.get_monitors ()) { + if (monitor.get_backlight () != null && monitor.is_active ()) { + has_backlight = true; + break; + } + } + + if (!has_backlight) { + continue; + } + + var brightness = new MonitorBrightness (logical_monitor, supported_monitors.size); + brightness.notify["value"].connect (on_brightness_changed); + + supported_monitors.add (brightness); + } + + monitors_changed (); + } + + private void on_brightness_changed (Object obj, ParamSpec pspec) { + var brightness = (MonitorBrightness) obj; + monitor_brightness_changed (brightness.index, brightness.value); + } + + public int get_n_monitors () throws IOError, DBusError { + return supported_monitors.size; + } + + public string get_monitor_name (int index) throws IOError, DBusError { + if (index < 0 || index >= supported_monitors.size) { + throw new IOError.INVALID_ARGUMENT ("Invalid monitor index: %d".printf (index)); + } + + return supported_monitors[index].name; + } + + public double get_monitor_brightness (int index) throws IOError, DBusError { + if (index < 0 || index >= supported_monitors.size) { + throw new IOError.INVALID_ARGUMENT ("Invalid monitor index: %d".printf (index)); + } + + return supported_monitors[index].value; + } + + public void set_monitor_brightness (int index, double brightness) throws IOError, DBusError { + if (index < 0 || index >= supported_monitors.size) { + throw new IOError.INVALID_ARGUMENT ("Invalid monitor index: %d".printf (index)); + } + + if (brightness < 0.0 || brightness > 1.0) { + throw new IOError.INVALID_ARGUMENT ("Invalid brightness value: %f".printf (brightness)); + } + + supported_monitors[index].value = brightness; + } + + public double get_global_brightness () throws IOError, DBusError { + if (supported_monitors.size == 0) { + throw new IOError.INVALID_ARGUMENT ("No supported monitors found."); + } + + var max = supported_monitors.max (MonitorBrightness.brightness_compare_func); + return max.value; + } + + public void set_global_brightness (double scale) throws IOError, DBusError { + if (supported_monitors.size == 0) { + throw new IOError.INVALID_ARGUMENT ("No supported monitors found."); + } + + if (scale < 0.0 || scale > 1.0) { + throw new IOError.INVALID_ARGUMENT ("Invalid scale value: %f".printf (scale)); + } + + var max = get_global_brightness (); + + foreach (var monitor in supported_monitors) { + monitor.value = (monitor.value / max) * scale; + } + } +} diff --git a/src/Misc/Brightness/MonitorBrightness.vala b/src/Misc/Brightness/MonitorBrightness.vala new file mode 100644 index 000000000..f439cd9cd --- /dev/null +++ b/src/Misc/Brightness/MonitorBrightness.vala @@ -0,0 +1,102 @@ +/* + * Copyright 2026 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class Gala.MonitorBrightness : Object { + // This is really hacky. This causes vala to first include meta/display.h which includes + // the file with the necessary macros that are used in meta/meta-logical-monitor.h and meta/meta-monitor.h + // but not included there. I will upstream fixes for this but for now make it compile + public Meta.Display display; + + public Meta.LogicalMonitor logical_monitor { private get; construct; } + public int index { get; construct; } + + public string name { get; private set; } + + private double _value = -1; + /** + * The brightness of all physical monitors in percent (0.0 - 1.0). This might + * sometimes not actually match the monitors actual brightness, e.g. when it gets dimmed + * because the system is idle, or when an auto target is enabled. + */ + public double value { + get { return _value; } + set { + _value = value; + + writing_backlights = true; + + foreach (var backlight in backlights) { + set_relative_brightness (backlight, value); + } + + writing_backlights = false; + } + } + + private Gee.ArrayList backlights; + private bool writing_backlights = false; + + public MonitorBrightness (Meta.LogicalMonitor logical_monitor, int index) { + Object (logical_monitor: logical_monitor, index: index); + } + + construct { + backlights = new Gee.ArrayList (); + + unowned var monitors = logical_monitor.get_monitors (); + + name = monitors.first ().data.get_display_name (); + + foreach (var monitor in monitors) { + var backlight = monitor.get_backlight (); + if (backlight == null || !monitor.is_active ()) { + continue; + } + + if (_value == -1) { + _value = get_relative_brightness (backlight); + } + + set_relative_brightness (backlight, value); + backlights.add (backlight); + backlight.notify["brightness"].connect (on_brightness_changed); + } + } + + private void on_brightness_changed (Object obj, ParamSpec pspec) { + if (writing_backlights) { + return; + } + + value = get_relative_brightness ((Meta.Backlight) obj); + } + + private static double get_relative_brightness (Meta.Backlight backlight) { + var current = backlight.brightness; + var min = backlight.brightness_min; + var max = backlight.brightness_max; + + return (double) (current - min) / (max - min); + } + + private static void set_relative_brightness (Meta.Backlight backlight, double value) { + var min = backlight.brightness_min; + var max = backlight.brightness_max; + + backlight.brightness = (int) (min + (value * (max - min))); + } + + public static int brightness_compare_func (MonitorBrightness a, MonitorBrightness b) { + if (a.value < b.value) { + return -1; + } else if (a.value > b.value) { + return 1; + } else { + return 0; + } + } +} diff --git a/src/meson.build b/src/meson.build index 6b941f249..5248c477a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -46,6 +46,8 @@ gala_bin_sources = files( 'Misc/WindowTracker.vala', 'Misc/WorkspaceManager.vala', 'Misc/Zoom.vala', + 'Misc/Brightness/BrightnessManager.vala', + 'Misc/Brightness/MonitorBrightness.vala', 'Misc/OSK/OSKManager.vala', 'Misc/OSK/OSKProxy.vala', 'Misc/OSK/OSKReceiver.vala', diff --git a/vapi/libmutter.vapi b/vapi/libmutter.vapi index 9b6a81576..ff133b5fa 100644 --- a/vapi/libmutter.vapi +++ b/vapi/libmutter.vapi @@ -276,7 +276,7 @@ namespace Meta { public void purge (GLib.File file); } #if HAS_MUTTER49 - [CCode (cheader_filename = "meta/main.h", type_id = "meta_backlight_get_type ()")] + [CCode (cheader_filename = "meta/meta-backlight.h", type_id = "meta_backlight_get_type ()")] public abstract class Backlight : GLib.Object { [CCode (has_construct_function = false)] protected Backlight (); @@ -647,14 +647,14 @@ namespace Meta { public Meta.Workspace workspace { owned get; set; } } #if HAS_MUTTER49 - [CCode (cheader_filename = "meta/main.h", type_id = "meta_logical_monitor_get_type ()")] + [CCode (cheader_filename = "meta/meta-logical-monitor.h", type_id = "meta_logical_monitor_get_type ()")] public class LogicalMonitor : GLib.Object { [CCode (has_construct_function = false)] protected LogicalMonitor (); public unowned GLib.List get_monitors (); public int get_number (); } - [CCode (cheader_filename = "meta/main.h", type_id = "meta_monitor_get_type ()")] + [CCode (cheader_filename = "meta/meta-monitor.h", type_id = "meta_monitor_get_type ()")] public class Monitor : GLib.Object { [CCode (has_construct_function = false)] protected Monitor ();