From 1d4815f3aa6d91458f74312eff05769054b9c5e1 Mon Sep 17 00:00:00 2001 From: Andy Holst Date: Wed, 16 Sep 2026 02:06:03 +0200 Subject: [PATCH] Add MSI Titan GT77HX driver wiring and Cooler Boost with sustained-temp trigger Wire MSI laptop drivers (Killer E3000/RTL8125B, msi-ec, CoolerControl, nvidia-settings) into the hardware install path behind omarchy-hw-msi detection, and add a Titan GT77HX watcher that enables the EC Cooler Boost flag only after CPU/GPU stay at or above 85C for more than 30 seconds, turning it off again at 70C. Matches #11583: Extreme 100% at 85C and Silent staying low until 70C. --- bin/omarchy-hw-msi | 30 +++ bin/omarchy-hw-msi-titan | 12 ++ bin/omarchy-msi-cooler-boost-watch | 143 ++++++++++++++ .../omarchy-msi-cooler-boost-watch.service | 18 ++ install/hardware/all.sh | 1 + install/hardware/msi.sh | 118 ++++++++++++ test/shell.d/hw-msi-test.sh | 181 ++++++++++++++++++ test/shell.d/msi-cooler-boost-test.sh | 167 ++++++++++++++++ 8 files changed, 670 insertions(+) create mode 100755 bin/omarchy-hw-msi create mode 100755 bin/omarchy-hw-msi-titan create mode 100755 bin/omarchy-msi-cooler-boost-watch create mode 100644 default/systemd/system/omarchy-msi-cooler-boost-watch.service create mode 100644 install/hardware/msi.sh create mode 100755 test/shell.d/hw-msi-test.sh create mode 100755 test/shell.d/msi-cooler-boost-test.sh diff --git a/bin/omarchy-hw-msi b/bin/omarchy-hw-msi new file mode 100755 index 00000000000..affa3f6976a --- /dev/null +++ b/bin/omarchy-hw-msi @@ -0,0 +1,30 @@ +#!/bin/bash + +# omarchy:summary=Detect whether the computer is an MSI laptop. + +# Read DMI IDs via OMARCHY_DMI_ID_PATH for test stubs, falling back to /sys. +dmi_path="${OMARCHY_DMI_ID_PATH:-/sys/class/dmi/id}" + +# MSI laptops report "Micro-Star International Co., Ltd." as sys_vendor. Some +# MSI desktop boards also report this, so we additionally check for laptop +# product families in either product_family or product_name. +sys_vendor=$(cat "$dmi_path/sys_vendor" 2>/dev/null) +[[ $sys_vendor == "Micro-Star International Co., Ltd." ]] || exit 1 + +# Combine both fields for a single match — laptops have the model line in +# product_family (e.g. "Titan GT77HX 13VI") and optionally the short model +# in product_name (e.g. "MSI Titan GT77HX 13VI"). Desktop boards usually +# only have the board name in product_name (e.g. "MEG X670E ACE"). +combined="$(cat "$dmi_path/product_family" 2>/dev/null) $(cat "$dmi_path/product_name" 2>/dev/null)" + +# All known MSI laptop families as of 2026. Katana, Cyborg, Pulse, Bravo, +# Alpha cover budget/mid-tier; Prestige covers content-creation ultraportables. +for family in "Titan" "Stealth" "Raider" "Vector" "Crosshair" "Summit" \ + "Creator" "Modern" "Katana" "Cyborg" "Pulse" "Bravo" \ + "Alpha" "Prestige"; do + if grep -qi "$family" <<<"$combined"; then + exit 0 + fi +done + +exit 1 diff --git a/bin/omarchy-hw-msi-titan b/bin/omarchy-hw-msi-titan new file mode 100755 index 00000000000..5c2437da4e2 --- /dev/null +++ b/bin/omarchy-hw-msi-titan @@ -0,0 +1,12 @@ +#!/bin/bash + +# omarchy:summary=Detect whether the computer is an MSI Titan GT77HX +# omarchy:hidden=true + +dmi_path="${OMARCHY_DMI_ID_PATH:-/sys/class/dmi/id}" + +sys_vendor=$(cat "$dmi_path/sys_vendor" 2>/dev/null) +[[ $sys_vendor == "Micro-Star International Co., Ltd." ]] || exit 1 + +combined="$(cat "$dmi_path/product_family" 2>/dev/null) $(cat "$dmi_path/product_name" 2>/dev/null)" +grep -qi "Titan GT77HX" <<<"$combined" diff --git a/bin/omarchy-msi-cooler-boost-watch b/bin/omarchy-msi-cooler-boost-watch new file mode 100755 index 00000000000..227e9b67db5 --- /dev/null +++ b/bin/omarchy-msi-cooler-boost-watch @@ -0,0 +1,143 @@ +#!/bin/bash + +# omarchy:summary=Turn MSI Cooler Boost on when CPU/GPU hit the high-temp threshold +# omarchy:args=[--once|--decide ] +# omarchy:hidden=true +# omarchy:requires-sudo=true + +# Cooler Boost is a binary EC flag. Silent/auto fan modes do not enable it at a +# temperature, so Titan GT77HX units stay on the quiet curve until this watcher +# flips the flag. Default: on at 85C after 30s sustained, off at 70C (hysteresis). +# Matches #11583 Extreme 100% at 85C and Silent staying low until 70C. + +set -euo pipefail + +ON_AT=${MSI_COOLER_BOOST_ON_AT:-85} +OFF_AT=${MSI_COOLER_BOOST_OFF_AT:-70} +SUSTAIN=${MSI_COOLER_BOOST_SUSTAIN:-30} +INTERVAL=${MSI_COOLER_BOOST_INTERVAL:-5} +EC_PATH=${OMARCHY_MSI_EC_PATH:-/sys/devices/platform/msi-ec} + +desired_cooler_boost() { + local state=$1 cpu=$2 gpu=$3 on_at=$4 off_at=$5 + local hot=$cpu + + [[ $cpu =~ ^[0-9]+$ ]] || cpu=0 + [[ $gpu =~ ^[0-9]+$ ]] || gpu=0 + hot=$cpu + if (( gpu > hot )); then + hot=$gpu + fi + + if [[ $state != on ]] && (( hot >= on_at )); then + printf '%s\n' on + elif [[ $state == on ]] && (( hot <= off_at )); then + printf '%s\n' off + elif [[ $state == on ]]; then + printf '%s\n' on + else + printf '%s\n' off + fi +} + +read_temp() { + local file=$1 + local value=0 + + if [[ -r $file ]]; then + value=$(< "$file") + fi + if [[ ! $value =~ ^[0-9]+$ ]]; then + value=0 + fi + printf '%s\n' "$value" +} + +read_state() { + local file=$1 + local value=off + + if [[ -r $file ]]; then + value=$(tr -d '[:space:]' < "$file") + fi + if [[ $value != on ]]; then + value=off + fi + printf '%s\n' "$value" +} + +if [[ ${1:-} == --decide ]]; then + desired_cooler_boost "${2:-off}" "${3:-0}" "${4:-0}" "$ON_AT" "$OFF_AT" + exit 0 +fi + +if ! omarchy-hw-msi-titan; then + echo "omarchy-msi-cooler-boost-watch: not an MSI Titan GT77HX; exiting" + exit 0 +fi + +boost=$EC_PATH/cooler_boost +cpu_temp=$EC_PATH/cpu/realtime_temperature +gpu_temp=$EC_PATH/gpu/realtime_temperature + +if [[ ! -e $boost ]]; then + echo "omarchy-msi-cooler-boost-watch: $boost missing (msi-ec not loaded)" >&2 + exit 1 +fi + +apply_once() { + local state cpu gpu want hot sustained_file count + + state=$(read_state "$boost") + cpu=$(read_temp "$cpu_temp") + gpu=$(read_temp "$gpu_temp") + sustained_file="${OMARCHY_MSI_SUSTAIN_FILE:-/run/omarchy-msi-cooler-boost.sustain}" + + hot=$cpu + [[ $cpu =~ ^[0-9]+$ ]] || cpu=0 + [[ $gpu =~ ^[0-9]+$ ]] || gpu=0 + if (( gpu > hot )); then + hot=$gpu + fi + + if [[ $state == off ]] && (( hot >= ON_AT )); then + # Track consecutive high-temp readings; flip only after SUSTAIN seconds + if [[ -s "$sustained_file" ]]; then + count=$(< "$sustained_file") + else + count=0 + fi + count=$(( count + 1 )) + printf '%s\n' "$count" > "$sustained_file" + + if (( count * INTERVAL > SUSTAIN )); then + want=on + else + want=off + fi + else + # Reset sustain tracking when temp drops or boost is already on + rm -f "$sustained_file" + want=$(desired_cooler_boost "$state" "$cpu" "$gpu" "$ON_AT" "$OFF_AT") + fi + + if [[ $want != "$state" ]]; then + if printf '%s\n' "$want" > "$boost"; then + echo "cooler_boost $state -> $want (cpu=${cpu}C gpu=${gpu}C)" + else + echo "omarchy-msi-cooler-boost-watch: cannot write $boost" >&2 + return 1 + fi + fi +} + +if [[ ${1:-} == --once ]]; then + apply_once + exit $? +fi + +echo "omarchy-msi-cooler-boost-watch: on>=${ON_AT}C off<=${OFF_AT}C sustain=${SUSTAIN}s interval=${INTERVAL}s" +while true; do + apply_once || true + sleep "$INTERVAL" +done diff --git a/default/systemd/system/omarchy-msi-cooler-boost-watch.service b/default/systemd/system/omarchy-msi-cooler-boost-watch.service new file mode 100644 index 00000000000..73b41b2fb40 --- /dev/null +++ b/default/systemd/system/omarchy-msi-cooler-boost-watch.service @@ -0,0 +1,18 @@ +[Unit] +Description=Enable MSI Cooler Boost when CPU/GPU hit 85C (sustained 30s) +Documentation=https://github.com/omacom/omarchy/issues/11583 +After=multi-user.target +ConditionPathExists=/sys/devices/platform/msi-ec/cooler_boost + +[Service] +Type=simple +ExecStart=/usr/bin/omarchy-msi-cooler-boost-watch +Restart=on-failure +RestartSec=10 +Environment=MSI_COOLER_BOOST_ON_AT=85 +Environment=MSI_COOLER_BOOST_OFF_AT=70 +Environment=MSI_COOLER_BOOST_SUSTAIN=30 +Environment=MSI_COOLER_BOOST_INTERVAL=5 + +[Install] +WantedBy=multi-user.target diff --git a/install/hardware/all.sh b/install/hardware/all.sh index 12add110c51..90863afdecb 100644 --- a/install/hardware/all.sh +++ b/install/hardware/all.sh @@ -1,5 +1,6 @@ run_logged "$OMARCHY_INSTALL/hardware/asus-rog.sh" run_logged "$OMARCHY_INSTALL/hardware/framework16.sh" +run_logged "$OMARCHY_INSTALL/hardware/msi.sh" run_logged "$OMARCHY_INSTALL/hardware/dell-xps-touchpad-haptics.sh" run_logged "$OMARCHY_INSTALL/hardware/surface.sh" diff --git a/install/hardware/msi.sh b/install/hardware/msi.sh new file mode 100644 index 00000000000..1bb4925d3ac --- /dev/null +++ b/install/hardware/msi.sh @@ -0,0 +1,118 @@ +#!/bin/bash +set -euo pipefail + +# Install and configure MSI-specific drivers for Titan/Stealth/Raider/etc laptops. +# Requires omarchy-hw-msi detection to gate execution. + +if omarchy-hw-msi; then + # Killer E-series Ethernet (RTL8125B) — blacklist r8169 for stability. + # Use sysfs reads instead of lspci (see PR #11259) to avoid waking + # suspended GPUs on hybrid laptops. + pci_devices_path="${OMARCHY_PCI_DEVICES_PATH:-/sys/bus/pci/devices}" + rtl8125b_found=false + for device in "$pci_devices_path"/*; do + [[ $(< "$device/vendor" 2>/dev/null) == "0x10ec" ]] || continue + device_id=$(< "$device/device" 2>/dev/null) + if [[ $device_id == "0x8125" ]]; then + rtl8125b_found=true + break + fi + done + + if $rtl8125b_found; then + if ! omarchy-pkg-present r8125-dkms 2>/dev/null; then + omarchy-pkg-add r8125-dkms || echo "WARNING: Failed to install r8125-dkms" >&2 + fi + + # Blacklist r8169 so r8125 loads exclusively. Only do this if r8125 + # is actually loaded — otherwise we risk leaving the system with no + # network driver until the next reboot. + if lsmod | grep -q r8125; then + sudo install -Dm644 /dev/stdin /etc/modprobe.d/r8169.conf <<'EOF' +blacklist r8169 +EOF + sudo install -Dm644 /dev/stdin /etc/modules-load.d/r8125.conf <<'EOF' +r8125 +EOF + limine-mkinitcpio 2>/dev/null || sudo mkinitcpio -P + else + echo "WARNING: r8125 module not loaded — skipping blacklist to avoid network loss" >&2 + fi + fi + + # MSI Embedded Controller — required for fan modes, shift modes, + # battery thresholds, and other laptop-specific features. + if ! lsmod | grep -q msi_ec; then + if ! omarchy-pkg-present msi-ec-dkms-git 2>/dev/null; then + omarchy-pkg-add msi-ec-dkms-git || echo "WARNING: Failed to install msi-ec-dkms-git" >&2 + fi + fi + + # Set battery charge thresholds for battery health. + # Start charging at 60%, stop at 80% to preserve battery lifespan. + # Only set if the system is currently on battery (not AC power). + if [[ -f /sys/class/power_supply/BAT1/charge_control_start_threshold ]]; then + current_status=$(cat /sys/class/power_supply/BAT1/status 2>/dev/null) + if [[ $current_status == "Discharging" ]]; then + echo 60 | sudo tee /sys/class/power_supply/BAT1/charge_control_start_threshold >/dev/null + echo 80 | sudo tee /sys/class/power_supply/BAT1/charge_control_end_threshold >/dev/null + fi + fi + + # CoolerControl for fan curves + thermal management. Disable thermald + # (generic DPTF) to avoid conflicts — coolercontrold talks to the EC + # directly and knows the MSI fan table. + if ! omarchy-pkg-present coolercontrol 2>/dev/null; then + omarchy-pkg-add coolercontrol || echo "WARNING: Failed to install coolercontrol" >&2 + fi + + if systemctl is-enabled thermald.service &>/dev/null; then + sudo systemctl disable --now thermald.service || echo "WARNING: Failed to disable thermald" >&2 + fi + + if ! systemctl is-active coolercontrold.service &>/dev/null; then + sudo systemctl enable --now coolercontrold.service || echo "WARNING: Failed to enable coolercontrold" >&2 + fi + + # CoolerControl cannot PWM these EC fans (RPM-only). Cooler Boost is the + # binary "fast fans" flag and is not turned on by silent/auto curves. On the + # Titan GT77HX, enable a watcher that flips it at 85C once sustained for 30s + # (off again at 70C). + if omarchy-hw-msi-titan; then + sudo install -Dm644 "$OMARCHY_PATH/default/systemd/system/omarchy-msi-cooler-boost-watch.service" \ + /etc/systemd/system/omarchy-msi-cooler-boost-watch.service + sudo systemctl daemon-reload + sudo systemctl enable --now omarchy-msi-cooler-boost-watch.service || \ + echo "WARNING: Failed to enable omarchy-msi-cooler-boost-watch" >&2 + fi + + # NVIDIA-settings for GPU monitoring and tuning on MSI dGPU laptops + if omarchy-hw-nvidia; then + if ! omarchy-pkg-present nvidia-settings 2>/dev/null; then + omarchy-pkg-add nvidia-settings || echo "WARNING: Failed to install nvidia-settings" >&2 + fi + + # X11 config for DPMS and backlight control via nvidia-settings + sudo install -Dm644 /dev/stdin /etc/X11/xorg.conf.d/10-nvidia-dpms.conf <<'EOF' +Section "Device" + Identifier "NVIDIA Card" + Driver "nvidia" + Option "RegistryDwords" "EnableBrightnessControl=1" +EndSection + +Section "Screen" + Identifier "Screen0" + Device "NVIDIA Card" + Option "AllowNVIDIAGPUScreens" "on" +EndSection +EOF + fi + + # Early-load msi_ec module via mkinitcpio if it's currently loaded + if lsmod | grep -q msi_ec; then + sudo install -Dm644 /dev/stdin /etc/mkinitcpio.conf.d/msi.conf <<'EOF' +MODULES+=(msi_ec) +EOF + limine-mkinitcpio 2>/dev/null || sudo mkinitcpio -P + fi +fi diff --git a/test/shell.d/hw-msi-test.sh b/test/shell.d/hw-msi-test.sh new file mode 100755 index 00000000000..bd9d52aef1a --- /dev/null +++ b/test/shell.d/hw-msi-test.sh @@ -0,0 +1,181 @@ +#!/bin/bash + +# Test omarchy-hw-msi detection via OMARCHY_DMI_ID_PATH stubs. +# The detector reads /sys/class/dmi/id/{sys_vendor,product_family,product_name} +# to identify MSI laptops. We point OMARCHY_DMI_ID_PATH at a fixture tree +# to exercise vendor string, product family, and laptop-vs-desktop filtering. + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +tmp_dir=$(mktemp -d) +trap 'rm -rf "$tmp_dir"' EXIT + +write_dmi_ids() { + mkdir -p "$tmp_dir/dmi/id" + printf '%s' "$1" >"$tmp_dir/dmi/id/sys_vendor" + printf '%s' "$2" >"$tmp_dir/dmi/id/product_family" + printf '%s' "$3" >"$tmp_dir/dmi/id/product_name" +} + +hw_msi() { + OMARCHY_DMI_ID_PATH="$tmp_dir/dmi/id" "$ROOT/bin/omarchy-hw-msi" +} + +assert_msi() { + local description="$1" expected="$2" + local actual=no + hw_msi && actual=yes + [[ $actual == "$expected" ]] || fail "$description" "expected $expected, got $actual" + pass "$description" +} + +# === MSI laptops — should detect (all 14 families) === + +write_dmi_ids "Micro-Star International Co., Ltd." "Titan GT77HX 13VI" "Titan GT77HX 13VI" +assert_msi "Titan GT77HX 13VI detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Stealth 16 Studio A13V" "Stealth 16 Studio A13V" +assert_msi "Stealth 16 Studio detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Raider GE78HX 13V" "Raider GE78HX 13V" +assert_msi "Raider GE78HX detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Vector 17 HX B14V" "Vector 17 HX B14V" +assert_msi "Vector 17 HX detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Crosshair 17 HX D14V" "Crosshair 17 HX D14V" +assert_msi "Crosshair 17 HX detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Summit E16 Flip Evo A13MT" "Summit E16 Flip Evo A13MT" +assert_msi "Summit E16 Flip detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Creator Z17 HX Studio A13V" "Creator Z17 HX Studio A13V" +assert_msi "Creator Z17 detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Modern 15 H B13M" "Modern 15 H B13M" +assert_msi "Modern 15 detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Katana 17 B13V" "Katana 17 B13V" +assert_msi "Katana 17 detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Cyborg 15 A13VF" "Cyborg 15 A13VF" +assert_msi "Cyborg 15 detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Pulse 17 B13V" "Pulse 17 B13V" +assert_msi "Pulse 17 detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Bravo 15 B7E" "Bravo 15 B7E" +assert_msi "Bravo 15 detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Alpha 17 B5E" "Alpha 17 B5E" +assert_msi "Alpha 17 detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Prestige 16 Evo B13M" "Prestige 16 Evo B13M" +assert_msi "Prestige 16 Evo detected" yes + +# === Edge cases — product_family only === + +write_dmi_ids "Micro-Star International Co., Ltd." "Titan GT77HX 13VI" "" +assert_msi "Titan detected (family only)" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "" "Titan GT77HX 13VI" +assert_msi "Titan detected (name only)" yes + +# === MSI desktop — should NOT detect === + +write_dmi_ids "Micro-Star International Co., Ltd." "MEG X670E ACE" "MEG X670E ACE" +assert_msi "MSI desktop board rejected" no + +write_dmi_ids "Micro-Star International Co., Ltd." "MAG B760M MORTAR WIFI" "MAG B760M MORTAR WIFI" +assert_msi "MSI motherboard rejected" no + +write_dmi_ids "Micro-Star International Co., Ltd." "" "" +assert_msi "MSI with empty DMI fields rejected" no + +# === Other vendors — should NOT detect === + +write_dmi_ids "ASUSTeK COMPUTER INC." "ROG Strix G733ZW" "ROG Strix G733ZW" +assert_msi "ASUS ROG rejected" no + +write_dmi_ids "Dell Inc." "XPS 15 9530" "XPS 15 9530" +assert_msi "Dell XPS rejected" no + +write_dmi_ids "LENOVO" "Legion 5 16IRX9" "Legion 5 16IRX9" +assert_msi "Lenovo Legion rejected" no + +write_dmi_ids "HP" "Omen 16" "Omen 16" +assert_msi "HP Omen rejected" no + +write_dmi_ids "Acer Incorporated" "Predator Helios 16" "Predator Helios 16" +assert_msi "Acer Predator rejected" no + +write_dmi_ids "Framework" "Laptop 16" "Laptop 16" +assert_msi "Framework 16 rejected" no + +# === Completely empty === + +write_dmi_ids "" "" "" +assert_msi "Empty DMI IDs rejected" no + +# === msi.sh install script tests === + +pass "msi.sh exists" +[[ -f "$ROOT/install/hardware/msi.sh" ]] || fail "msi.sh missing" + +pass "msi.sh references r8125-dkms" +grep -q "r8125-dkms" "$ROOT/install/hardware/msi.sh" || fail "r8125-dkms not referenced" + +pass "msi.sh references msi-ec-dkms-git" +grep -q "msi-ec-dkms-git" "$ROOT/install/hardware/msi.sh" || fail "msi-ec-dkms-git not referenced" + +pass "msi.sh references coolercontrol" +grep -q "coolercontrol" "$ROOT/install/hardware/msi.sh" || fail "coolercontrol not referenced" + +pass "msi.sh references thermald" +grep -q "thermald" "$ROOT/install/hardware/msi.sh" || fail "thermald not referenced" + +pass "msi.sh references nvidia-settings" +grep -q "nvidia-settings" "$ROOT/install/hardware/msi.sh" || fail "nvidia-settings not referenced" + +pass "msi.sh sets battery charge thresholds" +grep -q "charge_control" "$ROOT/install/hardware/msi.sh" || fail "battery thresholds missing" + +pass "msi.sh guards r8169 blacklist with r8125 loaded check" +grep -q "lsmod.*r8125" "$ROOT/install/hardware/msi.sh" || fail "r8125 guard missing" + +pass "msi.sh uses install -Dm644 for atomic writes" +grep -q "install -Dm644" "$ROOT/install/hardware/msi.sh" || fail "install -Dm644 missing" + +pass "msi.sh uses tee for sysfs writes" +grep -q "tee" "$ROOT/install/hardware/msi.sh" || fail "tee for sysfs missing" + +pass "msi.sh has set -euo pipefail" +grep -q "set -euo pipefail" "$ROOT/install/hardware/msi.sh" || fail "set -euo pipefail missing" + +pass "msi.sh does NOT use local keyword" +! grep -qw "local" "$ROOT/install/hardware/msi.sh" || fail "local keyword found" + +pass "msi.sh installs Cooler Boost watcher on Titan" +grep -q "omarchy-hw-msi-titan" "$ROOT/install/hardware/msi.sh" || fail "titan gate missing" + +pass "msi.sh references cooler-boost unit" +grep -q "omarchy-msi-cooler-boost-watch.service" "$ROOT/install/hardware/msi.sh" || fail "cooler-boost unit missing" + +# === systemd unit tests === + +pass "systemd unit exists" +[[ -f "$ROOT/default/systemd/system/omarchy-msi-cooler-boost-watch.service" ]] || fail "systemd unit missing" + +pass "unit on-threshold is 85" +grep -q "MSI_COOLER_BOOST_ON_AT=85" "$ROOT/default/systemd/system/omarchy-msi-cooler-boost-watch.service" || fail "ON_AT=85 not found" + +pass "unit off-threshold is 70" +grep -q "MSI_COOLER_BOOST_OFF_AT=70" "$ROOT/default/systemd/system/omarchy-msi-cooler-boost-watch.service" || fail "OFF_AT=70 not found" + +pass "unit sustain is 30" +grep -q "MSI_COOLER_BOOST_SUSTAIN=30" "$ROOT/default/systemd/system/omarchy-msi-cooler-boost-watch.service" || fail "SUSTAIN=30 not found" + +pass "unit interval is 5" +grep -q "MSI_COOLER_BOOST_INTERVAL=5" "$ROOT/default/systemd/system/omarchy-msi-cooler-boost-watch.service" || fail "INTERVAL=5 not found" diff --git a/test/shell.d/msi-cooler-boost-test.sh b/test/shell.d/msi-cooler-boost-test.sh new file mode 100755 index 00000000000..dc4d9d5682b --- /dev/null +++ b/test/shell.d/msi-cooler-boost-test.sh @@ -0,0 +1,167 @@ +#!/bin/bash + +# Titan GT77HX Cooler Boost watcher: hardware gate, 85C on / 70C off hysteresis, +# 30s sustained high-temp before enable, and --once against a stub msi-ec sysfs tree. + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +export PATH="$ROOT/bin:$PATH" + +tmp_dir=$(mktemp -d) +trap 'rm -rf "$tmp_dir"' EXIT + +write_dmi_ids() { + mkdir -p "$tmp_dir/dmi/id" + printf '%s' "$1" >"$tmp_dir/dmi/id/sys_vendor" + printf '%s' "$2" >"$tmp_dir/dmi/id/product_family" + printf '%s' "$3" >"$tmp_dir/dmi/id/product_name" +} + +hw_titan() { + OMARCHY_DMI_ID_PATH="$tmp_dir/dmi/id" "$ROOT/bin/omarchy-hw-msi-titan" +} + +assert_titan() { + local description="$1" expected="$2" + local actual=no + hw_titan && actual=yes + [[ $actual == "$expected" ]] || fail "$description" "expected $expected, got $actual" + pass "$description" +} + +assert_decide() { + local description="$1" expected="$2" + shift 2 + local actual + actual=$("$ROOT/bin/omarchy-msi-cooler-boost-watch" --decide "$@") + [[ $actual == "$expected" ]] || fail "$description" "expected $expected, got $actual" + pass "$description" +} + +write_dmi_ids "Micro-Star International Co., Ltd." "Titan GT77HX 13VI" "Titan GT77HX 13VI" +assert_titan "Titan GT77HX 13VI detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Titan GT77HX 13VH" "Titan GT77HX 13VH" +assert_titan "Titan GT77HX 13VH detected" yes + +write_dmi_ids "Micro-Star International Co., Ltd." "Stealth 16 Studio A13V" "Stealth 16 Studio A13V" +assert_titan "Stealth is not a Titan" no + +write_dmi_ids "Micro-Star International Co., Ltd." "MEG X670E ACE" "MEG X670E ACE" +assert_titan "MSI desktop rejected" no + +write_dmi_ids "ASUSTeK COMPUTER INC." "ROG Strix G733ZW" "ROG Strix G733ZW" +assert_titan "ASUS rejected" no + +# Hysteresis decision logic (no sustain): off until 85, stays on until 70 +assert_decide "off at 84C stays off" off off 84 50 +assert_decide "CPU 85C turns boost on" on off 85 50 +assert_decide "GPU 85C turns boost on" on off 50 85 +assert_decide "80C does not enable boost" off off 80 50 +assert_decide "on at 71C stays on" on on 71 50 +assert_decide "on at 70C turns boost off" off on 70 50 +assert_decide "off at 70C stays off" off off 70 50 +assert_decide "garbage temps stay off" off off x y + +# --once: sustain requires 30s at >=85C (6 polls at 5s interval) +write_dmi_ids "Micro-Star International Co., Ltd." "Titan GT77HX 13VI" "Titan GT77HX 13VI" +mkdir -p "$tmp_dir/ec/cpu" "$tmp_dir/ec/gpu" +printf 'off\n' >"$tmp_dir/ec/cooler_boost" +printf '86\n' >"$tmp_dir/ec/cpu/realtime_temperature" +printf '48\n' >"$tmp_dir/ec/gpu/realtime_temperature" +export OMARCHY_MSI_SUSTAIN_FILE="$tmp_dir/sustain" +rm -f "$OMARCHY_MSI_SUSTAIN_FILE" + +# 6 consecutive polls at 86C (30s accumulated) — should NOT turn on yet (needs >30s) +for i in 1 2 3 4 5 6; do + OMARCHY_DMI_ID_PATH="$tmp_dir/dmi/id" OMARCHY_MSI_EC_PATH="$tmp_dir/ec" \ + "$ROOT/bin/omarchy-msi-cooler-boost-watch" --once + [[ $(tr -d '[:space:]' <"$tmp_dir/ec/cooler_boost") == off ]] || fail "poll $i/7: boost turned on too early at 86C" +done +pass "6 polls at 86C does not enable boost (sustain not reached at exactly 30s)" + +# 7th poll reaches 35s — should turn on +OMARCHY_DMI_ID_PATH="$tmp_dir/dmi/id" OMARCHY_MSI_EC_PATH="$tmp_dir/ec" \ + "$ROOT/bin/omarchy-msi-cooler-boost-watch" --once +[[ $(tr -d '[:space:]' <"$tmp_dir/ec/cooler_boost") == on ]] || fail "7th poll at 86C should enable boost" +pass "7th poll at 86C enables boost after >30s sustained" + +# Exactly 85C counts as "at or above" — 6 polls still off, 7th enables +printf 'off\n' >"$tmp_dir/ec/cooler_boost" +printf '85\n' >"$tmp_dir/ec/cpu/realtime_temperature" +rm -f "$OMARCHY_MSI_SUSTAIN_FILE" +for i in 1 2 3 4 5 6; do + OMARCHY_DMI_ID_PATH="$tmp_dir/dmi/id" OMARCHY_MSI_EC_PATH="$tmp_dir/ec" \ + "$ROOT/bin/omarchy-msi-cooler-boost-watch" --once +done +[[ $(tr -d '[:space:]' <"$tmp_dir/ec/cooler_boost") == off ]] || fail "exactly 85C should not enable before the sustain window elapses" +OMARCHY_DMI_ID_PATH="$tmp_dir/dmi/id" OMARCHY_MSI_EC_PATH="$tmp_dir/ec" \ + "$ROOT/bin/omarchy-msi-cooler-boost-watch" --once +[[ $(tr -d '[:space:]' <"$tmp_dir/ec/cooler_boost") == on ]] || fail "exactly 85C should enable after the sustain window" +pass "exactly 85C counts toward the sustain window" + +# Drop below 70C — should turn off immediately +printf '69\n' >"$tmp_dir/ec/cpu/realtime_temperature" +OMARCHY_DMI_ID_PATH="$tmp_dir/dmi/id" OMARCHY_MSI_EC_PATH="$tmp_dir/ec" \ + "$ROOT/bin/omarchy-msi-cooler-boost-watch" --once +[[ $(tr -d '[:space:]' <"$tmp_dir/ec/cooler_boost") == off ]] || fail "boost should turn off at 69C" +pass "boost turns off immediately at 69C" + +# Temp drops before sustain reached — counter resets +printf 'off\n' >"$tmp_dir/ec/cooler_boost" +printf '86\n' >"$tmp_dir/ec/cpu/realtime_temperature" +rm -f "$OMARCHY_MSI_SUSTAIN_FILE" +# 3 polls at 86C (15s) +for i in 1 2 3; do + OMARCHY_DMI_ID_PATH="$tmp_dir/dmi/id" OMARCHY_MSI_EC_PATH="$tmp_dir/ec" \ + "$ROOT/bin/omarchy-msi-cooler-boost-watch" --once +done +[[ $(tr -d '[:space:]' <"$tmp_dir/ec/cooler_boost") == off ]] || fail "3 polls at 86C should not enable boost" +pass "3 polls at 86C does not enable boost" + +# Temp drops to 70C — counter resets +printf '70\n' >"$tmp_dir/ec/cpu/realtime_temperature" +OMARCHY_DMI_ID_PATH="$tmp_dir/dmi/id" OMARCHY_MSI_EC_PATH="$tmp_dir/ec" \ + "$ROOT/bin/omarchy-msi-cooler-boost-watch" --once +[[ ! -f "$OMARCHY_MSI_SUSTAIN_FILE" ]] || fail "sustain counter should reset when temp drops" +pass "sustain counter resets when temp drops below threshold" + +# 80C does not enable boost +printf 'off\n' >"$tmp_dir/ec/cooler_boost" +printf '80\n' >"$tmp_dir/ec/cpu/realtime_temperature" +OMARCHY_DMI_ID_PATH="$tmp_dir/dmi/id" OMARCHY_MSI_EC_PATH="$tmp_dir/ec" \ + "$ROOT/bin/omarchy-msi-cooler-boost-watch" --once +[[ $(tr -d '[:space:]' <"$tmp_dir/ec/cooler_boost") == off ]] || fail "80C does not enable boost" +pass "80C does not enable boost (threshold is 85)" + +# Non-Titan hardware exits cleanly without touching the EC +write_dmi_ids "ASUSTeK COMPUTER INC." "ROG Strix G733ZW" "ROG Strix G733ZW" +printf 'off\n' >"$tmp_dir/ec/cooler_boost" +OMARCHY_DMI_ID_PATH="$tmp_dir/dmi/id" OMARCHY_MSI_EC_PATH="$tmp_dir/ec" \ + "$ROOT/bin/omarchy-msi-cooler-boost-watch" --once +[[ $(tr -d '[:space:]' <"$tmp_dir/ec/cooler_boost") == off ]] || fail "non-Titan hardware must not touch the EC" +pass "non-Titan hardware exits cleanly without touching the EC" + +# systemd unit tests +[[ -f $ROOT/default/systemd/system/omarchy-msi-cooler-boost-watch.service ]] || fail "systemd unit missing" +pass "systemd unit exists" + +grep -q "MSI_COOLER_BOOST_ON_AT=85" "$ROOT/default/systemd/system/omarchy-msi-cooler-boost-watch.service" || \ + fail "unit on-threshold is not 85" +pass "unit on-threshold is 85" + +grep -q "MSI_COOLER_BOOST_OFF_AT=70" "$ROOT/default/systemd/system/omarchy-msi-cooler-boost-watch.service" || \ + fail "unit off-threshold is not 70" +pass "unit off-threshold is 70" + +grep -q "MSI_COOLER_BOOST_SUSTAIN=30" "$ROOT/default/systemd/system/omarchy-msi-cooler-boost-watch.service" || \ + fail "unit sustain is not 30" +pass "unit sustain is 30" + +grep -q "omarchy-hw-msi-titan" "$ROOT/install/hardware/msi.sh" || fail "msi.sh does not gate on titan" +pass "msi.sh enables watcher only on Titan" + +grep -q "omarchy-msi-cooler-boost-watch.service" "$ROOT/install/hardware/msi.sh" || fail "msi.sh does not install unit" +pass "msi.sh installs cooler-boost unit"