#!/bin/bash # gamemode-session — compositor-level gamemode for niri. # Reference-counted over two sources (feral, manual). Applies effects on the # first active source (0->1) and reverts on the last (1->0). # Spec: docs/superpowers/specs/2026-07-15-niri-gamemode-design.md set -u STATE_DIR="${GAMEMODE_STATE_DIR:-${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/gamemode}" FAN_AUTO="${XDG_STATE_HOME:-$HOME/.local/state}/fan-profile-auto" DRYRUN="${GAMEMODE_DRYRUN:-0}" log() { printf 'gamemode: %s\n' "$*" >&2; } run() { if [ "$DRYRUN" = 1 ]; then log "would: $*"; else "$@"; fi; } is_active() { [ -n "$(find "$STATE_DIR" -maxdepth 1 -name 'src.*' -print -quit 2>/dev/null)" ] } snapshot_baseline() { mkdir -p "$STATE_DIR" powerprofilesctl get 2>/dev/null > "$STATE_DIR/baseline.profile" || : > "$STATE_DIR/baseline.profile" if pgrep -x waybar >/dev/null 2>&1; then echo 1; else echo 0; fi > "$STATE_DIR/baseline.waybar" if [ -f "$FAN_AUTO" ]; then echo 1; else echo 0; fi > "$STATE_DIR/baseline.fanauto" } # --- effects ------------------------------------------------------------------ effect_idle_on() { if [ "$DRYRUN" = 1 ]; then log "would: systemd-inhibit idle"; return; fi command -v systemd-inhibit >/dev/null 2>&1 || { log "no systemd-inhibit"; return; } systemd-inhibit --what=idle --who=gamemode --why="gaming session" --mode=block \ sleep infinity >/dev/null 2>&1 & echo $! > "$STATE_DIR/idle.pid" } effect_idle_off() { local pid; pid=$(cat "$STATE_DIR/idle.pid" 2>/dev/null || echo "") [ -n "$pid" ] && run kill "$pid" rm -f "$STATE_DIR/idle.pid" } effect_dnd_on() { command -v makoctl >/dev/null 2>&1 && run makoctl mode -a do-not-disturb; } effect_dnd_off() { command -v makoctl >/dev/null 2>&1 && run makoctl mode -r do-not-disturb; } # maps a power profile to the fw-fanctrl strategy (mirrors fan-profile.sh) fan_for() { case "$1" in power-saver) echo lazy;; balanced) echo medium;; performance) echo agile;; *) return 1;; esac; } effect_perf_on() { local base; base=$(cat "$STATE_DIR/baseline.profile" 2>/dev/null || echo "") if [ -n "$base" ] && [ "$base" != performance ]; then run powerprofilesctl set performance run pkill -RTMIN+8 waybar fi if [ "$(cat "$STATE_DIR/baseline.fanauto" 2>/dev/null)" = 1 ]; then run fw-fanctrl use agile fi } effect_perf_off() { local base; base=$(cat "$STATE_DIR/baseline.profile" 2>/dev/null || echo "") if [ -n "$base" ] && [ "$base" != performance ]; then run powerprofilesctl set "$base" run pkill -RTMIN+8 waybar fi if [ -n "$base" ] && [ "$(cat "$STATE_DIR/baseline.fanauto" 2>/dev/null)" = 1 ]; then local strat; strat=$(fan_for "$base") && run fw-fanctrl use "$strat" fi } effect_waybar_on() { # hide pgrep -x waybar >/dev/null 2>&1 && run pkill -x waybar } effect_waybar_off() { # restore if it was running at entry [ "$(cat "$STATE_DIR/baseline.waybar" 2>/dev/null)" = 1 ] || return 0 if [ "$DRYRUN" = 1 ]; then log "would: relaunch waybar"; else setsid waybar >/dev/null 2>&1 & fi } apply_effects() { effect_idle_on; effect_dnd_on; effect_perf_on; effect_waybar_on; } revert_effects() { effect_idle_off; effect_dnd_off; effect_perf_off; effect_waybar_off; } # --- state machine ------------------------------------------------------------ cmd_add() { local src="$1" was=0 is_active && was=1 mkdir -p "$STATE_DIR" touch "$STATE_DIR/src.$src" if [ "$was" = 0 ]; then snapshot_baseline apply_effects log "activated (source: $src)" else log "already active; +source: $src" fi } cmd_del() { local src="$1" rm -f "$STATE_DIR/src.$src" if ! is_active; then revert_effects rm -f "$STATE_DIR"/baseline.* "$STATE_DIR/idle.pid" log "deactivated (last source: $src)" else log "still active; -source: $src" fi } cmd_toggle() { if [ -e "$STATE_DIR/src.manual" ]; then cmd_del manual; else cmd_add manual; fi } cmd_status() { if is_active; then echo active; exit 0; else echo inactive; exit 1; fi; } cmd_reset() { revert_effects 2>/dev/null || true rm -rf "$STATE_DIR" log "reset" } case "${1:-}" in add) [ -n "${2:-}" ] || { log "add needs a source"; exit 2; }; cmd_add "$2" ;; del) [ -n "${2:-}" ] || { log "del needs a source"; exit 2; }; cmd_del "$2" ;; toggle) cmd_toggle ;; status) cmd_status ;; reset) cmd_reset ;; *) log "usage: gamemode-session {add|del |toggle|status|reset}"; exit 2 ;; esac