From 5983ba2fd1626c04293f72e56963bdb35b30f036 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Mon, 24 Nov 2025 18:39:19 -0500 Subject: [PATCH] Mango: fixed connection "on toplevels changed". --- Services/Compositor/MangoService.qml | 867 ++++++++++++++------------- 1 file changed, 465 insertions(+), 402 deletions(-) diff --git a/Services/Compositor/MangoService.qml b/Services/Compositor/MangoService.qml index 07b8b34d..e277fcd1 100644 --- a/Services/Compositor/MangoService.qml +++ b/Services/Compositor/MangoService.qml @@ -7,457 +7,520 @@ import qs.Services.Keyboard import qs.Services.UI Item { - id: root + id: root - // 1. FACADE INTERFACE - property ListModel workspaces: ListModel {} - property var windows: [] - property int focusedWindowIndex: -1 + // 1. FACADE INTERFACE + property ListModel workspaces: ListModel {} + property var windows: [] + property int focusedWindowIndex: -1 - signal workspaceChanged - signal activeWindowChanged - signal windowListChanged - signal displayScalesChanged + signal workspaceChanged + signal activeWindowChanged + signal windowListChanged + signal displayScalesChanged - property string selectedMonitor: "" - property string currentLayoutSymbol: "" - property bool initialized: false + property string selectedMonitor: "" + property string currentLayoutSymbol: "" + property bool initialized: false - // 2. CONFIGURATION - QtObject { - id: config - readonly property int defaultWorkspaceId: 1 - - // Pre-compiled Regex for Performance - readonly property var reTagDetail: /^(\S+)\s+tag\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/ - readonly property var reTagBinary: /^(\S+)\s+tags\s+([01]+)\s+([01]+)\s+([01]+)$/ - readonly property var reLayout: /^(\S+)\s+layout\s+(\S+)$/ - readonly property var reMetadata: /^(\S+)\s+(title|appid)\s+(.*)$/ - readonly property var reKbLayout: /^(\S+)\s+kb_layout\s+(.*)$/ - readonly property var reScale: /^(\S+)\s+scale_factor\s+(\d+(\.\d+)?)$/ - - readonly property var query: ({ - eventStream: ["mmsg", "-w"], - monitors: ["mmsg", "-g", "-o"], - outputs: ["mmsg", "-g", "-A"], // Scales - workspaces: ["mmsg", "-g", "-t"] - }) + // 2. CONFIGURATION + QtObject { + id: config + readonly property int defaultWorkspaceId: 1 - readonly property var action: ({ - tag: ["mmsg", "-s", "-t"], - view: ["mmsg", "-s", "-d", "view"], - toggleOverview: ["mmsg", "-s", "-d", "toggleoverview"], - setLayout: ["mmsg", "-s", "-d", "setlayout"], - killClient: ["mmsg", "-s", "-d", "killclient"], - quit: ["mmsg", "-s", "-q"] - }) + // Pre-compiled Regex for Performance + readonly property var reTagDetail: /^(\S+)\s+tag\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/ + readonly property var reTagBinary: /^(\S+)\s+tags\s+([01]+)\s+([01]+)\s+([01]+)$/ + readonly property var reLayout: /^(\S+)\s+layout\s+(\S+)$/ + readonly property var reMetadata: /^(\S+)\s+(title|appid)\s+(.*)$/ + readonly property var reKbLayout: /^(\S+)\s+kb_layout\s+(.*)$/ + readonly property var reScale: /^(\S+)\s+scale_factor\s+(\d+(\.\d+)?)$/ + + readonly property var query: ({ + eventStream: ["mmsg", "-w"], + monitors: ["mmsg", "-g", "-o"], + outputs: ["mmsg", "-g", "-A"] // Scales + , + workspaces: ["mmsg", "-g", "-t"] + }) + + readonly property var action: ({ + tag: ["mmsg", "-s", "-t"], + view: ["mmsg", "-s", "-d", "view"], + toggleOverview: ["mmsg", "-s", "-d", "toggleoverview"], + setLayout: ["mmsg", "-s", "-d", "setlayout"], + killClient: ["mmsg", "-s", "-d", "killclient"], + quit: ["mmsg", "-s", "-q"] + }) + } + + // 3. LOGIC ENGINE + QtObject { + id: internal + + // State + property var activeTags: ({}) + property var multiTagState: ({}) + property bool hasValidTagData: false + + // Map + property var windowStateMap: new Map() + + property string mmsgFocusedTitle: "" + property string mmsgFocusedAppId: "" + property string currentKbLayout: "" + + // Caches + property var workspaceCache: ({}) + property var monitorScales: ({}) + property string lastWindowSig: "" + + // Buffers + property string streamBuffer: "" + + // --- STREAM PROCESSOR --- + function processFrame(output) { + const lines = output.trim().split('\n'); + const newWsList = []; + const newWsCache = {}; + const processedTags = {}; + let metadataChanged = false; + let receivedClientCounts = false; // Track if we got real numbers + + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + if (!line) + continue; + + // 1. Tag Details (High Quality Data: Contains Client Counts) + const tagMatch = line.match(config.reTagDetail); + if (tagMatch) { + const outputName = tagMatch[1]; + const tagId = parseInt(tagMatch[2]); + const state = parseInt(tagMatch[3]); + const clients = parseInt(tagMatch[4]); + const focused = parseInt(tagMatch[5]); + const isActive = (state & 1) !== 0; + + if (isActive) + internal.activeTags[outputName] = tagId; + const wsData = { + "id": tagId, + "idx": tagId, + "name": tagId.toString(), + "output": outputName, + "isActive": isActive, + "isFocused": isActive && (focused === 1 || outputName === root.selectedMonitor), + "isUrgent": (state & 2) !== 0, + "isOccupied": clients > 0, + "clients": clients // We have the real number! + }; + const key = `${outputName}-${tagId}`; + newWsCache[key] = wsData; + newWsList.push(wsData); + processedTags[key] = true; + receivedClientCounts = true; + continue; + } + + // 2. Binary Tags (Low Quality Data: No Client Counts) + const tagsMatch = line.match(config.reTagBinary); + if (tagsMatch) { + const outputName = tagsMatch[1]; + const occ = tagsMatch[2]; + const seltags = tagsMatch[3]; + const urg = tagsMatch[4]; + const len = occ.length; + + // Overview Detection + let activeCount = 0; + for (let c = 0; c < seltags.length; c++) + if (seltags[c] === '1') + activeCount++; + internal.multiTagState[outputName] = (activeCount > 1); + + for (let j = 0; j < len; j++) { + const tagId = j + 1; + const charIdx = len - 1 - j; + const key = `${outputName}-${tagId}`; + + if (processedTags[key]) + continue; + const isActive = seltags[charIdx] === '1'; + const isOccupied = occ[charIdx] === '1'; + + if (isActive) + internal.activeTags[outputName] = tagId; + newWsList.push({ + "id": tagId, + "idx": tagId, + "name": tagId.toString(), + "output": outputName, + "isActive": isActive, + "isFocused": false, + "isUrgent": urg[charIdx] === '1', + "isOccupied": isOccupied, + "clients": isOccupied ? -1 : 0 // -1 indicates "Unknown, but at least 1" + }); + } + continue; + } + + // 3. Metadata + const metaMatch = line.match(config.reMetadata); + if (metaMatch) { + const prop = metaMatch[2]; + const val = metaMatch[3]; + if (prop === "title") + internal.mmsgFocusedTitle = val; + else if (prop === "appid") + internal.mmsgFocusedAppId = val; + metadataChanged = true; + continue; + } + + // 4. Layout + const layoutMatch = line.match(config.reLayout); + if (layoutMatch) { + if (layoutMatch[2] !== root.currentLayoutSymbol) + root.currentLayoutSymbol = layoutMatch[2]; + } + + // 5. Keyboard + const kbMatch = line.match(config.reKbLayout); + if (kbMatch) { + const kbName = kbMatch[2]; + if (kbName !== internal.currentKbLayout) { + internal.currentKbLayout = kbName; + if (KeyboardLayoutService) + KeyboardLayoutService.setCurrentLayout(kbName); + } + } + } + + // Apply + if (JSON.stringify(newWsCache) !== JSON.stringify(internal.workspaceCache)) { + internal.workspaceCache = newWsCache; + newWsList.sort((a, b) => { + if (a.id !== b.id) + return a.id - b.id; + return a.output.localeCompare(b.output); + }); + root.workspaces.clear(); + for (let k = 0; k < newWsList.length; k++) + root.workspaces.append(newWsList[k]); + root.workspaceChanged(); + + if (receivedClientCounts) + internal.hasValidTagData = true; + } + + if (metadataChanged || newWsList.length > 0) { + internal.updateWindowList(); + } } - // 3. LOGIC ENGINE - QtObject { - id: internal + // --- WINDOW LIST MERGE --- + function updateWindowList() { + if (!ToplevelManager.toplevels) + return; - // State - property var activeTags: ({}) - property var multiTagState: ({}) - property bool hasValidTagData: false - - // Map - property var windowStateMap: new Map() - - property string mmsgFocusedTitle: "" - property string mmsgFocusedAppId: "" - property string currentKbLayout: "" - - // Caches - property var workspaceCache: ({}) - property var monitorScales: ({}) - property string lastWindowSig: "" + // FIX: SAFETY CHECK + // If we haven't received "Detailed" data yet (only binary), + // we don't know where the windows are. Abort to prevent map poisoning. + if (!internal.hasValidTagData) + return; - // Buffers - property string streamBuffer: "" + const rawList = ToplevelManager.toplevels.values; + const finalList = []; + let newFocusedIdx = -1; - // --- STREAM PROCESSOR --- - function processFrame(output) { - const lines = output.trim().split('\n'); - const newWsList = []; - const newWsCache = {}; - const processedTags = {}; - let metadataChanged = false; - let receivedClientCounts = false; // Track if we got real numbers + const currentObjects = new Set(); + const tagCapacities = new Map(); - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim(); - if (!line) continue; + // Build Capacities + let totalCapacity = 0; + for (let key in internal.workspaceCache) { + const ws = internal.workspaceCache[key]; + const cap = ws.clients > 0 ? ws.clients : 0; + tagCapacities.set(key, cap); + totalCapacity += cap; + } - // 1. Tag Details (High Quality Data: Contains Client Counts) - const tagMatch = line.match(config.reTagDetail); - if (tagMatch) { - const outputName = tagMatch[1]; - const tagId = parseInt(tagMatch[2]); - const state = parseInt(tagMatch[3]); - const clients = parseInt(tagMatch[4]); - const focused = parseInt(tagMatch[5]); - const isActive = (state & 1) !== 0; - - if (isActive) internal.activeTags[outputName] = tagId; - const wsData = { - "id": tagId, "idx": tagId, "name": tagId.toString(), - "output": outputName, - "isActive": isActive, - "isFocused": isActive && (focused === 1 || outputName === root.selectedMonitor), - "isUrgent": (state & 2) !== 0, - "isOccupied": clients > 0, - "clients": clients // We have the real number! - }; - const key = `${outputName}-${tagId}`; - newWsCache[key] = wsData; - newWsList.push(wsData); - processedTags[key] = true; - receivedClientCounts = true; - continue; - } - - // 2. Binary Tags (Low Quality Data: No Client Counts) - const tagsMatch = line.match(config.reTagBinary); - if (tagsMatch) { - const outputName = tagsMatch[1]; - const occ = tagsMatch[2]; - const seltags = tagsMatch[3]; - const urg = tagsMatch[4]; - const len = occ.length; + if (totalCapacity === 0 && rawList.length > 0 && !internal.hasValidTagData) + return; - // Overview Detection - let activeCount = 0; - for (let c = 0; c < seltags.length; c++) if (seltags[c] === '1') activeCount++; - internal.multiTagState[outputName] = (activeCount > 1); + const unassignedWindows = []; - for (let j = 0; j < len; j++) { - const tagId = j + 1; - const charIdx = len - 1 - j; - const key = `${outputName}-${tagId}`; - - if (processedTags[key]) continue; - const isActive = seltags[charIdx] === '1'; - const isOccupied = occ[charIdx] === '1'; + // Pass 1: Known & Focused + for (let i = 0; i < rawList.length; i++) { + const toplevel = rawList[i]; + if (!toplevel || toplevel.outliers) + continue; - if (isActive) internal.activeTags[outputName] = tagId; - newWsList.push({ - "id": tagId, "idx": tagId, "name": tagId.toString(), - "output": outputName, - "isActive": isActive, - "isFocused": false, - "isUrgent": urg[charIdx] === '1', - "isOccupied": isOccupied, - "clients": isOccupied ? -1 : 0 // -1 indicates "Unknown, but at least 1" - }); - } - continue; - } + currentObjects.add(toplevel); - // 3. Metadata - const metaMatch = line.match(config.reMetadata); - if (metaMatch) { - const prop = metaMatch[2]; - const val = metaMatch[3]; - if (prop === "title") internal.mmsgFocusedTitle = val; - else if (prop === "appid") internal.mmsgFocusedAppId = val; - metadataChanged = true; - continue; - } - - // 4. Layout - const layoutMatch = line.match(config.reLayout); - if (layoutMatch) { - if (layoutMatch[2] !== root.currentLayoutSymbol) root.currentLayoutSymbol = layoutMatch[2]; - } + const appId = toplevel.appId || toplevel.wayland.appId || ""; + const title = toplevel.title || toplevel.wayland.title || ""; - // 5. Keyboard - const kbMatch = line.match(config.reKbLayout); - if (kbMatch) { - const kbName = kbMatch[2]; - if (kbName !== internal.currentKbLayout) { - internal.currentKbLayout = kbName; - if (KeyboardLayoutService) KeyboardLayoutService.setCurrentLayout(kbName); - } - } - } - - // Apply - if (JSON.stringify(newWsCache) !== JSON.stringify(internal.workspaceCache)) { - internal.workspaceCache = newWsCache; - newWsList.sort((a, b) => { - if (a.id !== b.id) return a.id - b.id; - return a.output.localeCompare(b.output); - }); - root.workspaces.clear(); - for (let k = 0; k < newWsList.length; k++) root.workspaces.append(newWsList[k]); - root.workspaceChanged(); - - if (receivedClientCounts) internal.hasValidTagData = true; - } - - if (metadataChanged || newWsList.length > 0) { - internal.updateWindowList(); - } + let outputName = root.selectedMonitor; + if (toplevel.outputs && toplevel.outputs.length > 0) { + outputName = toplevel.outputs[0].name; } - // --- WINDOW LIST MERGE --- - function updateWindowList() { - if (!ToplevelManager.toplevels) return; + const currentActiveTag = internal.activeTags[outputName] || config.defaultWorkspaceId; + const isMultiTag = internal.multiTagState[outputName] === true; + let wsId = -1; - // FIX: SAFETY CHECK - // If we haven't received "Detailed" data yet (only binary), - // we don't know where the windows are. Abort to prevent map poisoning. - if (!internal.hasValidTagData) return; + const isFocused = toplevel.activated; + const isMmsgFocus = (title === internal.mmsgFocusedTitle) && (appId === internal.mmsgFocusedAppId); - const rawList = ToplevelManager.toplevels.values; - const finalList = []; - let newFocusedIdx = -1; - - const currentObjects = new Set(); - const tagCapacities = new Map(); - - // Build Capacities - let totalCapacity = 0; - for (let key in internal.workspaceCache) { - const ws = internal.workspaceCache[key]; - const cap = ws.clients > 0 ? ws.clients : 0; - tagCapacities.set(key, cap); - totalCapacity += cap; - } - - - if (totalCapacity === 0 && rawList.length > 0 && !internal.hasValidTagData) return; - - const unassignedWindows = []; - - // Pass 1: Known & Focused - for (let i = 0; i < rawList.length; i++) { - const toplevel = rawList[i]; - if (!toplevel || toplevel.outliers) continue; - - currentObjects.add(toplevel); - - const appId = toplevel.appId || toplevel.wayland.appId || ""; - const title = toplevel.title || toplevel.wayland.title || ""; - - let outputName = root.selectedMonitor; - if (toplevel.outputs && toplevel.outputs.length > 0) { - outputName = toplevel.outputs[0].name; - } - - const currentActiveTag = internal.activeTags[outputName] || config.defaultWorkspaceId; - const isMultiTag = internal.multiTagState[outputName] === true; - let wsId = -1; - - const isFocused = toplevel.activated; - const isMmsgFocus = (title === internal.mmsgFocusedTitle) && - (appId === internal.mmsgFocusedAppId); - - if (isFocused && isMmsgFocus && !isMultiTag) { - wsId = currentActiveTag; - internal.windowStateMap.set(toplevel, wsId); - } - else if (internal.windowStateMap.has(toplevel)) { - wsId = internal.windowStateMap.get(toplevel); - } - - if (wsId !== -1) { - const key = `${outputName}-${wsId}`; - const cap = tagCapacities.get(key) || 0; - if (cap > 0) tagCapacities.set(key, cap - 1); - - finalList.push(createWindowObject(toplevel, outputName, appId, title, wsId, isFocused, i)); - } else { - unassignedWindows.push({ toplevel, outputName, appId, title, isFocused, index: i }); - } - } - - // Pass 2: Distribute Unknowns - for (const win of unassignedWindows) { - let assignedId = -1; - - for (let key in internal.workspaceCache) { - const ws = internal.workspaceCache[key]; - // Robust output check: match name OR if both are undefined/generic - if (ws.output !== win.outputName && win.outputName !== "") continue; - - const cap = tagCapacities.get(key) || 0; - if (cap > 0) { - assignedId = ws.id; - tagCapacities.set(key, cap - 1); - break; - } - } - - if (assignedId === -1) { - assignedId = internal.activeTags[win.outputName] || config.defaultWorkspaceId; - } - - internal.windowStateMap.set(win.toplevel, assignedId); - finalList.push(createWindowObject( - win.toplevel, win.outputName, win.appId, win.title, assignedId, win.isFocused, win.index - )); - } - - finalList.sort((a, b) => { - const idxA = parseInt(a.id.split('-').pop()); - const idxB = parseInt(b.id.split('-').pop()); - return idxA - idxB; - }); - - for(let i=0; i rawList.length + 10) { - for (const key of internal.windowStateMap.keys()) { - if (!currentObjects.has(key)) internal.windowStateMap.delete(key); - } - } - - const sig = JSON.stringify(finalList.map(w => w.id + w.workspaceId + w.isFocused)); - if (sig !== internal.lastWindowSig) { - internal.lastWindowSig = sig; - root.windows = finalList; - root.windowListChanged(); - } - - if (newFocusedIdx !== root.focusedWindowIndex) { - root.focusedWindowIndex = newFocusedIdx; - root.activeWindowChanged(); - } + if (isFocused && isMmsgFocus && !isMultiTag) { + wsId = currentActiveTag; + internal.windowStateMap.set(toplevel, wsId); + } else if (internal.windowStateMap.has(toplevel)) { + wsId = internal.windowStateMap.get(toplevel); } - function createWindowObject(toplevel, outputName, appId, title, wsId, isFocused, index) { - return { - "id": `${outputName}-${appId}-${index}`, - "title": title, - "appId": appId, - "class": appId, - "workspaceId": wsId, - "isFocused": isFocused, - "output": outputName, - "handle": toplevel, - "fullscreen": toplevel.fullscreen, - "floating": toplevel.maximized === false && toplevel.fullscreen === false - }; + if (wsId !== -1) { + const key = `${outputName}-${wsId}`; + const cap = tagCapacities.get(key) || 0; + if (cap > 0) + tagCapacities.set(key, cap - 1); + + finalList.push(createWindowObject(toplevel, outputName, appId, title, wsId, isFocused, i)); + } else { + unassignedWindows.push({ + toplevel, + outputName, + appId, + title, + isFocused, + index: i + }); + } + } + + // Pass 2: Distribute Unknowns + for (const win of unassignedWindows) { + let assignedId = -1; + + for (let key in internal.workspaceCache) { + const ws = internal.workspaceCache[key]; + // Robust output check: match name OR if both are undefined/generic + if (ws.output !== win.outputName && win.outputName !== "") + continue; + + const cap = tagCapacities.get(key) || 0; + if (cap > 0) { + assignedId = ws.id; + tagCapacities.set(key, cap - 1); + break; + } } - // --- SCALES --- - function updateScales() { - const scalesMap = {}; - for (const [name, data] of Object.entries(internal.monitorScales)) { - scalesMap[name] = { - "name": name, - "scale": data.scale || 1.0, - "width": 0, "height": 0, "x": 0, "y": 0 - }; - } - if (CompositorService && CompositorService.onDisplayScalesUpdated) { - CompositorService.onDisplayScalesUpdated(scalesMap); - } - root.displayScalesChanged(); + if (assignedId === -1) { + assignedId = internal.activeTags[win.outputName] || config.defaultWorkspaceId; } + + internal.windowStateMap.set(win.toplevel, assignedId); + finalList.push(createWindowObject(win.toplevel, win.outputName, win.appId, win.title, assignedId, win.isFocused, win.index)); + } + + finalList.sort((a, b) => { + const idxA = parseInt(a.id.split('-').pop()); + const idxB = parseInt(b.id.split('-').pop()); + return idxA - idxB; + }); + + for (let i = 0; i < finalList.length; i++) { + if (finalList[i].isFocused) + newFocusedIdx = i; + } + + if (internal.windowStateMap.size > rawList.length + 10) { + for (const key of internal.windowStateMap.keys()) { + if (!currentObjects.has(key)) + internal.windowStateMap.delete(key); + } + } + + const sig = JSON.stringify(finalList.map(w => w.id + w.workspaceId + w.isFocused)); + if (sig !== internal.lastWindowSig) { + internal.lastWindowSig = sig; + root.windows = finalList; + root.windowListChanged(); + } + + if (newFocusedIdx !== root.focusedWindowIndex) { + root.focusedWindowIndex = newFocusedIdx; + root.activeWindowChanged(); + } } - // 4. PROCESSES + function createWindowObject(toplevel, outputName, appId, title, wsId, isFocused, index) { + return { + "id": `${outputName}-${appId}-${index}`, + "title": title, + "appId": appId, + "class": appId, + "workspaceId": wsId, + "isFocused": isFocused, + "output": outputName, + "handle": toplevel, + "fullscreen": toplevel.fullscreen, + "floating": toplevel.maximized === false && toplevel.fullscreen === false + }; + } - Process { - id: eventStream - running: false - command: config.query.eventStream - stdout: SplitParser { - onRead: (line) => { + // --- SCALES --- + function updateScales() { + const scalesMap = {}; + for (const [name, data] of Object.entries(internal.monitorScales)) { + scalesMap[name] = { + "name": name, + "scale": data.scale || 1.0, + "width": 0, + "height": 0, + "x": 0, + "y": 0 + }; + } + if (CompositorService && CompositorService.onDisplayScalesUpdated) { + CompositorService.onDisplayScalesUpdated(scalesMap); + } + root.displayScalesChanged(); + } + } + + // 4. PROCESSES + + Process { + id: eventStream + running: false + command: config.query.eventStream + stdout: SplitParser { + onRead: line => { if (line.includes("selmon") && line.includes(" 1")) { - const parts = line.split(' '); - if (parts.length >= 3) root.selectedMonitor = parts[0]; - return; + const parts = line.split(' '); + if (parts.length >= 3) + root.selectedMonitor = parts[0]; + return; } internal.streamBuffer += line + "\n"; if (line.match(config.reTagBinary)) { - internal.processFrame(internal.streamBuffer); - internal.streamBuffer = ""; + internal.processFrame(internal.streamBuffer); + internal.streamBuffer = ""; } - } - } - onExited: (code) => { if (code !== 0) restartTimer.start(); } + } } - Timer { id: restartTimer; interval: 1000; onTriggered: if(initialized) eventStream.running = true } + onExited: code => { + if (code !== 0) + restartTimer.start(); + } + } + Timer { + id: restartTimer + interval: 1000 + onTriggered: if (initialized) + eventStream.running = true + } - Process { - id: procInitial - command: config.query.workspaces - stdout: SplitParser { onRead: (line) => internal.streamBuffer += line + "\n" } - onExited: (code) => { - if (code === 0) { - internal.processFrame(internal.streamBuffer); - internal.streamBuffer = ""; - } - } + Process { + id: procInitial + command: config.query.workspaces + stdout: SplitParser { + onRead: line => internal.streamBuffer += line + "\n" } + onExited: code => { + if (code === 0) { + internal.processFrame(internal.streamBuffer); + internal.streamBuffer = ""; + } + } + } - Process { - id: procOutputs - command: config.query.outputs - stdout: SplitParser { - onRead: (line) => { + Process { + id: procOutputs + command: config.query.outputs + stdout: SplitParser { + onRead: line => { const match = line.match(config.reScale); if (match) { - const out = match[1]; - const scale = parseFloat(match[2]); - if (!internal.monitorScales[out]) internal.monitorScales[out] = {}; - internal.monitorScales[out].scale = scale; + const out = match[1]; + const scale = parseFloat(match[2]); + if (!internal.monitorScales[out]) + internal.monitorScales[out] = {}; + internal.monitorScales[out].scale = scale; } - } - } - onExited: (code) => { if (code === 0) internal.updateScales(); } + } } + onExited: code => { + if (code === 0) + internal.updateScales(); + } + } - // 5. WAYLAND & INIT + // 5. WAYLAND & INIT - Connections { - target: ToplevelManager - function onToplevelsChanged() { internal.updateWindowList(); } - } - Timer { - interval: 200; running: true; repeat: true - onTriggered: internal.updateWindowList() + Connections { + target: ToplevelManager.toplevels + function onValuesChanged() { + internal.updateWindowList(); } + } + Timer { + interval: 200 + running: true + repeat: true + onTriggered: internal.updateWindowList() + } - function initialize() { - if (initialized) return; - Logger.i("MangoService", "Service Started"); - - procOutputs.running = true; - procInitial.running = true; - eventStream.running = true; - Quickshell.execDetached(["mmsg", "-g", "-o"]); - - initialized = true; - } + function initialize() { + if (initialized) + return; + Logger.i("MangoService", "Service Started"); - function queryDisplayScales() { procOutputs.running = true; } + procOutputs.running = true; + procInitial.running = true; + eventStream.running = true; + Quickshell.execDetached(["mmsg", "-g", "-o"]); - function switchToWorkspace(workspace) { - const tagId = workspace.idx || workspace.id || config.defaultWorkspaceId; - const output = workspace.output || root.selectedMonitor || ""; - const cmd = config.action.tag.slice(); - if (output && Object.keys(internal.monitorScales).length > 1) cmd.push("-o", output); - cmd.push(tagId.toString()); - Quickshell.execDetached(cmd); - } + initialized = true; + } - function focusWindow(window) { - if (window && window.handle) window.handle.activate(); - else if (window.workspaceId) switchToWorkspace({ id: window.workspaceId, output: window.output }); - } + function queryDisplayScales() { + procOutputs.running = true; + } - function closeWindow(window) { - if (window && window.handle) window.handle.close(); - else Quickshell.execDetached(config.action.killClient); - } + function switchToWorkspace(workspace) { + const tagId = workspace.idx || workspace.id || config.defaultWorkspaceId; + const output = workspace.output || root.selectedMonitor || ""; + const cmd = config.action.tag.slice(); + if (output && Object.keys(internal.monitorScales).length > 1) + cmd.push("-o", output); + cmd.push(tagId.toString()); + Quickshell.execDetached(cmd); + } - function logout() { Quickshell.execDetached(config.action.quit); } + function focusWindow(window) { + if (window && window.handle) + window.handle.activate(); + else if (window.workspaceId) + switchToWorkspace({ + id: window.workspaceId, + output: window.output + }); + } + + function closeWindow(window) { + if (window && window.handle) + window.handle.close(); + else + Quickshell.execDetached(config.action.killClient); + } + + function logout() { + Quickshell.execDetached(config.action.quit); + } }