1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

Re-org niri service & handle reconnects to socket

This commit is contained in:
bbedward
2025-10-06 16:46:05 -04:00
parent 1b06090f72
commit b507b08e34

View File

@@ -12,6 +12,8 @@ import qs.Common
Singleton { Singleton {
id: root id: root
readonly property string socketPath: Quickshell.env("NIRI_SOCKET")
property var workspaces: ({}) property var workspaces: ({})
property var allWorkspaces: [] property var allWorkspaces: []
property int focusedWorkspaceIndex: 0 property int focusedWorkspaceIndex: 0
@@ -20,11 +22,8 @@ Singleton {
property string currentOutput: "" property string currentOutput: ""
property var outputs: ({}) property var outputs: ({})
property var windows: [] property var windows: []
signal windowUrgentChanged()
property bool inOverview: false property bool inOverview: false
property int currentKeyboardLayoutIndex: 0 property int currentKeyboardLayoutIndex: 0
@@ -37,16 +36,42 @@ Singleton {
property bool matugenSuppression: false property bool matugenSuppression: false
property bool configGenerationPending: false property bool configGenerationPending: false
readonly property string socketPath: Quickshell.env("NIRI_SOCKET") property bool _wantSockets: true
property int _reconnectAttempt: 0
Component.onCompleted: { readonly property int _reconnectBaseMs: 400
fetchOutputs() readonly property int _reconnectMaxMs: 15000
signal windowUrgentChanged()
Component.onCompleted: fetchOutputs()
Timer {
id: reconnectTimer
interval: 0
repeat: false
onTriggered: {
root._wantSockets = false
Qt.callLater(() => root._wantSockets = true)
}
} }
function fetchOutputs() { Timer {
if (CompositorService.isNiri) { id: suppressToastTimer
outputsProcess.running = true interval: 3000
} onTriggered: root.suppressConfigToast = false
}
Timer {
id: suppressResetTimer
interval: 2000
onTriggered: root.matugenSuppression = false
}
Timer {
id: configGenerationDebounce
interval: 100
onTriggered: root.doGenerateNiriLayoutConfig()
} }
Process { Process {
@@ -75,14 +100,70 @@ Singleton {
} }
} }
Process {
id: validateProcess
command: ["niri", "validate"]
running: false
stderr: StdioCollector {
onStreamFinished: {
const lines = text.split('\n')
const trimmedLines = lines.map(line => line.replace(/\s+$/, '')).filter(line => line.length > 0)
configValidationOutput = trimmedLines.join('\n').trim()
if (hasInitialConnection) {
ToastService.showError("niri: failed to load config", configValidationOutput)
}
}
}
onExited: exitCode => {
if (exitCode === 0) {
configValidationOutput = ""
}
}
}
Process {
id: writeConfigProcess
property string configContent: ""
property string configPath: ""
onExited: exitCode => {
if (exitCode === 0) {
console.log("NiriService: Generated layout config at", configPath)
return
}
console.warn("NiriService: Failed to write layout config, exit code:", exitCode)
}
}
Process {
id: writeBindsProcess
property string bindsPath: ""
onExited: exitCode => {
if (exitCode === 0) {
console.log("NiriService: Generated binds config at", bindsPath)
return
}
console.warn("NiriService: Failed to write binds config, exit code:", exitCode)
}
}
Socket { Socket {
id: eventStreamSocket id: eventStreamSocket
path: root.socketPath path: root.socketPath
connected: CompositorService.isNiri connected: CompositorService.isNiri && root._wantSockets
onConnectionStateChanged: { onConnectionStateChanged: {
if (connected) { if (connected) {
_reconnectAttempt = 0
write('"EventStream"\n') write('"EventStream"\n')
fetchOutputs()
return
}
if (CompositorService.isNiri) {
_scheduleReconnect()
} }
} }
@@ -101,99 +182,123 @@ Singleton {
Socket { Socket {
id: requestSocket id: requestSocket
path: root.socketPath path: root.socketPath
connected: CompositorService.isNiri connected: CompositorService.isNiri && root._wantSockets
onConnectionStateChanged: {
if (connected) {
_reconnectAttempt = 0
return
}
if (CompositorService.isNiri) {
_scheduleReconnect()
}
}
}
function fetchOutputs() {
if (!CompositorService.isNiri) return
outputsProcess.running = true
}
function _scheduleReconnect() {
const pow = Math.min(_reconnectAttempt, 10)
const base = Math.min(_reconnectBaseMs * Math.pow(2, pow), _reconnectMaxMs)
const jitter = Math.floor(Math.random() * Math.floor(base / 4))
reconnectTimer.interval = base + jitter
reconnectTimer.restart()
_reconnectAttempt++
console.warn("NiriService: scheduling reconnect in ~", reconnectTimer.interval, "ms (attempt", _reconnectAttempt, ")")
} }
function sortWindowsByLayout(windowList) { function sortWindowsByLayout(windowList) {
return [...windowList].sort((a, b) => { return [...windowList].sort((a, b) => {
const aWorkspace = workspaces[a.workspace_id] const aWorkspace = workspaces[a.workspace_id]
const bWorkspace = workspaces[b.workspace_id] const bWorkspace = workspaces[b.workspace_id]
if (aWorkspace && bWorkspace) { if (aWorkspace && bWorkspace) {
const aOutput = aWorkspace.output const aOutput = aWorkspace.output
const bOutput = bWorkspace.output const bOutput = bWorkspace.output
const aOutputInfo = outputs[aOutput] const aOutputInfo = outputs[aOutput]
const bOutputInfo = outputs[bOutput] const bOutputInfo = outputs[bOutput]
if (aOutputInfo && bOutputInfo && aOutputInfo.logical && bOutputInfo.logical) { if (aOutputInfo && bOutputInfo && aOutputInfo.logical && bOutputInfo.logical) {
if (aOutputInfo.logical.x !== bOutputInfo.logical.x) { if (aOutputInfo.logical.x !== bOutputInfo.logical.x) {
return aOutputInfo.logical.x - bOutputInfo.logical.x return aOutputInfo.logical.x - bOutputInfo.logical.x
} }
if (aOutputInfo.logical.y !== bOutputInfo.logical.y) { if (aOutputInfo.logical.y !== bOutputInfo.logical.y) {
return aOutputInfo.logical.y - bOutputInfo.logical.y return aOutputInfo.logical.y - bOutputInfo.logical.y
} }
} }
if (aOutput === bOutput && aWorkspace.idx !== bWorkspace.idx) { if (aOutput === bOutput && aWorkspace.idx !== bWorkspace.idx) {
return aWorkspace.idx - bWorkspace.idx return aWorkspace.idx - bWorkspace.idx
} }
} }
if (a.workspace_id === b.workspace_id && a.layout && b.layout) { if (a.workspace_id === b.workspace_id && a.layout && b.layout) {
if (a.layout.pos_in_scrolling_layout && b.layout.pos_in_scrolling_layout) {
const aPos = a.layout.pos_in_scrolling_layout
const bPos = b.layout.pos_in_scrolling_layout
if (a.layout.pos_in_scrolling_layout && b.layout.pos_in_scrolling_layout) { if (aPos.length > 1 && bPos.length > 1) {
const aPos = a.layout.pos_in_scrolling_layout if (aPos[0] !== bPos[0]) {
const bPos = b.layout.pos_in_scrolling_layout return aPos[0] - bPos[0]
}
if (aPos[1] !== bPos[1]) {
return aPos[1] - bPos[1]
}
}
}
}
if (aPos.length > 1 && bPos.length > 1) { return a.id - b.id
if (aPos[0] !== bPos[0]) { })
return aPos[0] - bPos[0]
}
if (aPos[1] !== bPos[1]) {
return aPos[1] - bPos[1]
}
}
}
}
return a.id - b.id
})
} }
function handleNiriEvent(event) { function handleNiriEvent(event) {
const eventType = Object.keys(event)[0]; const eventType = Object.keys(event)[0]
switch (eventType) { switch (eventType) {
case 'WorkspacesChanged': case 'WorkspacesChanged':
handleWorkspacesChanged(event.WorkspacesChanged); handleWorkspacesChanged(event.WorkspacesChanged)
break; break
case 'WorkspaceActivated': case 'WorkspaceActivated':
handleWorkspaceActivated(event.WorkspaceActivated); handleWorkspaceActivated(event.WorkspaceActivated)
break; break
case 'WorkspaceActiveWindowChanged': case 'WorkspaceActiveWindowChanged':
handleWorkspaceActiveWindowChanged(event.WorkspaceActiveWindowChanged); handleWorkspaceActiveWindowChanged(event.WorkspaceActiveWindowChanged)
break; break
case 'WindowsChanged': case 'WindowsChanged':
handleWindowsChanged(event.WindowsChanged); handleWindowsChanged(event.WindowsChanged)
break; break
case 'WindowClosed': case 'WindowClosed':
handleWindowClosed(event.WindowClosed); handleWindowClosed(event.WindowClosed)
break; break
case 'WindowOpenedOrChanged': case 'WindowOpenedOrChanged':
handleWindowOpenedOrChanged(event.WindowOpenedOrChanged); handleWindowOpenedOrChanged(event.WindowOpenedOrChanged)
break; break
case 'WindowLayoutsChanged': case 'WindowLayoutsChanged':
handleWindowLayoutsChanged(event.WindowLayoutsChanged); handleWindowLayoutsChanged(event.WindowLayoutsChanged)
break; break
case 'OutputsChanged': case 'OutputsChanged':
handleOutputsChanged(event.OutputsChanged); handleOutputsChanged(event.OutputsChanged)
break; break
case 'OverviewOpenedOrClosed': case 'OverviewOpenedOrClosed':
handleOverviewChanged(event.OverviewOpenedOrClosed); handleOverviewChanged(event.OverviewOpenedOrClosed)
break; break
case 'ConfigLoaded': case 'ConfigLoaded':
handleConfigLoaded(event.ConfigLoaded); handleConfigLoaded(event.ConfigLoaded)
break; break
case 'KeyboardLayoutsChanged': case 'KeyboardLayoutsChanged':
handleKeyboardLayoutsChanged(event.KeyboardLayoutsChanged); handleKeyboardLayoutsChanged(event.KeyboardLayoutsChanged)
break; break
case 'KeyboardLayoutSwitched': case 'KeyboardLayoutSwitched':
handleKeyboardLayoutSwitched(event.KeyboardLayoutSwitched); handleKeyboardLayoutSwitched(event.KeyboardLayoutSwitched)
break; break
case 'WorkspaceUrgencyChanged': case 'WorkspaceUrgencyChanged':
handleWorkspaceUrgencyChanged(event.WorkspaceUrgencyChanged); handleWorkspaceUrgencyChanged(event.WorkspaceUrgencyChanged)
break; break
} }
} }
@@ -254,31 +359,26 @@ Singleton {
} }
function handleWorkspaceActiveWindowChanged(data) { function handleWorkspaceActiveWindowChanged(data) {
if (data.active_window_id !== null && data.active_window_id !== undefined) { const updatedWindows = []
const updatedWindows = []
for (var i = 0; i < windows.length; i++) { for (var i = 0; i < windows.length; i++) {
const w = windows[i] const w = windows[i]
const updatedWindow = {} const updatedWindow = {}
for (let prop in w) {
updatedWindow[prop] = w[prop] for (let prop in w) {
} updatedWindow[prop] = w[prop]
}
if (data.active_window_id !== null && data.active_window_id !== undefined) {
updatedWindow.is_focused = (w.id == data.active_window_id) updatedWindow.is_focused = (w.id == data.active_window_id)
updatedWindows.push(updatedWindow) } else {
}
windows = updatedWindows
} else {
const updatedWindows = []
for (var i = 0; i < windows.length; i++) {
const w = windows[i]
const updatedWindow = {}
for (let prop in w) {
updatedWindow[prop] = w[prop]
}
updatedWindow.is_focused = w.workspace_id == data.workspace_id ? false : w.is_focused updatedWindow.is_focused = w.workspace_id == data.workspace_id ? false : w.is_focused
updatedWindows.push(updatedWindow)
} }
windows = updatedWindows
updatedWindows.push(updatedWindow)
} }
windows = updatedWindows
} }
function handleWindowsChanged(data) { function handleWindowsChanged(data) {
@@ -290,9 +390,7 @@ Singleton {
} }
function handleWindowOpenedOrChanged(data) { function handleWindowOpenedOrChanged(data) {
if (!data.window) { if (!data.window) return
return
}
const window = data.window const window = data.window
const existingIndex = windows.findIndex(w => w.id === window.id) const existingIndex = windows.findIndex(w => w.id === window.id)
@@ -301,15 +399,14 @@ Singleton {
const updatedWindows = [...windows] const updatedWindows = [...windows]
updatedWindows[existingIndex] = window updatedWindows[existingIndex] = window
windows = sortWindowsByLayout(updatedWindows) windows = sortWindowsByLayout(updatedWindows)
} else { return
windows = sortWindowsByLayout([...windows, window])
} }
windows = sortWindowsByLayout([...windows, window])
} }
function handleWindowLayoutsChanged(data) { function handleWindowLayoutsChanged(data) {
if (!data.changes) { if (!data.changes) return
return
}
const updatedWindows = [...windows] const updatedWindows = [...windows]
let hasChanges = false let hasChanges = false
@@ -319,28 +416,27 @@ Singleton {
const layoutData = change[1] const layoutData = change[1]
const windowIndex = updatedWindows.findIndex(w => w.id === windowId) const windowIndex = updatedWindows.findIndex(w => w.id === windowId)
if (windowIndex >= 0) { if (windowIndex < 0) continue
const updatedWindow = {}
for (var prop in updatedWindows[windowIndex]) { const updatedWindow = {}
updatedWindow[prop] = updatedWindows[windowIndex][prop] for (var prop in updatedWindows[windowIndex]) {
} updatedWindow[prop] = updatedWindows[windowIndex][prop]
updatedWindow.layout = layoutData
updatedWindows[windowIndex] = updatedWindow
hasChanges = true
} }
updatedWindow.layout = layoutData
updatedWindows[windowIndex] = updatedWindow
hasChanges = true
} }
if (hasChanges) { if (!hasChanges) return
windows = sortWindowsByLayout(updatedWindows)
windowsChanged() windows = sortWindowsByLayout(updatedWindows)
} windowsChanged()
} }
function handleOutputsChanged(data) { function handleOutputsChanged(data) {
if (data.outputs) { if (!data.outputs) return
outputs = data.outputs outputs = data.outputs
windows = sortWindowsByLayout(windows) windows = sortWindowsByLayout(windows)
}
} }
function handleOverviewChanged(data) { function handleOverviewChanged(data) {
@@ -380,9 +476,7 @@ Singleton {
function handleWorkspaceUrgencyChanged(data) { function handleWorkspaceUrgencyChanged(data) {
const ws = root.workspaces[data.id] const ws = root.workspaces[data.id]
if (!ws) { if (!ws) return
return
}
ws.is_urgent = data.urgent ws.is_urgent = data.urgent
@@ -394,29 +488,6 @@ Singleton {
windowUrgentChanged() windowUrgentChanged()
} }
Process {
id: validateProcess
command: ["niri", "validate"]
running: false
stderr: StdioCollector {
onStreamFinished: {
const lines = text.split('\n')
const trimmedLines = lines.map(line => line.replace(/\s+$/, '')).filter(line => line.length > 0)
configValidationOutput = trimmedLines.join('\n').trim()
if (hasInitialConnection) {
ToastService.showError("niri: failed to load config", configValidationOutput)
}
}
}
onExited: exitCode => {
if (exitCode === 0) {
configValidationOutput = ""
}
}
}
function updateCurrentOutputWorkspaces() { function updateCurrentOutputWorkspaces() {
if (!currentOutput) { if (!currentOutput) {
currentOutputWorkspaces = allWorkspaces currentOutputWorkspaces = allWorkspaces
@@ -428,59 +499,37 @@ Singleton {
} }
function send(request) { function send(request) {
if (!CompositorService.isNiri || !requestSocket.connected) { if (!CompositorService.isNiri || !requestSocket.connected) return false
return false
}
requestSocket.write(JSON.stringify(request) + "\n") requestSocket.write(JSON.stringify(request) + "\n")
return true return true
} }
function doScreenTransition() { function doScreenTransition() {
return send({ return send({"Action": {"DoScreenTransition": {"delay_ms": 0}}})
"Action": {
"DoScreenTransition": {
"delay_ms": 0,
}
}
})
} }
function switchToWorkspace(workspaceIndex) { function switchToWorkspace(workspaceIndex) {
return send({ return send({"Action": {"FocusWorkspace": {"reference": {"Index": workspaceIndex}}}})
"Action": {
"FocusWorkspace": {
"reference": {
"Index": workspaceIndex
}
}
}
})
} }
function focusWindow(windowId) { function focusWindow(windowId) {
return send({ return send({"Action": {"FocusWindow": {"id": windowId}}})
"Action": {
"FocusWindow": {
"id": windowId
}
}
})
} }
function powerOffMonitors() { function powerOffMonitors() {
return send({ return send({"Action": {"PowerOffMonitors": {}}})
"Action": {
"PowerOffMonitors": {}
}
})
} }
function powerOnMonitors() { function powerOnMonitors() {
return send({ return send({"Action": {"PowerOnMonitors": {}}})
"Action": { }
"PowerOnMonitors": {}
} function cycleKeyboardLayout() {
}) return send({"Action": {"SwitchLayout": {"layout": "Next"}}})
}
function quit() {
return send({"Action": {"Quit": {"skip_confirmation": true}}})
} }
function getCurrentOutputWorkspaceNumbers() { function getCurrentOutputWorkspaceNumbers() {
@@ -498,48 +547,22 @@ Singleton {
if (currentKeyboardLayoutIndex >= 0 && currentKeyboardLayoutIndex < keyboardLayoutNames.length) { if (currentKeyboardLayoutIndex >= 0 && currentKeyboardLayoutIndex < keyboardLayoutNames.length) {
return keyboardLayoutNames[currentKeyboardLayoutIndex] return keyboardLayoutNames[currentKeyboardLayoutIndex]
} }
return "" return ""
} }
function cycleKeyboardLayout() {
return send({
"Action": {
"SwitchLayout": {
"layout": "Next"
}
}
})
}
function quit() {
return send({
"Action": {
"Quit": {
"skip_confirmation": true
}
}
})
}
function suppressNextToast() { function suppressNextToast() {
matugenSuppression = true matugenSuppression = true
suppressResetTimer.restart() suppressResetTimer.restart()
} }
function findNiriWindow(toplevel) { function findNiriWindow(toplevel) {
if (!toplevel.appId) { if (!toplevel.appId) return null
return null
}
for (var j = 0; j < windows.length; j++) { for (var j = 0; j < windows.length; j++) {
const niriWindow = windows[j] const niriWindow = windows[j]
if (niriWindow.app_id === toplevel.appId) { if (niriWindow.app_id === toplevel.appId) {
if (!niriWindow.title || niriWindow.title === toplevel.title) { if (!niriWindow.title || niriWindow.title === toplevel.title) {
return { return {"niriIndex": j, "niriWindow": niriWindow}
"niriIndex": j,
"niriWindow": niriWindow
}
} }
} }
} }
@@ -564,40 +587,41 @@ Singleton {
if (niriWindow.title && toplevel.title === niriWindow.title) { if (niriWindow.title && toplevel.title === niriWindow.title) {
bestMatch = toplevel bestMatch = toplevel
break break
} else if (!niriWindow.title && !bestMatch) { }
if (!niriWindow.title && !bestMatch) {
bestMatch = toplevel bestMatch = toplevel
} }
} }
} }
if (bestMatch) { if (!bestMatch) continue
usedToplevels.add(bestMatch)
const enrichedToplevel = { usedToplevels.add(bestMatch)
appId: bestMatch.appId,
title: bestMatch.title, const enrichedToplevel = {
activated: bestMatch.activated, appId: bestMatch.appId,
niriWindowId: niriWindow.id, title: bestMatch.title,
niriWorkspaceId: niriWindow.workspace_id, activated: bestMatch.activated,
activate: function() { niriWindowId: niriWindow.id,
return NiriService.focusWindow(niriWindow.id) niriWorkspaceId: niriWindow.workspace_id,
}, activate: function() {
close: function() { return NiriService.focusWindow(niriWindow.id)
if (bestMatch.close) { },
return bestMatch.close() close: function() {
} if (bestMatch.close) {
return false return bestMatch.close()
} }
return false
} }
for (let prop in bestMatch) {
if (!(prop in enrichedToplevel)) {
enrichedToplevel[prop] = bestMatch[prop]
}
}
enrichedToplevels.push(enrichedToplevel)
} }
for (let prop in bestMatch) {
if (!(prop in enrichedToplevel)) {
enrichedToplevel[prop] = bestMatch[prop]
}
}
enrichedToplevels.push(enrichedToplevel)
} }
for (const toplevel of toplevels) { for (const toplevel of toplevels) {
@@ -611,6 +635,7 @@ Singleton {
function filterCurrentWorkspace(toplevels, screenName) { function filterCurrentWorkspace(toplevels, screenName) {
let currentWorkspaceId = null let currentWorkspaceId = null
for (var i = 0; i < allWorkspaces.length; i++) { for (var i = 0; i < allWorkspaces.length; i++) {
const ws = allWorkspaces[i] const ws = allWorkspaces[i]
if (ws.output === screenName && ws.is_active) { if (ws.output === screenName && ws.is_active) {
@@ -619,9 +644,7 @@ Singleton {
} }
} }
if (currentWorkspaceId === null) { if (currentWorkspaceId === null) return toplevels
return toplevels
}
const workspaceWindows = windows.filter(niriWindow => niriWindow.workspace_id === currentWorkspaceId) const workspaceWindows = windows.filter(niriWindow => niriWindow.workspace_id === currentWorkspaceId)
const usedToplevels = new Set() const usedToplevels = new Set()
@@ -637,73 +660,50 @@ Singleton {
if (niriWindow.title && toplevel.title === niriWindow.title) { if (niriWindow.title && toplevel.title === niriWindow.title) {
bestMatch = toplevel bestMatch = toplevel
break break
} else if (!niriWindow.title && !bestMatch) { }
if (!niriWindow.title && !bestMatch) {
bestMatch = toplevel bestMatch = toplevel
} }
} }
} }
if (bestMatch) { if (!bestMatch) continue
usedToplevels.add(bestMatch)
const enrichedToplevel = { usedToplevels.add(bestMatch)
appId: bestMatch.appId,
title: bestMatch.title, const enrichedToplevel = {
activated: bestMatch.activated, appId: bestMatch.appId,
niriWindowId: niriWindow.id, title: bestMatch.title,
niriWorkspaceId: niriWindow.workspace_id, activated: bestMatch.activated,
activate: function() { niriWindowId: niriWindow.id,
return NiriService.focusWindow(niriWindow.id) niriWorkspaceId: niriWindow.workspace_id,
}, activate: function() {
close: function() { return NiriService.focusWindow(niriWindow.id)
if (bestMatch.close) { },
return bestMatch.close() close: function() {
} if (bestMatch.close) {
return false return bestMatch.close()
} }
return false
} }
for (let prop in bestMatch) {
if (!(prop in enrichedToplevel)) {
enrichedToplevel[prop] = bestMatch[prop]
}
}
result.push(enrichedToplevel)
} }
for (let prop in bestMatch) {
if (!(prop in enrichedToplevel)) {
enrichedToplevel[prop] = bestMatch[prop]
}
}
result.push(enrichedToplevel)
} }
return result return result
} }
Timer {
id: suppressToastTimer
interval: 3000
onTriggered: root.suppressConfigToast = false
}
Timer {
id: suppressResetTimer
interval: 2000
onTriggered: root.matugenSuppression = false
}
Timer {
id: configGenerationDebounce
interval: 100
onTriggered: root.doGenerateNiriLayoutConfig()
}
function generateNiriLayoutConfig() { function generateNiriLayoutConfig() {
const niriSocket = Quickshell.env("NIRI_SOCKET") const niriSocket = Quickshell.env("NIRI_SOCKET")
if (!niriSocket || niriSocket.length === 0) { if (!niriSocket || niriSocket.length === 0) return
return if (configGenerationPending) return
}
if (configGenerationPending) {
return
}
configGenerationPending = true configGenerationPending = true
configGenerationDebounce.restart() configGenerationDebounce.restart()
@@ -757,32 +757,4 @@ window-rule {
writeBindsProcess.command = ["sh", "-c", `mkdir -p "${niriDmsDir}" && cp "${sourceBindsPath}" "${bindsPath}"`] writeBindsProcess.command = ["sh", "-c", `mkdir -p "${niriDmsDir}" && cp "${sourceBindsPath}" "${bindsPath}"`]
writeBindsProcess.running = true writeBindsProcess.running = true
} }
Process {
id: writeConfigProcess
property string configContent: ""
property string configPath: ""
onExited: exitCode => {
if (exitCode === 0) {
console.log("NiriService: Generated layout config at", configPath)
} else {
console.warn("NiriService: Failed to write layout config, exit code:", exitCode)
}
}
}
Process {
id: writeBindsProcess
property string bindsPath: ""
onExited: exitCode => {
if (exitCode === 0) {
console.log("NiriService: Generated binds config at", bindsPath)
} else {
console.warn("NiriService: Failed to write binds config, exit code:", exitCode)
}
}
}
} }