diff options
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig')
8 files changed, 0 insertions, 3262 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml deleted file mode 100644 index b6e253b..0000000 --- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml +++ /dev/null @@ -1,1837 +0,0 @@ -pragma Singleton -pragma ComponentBehavior: Bound - -import QtCore -import QtQuick -import Quickshell -import qs.Common -import qs.Services - -Singleton { - id: root - - readonly property bool hasOutputBackend: WlrOutputService.wlrOutputAvailable - readonly property var wlrOutputs: WlrOutputService.outputs - property var outputs: ({}) - property var savedOutputs: ({}) - property var allOutputs: buildAllOutputsMap() - - property var includeStatus: ({ - "exists": false, - "included": false - }) - property bool checkingInclude: false - property bool fixingInclude: false - - property var pendingChanges: ({}) - property var pendingNiriChanges: ({}) - property var pendingHyprlandChanges: ({}) - property var originalNiriSettings: null - property var originalHyprlandSettings: null - property var originalOutputs: null - property string originalDisplayNameMode: "" - property bool formatChanged: originalDisplayNameMode !== "" && originalDisplayNameMode !== SettingsData.displayNameMode - property bool hasPendingChanges: Object.keys(pendingChanges).length > 0 || Object.keys(pendingNiriChanges).length > 0 || Object.keys(pendingHyprlandChanges).length > 0 || formatChanged - - property bool validatingConfig: false - property string validationError: "" - - property var currentOutputSet: [] - property string matchedProfile: "" - property bool profilesLoading: false - property var validatedProfiles: ({}) - property bool manualActivation: false - - signal changesApplied(var changeDescriptions) - signal changesConfirmed - signal changesReverted - signal profileActivated(string profileId, string profileName) - signal profileSaved(string profileId, string profileName) - signal profileDeleted(string profileId) - signal profileError(string message) - - function buildCurrentOutputSet() { - const connected = []; - for (const name in outputs) { - const output = outputs[name]; - connected.push(getOutputIdentifier(output, name)); - } - return connected.sort(); - } - - function getOutputIdentifier(output, outputName) { - if (SettingsData.displayNameMode === "model" && output?.make && output?.model) { - if (CompositorService.isNiri) { - const serial = output.serial || "Unknown"; - return output.make + " " + output.model + " " + serial; - } - return output.make + " " + output.model; - } - return outputName; - } - - function validateProfiles() { - const compositor = CompositorService.compositor; - const profiles = SettingsData.getDisplayProfiles(compositor); - const profilesDir = getProfilesDir(); - const ext = getProfileExtension(); - - if (!profilesDir) { - validatedProfiles = {}; - return; - } - - const profileIds = Object.keys(profiles); - if (profileIds.length === 0) { - validatedProfiles = {}; - return; - } - - const fileChecks = profileIds.map(id => profilesDir + "/" + id + ext).join(" "); - Proc.runCommand("validate-profiles", ["sh", "-c", `for f in ${fileChecks}; do [ -f "$f" ] && echo "$f"; done`], (output, exitCode) => { - const existingFiles = new Set(output.trim().split("\n").filter(f => f)); - const validated = {}; - for (const profileId of profileIds) { - const profileFile = profilesDir + "/" + profileId + ext; - if (existingFiles.has(profileFile)) - validated[profileId] = profiles[profileId]; - else - SettingsData.removeDisplayProfile(compositor, profileId); - } - validatedProfiles = validated; - matchedProfile = findMatchingProfile(); - }); - } - - function findMatchingProfile() { - const profiles = validatedProfiles; - - console.log("[Profile Match] Current outputs:", JSON.stringify(currentOutputSet)); - - let bestMatch = ""; - let bestScore = -1; - let bestUpdatedAt = 0; - - for (const profileId in profiles) { - const profile = profiles[profileId]; - const profileSet = new Set(profile.outputSet); - - console.log("[Profile Match] Checking", profile.name, "outputSet:", JSON.stringify(profile.outputSet)); - - let allCurrentPresent = true; - for (const output of currentOutputSet) { - if (!profileSet.has(output)) { - console.log("[Profile Match] - Missing output:", output); - allCurrentPresent = false; - break; - } - } - if (!allCurrentPresent) { - console.log("[Profile Match] - SKIP: not all current outputs present"); - continue; - } - - const disconnectedCount = profile.outputSet.length - currentOutputSet.length; - const score = currentOutputSet.length * 100 - disconnectedCount; - const updatedAt = profile.updatedAt || profile.createdAt || 0; - console.log("[Profile Match] - MATCH score:", score, "(disconnected:", disconnectedCount, "updatedAt:", updatedAt + ")"); - - if (score > bestScore || (score === bestScore && updatedAt > bestUpdatedAt)) { - bestScore = score; - bestMatch = profileId; - bestUpdatedAt = updatedAt; - } - } - console.log("[Profile Match] Best match:", bestMatch, "score:", bestScore); - return bestMatch; - } - - function getProfilesDir() { - const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)); - switch (CompositorService.compositor) { - case "niri": - return configDir + "/niri/dms/profiles"; - case "hyprland": - return configDir + "/hypr/dms/profiles"; - case "dwl": - return configDir + "/mango/dms/profiles"; - default: - return ""; - } - } - - function getProfileExtension() { - return CompositorService.compositor === "niri" ? ".kdl" : ".conf"; - } - - function createProfile(name) { - const compositor = CompositorService.compositor; - const profileId = "profile_" + Date.now() + "_" + Math.random().toString(36).substr(2, 6); - const outputSet = buildCurrentOutputSet(); - const now = Date.now(); - - const profileData = { - "id": profileId, - "name": name, - "outputSet": outputSet, - "createdAt": now, - "updatedAt": now - }; - - const profilesDir = getProfilesDir(); - const profileFile = profilesDir + "/" + profileId + getProfileExtension(); - const paths = getConfigPaths(); - if (!paths) { - profileError(I18n.tr("Compositor not supported")); - return; - } - - profilesLoading = true; - Proc.runCommand("create-profile-dir", ["mkdir", "-p", profilesDir], (output, exitCode) => { - if (exitCode !== 0) { - profilesLoading = false; - profileError(I18n.tr("Failed to create profiles directory")); - return; - } - Proc.runCommand("copy-profile", ["cp", "-L", paths.outputsFile, profileFile], (output2, exitCode2) => { - if (exitCode2 !== 0) { - profilesLoading = false; - profileError(I18n.tr("Failed to save profile file")); - return; - } - SettingsData.setDisplayProfile(compositor, profileId, profileData); - SettingsData.setActiveDisplayProfile(compositor, profileId); - const updated = JSON.parse(JSON.stringify(validatedProfiles)); - updated[profileId] = profileData; - validatedProfiles = updated; - Proc.runCommand("link-new-profile", ["ln", "-sf", profileFile, paths.outputsFile], () => { - profilesLoading = false; - currentOutputSet = outputSet; - matchedProfile = profileId; - profileSaved(profileId, name); - }); - }); - }); - } - - function renameProfile(profileId, newName) { - const compositor = CompositorService.compositor; - const profiles = SettingsData.getDisplayProfiles(compositor); - const profile = profiles[profileId]; - if (!profile) { - profileError(I18n.tr("Profile not found")); - return; - } - profile.name = newName; - profile.updatedAt = Date.now(); - SettingsData.setDisplayProfile(compositor, profileId, profile); - } - - function deleteProfile(profileId) { - const compositor = CompositorService.compositor; - const profilesDir = getProfilesDir(); - const profileFile = profilesDir + "/" + profileId + getProfileExtension(); - const isActive = SettingsData.getActiveDisplayProfile(compositor) === profileId; - - profilesLoading = true; - Proc.runCommand("delete-profile", ["rm", "-f", profileFile], (output, exitCode) => { - profilesLoading = false; - SettingsData.removeDisplayProfile(compositor, profileId); - if (isActive) { - SettingsData.setActiveDisplayProfile(compositor, ""); - backendWriteOutputsConfig(allOutputs); - } - const updated = JSON.parse(JSON.stringify(validatedProfiles)); - delete updated[profileId]; - validatedProfiles = updated; - matchedProfile = findMatchingProfile(); - profileDeleted(profileId); - }); - } - - function activateProfile(profileId) { - const compositor = CompositorService.compositor; - const profiles = SettingsData.getDisplayProfiles(compositor); - const profile = profiles[profileId]; - if (!profile) { - profileError(I18n.tr("Profile not found")); - return; - } - - const profilesDir = getProfilesDir(); - const profileFile = profilesDir + "/" + profileId + getProfileExtension(); - const paths = getConfigPaths(); - if (!paths) - return; - - manualActivation = true; - profilesLoading = true; - Proc.runCommand("activate-profile", ["ln", "-sf", profileFile, paths.outputsFile], (output, exitCode) => { - if (exitCode !== 0) { - profilesLoading = false; - manualActivation = false; - profileError(I18n.tr("Failed to activate profile - file not found")); - return; - } - SettingsData.setActiveDisplayProfile(compositor, profileId); - - const reloadCmd = CompositorService.isNiri ? ["niri", "msg", "action", "reload-config-or-panic"] : CompositorService.isHyprland ? ["hyprctl", "reload"] : []; - if (reloadCmd.length > 0) { - Proc.runCommand("reload-compositor", reloadCmd, (output2, exitCode2) => { - profilesLoading = false; - WlrOutputService.requestState(); - profileActivated(profileId, profile.name); - manualActivationTimer.restart(); - }); - } else { - profilesLoading = false; - profileActivated(profileId, profile.name); - manualActivationTimer.restart(); - } - }); - } - - Timer { - id: manualActivationTimer - interval: 2000 - onTriggered: root.manualActivation = false - } - - Timer { - id: autoSelectDebounceTimer - interval: 800 - onTriggered: root.doAutoSelectProfile() - } - - // ! TODO - auto profile switching is buggy on niri and other compositors, might need a longer debounce before updating output configuration idk - function autoSelectProfile() { - return; // disabled - autoSelectDebounceTimer.restart(); - } - - function doAutoSelectProfile() { - return; // disabled - if (!SettingsData.displayProfileAutoSelect || manualActivation) - return; - currentOutputSet = buildCurrentOutputSet(); - const matched = findMatchingProfile(); - matchedProfile = matched; - if (matched && matched !== SettingsData.getActiveDisplayProfile(CompositorService.compositor)) - activateProfile(matched); - } - - function deleteDisconnectedOutput(outputName) { - if (outputs[outputName]?.connected) - return; - - const updated = JSON.parse(JSON.stringify(savedOutputs)); - delete updated[outputName]; - savedOutputs = updated; - - const mergedOutputs = {}; - for (const name in outputs) - mergedOutputs[name] = outputs[name]; - for (const name in updated) - mergedOutputs[name] = updated[name]; - - backendWriteOutputsConfig(mergedOutputs); - } - - function buildAllOutputsMap() { - const result = {}; - for (const name in savedOutputs) { - result[name] = Object.assign({}, savedOutputs[name], { - "connected": false - }); - } - for (const name in outputs) { - result[name] = Object.assign({}, outputs[name], { - "connected": true - }); - } - return result; - } - - onOutputsChanged: { - allOutputs = buildAllOutputsMap(); - currentOutputSet = buildCurrentOutputSet(); - matchedProfile = findMatchingProfile(); - // ! TODO - auto profile switching disabled for now - // if (SettingsData.displayProfileAutoSelect) - // Qt.callLater(autoSelectProfile); - } - onSavedOutputsChanged: allOutputs = buildAllOutputsMap() - - Connections { - target: WlrOutputService - function onStateChanged() { - root.outputs = root.buildOutputsMap(); - root.reloadSavedOutputs(); - } - } - - Component.onCompleted: { - outputs = buildOutputsMap(); - reloadSavedOutputs(); - checkIncludeStatus(); - currentOutputSet = buildCurrentOutputSet(); - validateProfiles(); - } - - function reloadSavedOutputs() { - const paths = getConfigPaths(); - if (!paths) { - savedOutputs = {}; - return; - } - - Proc.runCommand("load-saved-outputs", ["cat", paths.outputsFile], (content, exitCode) => { - if (exitCode !== 0 || !content.trim()) { - savedOutputs = {}; - return; - } - const parsed = parseOutputsConfig(content); - const filtered = filterDisconnectedOnly(parsed); - savedOutputs = filtered; - - if (CompositorService.isHyprland) { - initHyprlandSettingsFromConfig(parsed); - syncHyprlandVrrFromConfig(parsed); - } - if (CompositorService.isNiri) - syncNiriVrrFromConfig(parsed); - }); - } - - function initHyprlandSettingsFromConfig(parsedOutputs) { - const current = JSON.parse(JSON.stringify(SettingsData.hyprlandOutputSettings)); - let changed = false; - - for (const outputName in parsedOutputs) { - const output = parsedOutputs[outputName]; - const settings = output.hyprlandSettings; - if (!settings) - continue; - - if (current[outputName]) - continue; - - const hasSettings = settings.colorManagement || settings.bitdepth || settings.sdrBrightness !== undefined || settings.sdrSaturation !== undefined; - if (!hasSettings) - continue; - - current[outputName] = {}; - if (settings.colorManagement) - current[outputName].colorManagement = settings.colorManagement; - if (settings.bitdepth) - current[outputName].bitdepth = settings.bitdepth; - if (settings.sdrBrightness !== undefined) - current[outputName].sdrBrightness = settings.sdrBrightness; - if (settings.sdrSaturation !== undefined) - current[outputName].sdrSaturation = settings.sdrSaturation; - changed = true; - } - - if (changed) { - SettingsData.hyprlandOutputSettings = current; - SettingsData.saveSettings(); - } - } - - function syncHyprlandVrrFromConfig(parsedOutputs) { - const current = JSON.parse(JSON.stringify(SettingsData.hyprlandOutputSettings)); - let changed = false; - for (const outputName in parsedOutputs) { - const settings = parsedOutputs[outputName]?.hyprlandSettings; - const fromConfig = settings?.vrrFullscreenOnly ?? false; - const stored = current[outputName]?.vrrFullscreenOnly ?? false; - if (fromConfig === stored) - continue; - if (!current[outputName]) - current[outputName] = {}; - if (fromConfig) - current[outputName].vrrFullscreenOnly = true; - else - delete current[outputName].vrrFullscreenOnly; - changed = true; - } - if (changed) { - SettingsData.hyprlandOutputSettings = current; - SettingsData.saveSettings(); - } - } - - function syncNiriVrrFromConfig(parsedOutputs) { - let changed = false; - for (const outputName in parsedOutputs) { - const output = parsedOutputs[outputName]; - const current = SettingsData.getNiriOutputSetting(outputName, "vrrOnDemand", false); - const fromConfig = output.vrr_on_demand ?? false; - if (current === fromConfig) - continue; - SettingsData.setNiriOutputSetting(outputName, "vrrOnDemand", fromConfig || undefined); - changed = true; - } - if (changed) - SettingsData.saveSettings(); - } - - function filterDisconnectedOnly(parsedOutputs) { - const result = {}; - const liveNames = Object.keys(outputs); - const liveByIdentifier = {}; - for (const name of liveNames) { - const o = outputs[name]; - if (o?.make && o?.model) { - const serial = o.serial || "Unknown"; - const id = (o.make + " " + o.model + " " + serial).trim(); - liveByIdentifier[id] = true; - liveByIdentifier[o.make + " " + o.model] = true; - } - liveByIdentifier[name] = true; - } - - for (const savedName in parsedOutputs) { - const trimmed = savedName.trim(); - if (!liveByIdentifier[trimmed]) - result[savedName] = parsedOutputs[savedName]; - } - return result; - } - - function parseOutputsConfig(content) { - switch (CompositorService.compositor) { - case "niri": - return parseNiriOutputs(content); - case "hyprland": - return parseHyprlandOutputs(content); - case "dwl": - return parseMangoOutputs(content); - default: - return {}; - } - } - - function parseNiriOutputs(content) { - const result = {}; - const outputRegex = /output\s+"([^"]+)"\s*\{([^}]*)\}/g; - let match; - while ((match = outputRegex.exec(content)) !== null) { - const name = match[1]; - const body = match[2]; - - const modeMatch = body.match(/mode\s+"(\d+)x(\d+)@([\d.]+)"/); - const posMatch = body.match(/position\s+x=(-?\d+)\s+y=(-?\d+)/); - const scaleMatch = body.match(/scale\s+([\d.]+)/); - const transformMatch = body.match(/transform\s+"([^"]+)"/); - const vrrMatch = body.match(/variable-refresh-rate/); - const vrrOnDemandMatch = body.match(/variable-refresh-rate\s+on-demand=true/); - - result[name] = { - "name": name, - "logical": { - "x": posMatch ? parseInt(posMatch[1]) : 0, - "y": posMatch ? parseInt(posMatch[2]) : 0, - "scale": scaleMatch ? parseFloat(scaleMatch[1]) : 1.0, - "transform": transformMatch ? transformMatch[1] : "Normal" - }, - "modes": modeMatch ? [ - { - "width": parseInt(modeMatch[1]), - "height": parseInt(modeMatch[2]), - "refresh_rate": Math.round(parseFloat(modeMatch[3]) * 1000) - } - ] : [], - "current_mode": 0, - "vrr_enabled": !!vrrMatch, - "vrr_on_demand": !!vrrOnDemandMatch, - "vrr_supported": true - }; - } - return result; - } - - function parseHyprlandOutputs(content) { - const result = {}; - const lines = content.split("\n"); - for (const line of lines) { - const disableMatch = line.match(/^\s*monitor\s*=\s*([^,]+),\s*disable\s*$/); - if (disableMatch) { - const name = disableMatch[1].trim(); - result[name] = { - "name": name, - "logical": { - "x": 0, - "y": 0, - "scale": 1.0, - "transform": "Normal" - }, - "modes": [], - "current_mode": -1, - "vrr_enabled": false, - "vrr_supported": false, - "hyprlandSettings": { - "disabled": true - } - }; - continue; - } - const match = line.match(/^\s*monitor\s*=\s*([^,]+),\s*(\d+)x(\d+)@([\d.]+),\s*(-?\d+)x(-?\d+),\s*([\d.]+)/); - if (!match) - continue; - const name = match[1].trim(); - const rest = line.substring(line.indexOf(match[7]) + match[7].length); - - let transform = 0, vrrMode = 0, bitdepth = undefined, cm = undefined; - let sdrBrightness = undefined, sdrSaturation = undefined; - - const transformMatch = rest.match(/,\s*transform,\s*(\d+)/); - if (transformMatch) - transform = parseInt(transformMatch[1]); - - const vrrMatch = rest.match(/,\s*vrr,\s*(\d+)/); - if (vrrMatch) - vrrMode = parseInt(vrrMatch[1]); - - const bitdepthMatch = rest.match(/,\s*bitdepth,\s*(\d+)/); - if (bitdepthMatch) - bitdepth = parseInt(bitdepthMatch[1]); - - const cmMatch = rest.match(/,\s*cm,\s*(\w+)/); - if (cmMatch) - cm = cmMatch[1]; - - const sdrBrightnessMatch = rest.match(/,\s*sdrbrightness,\s*([\d.]+)/); - if (sdrBrightnessMatch) - sdrBrightness = parseFloat(sdrBrightnessMatch[1]); - - const sdrSaturationMatch = rest.match(/,\s*sdrsaturation,\s*([\d.]+)/); - if (sdrSaturationMatch) - sdrSaturation = parseFloat(sdrSaturationMatch[1]); - - let mirror = ""; - const mirrorMatch = rest.match(/,\s*mirror,\s*([^,\s]+)/); - if (mirrorMatch) - mirror = mirrorMatch[1]; - - result[name] = { - "name": name, - "logical": { - "x": parseInt(match[5]), - "y": parseInt(match[6]), - "scale": parseFloat(match[7]), - "transform": hyprlandToTransform(transform) - }, - "modes": [ - { - "width": parseInt(match[2]), - "height": parseInt(match[3]), - "refresh_rate": Math.round(parseFloat(match[4]) * 1000) - } - ], - "current_mode": 0, - "vrr_enabled": vrrMode >= 1, - "vrr_supported": true, - "hyprlandSettings": { - "bitdepth": bitdepth, - "colorManagement": cm, - "sdrBrightness": sdrBrightness, - "sdrSaturation": sdrSaturation, - "vrrFullscreenOnly": vrrMode === 2 ? true : undefined - }, - "mirror": mirror - }; - } - return result; - } - - 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 parseMangoOutputs(content) { - const result = {}; - const lines = content.split("\n"); - for (const line of lines) { - const trimmed = line.trim(); - if (!trimmed.startsWith("monitorrule=")) - continue; - - const params = {}; - for (const pair of trimmed.substring("monitorrule=".length).split(",")) { - const colonIdx = pair.indexOf(":"); - if (colonIdx < 0) - continue; - params[pair.substring(0, colonIdx).trim()] = pair.substring(colonIdx + 1).trim(); - } - - const name = params.name; - if (!name) - continue; - - result[name] = { - "name": name, - "logical": { - "x": parseInt(params.x || "0"), - "y": parseInt(params.y || "0"), - "scale": parseFloat(params.scale || "1"), - "transform": mangoToTransform(parseInt(params.rr || "0")) - }, - "modes": [ - { - "width": parseInt(params.width || "1920"), - "height": parseInt(params.height || "1080"), - "refresh_rate": parseFloat(params.refresh || "60") * 1000 - } - ], - "current_mode": 0, - "vrr_enabled": parseInt(params.vrr || "0") === 1, - "vrr_supported": true - }; - } - return result; - } - - function mangoToTransform(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 getConfigPaths() { - const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)); - switch (CompositorService.compositor) { - case "niri": - return { - "configFile": configDir + "/niri/config.kdl", - "outputsFile": configDir + "/niri/dms/outputs.kdl", - "grepPattern": 'include.*"dms/outputs.kdl"', - "includeLine": 'include "dms/outputs.kdl"' - }; - case "hyprland": - return { - "configFile": configDir + "/hypr/hyprland.conf", - "outputsFile": configDir + "/hypr/dms/outputs.conf", - "grepPattern": 'source.*dms/outputs.conf', - "includeLine": "source = ./dms/outputs.conf" - }; - case "dwl": - return { - "configFile": configDir + "/mango/config.conf", - "outputsFile": configDir + "/mango/dms/outputs.conf", - "grepPattern": 'source.*dms/outputs.conf', - "includeLine": "source=./dms/outputs.conf" - }; - default: - return null; - } - } - - function checkIncludeStatus() { - const compositor = CompositorService.compositor; - if (compositor !== "niri" && compositor !== "hyprland" && compositor !== "dwl") { - includeStatus = { - "exists": false, - "included": false - }; - return; - } - - const filename = (compositor === "niri") ? "outputs.kdl" : "outputs.conf"; - const compositorArg = (compositor === "dwl") ? "mangowc" : compositor; - - checkingInclude = true; - Proc.runCommand("check-outputs-include", ["dms", "config", "resolve-include", compositorArg, filename], (output, exitCode) => { - checkingInclude = false; - if (exitCode !== 0) { - includeStatus = { - "exists": false, - "included": false - }; - return; - } - try { - includeStatus = JSON.parse(output.trim()); - } catch (e) { - includeStatus = { - "exists": false, - "included": false - }; - } - }); - } - - function fixOutputsInclude() { - const paths = getConfigPaths(); - if (!paths) - return; - - fixingInclude = true; - const outputsDir = paths.outputsFile.substring(0, paths.outputsFile.lastIndexOf("/")); - const unixTime = Math.floor(Date.now() / 1000); - const backupFile = paths.configFile + ".backup" + unixTime; - - Proc.runCommand("fix-outputs-include", ["sh", "-c", `cp "${paths.configFile}" "${backupFile}" 2>/dev/null; ` + `mkdir -p "${outputsDir}" && ` + `touch "${paths.outputsFile}" && ` + `if ! grep -v '^[[:space:]]*\\(//\\|#\\)' "${paths.configFile}" 2>/dev/null | grep -q '${paths.grepPattern}'; then ` + `echo '' >> "${paths.configFile}" && ` + `echo '${paths.includeLine}' >> "${paths.configFile}"; fi`], (output, exitCode) => { - fixingInclude = false; - if (exitCode !== 0) - return; - checkIncludeStatus(); - WlrOutputService.requestState(); - }); - } - - function buildOutputsMap() { - const map = {}; - for (const output of wlrOutputs) { - const normalizedModes = (output.modes || []).map(m => ({ - "id": m.id, - "width": m.width, - "height": m.height, - "refresh_rate": m.refresh, - "preferred": m.preferred ?? false - })); - map[output.name] = { - "name": output.name, - "make": output.make || "", - "model": output.model || "", - "serial": output.serialNumber || "", - "modes": normalizedModes, - "current_mode": normalizedModes.findIndex(m => m.id === output.currentMode?.id), - "vrr_supported": output.adaptiveSyncSupported ?? false, - "vrr_enabled": output.adaptiveSync === 1, - "logical": { - "x": output.x ?? 0, - "y": output.y ?? 0, - "width": output.currentMode?.width ?? 1920, - "height": output.currentMode?.height ?? 1080, - "scale": output.scale ?? 1.0, - "transform": mapWlrTransform(output.transform) - } - }; - } - return map; - } - - function mapWlrTransform(wlrTransform) { - switch (wlrTransform) { - 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 mapTransformToWlr(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 backendFetchOutputs() { - WlrOutputService.requestState(); - } - - function backendWriteOutputsConfig(outputsData) { - switch (CompositorService.compositor) { - case "niri": - NiriService.generateOutputsConfig(outputsData); - break; - case "hyprland": - HyprlandService.generateOutputsConfig(outputsData, buildMergedHyprlandSettings()); - break; - case "dwl": - DwlService.generateOutputsConfig(outputsData); - break; - default: - WlrOutputService.applyOutputsConfig(outputsData, outputs); - break; - } - } - - function normalizeOutputPositions(outputsData) { - const names = Object.keys(outputsData); - if (names.length === 0) - return outputsData; - - let minX = Infinity; - let minY = Infinity; - - for (const name of names) { - const output = outputsData[name]; - if (!output.logical) - continue; - minX = Math.min(minX, output.logical.x); - minY = Math.min(minY, output.logical.y); - } - - if (minX === Infinity || (minX === 0 && minY === 0)) - return outputsData; - - const normalized = JSON.parse(JSON.stringify(outputsData)); - for (const name of names) { - if (!normalized[name].logical) - continue; - normalized[name].logical.x -= minX; - normalized[name].logical.y -= minY; - } - - return normalized; - } - - function buildOutputsWithPendingChanges() { - const result = {}; - - for (const outputName in savedOutputs) { - if (!outputs[outputName]) - result[outputName] = JSON.parse(JSON.stringify(savedOutputs[outputName])); - } - - for (const outputName in outputs) { - result[outputName] = JSON.parse(JSON.stringify(outputs[outputName])); - } - - for (const outputName in pendingChanges) { - if (!result[outputName]) - continue; - const changes = pendingChanges[outputName]; - if (changes.position && result[outputName].logical) { - result[outputName].logical.x = changes.position.x; - result[outputName].logical.y = changes.position.y; - } - if (changes.mode !== undefined && result[outputName].modes) { - for (var i = 0; i < result[outputName].modes.length; i++) { - if (formatMode(result[outputName].modes[i]) === changes.mode) { - result[outputName].current_mode = i; - break; - } - } - } - if (changes.scale !== undefined && result[outputName].logical) - result[outputName].logical.scale = changes.scale; - if (changes.transform !== undefined && result[outputName].logical) - result[outputName].logical.transform = changes.transform; - if (changes.vrr !== undefined) - result[outputName].vrr_enabled = changes.vrr; - if (changes.mirror !== undefined) - result[outputName].mirror = changes.mirror; - } - return normalizeOutputPositions(result); - } - - function backendUpdateOutputPosition(outputName, x, y) { - if (!outputs || !outputs[outputName]) - return; - const updatedOutputs = {}; - for (const name in outputs) { - const output = outputs[name]; - if (name === outputName && output.logical) { - updatedOutputs[name] = JSON.parse(JSON.stringify(output)); - updatedOutputs[name].logical.x = x; - updatedOutputs[name].logical.y = y; - } else { - updatedOutputs[name] = output; - } - } - outputs = updatedOutputs; - } - - function backendUpdateOutputScale(outputName, scale) { - if (!outputs || !outputs[outputName]) - return; - const updatedOutputs = {}; - for (const name in outputs) { - const output = outputs[name]; - if (name === outputName && output.logical) { - updatedOutputs[name] = JSON.parse(JSON.stringify(output)); - updatedOutputs[name].logical.scale = scale; - } else { - updatedOutputs[name] = output; - } - } - outputs = updatedOutputs; - } - - function getOutputDisplayName(output, outputName) { - if (SettingsData.displayNameMode === "model" && output?.make && output?.model) { - if (CompositorService.isNiri) { - const serial = output.serial || "Unknown"; - return output.make + " " + output.model + " " + serial; - } - return output.make + " " + output.model; - } - return outputName; - } - - function getNiriOutputIdentifier(output, outputName) { - if (SettingsData.displayNameMode === "model" && output?.make && output?.model) { - const serial = output.serial || "Unknown"; - return output.make + " " + output.model + " " + serial; - } - return outputName; - } - - function getNiriSetting(output, outputName, key, defaultValue) { - if (!CompositorService.isNiri) - return defaultValue; - const identifier = getNiriOutputIdentifier(output, outputName); - const pending = pendingNiriChanges[identifier]; - if (pending && pending[key] !== undefined) - return pending[key]; - return SettingsData.getNiriOutputSetting(identifier, key, defaultValue); - } - - function setNiriSetting(output, outputName, key, value) { - if (!CompositorService.isNiri) - return; - initOriginalNiriSettings(); - const identifier = getNiriOutputIdentifier(output, outputName); - const newPending = JSON.parse(JSON.stringify(pendingNiriChanges)); - if (!newPending[identifier]) - newPending[identifier] = {}; - newPending[identifier][key] = value; - pendingNiriChanges = newPending; - } - - function initOriginalNiriSettings() { - if (originalNiriSettings) - return; - originalNiriSettings = JSON.parse(JSON.stringify(SettingsData.niriOutputSettings)); - } - - function getHyprlandOutputIdentifier(output, outputName) { - if (SettingsData.displayNameMode === "model" && output?.make && output?.model) - return "desc:" + output.make + " " + output.model + " " + (output?.serial || "Unknown"); - return outputName; - } - - function getHyprlandSetting(output, outputName, key, defaultValue) { - if (!CompositorService.isHyprland) - return defaultValue; - const identifier = getHyprlandOutputIdentifier(output, outputName); - const pending = pendingHyprlandChanges[identifier]; - if (pending && (key in pending)) { - const val = pending[key]; - return (val !== null && val !== undefined) ? val : defaultValue; - } - return SettingsData.getHyprlandOutputSetting(identifier, key, defaultValue); - } - - function setHyprlandSetting(output, outputName, key, value) { - if (!CompositorService.isHyprland) - return; - initOriginalHyprlandSettings(); - const identifier = getHyprlandOutputIdentifier(output, outputName); - const newPending = JSON.parse(JSON.stringify(pendingHyprlandChanges)); - if (!newPending[identifier]) - newPending[identifier] = {}; - newPending[identifier][key] = value; - pendingHyprlandChanges = newPending; - } - - function initOriginalHyprlandSettings() { - if (originalHyprlandSettings) - return; - originalHyprlandSettings = JSON.parse(JSON.stringify(SettingsData.hyprlandOutputSettings)); - } - - function initOriginalOutputs() { - if (!originalOutputs) - originalOutputs = JSON.parse(JSON.stringify(outputs)); - } - - function setPendingChange(outputName, key, value) { - initOriginalOutputs(); - const newPending = JSON.parse(JSON.stringify(pendingChanges)); - if (!newPending[outputName]) - newPending[outputName] = {}; - newPending[outputName][key] = value; - pendingChanges = newPending; - - if (key === "scale") { - recalculateAdjacentPositions(outputName, value); - backendUpdateOutputScale(outputName, value); - } - } - - function recalculateAdjacentPositions(changedOutput, newScale) { - const output = outputs[changedOutput]; - if (!output?.logical) - return; - const oldPhys = getPhysicalSize(output); - const oldLogicalW = Math.round(oldPhys.w / (output.logical.scale || 1.0)); - const newLogicalW = Math.round(oldPhys.w / newScale); - - const changedX = getPendingValue(changedOutput, "position")?.x ?? output.logical.x; - const changedY = getPendingValue(changedOutput, "position")?.y ?? output.logical.y; - - for (const name in outputs) { - if (name === changedOutput) - continue; - const other = outputs[name]; - if (!other?.logical) - continue; - const otherX = getPendingValue(name, "position")?.x ?? other.logical.x; - const otherY = getPendingValue(name, "position")?.y ?? other.logical.y; - const otherSize = getLogicalSize(other); - const otherRight = otherX + otherSize.w; - - if (Math.abs(changedX - otherRight) < 5) { - const newX = otherRight; - const newPending = JSON.parse(JSON.stringify(pendingChanges)); - if (!newPending[changedOutput]) - newPending[changedOutput] = {}; - newPending[changedOutput].position = { - "x": newX, - "y": changedY - }; - pendingChanges = newPending; - backendUpdateOutputPosition(changedOutput, newX, changedY); - return; - } - - const changedRight = changedX + oldLogicalW; - if (Math.abs(otherX - changedRight) < 5) { - const newOtherX = changedX + newLogicalW; - const newPending = JSON.parse(JSON.stringify(pendingChanges)); - if (!newPending[name]) - newPending[name] = {}; - newPending[name].position = { - "x": newOtherX, - "y": otherY - }; - pendingChanges = newPending; - backendUpdateOutputPosition(name, newOtherX, otherY); - } - } - } - - function getPendingValue(outputName, key) { - if (!pendingChanges[outputName]) - return undefined; - return pendingChanges[outputName][key]; - } - - function getEffectiveValue(outputName, key, originalValue) { - const pending = getPendingValue(outputName, key); - return pending !== undefined ? pending : originalValue; - } - - function clearPendingChanges() { - pendingChanges = {}; - pendingNiriChanges = {}; - pendingHyprlandChanges = {}; - originalOutputs = null; - originalNiriSettings = null; - originalHyprlandSettings = null; - originalDisplayNameMode = ""; - } - - function discardChanges() { - if (originalDisplayNameMode !== "") { - SettingsData.displayNameMode = originalDisplayNameMode; - SettingsData.saveSettings(); - } - backendFetchOutputs(); - clearPendingChanges(); - } - - function applyChanges() { - if (!hasPendingChanges) - return; - const changeDescriptions = []; - - if (formatChanged) { - const formatLabel = SettingsData.displayNameMode === "model" ? I18n.tr("Model") : I18n.tr("Name"); - changeDescriptions.push(I18n.tr("Config Format") + " → " + formatLabel); - } - - for (const outputName in pendingChanges) { - const changes = pendingChanges[outputName]; - if (changes.position) - changeDescriptions.push(outputName + ": " + I18n.tr("Position") + " → " + changes.position.x + ", " + changes.position.y); - if (changes.mode) - changeDescriptions.push(outputName + ": " + I18n.tr("Mode") + " → " + changes.mode); - if (changes.scale !== undefined) - changeDescriptions.push(outputName + ": " + I18n.tr("Scale") + " → " + changes.scale); - if (changes.transform) - changeDescriptions.push(outputName + ": " + I18n.tr("Transform") + " → " + getTransformLabel(changes.transform)); - if (changes.vrr !== undefined) - changeDescriptions.push(outputName + ": " + I18n.tr("VRR") + " → " + (changes.vrr ? I18n.tr("Enabled") : I18n.tr("Disabled"))); - } - - for (const outputId in pendingNiriChanges) { - const changes = pendingNiriChanges[outputId]; - if (changes.disabled !== undefined) - changeDescriptions.push(outputId + ": " + I18n.tr("Disabled") + " → " + (changes.disabled ? I18n.tr("Yes") : I18n.tr("No"))); - if (changes.vrrOnDemand !== undefined) - changeDescriptions.push(outputId + ": " + I18n.tr("VRR On-Demand") + " → " + (changes.vrrOnDemand ? I18n.tr("Enabled") : I18n.tr("Disabled"))); - if (changes.focusAtStartup !== undefined) - changeDescriptions.push(outputId + ": " + I18n.tr("Focus at Startup") + " → " + (changes.focusAtStartup ? I18n.tr("Yes") : I18n.tr("No"))); - if (changes.hotCorners !== undefined) - changeDescriptions.push(outputId + ": " + I18n.tr("Hot Corners") + " → " + I18n.tr("Modified")); - if (changes.layout !== undefined) - changeDescriptions.push(outputId + ": " + I18n.tr("Layout") + " → " + I18n.tr("Modified")); - } - - for (const outputId in pendingHyprlandChanges) { - const changes = pendingHyprlandChanges[outputId]; - if (changes.disabled !== undefined) - changeDescriptions.push(outputId + ": " + I18n.tr("Disabled") + " → " + (changes.disabled ? I18n.tr("Yes") : I18n.tr("No"))); - if (changes.bitdepth !== undefined) - changeDescriptions.push(outputId + ": " + I18n.tr("Bit Depth") + " → " + changes.bitdepth); - if (changes.colorManagement !== undefined) - changeDescriptions.push(outputId + ": " + I18n.tr("Color Management") + " → " + changes.colorManagement); - if (changes.sdrBrightness !== undefined) - changeDescriptions.push(outputId + ": " + I18n.tr("SDR Brightness") + " → " + changes.sdrBrightness); - if (changes.sdrSaturation !== undefined) - changeDescriptions.push(outputId + ": " + I18n.tr("SDR Saturation") + " → " + changes.sdrSaturation); - if (changes.supportsHdr !== undefined) - changeDescriptions.push(outputId + ": " + I18n.tr("Force HDR") + " → " + (changes.supportsHdr ? I18n.tr("Yes") : I18n.tr("No"))); - if (changes.supportsWideColor !== undefined) - changeDescriptions.push(outputId + ": " + I18n.tr("Force Wide Color") + " → " + (changes.supportsWideColor ? I18n.tr("Yes") : I18n.tr("No"))); - if (changes.vrrFullscreenOnly !== undefined) - changeDescriptions.push(outputId + ": " + I18n.tr("VRR Fullscreen Only") + " → " + (changes.vrrFullscreenOnly ? I18n.tr("Enabled") : I18n.tr("Disabled"))); - } - - if (CompositorService.isNiri) { - validateAndApplyNiriConfig(changeDescriptions); - return; - } - - changesApplied(changeDescriptions); - - if (formatChanged) - SettingsData.saveSettings(); - - if (CompositorService.isHyprland) - commitHyprlandSettingsChanges(); - - const mergedOutputs = buildOutputsWithPendingChanges(); - backendWriteOutputsConfig(mergedOutputs); - } - - function validateAndApplyNiriConfig(changeDescriptions) { - validatingConfig = true; - validationError = ""; - - const mergedOutputs = buildOutputsWithPendingChanges(); - const mergedNiriSettings = buildMergedNiriSettings(); - const configContent = generateNiriOutputsKdl(mergedOutputs, mergedNiriSettings); - - const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)); - const tempFile = configDir + "/niri/dms/.outputs-validate-tmp.kdl"; - - Proc.runCommand("niri-validate-write-tmp", ["sh", "-c", `mkdir -p "$(dirname "${tempFile}")" && cat > "${tempFile}" << 'EOF'\n${configContent}EOF`], (output, writeExitCode) => { - if (writeExitCode !== 0) { - validatingConfig = false; - validationError = I18n.tr("Failed to write temp file for validation"); - ToastService.showError(I18n.tr("Config validation failed"), validationError, "", "display-config"); - return; - } - Proc.runCommand("niri-validate-config", ["sh", "-c", `niri validate -c "${tempFile}" 2>&1`], (validateOutput, validateExitCode) => { - validatingConfig = false; - Proc.runCommand("niri-validate-cleanup", ["rm", "-f", tempFile], () => {}); - if (validateExitCode !== 0) { - validationError = validateOutput.trim() || I18n.tr("Invalid configuration"); - ToastService.showError(I18n.tr("Config validation failed"), validationError, "", "display-config"); - return; - } - changesApplied(changeDescriptions); - if (formatChanged) - SettingsData.saveSettings(); - commitNiriSettingsChanges(); - backendWriteOutputsConfig(mergedOutputs); - }); - }); - } - - function buildMergedNiriSettings() { - const merged = JSON.parse(JSON.stringify(SettingsData.niriOutputSettings)); - for (const outputId in pendingNiriChanges) { - if (!merged[outputId]) - merged[outputId] = {}; - for (const key in pendingNiriChanges[outputId]) { - merged[outputId][key] = pendingNiriChanges[outputId][key]; - } - } - return merged; - } - - function commitNiriSettingsChanges() { - for (const outputId in pendingNiriChanges) { - for (const key in pendingNiriChanges[outputId]) { - SettingsData.setNiriOutputSetting(outputId, key, pendingNiriChanges[outputId][key]); - } - } - } - - function buildMergedHyprlandSettings() { - const merged = JSON.parse(JSON.stringify(SettingsData.hyprlandOutputSettings)); - for (const outputId in pendingHyprlandChanges) { - if (!merged[outputId]) - merged[outputId] = {}; - for (const key in pendingHyprlandChanges[outputId]) { - const val = pendingHyprlandChanges[outputId][key]; - if (val === null || val === undefined) - delete merged[outputId][key]; - else - merged[outputId][key] = val; - } - } - return merged; - } - - function commitHyprlandSettingsChanges() { - for (const outputId in pendingHyprlandChanges) { - for (const key in pendingHyprlandChanges[outputId]) { - const val = pendingHyprlandChanges[outputId][key]; - if (val === null || val === undefined) - SettingsData.removeHyprlandOutputSetting(outputId, key); - else - SettingsData.setHyprlandOutputSetting(outputId, key, val); - } - } - } - - function generateNiriOutputsKdl(outputsData, niriSettings) { - let kdlContent = `// Auto-generated by DMS - do not edit manually\n\n`; - const sortedNames = Object.keys(outputsData).sort((a, b) => { - const la = outputsData[a].logical || {}; - const lb = outputsData[b].logical || {}; - return (la.x ?? 0) - (lb.x ?? 0) || (la.y ?? 0) - (lb.y ?? 0); - }); - for (const outputName of sortedNames) { - const output = outputsData[outputName]; - const identifier = getNiriOutputIdentifier(output, outputName); - const settings = niriSettings[identifier] || {}; - kdlContent += `output "${identifier}" {\n`; - if (settings.disabled) { - kdlContent += ` off\n}\n\n`; - continue; - } - if (output.current_mode !== undefined && output.modes && output.modes[output.current_mode]) { - const mode = output.modes[output.current_mode]; - kdlContent += ` mode "${mode.width}x${mode.height}@${(mode.refresh_rate / 1000).toFixed(3)}"\n`; - } - if (output.logical) { - kdlContent += ` scale ${output.logical.scale ?? 1.0}\n`; - if (output.logical.transform && output.logical.transform !== "Normal") { - const transformMap = { - "Normal": "normal", - "90": "90", - "180": "180", - "270": "270", - "Flipped": "flipped", - "Flipped90": "flipped-90", - "Flipped180": "flipped-180", - "Flipped270": "flipped-270" - }; - kdlContent += ` transform "${transformMap[output.logical.transform] || "normal"}"\n`; - } - if (output.logical.x !== undefined && output.logical.y !== undefined) - kdlContent += ` position x=${output.logical.x} y=${output.logical.y}\n`; - } - if (settings.vrrOnDemand) { - kdlContent += ` variable-refresh-rate on-demand=true\n`; - } else if (output.vrr_enabled) { - kdlContent += ` variable-refresh-rate\n`; - } - if (settings.focusAtStartup) - kdlContent += ` focus-at-startup\n`; - if (settings.backdropColor) - kdlContent += ` backdrop-color "${settings.backdropColor}"\n`; - kdlContent += generateHotCornersBlock(settings); - kdlContent += generateLayoutBlock(settings); - kdlContent += `}\n\n`; - } - return kdlContent; - } - - function generateHotCornersBlock(settings) { - if (!settings.hotCorners) - return ""; - const hc = settings.hotCorners; - if (hc.off) - return ` hot-corners {\n off\n }\n`; - const corners = hc.corners || []; - if (corners.length === 0) - return ""; - let block = ` hot-corners {\n`; - for (const corner of corners) - block += ` ${corner}\n`; - block += ` }\n`; - return block; - } - - function generateLayoutBlock(settings) { - if (!settings.layout) - return ""; - const layout = settings.layout; - const hasSettings = layout.gaps !== undefined || layout.defaultColumnWidth || layout.presetColumnWidths || layout.alwaysCenterSingleColumn !== undefined; - if (!hasSettings) - return ""; - let block = ` layout {\n`; - if (layout.gaps !== undefined) - block += ` gaps ${layout.gaps}\n`; - if (layout.defaultColumnWidth?.type === "proportion") { - const val = layout.defaultColumnWidth.value; - const formatted = Number.isInteger(val) ? val.toFixed(1) : val.toString(); - block += ` default-column-width { proportion ${formatted}; }\n`; - } - if (layout.presetColumnWidths && layout.presetColumnWidths.length > 0) { - block += ` preset-column-widths {\n`; - for (const preset of layout.presetColumnWidths) { - if (preset.type === "proportion") { - const val = preset.value; - const formatted = Number.isInteger(val) ? val.toFixed(1) : val.toString(); - block += ` proportion ${formatted}\n`; - } - } - block += ` }\n`; - } - if (layout.alwaysCenterSingleColumn !== undefined) - block += layout.alwaysCenterSingleColumn ? ` always-center-single-column\n` : ` always-center-single-column false\n`; - block += ` }\n`; - return block; - } - - function confirmChanges() { - clearPendingChanges(); - changesConfirmed(); - } - - function revertChanges() { - const hadFormatChange = originalDisplayNameMode !== ""; - const hadNiriChanges = originalNiriSettings !== null; - const hadHyprlandChanges = originalHyprlandSettings !== null; - - if (hadFormatChange) { - SettingsData.displayNameMode = originalDisplayNameMode; - SettingsData.saveSettings(); - } - - if (hadNiriChanges) { - SettingsData.niriOutputSettings = JSON.parse(JSON.stringify(originalNiriSettings)); - SettingsData.saveSettings(); - } - - if (hadHyprlandChanges) { - SettingsData.hyprlandOutputSettings = JSON.parse(JSON.stringify(originalHyprlandSettings)); - SettingsData.saveSettings(); - } - - pendingHyprlandChanges = {}; - pendingNiriChanges = {}; - - if (!originalOutputs && !hadNiriChanges && !hadHyprlandChanges) { - if (hadFormatChange) - backendWriteOutputsConfig(buildOutputsWithPendingChanges()); - clearPendingChanges(); - changesReverted(); - return; - } - - const original = originalOutputs ? JSON.parse(JSON.stringify(originalOutputs)) : buildOutputsWithPendingChanges(); - for (const name in savedOutputs) { - if (!original[name]) - original[name] = JSON.parse(JSON.stringify(savedOutputs[name])); - } - backendWriteOutputsConfig(original); - clearPendingChanges(); - if (originalOutputs) - outputs = original; - changesReverted(); - } - - function getOutputBounds() { - if (!allOutputs || Object.keys(allOutputs).length === 0) - return { - "minX": 0, - "minY": 0, - "maxX": 1920, - "maxY": 1080, - "width": 1920, - "height": 1080 - }; - - let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; - - for (const name in allOutputs) { - const output = allOutputs[name]; - if (!output.logical) - continue; - const x = output.logical.x; - const y = output.logical.y; - const size = getLogicalSize(output); - minX = Math.min(minX, x); - minY = Math.min(minY, y); - maxX = Math.max(maxX, x + size.w); - maxY = Math.max(maxY, y + size.h); - } - - if (minX === Infinity) - return { - "minX": 0, - "minY": 0, - "maxX": 1920, - "maxY": 1080, - "width": 1920, - "height": 1080 - }; - return { - "minX": minX, - "minY": minY, - "maxX": maxX, - "maxY": maxY, - "width": maxX - minX, - "height": maxY - minY - }; - } - - function isRotated(transform) { - switch (transform) { - case "90": - case "270": - case "Flipped90": - case "Flipped270": - return true; - default: - return false; - } - } - - function getPhysicalSize(output) { - if (!output) - return { - "w": 1920, - "h": 1080 - }; - - let w = 1920, h = 1080; - if (output.modes && output.current_mode !== undefined) { - const mode = output.modes[output.current_mode]; - if (mode) { - w = mode.width || 1920; - h = mode.height || 1080; - } - } else if (output.logical) { - const scale = output.logical.scale || 1.0; - w = Math.round((output.logical.width || 1920) * scale); - h = Math.round((output.logical.height || 1080) * scale); - } - - if (output.logical && isRotated(output.logical.transform)) - return { - "w": h, - "h": w - }; - return { - "w": w, - "h": h - }; - } - - function getLogicalSize(output) { - if (!output) - return { - "w": 1920, - "h": 1080 - }; - - const phys = getPhysicalSize(output); - const scale = output.logical?.scale || 1.0; - - return { - "w": Math.round(phys.w / scale), - "h": Math.round(phys.h / scale) - }; - } - - function checkOverlap(testName, testX, testY, testW, testH) { - for (const name in outputs) { - if (name === testName) - continue; - const output = outputs[name]; - if (!output.logical) - continue; - const x = output.logical.x; - const y = output.logical.y; - const size = getLogicalSize(output); - if (!(testX + testW <= x || testX >= x + size.w || testY + testH <= y || testY >= y + size.h)) - return true; - } - return false; - } - - function snapToEdges(testName, posX, posY, testW, testH) { - const snapThreshold = 200; - let snappedX = posX; - let snappedY = posY; - let bestXDist = snapThreshold; - let bestYDist = snapThreshold; - - for (const name in outputs) { - if (name === testName) - continue; - const output = outputs[name]; - if (!output.logical) - continue; - const x = output.logical.x; - const y = output.logical.y; - const size = getLogicalSize(output); - - const rightEdge = x + size.w; - const bottomEdge = y + size.h; - const testRight = posX + testW; - const testBottom = posY + testH; - - const xSnaps = [ - { - "val": rightEdge, - "dist": Math.abs(posX - rightEdge) - }, - { - "val": x - testW, - "dist": Math.abs(testRight - x) - }, - { - "val": x, - "dist": Math.abs(posX - x) - }, - { - "val": rightEdge - testW, - "dist": Math.abs(testRight - rightEdge) - } - ]; - - const ySnaps = [ - { - "val": bottomEdge, - "dist": Math.abs(posY - bottomEdge) - }, - { - "val": y - testH, - "dist": Math.abs(testBottom - y) - }, - { - "val": y, - "dist": Math.abs(posY - y) - }, - { - "val": bottomEdge - testH, - "dist": Math.abs(testBottom - bottomEdge) - } - ]; - - for (const snap of xSnaps) { - if (snap.dist < bestXDist) { - bestXDist = snap.dist; - snappedX = snap.val; - } - } - - for (const snap of ySnaps) { - if (snap.dist < bestYDist) { - bestYDist = snap.dist; - snappedY = snap.val; - } - } - } - - if (checkOverlap(testName, snappedX, snappedY, testW, testH)) { - if (!checkOverlap(testName, snappedX, posY, testW, testH)) - return Qt.point(snappedX, posY); - if (!checkOverlap(testName, posX, snappedY, testW, testH)) - return Qt.point(posX, snappedY); - return Qt.point(posX, posY); - } - return Qt.point(snappedX, snappedY); - } - - function findBestSnapPosition(testName, posX, posY, testW, testH) { - const outputNames = Object.keys(outputs).filter(n => n !== testName); - - if (outputNames.length === 0) - return Qt.point(posX, posY); - - let bestPos = null; - let bestDist = Infinity; - - for (const name of outputNames) { - const output = outputs[name]; - if (!output.logical) - continue; - const x = output.logical.x; - const y = output.logical.y; - const size = getLogicalSize(output); - - const candidates = [ - { - "px": x + size.w, - "py": y - }, - { - "px": x - testW, - "py": y - }, - { - "px": x, - "py": y + size.h - }, - { - "px": x, - "py": y - testH - }, - { - "px": x + size.w, - "py": y + size.h - testH - }, - { - "px": x - testW, - "py": y + size.h - testH - }, - { - "px": x + size.w - testW, - "py": y + size.h - }, - { - "px": x + size.w - testW, - "py": y - testH - } - ]; - - for (const c of candidates) { - if (checkOverlap(testName, c.px, c.py, testW, testH)) - continue; - const dist = Math.hypot(c.px - posX, c.py - posY); - if (dist < bestDist) { - bestDist = dist; - bestPos = Qt.point(c.px, c.py); - } - } - } - - return bestPos || Qt.point(posX, posY); - } - - function formatMode(mode) { - if (!mode) - return ""; - return mode.width + "x" + mode.height + "@" + (mode.refresh_rate / 1000).toFixed(3); - } - - function getTransformLabel(transform) { - switch (transform) { - case "Normal": - return I18n.tr("Normal"); - case "90": - return I18n.tr("90°"); - case "180": - return I18n.tr("180°"); - case "270": - return I18n.tr("270°"); - case "Flipped": - return I18n.tr("Flipped"); - case "Flipped90": - return I18n.tr("Flipped 90°"); - case "Flipped180": - return I18n.tr("Flipped 180°"); - case "Flipped270": - return I18n.tr("Flipped 270°"); - default: - return I18n.tr("Normal"); - } - } - - function getTransformValue(label) { - if (label === I18n.tr("Normal")) - return "Normal"; - if (label === I18n.tr("90°")) - return "90"; - if (label === I18n.tr("180°")) - return "180"; - if (label === I18n.tr("270°")) - return "270"; - if (label === I18n.tr("Flipped")) - return "Flipped"; - if (label === I18n.tr("Flipped 90°")) - return "Flipped90"; - if (label === I18n.tr("Flipped 180°")) - return "Flipped180"; - if (label === I18n.tr("Flipped 270°")) - return "Flipped270"; - return "Normal"; - } - - function setOriginalDisplayNameMode(mode) { - if (originalDisplayNameMode === "") - originalDisplayNameMode = mode; - } -} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/HyprlandOutputSettings.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/HyprlandOutputSettings.qml deleted file mode 100644 index 7c36e4b..0000000 --- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/HyprlandOutputSettings.qml +++ /dev/null @@ -1,310 +0,0 @@ -import QtQuick -import qs.Common -import qs.Widgets - -Column { - id: root - - property string outputName: "" - property var outputData: null - property bool expanded: false - - width: parent.width - spacing: 0 - - Rectangle { - width: parent.width - height: headerRow.implicitHeight + Theme.spacingS * 2 - color: headerMouse.containsMouse ? Theme.withAlpha(Theme.primary, 0.1) : "transparent" - radius: Theme.cornerRadius / 2 - - Row { - id: headerRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: Theme.spacingS - anchors.rightMargin: Theme.spacingS - spacing: Theme.spacingS - - DankIcon { - name: root.expanded ? "expand_more" : "chevron_right" - size: Theme.iconSize - color: Theme.primary - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: I18n.tr("Compositor Settings") - font.pixelSize: Theme.fontSizeMedium - font.weight: Font.Medium - color: Theme.primary - anchors.verticalCenter: parent.verticalCenter - } - } - - MouseArea { - id: headerMouse - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: root.expanded = !root.expanded - } - } - - Column { - id: settingsColumn - width: parent.width - spacing: Theme.spacingS - visible: root.expanded - topPadding: Theme.spacingS - - property int currentBitdepth: { - DisplayConfigState.pendingHyprlandChanges; - return DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "bitdepth", 8); - } - property bool is10Bit: currentBitdepth === 10 - - property string currentCm: { - DisplayConfigState.pendingHyprlandChanges; - return DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "colorManagement", "auto"); - } - property bool isHdrMode: currentCm === "hdr" || currentCm === "hdredid" - - DankToggle { - width: parent.width - text: I18n.tr("Disable Output") - checked: DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "disabled", false) - onToggled: checked => DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "disabled", checked) - } - - DankDropdown { - width: parent.width - text: I18n.tr("Mirror Display") - addHorizontalPadding: true - - property var otherOutputs: { - const list = [I18n.tr("None")]; - for (const name in DisplayConfigState.outputs) { - if (name !== root.outputName) - list.push(name); - } - return list; - } - options: otherOutputs - - currentValue: { - DisplayConfigState.pendingChanges; - const pending = DisplayConfigState.getPendingValue(root.outputName, "mirror"); - const val = pending !== undefined ? pending : (root.outputData.mirror || ""); - return val === "" ? I18n.tr("None") : val; - } - - onValueChanged: value => { - const realVal = value === I18n.tr("None") ? "" : value; - DisplayConfigState.setPendingChange(root.outputName, "mirror", realVal); - } - } - - DankToggle { - width: parent.width - text: I18n.tr("10-bit Color") - description: I18n.tr("Enable 10-bit color depth for wider color gamut and HDR support") - checked: settingsColumn.is10Bit - onToggled: checked => { - if (checked) { - DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "bitdepth", 10); - } else { - DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "bitdepth", null); - if (settingsColumn.isHdrMode) - DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "colorManagement", "auto"); - } - } - } - - Column { - width: parent.width - spacing: Theme.spacingS - visible: settingsColumn.is10Bit - - Rectangle { - width: parent.width - height: 1 - color: Theme.withAlpha(Theme.outline, 0.15) - } - - DankDropdown { - width: parent.width - text: I18n.tr("Color Gamut") - addHorizontalPadding: true - currentValue: { - DisplayConfigState.pendingHyprlandChanges; - const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "colorManagement", "auto"); - return cmLabelMap[val] || I18n.tr("Auto (Wide)"); - } - options: [I18n.tr("Auto (Wide)"), I18n.tr("Wide (BT2020)"), "DCI-P3", "Apple P3", "Adobe RGB", "EDID", "HDR", I18n.tr("HDR (EDID)")] - - property var cmValueMap: ({ - [I18n.tr("Auto (Wide)")]: "auto", - [I18n.tr("Wide (BT2020)")]: "wide", - "DCI-P3": "dcip3", - "Apple P3": "dp3", - "Adobe RGB": "adobe", - "EDID": "edid", - "HDR": "hdr", - [I18n.tr("HDR (EDID)")]: "hdredid" - }) - - property var cmLabelMap: ({ - "auto": I18n.tr("Auto (Wide)"), - "wide": I18n.tr("Wide (BT2020)"), - "dcip3": "DCI-P3", - "dp3": "Apple P3", - "adobe": "Adobe RGB", - "edid": "EDID", - "hdr": "HDR", - "hdredid": I18n.tr("HDR (EDID)") - }) - - onValueChanged: value => { - const cmValue = cmValueMap[value] || "auto"; - DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "colorManagement", cmValue); - } - } - - Rectangle { - width: parent.width - Theme.spacingM * 2 - anchors.horizontalCenter: parent.horizontalCenter - height: warningColumn.implicitHeight + Theme.spacingM * 2 - radius: Theme.cornerRadius / 2 - color: Theme.withAlpha(Theme.warning, 0.15) - border.color: Theme.withAlpha(Theme.warning, 0.3) - border.width: 1 - visible: settingsColumn.isHdrMode - - Column { - id: warningColumn - anchors.fill: parent - anchors.margins: Theme.spacingM - spacing: Theme.spacingXS - - Row { - spacing: Theme.spacingS - DankIcon { - name: "warning" - size: Theme.iconSize - 4 - color: Theme.warning - anchors.verticalCenter: parent.verticalCenter - } - StyledText { - text: I18n.tr("Experimental Feature") - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.Medium - color: Theme.warning - anchors.verticalCenter: parent.verticalCenter - } - } - StyledText { - text: I18n.tr("HDR mode is experimental. Verify your monitor supports HDR before enabling.") - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - wrapMode: Text.WordWrap - width: parent.width - } - } - } - - Column { - width: parent.width - spacing: Theme.spacingS - visible: settingsColumn.isHdrMode - - Rectangle { - width: parent.width - height: 1 - color: Theme.withAlpha(Theme.outline, 0.15) - } - - StyledText { - text: I18n.tr("HDR Tone Mapping") - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.Medium - color: Theme.surfaceVariantText - leftPadding: Theme.spacingM - } - - Row { - width: parent.width - Theme.spacingM * 2 - anchors.horizontalCenter: parent.horizontalCenter - spacing: Theme.spacingM - - Column { - width: (parent.width - Theme.spacingM) / 2 - spacing: Theme.spacingXS - - StyledText { - text: I18n.tr("SDR Brightness") - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - } - - DankTextField { - width: parent.width - height: 40 - placeholderText: "1.0 - 2.0" - text: { - DisplayConfigState.pendingHyprlandChanges; - const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "sdrBrightness", null); - return val !== null ? val.toString() : ""; - } - onEditingFinished: { - const trimmed = text.trim(); - if (!trimmed) { - DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "sdrBrightness", null); - return; - } - const val = parseFloat(trimmed); - if (isNaN(val) || val < 0.1 || val > 5.0) - return; - DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "sdrBrightness", parseFloat(val.toFixed(2))); - } - } - } - - Column { - width: (parent.width - Theme.spacingM) / 2 - spacing: Theme.spacingXS - - StyledText { - text: I18n.tr("SDR Saturation") - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - } - - DankTextField { - width: parent.width - height: 40 - placeholderText: "0.5 - 1.5" - text: { - DisplayConfigState.pendingHyprlandChanges; - const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "sdrSaturation", null); - return val !== null ? val.toString() : ""; - } - onEditingFinished: { - const trimmed = text.trim(); - if (!trimmed) { - DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "sdrSaturation", null); - return; - } - const val = parseFloat(trimmed); - if (isNaN(val) || val < 0.0 || val > 3.0) - return; - DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "sdrSaturation", parseFloat(val.toFixed(2))); - } - } - } - } - } - } - } -} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/IncludeWarningBox.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/IncludeWarningBox.qml deleted file mode 100644 index f5f270b..0000000 --- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/IncludeWarningBox.qml +++ /dev/null @@ -1,94 +0,0 @@ -import QtQuick -import qs.Common -import qs.Widgets - -StyledRect { - id: root - - LayoutMirroring.enabled: I18n.isRtl - LayoutMirroring.childrenInherit: true - - width: parent.width - height: warningContent.implicitHeight + Theme.spacingL * 2 - radius: Theme.cornerRadius - - readonly property bool showError: DisplayConfigState.includeStatus.exists && !DisplayConfigState.includeStatus.included - readonly property bool showSetup: !DisplayConfigState.includeStatus.exists && !DisplayConfigState.includeStatus.included - - color: (showError || showSetup) ? Theme.withAlpha(Theme.primary, 0.15) : "transparent" - border.color: (showError || showSetup) ? Theme.withAlpha(Theme.primary, 0.3) : "transparent" - border.width: 1 - visible: (showError || showSetup) && DisplayConfigState.hasOutputBackend && !DisplayConfigState.checkingInclude - - Column { - id: warningContent - anchors.fill: parent - anchors.margins: Theme.spacingL - spacing: Theme.spacingM - - Row { - width: parent.width - spacing: Theme.spacingM - - DankIcon { - name: "warning" - size: Theme.iconSize - color: Theme.primary - anchors.verticalCenter: parent.verticalCenter - } - - Column { - width: parent.width - Theme.iconSize - (fixButton.visible ? fixButton.width + Theme.spacingM : 0) - Theme.spacingM - spacing: Theme.spacingXS - anchors.verticalCenter: parent.verticalCenter - - StyledText { - text: { - if (root.showSetup) - return I18n.tr("First Time Setup"); - if (root.showError) - return I18n.tr("Outputs Include Missing"); - return ""; - } - font.pixelSize: Theme.fontSizeMedium - font.weight: Font.Medium - color: Theme.primary - width: parent.width - horizontalAlignment: Text.AlignLeft - } - - StyledText { - text: { - if (root.showSetup) - return I18n.tr("Click 'Setup' to create the outputs config and add include to your compositor config."); - if (root.showError) - return I18n.tr("dms/outputs config exists but is not included in your compositor config. Display changes won't persist."); - return ""; - } - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - wrapMode: Text.WordWrap - width: parent.width - horizontalAlignment: Text.AlignLeft - } - } - - DankButton { - id: fixButton - visible: root.showError || root.showSetup - text: { - if (DisplayConfigState.fixingInclude) - return I18n.tr("Fixing..."); - if (root.showSetup) - return I18n.tr("Setup"); - return I18n.tr("Fix Now"); - } - backgroundColor: Theme.primary - textColor: Theme.primaryText - enabled: !DisplayConfigState.fixingInclude - anchors.verticalCenter: parent.verticalCenter - onClicked: DisplayConfigState.fixOutputsInclude() - } - } - } -} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/MonitorCanvas.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/MonitorCanvas.qml deleted file mode 100644 index accdfd7..0000000 --- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/MonitorCanvas.qml +++ /dev/null @@ -1,82 +0,0 @@ -import QtQuick -import qs.Common - -Rectangle { - id: root - - property var filteredOutputs: { - const all = DisplayConfigState.allOutputs || {}; - const keys = Object.keys(all); - if (SettingsData.displayShowDisconnected) - return keys; - return keys.filter(k => all[k]?.connected); - } - - property var filteredBounds: { - const all = DisplayConfigState.allOutputs || {}; - let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; - for (const name of filteredOutputs) { - const output = all[name]; - if (!output?.logical) - continue; - const x = output.logical.x; - const y = output.logical.y; - const size = DisplayConfigState.getLogicalSize(output); - const w = size.w || 1920; - const h = size.h || 1080; - minX = Math.min(minX, x); - minY = Math.min(minY, y); - maxX = Math.max(maxX, x + w); - maxY = Math.max(maxY, y + h); - } - if (minX === Infinity) - return { - minX: 0, - minY: 0, - width: 1920, - height: 1080 - }; - return { - minX: minX, - minY: minY, - width: maxX - minX, - height: maxY - minY - }; - } - - width: parent.width - height: 280 - radius: Theme.cornerRadius - color: Theme.surfaceContainerHighest - border.color: Theme.outline - border.width: 1 - - Item { - id: canvas - anchors.fill: parent - anchors.margins: Theme.spacingL - - property var bounds: root.filteredBounds - property real scaleFactor: { - if (bounds.width === 0 || bounds.height === 0) - return 0.1; - const padding = Theme.spacingL * 2; - const scaleX = (width - padding) / bounds.width; - const scaleY = (height - padding) / bounds.height; - return Math.min(scaleX, scaleY); - } - property point offset: Qt.point((width - bounds.width * scaleFactor) / 2 - bounds.minX * scaleFactor, (height - bounds.height * scaleFactor) / 2 - bounds.minY * scaleFactor) - - Repeater { - model: root.filteredOutputs - - delegate: MonitorRect { - required property string modelData - outputName: modelData - outputData: DisplayConfigState.allOutputs[modelData] - canvasScaleFactor: canvas.scaleFactor - canvasOffset: canvas.offset - } - } - } -} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/MonitorRect.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/MonitorRect.qml deleted file mode 100644 index 0209e5f..0000000 --- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/MonitorRect.qml +++ /dev/null @@ -1,159 +0,0 @@ -import QtQuick -import qs.Common -import qs.Services -import qs.Widgets - -Rectangle { - id: root - - required property string outputName - required property var outputData - required property real canvasScaleFactor - required property point canvasOffset - - property bool isConnected: outputData?.connected ?? false - property bool isDragging: false - property point originalLogical: Qt.point(0, 0) - property point snappedLogical: Qt.point(0, 0) - property bool isValidPosition: true - - property var physSize: DisplayConfigState.getPhysicalSize(outputData) - property var logicalSize: DisplayConfigState.getLogicalSize(outputData) - - x: isDragging ? x : (outputData?.logical?.x ?? 0) * canvasScaleFactor + canvasOffset.x - y: isDragging ? y : (outputData?.logical?.y ?? 0) * canvasScaleFactor + canvasOffset.y - width: logicalSize.w * canvasScaleFactor - height: logicalSize.h * canvasScaleFactor - radius: Theme.cornerRadius - opacity: isConnected ? 1.0 : 0.5 - - color: { - if (!isConnected) - return Theme.surfaceContainerHighest; - if (!isValidPosition) - return Theme.withAlpha(Theme.error, 0.3); - if (isDragging) - return Theme.withAlpha(Theme.primary, 0.4); - if (dragArea.containsMouse) - return Theme.withAlpha(Theme.primary, 0.2); - return Theme.surfaceContainerHigh; - } - - border.color: { - if (!isConnected) - return Theme.outline; - if (!isValidPosition) - return Theme.error; - if (isDragging) - return Theme.primary; - if (CompositorService.getFocusedScreen()?.name === outputName) - return Theme.primary; - return Theme.outline; - } - border.width: isDragging ? 3 : 2 - z: isDragging ? 100 : (isConnected ? 1 : 0) - - Rectangle { - id: snapPreview - visible: root.isDragging && root.isValidPosition && SettingsData.displaySnapToEdge - x: root.snappedLogical.x * root.canvasScaleFactor + root.canvasOffset.x - root.x - y: root.snappedLogical.y * root.canvasScaleFactor + root.canvasOffset.y - root.y - width: parent.width - height: parent.height - radius: Theme.cornerRadius - color: "transparent" - border.color: Theme.primary - border.width: 2 - opacity: 0.6 - } - - Column { - anchors.centerIn: parent - spacing: 2 - - DankIcon { - name: root.isConnected ? "desktop_windows" : "desktop_access_disabled" - size: Math.min(24, Math.min(root.width * 0.3, root.height * 0.25)) - color: root.isConnected ? (root.isValidPosition ? Theme.primary : Theme.error) : Theme.surfaceVariantText - anchors.horizontalCenter: parent.horizontalCenter - } - - StyledText { - text: DisplayConfigState.getOutputDisplayName(root.outputData, root.outputName) - font.pixelSize: Math.max(10, Math.min(14, root.width * 0.12)) - font.weight: Font.Medium - color: root.isConnected ? Theme.surfaceText : Theme.surfaceVariantText - horizontalAlignment: Text.AlignHCenter - anchors.horizontalCenter: parent.horizontalCenter - elide: Text.ElideMiddle - width: Math.min(implicitWidth, root.width - 8) - } - - StyledText { - text: root.isConnected ? (root.physSize.w + "x" + root.physSize.h) : I18n.tr("Disconnected") - font.pixelSize: Math.max(8, Math.min(11, root.width * 0.09)) - color: Theme.surfaceVariantText - anchors.horizontalCenter: parent.horizontalCenter - } - } - - MouseArea { - id: dragArea - anchors.fill: parent - hoverEnabled: true - enabled: root.isConnected - cursorShape: !root.isConnected ? Qt.ArrowCursor : (root.isDragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor) - drag.target: root.isConnected ? root : null - drag.axis: Drag.XAndYAxis - drag.threshold: 0 - - onPressed: mouse => { - if (!root.isConnected) - return; - root.isDragging = true; - root.originalLogical = Qt.point(root.outputData?.logical?.x ?? 0, root.outputData?.logical?.y ?? 0); - root.snappedLogical = root.originalLogical; - root.isValidPosition = true; - } - - onPositionChanged: mouse => { - if (!root.isDragging || !root.isConnected) - return; - let posX = Math.round((root.x - root.canvasOffset.x) / root.canvasScaleFactor); - let posY = Math.round((root.y - root.canvasOffset.y) / root.canvasScaleFactor); - - const size = DisplayConfigState.getLogicalSize(root.outputData); - - const snapped = SettingsData.displaySnapToEdge ? DisplayConfigState.snapToEdges(root.outputName, posX, posY, size.w, size.h) : Qt.point(posX, posY); - root.snappedLogical = snapped; - root.isValidPosition = SettingsData.displaySnapToEdge ? !DisplayConfigState.checkOverlap(root.outputName, snapped.x, snapped.y, size.w, size.h) : true; - } - - onReleased: { - if (!root.isDragging || !root.isConnected) - return; - root.isDragging = false; - - const size = DisplayConfigState.getLogicalSize(root.outputData); - const finalX = root.snappedLogical.x; - const finalY = root.snappedLogical.y; - - if (SettingsData.displaySnapToEdge && DisplayConfigState.checkOverlap(root.outputName, finalX, finalY, size.w, size.h)) { - root.isValidPosition = true; - return; - } - - if (finalX === root.originalLogical.x && finalY === root.originalLogical.y) - return; - - DisplayConfigState.initOriginalOutputs(); - DisplayConfigState.backendUpdateOutputPosition(root.outputName, finalX, finalY); - DisplayConfigState.setPendingChange(root.outputName, "position", { - "x": finalX, - "y": finalY - }); - } - } - - Drag.active: dragArea.drag.active && root.isConnected -} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/NiriOutputSettings.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/NiriOutputSettings.qml deleted file mode 100644 index da9a4a7..0000000 --- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/NiriOutputSettings.qml +++ /dev/null @@ -1,368 +0,0 @@ -import QtQuick -import qs.Common -import qs.Widgets - -Column { - id: root - - property string outputName: "" - property var outputData: null - property bool expanded: false - - width: parent.width - spacing: 0 - - Rectangle { - width: parent.width - height: headerRow.implicitHeight + Theme.spacingS * 2 - color: headerMouse.containsMouse ? Theme.withAlpha(Theme.primary, 0.1) : "transparent" - radius: Theme.cornerRadius / 2 - - Row { - id: headerRow - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: Theme.spacingS - anchors.rightMargin: Theme.spacingS - spacing: Theme.spacingS - - DankIcon { - name: root.expanded ? "expand_more" : "chevron_right" - size: Theme.iconSize - color: Theme.primary - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: I18n.tr("Compositor Settings") - font.pixelSize: Theme.fontSizeMedium - font.weight: Font.Medium - color: Theme.primary - anchors.verticalCenter: parent.verticalCenter - } - } - - MouseArea { - id: headerMouse - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: root.expanded = !root.expanded - } - } - - Column { - width: parent.width - spacing: Theme.spacingS - visible: root.expanded - topPadding: Theme.spacingS - - DankToggle { - width: parent.width - text: I18n.tr("Disable Output") - checked: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "disabled", false) - onToggled: checked => DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "disabled", checked) - } - - DankToggle { - width: parent.width - text: I18n.tr("Focus at Startup") - checked: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "focusAtStartup", false) - onToggled: checked => DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "focusAtStartup", checked) - } - - DankDropdown { - width: parent.width - text: I18n.tr("Hot Corners") - addHorizontalPadding: true - - property var hotCornersData: { - void (DisplayConfigState.pendingNiriChanges); - return DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "hotCorners", null); - } - - currentValue: { - if (!hotCornersData) - return I18n.tr("Inherit"); - if (hotCornersData.off) - return I18n.tr("Off"); - const corners = hotCornersData.corners || []; - if (corners.length === 0) - return I18n.tr("Inherit"); - if (corners.length === 4) - return I18n.tr("All"); - return I18n.tr("Select..."); - } - options: [I18n.tr("Inherit"), I18n.tr("Off"), I18n.tr("All"), I18n.tr("Select...")] - - onValueChanged: value => { - switch (value) { - case I18n.tr("Inherit"): - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", null); - break; - case I18n.tr("Off"): - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", { - "off": true - }); - break; - case I18n.tr("All"): - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", { - "corners": ["top-left", "top-right", "bottom-left", "bottom-right"] - }); - break; - case I18n.tr("Select..."): - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", { - "corners": [] - }); - break; - } - } - } - - Item { - width: parent.width - height: hotCornersGroup.implicitHeight - clip: true - - property var hotCornersData: { - void (DisplayConfigState.pendingNiriChanges); - return DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "hotCorners", null); - } - - visible: hotCornersData && !hotCornersData.off && hotCornersData.corners !== undefined - - DankButtonGroup { - id: hotCornersGroup - anchors.horizontalCenter: parent.horizontalCenter - selectionMode: "multi" - checkEnabled: false - buttonHeight: 32 - buttonPadding: parent.width < 400 ? Theme.spacingXS : Theme.spacingM - minButtonWidth: parent.width < 400 ? 28 : 56 - textSize: parent.width < 400 ? 11 : Theme.fontSizeMedium - model: [I18n.tr("Top Left"), I18n.tr("Top Right"), I18n.tr("Bottom Left"), I18n.tr("Bottom Right")] - - property var cornerKeys: ["top-left", "top-right", "bottom-left", "bottom-right"] - - currentSelection: { - const hcData = parent.hotCornersData; - if (!hcData?.corners) - return []; - return hcData.corners.map(key => { - const idx = cornerKeys.indexOf(key); - return idx >= 0 ? model[idx] : null; - }).filter(v => v !== null); - } - - onSelectionChanged: (index, selected) => { - const corners = currentSelection.map(label => { - const idx = model.indexOf(label); - return idx >= 0 ? cornerKeys[idx] : null; - }).filter(v => v !== null); - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", { - "corners": corners - }); - } - } - } - - Rectangle { - width: parent.width - height: 1 - color: Theme.withAlpha(Theme.outline, 0.15) - } - - Item { - width: parent.width - height: layoutColumn.implicitHeight - - Column { - id: layoutColumn - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: Theme.spacingM - anchors.rightMargin: Theme.spacingM - spacing: Theme.spacingS - - Column { - width: parent.width - spacing: Theme.spacingXS - - StyledText { - text: I18n.tr("Layout Overrides") - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.Medium - color: Theme.surfaceVariantText - } - - StyledText { - text: I18n.tr("Override global layout settings for this output") - font.pixelSize: Theme.fontSizeSmall - color: Theme.withAlpha(Theme.surfaceVariantText, 0.7) - wrapMode: Text.WordWrap - width: parent.width - } - } - - Row { - width: parent.width - spacing: Theme.spacingM - - Column { - width: (parent.width - Theme.spacingM) / 2 - spacing: Theme.spacingXS - - StyledText { - text: I18n.tr("Window Gaps (px)") - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - } - - DankTextField { - width: parent.width - height: 40 - placeholderText: I18n.tr("Inherit") - text: { - const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null); - if (layout?.gaps === undefined) - return ""; - return layout.gaps.toString(); - } - onEditingFinished: { - const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", {}) || {}; - const trimmed = text.trim(); - if (!trimmed) { - delete layout.gaps; - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null); - return; - } - const val = parseInt(trimmed); - if (isNaN(val) || val < 0) - return; - layout.gaps = val; - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", layout); - } - } - } - - Column { - width: (parent.width - Theme.spacingM) / 2 - spacing: Theme.spacingXS - - StyledText { - text: I18n.tr("Default Width (%)") - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - } - - DankTextField { - width: parent.width - height: 40 - placeholderText: I18n.tr("Inherit") - text: { - const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null); - if (!layout?.defaultColumnWidth) - return ""; - if (layout.defaultColumnWidth.type !== "proportion") - return ""; - const percent = layout.defaultColumnWidth.value * 100; - return parseFloat(percent.toFixed(4)).toString(); - } - onEditingFinished: { - const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", {}) || {}; - const trimmed = text.trim().replace("%", ""); - if (!trimmed) { - delete layout.defaultColumnWidth; - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null); - return; - } - const val = parseFloat(trimmed); - if (isNaN(val) || val <= 0 || val > 100) - return; - layout.defaultColumnWidth = { - "type": "proportion", - "value": parseFloat((val / 100).toFixed(6)) - }; - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", layout); - } - } - } - } - - Column { - width: parent.width - spacing: Theme.spacingXS - - StyledText { - text: I18n.tr("Preset Widths (%)") - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - } - - StyledText { - text: "e.g. 33.33, 50, 66.67" - font.pixelSize: Theme.fontSizeSmall - color: Theme.withAlpha(Theme.surfaceVariantText, 0.7) - } - - DankTextField { - width: parent.width - height: 40 - placeholderText: I18n.tr("Inherit") - text: { - const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null); - const presets = layout?.presetColumnWidths || []; - if (presets.length === 0) - return ""; - return presets.filter(p => p.type === "proportion").map(p => parseFloat((p.value * 100).toFixed(4))).join(", "); - } - onEditingFinished: { - const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", {}) || {}; - const trimmed = text.trim(); - if (!trimmed) { - delete layout.presetColumnWidths; - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null); - return; - } - const parts = trimmed.split(/[,\s]+/).filter(s => s); - const presets = []; - for (const part of parts) { - const val = parseFloat(part.replace("%", "")); - if (!isNaN(val) && val > 0 && val <= 100) - presets.push({ - "type": "proportion", - "value": parseFloat((val / 100).toFixed(6)) - }); - } - if (presets.length === 0) { - delete layout.presetColumnWidths; - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null); - return; - } - presets.sort((a, b) => a.value - b.value); - layout.presetColumnWidths = presets; - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", layout); - } - } - } - } - } - - DankToggle { - width: parent.width - text: I18n.tr("Center Single Column") - property var layoutData: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null) - checked: layoutData?.alwaysCenterSingleColumn ?? false - onToggled: checked => { - const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", {}) || {}; - if (checked) { - layout.alwaysCenterSingleColumn = true; - } else { - delete layout.alwaysCenterSingleColumn; - } - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null); - } - } - } -} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/NoBackendMessage.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/NoBackendMessage.qml deleted file mode 100644 index 93ab124..0000000 --- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/NoBackendMessage.qml +++ /dev/null @@ -1,60 +0,0 @@ -import QtQuick -import qs.Common -import qs.Widgets - -StyledRect { - id: root - - LayoutMirroring.enabled: I18n.isRtl - LayoutMirroring.childrenInherit: true - - width: parent.width - height: messageContent.implicitHeight + Theme.spacingL * 2 - radius: Theme.cornerRadius - color: Theme.surfaceContainerHigh - border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2) - border.width: 0 - - Column { - id: messageContent - anchors.fill: parent - anchors.margins: Theme.spacingL - spacing: Theme.spacingM - - Row { - width: parent.width - spacing: Theme.spacingM - - DankIcon { - name: "monitor" - size: Theme.iconSize - color: Theme.surfaceVariantText - anchors.verticalCenter: parent.verticalCenter - } - - Column { - width: parent.width - Theme.iconSize - Theme.spacingM - spacing: Theme.spacingXS - anchors.verticalCenter: parent.verticalCenter - - StyledText { - text: I18n.tr("Monitor Configuration") - font.pixelSize: Theme.fontSizeLarge - font.weight: Font.Medium - color: Theme.surfaceText - width: parent.width - horizontalAlignment: Text.AlignLeft - } - - StyledText { - text: I18n.tr("Display configuration is not available. WLR output management protocol not supported.") - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - wrapMode: Text.WordWrap - width: parent.width - horizontalAlignment: Text.AlignLeft - } - } - } - } -} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml deleted file mode 100644 index 0da1175..0000000 --- a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml +++ /dev/null @@ -1,352 +0,0 @@ -import QtQuick -import qs.Common -import qs.Services -import qs.Widgets - -StyledRect { - id: root - - LayoutMirroring.enabled: I18n.isRtl - LayoutMirroring.childrenInherit: true - - required property string outputName - required property var outputData - property bool isConnected: outputData?.connected ?? false - - width: parent.width - height: settingsColumn.implicitHeight + Theme.spacingM * 2 - radius: Theme.cornerRadius - color: Theme.withAlpha(Theme.surfaceContainerHigh, isConnected ? 0.5 : 0.3) - border.color: Theme.withAlpha(Theme.outline, 0.3) - border.width: 1 - opacity: isConnected ? 1.0 : 0.7 - - Column { - id: settingsColumn - anchors.fill: parent - anchors.margins: Theme.spacingM - spacing: Theme.spacingS - - Row { - width: parent.width - spacing: Theme.spacingM - - DankIcon { - name: root.isConnected ? "desktop_windows" : "desktop_access_disabled" - size: Theme.iconSize - 4 - color: root.isConnected ? Theme.primary : Theme.surfaceVariantText - anchors.verticalCenter: parent.verticalCenter - } - - Column { - width: parent.width - Theme.iconSize - Theme.spacingM - (disconnectedBadge.visible ? disconnectedBadge.width + deleteButton.width + Theme.spacingS * 2 : 0) - spacing: 2 - - StyledText { - text: DisplayConfigState.getOutputDisplayName(root.outputData, root.outputName) - font.pixelSize: Theme.fontSizeMedium - font.weight: Font.Medium - color: root.isConnected ? Theme.surfaceText : Theme.surfaceVariantText - width: parent.width - horizontalAlignment: Text.AlignLeft - } - - StyledText { - text: (root.outputData?.model ?? "") + (root.outputData?.make ? " - " + root.outputData.make : "") - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - width: parent.width - horizontalAlignment: Text.AlignLeft - } - } - - Rectangle { - id: disconnectedBadge - visible: !root.isConnected - width: disconnectedText.implicitWidth + Theme.spacingM - height: disconnectedText.implicitHeight + Theme.spacingXS - radius: height / 2 - color: Theme.withAlpha(Theme.outline, 0.3) - anchors.verticalCenter: parent.verticalCenter - - StyledText { - id: disconnectedText - text: I18n.tr("Disconnected") - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - anchors.centerIn: parent - } - } - - Rectangle { - id: deleteButton - visible: !root.isConnected - width: 28 - height: 28 - radius: Theme.cornerRadius - color: deleteArea.containsMouse ? Theme.errorHover : "transparent" - anchors.verticalCenter: parent.verticalCenter - - DankIcon { - anchors.centerIn: parent - name: "delete" - size: 18 - color: deleteArea.containsMouse ? Theme.error : Theme.surfaceVariantText - } - - MouseArea { - id: deleteArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: DisplayConfigState.deleteDisconnectedOutput(root.outputName) - } - } - } - - DankDropdown { - width: parent.width - text: I18n.tr("Resolution & Refresh") - visible: root.isConnected - currentValue: { - const pendingMode = DisplayConfigState.getPendingValue(root.outputName, "mode"); - if (pendingMode) - return pendingMode; - const data = DisplayConfigState.outputs[root.outputName]; - if (!data?.modes || data?.current_mode === undefined) - return "Auto"; - const mode = data.modes[data.current_mode]; - return mode ? DisplayConfigState.formatMode(mode) : "Auto"; - } - options: { - const data = DisplayConfigState.outputs[root.outputName]; - if (!data?.modes) - return ["Auto"]; - const opts = []; - for (var i = 0; i < data.modes.length; i++) { - opts.push(DisplayConfigState.formatMode(data.modes[i])); - } - return opts; - } - onValueChanged: value => DisplayConfigState.setPendingChange(root.outputName, "mode", value) - } - - StyledText { - visible: !root.isConnected - text: I18n.tr("Configuration will be preserved when this display reconnects") - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - wrapMode: Text.WordWrap - width: parent.width - horizontalAlignment: Text.AlignLeft - } - - Row { - width: parent.width - spacing: Theme.spacingM - visible: root.isConnected - - Column { - width: (parent.width - Theme.spacingM) / 2 - spacing: Theme.spacingXS - - StyledText { - text: I18n.tr("Scale") - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - width: parent.width - horizontalAlignment: Text.AlignLeft - } - - Item { - id: scaleContainer - width: parent.width - height: scaleDropdown.visible ? scaleDropdown.height : scaleInput.height - - property bool customMode: false - property string currentScale: { - const pendingScale = DisplayConfigState.getPendingValue(root.outputName, "scale"); - if (pendingScale !== undefined) - return parseFloat(pendingScale.toFixed(2)).toString(); - const scale = DisplayConfigState.outputs[root.outputName]?.logical?.scale ?? 1.0; - return parseFloat(scale.toFixed(2)).toString(); - } - - DankDropdown { - id: scaleDropdown - width: parent.width - dropdownWidth: parent.width - visible: !scaleContainer.customMode - currentValue: scaleContainer.currentScale - options: { - const standard = ["0.5", "0.75", "1", "1.25", "1.5", "1.75", "2", "2.5", "3", I18n.tr("Custom...")]; - const current = scaleContainer.currentScale; - if (standard.slice(0, -1).includes(current)) - return standard; - const opts = [...standard.slice(0, -1), current, standard[standard.length - 1]]; - return opts.sort((a, b) => { - if (a === I18n.tr("Custom...")) - return 1; - if (b === I18n.tr("Custom...")) - return -1; - return parseFloat(a) - parseFloat(b); - }); - } - onValueChanged: value => { - if (value === I18n.tr("Custom...")) { - scaleContainer.customMode = true; - scaleInput.text = scaleContainer.currentScale; - scaleInput.forceActiveFocus(); - scaleInput.selectAll(); - return; - } - DisplayConfigState.setPendingChange(root.outputName, "scale", parseFloat(value)); - } - } - - DankTextField { - id: scaleInput - width: parent.width - height: 40 - visible: scaleContainer.customMode - placeholderText: "0.5 - 4.0" - - function applyValue() { - const val = parseFloat(text); - if (isNaN(val) || val < 0.25 || val > 4) { - text = scaleContainer.currentScale; - scaleContainer.customMode = false; - return; - } - DisplayConfigState.setPendingChange(root.outputName, "scale", parseFloat(val.toFixed(2))); - scaleContainer.customMode = false; - } - - onAccepted: applyValue() - onEditingFinished: applyValue() - Keys.onEscapePressed: { - text = scaleContainer.currentScale; - scaleContainer.customMode = false; - } - } - } - } - - Column { - width: (parent.width - Theme.spacingM) / 2 - spacing: Theme.spacingXS - - StyledText { - text: I18n.tr("Transform") - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceVariantText - width: parent.width - horizontalAlignment: Text.AlignLeft - } - - DankDropdown { - width: parent.width - dropdownWidth: parent.width - currentValue: { - const pendingTransform = DisplayConfigState.getPendingValue(root.outputName, "transform"); - if (pendingTransform) - return DisplayConfigState.getTransformLabel(pendingTransform); - const data = DisplayConfigState.outputs[root.outputName]; - return DisplayConfigState.getTransformLabel(data?.logical?.transform ?? "Normal"); - } - options: [I18n.tr("Normal"), I18n.tr("90°"), I18n.tr("180°"), I18n.tr("270°"), I18n.tr("Flipped"), I18n.tr("Flipped 90°"), I18n.tr("Flipped 180°"), I18n.tr("Flipped 270°")] - onValueChanged: value => DisplayConfigState.setPendingChange(root.outputName, "transform", DisplayConfigState.getTransformValue(value)) - } - } - } - - DankToggle { - width: parent.width - text: I18n.tr("Variable Refresh Rate") - visible: root.isConnected && !CompositorService.isDwl && !CompositorService.isHyprland && !CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false) - checked: { - const pendingVrr = DisplayConfigState.getPendingValue(root.outputName, "vrr"); - if (pendingVrr !== undefined) - return pendingVrr; - return DisplayConfigState.outputs[root.outputName]?.vrr_enabled ?? false; - } - onToggled: checked => DisplayConfigState.setPendingChange(root.outputName, "vrr", checked) - } - - DankDropdown { - width: parent.width - text: I18n.tr("Variable Refresh Rate") - visible: root.isConnected && CompositorService.isHyprland && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false) - options: [I18n.tr("Off"), I18n.tr("On"), I18n.tr("Fullscreen Only")] - currentValue: { - DisplayConfigState.pendingHyprlandChanges; - if (DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "vrrFullscreenOnly", false)) - return I18n.tr("Fullscreen Only"); - const pendingVrr = DisplayConfigState.getPendingValue(root.outputName, "vrr"); - const vrrEnabled = pendingVrr !== undefined ? pendingVrr : (DisplayConfigState.outputs[root.outputName]?.vrr_enabled ?? false); - if (vrrEnabled) - return I18n.tr("On"); - return I18n.tr("Off"); - } - onValueChanged: value => { - const off = I18n.tr("Off"); - const fullscreen = I18n.tr("Fullscreen Only"); - DisplayConfigState.setPendingChange(root.outputName, "vrr", value !== off); - DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "vrrFullscreenOnly", value === fullscreen || null); - } - } - - DankDropdown { - width: parent.width - text: I18n.tr("Variable Refresh Rate") - visible: root.isConnected && CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false) - options: [I18n.tr("Off"), I18n.tr("On"), I18n.tr("On-Demand")] - currentValue: { - DisplayConfigState.pendingNiriChanges; - if (DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "vrrOnDemand", false)) - return I18n.tr("On-Demand"); - const pendingVrr = DisplayConfigState.getPendingValue(root.outputName, "vrr"); - const vrrEnabled = pendingVrr !== undefined ? pendingVrr : (DisplayConfigState.outputs[root.outputName]?.vrr_enabled ?? false); - if (!vrrEnabled) - return I18n.tr("Off"); - return I18n.tr("On"); - } - onValueChanged: value => { - const off = I18n.tr("Off"); - const onDemand = I18n.tr("On-Demand"); - DisplayConfigState.setPendingChange(root.outputName, "vrr", value !== off); - DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "vrrOnDemand", value === onDemand || null); - } - } - - Rectangle { - width: parent.width - height: 1 - color: Theme.withAlpha(Theme.outline, 0.2) - visible: compositorSettingsLoader.active - } - - Loader { - id: compositorSettingsLoader - width: parent.width - active: root.isConnected && compositorSettingsSource !== "" - source: compositorSettingsSource - - property string compositorSettingsSource: { - switch (CompositorService.compositor) { - case "niri": - return "NiriOutputSettings.qml"; - case "hyprland": - return "HyprlandOutputSettings.qml"; - default: - return ""; - } - } - - onLoaded: { - item.outputName = root.outputName; - item.outputData = root.outputData; - } - } - } -} |