pragma Singleton import Quickshell import Quickshell.Io import QtQuick Singleton { id: root property var workspaces: [] property var outputs: {} property var keyboardLayouts: {} property var windows: [] 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 onConnectionStateChanged: { if (connected) { 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 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 (ws.output === relevant_output) { ws.is_active = false; ws.is_focused = 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).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; }); } }