-
-
Notifications
You must be signed in to change notification settings - Fork 83
Introduce a BrightnessManager #2916
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
leolost2605
wants to merge
8
commits into
main
Choose a base branch
from
leolost/brightness-manager
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
4921f0c
Introduce a BrightnessManager
leolost2605 ab81351
Add documentation
leolost2605 4f9d4a4
Sort primary monitor first
leolost2605 df9c08a
WIP GSD interface
leolost2605 e57e994
Update vapi metadata
leolost2605 ce25c64
Add mutter 50 guards
leolost2605 df2bd3a
Fix doc
leolost2605 650081b
Fix being trapped at zero
leolost2605 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| /* | ||
| * Copyright 2026 elementary, Inc. (https://elementary.io) | ||
| * SPDX-License-Identifier: GPL-3.0-or-later | ||
| * | ||
| * Authored by: Leonhard Kargl <leo.kargl@proton.me> | ||
| */ | ||
|
|
||
| #if HAS_MUTTER50 | ||
| [DBus (name = "io.elementary.gala.BrightnessManager")] | ||
| public class Gala.BrightnessManager : Object { | ||
| /** | ||
| * The monitors changed, you should treat everything as invalid | ||
| * and re-query the number of monitors and their brightness. | ||
| */ | ||
| public signal void monitors_changed (); | ||
|
|
||
| /** | ||
| * The brightness of the monitor with the given index changed. | ||
| * Note that this might change the global brightness so it should | ||
| * be re-queried as well. | ||
| */ | ||
| public signal void monitor_brightness_changed (int index, double brightness); | ||
|
|
||
| [DBus (visible = false)] | ||
| public Meta.Display display { private get; construct; } | ||
|
|
||
| public bool dimming_enabled { get; set; default = false; } | ||
| public double auto_brightness_target { get; set; default = -1; } | ||
|
|
||
| private Gee.ArrayList<MonitorBrightness> supported_monitors; | ||
|
|
||
| [DBus (visible = false)] | ||
| public BrightnessManager (Meta.Display display) { | ||
| Object (display: display); | ||
| } | ||
|
|
||
| construct { | ||
| supported_monitors = new Gee.ArrayList<MonitorBrightness> (); | ||
|
|
||
| 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_primary = false; | ||
| bool has_backlight = false; | ||
| foreach (var monitor in logical_monitor.get_monitors ()) { | ||
| if (monitor.is_primary ()) { | ||
| has_primary = true; | ||
| } | ||
|
|
||
| if (monitor.get_backlight () != null && monitor.is_active ()) { | ||
| has_backlight = true; | ||
| } | ||
| } | ||
|
|
||
| if (!has_backlight) { | ||
| continue; | ||
| } | ||
|
|
||
| var brightness = new MonitorBrightness (logical_monitor, supported_monitors.size); | ||
| brightness.notify["value"].connect (on_brightness_changed); | ||
|
|
||
| bind_property ("dimming-enabled", brightness, "dimming-enabled", SYNC_CREATE); | ||
| bind_property ("auto-brightness-target", brightness, "auto-brightness-target", SYNC_CREATE); | ||
|
|
||
| if (has_primary) { | ||
| supported_monitors.insert (0, brightness); | ||
| } else { | ||
| 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; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the percentage that represents the "global" brightness. | ||
| * This is currently the maximum brightness of all monitors. | ||
| * See {@link set_global_brightness} for more information. | ||
| */ | ||
| 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; | ||
| } | ||
|
|
||
| /** | ||
| * Sets a new "global" brightness. | ||
| * This will adjust the brightness of all monitors in a way that | ||
| * their relative brightness is kept. | ||
| */ | ||
| 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)); | ||
| } | ||
|
|
||
| /* Make sure we don't divide by zero */ | ||
| var max = double.max (get_global_brightness (), 0.01); | ||
|
|
||
| foreach (var monitor in supported_monitors) { | ||
| /* Make sure we don't get trapped at zero */ | ||
| var monitor_val = double.max (monitor.value, 0.01); | ||
| monitor.value = (monitor_val / max) * scale; | ||
| } | ||
| } | ||
| } | ||
| #endif | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| /* | ||
| * Copyright 2026 elementary, Inc. (https://elementary.io) | ||
| * SPDX-License-Identifier: GPL-3.0-or-later | ||
| * | ||
| * Authored by: Leonhard Kargl <leo.kargl@proton.me> | ||
| */ | ||
|
|
||
| #if HAS_MUTTER50 | ||
| /** | ||
| * Implements the interface that the gnome settings daemon | ||
| * expects from gnome shell for things like dimming on idle and | ||
| * auto brightness. | ||
| * See: [[https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/data/dbus-interfaces/org.gnome.Shell.Brightness.xml]] | ||
| */ | ||
| [DBus (name = "org.gnome.Shell.Brightness")] | ||
| public class Gala.GSDBrightnessAdapter : Object { | ||
| public signal void brightness_changed (); | ||
|
|
||
| [DBus (visible = false)] | ||
| public BrightnessManager brightness_manager { private get; construct; } | ||
|
|
||
| public bool has_brightness_control { get; private set; } | ||
|
|
||
| public GSDBrightnessAdapter (BrightnessManager brightness_manager) { | ||
| Object (brightness_manager: brightness_manager); | ||
| } | ||
|
|
||
| construct { | ||
| brightness_manager.monitors_changed.connect (on_monitors_changed); | ||
| on_monitors_changed (); | ||
|
|
||
| brightness_manager.monitor_brightness_changed.connect (on_monitor_brightness_changed); | ||
| } | ||
|
|
||
| private void on_monitors_changed () { | ||
| try { | ||
| has_brightness_control = brightness_manager.get_n_monitors () > 0; | ||
| } catch (Error e) { | ||
| warning ("Failed to query number of monitors: %s", e.message); | ||
| has_brightness_control = false; | ||
| } | ||
| } | ||
|
|
||
| private void on_monitor_brightness_changed () { | ||
| brightness_changed (); | ||
| } | ||
|
|
||
| public void set_dimming (bool enable) throws DBusError, IOError { | ||
| brightness_manager.dimming_enabled = enable; | ||
| } | ||
|
|
||
| public void set_auto_brightness_target (double target) throws DBusError, IOError { | ||
| brightness_manager.auto_brightness_target = target; | ||
| } | ||
| } | ||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| /* | ||
| * Copyright 2026 elementary, Inc. (https://elementary.io) | ||
| * SPDX-License-Identifier: GPL-3.0-or-later | ||
| * | ||
| * Authored by: Leonhard Kargl <leo.kargl@proton.me> | ||
| */ | ||
|
|
||
| #if HAS_MUTTER50 | ||
| public class Gala.MonitorBrightness : Object { | ||
| private const string DIMMING_SCHEMA_ID = "org.gnome.settings-daemon.plugins.power"; | ||
|
|
||
| // 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; } | ||
|
|
||
| /** | ||
| * 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; set; default = -1; } | ||
|
|
||
| public bool dimming_enabled { get; set; default = false; } | ||
| public double auto_brightness_target { get; set; default = -1; } | ||
|
|
||
| private static Settings? dimming_settings; | ||
|
|
||
| private Gee.ArrayList<Meta.Backlight> backlights; | ||
| private bool writing_backlights = false; | ||
|
|
||
| public MonitorBrightness (Meta.LogicalMonitor logical_monitor, int index) { | ||
| Object (logical_monitor: logical_monitor, index: index); | ||
| } | ||
|
|
||
| static construct { | ||
| if (SettingsSchemaSource.get_default ().lookup (DIMMING_SCHEMA_ID, true) != null) { | ||
| dimming_settings = new Settings (DIMMING_SCHEMA_ID); | ||
| } | ||
| } | ||
|
|
||
| construct { | ||
| backlights = new Gee.ArrayList<Meta.Backlight> (); | ||
|
|
||
| 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); | ||
| } | ||
|
|
||
| notify["value"].connect (write_to_backlights); | ||
| notify["dimming-enabled"].connect (write_to_backlights); | ||
| notify["auto-brightness-target"].connect (write_to_backlights); | ||
| } | ||
|
|
||
| private void on_brightness_changed (Object obj, ParamSpec pspec) { | ||
| if (writing_backlights) { | ||
| return; | ||
| } | ||
|
|
||
| value = get_relative_brightness ((Meta.Backlight) obj); | ||
| } | ||
|
|
||
| private void write_to_backlights () { | ||
| var real_brightness = value; | ||
| if (auto_brightness_target >= 0) { | ||
| /* If we have an auto brightness target we use the value as a bias around that | ||
| instead of directly */ | ||
| real_brightness = (auto_brightness_target + value - 0.5).clamp (0.0, 1.0); | ||
| } | ||
|
|
||
| if (dimming_enabled) { | ||
| var dimming_max = dimming_settings != null ? (double) dimming_settings.get_int ("idle-brightness") / 100.0 : 0.3; | ||
| real_brightness = double.min (real_brightness, dimming_max); | ||
| } | ||
|
|
||
| writing_backlights = true; | ||
|
|
||
| foreach (var backlight in backlights) { | ||
| set_relative_brightness (backlight, real_brightness); | ||
| } | ||
|
|
||
| writing_backlights = false; | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
| } | ||
| } | ||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.