mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-16 01:02:46 -04:00
Automatic Display Profiles Enhancement & Disabled Output Support (#2367)
* fix(niri): properly close KDL output block when disabled * feat(display): parse off directive and sync disabled state from compositor configs * feat(display): visual treatment and canvas filtering for disabled outputs * feat(display): eager auto-profile generation with debounced auto-select * refactor(display): pass target profile ID to confirmChanges and remove dead code * fix(display): make profile dropdown reactive by binding to property directly * i18n(display): add Disabled translation term for output state
This commit is contained in:
@@ -262,6 +262,17 @@ Singleton {
|
|||||||
return "profile_" + Date.now() + "_" + Math.random().toString(36).slice(2, 9);
|
return "profile_" + Date.now() + "_" + Math.random().toString(36).slice(2, 9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateAutoProfileId(outputIdentifiers) {
|
||||||
|
const fp = outputSetFingerprint(outputIdentifiers);
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < fp.length; i++) {
|
||||||
|
const char = fp.charCodeAt(i);
|
||||||
|
hash = ((hash << 5) - hash) + char;
|
||||||
|
}
|
||||||
|
const hashStr = (hash >>> 0).toString(16);
|
||||||
|
return "auto_" + hashStr;
|
||||||
|
}
|
||||||
|
|
||||||
function configFingerprint(configEntry) {
|
function configFingerprint(configEntry) {
|
||||||
return Object.keys(configEntry.outputs || {}).sort().join("+");
|
return Object.keys(configEntry.outputs || {}).sort().join("+");
|
||||||
}
|
}
|
||||||
@@ -282,41 +293,22 @@ Singleton {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findConfigEntryByFingerprint(data, outputIdentifiers) {
|
function findConfigEntryByFingerprint(data, outputIdentifiers, autoOnly) {
|
||||||
const targetKey = outputSetFingerprint(outputIdentifiers);
|
const targetKey = outputSetFingerprint(outputIdentifiers);
|
||||||
const configs = data.configurations || [];
|
const configs = data.configurations || [];
|
||||||
for (let i = 0; i < configs.length; i++) {
|
for (let i = 0; i < configs.length; i++) {
|
||||||
if (configFingerprint(configs[i]) === targetKey)
|
if (configFingerprint(configs[i]) === targetKey) {
|
||||||
|
if (autoOnly && configs[i].name)
|
||||||
|
continue;
|
||||||
return {
|
return {
|
||||||
entry: configs[i],
|
entry: configs[i],
|
||||||
index: i
|
index: i
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findPartialConfigEntry(data, outputIdentifiers) {
|
|
||||||
const currentSet = new Set(outputIdentifiers);
|
|
||||||
const configs = data.configurations || [];
|
|
||||||
let bestEntry = null;
|
|
||||||
let bestCount = 0;
|
|
||||||
for (let i = 0; i < configs.length; i++) {
|
|
||||||
const cfgKeys = Object.keys(configs[i].outputs || {});
|
|
||||||
if (cfgKeys.length === 0)
|
|
||||||
continue;
|
|
||||||
if (!cfgKeys.every(k => currentSet.has(k)))
|
|
||||||
continue;
|
|
||||||
if (cfgKeys.length > bestCount) {
|
|
||||||
bestCount = cfgKeys.length;
|
|
||||||
bestEntry = {
|
|
||||||
entry: configs[i],
|
|
||||||
index: i
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bestEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getProfileMonitorInclusion(profileId) {
|
function getProfileMonitorInclusion(profileId) {
|
||||||
const profile = validatedProfiles[profileId];
|
const profile = validatedProfiles[profileId];
|
||||||
const profileOutputIds = new Set(Object.keys(profile?.outputs || {}));
|
const profileOutputIds = new Set(Object.keys(profile?.outputs || {}));
|
||||||
@@ -503,15 +495,13 @@ Singleton {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onWriteSuccess = () => {
|
const onWriteSuccess = () => {
|
||||||
SettingsData.setActiveDisplayProfile(CompositorService.compositor, configEntry.name ? configId : "");
|
SettingsData.setActiveDisplayProfile(CompositorService.compositor, configId);
|
||||||
if (isManual) {
|
if (isManual) {
|
||||||
WlrOutputService.requestState();
|
|
||||||
profilesLoading = false;
|
profilesLoading = false;
|
||||||
profileActivated(configId, profileName);
|
profileActivated(configId, profileName);
|
||||||
manualActivationTimer.restart();
|
manualActivationTimer.restart();
|
||||||
} else {
|
|
||||||
saveConfigEntry(configEntry);
|
|
||||||
}
|
}
|
||||||
|
WlrOutputService.requestState();
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (CompositorService.compositor) {
|
switch (CompositorService.compositor) {
|
||||||
@@ -591,6 +581,8 @@ Singleton {
|
|||||||
const currentKey = currentOutputSet.join("+");
|
const currentKey = currentOutputSet.join("+");
|
||||||
for (const id in validatedProfiles) {
|
for (const id in validatedProfiles) {
|
||||||
const p = validatedProfiles[id];
|
const p = validatedProfiles[id];
|
||||||
|
if (p.name === "")
|
||||||
|
continue;
|
||||||
if (Object.keys(p.outputs || {}).sort().join("+") === currentKey)
|
if (Object.keys(p.outputs || {}).sort().join("+") === currentKey)
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -696,42 +688,57 @@ Singleton {
|
|||||||
onTriggered: root.manualActivation = false
|
onTriggered: root.manualActivation = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: autoSelectDebounceTimer
|
||||||
|
interval: 400
|
||||||
|
onTriggered: {
|
||||||
|
if (root.hasPendingChanges)
|
||||||
|
return;
|
||||||
|
root.applyAutoConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function applyAutoConfig() {
|
function applyAutoConfig() {
|
||||||
if (!profilesReady || !SettingsData.displayProfileAutoSelect || manualActivation || !currentOutputSet.length)
|
if (!profilesReady || !SettingsData.displayProfileAutoSelect || manualActivation || !currentOutputSet.length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
readMonitorsJson(data => {
|
readMonitorsJson(data => {
|
||||||
const match = findConfigEntryByFingerprint(data, currentOutputSet);
|
const match = findConfigEntryByFingerprint(data, currentOutputSet, true);
|
||||||
if (match) {
|
if (match) {
|
||||||
applyConfigEntry(match.entry, match.entry.id, match.entry.name || "", false);
|
applyConfigEntry(match.entry, match.entry.id, "", false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const partial = findPartialConfigEntry(data, currentOutputSet);
|
const outputConfigs = buildCurrentOutputConfigs();
|
||||||
const niriSettings = buildMergedNiriSettings();
|
const id = generateAutoProfileId(currentOutputSet);
|
||||||
const hyprlandSettings = buildMergedHyprlandSettings();
|
const existingIdx = data.configurations.findIndex(c => c.id === id);
|
||||||
const mergedOutputs = buildOutputsWithPendingChanges();
|
if (existingIdx >= 0)
|
||||||
|
data.configurations[existingIdx] = {
|
||||||
const outputConfigs = partial ? JSON.parse(JSON.stringify(partial.entry.outputs || {})) : {};
|
"id": id,
|
||||||
|
"name": "",
|
||||||
for (const name in outputs) {
|
"outputs": outputConfigs
|
||||||
const outputId = getOutputIdentifier(outputs[name], name);
|
};
|
||||||
const alreadyCovered = Object.keys(outputConfigs).some(k => k === outputId);
|
else
|
||||||
if (!alreadyCovered) {
|
data.configurations.push({
|
||||||
const od = mergedOutputs[name];
|
"id": id,
|
||||||
if (od)
|
"name": "",
|
||||||
outputConfigs[outputId] = extractOutputNeutralConfig(name, od, niriSettings, hyprlandSettings);
|
"outputs": outputConfigs
|
||||||
}
|
});
|
||||||
}
|
writeMonitorsJson(data, success => {
|
||||||
|
if (!success)
|
||||||
if (Object.keys(outputConfigs).length === 0)
|
|
||||||
return;
|
return;
|
||||||
|
const updated = JSON.parse(JSON.stringify(validatedProfiles));
|
||||||
const syntheticEntry = {
|
updated[id] = {
|
||||||
|
id: id,
|
||||||
name: "",
|
name: "",
|
||||||
outputs: outputConfigs
|
outputs: outputConfigs
|
||||||
};
|
};
|
||||||
applyConfigEntry(syntheticEntry, "", "", false);
|
validatedProfiles = updated;
|
||||||
|
matchedProfile = "";
|
||||||
|
const match = findConfigEntryById(data, id);
|
||||||
|
if (match)
|
||||||
|
applyConfigEntry(match.entry, id, "", false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,22 +755,6 @@ Singleton {
|
|||||||
return outputConfigs;
|
return outputConfigs;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveConfigEntry(configEntry) {
|
|
||||||
if (!configEntry.id || !configEntry.name)
|
|
||||||
return;
|
|
||||||
readMonitorsJson(data => {
|
|
||||||
const match = findConfigEntryById(data, configEntry.id);
|
|
||||||
if (!match)
|
|
||||||
return;
|
|
||||||
data.configurations[match.index] = {
|
|
||||||
"id": configEntry.id,
|
|
||||||
"name": configEntry.name,
|
|
||||||
"outputs": configEntry.outputs
|
|
||||||
};
|
|
||||||
writeMonitorsJson(data, null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteDisconnectedOutput(outputName) {
|
function deleteDisconnectedOutput(outputName) {
|
||||||
if (outputs[outputName]?.connected)
|
if (outputs[outputName]?.connected)
|
||||||
return;
|
return;
|
||||||
@@ -831,7 +822,7 @@ Singleton {
|
|||||||
if (hasPendingChanges)
|
if (hasPendingChanges)
|
||||||
clearPendingChanges();
|
clearPendingChanges();
|
||||||
currentOutputSet = newOutputSet;
|
currentOutputSet = newOutputSet;
|
||||||
applyAutoConfig();
|
autoSelectDebounceTimer.restart();
|
||||||
}
|
}
|
||||||
onSavedOutputsChanged: allOutputs = buildAllOutputsMap()
|
onSavedOutputsChanged: allOutputs = buildAllOutputsMap()
|
||||||
onLastAppliedEntryChanged: allOutputs = buildAllOutputsMap()
|
onLastAppliedEntryChanged: allOutputs = buildAllOutputsMap()
|
||||||
@@ -875,9 +866,12 @@ Singleton {
|
|||||||
if (CompositorService.isHyprland) {
|
if (CompositorService.isHyprland) {
|
||||||
initHyprlandSettingsFromConfig(parsed);
|
initHyprlandSettingsFromConfig(parsed);
|
||||||
syncHyprlandVrrFromConfig(parsed);
|
syncHyprlandVrrFromConfig(parsed);
|
||||||
|
syncHyprlandDisabledFromConfig(parsed);
|
||||||
}
|
}
|
||||||
if (CompositorService.isNiri)
|
if (CompositorService.isNiri) {
|
||||||
syncNiriVrrFromConfig(parsed);
|
syncNiriVrrFromConfig(parsed);
|
||||||
|
syncNiriDisabledFromConfig(parsed);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -954,6 +948,44 @@ Singleton {
|
|||||||
SettingsData.saveSettings();
|
SettingsData.saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncHyprlandDisabledFromConfig(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?.disabled ?? false;
|
||||||
|
const stored = current[outputName]?.disabled ?? false;
|
||||||
|
if (fromConfig === stored)
|
||||||
|
continue;
|
||||||
|
if (!current[outputName])
|
||||||
|
current[outputName] = {};
|
||||||
|
if (fromConfig)
|
||||||
|
current[outputName].disabled = true;
|
||||||
|
else
|
||||||
|
delete current[outputName].disabled;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
SettingsData.hyprlandOutputSettings = current;
|
||||||
|
SettingsData.saveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncNiriDisabledFromConfig(parsedOutputs) {
|
||||||
|
let changed = false;
|
||||||
|
for (const outputName in parsedOutputs) {
|
||||||
|
const output = parsedOutputs[outputName];
|
||||||
|
const fromConfig = output.disabled ?? false;
|
||||||
|
const current = SettingsData.getNiriOutputSetting(outputName, "disabled", false);
|
||||||
|
if (current === fromConfig)
|
||||||
|
continue;
|
||||||
|
SettingsData.setNiriOutputSetting(outputName, "disabled", fromConfig || undefined);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed)
|
||||||
|
SettingsData.saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
function filterDisconnectedOnly(parsedOutputs) {
|
function filterDisconnectedOnly(parsedOutputs) {
|
||||||
const result = {};
|
const result = {};
|
||||||
const liveNames = Object.keys(outputs);
|
const liveNames = Object.keys(outputs);
|
||||||
@@ -998,6 +1030,15 @@ Singleton {
|
|||||||
const name = match[1];
|
const name = match[1];
|
||||||
const body = match[2];
|
const body = match[2];
|
||||||
|
|
||||||
|
if (body.trim() === "off") {
|
||||||
|
result[name] = {
|
||||||
|
"name": name,
|
||||||
|
"disabled": true,
|
||||||
|
"logical": { "x": 0, "y": 0, "scale": 1.0, "transform": "Normal" }
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const modeMatch = body.match(/mode\s+"(\d+)x(\d+)@([\d.]+)"/);
|
const modeMatch = body.match(/mode\s+"(\d+)x(\d+)@([\d.]+)"/);
|
||||||
const posMatch = body.match(/position\s+x=(-?\d+)\s+y=(-?\d+)/);
|
const posMatch = body.match(/position\s+x=(-?\d+)\s+y=(-?\d+)/);
|
||||||
const scaleMatch = body.match(/scale\s+([\d.]+)/);
|
const scaleMatch = body.match(/scale\s+([\d.]+)/);
|
||||||
@@ -1979,23 +2020,23 @@ Singleton {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmChanges() {
|
function confirmChanges(profileId) {
|
||||||
const outputConfigs = buildCurrentOutputConfigs();
|
const outputConfigs = buildCurrentOutputConfigs();
|
||||||
lastAppliedEntry = {
|
lastAppliedEntry = { outputs: outputConfigs };
|
||||||
outputs: outputConfigs
|
|
||||||
};
|
|
||||||
|
|
||||||
|
if (profileId) {
|
||||||
readMonitorsJson(data => {
|
readMonitorsJson(data => {
|
||||||
const match = findConfigEntryByFingerprint(data, Object.keys(outputConfigs));
|
const match = findConfigEntryById(data, profileId);
|
||||||
if (!match || !match.entry.name)
|
if (match) {
|
||||||
return;
|
|
||||||
data.configurations[match.index] = {
|
data.configurations[match.index] = {
|
||||||
"id": match.entry.id,
|
"id": match.entry.id,
|
||||||
"name": match.entry.name,
|
"name": match.entry.name || "",
|
||||||
"outputs": outputConfigs
|
"outputs": outputConfigs
|
||||||
};
|
};
|
||||||
writeMonitorsJson(data, null);
|
writeMonitorsJson(data, null);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
clearPendingChanges();
|
clearPendingChanges();
|
||||||
changesConfirmed();
|
changesConfirmed();
|
||||||
@@ -2148,10 +2189,22 @@ Singleton {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isOutputDisabled(outputName) {
|
||||||
|
if (!outputs[outputName])
|
||||||
|
return false;
|
||||||
|
if (CompositorService.isHyprland)
|
||||||
|
return getHyprlandSetting(outputs[outputName], outputName, "disabled", false);
|
||||||
|
if (CompositorService.isNiri)
|
||||||
|
return getNiriSetting(outputs[outputName], outputName, "disabled", false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function checkOverlap(testName, testX, testY, testW, testH) {
|
function checkOverlap(testName, testX, testY, testW, testH) {
|
||||||
for (const name in outputs) {
|
for (const name in outputs) {
|
||||||
if (name === testName)
|
if (name === testName)
|
||||||
continue;
|
continue;
|
||||||
|
if (isOutputDisabled(name))
|
||||||
|
continue;
|
||||||
const output = outputs[name];
|
const output = outputs[name];
|
||||||
if (!output.logical)
|
if (!output.logical)
|
||||||
continue;
|
continue;
|
||||||
@@ -2174,6 +2227,8 @@ Singleton {
|
|||||||
for (const name in outputs) {
|
for (const name in outputs) {
|
||||||
if (name === testName)
|
if (name === testName)
|
||||||
continue;
|
continue;
|
||||||
|
if (isOutputDisabled(name))
|
||||||
|
continue;
|
||||||
const output = outputs[name];
|
const output = outputs[name];
|
||||||
if (!output.logical)
|
if (!output.logical)
|
||||||
continue;
|
continue;
|
||||||
@@ -2250,7 +2305,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findBestSnapPosition(testName, posX, posY, testW, testH) {
|
function findBestSnapPosition(testName, posX, posY, testW, testH) {
|
||||||
const outputNames = Object.keys(outputs).filter(n => n !== testName);
|
const outputNames = Object.keys(outputs).filter(n => n !== testName && !isOutputDisabled(n));
|
||||||
|
|
||||||
if (outputNames.length === 0)
|
if (outputNames.length === 0)
|
||||||
return Qt.point(posX, posY);
|
return Qt.point(posX, posY);
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ Column {
|
|||||||
return DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "colorManagement", "auto");
|
return DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "colorManagement", "auto");
|
||||||
}
|
}
|
||||||
property bool isHdrMode: currentCm === "hdr" || currentCm === "hdredid"
|
property bool isHdrMode: currentCm === "hdr" || currentCm === "hdredid"
|
||||||
|
property bool isDisabled: {
|
||||||
|
void (DisplayConfigState.pendingHyprlandChanges);
|
||||||
|
return DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "disabled", false);
|
||||||
|
}
|
||||||
|
|
||||||
DankToggle {
|
DankToggle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -83,6 +87,7 @@ Column {
|
|||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: I18n.tr("Mirror Display")
|
text: I18n.tr("Mirror Display")
|
||||||
|
enabled: !settingsColumn.isDisabled
|
||||||
addHorizontalPadding: true
|
addHorizontalPadding: true
|
||||||
|
|
||||||
property var otherOutputs: {
|
property var otherOutputs: {
|
||||||
@@ -112,6 +117,7 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
text: I18n.tr("10-bit Color")
|
text: I18n.tr("10-bit Color")
|
||||||
description: I18n.tr("Enable 10-bit color depth for wider color gamut and HDR support")
|
description: I18n.tr("Enable 10-bit color depth for wider color gamut and HDR support")
|
||||||
|
enabled: !settingsColumn.isDisabled
|
||||||
checked: settingsColumn.is10Bit
|
checked: settingsColumn.is10Bit
|
||||||
onToggled: checked => {
|
onToggled: checked => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
@@ -139,6 +145,7 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
text: I18n.tr("Color Gamut")
|
text: I18n.tr("Color Gamut")
|
||||||
addHorizontalPadding: true
|
addHorizontalPadding: true
|
||||||
|
enabled: !settingsColumn.isDisabled
|
||||||
currentValue: {
|
currentValue: {
|
||||||
DisplayConfigState.pendingHyprlandChanges;
|
DisplayConfigState.pendingHyprlandChanges;
|
||||||
const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "colorManagement", "auto");
|
const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "colorManagement", "auto");
|
||||||
@@ -254,6 +261,7 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 40
|
height: 40
|
||||||
placeholderText: "1.0 - 2.0"
|
placeholderText: "1.0 - 2.0"
|
||||||
|
enabled: !settingsColumn.isDisabled
|
||||||
text: {
|
text: {
|
||||||
DisplayConfigState.pendingHyprlandChanges;
|
DisplayConfigState.pendingHyprlandChanges;
|
||||||
const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "sdrBrightness", null);
|
const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "sdrBrightness", null);
|
||||||
@@ -287,6 +295,7 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 40
|
height: 40
|
||||||
placeholderText: "0.5 - 1.5"
|
placeholderText: "0.5 - 1.5"
|
||||||
|
enabled: !settingsColumn.isDisabled
|
||||||
text: {
|
text: {
|
||||||
DisplayConfigState.pendingHyprlandChanges;
|
DisplayConfigState.pendingHyprlandChanges;
|
||||||
const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "sdrSaturation", null);
|
const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "sdrSaturation", null);
|
||||||
|
|||||||
@@ -1,15 +1,26 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var filteredOutputs: {
|
property var filteredOutputs: {
|
||||||
|
void (DisplayConfigState.pendingHyprlandChanges);
|
||||||
|
void (DisplayConfigState.pendingNiriChanges);
|
||||||
const all = DisplayConfigState.allOutputs || {};
|
const all = DisplayConfigState.allOutputs || {};
|
||||||
const keys = Object.keys(all);
|
const keys = Object.keys(all);
|
||||||
if (SettingsData.displayShowDisconnected)
|
return keys.filter(k => {
|
||||||
return keys;
|
const od = all[k];
|
||||||
return keys.filter(k => all[k]?.connected);
|
const isConnected = od?.connected ?? false;
|
||||||
|
if (!isConnected)
|
||||||
|
return SettingsData.displayShowDisconnected;
|
||||||
|
if (CompositorService.isHyprland && DisplayConfigState.getHyprlandSetting(od, k, "disabled", false))
|
||||||
|
return false;
|
||||||
|
if (CompositorService.isNiri && DisplayConfigState.getNiriSetting(od, k, "disabled", false))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
property var filteredBounds: {
|
property var filteredBounds: {
|
||||||
|
|||||||
@@ -53,10 +53,15 @@ Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
id: settingsColumn
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
visible: root.expanded
|
visible: root.expanded
|
||||||
topPadding: Theme.spacingS
|
topPadding: Theme.spacingS
|
||||||
|
property bool isDisabled: {
|
||||||
|
void (DisplayConfigState.pendingNiriChanges);
|
||||||
|
return DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "disabled", false);
|
||||||
|
}
|
||||||
|
|
||||||
DankToggle {
|
DankToggle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -70,6 +75,7 @@ Column {
|
|||||||
DankToggle {
|
DankToggle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: I18n.tr("Focus at Startup")
|
text: I18n.tr("Focus at Startup")
|
||||||
|
enabled: !settingsColumn.isDisabled
|
||||||
checked: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "focusAtStartup", false)
|
checked: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "focusAtStartup", false)
|
||||||
onToggled: checked => DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "focusAtStartup", checked)
|
onToggled: checked => DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "focusAtStartup", checked)
|
||||||
}
|
}
|
||||||
@@ -78,6 +84,7 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
text: I18n.tr("Hot Corners")
|
text: I18n.tr("Hot Corners")
|
||||||
addHorizontalPadding: true
|
addHorizontalPadding: true
|
||||||
|
enabled: !settingsColumn.isDisabled
|
||||||
|
|
||||||
property var hotCornersData: {
|
property var hotCornersData: {
|
||||||
void (DisplayConfigState.pendingNiriChanges);
|
void (DisplayConfigState.pendingNiriChanges);
|
||||||
@@ -139,6 +146,7 @@ Column {
|
|||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
selectionMode: "multi"
|
selectionMode: "multi"
|
||||||
checkEnabled: false
|
checkEnabled: false
|
||||||
|
enabled: !settingsColumn.isDisabled
|
||||||
buttonHeight: 32
|
buttonHeight: 32
|
||||||
buttonPadding: parent.width < 400 ? Theme.spacingXS : Theme.spacingM
|
buttonPadding: parent.width < 400 ? Theme.spacingXS : Theme.spacingM
|
||||||
minButtonWidth: parent.width < 400 ? 28 : 56
|
minButtonWidth: parent.width < 400 ? 28 : 56
|
||||||
@@ -225,6 +233,7 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 40
|
height: 40
|
||||||
placeholderText: I18n.tr("Inherit")
|
placeholderText: I18n.tr("Inherit")
|
||||||
|
enabled: !settingsColumn.isDisabled
|
||||||
text: {
|
text: {
|
||||||
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
||||||
if (layout?.gaps === undefined)
|
if (layout?.gaps === undefined)
|
||||||
@@ -262,6 +271,7 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 40
|
height: 40
|
||||||
placeholderText: I18n.tr("Inherit")
|
placeholderText: I18n.tr("Inherit")
|
||||||
|
enabled: !settingsColumn.isDisabled
|
||||||
text: {
|
text: {
|
||||||
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
||||||
if (!layout?.defaultColumnWidth)
|
if (!layout?.defaultColumnWidth)
|
||||||
@@ -312,6 +322,7 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 40
|
height: 40
|
||||||
placeholderText: I18n.tr("Inherit")
|
placeholderText: I18n.tr("Inherit")
|
||||||
|
enabled: !settingsColumn.isDisabled
|
||||||
text: {
|
text: {
|
||||||
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
||||||
const presets = layout?.presetColumnWidths || [];
|
const presets = layout?.presetColumnWidths || [];
|
||||||
@@ -354,6 +365,7 @@ Column {
|
|||||||
DankToggle {
|
DankToggle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: I18n.tr("Center Single Column")
|
text: I18n.tr("Center Single Column")
|
||||||
|
enabled: !settingsColumn.isDisabled
|
||||||
property var layoutData: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null)
|
property var layoutData: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null)
|
||||||
checked: layoutData?.alwaysCenterSingleColumn ?? false
|
checked: layoutData?.alwaysCenterSingleColumn ?? false
|
||||||
onToggled: checked => {
|
onToggled: checked => {
|
||||||
|
|||||||
@@ -12,6 +12,17 @@ StyledRect {
|
|||||||
required property string outputName
|
required property string outputName
|
||||||
required property var outputData
|
required property var outputData
|
||||||
property bool isConnected: outputData?.connected ?? false
|
property bool isConnected: outputData?.connected ?? false
|
||||||
|
property bool isDisabled: {
|
||||||
|
void (DisplayConfigState.pendingHyprlandChanges);
|
||||||
|
void (DisplayConfigState.pendingNiriChanges);
|
||||||
|
if (!root.isConnected)
|
||||||
|
return false;
|
||||||
|
if (CompositorService.isHyprland)
|
||||||
|
return DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "disabled", false);
|
||||||
|
if (CompositorService.isNiri)
|
||||||
|
return DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "disabled", false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: settingsColumn.implicitHeight + Theme.spacingM * 2
|
height: settingsColumn.implicitHeight + Theme.spacingM * 2
|
||||||
@@ -19,7 +30,7 @@ StyledRect {
|
|||||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, isConnected ? 0.5 : 0.3)
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, isConnected ? 0.5 : 0.3)
|
||||||
border.color: Theme.withAlpha(Theme.outline, 0.3)
|
border.color: Theme.withAlpha(Theme.outline, 0.3)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
opacity: isConnected ? 1.0 : 0.7
|
opacity: isConnected ? (isDisabled ? 0.5 : 1.0) : 0.7
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: settingsColumn
|
id: settingsColumn
|
||||||
@@ -32,14 +43,14 @@ StyledRect {
|
|||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: root.isConnected ? "desktop_windows" : "desktop_access_disabled"
|
name: root.isConnected && !root.isDisabled ? "desktop_windows" : "desktop_access_disabled"
|
||||||
size: Theme.iconSize - 4
|
size: Theme.iconSize - 4
|
||||||
color: root.isConnected ? Theme.primary : Theme.surfaceVariantText
|
color: root.isConnected && !root.isDisabled ? Theme.primary : Theme.surfaceVariantText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width - Theme.iconSize - Theme.spacingM - (disconnectedBadge.visible ? disconnectedBadge.width + deleteButton.width + Theme.spacingS * 2 : 0)
|
width: parent.width - Theme.iconSize - Theme.spacingM - (disconnectedBadge.visible ? disconnectedBadge.width + deleteButton.width + Theme.spacingS * 2 : disabledBadge.visible ? disabledBadge.width + Theme.spacingS : 0)
|
||||||
spacing: 2
|
spacing: 2
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
@@ -102,12 +113,30 @@ StyledRect {
|
|||||||
onClicked: DisplayConfigState.deleteDisconnectedOutput(root.outputName)
|
onClicked: DisplayConfigState.deleteDisconnectedOutput(root.outputName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: disabledBadge
|
||||||
|
visible: root.isDisabled
|
||||||
|
width: disabledText.implicitWidth + Theme.spacingM
|
||||||
|
height: disabledText.implicitHeight + Theme.spacingXS
|
||||||
|
radius: height / 2
|
||||||
|
color: Theme.withAlpha(Theme.outline, 0.3)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: disabledText
|
||||||
|
text: I18n.tr("Disabled")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: I18n.tr("Resolution & Refresh")
|
text: I18n.tr("Resolution & Refresh")
|
||||||
visible: root.isConnected
|
visible: root.isConnected && !root.isDisabled
|
||||||
currentValue: {
|
currentValue: {
|
||||||
const pendingMode = DisplayConfigState.getPendingValue(root.outputName, "mode");
|
const pendingMode = DisplayConfigState.getPendingValue(root.outputName, "mode");
|
||||||
if (pendingMode)
|
if (pendingMode)
|
||||||
@@ -141,10 +170,20 @@ StyledRect {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: root.isDisabled
|
||||||
|
text: I18n.tr("This output is disabled in the current profile")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
visible: root.isConnected
|
visible: root.isConnected && !root.isDisabled
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: (parent.width - Theme.spacingM) / 2
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
@@ -262,7 +301,7 @@ StyledRect {
|
|||||||
DankToggle {
|
DankToggle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: I18n.tr("Variable Refresh Rate")
|
text: I18n.tr("Variable Refresh Rate")
|
||||||
visible: root.isConnected && !CompositorService.isDwl && !CompositorService.isHyprland && !CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
|
visible: root.isConnected && !root.isDisabled && !CompositorService.isDwl && !CompositorService.isHyprland && !CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
|
||||||
checked: {
|
checked: {
|
||||||
const pendingVrr = DisplayConfigState.getPendingValue(root.outputName, "vrr");
|
const pendingVrr = DisplayConfigState.getPendingValue(root.outputName, "vrr");
|
||||||
if (pendingVrr !== undefined)
|
if (pendingVrr !== undefined)
|
||||||
@@ -275,7 +314,7 @@ StyledRect {
|
|||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: I18n.tr("Variable Refresh Rate")
|
text: I18n.tr("Variable Refresh Rate")
|
||||||
visible: root.isConnected && CompositorService.isHyprland && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
|
visible: root.isConnected && !root.isDisabled && CompositorService.isHyprland && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
|
||||||
options: [I18n.tr("Off"), I18n.tr("On"), I18n.tr("Fullscreen Only")]
|
options: [I18n.tr("Off"), I18n.tr("On"), I18n.tr("Fullscreen Only")]
|
||||||
currentValue: {
|
currentValue: {
|
||||||
DisplayConfigState.pendingHyprlandChanges;
|
DisplayConfigState.pendingHyprlandChanges;
|
||||||
@@ -298,7 +337,7 @@ StyledRect {
|
|||||||
DankDropdown {
|
DankDropdown {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: I18n.tr("Variable Refresh Rate")
|
text: I18n.tr("Variable Refresh Rate")
|
||||||
visible: root.isConnected && CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
|
visible: root.isConnected && !root.isDisabled && CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
|
||||||
options: [I18n.tr("Off"), I18n.tr("On"), I18n.tr("On-Demand")]
|
options: [I18n.tr("Off"), I18n.tr("On"), I18n.tr("On-Demand")]
|
||||||
currentValue: {
|
currentValue: {
|
||||||
DisplayConfigState.pendingNiriChanges;
|
DisplayConfigState.pendingNiriChanges;
|
||||||
|
|||||||
@@ -11,7 +11,15 @@ Item {
|
|||||||
LayoutMirroring.enabled: I18n.isRtl
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
LayoutMirroring.childrenInherit: true
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
property string selectedProfileId: SettingsData.getActiveDisplayProfile(CompositorService.compositor)
|
property string selectedProfileId: {
|
||||||
|
const id = SettingsData.activeDisplayProfile[CompositorService.compositor] || "";
|
||||||
|
if (!SettingsData.displayProfileAutoSelect) {
|
||||||
|
const profile = DisplayConfigState.validatedProfiles[id];
|
||||||
|
if (profile && profile.name === "")
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
property bool showNewProfileDialog: false
|
property bool showNewProfileDialog: false
|
||||||
property bool showDeleteConfirmDialog: false
|
property bool showDeleteConfirmDialog: false
|
||||||
property bool showRenameDialog: false
|
property bool showRenameDialog: false
|
||||||
@@ -60,15 +68,12 @@ Item {
|
|||||||
function onChangesReverted() {
|
function onChangesReverted() {
|
||||||
}
|
}
|
||||||
function onProfileActivated(profileId, profileName) {
|
function onProfileActivated(profileId, profileName) {
|
||||||
root.selectedProfileId = profileId;
|
|
||||||
ToastService.showInfo(I18n.tr("Profile activated: %1").arg(profileName));
|
ToastService.showInfo(I18n.tr("Profile activated: %1").arg(profileName));
|
||||||
}
|
}
|
||||||
function onProfileSaved(profileId, profileName) {
|
function onProfileSaved(profileId, profileName) {
|
||||||
root.selectedProfileId = profileId;
|
|
||||||
ToastService.showInfo(I18n.tr("Profile saved: %1").arg(profileName));
|
ToastService.showInfo(I18n.tr("Profile saved: %1").arg(profileName));
|
||||||
}
|
}
|
||||||
function onProfileDeleted(profileId) {
|
function onProfileDeleted(profileId) {
|
||||||
root.selectedProfileId = SettingsData.getActiveDisplayProfile(CompositorService.compositor);
|
|
||||||
ToastService.showInfo(I18n.tr("Profile deleted"));
|
ToastService.showInfo(I18n.tr("Profile deleted"));
|
||||||
}
|
}
|
||||||
function onProfileError(message) {
|
function onProfileError(message) {
|
||||||
@@ -163,6 +168,8 @@ Item {
|
|||||||
checked: SettingsData.displayProfileAutoSelect
|
checked: SettingsData.displayProfileAutoSelect
|
||||||
onToggled: checked => {
|
onToggled: checked => {
|
||||||
SettingsData.displayProfileAutoSelect = checked;
|
SettingsData.displayProfileAutoSelect = checked;
|
||||||
|
if (!checked)
|
||||||
|
SettingsData.setActiveDisplayProfile(CompositorService.compositor, "");
|
||||||
SettingsData.saveSettings();
|
SettingsData.saveSettings();
|
||||||
if (checked)
|
if (checked)
|
||||||
DisplayConfigState.applyAutoConfig();
|
DisplayConfigState.applyAutoConfig();
|
||||||
@@ -624,7 +631,7 @@ Item {
|
|||||||
|
|
||||||
DisplayConfirmationModal {
|
DisplayConfirmationModal {
|
||||||
id: confirmationModal
|
id: confirmationModal
|
||||||
onConfirmed: DisplayConfigState.confirmChanges()
|
onConfirmed: DisplayConfigState.confirmChanges(root.selectedProfileId)
|
||||||
onReverted: DisplayConfigState.revertChanges()
|
onReverted: DisplayConfigState.revertChanges()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1317,6 +1317,8 @@ Singleton {
|
|||||||
|
|
||||||
if (niriSettings.disabled) {
|
if (niriSettings.disabled) {
|
||||||
kdlContent += ` off\n`;
|
kdlContent += ` off\n`;
|
||||||
|
kdlContent += `}\n\n`;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output.current_mode !== undefined && output.modes && output.modes[output.current_mode]) {
|
if (output.current_mode !== undefined && output.modes && output.modes[output.current_mode]) {
|
||||||
|
|||||||
@@ -2759,6 +2759,12 @@
|
|||||||
"reference": "Modules/Settings/DisplayConfig/OutputCard.qml:136",
|
"reference": "Modules/Settings/DisplayConfig/OutputCard.qml:136",
|
||||||
"comment": ""
|
"comment": ""
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"term": "This output is disabled in the current profile",
|
||||||
|
"context": "This output is disabled in the current profile",
|
||||||
|
"reference": "Modules/Settings/DisplayConfig/OutputCard.qml",
|
||||||
|
"comment": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"term": "Configure",
|
"term": "Configure",
|
||||||
"context": "Configure",
|
"context": "Configure",
|
||||||
|
|||||||
@@ -17638,5 +17638,12 @@
|
|||||||
"context": "Keyboard hints when enter-to-paste is enabled",
|
"context": "Keyboard hints when enter-to-paste is enabled",
|
||||||
"reference": "",
|
"reference": "",
|
||||||
"comment": ""
|
"comment": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"term": "This output is disabled in the current profile",
|
||||||
|
"translation": "",
|
||||||
|
"context": "This output is disabled in the current profile",
|
||||||
|
"reference": "Modules/Settings/DisplayConfig/OutputCard.qml",
|
||||||
|
"comment": ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user