gamemode: global auto-trigger via niri window watch
Add scripts/gamemode-watch.py, launched at niri startup, which subscribes to niri's event stream and toggles gamemode whenever a game window (steam_app_*, RuneLite, gamescope) is open — so Steam games trigger gamemode with zero per-game setup instead of needing gamemoderun %command%. 'winwatch' is a third reference-counted source alongside feral + manual. Pure Watcher class is unit-tested; SIGTERM/finally guarantee cleanup so the watcher stopping never leaves gamemode stuck on. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Unit tests for gamemode-watch's pure event logic (no niri, no subprocess).
|
||||
Loads the hyphenated script by path and drives Watcher with synthetic events,
|
||||
asserting the exact sequence of add/del calls."""
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
MOD_PATH = os.path.join(HERE, "..", "gamemode-watch.py")
|
||||
spec = importlib.util.spec_from_file_location("gamemode_watch", MOD_PATH)
|
||||
gw = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(gw)
|
||||
|
||||
fails = 0
|
||||
|
||||
|
||||
def check(desc, got, want):
|
||||
global fails
|
||||
if got == want:
|
||||
print(f"ok - {desc}")
|
||||
else:
|
||||
print(f"FAIL - {desc} (got {got!r} want {want!r})")
|
||||
fails = 1
|
||||
|
||||
|
||||
# --- is_game ---------------------------------------------------------------
|
||||
check("steam_app_ matches", gw.is_game("steam_app_480"), True)
|
||||
check("bare steam_app_ matches", gw.is_game("steam_app_"), True)
|
||||
check("RuneLite matches", gw.is_game("RuneLite"), True)
|
||||
check("runelite matches", gw.is_game("runelite"), True)
|
||||
check("runelite full id matches", gw.is_game("net-runelite-client-RuneLite"), True)
|
||||
check("gamescope matches", gw.is_game("gamescope"), True)
|
||||
check("Gamescope (case) matches", gw.is_game("Gamescope"), True)
|
||||
check("Alacritty is not a game", gw.is_game("Alacritty"), False)
|
||||
check("bolt-launcher excluded", gw.is_game("bolt-launcher"), False)
|
||||
check("empty app_id is not a game", gw.is_game(""), False)
|
||||
check("None app_id is not a game", gw.is_game(None), False)
|
||||
|
||||
|
||||
# --- Watcher transitions ---------------------------------------------------
|
||||
def drive(events):
|
||||
calls = []
|
||||
w = gw.Watcher(lambda action: calls.append(action))
|
||||
for ev in events:
|
||||
w.handle(ev)
|
||||
return calls
|
||||
|
||||
|
||||
def win(wid, app_id):
|
||||
return {"WindowOpenedOrChanged": {"window": {"id": wid, "app_id": app_id}}}
|
||||
|
||||
|
||||
def closed(wid):
|
||||
return {"WindowClosed": {"id": wid}}
|
||||
|
||||
|
||||
def snapshot(pairs):
|
||||
return {"WindowsChanged": {"windows": [{"id": i, "app_id": a} for i, a in pairs]}}
|
||||
|
||||
|
||||
check("empty snapshot: no calls", drive([snapshot([])]), [])
|
||||
check("non-game window: no calls", drive([win(1, "Alacritty")]), [])
|
||||
check("one game opens: add", drive([win(1, "steam_app_10")]), ["add"])
|
||||
check(
|
||||
"second game keeps active (single add)",
|
||||
drive([win(1, "steam_app_10"), win(2, "RuneLite")]),
|
||||
["add"],
|
||||
)
|
||||
check(
|
||||
"close one of two: still active",
|
||||
drive([win(1, "steam_app_10"), win(2, "RuneLite"), closed(2)]),
|
||||
["add"],
|
||||
)
|
||||
check(
|
||||
"close last game: add then del",
|
||||
drive([win(1, "steam_app_10"), closed(1)]),
|
||||
["add", "del"],
|
||||
)
|
||||
check(
|
||||
"snapshot already containing a game: add (restart mid-game)",
|
||||
drive([snapshot([(1, "Alacritty"), (5, "steam_app_9")])]),
|
||||
["add"],
|
||||
)
|
||||
check(
|
||||
"game window changes to non-game app_id: del",
|
||||
drive([win(1, "steam_app_10"), win(1, "Alacritty")]),
|
||||
["add", "del"],
|
||||
)
|
||||
check(
|
||||
"repeated OpenedOrChanged for same game: single add",
|
||||
drive([win(1, "gamescope"), win(1, "gamescope"), win(1, "gamescope")]),
|
||||
["add"],
|
||||
)
|
||||
|
||||
sys.exit(fails)
|
||||
Reference in New Issue
Block a user