summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig
diff options
context:
space:
mode:
authorNippy <nippy@rp1.hu>2026-05-09 13:33:09 +0200
committerNippy <nippy@rp1.hu>2026-05-09 13:33:09 +0200
commita2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5 (patch)
tree1a8769217f84bfe9d6216fafaadaf8cdd876d457 /raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig
parent34b9c34f982b2596cfa9e368a2d7f74e9a17477c (diff)
downloadRaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.tar.gz
RaveOS-PKGBUILD-a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5.zip
raveos update
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml1837
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/HyprlandOutputSettings.qml310
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/IncludeWarningBox.qml94
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/MonitorCanvas.qml82
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/MonitorRect.qml159
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/NiriOutputSettings.qml368
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/NoBackendMessage.qml60
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml352
8 files changed, 0 insertions, 3262 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml
deleted file mode 100644
index b6e253b..0000000
--- a/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/HyprlandOutputSettings.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/HyprlandOutputSettings.qml
deleted file mode 100644
index 7c36e4b..0000000
--- a/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/IncludeWarningBox.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/IncludeWarningBox.qml
deleted file mode 100644
index f5f270b..0000000
--- a/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/MonitorCanvas.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/MonitorCanvas.qml
deleted file mode 100644
index accdfd7..0000000
--- a/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/MonitorRect.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/MonitorRect.qml
deleted file mode 100644
index 0209e5f..0000000
--- a/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/NiriOutputSettings.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/NiriOutputSettings.qml
deleted file mode 100644
index da9a4a7..0000000
--- a/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/NoBackendMessage.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/NoBackendMessage.qml
deleted file mode 100644
index 93ab124..0000000
--- a/raveos-hyprland-theme/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-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml
deleted file mode 100644
index 0da1175..0000000
--- a/raveos-hyprland-theme/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;
- }
- }
- }
-}