summaryrefslogtreecommitdiff
path: root/accounts/gkleen@skadhi/shell/quickshell/Services/NiriService.qml
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2026-09-23 21:08:35 +0200
committerGregor Kleen <gkleen@yggdrasil.li>2026-09-23 21:08:35 +0200
commitae9a80c1f012686c1f65fce7ce76fdd1b77b0b71 (patch)
tree141f738276cf84c6b7e5633c2bd5f2b44e9b0b75 /accounts/gkleen@skadhi/shell/quickshell/Services/NiriService.qml
parent6a763272038e637a8a03dd9059d43a1ff44921dd (diff)
downloadnixos-ae9a80c1f012686c1f65fce7ce76fdd1b77b0b71.tar
nixos-ae9a80c1f012686c1f65fce7ce76fdd1b77b0b71.tar.gz
nixos-ae9a80c1f012686c1f65fce7ce76fdd1b77b0b71.tar.bz2
nixos-ae9a80c1f012686c1f65fce7ce76fdd1b77b0b71.tar.xz
nixos-ae9a80c1f012686c1f65fce7ce76fdd1b77b0b71.zip
...
Diffstat (limited to 'accounts/gkleen@skadhi/shell/quickshell/Services/NiriService.qml')
-rw-r--r--accounts/gkleen@skadhi/shell/quickshell/Services/NiriService.qml217
1 files changed, 217 insertions, 0 deletions
diff --git a/accounts/gkleen@skadhi/shell/quickshell/Services/NiriService.qml b/accounts/gkleen@skadhi/shell/quickshell/Services/NiriService.qml
new file mode 100644
index 00000000..e9e86f95
--- /dev/null
+++ b/accounts/gkleen@skadhi/shell/quickshell/Services/NiriService.qml
@@ -0,0 +1,217 @@
1pragma Singleton
2
3import Quickshell
4import Quickshell.Io
5import QtQuick
6
7Singleton {
8 id: root
9
10 property var workspaces: []
11 property var outputs: {}
12 property var keyboardLayouts: {}
13 property var windows: []
14 property var casts: []
15 readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
16
17 function refreshOutputs() {
18 commandSocket.sendCommand("Outputs", data => {
19 outputs = data.Ok.Outputs;
20 });
21 }
22
23 function sendCommand(command, callback) {
24 commandSocket.sendCommand(command, callback);
25 }
26
27 Socket {
28 id: eventStreamSocket
29 path: root.socketPath
30 connected: true
31
32 property bool acked: false
33
34 onConnectionStateChanged: {
35 if (connected) {
36 acked = false;
37 write('"EventStream"\n');
38 }
39 }
40
41 parser: SplitParser {
42 onRead: line => {
43 try {
44 const event = JSON.parse(line)
45
46 // console.log(JSON.stringify(event))
47
48 if (event.WorkspacesChanged) {
49 root.workspaces = event.WorkspacesChanged.workspaces
50 root.refreshOutputs();
51 } else if (event.WorkspaceActivated)
52 eventWorkspaceActivated(event.WorkspaceActivated);
53 else if (event.WorkspaceUrgencyChanged)
54 eventWorkspaceUrgencyChanged(event.WorkspaceUrgencyChanged);
55 else if (event.WorkspaceActiveWindowChanged)
56 eventWorkspaceActiveWindowChanged(event.WorkspaceActiveWindowChanged);
57 else if (event.KeyboardLayoutsChanged)
58 root.keyboardLayouts = event.KeyboardLayoutsChanged.keyboard_layouts;
59 else if (event.KeyboardLayoutSwitched)
60 root.keyboardLayouts = Object.assign({}, root.keyboardLayouts, {"current_idx": event.KeyboardLayoutSwitched.idx });
61 else if (event.WindowsChanged)
62 root.windows = event.WindowsChanged.windows
63 else if (event.WindowOpenedOrChanged)
64 eventWindowOpenedOrChanged(event.WindowOpenedOrChanged);
65 else if (event.WindowClosed)
66 eventWindowClosed(event.WindowClosed);
67 else if (event.WindowFocusChanged)
68 eventWindowFocusChanged(event.WindowFocusChanged);
69 else if (event.WindowUrgencyChanged)
70 eventWindowUrgencyChanged(event.WindowUrgencyChanged);
71 else if (event.WindowLayoutsChanged)
72 eventWindowLayoutsChanged(event.WindowLayoutsChanged);
73 else if (event.WindowFocusTimestampChanged)
74 eventWindowFocusTimestampChanged(event.WindowFocusTimestampChanged);
75 else if (event.CastsChanged)
76 root.casts = event.CastsChanged.casts
77 else if (event.CastStartedOrChanged)
78 eventCastStartedOrChanged(event.CastStartedOrChanged);
79 else if (event.CastStopped)
80 eventCastStopped(event.CastStopped);
81 else if (event.Ok && !eventStreamSocket.acked) { eventStreamSocket.acked = true; }
82 else if (event.OverviewOpenedOrClosed) {}
83 else if (event.ScreenshotCaptured) {}
84 else if (event.ConfigLoaded) {}
85 else
86 console.log(JSON.stringify(event));
87 } catch (e) {
88 console.warn("NiriService: Failed to parse event:", line, e)
89 }
90 }
91 }
92 }
93
94 Socket {
95 id: commandSocket
96 path: root.socketPath
97 connected: true
98
99 property var awaitingAnswer: null
100 property var cmdQueue: []
101
102 parser: SplitParser {
103 onRead: line => {
104 if (commandSocket.awaitingAnswer === null)
105 return;
106
107 try {
108 const response = JSON.parse(line);
109 commandSocket.awaitingAnswer.callback(response);
110 commandSocket.awaitingAnswer = null;
111 } catch (e) {
112 console.warn("NiriService: Failed to parse response:", line, e)
113 }
114 commandSocket._handleQueue();
115 }
116 }
117
118 onCmdQueueChanged: {
119 _handleQueue();
120 }
121 onAwaitingAnswerChanged: {
122 _handleQueue();
123 }
124
125 function _handleQueue() {
126 if (cmdQueue.length <= 0 || awaitingAnswer !== null)
127 return;
128
129 let localQueue = Array.from(cmdQueue);
130 awaitingAnswer = localQueue.shift();
131 cmdQueue = localQueue;
132 write(JSON.stringify(awaitingAnswer.command) + '\n');
133 }
134
135 function sendCommand(command, callback) {
136 cmdQueue = Array.from(cmdQueue).concat([{ "command": command, "callback": callback }])
137 }
138 }
139
140 function eventWorkspaceActivated(data) {
141 let relevant_output = null;
142 Array.from(root.workspaces).forEach(ws => {
143 if (data.id === ws.id)
144 relevant_output = ws.output;
145 });
146 root.workspaces = Array.from(root.workspaces).map(ws => {
147 if (data.focused)
148 ws.is_focused = false;
149 if (ws.output === relevant_output)
150 ws.is_active = false;
151 if (data.id === ws.id) {
152 ws.is_active = true;
153 ws.is_focused = data.focused;
154 }
155 return ws;
156 });
157 }
158 function eventWorkspaceUrgencyChanged(data) {
159 root.workspaces = Array.from(root.workspaces).map(ws => {
160 if (data.id == ws.id)
161 ws.is_urgent = data.urgent;
162 return ws;
163 });
164 }
165 function eventWorkspaceActiveWindowChanged(data) {
166 root.workspaces = Array.from(root.workspaces).map(ws => {
167 if (data.workspace_id === ws.id)
168 ws.active_window_id = data.active_window_id;
169 return ws;
170 });
171 }
172 function eventWindowOpenedOrChanged(data) {
173 root.windows = Array.from(root.windows).map(win => {
174 if (data.window.is_focused)
175 win.is_focused = false;
176 return win;
177 }).filter(win => win.id !== data.window.id).concat([data.window]);
178 }
179 function eventWindowClosed(data) {
180 root.windows = Array.from(root.windows).filter(win => win.id !== data.id);
181 }
182 function eventWindowFocusChanged(data) {
183 root.windows = Array.from(root.windows).map(win => {
184 win.is_focused = win.id === data.id;
185 return win;
186 });
187 }
188 function eventWindowUrgencyChanged(data) {
189 root.windows = Array.from(root.windows).map(win => {
190 if (win.id === data.id)
191 win.is_urgent = data.urgent;
192 return win;
193 });
194 }
195 function eventWindowLayoutsChanged(data) {
196 root.windows = Array.from(root.windows).map(win => {
197 Array.from(data.changes).forEach(change => {
198 if (win.id === change[0])
199 win.layout = change[1];
200 });
201 return win;
202 });
203 }
204 function eventWindowFocusTimestampChanged(data) {
205 root.windows = Array.from(root.windows).map(win => {
206 if (win.id === data.id)
207 win.focus_timestamp = data.focus_timestamp;
208 return win;
209 });
210 }
211 function eventCastStartedOrChanged(data) {
212 root.casts = [...Array.from(root.casts).filter(cast => cast.stream_id !== data.cast.stream_id), data.cast];
213 }
214 function eventCastStopped(data) {
215 root.casts = Array.from(root.casts).filter(cast => cast.stream_id !== data.stream_id);
216 }
217}