From ae9a80c1f012686c1f65fce7ce76fdd1b77b0b71 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Wed, 23 Sep 2026 21:08:35 +0200 Subject: ... --- .../shell/quickshell/Services/NiriService.qml | 217 +++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 accounts/gkleen@skadhi/shell/quickshell/Services/NiriService.qml (limited to 'accounts/gkleen@skadhi/shell/quickshell/Services/NiriService.qml') 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 @@ +pragma Singleton + +import Quickshell +import Quickshell.Io +import QtQuick + +Singleton { + id: root + + property var workspaces: [] + property var outputs: {} + property var keyboardLayouts: {} + property var windows: [] + property var casts: [] + readonly property string socketPath: Quickshell.env("NIRI_SOCKET") + + function refreshOutputs() { + commandSocket.sendCommand("Outputs", data => { + outputs = data.Ok.Outputs; + }); + } + + function sendCommand(command, callback) { + commandSocket.sendCommand(command, callback); + } + + Socket { + id: eventStreamSocket + path: root.socketPath + connected: true + + property bool acked: false + + onConnectionStateChanged: { + if (connected) { + acked = false; + write('"EventStream"\n'); + } + } + + parser: SplitParser { + onRead: line => { + try { + const event = JSON.parse(line) + + // console.log(JSON.stringify(event)) + + if (event.WorkspacesChanged) { + root.workspaces = event.WorkspacesChanged.workspaces + root.refreshOutputs(); + } else if (event.WorkspaceActivated) + eventWorkspaceActivated(event.WorkspaceActivated); + else if (event.WorkspaceUrgencyChanged) + eventWorkspaceUrgencyChanged(event.WorkspaceUrgencyChanged); + else if (event.WorkspaceActiveWindowChanged) + eventWorkspaceActiveWindowChanged(event.WorkspaceActiveWindowChanged); + else if (event.KeyboardLayoutsChanged) + root.keyboardLayouts = event.KeyboardLayoutsChanged.keyboard_layouts; + else if (event.KeyboardLayoutSwitched) + root.keyboardLayouts = Object.assign({}, root.keyboardLayouts, {"current_idx": event.KeyboardLayoutSwitched.idx }); + else if (event.WindowsChanged) + root.windows = event.WindowsChanged.windows + else if (event.WindowOpenedOrChanged) + eventWindowOpenedOrChanged(event.WindowOpenedOrChanged); + else if (event.WindowClosed) + eventWindowClosed(event.WindowClosed); + else if (event.WindowFocusChanged) + eventWindowFocusChanged(event.WindowFocusChanged); + else if (event.WindowUrgencyChanged) + eventWindowUrgencyChanged(event.WindowUrgencyChanged); + else if (event.WindowLayoutsChanged) + eventWindowLayoutsChanged(event.WindowLayoutsChanged); + else if (event.WindowFocusTimestampChanged) + eventWindowFocusTimestampChanged(event.WindowFocusTimestampChanged); + else if (event.CastsChanged) + root.casts = event.CastsChanged.casts + else if (event.CastStartedOrChanged) + eventCastStartedOrChanged(event.CastStartedOrChanged); + else if (event.CastStopped) + eventCastStopped(event.CastStopped); + else if (event.Ok && !eventStreamSocket.acked) { eventStreamSocket.acked = true; } + else if (event.OverviewOpenedOrClosed) {} + else if (event.ScreenshotCaptured) {} + else if (event.ConfigLoaded) {} + else + console.log(JSON.stringify(event)); + } catch (e) { + console.warn("NiriService: Failed to parse event:", line, e) + } + } + } + } + + Socket { + id: commandSocket + path: root.socketPath + connected: true + + property var awaitingAnswer: null + property var cmdQueue: [] + + parser: SplitParser { + onRead: line => { + if (commandSocket.awaitingAnswer === null) + return; + + try { + const response = JSON.parse(line); + commandSocket.awaitingAnswer.callback(response); + commandSocket.awaitingAnswer = null; + } catch (e) { + console.warn("NiriService: Failed to parse response:", line, e) + } + commandSocket._handleQueue(); + } + } + + onCmdQueueChanged: { + _handleQueue(); + } + onAwaitingAnswerChanged: { + _handleQueue(); + } + + function _handleQueue() { + if (cmdQueue.length <= 0 || awaitingAnswer !== null) + return; + + let localQueue = Array.from(cmdQueue); + awaitingAnswer = localQueue.shift(); + cmdQueue = localQueue; + write(JSON.stringify(awaitingAnswer.command) + '\n'); + } + + function sendCommand(command, callback) { + cmdQueue = Array.from(cmdQueue).concat([{ "command": command, "callback": callback }]) + } + } + + function eventWorkspaceActivated(data) { + let relevant_output = null; + Array.from(root.workspaces).forEach(ws => { + if (data.id === ws.id) + relevant_output = ws.output; + }); + root.workspaces = Array.from(root.workspaces).map(ws => { + if (data.focused) + ws.is_focused = false; + if (ws.output === relevant_output) + ws.is_active = false; + if (data.id === ws.id) { + ws.is_active = true; + ws.is_focused = data.focused; + } + return ws; + }); + } + function eventWorkspaceUrgencyChanged(data) { + root.workspaces = Array.from(root.workspaces).map(ws => { + if (data.id == ws.id) + ws.is_urgent = data.urgent; + return ws; + }); + } + function eventWorkspaceActiveWindowChanged(data) { + root.workspaces = Array.from(root.workspaces).map(ws => { + if (data.workspace_id === ws.id) + ws.active_window_id = data.active_window_id; + return ws; + }); + } + function eventWindowOpenedOrChanged(data) { + root.windows = Array.from(root.windows).map(win => { + if (data.window.is_focused) + win.is_focused = false; + return win; + }).filter(win => win.id !== data.window.id).concat([data.window]); + } + function eventWindowClosed(data) { + root.windows = Array.from(root.windows).filter(win => win.id !== data.id); + } + function eventWindowFocusChanged(data) { + root.windows = Array.from(root.windows).map(win => { + win.is_focused = win.id === data.id; + return win; + }); + } + function eventWindowUrgencyChanged(data) { + root.windows = Array.from(root.windows).map(win => { + if (win.id === data.id) + win.is_urgent = data.urgent; + return win; + }); + } + function eventWindowLayoutsChanged(data) { + root.windows = Array.from(root.windows).map(win => { + Array.from(data.changes).forEach(change => { + if (win.id === change[0]) + win.layout = change[1]; + }); + return win; + }); + } + function eventWindowFocusTimestampChanged(data) { + root.windows = Array.from(root.windows).map(win => { + if (win.id === data.id) + win.focus_timestamp = data.focus_timestamp; + return win; + }); + } + function eventCastStartedOrChanged(data) { + root.casts = [...Array.from(root.casts).filter(cast => cast.stream_id !== data.cast.stream_id), data.cast]; + } + function eventCastStopped(data) { + root.casts = Array.from(root.casts).filter(cast => cast.stream_id !== data.stream_id); + } +} -- cgit v1.2.3