1
0
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:
Kilian Mio
2026-05-11 15:34:51 +02:00
committed by GitHub
parent 2b6ae58bff
commit a5352623fd
9 changed files with 254 additions and 106 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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: {

View File

@@ -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 => {

View File

@@ -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;

View File

@@ -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()
}
}

View File

@@ -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]) {

View File

@@ -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",

View File

@@ -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": ""
}
]