summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAhmed Abdelhalim <[email protected]>2026-02-09 19:00:45 +0100
committerAhmed Abdelhalim <[email protected]>2026-02-09 19:03:37 +0100
commit89773438b6583dd1dcaf33ad6d581eee697246f2 (patch)
tree7cfdc647507af97bcb29f6907c87eaadba476882
parentdc8f42e5666e9d498bd19b323f5e1c3b9440ba3f (diff)
Add brightness control using ddcutil to hyprland
The ddcutil is really slow by design and needing to wait the recommended 40-200ms after each request to get the response according to the ddc protocol specifications. For now this is a working draft, might switch to ddccontrol which is much faster but limited to communicate over /dev/<id> which can't be figured automatically or correlated with hyprctl monitors command
-rw-r--r--roles/ddcutil/handlers/main.yml12
-rw-r--r--roles/ddcutil/meta/argument_specs.yml6
-rw-r--r--roles/ddcutil/meta/main.yml17
-rw-r--r--roles/ddcutil/tasks/main.yml38
-rw-r--r--roles/hyprland/files/scripts/brightness.sh117
-rw-r--r--roles/hyprland/meta/main.yml1
-rw-r--r--roles/hyprland/tasks/main.yml11
7 files changed, 202 insertions, 0 deletions
diff --git a/roles/ddcutil/handlers/main.yml b/roles/ddcutil/handlers/main.yml
new file mode 100644
index 00000000..85720137
--- /dev/null
+++ b/roles/ddcutil/handlers/main.yml
@@ -0,0 +1,12 @@
+---
+- name: "Reload rules"
+ become: true
+ ansible.builtin.command:
+ cmd: "udevadm control --reload-rules"
+ changed_when: true
+
+- name: "Trigger rules"
+ become: true
+ ansible.builtin.command:
+ cmd: "udevadm trigger"
+ changed_when: true
diff --git a/roles/ddcutil/meta/argument_specs.yml b/roles/ddcutil/meta/argument_specs.yml
new file mode 100644
index 00000000..09847b93
--- /dev/null
+++ b/roles/ddcutil/meta/argument_specs.yml
@@ -0,0 +1,6 @@
+---
+argument_specs:
+ main:
+ short_description: "Install ddcutil on Linux distribution"
+ description: "Install ddcutil on Linux distribution"
+ options: {}
diff --git a/roles/ddcutil/meta/main.yml b/roles/ddcutil/meta/main.yml
new file mode 100644
index 00000000..025d3e11
--- /dev/null
+++ b/roles/ddcutil/meta/main.yml
@@ -0,0 +1,17 @@
+---
+dependencies: []
+galaxy_info:
+ author: "a14m"
+ description: "Install ddcutil"
+ license: "MIT"
+ min_ansible_version: "2.18"
+ platforms:
+ - name: "ArchLinux"
+ versions:
+ - "all"
+ - name: "Ubuntu"
+ versions:
+ - "noble"
+ - name: "Debian"
+ versions:
+ - "bookworm"
diff --git a/roles/ddcutil/tasks/main.yml b/roles/ddcutil/tasks/main.yml
new file mode 100644
index 00000000..feef8683
--- /dev/null
+++ b/roles/ddcutil/tasks/main.yml
@@ -0,0 +1,38 @@
+---
+- name: "Ensure ddcutil is installed"
+ become: true
+ ansible.builtin.package:
+ name: "ddcutil"
+ state: "present"
+
+- name: "Ensure kernel module is loaded on boot"
+ become: true
+ ansible.builtin.copy:
+ dest: "/etc/modules-load.d/i2c.conf"
+ mode: "0644"
+ content: |
+ i2c-dev
+
+- name: "Ensure i2c group is created"
+ become: true
+ ansible.builtin.group:
+ name: "i2c"
+ state: "present"
+
+- name: "Ensure $USER is added to i2c group"
+ become: true
+ ansible.builtin.user:
+ name: "{{ ansible_facts['env']['USER'] }}"
+ groups: "i2c"
+ append: true
+
+- name: "Create udev rule for i2c device permissions"
+ become: true
+ ansible.builtin.copy:
+ dest: "/etc/udev/rules.d/99-i2c.rules"
+ mode: "0644"
+ content: |
+ KERNEL=="i2c-[0-9]*", GROUP="i2c", MODE="0660"
+ notify:
+ - "Reload rules"
+ - "Trigger rules"
diff --git a/roles/hyprland/files/scripts/brightness.sh b/roles/hyprland/files/scripts/brightness.sh
new file mode 100644
index 00000000..10a33d8b
--- /dev/null
+++ b/roles/hyprland/files/scripts/brightness.sh
@@ -0,0 +1,117 @@
+#!/bin/bash
+
+# Original Inspiration:
+# - https://github.com/basecamp/omarchy/issues/2509
+# - https://albert.nz/hyprland-brightness
+# ---------------------
+
+# Get focused monitor name
+get_focused_display_name() {
+ hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name'
+}
+
+# Get focused monitor serial
+get_focused_display_serial() {
+ hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .serial'
+}
+
+set_brightness_ddcutil() {
+ local display_serial="$1"
+ local step="$2"
+ local direction="$3"
+ ddcutil \
+ --noconfig \
+ --skip-ddc-checks \
+ --sleep-multiplier=0.1 \
+ --sn="$display_serial" \
+ setvcp 10 $direction "$step"
+}
+
+set_brightness_brightnessctl() {
+ local step="$1"
+ local direction="$2"
+ brightnessctl set "${step}%${direction}"
+}
+
+get_brightness_ddcutil() {
+ local display_serial="$1"
+ ddcutil \
+ --noconfig \
+ --skip-ddc-checks \
+ --sleep-multiplier=0.1 \
+ --sn="$display_serial" \
+ --terse getvcp 10 \
+ | awk '{print $4}'
+}
+
+get_brightness_brightnessctl() {
+ brightnessctl -m | cut -d, -f4 | sed 's/%//'
+}
+
+
+# Get current brightness for a specific monitor
+get_current_brightness() {
+ local display_name=$(get_focused_display_name)
+ local display_serial=$(get_focused_display_serial)
+ if [ "$display_name" = "eDP-1" ]; then
+ echo "$(get_brightness_brightnessctl)"
+ elif [ -n "$display_serial" ]; then
+ echo "$(get_brightness_ddcutil $display_serial)"
+ else
+ echo "N/A"
+ fi
+}
+
+# Set brightness for a specific monitor
+set_brightness() {
+ local step="$1"
+ local direction="$2"
+ local display_name=$(get_focused_display_name)
+ local display_serial=$(get_focused_display_serial)
+
+ if [ "$display_name" = "eDP-1" ]; then
+ set_brighntess_brightnessctl $step $direction
+ elif [ -n "$display_serial" ]; then
+ set_brightness_ddcutil $display_serial $step $direction
+ else
+ return
+ fi
+
+ notify $display_serial $display_name $(get_current_brightness)
+}
+
+notify() {
+ serial="$1"
+ name="$2"
+ value="$3"
+ notify-send -e \
+ -h string:x-canonical-private-synchronous:brightness-$serial \
+ -u low \
+ "Brightness" \
+ " $name: $value%"
+}
+
+STEP="${2:-1}"
+case "$1" in
+ "--get")
+ value=$(get_current_brightness)
+ echo "${value}%"
+ ;;
+ "--set")
+ set_brightness $STEP
+ ;;
+ "--inc")
+ set_brightness $STEP +
+ ;;
+ "--dec")
+ set_brightness $STEP -
+ ;;
+ *)
+ echo -e "Usage:"
+ echo -e " --get to get current brightness value"
+ echo -e " --set [arg] to set current brightness value to [arg]%"
+ echo -e " --inc [arg] to increment current brightness value by [arg]%"
+ echo -e " --dec [arg] to decrement current brightness value by [arg]%"
+ exit 0
+ ;;
+esac
diff --git a/roles/hyprland/meta/main.yml b/roles/hyprland/meta/main.yml
index 8794e8c6..5ef0b024 100644
--- a/roles/hyprland/meta/main.yml
+++ b/roles/hyprland/meta/main.yml
@@ -6,6 +6,7 @@ dependencies:
- role: "mako"
- role: "pipewire"
- role: "brightnessctl"
+ - role: "ddcutil"
- role: "font"
- role: "aur_font"
- role: "waybar"
diff --git a/roles/hyprland/tasks/main.yml b/roles/hyprland/tasks/main.yml
index 5255a318..fc8c0f29 100644
--- a/roles/hyprland/tasks/main.yml
+++ b/roles/hyprland/tasks/main.yml
@@ -36,6 +36,7 @@
mode: "0755"
loop:
- "/etc/hypr"
+ - "/etc/hypr/scripts"
- "{{ ansible_facts['env']['HOME'] }}/.config/hypr"
- name: "Configure system defaults"
@@ -89,3 +90,13 @@
block: |
# System defaults
source = /etc/hypr/hypridle.conf
+
+- name: "Copy hyprland scripts"
+ become: true
+ ansible.builtin.copy:
+ src: "{{ item.src }}"
+ dest: "{{ item.dest }}"
+ mode: "0755"
+ loop:
+ - src: "scripts/brightness.sh"
+ dest: "/etc/hypr/scripts/brightness.sh"