#!/usr/bin/env bash # System power profile, via the PowerProfiles D-Bus API. # # GNOME's Power panel offers Balanced / Performance / Power Saver; this is the # same daemon behind it. On Fedora 44 the implementation is tuned-ppd rather # than power-profiles-daemon, but it serves the same net.hadess.PowerProfiles # interface, which is why this talks to the interface rather than to either # binary -- powerprofilesctl is not even installed here. # # Setting a profile needs no privileges: the daemon accepts a property write # from the active session user. # # Usage: # panama-power-profile list -> {"profiles":[...],"active":"...","degraded":"..."} # panama-power-profile set # # PerformanceDegraded is reported because it is the one thing that makes the # choice a lie: a thermally throttled laptop reports "performance" while # behaving otherwise, and GNOME surfaces exactly this. It is an empty string # when nothing is wrong. set -uo pipefail readonly BUS_NAME=net.hadess.PowerProfiles readonly OBJECT=/net/hadess/PowerProfiles emit_error() { printf '{"profiles":[],"active":"","degraded":"","error":%s}\n' "$(jq -Rn --arg e "$1" '$e')" exit 0 } command -v busctl >/dev/null 2>&1 || emit_error 'busctl is not available' property() { busctl get-property "$BUS_NAME" "$OBJECT" "$BUS_NAME" "$1" 2>/dev/null } cmd_list() { # A machine with no power-profiles daemon is a normal state -- plenty of # desktops have none -- so it is reported rather than treated as a failure. busctl status "$BUS_NAME" >/dev/null 2>&1 \ || emit_error 'No power profile service is running. GNOME uses power-profiles-daemon; Fedora ships tuned-ppd.' local active degraded profiles active="$(property ActiveProfile | sed 's/^s //; s/"//g')" degraded="$(property PerformanceDegraded | sed 's/^s //; s/"//g')" # Profiles is an array of dicts, which busctl renders flat: # v aa{sv} 3 2 "Profile" s "power-saver" "Driver" s "tuned" 2 "Profile" ... # so each profile is the string following its own "Profile" marker. Matching # the marker matters: "Driver" values sit in the same stream, and on this # machine the driver is called "tuned", which a looser pattern happily # reports as a fourth profile that does not exist. profiles="$(property Profiles \ | grep -oE '"Profile" s "[a-z-]+"' \ | sed 's/.*s "//; s/"$//' \ | awk '!seen[$0]++')" [[ -n "$profiles" ]] || emit_error 'The power profile service reported no profiles.' jq -cn \ --arg active "$active" \ --arg degraded "$degraded" \ --argjson profiles "$(printf '%s\n' "$profiles" | jq -Rn '[inputs | select(length > 0)]')" \ '{profiles: $profiles, active: $active, degraded: $degraded, error: ""}' } cmd_set() { local profile="${1:-}" # Constrained rather than passed through: this reaches a system service. [[ "$profile" =~ ^[a-z-]+$ ]] || { printf 'panama-power-profile: refusing a profile name with unexpected characters\n' >&2 return 2 } busctl set-property "$BUS_NAME" "$OBJECT" "$BUS_NAME" ActiveProfile s "$profile" 2>&1 >/dev/null \ | head -2 >&2 return 0 } case "${1:-list}" in list) cmd_list ;; set) shift; cmd_set "${1:-}" ;; *) printf 'usage: panama-power-profile [list|set ]\n' >&2; exit 2 ;; esac