Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/DBus.vala
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,34 @@ public class Gala.DBus {
on_name_lost
);

#if HAS_MUTTER50
var brightness_manager = new BrightnessManager (wm.get_display ());

Bus.own_name (
SESSION, "io.elementary.gala.BrightnessManager", NONE, null,
(connection, name) => {
try {
connection.register_object ("/io/elementary/gala/BrightnessManager", brightness_manager);
} catch (Error e) {
warning (e.message);
}
},
on_name_lost
);

Bus.own_name (
SESSION, "org.gnome.Shell.Brightness", NONE, null,
(connection, name) => {
try {
connection.register_object ("/org/gnome/Shell/Brightness", new GSDBrightnessAdapter (brightness_manager));
} catch (Error e) {
warning (e.message);
}
},
on_name_lost
);
#endif

Bus.own_name (
SESSION, "org.pantheon.gala", NONE, null,
(connection, name) => {
Expand Down
159 changes: 159 additions & 0 deletions src/Misc/Brightness/BrightnessManager.vala
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;
Comment thread
leolost2605 marked this conversation as resolved.
}
}
}
#endif
56 changes: 56 additions & 0 deletions src/Misc/Brightness/GSDBrightnessAdapter.vala
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
129 changes: 129 additions & 0 deletions src/Misc/Brightness/MonitorBrightness.vala
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
3 changes: 3 additions & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ gala_bin_sources = files(
'Misc/WindowTracker.vala',
'Misc/WorkspaceManager.vala',
'Misc/Zoom.vala',
'Misc/Brightness/BrightnessManager.vala',
'Misc/Brightness/GSDBrightnessAdapter.vala',
'Misc/Brightness/MonitorBrightness.vala',
'Misc/OSK/OSKManager.vala',
'Misc/OSK/OSKProxy.vala',
'Misc/OSK/OSKReceiver.vala',
Expand Down
Loading