mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-11 14:59:38 -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);
|
||||
}
|
||||
|
||||
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) {
|
||||
return Object.keys(configEntry.outputs || {}).sort().join("+");
|
||||
}
|
||||
@@ -282,39 +293,20 @@ Singleton {
|
||||
return null;
|
||||
}
|
||||
|
||||
function findConfigEntryByFingerprint(data, outputIdentifiers) {
|
||||
function findConfigEntryByFingerprint(data, outputIdentifiers, autoOnly) {
|
||||
const targetKey = outputSetFingerprint(outputIdentifiers);
|
||||
const configs = data.configurations || [];
|
||||
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 {
|
||||
entry: configs[i],
|
||||
index: i
|
||||
};
|
||||
}
|
||||
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;
|
||||
return null;
|
||||
}
|
||||
|
||||
function getProfileMonitorInclusion(profileId) {
|
||||
@@ -503,15 +495,13 @@ Singleton {
|
||||
}
|
||||
};
|
||||
const onWriteSuccess = () => {
|
||||
SettingsData.setActiveDisplayProfile(CompositorService.compositor, configEntry.name ? configId : "");
|
||||
SettingsData.setActiveDisplayProfile(CompositorService.compositor, configId);
|
||||
if (isManual) {
|
||||
WlrOutputService.requestState();
|
||||
profilesLoading = false;
|
||||
profileActivated(configId, profileName);
|
||||
manualActivationTimer.restart();
|
||||
} else {
|
||||
saveConfigEntry(configEntry);
|
||||
}
|
||||
WlrOutputService.requestState();
|
||||
};
|
||||
|
||||
switch (CompositorService.compositor) {
|
||||
@@ -591,6 +581,8 @@ Singleton {
|
||||
const currentKey = currentOutputSet.join("+");
|
||||
for (const id in validatedProfiles) {
|
||||
const p = validatedProfiles[id];
|
||||
if (p.name === "")
|
||||
continue;
|
||||
if (Object.keys(p.outputs || {}).sort().join("+") === currentKey)
|
||||
return id;
|
||||
}
|
||||
@@ -696,42 +688,57 @@ Singleton {
|
||||
onTriggered: root.manualActivation = false
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: autoSelectDebounceTimer
|
||||
interval: 400
|
||||
onTriggered: {
|
||||
if (root.hasPendingChanges)
|
||||
return;
|
||||
root.applyAutoConfig();
|
||||
}
|
||||
}
|
||||
|
||||
function applyAutoConfig() {
|
||||
if (!profilesReady || !SettingsData.displayProfileAutoSelect || manualActivation || !currentOutputSet.length)
|
||||
return;
|
||||
|
||||
readMonitorsJson(data => {
|
||||
const match = findConfigEntryByFingerprint(data, currentOutputSet);
|
||||
const match = findConfigEntryByFingerprint(data, currentOutputSet, true);
|
||||
if (match) {
|
||||
applyConfigEntry(match.entry, match.entry.id, match.entry.name || "", false);
|
||||
applyConfigEntry(match.entry, match.entry.id, "", false);
|
||||
return;
|
||||
}
|
||||
|
||||
const partial = findPartialConfigEntry(data, currentOutputSet);
|
||||
const niriSettings = buildMergedNiriSettings();
|
||||
const hyprlandSettings = buildMergedHyprlandSettings();
|
||||
const mergedOutputs = buildOutputsWithPendingChanges();
|
||||
|
||||
const outputConfigs = partial ? JSON.parse(JSON.stringify(partial.entry.outputs || {})) : {};
|
||||
|
||||
for (const name in outputs) {
|
||||
const outputId = getOutputIdentifier(outputs[name], name);
|
||||
const alreadyCovered = Object.keys(outputConfigs).some(k => k === outputId);
|
||||
if (!alreadyCovered) {
|
||||
const od = mergedOutputs[name];
|
||||
if (od)
|
||||
outputConfigs[outputId] = extractOutputNeutralConfig(name, od, niriSettings, hyprlandSettings);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(outputConfigs).length === 0)
|
||||
return;
|
||||
|
||||
const syntheticEntry = {
|
||||
name: "",
|
||||
outputs: outputConfigs
|
||||
};
|
||||
applyConfigEntry(syntheticEntry, "", "", false);
|
||||
const outputConfigs = buildCurrentOutputConfigs();
|
||||
const id = generateAutoProfileId(currentOutputSet);
|
||||
const existingIdx = data.configurations.findIndex(c => c.id === id);
|
||||
if (existingIdx >= 0)
|
||||
data.configurations[existingIdx] = {
|
||||
"id": id,
|
||||
"name": "",
|
||||
"outputs": outputConfigs
|
||||
};
|
||||
else
|
||||
data.configurations.push({
|
||||
"id": id,
|
||||
"name": "",
|
||||
"outputs": outputConfigs
|
||||
});
|
||||
writeMonitorsJson(data, success => {
|
||||
if (!success)
|
||||
return;
|
||||
const updated = JSON.parse(JSON.stringify(validatedProfiles));
|
||||
updated[id] = {
|
||||
id: id,
|
||||
name: "",
|
||||
outputs: outputConfigs
|
||||
};
|
||||
validatedProfiles = updated;
|
||||
matchedProfile = "";
|
||||
const match = findConfigEntryById(data, id);
|
||||
if (match)
|
||||
applyConfigEntry(match.entry, id, "", false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -748,22 +755,6 @@ Singleton {
|
||||
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) {
|
||||
if (outputs[outputName]?.connected)
|
||||
return;
|
||||
@@ -831,7 +822,7 @@ Singleton {
|
||||
if (hasPendingChanges)
|
||||
clearPendingChanges();
|
||||
currentOutputSet = newOutputSet;
|
||||
applyAutoConfig();
|
||||
autoSelectDebounceTimer.restart();
|
||||
}
|
||||
onSavedOutputsChanged: allOutputs = buildAllOutputsMap()
|
||||
onLastAppliedEntryChanged: allOutputs = buildAllOutputsMap()
|
||||
@@ -875,9 +866,12 @@ Singleton {
|
||||
if (CompositorService.isHyprland) {
|
||||
initHyprlandSettingsFromConfig(parsed);
|
||||
syncHyprlandVrrFromConfig(parsed);
|
||||
syncHyprlandDisabledFromConfig(parsed);
|
||||
}
|
||||
if (CompositorService.isNiri)
|
||||
if (CompositorService.isNiri) {
|
||||
syncNiriVrrFromConfig(parsed);
|
||||
syncNiriDisabledFromConfig(parsed);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -954,6 +948,44 @@ Singleton {
|
||||
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) {
|
||||
const result = {};
|
||||
const liveNames = Object.keys(outputs);
|
||||
@@ -998,6 +1030,15 @@ Singleton {
|
||||
const name = match[1];
|
||||
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 posMatch = body.match(/position\s+x=(-?\d+)\s+y=(-?\d+)/);
|
||||
const scaleMatch = body.match(/scale\s+([\d.]+)/);
|
||||
@@ -1979,23 +2020,23 @@ Singleton {
|
||||
return block;
|
||||
}
|
||||
|
||||
function confirmChanges() {
|
||||
function confirmChanges(profileId) {
|
||||
const outputConfigs = buildCurrentOutputConfigs();
|
||||
lastAppliedEntry = {
|
||||
outputs: outputConfigs
|
||||
};
|
||||
lastAppliedEntry = { outputs: outputConfigs };
|
||||
|
||||
readMonitorsJson(data => {
|
||||
const match = findConfigEntryByFingerprint(data, Object.keys(outputConfigs));
|
||||
if (!match || !match.entry.name)
|
||||
return;
|
||||
data.configurations[match.index] = {
|
||||
"id": match.entry.id,
|
||||
"name": match.entry.name,
|
||||
"outputs": outputConfigs
|
||||
};
|
||||
writeMonitorsJson(data, null);
|
||||
});
|
||||
if (profileId) {
|
||||
readMonitorsJson(data => {
|
||||
const match = findConfigEntryById(data, profileId);
|
||||
if (match) {
|
||||
data.configurations[match.index] = {
|
||||
"id": match.entry.id,
|
||||
"name": match.entry.name || "",
|
||||
"outputs": outputConfigs
|
||||
};
|
||||
writeMonitorsJson(data, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clearPendingChanges();
|
||||
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) {
|
||||
for (const name in outputs) {
|
||||
if (name === testName)
|
||||
continue;
|
||||
if (isOutputDisabled(name))
|
||||
continue;
|
||||
const output = outputs[name];
|
||||
if (!output.logical)
|
||||
continue;
|
||||
@@ -2174,6 +2227,8 @@ Singleton {
|
||||
for (const name in outputs) {
|
||||
if (name === testName)
|
||||
continue;
|
||||
if (isOutputDisabled(name))
|
||||
continue;
|
||||
const output = outputs[name];
|
||||
if (!output.logical)
|
||||
continue;
|
||||
@@ -2250,7 +2305,7 @@ Singleton {
|
||||
}
|
||||
|
||||
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)
|
||||
return Qt.point(posX, posY);
|
||||
|
||||
@@ -70,6 +70,10 @@ Column {
|
||||
return DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "colorManagement", "auto");
|
||||
}
|
||||
property bool isHdrMode: currentCm === "hdr" || currentCm === "hdredid"
|
||||
property bool isDisabled: {
|
||||
void (DisplayConfigState.pendingHyprlandChanges);
|
||||
return DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "disabled", false);
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
@@ -83,6 +87,7 @@ Column {
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
text: I18n.tr("Mirror Display")
|
||||
enabled: !settingsColumn.isDisabled
|
||||
addHorizontalPadding: true
|
||||
|
||||
property var otherOutputs: {
|
||||
@@ -112,6 +117,7 @@ Column {
|
||||
width: parent.width
|
||||
text: I18n.tr("10-bit Color")
|
||||
description: I18n.tr("Enable 10-bit color depth for wider color gamut and HDR support")
|
||||
enabled: !settingsColumn.isDisabled
|
||||
checked: settingsColumn.is10Bit
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
@@ -139,6 +145,7 @@ Column {
|
||||
width: parent.width
|
||||
text: I18n.tr("Color Gamut")
|
||||
addHorizontalPadding: true
|
||||
enabled: !settingsColumn.isDisabled
|
||||
currentValue: {
|
||||
DisplayConfigState.pendingHyprlandChanges;
|
||||
const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "colorManagement", "auto");
|
||||
@@ -254,6 +261,7 @@ Column {
|
||||
width: parent.width
|
||||
height: 40
|
||||
placeholderText: "1.0 - 2.0"
|
||||
enabled: !settingsColumn.isDisabled
|
||||
text: {
|
||||
DisplayConfigState.pendingHyprlandChanges;
|
||||
const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "sdrBrightness", null);
|
||||
@@ -287,6 +295,7 @@ Column {
|
||||
width: parent.width
|
||||
height: 40
|
||||
placeholderText: "0.5 - 1.5"
|
||||
enabled: !settingsColumn.isDisabled
|
||||
text: {
|
||||
DisplayConfigState.pendingHyprlandChanges;
|
||||
const val = DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "sdrSaturation", null);
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property var filteredOutputs: {
|
||||
void (DisplayConfigState.pendingHyprlandChanges);
|
||||
void (DisplayConfigState.pendingNiriChanges);
|
||||
const all = DisplayConfigState.allOutputs || {};
|
||||
const keys = Object.keys(all);
|
||||
if (SettingsData.displayShowDisconnected)
|
||||
return keys;
|
||||
return keys.filter(k => all[k]?.connected);
|
||||
return keys.filter(k => {
|
||||
const od = all[k];
|
||||
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: {
|
||||
|
||||
@@ -53,10 +53,15 @@ Column {
|
||||
}
|
||||
|
||||
Column {
|
||||
id: settingsColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: root.expanded
|
||||
topPadding: Theme.spacingS
|
||||
property bool isDisabled: {
|
||||
void (DisplayConfigState.pendingNiriChanges);
|
||||
return DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "disabled", false);
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
@@ -70,6 +75,7 @@ Column {
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Focus at Startup")
|
||||
enabled: !settingsColumn.isDisabled
|
||||
checked: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "focusAtStartup", false)
|
||||
onToggled: checked => DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "focusAtStartup", checked)
|
||||
}
|
||||
@@ -78,6 +84,7 @@ Column {
|
||||
width: parent.width
|
||||
text: I18n.tr("Hot Corners")
|
||||
addHorizontalPadding: true
|
||||
enabled: !settingsColumn.isDisabled
|
||||
|
||||
property var hotCornersData: {
|
||||
void (DisplayConfigState.pendingNiriChanges);
|
||||
@@ -139,6 +146,7 @@ Column {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
selectionMode: "multi"
|
||||
checkEnabled: false
|
||||
enabled: !settingsColumn.isDisabled
|
||||
buttonHeight: 32
|
||||
buttonPadding: parent.width < 400 ? Theme.spacingXS : Theme.spacingM
|
||||
minButtonWidth: parent.width < 400 ? 28 : 56
|
||||
@@ -225,6 +233,7 @@ Column {
|
||||
width: parent.width
|
||||
height: 40
|
||||
placeholderText: I18n.tr("Inherit")
|
||||
enabled: !settingsColumn.isDisabled
|
||||
text: {
|
||||
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
||||
if (layout?.gaps === undefined)
|
||||
@@ -262,6 +271,7 @@ Column {
|
||||
width: parent.width
|
||||
height: 40
|
||||
placeholderText: I18n.tr("Inherit")
|
||||
enabled: !settingsColumn.isDisabled
|
||||
text: {
|
||||
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
||||
if (!layout?.defaultColumnWidth)
|
||||
@@ -312,6 +322,7 @@ Column {
|
||||
width: parent.width
|
||||
height: 40
|
||||
placeholderText: I18n.tr("Inherit")
|
||||
enabled: !settingsColumn.isDisabled
|
||||
text: {
|
||||
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
||||
const presets = layout?.presetColumnWidths || [];
|
||||
@@ -354,6 +365,7 @@ Column {
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: I18n.tr("Center Single Column")
|
||||
enabled: !settingsColumn.isDisabled
|
||||
property var layoutData: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null)
|
||||
checked: layoutData?.alwaysCenterSingleColumn ?? false
|
||||
onToggled: checked => {
|
||||
|
||||
@@ -12,6 +12,17 @@ StyledRect {
|
||||
required property string outputName
|
||||
required property var outputData
|
||||
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
|
||||
height: settingsColumn.implicitHeight + Theme.spacingM * 2
|
||||
@@ -19,7 +30,7 @@ StyledRect {
|
||||
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
|
||||
opacity: isConnected ? (isDisabled ? 0.5 : 1.0) : 0.7
|
||||
|
||||
Column {
|
||||
id: settingsColumn
|
||||
@@ -32,14 +43,14 @@ StyledRect {
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: root.isConnected ? "desktop_windows" : "desktop_access_disabled"
|
||||
name: root.isConnected && !root.isDisabled ? "desktop_windows" : "desktop_access_disabled"
|
||||
size: Theme.iconSize - 4
|
||||
color: root.isConnected ? Theme.primary : Theme.surfaceVariantText
|
||||
color: root.isConnected && !root.isDisabled ? 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)
|
||||
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
|
||||
|
||||
StyledText {
|
||||
@@ -102,12 +113,30 @@ StyledRect {
|
||||
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 {
|
||||
width: parent.width
|
||||
text: I18n.tr("Resolution & Refresh")
|
||||
visible: root.isConnected
|
||||
visible: root.isConnected && !root.isDisabled
|
||||
currentValue: {
|
||||
const pendingMode = DisplayConfigState.getPendingValue(root.outputName, "mode");
|
||||
if (pendingMode)
|
||||
@@ -141,10 +170,20 @@ StyledRect {
|
||||
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 {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: root.isConnected
|
||||
visible: root.isConnected && !root.isDisabled
|
||||
|
||||
Column {
|
||||
width: (parent.width - Theme.spacingM) / 2
|
||||
@@ -262,7 +301,7 @@ StyledRect {
|
||||
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)
|
||||
visible: root.isConnected && !root.isDisabled && !CompositorService.isDwl && !CompositorService.isHyprland && !CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
|
||||
checked: {
|
||||
const pendingVrr = DisplayConfigState.getPendingValue(root.outputName, "vrr");
|
||||
if (pendingVrr !== undefined)
|
||||
@@ -275,7 +314,7 @@ StyledRect {
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
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")]
|
||||
currentValue: {
|
||||
DisplayConfigState.pendingHyprlandChanges;
|
||||
@@ -298,7 +337,7 @@ StyledRect {
|
||||
DankDropdown {
|
||||
width: parent.width
|
||||
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")]
|
||||
currentValue: {
|
||||
DisplayConfigState.pendingNiriChanges;
|
||||
|
||||
@@ -11,7 +11,15 @@ Item {
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
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 showDeleteConfirmDialog: false
|
||||
property bool showRenameDialog: false
|
||||
@@ -60,15 +68,12 @@ Item {
|
||||
function onChangesReverted() {
|
||||
}
|
||||
function onProfileActivated(profileId, profileName) {
|
||||
root.selectedProfileId = profileId;
|
||||
ToastService.showInfo(I18n.tr("Profile activated: %1").arg(profileName));
|
||||
}
|
||||
function onProfileSaved(profileId, profileName) {
|
||||
root.selectedProfileId = profileId;
|
||||
ToastService.showInfo(I18n.tr("Profile saved: %1").arg(profileName));
|
||||
}
|
||||
function onProfileDeleted(profileId) {
|
||||
root.selectedProfileId = SettingsData.getActiveDisplayProfile(CompositorService.compositor);
|
||||
ToastService.showInfo(I18n.tr("Profile deleted"));
|
||||
}
|
||||
function onProfileError(message) {
|
||||
@@ -163,6 +168,8 @@ Item {
|
||||
checked: SettingsData.displayProfileAutoSelect
|
||||
onToggled: checked => {
|
||||
SettingsData.displayProfileAutoSelect = checked;
|
||||
if (!checked)
|
||||
SettingsData.setActiveDisplayProfile(CompositorService.compositor, "");
|
||||
SettingsData.saveSettings();
|
||||
if (checked)
|
||||
DisplayConfigState.applyAutoConfig();
|
||||
@@ -624,7 +631,7 @@ Item {
|
||||
|
||||
DisplayConfirmationModal {
|
||||
id: confirmationModal
|
||||
onConfirmed: DisplayConfigState.confirmChanges()
|
||||
onConfirmed: DisplayConfigState.confirmChanges(root.selectedProfileId)
|
||||
onReverted: DisplayConfigState.revertChanges()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1317,6 +1317,8 @@ Singleton {
|
||||
|
||||
if (niriSettings.disabled) {
|
||||
kdlContent += ` off\n`;
|
||||
kdlContent += `}\n\n`;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (output.current_mode !== undefined && output.modes && output.modes[output.current_mode]) {
|
||||
|
||||
@@ -2759,6 +2759,12 @@
|
||||
"reference": "Modules/Settings/DisplayConfig/OutputCard.qml:136",
|
||||
"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",
|
||||
"context": "Configure",
|
||||
|
||||
@@ -17638,5 +17638,12 @@
|
||||
"context": "Keyboard hints when enter-to-paste is enabled",
|
||||
"reference": "",
|
||||
"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