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
35 changes: 35 additions & 0 deletions bin/omarchy-hw-nightlight
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash

# omarchy:summary=Detect whether the GPU can apply a nightlight color temperature.

# hyprsunset sends its color temperature to Hyprland as a colour transform
# matrix over hyprland-ctm-control-v1, and aquamarine only submits a CTM on the
# DRM atomic path. A driver without atomic modesetting accepts the request and
# drops it, so hyprctl answers "ok", the temperature reads back, and the panel
# never changes — a silent no-op rather than an error.
#
# radeon is the one such driver still in use: it drives pre-GCN AMD cards
# (TeraScale and older, e.g. the Radeon HD 2400 in a 2007 iMac) and reports
# "crtc doesn't support CTM". Everything newer is on amdgpu and is atomic.
#
# Only answer no when every DRM card is on radeon. A machine that also has a
# non-radeon GPU may well be driving its display from that one, and hiding a
# working feature is worse than leaving a broken one visible.
drm_path="${OMARCHY_DRM_PATH:-/sys/class/drm}"

cards=0

for card in "$drm_path"/card[0-9]*; do
name=${card##*/}

# Connectors are exposed as card0-LVDS-1 alongside the card itself.
[[ $name == *-* ]] && continue
[[ -e $card/device/driver ]] || continue

(( ++cards ))
[[ $(readlink -f "$card/device/driver") == */radeon ]] || exit 0
done

(( cards > 0 )) && exit 1

exit 0
16 changes: 16 additions & 0 deletions bin/omarchy-toggle-nightlight
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ status_json() {
'{enabled:$enabled, temperature:(($temp | tonumber?) // null)}'
}

# Without a CTM-capable display pipeline hyprsunset still answers "ok" and
# reports the temperature back, so the retry loop below would run to completion
# and this command would claim success over a panel that never changed. Say so
# instead. The menu hides this row on such hardware; this covers the keybind,
# the CLI, and anything reading --status.
if ! omarchy-hw-nightlight; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Bar bypasses nightlight guard

On a radeon-only machine, this guard blocks the CLI toggle, but the bar indicator calls the QML nightlight service directly. That service still sends the temperature to Hyprland and marks nightlight as enabled. Clicking the indicator therefore reports success even though the panel does not change. The same capability check needs to cover the bar’s control path.

Knowledge Base Used: Hardware profiles and device controls

if [[ ${1:-} == "--status" ]]; then
jq -cn '{enabled:false, temperature:null}'
exit 0
fi

omarchy-notification-send -u normal "Nightlight unavailable" \
"This GPU's display driver cannot apply a color temperature."
exit 1
fi

if [[ ${1:-} == "--status" ]]; then
status_json
exit 0
Expand Down
2 changes: 1 addition & 1 deletion default/omarchy/omarchy-menu.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
"trigger.toggle.crash-capture": {"icon":"󱚡","label":"Crash Capture","action":"omarchy-toggle-crash-capture"},
"trigger.toggle.screensaver": {"icon":"󱄄","label":"Screensaver","action":"omarchy-toggle-screensaver"},
"trigger.toggle.theme-sync": {"icon":"󰸌","label":"Herdr Theme Sync","when":"omarchy-theme-set-herdr-machines --available","action":"omarchy-toggle-theme-sync"},
"trigger.toggle.nightlight": {"icon":"󰔎","label":"Nightlight","action":"omarchy-toggle-nightlight"},
"trigger.toggle.nightlight": {"icon":"󰔎","label":"Nightlight","when":"omarchy-hw-nightlight","action":"omarchy-toggle-nightlight"},
"trigger.toggle.top-bar": {"icon":"󰍜","label":"Menu Bar","action":"omarchy-toggle-bar"},
"trigger.toggle.battery-percentage": {"icon":"󰁹","label":"Battery Percentage","when":"omarchy-hw-laptop","action":"omarchy-shell omarchy.power togglePercentage"},
"trigger.toggle.workspace-layout": {"icon":"󱂬","label":"Workspace Layout","action":"omarchy-hyprland-workspace-layout-toggle"},
Expand Down
5 changes: 3 additions & 2 deletions install/hardware/fix-bcm43xx.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Install Wi-Fi drivers for Broadcom chips found in some MacBooks, as well as other systems:
# - BCM4360 (2013–2015 MacBooks)
# - BCM4331 (2012, early 2013 MacBooks)
# - BCM4321 (2007–2008 iMacs and MacBooks; 4329 and 432a are the same chip)

pci_info=$(lspci -nn)

if (echo "$pci_info" | grep -q "14e4:43a0" || echo "$pci_info" | grep -q "14e4:4331"); then
echo "BCM4360 / BCM4331 detected"
if echo "$pci_info" | grep -qE "14e4:(43a0|4331|4328|4329|432a)"; then
echo "Broadcom BCM4360 / BCM4331 / BCM4321 detected"
omarchy-pkg-add broadcom-wl-dkms
fi
31 changes: 31 additions & 0 deletions install/hardware/vulkan.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,39 @@ declare -A VULKAN_DRIVERS=(

PACKAGES=()

# RADV only drives GPUs bound to amdgpu. Pre-GCN cards (TeraScale and older,
# e.g. the Radeon HD 2400 in a 2007 iMac) stay on the radeon driver, where
# vulkan-radeon still installs radeon_icd.json but every device enumeration
# fails with VK_ERROR_INCOMPATIBLE_DRIVER. That stray ICD makes
# omarchy-hw-vulkan answer yes on a machine with no working Vulkan.
#
# Skip only on radeon: an installer booted with nomodeset binds no driver at
# all, which says nothing about the installed system.
amd_gpu_only_on_radeon() {
local device driver
local on_radeon=false
local pci_devices_path="${OMARCHY_PCI_DEVICES_PATH:-/sys/bus/pci/devices}"

for device in "$pci_devices_path"/*; do
[[ -e $device/vendor ]] || continue
[[ $(< "$device/vendor") == "0x1002" ]] || continue
[[ $(< "$device/class") == 0x03* ]] || continue

driver=$(readlink -f "$device/driver")
[[ $driver == */amdgpu ]] && return 1
[[ $driver == */radeon ]] && on_radeon=true
done

[[ $on_radeon == true ]]
}

for vendor in "${!VULKAN_DRIVERS[@]}"; do
if lspci | grep -iE "(VGA|Display).*$vendor" > /dev/null; then
if [[ $vendor == "AMD" ]] && amd_gpu_only_on_radeon; then
echo "Skipping vulkan-radeon: AMD GPU is on the radeon driver, which RADV cannot drive"
continue
Comment on lines +40 to +42

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Upgrades still install unusable Vulkan

On a radeon-only machine, this hardware pass skips vulkan-radeon, but the quattro upgrade and its hardware backfill migration still install it for any AMD display device. The skip also leaves an already-installed package in place. Those machines can therefore retain or acquire the unusable ICD, so the Vulkan probe can still report support where there is no working Vulkan device.

Knowledge Base Used: Install stages and package sets

fi

PACKAGES+=("${VULKAN_DRIVERS[$vendor]}")
fi
done
Expand Down
60 changes: 60 additions & 0 deletions test/shell.d/broadcom-wifi-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/bin/bash

set -euo pipefail

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

fix_bcm="$ROOT/install/hardware/fix-bcm43xx.sh"

test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT

stub_bin="$test_tmp/bin"
mkdir -p "$stub_bin"

# Real lspci keeps printing well past any match, so a grep -q consumer can be
# killed by SIGPIPE; keep the stub chatty for the same reason as the T2 test.
cat >"$stub_bin/lspci" <<'SH'
#!/bin/bash
[[ -n ${STUB_WIFI_ID:-} ]] &&
echo "04:00.0 Network controller [0280]: Broadcom Inc. and subsidiaries Wireless [$STUB_WIFI_ID] (rev 03)"
for _ in {1..4096}; do
echo '02:00.0 Host bridge [0600]: Filler Device [ffff:0000]'
done
SH
chmod +x "$stub_bin/lspci"

installs_driver_for() {
local wifi_id=${1:-}
local calls="$test_tmp/calls-$RANDOM"

PATH="$stub_bin:$PATH" \
STUB_WIFI_ID="$wifi_id" \
OMARCHY_TEST_CALLS="$calls" \
"$BASH" -c '
omarchy-pkg-add() { printf "%s\n" "$@" >>"$OMARCHY_TEST_CALLS"; }
source "$1"
' _ "$fix_bcm" >/dev/null 2>&1

[[ -f $calls ]] && grep -qx 'broadcom-wl-dkms' "$calls"
}

# BCM4321 is the 2007-2008 iMac/MacBook card. b43 can drive it, but Omarchy
# standardized on the wl driver, which covers it too.
for id in 14e4:4328 14e4:4329 14e4:432a; do
installs_driver_for "$id" ||
fail "BCM4321 ($id) gets broadcom-wl-dkms"
done
pass "BCM4321 device ids pull in broadcom-wl-dkms"

# Regression guard: the ids that already worked must keep working.
installs_driver_for 14e4:43a0 || fail "BCM4360 still gets broadcom-wl-dkms"
installs_driver_for 14e4:4331 || fail "BCM4331 still gets broadcom-wl-dkms"
pass "BCM4360 and BCM4331 keep their driver"

# A Broadcom card brcmfmac drives (BCM4350) must not drag wl in.
! installs_driver_for 14e4:43a3 ||
fail "a brcmfmac Broadcom id does not install broadcom-wl-dkms"
! installs_driver_for ||
fail "a machine with no Broadcom wireless installs nothing"
pass "non-matching hardware installs no Broadcom driver"
67 changes: 67 additions & 0 deletions test/shell.d/nightlight-hardware-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/bin/bash

set -euo pipefail

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

hw_nightlight="$ROOT/bin/omarchy-hw-nightlight"
toggle="$ROOT/bin/omarchy-toggle-nightlight"
menu="$ROOT/default/omarchy/omarchy-menu.jsonc"

test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT

# A /sys/class/drm shaped tree: one directory per card bound to the named
# driver, plus the connector directories the real thing interleaves with them.
make_drm_tree() {
local tree="$test_tmp/drm-$RANDOM"
local index=0
local driver

mkdir -p "$tree/drivers"

for driver in "$@"; do
mkdir -p "$tree/card$index/device" "$tree/drivers/$driver"
ln -s "../../drivers/$driver" "$tree/card$index/device/driver"
# Connectors hang off the same directory and must not be counted as cards.
mkdir -p "$tree/card$index-LVDS-1" "$tree/card$index-DVI-I-1"
(( ++index ))
done

echo "$tree"
}

supports_nightlight() {
OMARCHY_DRM_PATH="$1" "$BASH" "$hw_nightlight"
}

# Pre-GCN AMD: aquamarine's legacy DRM path never submits a CTM, so hyprsunset
# is a silent no-op here.
! supports_nightlight "$(make_drm_tree radeon)" ||
fail "a radeon-only machine reports no nightlight support"
pass "radeon-only hardware reports no nightlight support"

# Everything atomic must keep the feature.
supports_nightlight "$(make_drm_tree amdgpu)" ||
fail "an amdgpu machine still supports nightlight"
supports_nightlight "$(make_drm_tree i915)" ||
fail "an Intel machine still supports nightlight"
pass "atomic drivers keep nightlight"

# Conservative on mixed hardware: the display may be driven by the other GPU,
# and hiding a working feature is worse than leaving a broken one visible.
supports_nightlight "$(make_drm_tree radeon i915)" ||
fail "a machine with both radeon and an atomic GPU keeps nightlight"
pass "mixed radeon plus atomic hardware keeps nightlight"

# Nothing to go on is not evidence of breakage.
supports_nightlight "$(make_drm_tree)" ||
fail "a tree with no DRM cards does not claim nightlight is broken"
pass "an empty DRM tree keeps nightlight"

# The guard has to be wired up in both places, or the silent no-op comes back.
grep -Fq '"when":"omarchy-hw-nightlight"' "$menu" ||
fail "the menu hides the Nightlight row on hardware that cannot do it"
grep -Fq 'if ! omarchy-hw-nightlight; then' "$toggle" ||
fail "omarchy-toggle-nightlight refuses instead of reporting a false success"
pass "the nightlight guard is wired into the menu and the toggle"
36 changes: 35 additions & 1 deletion test/shell.d/nightlight-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,28 @@ cat >"$TMPDIR/bin/omarchy-shell" <<'SH'
printf '%s\n' "$*" >>"$OMARCHY_SHELL_LOG"
SH

chmod +x "$TMPDIR/bin/hyprctl" "$TMPDIR/bin/pgrep" "$TMPDIR/bin/omarchy-shell"
# Hardware that cannot apply a CTM is the interesting second case, so make the
# capability probe switchable rather than assuming it always answers yes.
cat >"$TMPDIR/bin/omarchy-hw-nightlight" <<'SH'
#!/bin/bash
exit "${NIGHTLIGHT_SUPPORTED:-0}"
SH

cat >"$TMPDIR/bin/omarchy-notification-send" <<'SH'
#!/bin/bash
printf '%s\n' "$*" >>"$NOTIFICATION_LOG"
SH

NOTIFICATIONS="$TMPDIR/notifications"

chmod +x "$TMPDIR/bin/hyprctl" "$TMPDIR/bin/pgrep" "$TMPDIR/bin/omarchy-shell" \
"$TMPDIR/bin/omarchy-hw-nightlight" "$TMPDIR/bin/omarchy-notification-send"

nightlight_cli() {
PATH="$TMPDIR/bin:$PATH" \
HYPRSUNSET_STATE="$STATE" \
OMARCHY_SHELL_LOG="$SHELL_LOG" \
NOTIFICATION_LOG="$NOTIFICATIONS" \
"$ROOT/bin/omarchy-toggle-nightlight" "$@"
}

Expand Down Expand Up @@ -90,3 +106,21 @@ if rg -q 'omarchy.indicators' "$ROOT/bin/omarchy-toggle-nightlight"; then
fail "nightlight toggle leaves indicator refresh to the nightlight service"
fi
pass "nightlight toggle leaves indicator refresh to the nightlight service"

# On a display pipeline with no CTM support hyprsunset still answers "ok" and
# echoes the temperature back, so the toggle has to refuse up front rather than
# run its retry loop and claim a success the panel never showed.
printf '6500\n' >"$STATE"
: >"$NOTIFICATIONS"

NIGHTLIGHT_SUPPORTED=1 nightlight_cli >/dev/null 2>&1 &&
fail "nightlight toggle exits non-zero on hardware that cannot apply a temperature"
[[ $(<"$STATE") == 6500 ]] ||
fail "nightlight toggle leaves the temperature alone on unsupported hardware"
grep -Fq 'Nightlight unavailable' "$NOTIFICATIONS" ||
fail "nightlight toggle tells the user why it did nothing"
pass "nightlight toggle refuses on hardware that cannot apply a temperature"

[[ $(NIGHTLIGHT_SUPPORTED=1 nightlight_status 4000 | jq -r .enabled) == "false" ]] ||
fail "nightlight status reports disabled on unsupported hardware"
pass "nightlight status reports disabled on unsupported hardware"
87 changes: 87 additions & 0 deletions test/shell.d/vulkan-hardware-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/bin/bash

set -euo pipefail

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

vulkan="$ROOT/install/hardware/vulkan.sh"

test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT

stub_bin="$test_tmp/bin"
mkdir -p "$stub_bin"

# lspci only has to name the vendor on a VGA line; the driver question is
# answered from sysfs, which is what this script actually gates on.
cat >"$stub_bin/lspci" <<'SH'
#!/bin/bash
echo "01:00.0 VGA compatible controller: ${STUB_GPU_VENDOR:-Advanced Micro Devices, Inc. [AMD/ATI]} RV610"
SH
chmod +x "$stub_bin/lspci"

# A GPU claimed by $1 ("radeon" or "amdgpu"), shaped like /sys/bus/pci/devices.
make_pci_tree() {
local driver=$1
local vendor=${2:-0x1002}
local tree="$test_tmp/pci-$driver-$RANDOM"

mkdir -p "$tree/0000:01:00.0" "$tree/drivers/$driver"
echo "$vendor" >"$tree/0000:01:00.0/vendor"
echo "0x030000" >"$tree/0000:01:00.0/class"
ln -s "../drivers/$driver" "$tree/0000:01:00.0/driver"

echo "$tree"
}

# Sourcing vulkan.sh with omarchy-pkg-add stubbed records what it would install.
# The packages go to a file rather than stdout: the script also explains itself
# on stdout, and those messages name the very packages being asserted on.
installed_packages() {
local pci_path=$1
local calls="$test_tmp/calls-$RANDOM"

# "$BASH", not "bash": vulkan.sh needs an associative array, and a stray
# bash 3.2 earlier in PATH would silently drop the AMD entry instead.
PATH="$stub_bin:$PATH" \
OMARCHY_PCI_DEVICES_PATH="$pci_path" \
OMARCHY_TEST_CALLS="$calls" \
"$BASH" -c '
omarchy-pkg-add() { printf "%s\n" "$@" >>"$OMARCHY_TEST_CALLS"; }
source "$1"
' _ "$vulkan" >/dev/null 2>&1

[[ -f $calls ]] && cat "$calls" || true
}

# Pre-GCN: radeon-driven AMD GPUs cannot be used by RADV, and the ICD that
# vulkan-radeon drops in would otherwise make omarchy-hw-vulkan answer yes.
pre_gcn=$(make_pci_tree radeon)
result=$(installed_packages "$pre_gcn")
[[ $result != *vulkan-radeon* ]] ||
fail "vulkan-radeon is skipped on a pre-GCN AMD GPU bound to radeon (got: $result)"
pass "pre-GCN AMD GPUs on the radeon driver do not get vulkan-radeon"

# GCN and newer still must, or every supported AMD machine loses Vulkan.
gcn=$(make_pci_tree amdgpu)
result=$(installed_packages "$gcn")
[[ $result == *vulkan-radeon* ]] ||
fail "vulkan-radeon is still installed for an amdgpu-bound GPU (got: $result)"
pass "AMD GPUs on the amdgpu driver still get vulkan-radeon"

# A GPU with no bound driver (an installer booted with nomodeset) says nothing
# about the installed system, so it keeps vulkan-radeon as before.
unbound="$test_tmp/pci-unbound"
mkdir -p "$unbound/0000:01:00.0"
echo "0x1002" >"$unbound/0000:01:00.0/vendor"
echo "0x030000" >"$unbound/0000:01:00.0/class"
result=$(installed_packages "$unbound")
[[ $result == *vulkan-radeon* ]] ||
fail "vulkan-radeon is still installed when no driver is bound to the AMD GPU (got: $result)"
pass "an AMD GPU with no bound driver still gets vulkan-radeon"

# The AMD guard must not touch the other vendors' drivers.
result=$(STUB_GPU_VENDOR="Intel Corporation" installed_packages "$pre_gcn")
[[ $result == *vulkan-intel* ]] ||
fail "Intel GPUs still get vulkan-intel regardless of the AMD guard (got: $result)"
pass "the AMD guard leaves vulkan-intel selection alone"