diff --git a/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml b/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml index 4b91ffa1..32463dd7 100644 --- a/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml +++ b/quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml @@ -857,6 +857,8 @@ Singleton { Component.onCompleted: { outputs = buildOutputsMap(); reloadSavedOutputs(); + if (CompositorService.isHyprland) + checkIncludeStatus(); } function reloadSavedOutputs() { @@ -1424,6 +1426,11 @@ Singleton { showHyprlandReadOnlyWarning(); return; } + if (CompositorService.isHyprland && !HyprlandService.luaConfigActive) { + showHyprlandReadOnlyWarning(); + checkIncludeStatus(); + return; + } const paths = getConfigPaths(); if (!paths) return; @@ -1440,9 +1447,25 @@ Singleton { }); Proc.runCommand("fix-outputs-include", ["sh", "-c", script], (output, exitCode) => { - fixingInclude = false; - if (exitCode !== 0) + if (exitCode !== 0) { + fixingInclude = false; return; + } + + const liveOutputs = buildOutputsMap(); + if (CompositorService.isHyprland && Object.keys(liveOutputs).length > 0) { + outputs = liveOutputs; + HyprlandService.generateOutputsConfig(liveOutputs, SettingsData.hyprlandOutputSettings, success => { + fixingInclude = false; + if (!success) + ToastService.showError(I18n.tr("Display setup failed"), I18n.tr("Failed to write Hyprland outputs config."), "", "display-config"); + checkIncludeStatus(); + WlrOutputService.requestState(); + }); + return; + } + + fixingInclude = false; checkIncludeStatus(); WlrOutputService.requestState(); }); @@ -2504,6 +2527,50 @@ Singleton { return mode.width + "x" + mode.height + "@" + (mode.refresh_rate / 1000).toFixed(3); } + function formatScaleLabel(scale) { + const value = Number(scale); + if (!isFinite(value)) + return "1"; + return parseFloat(value.toFixed(2)).toString(); + } + + function getScalePresetValues(outputName, outputData) { + if (!CompositorService.isHyprland) + return [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3]; + + const candidates = [0.5, 2 / 3, 0.75, 0.8, 1, 4 / 3, 1.6, 2, 2.5, 8 / 3, 3.2, 4]; + const mode = getModeForScalePresets(outputName, outputData); + if (!mode) + return candidates; + + return candidates.filter(scale => scaleFitsMode(mode, scale)); + } + + function getModeForScalePresets(outputName, outputData) { + const pendingMode = getPendingValue(outputName, "mode"); + const modes = outputData?.modes || []; + if (pendingMode) { + for (const mode of modes) { + if (formatMode(mode) === pendingMode) + return mode; + } + } + const currentMode = outputData?.current_mode; + if (currentMode !== undefined && modes[currentMode]) + return modes[currentMode]; + return null; + } + + function scaleFitsMode(mode, scale) { + const width = Number(mode?.width || 0); + const height = Number(mode?.height || 0); + if (width <= 0 || height <= 0 || scale <= 0) + return false; + const logicalWidth = width / scale; + const logicalHeight = height / scale; + return Math.abs(logicalWidth - Math.round(logicalWidth)) < 0.001 && Math.abs(logicalHeight - Math.round(logicalHeight)) < 0.001; + } + function getTransformLabel(transform) { switch (transform) { case "Normal": diff --git a/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml b/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml index 96367d81..20bcd73b 100644 --- a/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml +++ b/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml @@ -203,12 +203,40 @@ StyledRect { height: scaleDropdown.visible ? scaleDropdown.height : scaleInput.height property bool customMode: false - property string currentScale: { + property real currentScaleValue: { const pendingScale = DisplayConfigState.getPendingValue(root.outputName, "scale"); if (pendingScale !== undefined) - return parseFloat(pendingScale.toFixed(2)).toString(); - const scale = root.outputData?.logical?.scale || 1.0; - return parseFloat(scale.toFixed(2)).toString(); + return pendingScale; + return root.outputData?.logical?.scale || 1.0; + } + property string currentScale: DisplayConfigState.formatScaleLabel(currentScaleValue) + property var scaleOptionsData: { + void (DisplayConfigState.pendingChanges); + const customLabel = I18n.tr("Custom..."); + const values = DisplayConfigState.getScalePresetValues(root.outputName, root.outputData); + const labels = []; + const valueByLabel = {}; + + function addValue(value) { + if (!isFinite(value) || value <= 0) + return; + const label = DisplayConfigState.formatScaleLabel(value); + if (valueByLabel[label] !== undefined) + return; + valueByLabel[label] = value; + labels.push(label); + } + + for (const value of values) + addValue(value); + addValue(scaleContainer.currentScaleValue); + + labels.sort((a, b) => parseFloat(a) - parseFloat(b)); + labels.push(customLabel); + return { + "labels": labels, + "valueByLabel": valueByLabel + }; } DankDropdown { @@ -217,20 +245,7 @@ StyledRect { dropdownWidth: parent.width visible: !scaleContainer.customMode currentValue: scaleContainer.currentScale - options: { - const standard = ["0.5", "0.75", "1", "1.25", "1.5", "1.75", "2", "2.5", "3", I18n.tr("Custom...")]; - const current = scaleContainer.currentScale; - if (standard.slice(0, -1).includes(current)) - return standard; - const opts = [...standard.slice(0, -1), current, standard[standard.length - 1]]; - return opts.sort((a, b) => { - if (a === I18n.tr("Custom...")) - return 1; - if (b === I18n.tr("Custom...")) - return -1; - return parseFloat(a) - parseFloat(b); - }); - } + options: scaleContainer.scaleOptionsData.labels onValueChanged: value => { if (value === I18n.tr("Custom...")) { scaleContainer.customMode = true; @@ -239,7 +254,8 @@ StyledRect { scaleInput.selectAll(); return; } - DisplayConfigState.setPendingChange(root.outputName, "scale", parseFloat(value)); + const mapped = scaleContainer.scaleOptionsData.valueByLabel[value]; + DisplayConfigState.setPendingChange(root.outputName, "scale", mapped !== undefined ? mapped : parseFloat(value)); } } @@ -248,7 +264,7 @@ StyledRect { width: parent.width height: 40 visible: scaleContainer.customMode - placeholderText: "0.5 - 4.0" + placeholderText: "0.25 - 4.0" function applyValue() { const val = parseFloat(text); @@ -257,7 +273,7 @@ StyledRect { scaleContainer.customMode = false; return; } - DisplayConfigState.setPendingChange(root.outputName, "scale", parseFloat(val.toFixed(2))); + DisplayConfigState.setPendingChange(root.outputName, "scale", parseFloat(val.toFixed(6))); scaleContainer.customMode = false; }