1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-05 12:02:06 -04:00

feat(display): Fix and implement display auto-switching with JSON profile storage (#2275)

* feat(display): fix monitor auto config and add output disable guard

* feat(display): fixed some race conditions and sole display getting disabled.

Co-authored-by: Copilot <copilot@github.com>

* feat(display): changes console log to use new log service

* feat(display): fix trailing spaces

* prek run

* add migration, fix missing hyprland HDR parameters, use FileView >
python

---------

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: bbedward <bbedward@gmail.com>
This commit is contained in:
DK
2026-05-05 02:07:24 +09:00
committed by GitHub
parent 7c991bc4e3
commit cfe6e6867e
9 changed files with 907 additions and 259 deletions

View File

@@ -74,6 +74,8 @@ Column {
DankToggle {
width: parent.width
text: I18n.tr("Disable Output")
enabled: checked || DisplayConfigState.canDisableOutput()
description: (!checked && !DisplayConfigState.canDisableOutput()) ? (Object.keys(DisplayConfigState.outputs).length <= 1 ? I18n.tr("Cannot disable the only output") : I18n.tr("At least one output must remain enabled")) : ""
checked: DisplayConfigState.getHyprlandSetting(root.outputData, root.outputName, "disabled", false)
onToggled: checked => DisplayConfigState.setHyprlandSetting(root.outputData, root.outputName, "disabled", checked)
}

View File

@@ -61,6 +61,8 @@ Column {
DankToggle {
width: parent.width
text: I18n.tr("Disable Output")
enabled: checked || DisplayConfigState.canDisableOutput()
description: (!checked && !DisplayConfigState.canDisableOutput()) ? (Object.keys(DisplayConfigState.outputs).length <= 1 ? I18n.tr("Cannot disable the only output") : I18n.tr("At least one output must remain enabled")) : ""
checked: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "disabled", false)
onToggled: checked => DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "disabled", checked)
}

View File

@@ -168,7 +168,7 @@ StyledRect {
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;
const scale = root.outputData?.logical?.scale || 1.0;
return parseFloat(scale.toFixed(2)).toString();
}
@@ -251,8 +251,7 @@ StyledRect {
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");
return DisplayConfigState.getTransformLabel(root.outputData?.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))

View File

@@ -15,15 +15,13 @@ Item {
property bool showNewProfileDialog: false
property bool showDeleteConfirmDialog: false
property bool showRenameDialog: false
property bool showEditMonitorsDialog: false
property string newProfileName: ""
property string renameProfileName: ""
property var editMonitorSelection: ({})
function getProfileOptions() {
const profiles = DisplayConfigState.validatedProfiles;
const options = [];
for (const id in profiles)
options.push(profiles[id].name);
return options;
return Object.values(DisplayConfigState.validatedProfiles).filter(p => p.name !== "").map(p => p.name);
}
function getProfileIds() {
@@ -44,6 +42,13 @@ Item {
return profiles[id]?.name || "";
}
function openEditMonitorsDialog() {
if (!root.selectedProfileId)
return;
editMonitorSelection = DisplayConfigState.getProfileMonitorInclusion(root.selectedProfileId);
showEditMonitorsDialog = true;
}
Connections {
target: DisplayConfigState
function onChangesApplied(changeDescriptions) {
@@ -139,10 +144,9 @@ Item {
}
}
// ! TODO - auto profile switching is buggy on niri and other compositors
Column {
id: autoSelectColumn
visible: false // disabled for now
visible: true
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
@@ -156,12 +160,12 @@ Item {
DankToggle {
id: autoSelectToggle
checked: false // disabled for now
enabled: false
checked: SettingsData.displayProfileAutoSelect
onToggled: checked => {
// disabled for now
// SettingsData.displayProfileAutoSelect = checked;
// SettingsData.saveSettings();
SettingsData.displayProfileAutoSelect = checked;
SettingsData.saveSettings();
if (checked)
DisplayConfigState.applyAutoConfig();
}
}
}
@@ -170,16 +174,17 @@ Item {
Row {
width: parent.width
spacing: Theme.spacingS
visible: !root.showNewProfileDialog && !root.showDeleteConfirmDialog && !root.showRenameDialog
visible: !root.showNewProfileDialog && !root.showDeleteConfirmDialog && !root.showRenameDialog && !root.showEditMonitorsDialog
opacity: SettingsData.displayProfileAutoSelect ? 0.4 : 1.0
DankDropdown {
id: profileDropdown
width: parent.width - newButton.width - deleteButton.width - Theme.spacingS * 2
width: parent.width - newButton.width - editMonitorsButton.width - deleteButton.width - Theme.spacingS * 3
compactMode: true
dropdownWidth: width
options: root.getProfileOptions()
currentValue: root.getProfileNameById(root.selectedProfileId)
emptyText: I18n.tr("No profiles")
enabled: !SettingsData.displayProfileAutoSelect
onValueChanged: value => {
const profileId = root.getProfileIdByName(value);
if (profileId && profileId !== root.selectedProfileId)
@@ -187,6 +192,12 @@ Item {
}
}
Binding {
target: profileDropdown
property: "currentValue"
value: SettingsData.displayProfileAutoSelect ? I18n.tr("Auto") : root.getProfileNameById(root.selectedProfileId)
}
DankButton {
id: newButton
iconName: "add"
@@ -195,12 +206,25 @@ Item {
horizontalPadding: Theme.spacingM
backgroundColor: Theme.surfaceContainer
textColor: Theme.surfaceText
enabled: !SettingsData.displayProfileAutoSelect
onClicked: {
root.newProfileName = "";
root.showNewProfileDialog = true;
}
}
DankButton {
id: editMonitorsButton
iconName: "edit"
text: ""
buttonHeight: 40
horizontalPadding: Theme.spacingM
backgroundColor: Theme.surfaceContainer
textColor: Theme.surfaceText
enabled: root.selectedProfileId !== "" && !SettingsData.displayProfileAutoSelect
onClicked: root.openEditMonitorsDialog()
}
DankButton {
id: deleteButton
iconName: "delete"
@@ -209,7 +233,7 @@ Item {
horizontalPadding: Theme.spacingM
backgroundColor: Theme.surfaceContainer
textColor: Theme.error
enabled: root.selectedProfileId !== ""
enabled: root.selectedProfileId !== "" && !SettingsData.displayProfileAutoSelect
onClicked: root.showDeleteConfirmDialog = true
}
}
@@ -307,23 +331,89 @@ Item {
}
}
Row {
Rectangle {
width: parent.width
spacing: Theme.spacingS
visible: DisplayConfigState.matchedProfile !== ""
height: editMonitorsColumn.height + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainer
visible: root.showEditMonitorsDialog
DankIcon {
name: "check_circle"
size: 16
color: Theme.success
anchors.verticalCenter: parent.verticalCenter
}
Column {
id: editMonitorsColumn
anchors.centerIn: parent
width: parent.width - Theme.spacingM * 2
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Matches profile: %1").arg(root.getProfileNameById(DisplayConfigState.matchedProfile))
font.pixelSize: Theme.fontSizeSmall
color: Theme.success
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Monitors in \"%1\":").arg(root.getProfileNameById(root.selectedProfileId))
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: parent.width
}
Repeater {
model: Object.keys(DisplayConfigState.allOutputs || {})
delegate: Row {
required property string modelData
width: parent.width
spacing: Theme.spacingM
DankToggle {
id: monitorToggle
checked: root.editMonitorSelection[modelData] ?? false
anchors.verticalCenter: parent.verticalCenter
onToggled: checked => {
const sel = Object.assign({}, root.editMonitorSelection);
sel[modelData] = checked;
root.editMonitorSelection = sel;
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
StyledText {
text: {
const od = DisplayConfigState.allOutputs[modelData];
return DisplayConfigState.getOutputDisplayName(od, modelData);
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
}
StyledText {
text: DisplayConfigState.allOutputs[modelData]?.connected
? I18n.tr("Connected") : I18n.tr("Disconnected")
font.pixelSize: Theme.fontSizeSmall
color: DisplayConfigState.allOutputs[modelData]?.connected
? Theme.success : Theme.surfaceVariantText
}
}
}
}
Row {
spacing: Theme.spacingS
anchors.right: parent.right
DankButton {
text: I18n.tr("Save")
enabled: Object.values(root.editMonitorSelection).some(v => v)
onClicked: {
const enabled = Object.keys(root.editMonitorSelection).filter(k => root.editMonitorSelection[k]);
DisplayConfigState.updateProfileMonitors(root.selectedProfileId, enabled);
root.showEditMonitorsDialog = false;
}
}
DankButton {
text: I18n.tr("Cancel")
backgroundColor: "transparent"
textColor: Theme.surfaceText
onClicked: root.showEditMonitorsDialog = false
}
}
}
}
}