pragma Singleton pragma ComponentBehavior: Bound import QtCore import QtQuick import Quickshell import qs.Common Singleton { id: root readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) readonly property string hyprDmsDir: configDir + "/hypr/dms" readonly property string outputsPath: hyprDmsDir + "/outputs.conf" readonly property string layoutPath: hyprDmsDir + "/layout.conf" readonly property string cursorPath: hyprDmsDir + "/cursor.conf" property int _lastGapValue: -1 Component.onCompleted: { if (CompositorService.isHyprland) Qt.callLater(generateLayoutConfig); } Connections { target: SettingsData function onBarConfigsChanged() { if (!CompositorService.isHyprland) return; const newGaps = Math.max(4, (SettingsData.barConfigs[0]?.spacing ?? 4)); if (newGaps === root._lastGapValue) return; root._lastGapValue = newGaps; generateLayoutConfig(); } } Connections { target: CompositorService function onIsHyprlandChanged() { if (CompositorService.isHyprland) generateLayoutConfig(); } } function getOutputIdentifier(output, outputName) { if (SettingsData.displayNameMode === "model" && output.make && output.model) return "desc:" + output.make + " " + output.model; return outputName; } function generateOutputsConfig(outputsData, hyprlandSettings) { if (!outputsData || Object.keys(outputsData).length === 0) return; const settings = hyprlandSettings || SettingsData.hyprlandOutputSettings; let lines = ["# Auto-generated by DMS - do not edit manually", ""]; let monitorv2Blocks = []; for (const outputName in outputsData) { const output = outputsData[outputName]; if (!output) continue; const identifier = getOutputIdentifier(output, outputName); const outputSettings = settings[identifier] || {}; if (outputSettings.disabled) { lines.push("monitor = " + identifier + ", disable"); continue; } let resolution = "preferred"; if (output.modes && output.current_mode !== undefined) { const mode = output.modes[output.current_mode]; if (mode) resolution = mode.width + "x" + mode.height + "@" + (mode.refresh_rate / 1000).toFixed(3); } const x = output.logical?.x ?? 0; const y = output.logical?.y ?? 0; const position = x + "x" + y; const scale = output.logical?.scale ?? 1.0; let monitorLine = "monitor = " + identifier + ", " + resolution + ", " + position + ", " + scale; const transform = transformToHyprland(output.logical?.transform ?? "Normal"); if (transform !== 0) monitorLine += ", transform, " + transform; if (output.vrr_supported && output.vrr_enabled) monitorLine += ", vrr, 1"; if (output.mirror && output.mirror.length > 0) monitorLine += ", mirror, " + output.mirror; if (outputSettings.bitdepth && outputSettings.bitdepth !== 8) monitorLine += ", bitdepth, " + outputSettings.bitdepth; if (outputSettings.colorManagement && outputSettings.colorManagement !== "auto") monitorLine += ", cm, " + outputSettings.colorManagement; if (outputSettings.sdrBrightness !== undefined && outputSettings.sdrBrightness !== 1.0) monitorLine += ", sdrbrightness, " + outputSettings.sdrBrightness; if (outputSettings.sdrSaturation !== undefined && outputSettings.sdrSaturation !== 1.0) monitorLine += ", sdrsaturation, " + outputSettings.sdrSaturation; lines.push(monitorLine); const needsMonitorv2 = outputSettings.supportsHdr || outputSettings.supportsWideColor || outputSettings.sdrMinLuminance !== undefined || outputSettings.sdrMaxLuminance !== undefined || outputSettings.minLuminance !== undefined || outputSettings.maxLuminance !== undefined || outputSettings.maxAvgLuminance !== undefined; if (needsMonitorv2) { let block = "monitorv2 {\n"; block += " output = " + identifier + "\n"; if (outputSettings.supportsWideColor) block += " supports_wide_color = true\n"; if (outputSettings.supportsHdr) block += " supports_hdr = true\n"; if (outputSettings.sdrMinLuminance !== undefined) block += " sdr_min_luminance = " + outputSettings.sdrMinLuminance + "\n"; if (outputSettings.sdrMaxLuminance !== undefined) block += " sdr_max_luminance = " + outputSettings.sdrMaxLuminance + "\n"; if (outputSettings.minLuminance !== undefined) block += " min_luminance = " + outputSettings.minLuminance + "\n"; if (outputSettings.maxLuminance !== undefined) block += " max_luminance = " + outputSettings.maxLuminance + "\n"; if (outputSettings.maxAvgLuminance !== undefined) block += " max_avg_luminance = " + outputSettings.maxAvgLuminance + "\n"; block += "}"; monitorv2Blocks.push(block); } } if (monitorv2Blocks.length > 0) { lines.push(""); for (const block of monitorv2Blocks) lines.push(block); } lines.push(""); const content = lines.join("\n"); Proc.runCommand("hypr-write-outputs", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && cat > "${outputsPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => { if (exitCode !== 0) { console.warn("HyprlandService: Failed to write outputs config:", output); return; } console.info("HyprlandService: Generated outputs config at", outputsPath); if (CompositorService.isHyprland) reloadConfig(); }); } function reloadConfig() { Proc.runCommand("hyprctl-reload", ["hyprctl", "reload"], (output, exitCode) => { if (exitCode !== 0) console.warn("HyprlandService: hyprctl reload failed:", output); }); } function generateLayoutConfig() { if (!CompositorService.isHyprland) 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.hyprlandLayoutRadiusOverride >= 0) ? SettingsData.hyprlandLayoutRadiusOverride : defaultRadius; const gaps = (typeof SettingsData !== "undefined" && SettingsData.hyprlandLayoutGapsOverride >= 0) ? SettingsData.hyprlandLayoutGapsOverride : defaultGaps; const borderSize = (typeof SettingsData !== "undefined" && SettingsData.hyprlandLayoutBorderSize >= 0) ? SettingsData.hyprlandLayoutBorderSize : defaultBorderSize; let content = `# Auto-generated by DMS - do not edit manually general { gaps_in = ${gaps} gaps_out = ${gaps} border_size = ${borderSize} } decoration { rounding = ${cornerRadius} } `; Proc.runCommand("hypr-write-layout", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && cat > "${layoutPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => { if (exitCode !== 0) { console.warn("HyprlandService: Failed to write layout config:", output); return; } console.info("HyprlandService: Generated layout config at", layoutPath); reloadConfig(); }); } function transformToHyprland(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 hyprlandToTransform(value) { switch (value) { case 0: return "Normal"; case 1: return "90"; case 2: return "180"; case 3: return "270"; case 4: return "Flipped"; case 5: return "Flipped90"; case 6: return "Flipped180"; case 7: return "Flipped270"; default: return "Normal"; } } function generateCursorConfig() { if (!CompositorService.isHyprland) return; const settings = typeof SettingsData !== "undefined" ? SettingsData.cursorSettings : null; if (!settings) { Proc.runCommand("hypr-write-cursor", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && : > "${cursorPath}"`], (output, exitCode) => { if (exitCode !== 0) console.warn("HyprlandService: Failed to write cursor config:", output); }); return; } const themeName = settings.theme === "System Default" ? (SettingsData.systemDefaultCursorTheme || "") : settings.theme; const size = settings.size || 24; const hideOnKeyPress = settings.hyprland?.hideOnKeyPress || false; const hideOnTouch = settings.hyprland?.hideOnTouch || false; const inactiveTimeout = settings.hyprland?.inactiveTimeout || 0; const hasTheme = themeName && themeName.length > 0; const hasNonDefaultSize = size !== 24; const hasCursorSettings = hideOnKeyPress || hideOnTouch || inactiveTimeout > 0; if (!hasTheme && !hasNonDefaultSize && !hasCursorSettings) { Proc.runCommand("hypr-write-cursor", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && : > "${cursorPath}"`], (output, exitCode) => { if (exitCode !== 0) console.warn("HyprlandService: Failed to write cursor config:", output); }); return; } let lines = ["# Auto-generated by DMS - do not edit manually", ""]; if (hasTheme) { lines.push(`env = HYPRCURSOR_THEME,${themeName}`); lines.push(`env = XCURSOR_THEME,${themeName}`); } lines.push(`env = HYPRCURSOR_SIZE,${size}`); lines.push(`env = XCURSOR_SIZE,${size}`); if (hasCursorSettings) { lines.push(""); lines.push("cursor {"); if (hideOnKeyPress) lines.push(" hide_on_key_press = true"); if (hideOnTouch) lines.push(" hide_on_touch = true"); if (inactiveTimeout > 0) lines.push(` inactive_timeout = ${inactiveTimeout}`); lines.push("}"); } lines.push(""); const content = lines.join("\n"); Proc.runCommand("hypr-write-cursor", ["sh", "-c", `mkdir -p "${hyprDmsDir}" && cat > "${cursorPath}" << 'EOF'\n${content}EOF`], (output, exitCode) => { if (exitCode !== 0) { console.warn("HyprlandService: Failed to write cursor config:", output); return; } if (hasTheme) Proc.runCommand("hyprctl-setcursor", ["hyprctl", "setcursor", themeName, String(size)], () => {}); reloadConfig(); }); } function renameWorkspace(newName) { if (!Hyprland.focusedWorkspace) return; const wsId = Hyprland.focusedWorkspace.id; if (!wsId) return; const fullName = wsId + " " + newName; Proc.runCommand("hyprland-rename-ws", ["hyprctl", "dispatch", "renameworkspace", String(wsId), fullName], (output, exitCode) => { if (exitCode !== 0) { console.warn("HyprlandService: Failed to rename workspace:", output); } }); } }