diff options
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/DwlService.qml')
| -rw-r--r-- | raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/DwlService.qml | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/DwlService.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/DwlService.qml new file mode 100644 index 0000000..564e561 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Services/DwlService.qml @@ -0,0 +1,454 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtCore +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Common + +Singleton { + id: root + + readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + readonly property string mangoDmsDir: configDir + "/mango/dms" + readonly property string outputsPath: mangoDmsDir + "/outputs.conf" + readonly property string layoutPath: mangoDmsDir + "/layout.conf" + readonly property string cursorPath: mangoDmsDir + "/cursor.conf" + + property int _lastGapValue: -1 + + property bool dwlAvailable: false + property var outputs: ({}) + property var tagCount: 9 + property var layouts: [] + property string activeOutput: "" + property var outputScales: ({}) + property string currentKeyboardLayout: { + if (!outputs || !activeOutput) + return ""; + const output = outputs[activeOutput]; + return (output && output.kbLayout) || ""; + } + + signal stateChanged + + Connections { + target: SettingsData + function onBarConfigsChanged() { + if (!CompositorService.isDwl) + return; + const newGaps = Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4)); + if (newGaps === root._lastGapValue) + return; + root._lastGapValue = newGaps; + generateLayoutConfig(); + } + } + + Connections { + target: CompositorService + function onIsDwlChanged() { + if (CompositorService.isDwl) + generateLayoutConfig(); + } + } + + Connections { + target: DMSService + function onCapabilitiesReceived() { + checkCapabilities(); + } + function onConnectionStateChanged() { + if (DMSService.isConnected) { + checkCapabilities(); + } else { + dwlAvailable = false; + } + } + function onDwlStateUpdate(data) { + if (dwlAvailable) { + handleStateUpdate(data); + } + } + } + + Component.onCompleted: { + if (DMSService.dmsAvailable) + checkCapabilities(); + if (dwlAvailable) + refreshOutputScales(); + if (CompositorService.isDwl) + Qt.callLater(generateLayoutConfig); + } + + function checkCapabilities() { + if (!DMSService.capabilities || !Array.isArray(DMSService.capabilities)) { + dwlAvailable = false; + return; + } + + const hasDwl = DMSService.capabilities.includes("dwl"); + if (hasDwl && !dwlAvailable) { + dwlAvailable = true; + console.info("DwlService: DWL capability detected"); + requestState(); + refreshOutputScales(); + } else if (!hasDwl) { + dwlAvailable = false; + } + } + + function requestState() { + if (!DMSService.isConnected || !dwlAvailable) { + return; + } + + DMSService.sendRequest("dwl.getState", null, response => { + if (response.result) { + handleStateUpdate(response.result); + } + }); + } + + function handleStateUpdate(state) { + outputs = state.outputs || {}; + tagCount = state.tagCount || 9; + layouts = state.layouts || []; + activeOutput = state.activeOutput || ""; + stateChanged(); + } + + function setTags(outputName, tagmask, toggleTagset) { + if (!DMSService.isConnected || !dwlAvailable) { + return; + } + + DMSService.sendRequest("dwl.setTags", { + "output": outputName, + "tagmask": tagmask, + "toggleTagset": toggleTagset + }, response => { + if (response.error) { + console.warn("DwlService: setTags error:", response.error); + } + }); + } + + function setClientTags(outputName, andTags, xorTags) { + if (!DMSService.isConnected || !dwlAvailable) { + return; + } + + DMSService.sendRequest("dwl.setClientTags", { + "output": outputName, + "andTags": andTags, + "xorTags": xorTags + }, response => { + if (response.error) { + console.warn("DwlService: setClientTags error:", response.error); + } + }); + } + + function setLayout(outputName, index) { + if (!DMSService.isConnected || !dwlAvailable) { + return; + } + + DMSService.sendRequest("dwl.setLayout", { + "output": outputName, + "index": index + }, response => { + if (response.error) { + console.warn("DwlService: setLayout error:", response.error); + } + }); + } + + function getOutputState(outputName) { + if (!outputs || !outputs[outputName]) { + return null; + } + return outputs[outputName]; + } + + function getActiveTags(outputName) { + const output = getOutputState(outputName); + if (!output || !output.tags) { + return []; + } + return output.tags.filter(tag => tag.state === 1).map(tag => tag.tag); + } + + function getTagsWithClients(outputName) { + const output = getOutputState(outputName); + if (!output || !output.tags) { + return []; + } + return output.tags.filter(tag => tag.clients > 0).map(tag => tag.tag); + } + + function getUrgentTags(outputName) { + const output = getOutputState(outputName); + if (!output || !output.tags) { + return []; + } + return output.tags.filter(tag => tag.state === 2).map(tag => tag.tag); + } + + function switchToTag(outputName, tagIndex) { + const tagmask = 1 << tagIndex; + setTags(outputName, tagmask, 0); + } + + function toggleTag(outputName, tagIndex) { + const output = getOutputState(outputName); + if (!output || !output.tags) { + console.log("toggleTag: no output or tags for", outputName); + return; + } + + let currentMask = 0; + output.tags.forEach(tag => { + if (tag.state === 1) { + currentMask |= (1 << tag.tag); + } + }); + + const clickedMask = 1 << tagIndex; + const newMask = currentMask ^ clickedMask; + + console.log("toggleTag:", outputName, "tag:", tagIndex, "currentMask:", currentMask.toString(2), "clickedMask:", clickedMask.toString(2), "newMask:", newMask.toString(2)); + + if (newMask === 0) { + console.log("toggleTag: newMask is 0, switching to tag", tagIndex); + setTags(outputName, 1 << tagIndex, 0); + } else { + console.log("toggleTag: setting combined mask", newMask); + setTags(outputName, newMask, 0); + } + } + + function quit() { + Quickshell.execDetached(["mmsg", "-d", "quit"]); + } + + Process { + id: scaleQueryProcess + command: ["mmsg", "-A"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + try { + const newScales = {}; + const lines = text.trim().split('\n'); + for (const line of lines) { + const parts = line.trim().split(/\s+/); + if (parts.length >= 3 && parts[1] === "scale_factor") { + const outputName = parts[0]; + const scale = parseFloat(parts[2]); + if (!isNaN(scale)) { + newScales[outputName] = scale; + } + } + } + outputScales = newScales; + } catch (e) { + console.warn("DwlService: Failed to parse mmsg output:", e); + } + } + } + + onExited: exitCode => { + if (exitCode !== 0) { + console.warn("DwlService: mmsg failed with exit code:", exitCode); + } + } + } + + function refreshOutputScales() { + if (!dwlAvailable) + return; + scaleQueryProcess.running = true; + } + + function getOutputScale(outputName) { + return outputScales[outputName]; + } + + function getVisibleTags(outputName) { + const output = getOutputState(outputName); + if (!output || !output.tags) { + return []; + } + + const visibleTags = new Set(); + + output.tags.forEach(tag => { + if (tag.state === 1 || tag.clients > 0) { + visibleTags.add(tag.tag); + } + }); + + return Array.from(visibleTags).sort((a, b) => a - b); + } + + function generateOutputsConfig(outputsData) { + if (!outputsData || Object.keys(outputsData).length === 0) + return; + let lines = ["# Auto-generated by DMS - do not edit manually", ""]; + + for (const outputName in outputsData) { + const output = outputsData[outputName]; + if (!output) + continue; + let width = 1920; + let height = 1080; + let refreshRate = 60; + if (output.modes && output.current_mode !== undefined) { + const mode = output.modes[output.current_mode]; + if (mode) { + width = mode.width || 1920; + height = mode.height || 1080; + refreshRate = Math.round((mode.refresh_rate || 60000) / 1000); + } + } + + const x = output.logical?.x ?? 0; + const y = output.logical?.y ?? 0; + const scale = output.logical?.scale ?? 1.0; + const transform = transformToMango(output.logical?.transform ?? "Normal"); + const vrr = output.vrr_enabled ? 1 : 0; + + const rule = ["name:" + outputName, "width:" + width, "height:" + height, "refresh:" + refreshRate, "x:" + x, "y:" + y, "scale:" + scale, "rr:" + transform, "vrr:" + vrr].join(","); + + lines.push("monitorrule=" + rule); + } + + lines.push(""); + + const content = lines.join("\n"); + + Proc.runCommand("mango-write-outputs", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && cat > "${outputsPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => { + if (exitCode !== 0) { + console.warn("DwlService: Failed to write outputs config:", output); + return; + } + console.info("DwlService: Generated outputs config at", outputsPath); + if (CompositorService.isDwl) + reloadConfig(); + }); + } + + function reloadConfig() { + Proc.runCommand("mango-reload", ["mmsg", "-d", "reload_config"], (output, exitCode) => { + if (exitCode !== 0) + console.warn("DwlService: mmsg reload_config failed:", output); + }); + } + + function generateLayoutConfig() { + if (!CompositorService.isDwl) + return; + + const defaultRadius = typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12; + const defaultGaps = typeof SettingsData !== "undefined" ? Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4)) : 4; + const defaultBorderSize = 2; + + const cornerRadius = (typeof SettingsData !== "undefined" && SettingsData.mangoLayoutRadiusOverride >= 0) ? SettingsData.mangoLayoutRadiusOverride : defaultRadius; + const gaps = (typeof SettingsData !== "undefined" && SettingsData.mangoLayoutGapsOverride >= 0) ? SettingsData.mangoLayoutGapsOverride : defaultGaps; + const borderSize = (typeof SettingsData !== "undefined" && SettingsData.mangoLayoutBorderSize >= 0) ? SettingsData.mangoLayoutBorderSize : defaultBorderSize; + + let content = `# Auto-generated by DMS - do not edit manually +border_radius=${cornerRadius} +gappih=${gaps} +gappiv=${gaps} +gappoh=${gaps} +gappov=${gaps} +borderpx=${borderSize} +`; + + Proc.runCommand("mango-write-layout", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && cat > "${layoutPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => { + if (exitCode !== 0) { + console.warn("DwlService: Failed to write layout config:", output); + return; + } + console.info("DwlService: Generated layout config at", layoutPath); + reloadConfig(); + }); + } + + function transformToMango(transform) { + switch (transform) { + case "Normal": + return 0; + case "90": + return 1; + case "180": + return 2; + case "270": + return 3; + case "Flipped": + return 4; + case "Flipped90": + return 5; + case "Flipped180": + return 6; + case "Flipped270": + return 7; + default: + return 0; + } + } + + function generateCursorConfig() { + if (!CompositorService.isDwl) + return; + + console.log("DwlService: Generating cursor config..."); + + const settings = typeof SettingsData !== "undefined" ? SettingsData.cursorSettings : null; + if (!settings) { + Proc.runCommand("mango-write-cursor", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && : > "${cursorPath}"`], (output, exitCode) => { + if (exitCode !== 0) + console.warn("DwlService: Failed to write cursor config:", output); + }); + return; + } + + const themeName = settings.theme === "System Default" ? (SettingsData.systemDefaultCursorTheme || "") : settings.theme; + const size = settings.size || 24; + const hideTimeout = settings.dwl?.cursorHideTimeout || 0; + + const isDefaultConfig = !themeName && size === 24 && hideTimeout === 0; + if (isDefaultConfig) { + Proc.runCommand("mango-write-cursor", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && : > "${cursorPath}"`], (output, exitCode) => { + if (exitCode !== 0) + console.warn("DwlService: Failed to write cursor config:", output); + }); + return; + } + + let content = `# Auto-generated by DMS - do not edit manually +cursor_size=${size}`; + + if (themeName) + content += `\ncursor_theme=${themeName}`; + + if (hideTimeout > 0) + content += `\ncursor_hide_timeout=${hideTimeout}`; + + content += `\n`; + + Proc.runCommand("mango-write-cursor", ["sh", "-c", `mkdir -p "${mangoDmsDir}" && cat > "${cursorPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => { + if (exitCode !== 0) { + console.warn("DwlService: Failed to write cursor config:", output); + return; + } + console.info("DwlService: Generated cursor config at", cursorPath); + reloadConfig(); + }); + } +} |