ags: implement per-monitor brightness control (#453)

This commit is contained in:
end-4
2024-05-03 10:19:36 +07:00
committed by GitHub
9 changed files with 131 additions and 33 deletions
@@ -38,6 +38,16 @@ let configOptions = {
'warnTitles': ["Low battery", "Very low battery", 'Critical Battery'],
'warnMessages': ["Plug in the charger", "You there?", 'PLUG THE CHARGER ALREADY'],
},
'brightness': {
// Object of controller names for each monitor, either "brightnessctl" or "ddcutil" or "auto"
// 'default' one will be used if unspecified
// Examples
// 'eDP-1': "brightnessctl",
// 'DP-1': "ddcutil",
'controllers': {
'default': "auto",
},
},
'music': {
'preferredPlayer': "plasma-browser-integration",
},
@@ -159,4 +169,4 @@ function overrideConfigRecursive(userOverrides, configOptions = {}) {
overrideConfigRecursive(userOverrides, configOptions);
globalThis['userOptions'] = configOptions;
export default configOptions;
export default configOptions;
+1 -1
View File
@@ -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: [
+4 -4
View File
@@ -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 () => {
]
})
});
}
}
@@ -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 () => {
]
})
});
}
}
+1 -2
View File
@@ -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({
})
}),
});
+97 -13
View File
@@ -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,101 @@ class BrightnessService extends Service {
}
}
// the singleton instance
const service = new BrightnessService();
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(busNum) {
super();
this._busNum = busNum;
Utils.execAsync(`ddcutil -b ${this._busNum} 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 -b ${this._busNum} setvcp 10 ${Math.round(percent * 100)}`;
}
}
async function listDdcMonitorsSnBus() {
let ddcSnBus = {};
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];
const busNum = lines[1].split('/dev/i2c-')[1];
ddcSnBus[sn] = busNum;
});
} catch (err) {
print(err);
}
return ddcSnBus;
}
// Service instance
const numMonitors = Hyprland.monitors.length;
const service = Array(numMonitors);
const ddcSnBus = await listDdcMonitorsSnBus();
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) {
switch (preferredController) {
case "brightnessctl":
service[i] = new BrightnessCtlService();
break;
case "ddcutil":
service[i] = new BrightnessDdcService(ddcSnBus[monitorSn]);
break;
case "auto":
if (monitorSn in ddcSnBus)
service[i] = new BrightnessDdcService(ddcSnBus[monitorSn]);
else
service[i] = new BrightnessCtlService();
break;
default:
throw new Error(`Unknown brightness controller ${preferredController}`);
}
}
}
// make it global for easy use with cli
globalThis.brightness = service;
globalThis.brightness = service[0];
// export to use in other modules
export default service;
export default service;
+2 -2
View File
@@ -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
+4 -1
View File
@@ -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
dart-sass python-pywayland python-psutil hypridle-git hyprlock-git wlogout wl-clipboard hyprpicker-git anyrun-git
+5 -3
View File
@@ -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'