From 7d45e9dc1bbe48b80667d2ca4256429bf551d0ab Mon Sep 17 00:00:00 2001 From: MoetaYuko Date: Wed, 1 May 2024 15:23:30 +0800 Subject: [PATCH 1/7] ags: implement per-monitor brightness control --- .../modules/.configuration/user_options.js | 5 +- .config/ags/modules/bar/main.js | 2 +- .config/ags/modules/bar/normal/spaceleft.js | 8 +- .../ags/modules/indicators/indicatorvalues.js | 12 +-- .config/ags/modules/indicators/main.js | 3 +- .config/ags/services/brightness.js | 77 ++++++++++++++++--- 6 files changed, 81 insertions(+), 26 deletions(-) diff --git a/.config/ags/modules/.configuration/user_options.js b/.config/ags/modules/.configuration/user_options.js index fcc7ee6c5..1aa791def 100644 --- a/.config/ags/modules/.configuration/user_options.js +++ b/.config/ags/modules/.configuration/user_options.js @@ -38,6 +38,9 @@ let configOptions = { 'warnTitles': ["Low battery", "Very low battery", 'Critical Battery'], 'warnMessages': ["Plug in the charger", "You there?", 'PLUG THE CHARGER ALREADY'], }, + 'brightness': { + 'controller': "brightnessctl", + }, 'music': { 'preferredPlayer': "plasma-browser-integration", }, @@ -159,4 +162,4 @@ function overrideConfigRecursive(userOverrides, configOptions = {}) { overrideConfigRecursive(userOverrides, configOptions); globalThis['userOptions'] = configOptions; -export default configOptions; \ No newline at end of file +export default configOptions; diff --git a/.config/ags/modules/bar/main.js b/.config/ags/modules/bar/main.js index 7f74c541a..0a0d8f42e 100644 --- a/.config/ags/modules/bar/main.js +++ b/.config/ags/modules/bar/main.js @@ -46,7 +46,7 @@ export const Bar = async (monitor = 0) => { const minHeight = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL); // execAsync(['bash', '-c', `hyprctl keyword monitor ,addreserved,${minHeight},0,0,0`]).catch(print); }, - startWidget: (await WindowTitle()), + startWidget: (await WindowTitle(monitor)), centerWidget: Widget.Box({ className: 'spacing-h-4', children: [ diff --git a/.config/ags/modules/bar/normal/spaceleft.js b/.config/ags/modules/bar/normal/spaceleft.js index 6fbe5d394..b3d796b63 100644 --- a/.config/ags/modules/bar/normal/spaceleft.js +++ b/.config/ags/modules/bar/normal/spaceleft.js @@ -39,16 +39,16 @@ const WindowTitle = async () => { } -export default async () => { +export default async (monitor = 0) => { const optionalWindowTitleInstance = await WindowTitle(); return Widget.EventBox({ onScrollUp: () => { Indicator.popup(1); // Since the brightness and speaker are both on the same window - Brightness.screen_value += 0.05; + Brightness[monitor].screen_value += 0.05; }, onScrollDown: () => { Indicator.popup(1); // Since the brightness and speaker are both on the same window - Brightness.screen_value -= 0.05; + Brightness[monitor].screen_value -= 0.05; }, onPrimaryClick: () => { App.toggleWindow('sideleft'); @@ -75,4 +75,4 @@ export default async () => { ] }) }); -} \ No newline at end of file +} diff --git a/.config/ags/modules/indicators/indicatorvalues.js b/.config/ags/modules/indicators/indicatorvalues.js index 18f228626..755d63e8b 100644 --- a/.config/ags/modules/indicators/indicatorvalues.js +++ b/.config/ags/modules/indicators/indicatorvalues.js @@ -49,16 +49,16 @@ const OsdValue = ({ }); } -export default () => { +export default (monitor = 0) => { const brightnessIndicator = OsdValue({ name: 'Brightness', extraClassName: 'osd-brightness', extraProgressClassName: 'osd-brightness-progress', - labelSetup: (self) => self.hook(Brightness, self => { - self.label = `${Math.round(Brightness.screen_value * 100)}`; + labelSetup: (self) => self.hook(Brightness[monitor], self => { + self.label = `${Math.round(Brightness[monitor].screen_value * 100)}`; }, 'notify::screen-value'), - progressSetup: (self) => self.hook(Brightness, (progress) => { - const updateValue = Brightness.screen_value; + progressSetup: (self) => self.hook(Brightness[monitor], (progress) => { + const updateValue = Brightness[monitor].screen_value; progress.value = updateValue; }, 'notify::screen-value'), }); @@ -109,4 +109,4 @@ export default () => { ] }) }); -} \ No newline at end of file +} diff --git a/.config/ags/modules/indicators/main.js b/.config/ags/modules/indicators/main.js index db3fc4c8c..16741937f 100644 --- a/.config/ags/modules/indicators/main.js +++ b/.config/ags/modules/indicators/main.js @@ -22,7 +22,7 @@ export default (monitor = 0) => Widget.Window({ className: 'osd-window', css: 'min-height: 2px;', children: [ - IndicatorValues(), + IndicatorValues(monitor), MusicControls(), NotificationPopups(), ColorScheme(), @@ -30,4 +30,3 @@ export default (monitor = 0) => Widget.Window({ }) }), }); - diff --git a/.config/ags/services/brightness.js b/.config/ags/services/brightness.js index 89bfbac10..778f44ef2 100644 --- a/.config/ags/services/brightness.js +++ b/.config/ags/services/brightness.js @@ -1,10 +1,11 @@ +import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Service from 'resource:///com/github/Aylur/ags/service.js'; import * as Utils from 'resource:///com/github/Aylur/ags/utils.js'; const { exec, execAsync } = Utils; import { clamp } from '../modules/.miscutils/mathfuncs.js'; -class BrightnessService extends Service { +class BrightnessServiceBase extends Service { static { Service.register( this, @@ -23,7 +24,7 @@ class BrightnessService extends Service { percent = clamp(percent, 0, 1); this._screenValue = percent; - Utils.execAsync(`brightnessctl s ${percent * 100}% -q`) + Utils.execAsync(this.setBrightnessCmd(percent)) .then(() => { // signals has to be explicity emitted this.emit('screen-changed', percent); @@ -35,13 +36,6 @@ class BrightnessService extends Service { .catch(print); } - constructor() { - super(); - const current = Number(exec('brightnessctl g')); - const max = Number(exec('brightnessctl m')); - this._screenValue = current / max; - } - // overwriting connectWidget method, lets you // change the default event that widgets connect to connectWidget(widget, callback, event = 'screen-changed') { @@ -49,11 +43,70 @@ class BrightnessService extends Service { } } +class BrightnessCtlService extends BrightnessServiceBase { + static { + Service.register(this); + } + + constructor() { + super(); + const current = Number(exec('brightnessctl g')); + const max = Number(exec('brightnessctl m')); + this._screenValue = current / max; + } + + setBrightnessCmd(percent) { + return `brightnessctl s ${percent * 100}% -q`; + } +} + +class BrightnessDdcService extends BrightnessServiceBase { + static { + Service.register(this); + } + + constructor(monitor = 0) { + super(); + // don't use Hyprland.getMonitor(id), Hyprland monitor id isn't consistent + // with Gdk, but the Array ordering is (magically) + this._sn = Hyprland.monitors[monitor].serial; + Utils.execAsync(`ddcutil --sn ${this._sn} getvcp 10 --brief`) + .then((out) => { + // only the last line is useful + out = out.split('\n'); + out = out[out.length - 1]; + + out = out.split(' '); + const current = Number(out[3]); + const max = Number(out[4]); + this._screenValue = current / max; + }) + .catch(print); + } + + setBrightnessCmd(percent) { + return `ddcutil --sn ${this._sn} setvcp 10 ${Math.round(percent * 100)}`; + } +} + // the singleton instance -const service = new BrightnessService(); +const numMonitors = Hyprland.monitors.length; +const service = Array(numMonitors); +switch (userOptions.brightness.controller) { + case "brightnessctl": + service.fill(new BrightnessCtlService()); + break; + case "ddcutil": + for (let i = 0; i < numMonitors; i++) { + service[i] = new BrightnessDdcService(i); + } + break; + default: + throw new Error(`Unknown brightness controller ${userOptions.brightness.controller}`); +} // make it global for easy use with cli -globalThis.brightness = service; +globalThis.brightness = service[0]; // export to use in other modules -export default service; \ No newline at end of file +export default service; From 409a6d508ae375129ca948f95d0c1b9179c38309 Mon Sep 17 00:00:00 2001 From: MoetaYuko Date: Wed, 1 May 2024 16:01:29 +0800 Subject: [PATCH 2/7] Add ddcutil to installer --- install.sh | 4 ++-- scriptdata/dependencies.conf | 5 ++++- uninstall.sh | 8 +++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/install.sh b/install.sh index 392837512..c4ed16da7 100755 --- a/install.sh +++ b/install.sh @@ -136,7 +136,8 @@ case $SKIP_PLASMAINTG in ;; esac -v sudo usermod -aG video,input "$(whoami)" +v sudo usermod -aG video,i2c,input "$(whoami)" +v bash -c "echo i2c-dev | sudo tee /etc/modules-load.d/i2c-dev.conf" v systemctl --user enable ydotool --now ##################################################################################### @@ -306,4 +307,3 @@ case $existed_hypr_conf in printf "\e[33mPlease use \"~/.config/hypr/hyprland.conf.new\" as a reference for a proper format.\e[0m\n" printf "\e[33mIf this is your first time installation, you must overwrite \"~/.config/hypr/hyprland.conf\" with \"~/.config/hypr/hyprland.conf.new\".\e[0m\n" ;;esac - diff --git a/scriptdata/dependencies.conf b/scriptdata/dependencies.conf index 9fe10337d..9c7bcd95d 100644 --- a/scriptdata/dependencies.conf +++ b/scriptdata/dependencies.conf @@ -26,7 +26,10 @@ pavucontrol wireplumber libdbusmenu-gtk3 playerctl swww webp-pixbuf-loader gtk-layer-shell gtk3 gtksourceview3 gobject-introspection upower yad ydotool ### Gnome -polkit-gnome gnome-keyring gnome-control-center blueberry networkmanager brightnessctl wlsunset gnome-bluetooth-3.0 +polkit-gnome gnome-keyring gnome-control-center blueberry networkmanager wlsunset gnome-bluetooth-3.0 + +### Backlight +brightnessctl ddcutil ### Widgets python-pywayland python-psutil hypridle-git hyprlock-git wlogout wl-clipboard hyprpicker-git anyrun-git diff --git a/uninstall.sh b/uninstall.sh index 4c2dc6a72..174958eae 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -35,16 +35,18 @@ v rm -rf "$HOME/.local/bin/fuzzel-emoji" ############################################################################################################################## -# Undo Step 1: Remove added user from video and input groups and remove yay packages -printf '\e[36mRemoving user from video and input groups and removing packages...\n\e[97m' +# Undo Step 1: Remove added user from video, i2c, and input groups and remove yay packages +printf '\e[36mRemoving user from video, i2c, and input groups and removing packages...\n\e[97m' user=$(whoami) v sudo deluser "$user" video +v sudo deluser "$user" i2c v sudo deluser "$user" input +v sudo rm /etc/modules-load.d/i2c-dev.conf ############################################################################################################################## read -p "Do you want to uninstall packages used by the dotfiles?\nCtrl+C to exit, or press Enter to proceed" # Removing installed yay packages and dependencies -v yay -Rns adw-gtk3-git brightnessctl cava foot fuzzel gjs gojq gradience-git grim gtk-layer-shell hyprland-git lexend-fonts-git libdbusmenu-gtk3 plasma-browser-integration playerctl python-build python-material-color-utilities python-poetry python-pywal ripgrep sassc swww slurp starship swayidle hyprlock-git tesseract ttf-jetbrains-mono-nerd ttf-material-symbols-variable-git ttf-space-mono-nerd typescript webp-pixbuf-loader wl-clipboard wlogout yad ydotool +v yay -Rns adw-gtk3-git brightnessctl cava ddcutil foot fuzzel gjs gojq gradience-git grim gtk-layer-shell hyprland-git lexend-fonts-git libdbusmenu-gtk3 plasma-browser-integration playerctl python-build python-material-color-utilities python-poetry python-pywal ripgrep sassc swww slurp starship swayidle hyprlock-git tesseract ttf-jetbrains-mono-nerd ttf-material-symbols-variable-git ttf-space-mono-nerd typescript webp-pixbuf-loader wl-clipboard wlogout yad ydotool printf '\e[36mUninstall Complete.\n\e[97m' From aa9a38cf5f3b9fd6f6822c8a049a0cfe7efd3e32 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 1 May 2024 22:12:27 +0700 Subject: [PATCH 3/7] comment possible brightness controllers --- .config/ags/modules/.configuration/user_options.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/ags/modules/.configuration/user_options.js b/.config/ags/modules/.configuration/user_options.js index 1aa791def..36aa53ca6 100644 --- a/.config/ags/modules/.configuration/user_options.js +++ b/.config/ags/modules/.configuration/user_options.js @@ -39,7 +39,7 @@ let configOptions = { 'warnMessages': ["Plug in the charger", "You there?", 'PLUG THE CHARGER ALREADY'], }, 'brightness': { - 'controller': "brightnessctl", + 'controller': "brightnessctl", // "brightnessctl" or "ddcutil" }, 'music': { 'preferredPlayer': "plasma-browser-integration", From b598dfac1d26dd305e4b213d808445b2254da2c7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 1 May 2024 22:32:04 +0700 Subject: [PATCH 4/7] brightness controller: per-name config; add "auto" --- .../modules/.configuration/user_options.js | 9 ++++- .config/ags/services/brightness.js | 35 +++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/.config/ags/modules/.configuration/user_options.js b/.config/ags/modules/.configuration/user_options.js index 36aa53ca6..5b22c66ce 100644 --- a/.config/ags/modules/.configuration/user_options.js +++ b/.config/ags/modules/.configuration/user_options.js @@ -39,7 +39,14 @@ let configOptions = { 'warnMessages': ["Plug in the charger", "You there?", 'PLUG THE CHARGER ALREADY'], }, 'brightness': { - 'controller': "brightnessctl", // "brightnessctl" or "ddcutil" + // Object of controller names for each monitor, either "brightnessctl" or "ddcutil" or "auto" + // "default" will be used if unspecified + // Examples + // 'eDP-1': "brightnessctl", + // 'DP-1': "ddcutil", + 'controllers': { + 'default': "auto", + }, }, 'music': { 'preferredPlayer': "plasma-browser-integration", diff --git a/.config/ags/services/brightness.js b/.config/ags/services/brightness.js index 778f44ef2..6bffd5554 100644 --- a/.config/ags/services/brightness.js +++ b/.config/ags/services/brightness.js @@ -89,20 +89,33 @@ class BrightnessDdcService extends BrightnessServiceBase { } } -// the singleton instance +// Service instance const numMonitors = Hyprland.monitors.length; const service = Array(numMonitors); -switch (userOptions.brightness.controller) { - case "brightnessctl": - service.fill(new BrightnessCtlService()); - break; - case "ddcutil": - for (let i = 0; i < numMonitors; i++) { - service[i] = new BrightnessDdcService(i); +for (let i = 0; i < service.length; i++) { + const monitorName = Hyprland.monitors[i].name; + const preferredController = userOptions.brightness.controllers[monitorName] + || userOptions.brightness.controllers.default || "auto"; + if (preferredController) { + switch (preferredController) { + case "brightnessctl": + service[i] = new BrightnessCtlService(); + break; + case "ddcutil": + service[i] = new BrightnessDdcService(i); + break; + case "auto": + if (monitorName.startsWith("eDP-")) + service[i] = new BrightnessCtlService(); + else if (monitorName.startsWith("DP-")) + service[i] = new BrightnessDdcService(i); + else + service[i] = new BrightnessCtlService(); + break; + default: + throw new Error(`Unknown brightness controller ${preferredController}`); } - break; - default: - throw new Error(`Unknown brightness controller ${userOptions.brightness.controller}`); + } } // make it global for easy use with cli From be6fe404b3bfb3f7db8d6d405676329266674773 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Wed, 1 May 2024 22:38:16 +0700 Subject: [PATCH 5/7] make brightness controller comment: less misleading --- .config/ags/modules/.configuration/user_options.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/ags/modules/.configuration/user_options.js b/.config/ags/modules/.configuration/user_options.js index 5b22c66ce..aef4724f9 100644 --- a/.config/ags/modules/.configuration/user_options.js +++ b/.config/ags/modules/.configuration/user_options.js @@ -40,7 +40,7 @@ let configOptions = { }, 'brightness': { // Object of controller names for each monitor, either "brightnessctl" or "ddcutil" or "auto" - // "default" will be used if unspecified + // 'default' one will be used if unspecified // Examples // 'eDP-1': "brightnessctl", // 'DP-1': "ddcutil", From 668927a153a6d75c61c918e084ddfcffddd96ac1 Mon Sep 17 00:00:00 2001 From: MoetaYuko Date: Thu, 2 May 2024 11:38:37 +0800 Subject: [PATCH 6/7] detect ddcci support via ddcutil --- .config/ags/services/brightness.js | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.config/ags/services/brightness.js b/.config/ags/services/brightness.js index 6bffd5554..4b8229163 100644 --- a/.config/ags/services/brightness.js +++ b/.config/ags/services/brightness.js @@ -89,11 +89,32 @@ class BrightnessDdcService extends BrightnessServiceBase { } } +async function listDdcMonitorsSn() { + let ddcSn = []; + try { + const out = await Utils.execAsync('ddcutil detect --brief'); + const displays = out.split('\n\n'); + displays.forEach(display => { + const reg = /^Display \d+/; + if (!reg.test(display)) + return; + const lines = display.split('\n'); + const sn = lines[3].split(':')[3]; + ddcSn.push(sn); + }); + } catch (err) { + print(err); + } + return ddcSn; +} + // Service instance const numMonitors = Hyprland.monitors.length; const service = Array(numMonitors); +const ddcSn = await listDdcMonitorsSn(); for (let i = 0; i < service.length; i++) { const monitorName = Hyprland.monitors[i].name; + const monitorSn = Hyprland.monitors[i].serial; const preferredController = userOptions.brightness.controllers[monitorName] || userOptions.brightness.controllers.default || "auto"; if (preferredController) { @@ -105,9 +126,7 @@ for (let i = 0; i < service.length; i++) { service[i] = new BrightnessDdcService(i); break; case "auto": - if (monitorName.startsWith("eDP-")) - service[i] = new BrightnessCtlService(); - else if (monitorName.startsWith("DP-")) + if (ddcSn.includes(monitorSn)) service[i] = new BrightnessDdcService(i); else service[i] = new BrightnessCtlService(); From 7996fca302798522e6b5ba36e0de9dfc659f6ede Mon Sep 17 00:00:00 2001 From: MoetaYuko Date: Thu, 2 May 2024 11:56:24 +0800 Subject: [PATCH 7/7] use the more efficient i2c bus number to control ddcci backlight --- .config/ags/services/brightness.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/.config/ags/services/brightness.js b/.config/ags/services/brightness.js index 4b8229163..04fccd0ca 100644 --- a/.config/ags/services/brightness.js +++ b/.config/ags/services/brightness.js @@ -65,12 +65,10 @@ class BrightnessDdcService extends BrightnessServiceBase { Service.register(this); } - constructor(monitor = 0) { + constructor(busNum) { super(); - // don't use Hyprland.getMonitor(id), Hyprland monitor id isn't consistent - // with Gdk, but the Array ordering is (magically) - this._sn = Hyprland.monitors[monitor].serial; - Utils.execAsync(`ddcutil --sn ${this._sn} getvcp 10 --brief`) + this._busNum = busNum; + Utils.execAsync(`ddcutil -b ${this._busNum} getvcp 10 --brief`) .then((out) => { // only the last line is useful out = out.split('\n'); @@ -85,12 +83,12 @@ class BrightnessDdcService extends BrightnessServiceBase { } setBrightnessCmd(percent) { - return `ddcutil --sn ${this._sn} setvcp 10 ${Math.round(percent * 100)}`; + return `ddcutil -b ${this._busNum} setvcp 10 ${Math.round(percent * 100)}`; } } -async function listDdcMonitorsSn() { - let ddcSn = []; +async function listDdcMonitorsSnBus() { + let ddcSnBus = {}; try { const out = await Utils.execAsync('ddcutil detect --brief'); const displays = out.split('\n\n'); @@ -100,18 +98,19 @@ async function listDdcMonitorsSn() { return; const lines = display.split('\n'); const sn = lines[3].split(':')[3]; - ddcSn.push(sn); + const busNum = lines[1].split('/dev/i2c-')[1]; + ddcSnBus[sn] = busNum; }); } catch (err) { print(err); } - return ddcSn; + return ddcSnBus; } // Service instance const numMonitors = Hyprland.monitors.length; const service = Array(numMonitors); -const ddcSn = await listDdcMonitorsSn(); +const ddcSnBus = await listDdcMonitorsSnBus(); for (let i = 0; i < service.length; i++) { const monitorName = Hyprland.monitors[i].name; const monitorSn = Hyprland.monitors[i].serial; @@ -123,11 +122,11 @@ for (let i = 0; i < service.length; i++) { service[i] = new BrightnessCtlService(); break; case "ddcutil": - service[i] = new BrightnessDdcService(i); + service[i] = new BrightnessDdcService(ddcSnBus[monitorSn]); break; case "auto": - if (ddcSn.includes(monitorSn)) - service[i] = new BrightnessDdcService(i); + if (monitorSn in ddcSnBus) + service[i] = new BrightnessDdcService(ddcSnBus[monitorSn]); else service[i] = new BrightnessCtlService(); break;