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
129 changes: 88 additions & 41 deletions bin/omarchy-brightness-display
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,42 @@ monitor_is_internal() {
[[ $monitor =~ ^(eDP|LVDS|DSI)- ]]
}

# Hyprland monitor names are DRM connector names, so a backlight on the monitor's connected external
# connector is its own. Another card can expose a connector with the same name; when more than one is
# connected the name can't tell them apart, so neither backlight is used. Internal panels stay on
# omarchy-hw-display: GPU drivers can register an eDP backlight that doesn't drive the panel.
connector_backlight() {
local status connector="" subsystem

if [[ -z $monitor ]] || monitor_is_internal; then
return 1
fi

for status in "${OMARCHY_DRM_PATH:-/sys/class/drm}"/card*-"$monitor"/status; do
[[ -e $status ]] || continue

if [[ $(<"$status") == "connected" ]]; then
[[ -z $connector ]] || return 1
connector="${status%/status}"
fi
done

[[ -n $connector ]] || return 1

for subsystem in "$connector"/*/subsystem; do
if [[ $(readlink "$subsystem") == */class/backlight ]]; then
subsystem="${subsystem%/subsystem}"
printf '%s\n' "${subsystem##*/}"
return 0
fi
done

return 1
}

# A connector backlight that can't be read or written falls back to the display's other backends
device="$(connector_backlight)"
Comment thread
greptile-apps[bot] marked this conversation as resolved.

use_apple_display() {
omarchy-hyprland-monitor-focused-apple "$monitor"
}
Expand All @@ -43,8 +79,47 @@ use_ddc_display() {
[[ -n $monitor ]] && ! monitor_is_internal
}

# Step a kernel backlight and show its new level, failing when it can't be read or written
step_backlight() {
local device="$1" step="$2" current target

# Current brightness percentage
current=$(backlight_brightness "$device") || return 1

# Apply non-uniform step size: 1% steps if at or below 5%, otherwise set an
# absolute target percentage to avoid raw backlight rounding causing uneven OSD steps.
if [[ $step == "+5%" ]]; then
if (( current < 5 )); then
(( target = current + 1 ))
else
(( target = current + 5 ))
fi

(( target > 100 )) && target=100
step="$target%"
elif [[ $step == "5%-" ]]; then
if (( current <= 5 )); then
(( target = current - 1 ))
else
(( target = current - 5 ))
fi

(( target < 1 )) && target=1
step="$target%"
fi

# Set brightness of the display device.
brightnessctl -d "$device" set "$step" >/dev/null || return 1

# Show the new brightness in OSD
(( no_osd )) || omarchy-osd -i brightness -p "$(backlight_brightness "$device")"
return 0
}

if (( $# == 0 )); then
if use_apple_display; then
if [[ -n $device ]] && backlight_brightness "$device"; then
exit
elif use_apple_display; then
omarchy-brightness-display-apple
exit
elif use_ddc_display; then
Expand Down Expand Up @@ -76,47 +151,19 @@ fi
exec {lock_fd}>"${XDG_RUNTIME_DIR:-/tmp}/omarchy-brightness-display.lock"
flock -n "$lock_fd" || exit 0

if use_apple_display; then
if (( no_osd )); then
omarchy-brightness-display-apple --no-osd "$step"
else
omarchy-brightness-display-apple "$step"
fi
elif use_ddc_display; then
brightness="$(omarchy-brightness-display-ddc "$monitor" "$step")" || exit 1
(( no_osd )) || omarchy-osd -i brightness -p "$brightness"
else
# Current device highlighted
device="$(omarchy-hw-display)" || exit 1

# Current brightness percentage
current=$(backlight_brightness "$device") || exit 1

# Apply non-uniform step size: 1% steps if at or below 5%, otherwise set an
# absolute target percentage to avoid raw backlight rounding causing uneven OSD steps.
if [[ $step == "+5%" ]]; then
if (( current < 5 )); then
(( target = current + 1 ))
else
(( target = current + 5 ))
fi

(( target > 100 )) && target=100
step="$target%"
elif [[ $step == "5%-" ]]; then
if (( current <= 5 )); then
(( target = current - 1 ))
if [[ -z $device ]] || ! step_backlight "$device" "$step"; then
if use_apple_display; then
if (( no_osd )); then
omarchy-brightness-display-apple --no-osd "$step"
else
(( target = current - 5 ))
omarchy-brightness-display-apple "$step"
fi

(( target < 1 )) && target=1
step="$target%"
elif use_ddc_display; then
brightness="$(omarchy-brightness-display-ddc "$monitor" "$step")" || exit 1
(( no_osd )) || omarchy-osd -i brightness -p "$brightness"
else
# The most likely panel backlight
device="$(omarchy-hw-display)" || exit 1
step_backlight "$device" "$step" || exit 1
fi

# Set brightness of the display device.
brightnessctl -d "$device" set "$step" >/dev/null

# Show the new brightness in OSD
(( no_osd )) || omarchy-osd -i brightness -p "$(backlight_brightness "$device")"
fi
4 changes: 2 additions & 2 deletions manual/33-monitors.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ You can also checkout [Hyprmon](https://github.com/erans/hyprmon/), if you'd lik

### Controlling brightness

Monitor brightness is controlled by the dedicated function keys for brightness up/down. If you hold down shift while pressing these, you'll go to maximum or minimum brightness. The keys control the display you're focused on, so external monitors that speak DDC/CI are adjusted the same way as the laptop screen.
Monitor brightness is controlled by the dedicated function keys for brightness up/down. If you hold down shift while pressing these, you'll go to maximum or minimum brightness. The keys control the display you're focused on, so external monitors are adjusted the same way as the laptop screen, either through a kernel backlight the monitor exposes or over DDC/CI.

### Apple Displays

If you're using an Apple display, the regular keyboard brightness keys will also automatically work, if you're focused on the Apple display. This is done through the `asdcontrol` command.
If you're using an Apple display, the regular keyboard brightness keys will also automatically work, if you're focused on the Apple display. This is done through the kernel backlight when the display exposes one, and through the `asdcontrol` command otherwise.

Note that if you're using an Apple 6K XDR display, you may see a phantom screen in your `hyprctl monitors` listing. You can turn this off with something like `hl.monitor({ output = "DP-2", disabled = true })` via _Setup > Monitors_.

Expand Down
3 changes: 2 additions & 1 deletion shell/services/BrightnessKeys.qml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import "BrightnessModel.js" as BrightnessModel
// device, reading, writing and reading back, then an IPC client for the OSD.
// They step, clamp and read back the way that script does, so either path
// lands on the same level and OSD. External and Apple displays go through the
// script, which drives them over DDC or their own helper.
// script, which drives them through their connector's kernel backlight when
// they have one, else over DDC or their own helper.
Item {
id: root

Expand Down
100 changes: 98 additions & 2 deletions test/shell.d/brightness-display-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,38 @@ trap 'rm -rf "$test_tmp"' EXIT
mock_bin="$test_tmp/bin"
call_log="$test_tmp/calls"
runtime_dir="$test_tmp/runtime"
drm_dir="$test_tmp/drm"
mkdir -p "$mock_bin" "$runtime_dir"

# DP-3 has a connector backlight and DP-1 only a non-backlight child. eDP-1's i915-style backlight must
# not override omarchy-hw-display, and card1's disconnected DP-3 backlight must be ignored. DP-4 is
# connected on two cards, so card2's backlight can't be told apart from card1's plain monitor.
mkdir -p "$drm_dir/card2-DP-3/apple-DP-3-bl" "$drm_dir/card2-DP-1/power" "$drm_dir/card0-eDP-1/intel_backlight" \
"$drm_dir/card1-DP-3/other-DP-3-bl" "$drm_dir/card2-DP-4/apple-DP-4-bl" "$drm_dir/card1-DP-4"
ln -s ../../../../class/backlight "$drm_dir/card2-DP-3/apple-DP-3-bl/subsystem"
ln -s ../../../../class/backlight "$drm_dir/card0-eDP-1/intel_backlight/subsystem"
ln -s ../../../../bus/platform "$drm_dir/card2-DP-1/power/subsystem"
ln -s ../../../../class/backlight "$drm_dir/card1-DP-3/other-DP-3-bl/subsystem"
ln -s ../../../../class/backlight "$drm_dir/card2-DP-4/apple-DP-4-bl/subsystem"
for connector in card2-DP-3 card2-DP-1 card0-eDP-1 card2-DP-4 card1-DP-4; do
printf 'connected\n' >"$drm_dir/$connector/status"
done
printf 'disconnected\n' >"$drm_dir/card1-DP-3/status"

cat >"$mock_bin/omarchy-hyprland-monitor-focused-apple" <<'SH'
#!/bin/bash
exit 1
printf 'omarchy-hyprland-monitor-focused-apple %s\n' "$*" >>"$CALL_LOG"
[[ ${APPLE_DISPLAY:-0} == "1" ]]
SH

cat >"$mock_bin/omarchy-brightness-display-apple" <<'SH'
#!/bin/bash
printf 'omarchy-brightness-display-apple %s\n' "$*" >>"$CALL_LOG"
SH

cat >"$mock_bin/omarchy-osd" <<'SH'
#!/bin/bash
printf 'omarchy-osd %s\n' "$*" >>"$CALL_LOG"
SH

cat >"$mock_bin/omarchy-hyprland-monitor-focused" <<'SH'
Expand All @@ -31,7 +58,10 @@ cat >"$mock_bin/brightnessctl" <<'SH'
#!/bin/bash
printf 'brightnessctl %s\n' "$*" >>"$CALL_LOG"
if [[ $* == *" -m"* ]]; then
[[ -n ${BRIGHTNESSCTL_READ_FAIL:-} && $* == "-d $BRIGHTNESSCTL_READ_FAIL "* ]] && exit 1
printf 'mock_backlight,backlight,40,40%%\n'
elif [[ -n ${BRIGHTNESSCTL_WRITE_FAIL:-} && $* == "-d $BRIGHTNESSCTL_WRITE_FAIL set "* ]]; then
exit 1
fi
SH

Expand All @@ -54,7 +84,7 @@ SH
chmod +x "$mock_bin"/*

run_brightness() {
CALL_LOG="$call_log" XDG_RUNTIME_DIR="$runtime_dir" PATH="$mock_bin:$ROOT/bin:$PATH" \
CALL_LOG="$call_log" XDG_RUNTIME_DIR="$runtime_dir" OMARCHY_DRM_PATH="$drm_dir" PATH="$mock_bin:$ROOT/bin:$PATH" \
"$ROOT/bin/omarchy-brightness-display" "$@"
}

Expand Down Expand Up @@ -129,6 +159,72 @@ grep -F 'ddcutil --bus 7 --skip-ddc-checks --noverify setvcp 10 5' "$call_log" >
fail "external low brightness writes the one-percent target"
pass "external low brightness uses a one-percent step"

: >"$call_log"
brightness=$(APPLE_DISPLAY=1 run_brightness --monitor DP-3)
[[ $brightness == "40" ]] || fail "connector backlight reports brightness" "actual: $brightness"
grep -F 'brightnessctl -d apple-DP-3-bl -m' "$call_log" >/dev/null || \
fail "connector backlight is queried through brightnessctl"
pass "connector backlight reports brightness"

: >"$call_log"
APPLE_DISPLAY=1 run_brightness --monitor DP-3 +5%
grep -Fx 'brightnessctl -d apple-DP-3-bl set 45%' "$call_log" >/dev/null || \
fail "connector backlight steps through brightnessctl" "$(cat "$call_log")"
grep -Fx 'omarchy-osd -i brightness -p 40' "$call_log" >/dev/null || \
fail "connector backlight shows the OSD" "$(cat "$call_log")"
if grep -E 'omarchy-brightness-display-apple|omarchy-hyprland-monitor-focused-apple|ddcutil' "$call_log"; then
fail "connector backlight skips the Apple and DDC backends"
fi
pass "connector backlight wins over the Apple and DDC backends"

: >"$call_log"
FOCUSED_MONITOR=DP-3 run_brightness --no-osd 1%-
grep -Fx 'brightnessctl -d apple-DP-3-bl set 1%-' "$call_log" >/dev/null || \
fail "focused monitor's connector backlight takes precise steps" "$(cat "$call_log")"
pass "focused monitor's connector backlight takes precise steps"

: >"$call_log"
APPLE_DISPLAY=1 run_brightness --no-osd --monitor DP-1 +5%
grep -Fx 'omarchy-brightness-display-apple --no-osd +5%' "$call_log" >/dev/null || \
fail "Apple display without a connector backlight keeps asdcontrol" "$(cat "$call_log")"
pass "Apple display without a connector backlight keeps asdcontrol"

: >"$call_log"
APPLE_DISPLAY=1 BRIGHTNESSCTL_WRITE_FAIL=apple-DP-3-bl run_brightness --monitor DP-3 +5%
grep -Fx 'omarchy-brightness-display-apple +5%' "$call_log" >/dev/null || \
fail "unwritable connector backlight falls back to asdcontrol" "$(cat "$call_log")"
if grep -F 'omarchy-osd' "$call_log"; then
fail "unwritable connector backlight shows no OSD of its own"
fi
pass "unwritable connector backlight falls back to asdcontrol"

: >"$call_log"
rm -f "$runtime_dir/omarchy-brightness-display-ddc/DP-3.bus"
DDC_CONNECTOR=DP-3 DDC_CURRENT=40 DDC_MAXIMUM=100 BRIGHTNESSCTL_WRITE_FAIL=apple-DP-3-bl \
run_brightness --no-osd --monitor DP-3 30%
grep -F 'ddcutil --bus 7 --skip-ddc-checks --noverify setvcp 10 30' "$call_log" >/dev/null || \
fail "unwritable connector backlight falls back to DDC with the requested step" "$(cat "$call_log")"
pass "unwritable connector backlight falls back to DDC with the requested step"

: >"$call_log"
APPLE_DISPLAY=1 BRIGHTNESSCTL_READ_FAIL=apple-DP-3-bl run_brightness --monitor DP-3 >/dev/null
grep -Fx 'omarchy-brightness-display-apple ' "$call_log" >/dev/null || \
fail "unreadable connector backlight falls back to asdcontrol for its level" "$(cat "$call_log")"
pass "unreadable connector backlight falls back to asdcontrol for its level"

stderr=$(APPLE_DISPLAY=1 run_brightness --no-osd --monitor HEADLESS-1 +5% 2>&1 >/dev/null)
[[ -z $stderr ]] || fail "monitor without a DRM connector looks up its backlight quietly" "$stderr"
pass "monitor without a DRM connector looks up its backlight quietly"

: >"$call_log"
APPLE_DISPLAY=1 run_brightness --no-osd --monitor DP-4 +5%
if grep -F 'apple-DP-4-bl' "$call_log"; then
fail "connector name shared by two connected cards uses neither backlight"
fi
grep -Fx 'omarchy-brightness-display-apple --no-osd +5%' "$call_log" >/dev/null || \
fail "connector name shared by two connected cards keeps the other backends" "$(cat "$call_log")"
pass "connector name shared by two connected cards uses neither backlight"

cat >"$mock_bin/hyprctl" <<'SH'
#!/bin/bash
printf '%s\n' '[
Expand Down