mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-01 19:18:28 -04:00
Compare commits
6 Commits
c2a5397d8a
...
906eba5c17
| Author | SHA1 | Date | |
|---|---|---|---|
| 906eba5c17 | |||
| d2b61b91ef | |||
| 6c27b92445 | |||
| a64bd80cc9 | |||
| 661dc27d26 | |||
| 3d5a6fb9d7 |
@@ -928,8 +928,8 @@ func (p *NiriWritableProvider) formatRule(rule windowrules.WindowRule) string {
|
||||
if a.Opacity != nil {
|
||||
lines = append(lines, fmt.Sprintf(" opacity %.2f", *a.Opacity))
|
||||
}
|
||||
if a.OpenFloating != nil && *a.OpenFloating {
|
||||
lines = append(lines, " open-floating true")
|
||||
if a.OpenFloating != nil {
|
||||
lines = append(lines, fmt.Sprintf(" open-floating %t", *a.OpenFloating))
|
||||
}
|
||||
if a.OpenMaximized != nil && *a.OpenMaximized {
|
||||
lines = append(lines, " open-maximized true")
|
||||
|
||||
@@ -52,6 +52,7 @@ FloatingWindow {
|
||||
condInitialised.triState = 0;
|
||||
opacityEnabled.checked = false;
|
||||
opacitySlider.value = 100;
|
||||
floatingCond.triState = 0;
|
||||
floatingToggle.checked = false;
|
||||
maximizedToggle.checked = false;
|
||||
maximizedToEdgesToggle.checked = false;
|
||||
@@ -164,6 +165,7 @@ FloatingWindow {
|
||||
opacityEnabled.checked = hasOpacity;
|
||||
opacitySlider.value = hasOpacity ? Math.round(actions.opacity * 100) : 100;
|
||||
|
||||
floatingCond.triState = triFromBool(actions.openFloating);
|
||||
floatingToggle.checked = actions.openFloating || false;
|
||||
maximizedToggle.checked = actions.openMaximized || false;
|
||||
maximizedToEdgesToggle.checked = actions.openMaximizedToEdges || false;
|
||||
@@ -318,7 +320,9 @@ FloatingWindow {
|
||||
|
||||
if (opacityEnabled.checked)
|
||||
actions.opacity = opacitySlider.value / 100;
|
||||
if (floatingToggle.checked)
|
||||
if (isNiri)
|
||||
applyCond(actions, "openFloating", floatingCond.triState);
|
||||
else if (floatingToggle.checked)
|
||||
actions.openFloating = true;
|
||||
if (maximizedToggle.checked)
|
||||
actions.openMaximized = true;
|
||||
@@ -943,6 +947,17 @@ FloatingWindow {
|
||||
title: I18n.tr("Window Opening")
|
||||
}
|
||||
|
||||
Flow {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: isNiri
|
||||
|
||||
MatchCond {
|
||||
id: floatingCond
|
||||
label: I18n.tr("Float")
|
||||
}
|
||||
}
|
||||
|
||||
Flow {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
@@ -950,6 +965,7 @@ FloatingWindow {
|
||||
CheckboxRow {
|
||||
id: floatingToggle
|
||||
label: I18n.tr("Float")
|
||||
visible: !isNiri
|
||||
}
|
||||
CheckboxRow {
|
||||
id: maximizedToggle
|
||||
|
||||
@@ -29,6 +29,8 @@ Item {
|
||||
property var centerWidgets: []
|
||||
property int totalWidgets: 0
|
||||
property real totalSize: 0
|
||||
property real contentStart: 0
|
||||
property real contentSize: 0
|
||||
|
||||
function updateLayout() {
|
||||
if (SettingsData.centeringMode === "geometric") {
|
||||
@@ -36,6 +38,25 @@ Item {
|
||||
} else {
|
||||
applyIndexLayout();
|
||||
}
|
||||
updateContentExtent();
|
||||
}
|
||||
|
||||
function updateContentExtent() {
|
||||
if (centerWidgets.length === 0) {
|
||||
contentStart = 0;
|
||||
contentSize = 0;
|
||||
return;
|
||||
}
|
||||
let start = Infinity;
|
||||
let end = -Infinity;
|
||||
for (const widget of centerWidgets) {
|
||||
const pos = isVertical ? widget.y : widget.x;
|
||||
const size = isVertical ? widget.height : widget.width;
|
||||
start = Math.min(start, pos);
|
||||
end = Math.max(end, pos + size);
|
||||
}
|
||||
contentStart = start;
|
||||
contentSize = end - start;
|
||||
}
|
||||
|
||||
function applyGeometricLayout() {
|
||||
|
||||
@@ -833,16 +833,30 @@ PanelWindow {
|
||||
const pos = section.mapToItem(barWindow.contentItem, 0, 0);
|
||||
const implW = section.implicitWidth || 0;
|
||||
const implH = section.implicitHeight || 0;
|
||||
const contentSize = isCenter ? (section.contentSize || 0) : 0;
|
||||
|
||||
const offsetX = isCenter && !barWindow.isVertical ? (section.width - implW) / 2 : 0;
|
||||
const offsetY = !barWindow.isVertical ? (section.height - implH) / 2 : (isCenter ? (section.height - implH) / 2 : 0);
|
||||
let offsetX = isCenter && !barWindow.isVertical ? (section.width - implW) / 2 : 0;
|
||||
let offsetY = !barWindow.isVertical ? (section.height - implH) / 2 : (isCenter ? (section.height - implH) / 2 : 0);
|
||||
let w = implW;
|
||||
let h = implH;
|
||||
|
||||
// index centering lays content out asymmetrically; use the real extent
|
||||
if (contentSize > 0) {
|
||||
if (barWindow.isVertical) {
|
||||
offsetY = section.contentStart;
|
||||
h = contentSize;
|
||||
} else {
|
||||
offsetX = section.contentStart;
|
||||
w = contentSize;
|
||||
}
|
||||
}
|
||||
|
||||
const edgePad = 2;
|
||||
return {
|
||||
"x": pos.x + offsetX - edgePad,
|
||||
"y": pos.y + offsetY - edgePad,
|
||||
"w": implW + edgePad * 2,
|
||||
"h": implH + edgePad * 2
|
||||
"w": w + edgePad * 2,
|
||||
"h": h + edgePad * 2
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,44 +35,42 @@ BasePill {
|
||||
}
|
||||
}
|
||||
|
||||
onRightClicked: (rx, ry) => {
|
||||
onRightClicked: {
|
||||
const screen = root.parentScreen || Screen;
|
||||
if (!screen)
|
||||
return;
|
||||
const globalPos = root.visualContent.mapToItem(null, 0, 0);
|
||||
|
||||
const isVertical = root.axis?.isVertical ?? false;
|
||||
const edge = root.axis?.edge ?? "top";
|
||||
const gap = Math.max(Theme.spacingXS, root.barSpacing ?? Theme.spacingXS);
|
||||
const barOffset = root.barThickness + root.barSpacing + gap;
|
||||
const localPos = root.visualContent.mapToItem(null, root.visualContent.width / 2, root.visualContent.height / 2);
|
||||
|
||||
let anchorX;
|
||||
let anchorY;
|
||||
let anchorEdge;
|
||||
if (isVertical) {
|
||||
anchorY = globalPos.y - (screen.y || 0) + root.visualContent.height / 2;
|
||||
if (edge === "left") {
|
||||
anchorX = barOffset;
|
||||
anchorEdge = "top";
|
||||
} else {
|
||||
anchorX = screen.width - barOffset;
|
||||
anchorEdge = "top";
|
||||
}
|
||||
anchorX = edge === "left" ? barOffset : screen.width - barOffset;
|
||||
anchorY = localPos.y;
|
||||
} else {
|
||||
anchorX = globalPos.x - (screen.x || 0) + root.visualContent.width / 2;
|
||||
if (edge === "bottom") {
|
||||
anchorY = screen.height - barOffset;
|
||||
anchorEdge = "bottom";
|
||||
} else {
|
||||
anchorY = barOffset;
|
||||
anchorEdge = "top";
|
||||
}
|
||||
anchorX = localPos.x;
|
||||
anchorY = edge === "bottom" ? screen.height - barOffset : barOffset;
|
||||
}
|
||||
|
||||
dndPopupLoader.active = true;
|
||||
const popup = dndPopupLoader.item;
|
||||
if (!popup)
|
||||
return;
|
||||
popup.showAt(anchorX, anchorY, screen, anchorEdge);
|
||||
|
||||
popup.showAt(anchorX, anchorY, isVertical, edge, screen);
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.MiddleButton
|
||||
onPressed: mouse => {
|
||||
root.triggerRipple(this, mouse.x, mouse.y);
|
||||
SessionData.setDoNotDisturb(!SessionData.doNotDisturb);
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
|
||||
@@ -31,14 +31,16 @@ PanelWindow {
|
||||
}
|
||||
|
||||
property point anchorPos: Qt.point(0, 0)
|
||||
property string anchorEdge: "top"
|
||||
property bool isVertical: false
|
||||
property string edge: "top"
|
||||
visible: false
|
||||
|
||||
function showAt(x, y, targetScreen, edge) {
|
||||
function showAt(x, y, vertical, barEdge, targetScreen) {
|
||||
if (targetScreen)
|
||||
root.screen = targetScreen;
|
||||
anchorPos = Qt.point(x, y);
|
||||
anchorEdge = edge || "top";
|
||||
isVertical = vertical ?? false;
|
||||
edge = barEdge ?? "top";
|
||||
visible = true;
|
||||
}
|
||||
|
||||
@@ -66,21 +68,19 @@ PanelWindow {
|
||||
visible: root.visible
|
||||
|
||||
x: {
|
||||
const left = 10;
|
||||
const right = root.width - width - 10;
|
||||
const want = root.anchorPos.x - width / 2;
|
||||
return Math.max(left, Math.min(right, want));
|
||||
if (root.isVertical) {
|
||||
if (root.edge === "left")
|
||||
return Math.min(root.width - width - 10, root.anchorPos.x);
|
||||
return Math.max(10, root.anchorPos.x - width);
|
||||
}
|
||||
return Math.max(10, Math.min(root.width - width - 10, root.anchorPos.x - width / 2));
|
||||
}
|
||||
y: {
|
||||
switch (root.anchorEdge) {
|
||||
case "bottom":
|
||||
return Math.max(10, root.anchorPos.y - height);
|
||||
case "left":
|
||||
case "right":
|
||||
if (root.isVertical)
|
||||
return Math.max(10, Math.min(root.height - height - 10, root.anchorPos.y - height / 2));
|
||||
default:
|
||||
return Math.min(root.height - height - 10, root.anchorPos.y);
|
||||
}
|
||||
if (root.edge === "bottom")
|
||||
return Math.max(10, root.anchorPos.y - height);
|
||||
return Math.min(root.height - height - 10, root.anchorPos.y);
|
||||
}
|
||||
|
||||
onDismissed: root.closeMenu()
|
||||
|
||||
@@ -375,6 +375,8 @@ Item {
|
||||
visible: DesktopService.autostartAvailable
|
||||
|
||||
SettingsCard {
|
||||
settingKey: "autostartAddEntry"
|
||||
tags: ["autostart", "add", "entry", "command", "startup"]
|
||||
width: parent.width
|
||||
iconName: "add_circle"
|
||||
title: I18n.tr("Add Entry")
|
||||
@@ -764,6 +766,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
settingKey: "autostartTrayIconFix"
|
||||
tags: ["tray", "icons", "fix", "systemd"]
|
||||
width: parent.width
|
||||
iconName: "handyman"
|
||||
title: I18n.tr("Tray Icon Fix")
|
||||
|
||||
@@ -463,6 +463,7 @@ Item {
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
settingKey: "barEnable"
|
||||
iconName: selectedBarConfig?.enabled ? "visibility" : "visibility_off"
|
||||
title: I18n.tr("Enable Bar")
|
||||
visible: !dankBarTab.appearanceOnly && selectedBarId !== "default"
|
||||
@@ -651,6 +652,8 @@ Item {
|
||||
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "barAutoHide"
|
||||
tags: ["autohide", "auto-hide", "reveal", "intellihide"]
|
||||
text: I18n.tr("Auto-hide")
|
||||
description: I18n.tr("Automatically hide the bar when the pointer moves away")
|
||||
checked: selectedBarConfig?.autoHide ?? false
|
||||
@@ -700,6 +703,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "barAutoHideStrict"
|
||||
tags: ["autohide", "strict", "popout"]
|
||||
width: parent.width - parent.leftPadding
|
||||
text: I18n.tr("Strict auto-hide", "Dank bar setting: hide the bar when the pointer leaves even if a menu or bar popover is still open")
|
||||
description: I18n.tr("Hide the bar when the pointer leaves even if a popout is still open")
|
||||
@@ -713,6 +718,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "barHideWhenWindowsOpen"
|
||||
tags: ["hide", "windows", "empty", "workspace"]
|
||||
width: parent.width - parent.leftPadding
|
||||
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
|
||||
text: I18n.tr("Hide When Windows Open")
|
||||
@@ -734,6 +741,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "barManualVisibility"
|
||||
tags: ["manual", "show", "hide", "ipc", "toggle"]
|
||||
text: I18n.tr("Manual Show/Hide")
|
||||
description: I18n.tr("Toggle bar visibility manually via IPC")
|
||||
checked: selectedBarConfig?.visible ?? true
|
||||
@@ -753,6 +762,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "barClickThrough"
|
||||
tags: ["clickthrough", "click", "through", "mouse", "input", "mask", "passthrough"]
|
||||
text: I18n.tr("Click Through")
|
||||
description: I18n.tr("Mouse clicks pass through the bar to windows behind it")
|
||||
checked: selectedBarConfig?.clickThrough ?? false
|
||||
@@ -910,6 +921,8 @@ Item {
|
||||
|
||||
SettingsSliderRow {
|
||||
id: exclusiveZoneSlider
|
||||
settingKey: "barExclusiveZone"
|
||||
tags: ["exclusive", "zone", "reserved", "offset"]
|
||||
visible: !SettingsData.frameEnabled
|
||||
text: I18n.tr("Exclusive Zone Offset")
|
||||
description: I18n.tr("Fine-tune the space reserved for the bar from the screen edge")
|
||||
@@ -933,6 +946,8 @@ Item {
|
||||
|
||||
SettingsSliderRow {
|
||||
id: sizeSlider
|
||||
settingKey: "barSize"
|
||||
tags: ["size", "thickness", "height", "inner"]
|
||||
visible: !SettingsData.frameEnabled
|
||||
text: I18n.tr("Size")
|
||||
description: I18n.tr("Adjust the bar height via inner padding")
|
||||
@@ -1154,6 +1169,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "barSquareCorners"
|
||||
tags: ["square", "corners", "rounding"]
|
||||
text: I18n.tr("Square Corners")
|
||||
description: I18n.tr("Remove corner rounding from the bar")
|
||||
visible: !SettingsData.frameEnabled
|
||||
@@ -1164,6 +1181,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "barNoBackground"
|
||||
tags: ["transparent", "background", "invisible"]
|
||||
text: I18n.tr("No Background")
|
||||
description: I18n.tr("Make the bar background fully transparent")
|
||||
visible: !SettingsData.frameEnabled
|
||||
@@ -1174,6 +1193,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "barMaximizeWidgetIcons"
|
||||
tags: ["maximize", "icons", "stretch"]
|
||||
text: I18n.tr("Maximize Widget Icons")
|
||||
description: I18n.tr("Stretch widget icons to fill the available bar height")
|
||||
checked: selectedBarConfig?.maximizeWidgetIcons ?? false
|
||||
@@ -1183,6 +1204,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "barMaximizeWidgetText"
|
||||
tags: ["maximize", "text", "stretch"]
|
||||
text: I18n.tr("Maximize Widget Text")
|
||||
description: I18n.tr("Stretch widget text to fill the available bar height")
|
||||
checked: selectedBarConfig?.maximizeWidgetText ?? false
|
||||
@@ -1192,6 +1215,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "barRemoveWidgetPadding"
|
||||
tags: ["padding", "compact", "widgets"]
|
||||
text: I18n.tr("Remove Widget Padding")
|
||||
description: I18n.tr("Remove inner padding from all widgets")
|
||||
checked: selectedBarConfig?.removeWidgetPadding ?? false
|
||||
@@ -1208,6 +1233,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "barGothCorners"
|
||||
tags: ["goth", "corners", "concave", "cutout"]
|
||||
text: I18n.tr("Goth Corners")
|
||||
description: I18n.tr("Apply inverse concave corner cutouts to the bar")
|
||||
visible: !SettingsData.frameEnabled
|
||||
@@ -1302,6 +1329,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleCard {
|
||||
settingKey: "barMaximizeDetection"
|
||||
tags: ["maximize", "gaps", "border", "fullscreen"]
|
||||
iconName: "fit_screen"
|
||||
title: I18n.tr("Maximize Detection")
|
||||
description: I18n.tr("Remove gaps and border when windows are maximized")
|
||||
@@ -1847,6 +1876,8 @@ Item {
|
||||
|
||||
SettingsToggleCard {
|
||||
iconName: "mouse"
|
||||
settingKey: "barScrollWheel"
|
||||
tags: ["scroll", "wheel", "workspace", "axis"]
|
||||
title: I18n.tr("Scroll Wheel")
|
||||
description: I18n.tr("Control workspaces and columns by scrolling on the bar")
|
||||
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
||||
|
||||
@@ -267,6 +267,8 @@ Item {
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
SettingsCard {
|
||||
settingKey: "defaultAppsInternet"
|
||||
tags: ["browser", "mail", "email", "web"]
|
||||
title: I18n.tr("Internet", "Internet")
|
||||
iconName: "public"
|
||||
|
||||
@@ -293,6 +295,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
settingKey: "defaultAppsUtilities"
|
||||
tags: ["file", "manager", "terminal", "editor"]
|
||||
title: I18n.tr("Utilities", "Utilities")
|
||||
iconName: "terminal"
|
||||
|
||||
@@ -317,6 +321,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
settingKey: "defaultAppsDocuments"
|
||||
tags: ["pdf", "text", "reader", "office"]
|
||||
title: I18n.tr("Documents", "Documents")
|
||||
iconName: "edit_document"
|
||||
|
||||
@@ -335,6 +341,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
settingKey: "defaultAppsMultimedia"
|
||||
tags: ["image", "video", "music", "viewer", "player"]
|
||||
title: I18n.tr("Multimedia", "Multimedia")
|
||||
iconName: "movie"
|
||||
AppSelector {
|
||||
|
||||
@@ -166,6 +166,8 @@ Item {
|
||||
spacing: Theme.spacingXL
|
||||
|
||||
SettingsCard {
|
||||
settingKey: "desktopWidgetsManage"
|
||||
tags: ["desktop", "widgets", "clock", "conky"]
|
||||
width: parent.width
|
||||
iconName: "widgets"
|
||||
title: I18n.tr("Desktop Widgets")
|
||||
@@ -203,6 +205,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
settingKey: "desktopWidgetGroups"
|
||||
tags: ["groups", "profiles", "layouts"]
|
||||
width: parent.width
|
||||
iconName: "folder"
|
||||
title: I18n.tr("Groups")
|
||||
|
||||
@@ -17,6 +17,7 @@ Singleton {
|
||||
readonly property var wlrOutputs: WlrOutputService.outputs
|
||||
property var outputs: ({})
|
||||
property var savedOutputs: ({})
|
||||
property var savedParsedOutputs: ({})
|
||||
property var allOutputs: buildAllOutputsMap()
|
||||
|
||||
property var includeStatus: ({
|
||||
@@ -911,15 +912,18 @@ Singleton {
|
||||
const paths = getConfigPaths();
|
||||
if (!paths) {
|
||||
savedOutputs = {};
|
||||
savedParsedOutputs = {};
|
||||
return;
|
||||
}
|
||||
|
||||
Proc.runCommand("load-saved-outputs", ["cat", paths.outputsFile], (content, exitCode) => {
|
||||
if (exitCode !== 0 || !content.trim()) {
|
||||
savedOutputs = {};
|
||||
savedParsedOutputs = {};
|
||||
return;
|
||||
}
|
||||
const parsed = parseOutputsConfig(content);
|
||||
savedParsedOutputs = parsed;
|
||||
const filtered = filterDisconnectedOnly(parsed);
|
||||
savedOutputs = filtered;
|
||||
|
||||
@@ -1092,20 +1096,7 @@ 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 disabled = /^\s*off\s*$/m.test(body);
|
||||
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.]+)/);
|
||||
@@ -1115,6 +1106,7 @@ Singleton {
|
||||
|
||||
result[name] = {
|
||||
"name": name,
|
||||
"disabled": disabled,
|
||||
"logical": {
|
||||
"x": posMatch ? parseInt(posMatch[1]) : 0,
|
||||
"y": posMatch ? parseInt(posMatch[2]) : 0,
|
||||
@@ -1778,6 +1770,41 @@ Singleton {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function findSavedParsedOutput(outputName) {
|
||||
const output = outputs[outputName];
|
||||
const candidates = [outputName];
|
||||
if (output?.make && output?.model) {
|
||||
candidates.push(output.make + " " + output.model + " " + (output.serial || "Unknown"));
|
||||
candidates.push(output.make + " " + output.model);
|
||||
}
|
||||
for (const savedName in savedParsedOutputs) {
|
||||
if (candidates.includes(savedName.trim()))
|
||||
return savedParsedOutputs[savedName];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// A disabled wlr head reports no current mode, scale 0 and position 0,0 —
|
||||
// overlay the last written config so rewrites don't discard real settings.
|
||||
function overlaySavedDisabledOutput(entry, outputName) {
|
||||
const saved = findSavedParsedOutput(outputName);
|
||||
if (!saved)
|
||||
return;
|
||||
|
||||
const savedMode = saved.modes?.[saved.current_mode ?? 0];
|
||||
if (savedMode && !entry.configured_mode)
|
||||
entry.configured_mode = savedMode.width + "x" + savedMode.height + "@" + (savedMode.refresh_rate / 1000).toFixed(3);
|
||||
if (saved.vrr_enabled !== undefined)
|
||||
entry.vrr_enabled = saved.vrr_enabled;
|
||||
if (!saved.logical || !entry.logical)
|
||||
return;
|
||||
|
||||
entry.logical.x = saved.logical.x;
|
||||
entry.logical.y = saved.logical.y;
|
||||
entry.logical.scale = saved.logical.scale;
|
||||
entry.logical.transform = saved.logical.transform;
|
||||
}
|
||||
|
||||
function buildOutputsWithPendingChanges() {
|
||||
const result = {};
|
||||
|
||||
@@ -1787,7 +1814,10 @@ Singleton {
|
||||
}
|
||||
|
||||
for (const outputName in outputs) {
|
||||
result[outputName] = JSON.parse(JSON.stringify(outputs[outputName]));
|
||||
const entry = JSON.parse(JSON.stringify(outputs[outputName]));
|
||||
if (entry.enabled === false)
|
||||
overlaySavedDisabledOutput(entry, outputName);
|
||||
result[outputName] = entry;
|
||||
}
|
||||
|
||||
for (const outputName in pendingChanges) {
|
||||
@@ -1799,6 +1829,8 @@ Singleton {
|
||||
result[outputName].logical.y = changes.position.y;
|
||||
}
|
||||
if (changes.mode !== undefined && result[outputName].modes) {
|
||||
if (result[outputName].configured_mode)
|
||||
result[outputName].configured_mode = changes.mode;
|
||||
for (var i = 0; i < result[outputName].modes.length; i++) {
|
||||
if (formatMode(result[outputName].modes[i]) === changes.mode) {
|
||||
result[outputName].current_mode = i;
|
||||
|
||||
@@ -618,6 +618,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
settingKey: "dockExclusiveZone"
|
||||
tags: ["exclusive", "zone", "reserved", "offset"]
|
||||
text: I18n.tr("Exclusive Zone Offset")
|
||||
visible: !root.connectedFrameModeActive || root.connectedPersistentDockActive
|
||||
value: SettingsData.dockBottomGap
|
||||
|
||||
@@ -38,6 +38,8 @@ Item {
|
||||
settingKey: "mediaPlayer"
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "mediaWaveProgress"
|
||||
tags: ["wave", "progress", "animated"]
|
||||
text: I18n.tr("Wave Progress Bars")
|
||||
description: I18n.tr("Use animated wave progress bars for media playback")
|
||||
checked: SettingsData.waveProgressEnabled
|
||||
@@ -45,6 +47,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "mediaScrollTitle"
|
||||
tags: ["scroll", "title", "marquee"]
|
||||
text: I18n.tr("Scroll song title")
|
||||
description: I18n.tr("Scroll title if it doesn't fit in widget")
|
||||
checked: SettingsData.scrollTitleEnabled
|
||||
@@ -52,6 +56,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "mediaVisualizer"
|
||||
tags: ["visualizer", "cava", "spectrum"]
|
||||
text: I18n.tr("Audio Visualizer")
|
||||
description: I18n.tr("Show cava audio visualizer in media widget")
|
||||
checked: SettingsData.audioVisualizerEnabled
|
||||
@@ -59,6 +65,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "mediaAdaptiveWidth"
|
||||
tags: ["adaptive", "width", "shrink"]
|
||||
text: I18n.tr("Adaptive Media Width")
|
||||
description: I18n.tr("Shrink the media widget to fit shorter song titles while still respecting the configured maximum size")
|
||||
checked: SettingsData.mediaAdaptiveWidthEnabled
|
||||
@@ -66,6 +74,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "mediaAlbumArtAccent"
|
||||
tags: ["album", "art", "accent", "colors"]
|
||||
text: I18n.tr("Use album art accent")
|
||||
description: I18n.tr("Use colors extracted from album art instead of system theme colors")
|
||||
checked: SettingsData.mediaUseAlbumArtAccent
|
||||
@@ -136,6 +146,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "mediaDeviceScrollVolume"
|
||||
tags: ["device", "scroll", "volume"]
|
||||
text: I18n.tr("Device list scroll volume")
|
||||
description: I18n.tr("Allow adjusting device volume by scrolling on the right half of items in the device list")
|
||||
checked: SettingsData.audioDeviceScrollVolumeEnabled
|
||||
|
||||
@@ -81,6 +81,7 @@ Item {
|
||||
SettingsCard {
|
||||
tab: "mux"
|
||||
tags: ["mux", "session", "filter", "exclude", "hide"]
|
||||
settingKey: "muxSessionFilter"
|
||||
title: I18n.tr("Session Filter")
|
||||
iconName: "filter_list"
|
||||
|
||||
|
||||
@@ -89,6 +89,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
settingKey: "systemUpdaterCheckInterval"
|
||||
tags: ["interval", "poll", "frequency"]
|
||||
text: I18n.tr("Check interval")
|
||||
description: I18n.tr("How often the server polls for new updates.")
|
||||
options: root.intervalOptions.map(o => o.label).concat([root.customIntervalLabel])
|
||||
@@ -157,6 +159,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "systemUpdaterCheckOnStart"
|
||||
tags: ["startup", "check", "boot"]
|
||||
text: I18n.tr("Check on startup")
|
||||
description: I18n.tr("When enabled, checks updates on startup. When disabled, only the interval above or a manual refresh runs a check.")
|
||||
checked: SettingsData.updaterCheckOnStart
|
||||
@@ -164,6 +168,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "systemUpdaterFlatpak"
|
||||
tags: ["flatpak", "include"]
|
||||
text: I18n.tr("Include Flatpak updates")
|
||||
description: I18n.tr("Apply Flatpak updates alongside system updates when running 'Update All'.")
|
||||
visible: (SystemUpdateService.backends || []).some(b => b.repo === "flatpak")
|
||||
@@ -172,6 +178,8 @@ Item {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "systemUpdaterAUR"
|
||||
tags: ["aur", "paru", "yay"]
|
||||
text: I18n.tr("Include AUR updates")
|
||||
description: I18n.tr("Run paru/yay with AUR enabled when 'Update All' is clicked.")
|
||||
visible: (SystemUpdateService.backends || []).some(b => b.id === "paru" || b.id === "yay")
|
||||
@@ -328,6 +336,8 @@ Item {
|
||||
settingKey: "systemUpdaterAdvanced"
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "systemUpdaterCustomCommand"
|
||||
tags: ["custom", "command", "terminal"]
|
||||
text: I18n.tr("Use Custom Command")
|
||||
description: I18n.tr("Open a terminal and run a custom command instead of the in-shell upgrade flow.")
|
||||
checked: SettingsData.updaterUseCustomCommand
|
||||
|
||||
@@ -167,7 +167,13 @@ FloatingWindow {
|
||||
width: parent.width
|
||||
height: parent.height - searchField.height - Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
model: root.filteredApps
|
||||
// Start with no model. It is (re)bound in show() so that each open
|
||||
// gets a fresh QQmlDelegateModel, avoiding a crash where a stale
|
||||
// incubation queue from a previous open races with a background
|
||||
// refreshApplications() replacing the underlying DesktopEntries
|
||||
// QObjectModel (SIGSEGV in QQmlIncubatorPrivate::incubate via
|
||||
// libQt6QmlModels). See hide()/onVisibleChanged() for the teardown.
|
||||
model: null
|
||||
clip: true
|
||||
|
||||
delegate: Rectangle {
|
||||
@@ -316,11 +322,21 @@ FloatingWindow {
|
||||
|
||||
function show() {
|
||||
updateFilteredApps();
|
||||
// Rebind the model reactively (search relies on filteredApps changes flowing
|
||||
// through), and do it after populating so the ListView starts incubating from
|
||||
// a fully-formed array rather than [].
|
||||
appList.model = Qt.binding(() => root.filteredApps);
|
||||
visible = true;
|
||||
Qt.callLater(() => searchField.forceActiveFocus());
|
||||
}
|
||||
|
||||
function hide() {
|
||||
// Drop the model before clearing visible, so the ListView releases its
|
||||
// QQmlDelegateModel (and any in-flight incubators) synchronously on this
|
||||
// frame. This is the crux of the fix: a later show() will allocate a fresh
|
||||
// DelegateModel instead of reusing one whose incubation queue may hold
|
||||
// references invalidated by a concurrent refreshApplications().
|
||||
appList.model = null;
|
||||
visible = false;
|
||||
searchQuery = "";
|
||||
filteredApps = [];
|
||||
@@ -330,6 +346,9 @@ FloatingWindow {
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
// Guard against visibility being cleared without going through hide()
|
||||
// (e.g. window manager close, FloatingWindow.onClosed -> hide()).
|
||||
appList.model = null;
|
||||
searchQuery = "";
|
||||
filteredApps = [];
|
||||
selectedIndex = -1;
|
||||
|
||||
@@ -17,7 +17,22 @@ Singleton {
|
||||
interval: 500
|
||||
repeat: false
|
||||
running: true
|
||||
onTriggered: root.suppressSound = false
|
||||
onTriggered: {
|
||||
root.suppressSound = false;
|
||||
root.applyPowerProfile();
|
||||
}
|
||||
}
|
||||
|
||||
function applyPowerProfile() {
|
||||
if (!batteryAvailable)
|
||||
return;
|
||||
const profileValue = isPluggedIn ? SettingsData.acProfileName : SettingsData.batteryProfileName;
|
||||
if (profileValue === "")
|
||||
return;
|
||||
const targetProfile = parseInt(profileValue);
|
||||
if (isNaN(targetProfile) || PowerProfiles.profile === targetProfile)
|
||||
return;
|
||||
PowerProfiles.profile = targetProfile;
|
||||
}
|
||||
|
||||
readonly property string preferredBatteryOverride: Quickshell.env("DMS_PREFERRED_BATTERY")
|
||||
@@ -206,14 +221,7 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
const profileValue = BatteryService.isPluggedIn ? SettingsData.acProfileName : SettingsData.batteryProfileName;
|
||||
|
||||
if (profileValue !== "") {
|
||||
const targetProfile = parseInt(profileValue);
|
||||
if (!isNaN(targetProfile) && PowerProfiles.profile !== targetProfile) {
|
||||
PowerProfiles.profile = targetProfile;
|
||||
}
|
||||
}
|
||||
applyPowerProfile();
|
||||
|
||||
previousPluggedState = isPluggedIn;
|
||||
}
|
||||
|
||||
@@ -1487,8 +1487,6 @@ window-rule {
|
||||
|
||||
if (outputSettings.disabled) {
|
||||
kdlContent += ` off\n`;
|
||||
kdlContent += `}\n\n`;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (output.configured_mode) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user