-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathniri-language.py
More file actions
executable file
·175 lines (139 loc) · 4.44 KB
/
niri-language.py
File metadata and controls
executable file
·175 lines (139 loc) · 4.44 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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#!/usr/bin/env python3
import json
import os
import shutil
import subprocess
import sys
import time
LAYOUT_MAP = {
"English (US)": ("EN", "us"),
"Russian": ("RU", "ru"),
}
PREFERRED_OSD_MONITOR = os.environ.get("VIBEBAR_OSD_MONITOR", "").strip()
def run_json(*args):
proc = subprocess.run(
args,
check=True,
capture_output=True,
text=True,
)
return json.loads(proc.stdout)
def format_layout(names, idx):
if not names:
return {
"text": "?",
"tooltip": "Input layout: unknown",
"class": "unknown",
}
safe_idx = max(0, min(idx, len(names) - 1))
name = names[safe_idx]
text, css_class = LAYOUT_MAP.get(name, (name, "unknown"))
return {
"text": text,
"tooltip": f"Input layout: {name}",
"class": css_class,
}
def emit(payload):
print(json.dumps(payload), flush=True)
def pick_osd_monitor():
if PREFERRED_OSD_MONITOR:
try:
outputs = run_json("niri", "msg", "--json", "outputs")
except Exception:
outputs = None
if isinstance(outputs, dict):
if PREFERRED_OSD_MONITOR in outputs:
return PREFERRED_OSD_MONITOR
elif isinstance(outputs, list):
for output in outputs:
if isinstance(output, dict) and output.get("name") == PREFERRED_OSD_MONITOR:
return PREFERRED_OSD_MONITOR
try:
focused_output = run_json("niri", "msg", "--json", "focused-output")
except Exception:
focused_output = None
if isinstance(focused_output, dict):
return focused_output.get("name")
return None
def show_osd(names, idx, text):
client = shutil.which("swayosd-client")
if client is None or not names:
return
monitor = pick_osd_monitor()
segmented = f"{max(0, idx) + 1}:{len(names)}"
cmd = [
client,
"--custom-segmented-progress",
segmented,
"--custom-progress-text",
text,
"--custom-icon",
"input-keyboard-symbolic",
]
if monitor:
cmd.extend(["--monitor", monitor])
subprocess.run(
cmd,
check=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
def current_state():
payload = run_json("niri", "msg", "--json", "keyboard-layouts")
names = payload.get("names", [])
current_idx = int(payload.get("current_idx", 0))
return names, current_idx
def handle_event(event, names, current_idx):
if "KeyboardLayoutsChanged" in event:
keyboard_layouts = event["KeyboardLayoutsChanged"]["keyboard_layouts"]
names = keyboard_layouts.get("names", names)
current_idx = int(keyboard_layouts.get("current_idx", current_idx))
return names, current_idx, True, False
if "KeyboardLayoutSwitched" in event:
current_idx = int(event["KeyboardLayoutSwitched"]["idx"])
return names, current_idx, True, True
return names, current_idx, False, False
def event_stream():
proc = subprocess.Popen(
["niri", "msg", "--json", "event-stream"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
assert proc.stdout is not None
for line in proc.stdout:
line = line.strip()
if not line:
continue
yield json.loads(line)
proc.wait()
raise RuntimeError(f"niri event stream exited with code {proc.returncode}")
def main():
names = []
current_idx = 0
last_payload = None
while True:
try:
names, current_idx = current_state()
payload = format_layout(names, current_idx)
emit(payload)
last_payload = payload
for event in event_stream():
names, current_idx, changed, should_osd = handle_event(
event, names, current_idx
)
if not changed:
continue
payload = format_layout(names, current_idx)
if payload != last_payload:
emit(payload)
last_payload = payload
if should_osd:
show_osd(names, current_idx, payload["text"])
except KeyboardInterrupt:
return
except Exception as exc:
print(f"niri-language watcher error: {exc}", file=sys.stderr, flush=True)
time.sleep(1)
if __name__ == "__main__":
main()