diff --git a/README.md b/README.md index 9f895a5..2bfa44b 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ These widgets can be customized, added, removed and even reordered - Menubar with dropdown and buttons - Button grid - Volume slider using PulseAudio +- Backlight slider ## Planned Features diff --git a/man/swaync.5.scd b/man/swaync.5.scd index 42fc6c2..369136e 100644 --- a/man/swaync.5.scd +++ b/man/swaync.5.scd @@ -206,6 +206,7 @@ config file to be able to detect config errors multiple of same widget: ++ Append a # with any value to the end of the widget name. ++ Example: "title#TheMainTitle" ++ + To address this widget specifically in the css file use the css class .TheMainTitle ++ example: ``` { @@ -223,6 +224,7 @@ config file to be able to detect config errors multiple of same widget: ++ Append a # with any value to the end of the widget name. ++ Example: "title#TheMainTitle" ++ + To address this widget specifically in the css file use the css class .TheMainTitle ++ Widgets to customize: ++ *title*++ type: object ++ @@ -379,7 +381,33 @@ config file to be able to detect config errors optional: true ++ default: "Volume" ++ description: Text displayed in front of the volume slider ++ - description: Slider to control pulse volume ++ + description: Slider to control pulse volume ++ + *backlight*++ + type: object ++ + css class: widget-backlight ++ + properties: ++ + label: ++ + type: string ++ + optional: true ++ + default: "Brightness" ++ + description: Text displayed in front of the backlight slider ++ + device: ++ + type: string ++ + optional: true ++ + default: "intel_backlight" ++ + description: Device in `/sys/class/backlight` or `/sys/class/leds` ++ + subsystem: ++ + type: string ++ + optional: true ++ + default: "backlight" ++ + description: Kernel subsystem for brightness control ++ + enum: ["backlight", "leds"] ++ + min: ++ + type: integer ++ + optional: true ++ + default: 0 ++ + description: Lowest possible value for brightness ++ + description: Slider to control screen brightness ++ example: ``` diff --git a/src/configSchema.json b/src/configSchema.json index 30e11d2..3344314 100644 --- a/src/configSchema.json +++ b/src/configSchema.json @@ -295,6 +295,9 @@ }, "^volume(#[a-zA-Z0-9_-]{1,}){0,1}?$": { "$ref": "#/widgets/volume" + }, + "^backlight(#[a-zA-Z0-9_-]{1,}){0,1}?$": { + "$ref": "#/widgets/backlight" } } } @@ -449,7 +452,35 @@ "description": "Text displayed in front of the volume slider", "default": "Volume" } - } + } + }, + "backlight": { + "type": "object", + "description": "Slider to control monitor brightness", + "additionalProperties": false, + "properties": { + "label": { + "type": "string", + "description": "Text displayed in front of the backlight slider", + "default": "Brightness" + }, + "device": { + "type": "string", + "description": "Name of monitor (find possible devices using `ls /sys/class/backlight` or `ls /sys/class/leds`)", + "default": "intel_backlight" + }, + "subsystem": { + "type": "string", + "description": "Kernel subsystem for brightness control", + "default": "backlight", + "enum": ["backlight", "leds"] + }, + "min": { + "type": "integer", + "default": 0, + "description": "Lowest possible value for brightness" + } + } } } } diff --git a/src/controlCenter/widgets/backlight/backlight.vala b/src/controlCenter/widgets/backlight/backlight.vala new file mode 100644 index 0000000..e25109e --- /dev/null +++ b/src/controlCenter/widgets/backlight/backlight.vala @@ -0,0 +1,73 @@ +using GLib; + +namespace SwayNotificationCenter.Widgets { + public class Backlight : BaseWidget { + public override string widget_name { + get { + return "backlight"; + } + } + + BacklightUtil client; + + Gtk.Label label_widget = new Gtk.Label (null); + Gtk.Scale slider = new Gtk.Scale.with_range (Gtk.Orientation.HORIZONTAL, 0, 100, 1); + + public Backlight (string suffix, SwayncDaemon swaync_daemon, NotiDaemon noti_daemon) { + base (suffix, swaync_daemon, noti_daemon); + + Json.Object ? config = get_config (this); + if (config != null) { + string ? label = get_prop (config, "label"); + label_widget.set_label (label ?? "Brightness"); + string device = (get_prop (config, "device") ?? "intel_backlight"); + string subsystem = (get_prop (config, "subsystem") ?? "backlight"); + int min = int.max (0, get_prop (config, "min")); + + switch (subsystem) { + default: + case "backlight": + if (subsystem != "backlight") + info ("Invalid subsystem %s for device %s. " + + "Use 'backlight' or 'leds'. Using default: 'backlight'", + subsystem, device); + client = new BacklightUtil ("backlight", device); + slider.set_range (min, 100); + break; + case "leds": + client = new BacklightUtil ("leds", device); + slider.set_range (min, this.client.get_max_value ()); + break; + } + } + + this.client.brightness_change.connect ((percent) => { + if (percent < 0) { // invalid device path + hide (); + } else { + slider.set_value (percent); + } + }); + + slider.set_draw_value (false); + slider.set_round_digits (0); + slider.value_changed.connect (() => { + this.client.set_brightness ((float) slider.get_value ()); + slider.tooltip_text = ((int) slider.get_value ()).to_string (); + }); + + add (label_widget); + pack_start (slider, true, true, 0); + + show_all (); + } + + public override void on_cc_visibility_change (bool val) { + if (val) { + this.client.start (); + } else { + this.client.close (); + } + } + } +} diff --git a/src/controlCenter/widgets/backlight/backlightUtil.vala b/src/controlCenter/widgets/backlight/backlightUtil.vala new file mode 100644 index 0000000..6ac2c4c --- /dev/null +++ b/src/controlCenter/widgets/backlight/backlightUtil.vala @@ -0,0 +1,130 @@ +namespace SwayNotificationCenter.Widgets { + class BacklightUtil { + + [DBus (name = "org.freedesktop.login1.Session")] + interface Login1 : Object { + public abstract void set_brightness (string subsystem, + string name, uint32 brightness) throws GLib.Error; + } + + string path_current; + string path_max; + File fd; + FileMonitor monitor = null; + + int max; + + Login1 login1; + string device; + string subsystem; + + public signal void brightness_change (int percent); + + public BacklightUtil (string s, string d) { + this.subsystem = s; + this.device = d; + + path_current = Path.build_path (Path.DIR_SEPARATOR_S, + "/sys", "class", subsystem, device, "brightness"); + path_max = Path.build_path (Path.DIR_SEPARATOR_S, + "/sys", "class", subsystem, device, "max_brightness"); + fd = File.new_for_path (path_current); + if (fd.query_exists ()) { + set_max_value (); + try { + monitor = fd.monitor (FileMonitorFlags.NONE, null); + } catch (Error e) { + error ("Error %s\n", e.message); + } + } else { + this.brightness_change (-1); + warning ("Could not find device %s\n", path_current); + close (); + } + + try { + // setup DBus for setting brightness + login1 = Bus.get_proxy_sync (BusType.SYSTEM, + "org.freedesktop.login1", "/org/freedesktop/login1/session/auto"); + } catch (Error e) { + error ("Error %s\n", e.message); + } + } + + public void start () { + if (fd.query_exists ()) { + // get changes made while controlCenter not shown + get_brightness (); + + connect_monitor (); + } else { + this.brightness_change (-1); + warning ("Could not find device %s\n", path_current); + close (); + } + } + + private void connect_monitor () { + if (monitor != null) { + // connect monitor to monitor changes + monitor.changed.connect ((src, dest, event) => { + get_brightness (); + }); + } + } + + public void close () { + if (monitor != null) monitor.cancel (); + } + + public void set_brightness (float percent) { + this.close (); + try { + if (subsystem == "backlight") { + int actual = calc_actual (percent); + login1.set_brightness (subsystem, device, actual); + } else { + login1.set_brightness (subsystem, device, (uint32) percent); + } + } catch (Error e) { + error ("Error %s\n", e.message); + } + connect_monitor (); + } + + // get current brightness and emit signal + private void get_brightness () { + try { + var dis = new DataInputStream (fd.read (null)); + string data = dis.read_line (null); + int val = calc_percent (int.parse (data)); + this.brightness_change (val); + } catch (Error e) { + error ("Error %s\n", e.message); + } + } + + private void set_max_value () { + try { + File fd_max = File.new_for_path (path_max); + DataInputStream dis_max = new DataInputStream (fd_max.read (null)); + string data = dis_max.read_line (null); + max = int.parse (data); + } catch (Error e) { + error ("Error %s\n", e.message); + } + } + + private int calc_percent (int val) { + return val * 100 / max; + } + + private int calc_actual (float val) { + return (int) val * max / 100; + } + + public int get_max_value () { + return this.max; + } + } +} diff --git a/src/controlCenter/widgets/factory.vala b/src/controlCenter/widgets/factory.vala index dd12671..ea7dda7 100644 --- a/src/controlCenter/widgets/factory.vala +++ b/src/controlCenter/widgets/factory.vala @@ -29,6 +29,9 @@ namespace SwayNotificationCenter.Widgets { case "volume": widget = new Volume (suffix, swaync_daemon, noti_daemon); break; + case "backlight": + widget = new Backlight (suffix, swaync_daemon, noti_daemon); + break; default: warning ("Could not find widget: \"%s\"!", key); return null; diff --git a/src/meson.build b/src/meson.build index 64f7296..e0550ad 100644 --- a/src/meson.build +++ b/src/meson.build @@ -44,6 +44,9 @@ widget_sources = [ 'controlCenter/widgets/volume/volume.vala', 'controlCenter/widgets/volume/pulseDaemon.vala', 'controlCenter/widgets/volume/pulseDevice.vala', + # Widget: Backlight Slider + 'controlCenter/widgets/backlight/backlight.vala', + 'controlCenter/widgets/backlight/backlightUtil.vala', ] app_sources = [ diff --git a/src/style.css b/src/style.css index 23a8874..816d528 100644 --- a/src/style.css +++ b/src/style.css @@ -290,3 +290,11 @@ margin: 8px; border-radius: 12px; } + +/* Backlight widget */ +.widget-backlight { + background-color: @noti-bg; + padding: 8px; + margin: 8px; + border-radius: 12px; +}