-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstasis-status.py
More file actions
143 lines (120 loc) · 4.05 KB
/
Copy pathstasis-status.py
File metadata and controls
143 lines (120 loc) · 4.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#!/usr/bin/env python3
import json
import subprocess
import sys
LEGEND = (
"\n\nLegend:\n"
"- WAIT: armed, waiting for the session to become idle.\n"
"- RUN: idle detected, countdown/plan is advancing toward the next action.\n"
"- HOLD: inhibited, but the source is not classified more specifically.\n"
"- DBUS: inhibited by a D-Bus/logind/screensaver-style inhibit.\n"
"- CALL: inhibited by active media or call activity.\n"
"- APP: inhibited by a matched app rule.\n"
"- PAUSE: manually inhibited via `stasis toggle-inhibit`.\n"
"- DOWN: `stasis` daemon unavailable or probe failed."
)
TOOLTIP_NOTES = (
"\n\nNotes:\n"
"- ignore_remote_media=true: remote players do not inhibit idle.\n"
"- Browser audio is not treated as regular media inhibit.\n"
"- Browser calls rely on D-Bus inhibit or active mic/source-output.\n"
"- Without a real mic/source-output, browser/portal may drop inhibit mid-call."
)
STATE_MAP = {
"idle_waiting": {
"text": "WAIT",
"class": "waiting",
"tooltip_prefix": "Stasis: armed\n",
},
"idle_active": {
"text": "RUN",
"class": "active",
"tooltip_prefix": "Stasis: armed and counting down\n",
},
"idle_inhibited": {
"text": "HOLD",
"class": "inhibited",
"tooltip_prefix": "Stasis: blocked by app/media/dbus inhibit\n",
},
"manually_inhibited": {
"text": "PAUSE",
"class": "manual",
"tooltip_prefix": "Stasis: manually paused\n",
},
"not_running": {
"text": "DOWN",
"class": "down",
"tooltip_prefix": "Stasis: daemon unavailable\n",
},
}
def parse_bool_line(text: str, key: str) -> bool:
prefix = f"{key}: "
for line in text.splitlines():
if line.startswith(prefix):
return line[len(prefix) :].strip().lower() == "yes"
return False
def parse_int_line(text: str, key: str) -> int:
prefix = f"{key}: "
for line in text.splitlines():
if line.startswith(prefix):
try:
return int(line[len(prefix) :].strip())
except ValueError:
return 0
return 0
def resolve_display(alt: str, tooltip_body: str) -> tuple[str, str, str]:
mapped = STATE_MAP.get(
alt,
{
"text": alt.upper(),
"class": "unknown",
"tooltip_prefix": "Stasis: unknown state\n",
},
)
if alt != "idle_inhibited":
return mapped["text"], mapped["class"], mapped["tooltip_prefix"]
if parse_bool_line(tooltip_body, "D-Bus Inhibiting"):
return "DBUS", "inhibited dbus", "Stasis: blocked by D-Bus inhibit\n"
if parse_int_line(tooltip_body, "Media Players Playing") > 0:
return "CALL", "inhibited call", "Stasis: blocked by media/call activity\n"
if parse_int_line(tooltip_body, "Apps Inhibiting") > 0:
return "APP", "inhibited app", "Stasis: blocked by app inhibit\n"
return mapped["text"], mapped["class"], mapped["tooltip_prefix"]
def main() -> int:
try:
proc = subprocess.run(
["stasis", "info", "--json"],
check=True,
capture_output=True,
text=True,
)
payload = json.loads(proc.stdout)
except Exception as exc:
json.dump(
{
"text": "DOWN",
"class": "down",
"tooltip": f"Stasis status probe failed:\n{exc}",
},
sys.stdout,
ensure_ascii=False,
)
sys.stdout.write("\n")
return 0
alt = payload.get("alt", "not_running")
tooltip_body = payload.get("tooltip", "").strip()
text, css_class, tooltip_prefix = resolve_display(alt, tooltip_body)
tooltip = (tooltip_prefix + tooltip_body).strip() + LEGEND + TOOLTIP_NOTES
json.dump(
{
"text": text,
"class": css_class,
"tooltip": tooltip,
},
sys.stdout,
ensure_ascii=False,
)
sys.stdout.write("\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())