1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-10 15:38:31 -04:00

Compare commits

..

8 Commits

Author SHA1 Message Date
bbedward 59c83d19e4 battery: auto-apply power profile at startup when DMS managed
fixes #2935
port 1.5
2026-07-26 15:27:07 -04:00
bbedward 9b7d3c64fe dbar: fix click-through mask of center section
fixes #2938
2026-07-26 15:16:48 -04:00
bbedward c367153bac sysupdate: fix gnome-terminal title 2026-07-26 15:00:40 -04:00
bbedward 1f94c3cbd4 dbar/notifications: fix right click context menu position and add middle
click to DnD
2026-07-26 14:51:51 -04:00
bbedward 564583951c theme: stop overwriting user gtk.css and dconf gtk-theme (#2933) 2026-07-26 14:48:23 -04:00
bbedward eed3617a0d ipc/dash: add openAt/toggleAt for opening popouts at specific locations
fixes #2932
2026-07-26 14:37:17 -04:00
bbedward 24cb3d19a0 display config: preserve config when turning off on niri
fixes #2939
port 1.5
2026-07-26 14:26:00 -04:00
feng-yifan 3182c70857 fix(settings): reset AppBrowserPopup model on close to avoid crash (#2925)
Reopening the autostart "Browse" picker crashed in
QQmlIncubatorPrivate::incubate. Drop the ListView model on hide()
and rebind it on show() so each open starts with a fresh
QQmlDelegateModel instead of reusing a stale incubation queue.
2026-07-26 14:24:21 -04:00
25 changed files with 1379 additions and 210 deletions
+22 -1
View File
@@ -968,11 +968,32 @@ func applyKDEColorScheme(mode ColorMode) {
} }
} }
func gtkThemeInstalled(theme string) bool {
home, _ := os.UserHomeDir()
candidates := []string{
filepath.Join(home, ".local/share/themes", theme),
filepath.Join(home, ".themes", theme),
filepath.Join("/usr/share/themes", theme),
filepath.Join("/usr/local/share/themes", theme),
}
for _, dir := range candidates {
if info, err := os.Stat(dir); err == nil && info.IsDir() {
return true
}
}
return false
}
func refreshGTKTheme(mode ColorMode) { func refreshGTKTheme(mode ColorMode) {
theme := mode.GTKTheme()
if !gtkThemeInstalled(theme) {
log.Infof("Skipping gtk-theme refresh: %s is not installed", theme)
return
}
if err := utils.GsettingsSet("org.gnome.desktop.interface", "gtk-theme", ""); err != nil { if err := utils.GsettingsSet("org.gnome.desktop.interface", "gtk-theme", ""); err != nil {
log.Warnf("Failed to reset gtk-theme: %v", err) log.Warnf("Failed to reset gtk-theme: %v", err)
} }
if err := utils.GsettingsSet("org.gnome.desktop.interface", "gtk-theme", mode.GTKTheme()); err != nil { if err := utils.GsettingsSet("org.gnome.desktop.interface", "gtk-theme", theme); err != nil {
log.Warnf("Failed to set gtk-theme: %v", err) log.Warnf("Failed to set gtk-theme: %v", err)
} }
} }
+2 -1
View File
@@ -143,7 +143,8 @@ func wrapInTerminal(term, title, shellCmd string, extraArgs []string) []string {
case "konsole": case "konsole":
argv = []string{term, "-p", "tabtitle=" + title} argv = []string{term, "-p", "tabtitle=" + title}
case "gnome-terminal": case "gnome-terminal":
argv = []string{term, "--title=" + title} // --wait: the factory process otherwise returns immediately
argv = []string{term, "--wait", "--title=" + title}
execFlag = "--" execFlag = "--"
default: default:
argv = []string{term} argv = []string{term}
+14 -9
View File
@@ -412,25 +412,30 @@ func (m *Manager) runCustomUpgrade(ctx context.Context, opts UpgradeOptions) {
onLine := func(line string) { m.appendLog(line) } onLine := func(line string) { m.appendLog(line) }
argv := wrapInTerminal(term, "DMS — System Update (custom)", opts.CustomCommand, opts.TerminalArgs) argv := wrapInTerminal(term, "DMS — System Update (custom)", opts.CustomCommand, opts.TerminalArgs)
if err := Run(ctx, argv, RunOptions{OnLine: onLine}); err != nil { if err := Run(ctx, argv, RunOptions{OnLine: onLine}); err != nil {
code := ErrCodeBackendFailed
switch { switch {
case errors.Is(ctx.Err(), context.DeadlineExceeded): case errors.Is(ctx.Err(), context.DeadlineExceeded):
code = ErrCodeTimeout m.failCustomUpgrade(ErrCodeTimeout, err)
return
case errors.Is(ctx.Err(), context.Canceled): case errors.Is(ctx.Err(), context.Canceled):
code = ErrCodeCancelled m.failCustomUpgrade(ErrCodeCancelled, err)
return
} }
m.mu.Lock() // exit status reflects the trailing `read`, not the update command
m.state.Phase = PhaseError m.appendLog(fmt.Sprintf("Terminal exited early: %v", err))
m.state.Error = &ErrorInfo{Code: code, Message: err.Error()}
m.mu.Unlock()
m.markDirty()
return
} }
m.finishSuccessfulUpgrade(false) m.finishSuccessfulUpgrade(false)
m.runRefresh(context.Background(), false) m.runRefresh(context.Background(), false)
} }
func (m *Manager) failCustomUpgrade(code ErrorCode, err error) {
m.mu.Lock()
m.state.Phase = PhaseError
m.state.Error = &ErrorInfo{Code: code, Message: err.Error()}
m.mu.Unlock()
m.markDirty()
}
func (m *Manager) finishSuccessfulUpgrade(clearPackages bool) { func (m *Manager) finishSuccessfulUpgrade(clearPackages bool) {
m.appendLog("Upgrade complete.") m.appendLog("Upgrade complete.")
@@ -233,3 +233,27 @@ func TestUpgradeBackendsFiltersFlatpakOnly(t *testing.T) {
t.Fatalf("upgradeBackends(mixed) = %#v, want dnf5 then flatpak", got) t.Fatalf("upgradeBackends(mixed) = %#v, want dnf5 then flatpak", got)
} }
} }
func TestWrapInTerminal(t *testing.T) {
tests := []struct {
term string
wantPrefix []string
}{
{"kitty", []string{"kitty", "--class", "com.danklinux.dms", "-T", "Title"}},
{"gnome-terminal", []string{"gnome-terminal", "--wait", "--title=Title"}},
{"foot", []string{"foot", "--app-id=com.danklinux.dms", "--title=Title"}},
}
for _, tt := range tests {
got := wrapInTerminal(tt.term, "Title", "echo hi", nil)
if len(got) < len(tt.wantPrefix) || !reflect.DeepEqual(got[:len(tt.wantPrefix)], tt.wantPrefix) {
t.Errorf("wrapInTerminal(%q) = %#v, want prefix %#v", tt.term, got, tt.wantPrefix)
}
tail := got[len(got)-3:]
if tail[0] != "sh" || tail[1] != "-c" {
t.Errorf("wrapInTerminal(%q) tail = %#v, want [sh -c <cmd>]", tt.term, tail)
}
if !strings.Contains(tail[2], "echo hi") {
t.Errorf("wrapInTerminal(%q) command %q does not contain shell command", tt.term, tail[2])
}
}
}
+9 -5
View File
@@ -144,7 +144,7 @@ Singleton {
Quickshell.execDetached(["mkdir", "-p", stateDir]); Quickshell.execDetached(["mkdir", "-p", stateDir]);
// shellDir may be an embedded-UI extraction, which is read-only and // shellDir may be an embedded-UI extraction, which is read-only and
// unexecutable (dankgo shellapp/shellfs makeReadOnly chmods 0444) // unexecutable (dankgo shellapp/shellfs makeReadOnly chmods 0444)
Quickshell.execDetached(["bash", shellDir + "/scripts/gtk.sh", configDir, "", "", shellDir]); Quickshell.execDetached(["bash", shellDir + "/scripts/gtk.sh", configDir, "assets", "", shellDir]);
Proc.runCommand("matugenCheck", ["sh", "-c", "command -v matugen"], (output, code) => { Proc.runCommand("matugenCheck", ["sh", "-c", "command -v matugen"], (output, code) => {
matugenAvailable = (code === 0) && !envDisableMatugen; matugenAvailable = (code === 0) && !envDisableMatugen;
@@ -1955,7 +1955,13 @@ Singleton {
function patchGtk3colors() { function patchGtk3colors() {
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode); const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode);
Proc.runCommand("gtk3Patcher", ["bash", shellDir + "/scripts/gtk.sh", configDir, "patch", isLight, shellDir], (output, exitCode) => { Proc.runCommand("gtk3Patcher", ["bash", shellDir + "/scripts/gtk.sh", configDir, "patch", isLight, shellDir], (output, exitCode) => {
if (exitCode !== 0) { switch (exitCode) {
case 0:
refreshGtkTheme();
break;
case 2:
break;
default:
log.warn(`Failed to patch GTK3 colors: ${output}`); log.warn(`Failed to patch GTK3 colors: ${output}`);
} }
}); });
@@ -2167,10 +2173,8 @@ Singleton {
} }
if (!pendingThemeRequest) { if (!pendingThemeRequest) {
if (SettingsData.matugenTemplateGtk) { if (SettingsData.matugenTemplateGtk)
patchGtk3colors(); patchGtk3colors();
refreshGtkTheme();
}
return; return;
} }
+58 -27
View File
@@ -285,28 +285,66 @@ Item {
} }
} }
function _resolvePosition(position) {
switch ((position || "").toLowerCase()) {
case "left":
return "left";
case "center":
return "center";
case "right":
return "right";
default:
return "";
}
}
function _dashBar(position) {
if (position)
return root.getPreferredBar();
return root.getPreferredBar("clockButtonRef") || root.getPreferredBar();
}
function _openDash(tab, position) {
const bar = _dashBar(position);
if (!bar)
return false;
const tabId = _resolveTabId(tab);
const dash = root.dankDashPopoutLoader.item;
if (dash && dash.shouldBeVisible && dash.triggerScreen?.name === bar.screen?.name) {
if (position && bar.positionDash)
bar.positionDash(dash, position);
dash.requestTab(tabId);
if (dash.updateSurfacePosition)
dash.updateSurfacePosition();
return true;
}
return bar.triggerDashTab(tabId, position);
}
function _toggleDash(tab, position) {
if (root.dankDashPopoutLoader.item?.dashVisible) {
root.dankDashPopoutLoader.item.dashVisible = false;
return true;
}
const bar = _dashBar(position);
if (!bar)
return false;
return bar.triggerDashTab(_resolveTabId(tab), position);
}
function resolveTabIndex(tab: string): int { function resolveTabIndex(tab: string): int {
return SettingsData.dashTabIndexForId(_resolveTabId(tab)); return SettingsData.dashTabIndexForId(_resolveTabId(tab));
} }
function open(tab: string): string { function open(tab: string): string {
const bar = root.getPreferredBar("clockButtonRef") || root.getPreferredBar(); return _openDash(tab, "") ? "DASH_OPEN_SUCCESS" : "DASH_OPEN_FAILED";
if (!bar) }
return "DASH_OPEN_FAILED";
const tabId = _resolveTabId(tab); function openAt(tab: string, position: string): string {
const dash = root.dankDashPopoutLoader.item; return _openDash(tab, _resolvePosition(position)) ? "DASH_OPEN_SUCCESS" : "DASH_OPEN_FAILED";
if (dash && dash.shouldBeVisible && dash.triggerScreen?.name === bar.screen?.name) {
dash.requestTab(tabId);
if (dash.updateSurfacePosition)
dash.updateSurfacePosition();
return "DASH_OPEN_SUCCESS";
}
if (!bar.triggerDashTab(tabId))
return "DASH_OPEN_FAILED";
return "DASH_OPEN_SUCCESS";
} }
function close(): string { function close(): string {
@@ -318,18 +356,11 @@ Item {
} }
function toggle(tab: string): string { function toggle(tab: string): string {
if (root.dankDashPopoutLoader.item?.dashVisible) { return _toggleDash(tab, "") ? "DASH_TOGGLE_SUCCESS" : "DASH_TOGGLE_FAILED";
root.dankDashPopoutLoader.item.dashVisible = false; }
return "DASH_TOGGLE_SUCCESS";
}
const bar = root.getPreferredBar("clockButtonRef") || root.getPreferredBar(); function toggleAt(tab: string, position: string): string {
if (bar) { return _toggleDash(tab, _resolvePosition(position)) ? "DASH_TOGGLE_SUCCESS" : "DASH_TOGGLE_FAILED";
if (!bar.triggerDashTab(_resolveTabId(tab)))
return "DASH_TOGGLE_FAILED";
return "DASH_TOGGLE_SUCCESS";
}
return "DASH_TOGGLE_FAILED";
} }
target: "dash" target: "dash"
@@ -29,6 +29,8 @@ Item {
property var centerWidgets: [] property var centerWidgets: []
property int totalWidgets: 0 property int totalWidgets: 0
property real totalSize: 0 property real totalSize: 0
property real contentStart: 0
property real contentSize: 0
function updateLayout() { function updateLayout() {
if (SettingsData.centeringMode === "geometric") { if (SettingsData.centeringMode === "geometric") {
@@ -36,6 +38,25 @@ Item {
} else { } else {
applyIndexLayout(); 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() { function applyGeometricLayout() {
+75 -37
View File
@@ -69,7 +69,62 @@ Item {
} }
} }
function triggerDashTab(tabId) { function dashSectionAnchor(section) {
let item;
switch (section) {
case "left":
item = barWindow.isVertical ? topBarContent.vLeftSection : topBarContent.hLeftSection;
break;
case "right":
item = barWindow.isVertical ? topBarContent.vRightSection : topBarContent.hRightSection;
break;
default:
item = barWindow.isVertical ? topBarContent.vCenterSection : topBarContent.hCenterSection;
}
if (!item)
return null;
if (barWindow.isVertical)
return {
"pos": item.mapToItem(null, 0, item.height / 2),
"width": item.height
};
return {
"pos": item.mapToItem(null, 0, 0),
"width": item.width
};
}
function positionDash(popout, position) {
if (!popout.setTriggerPosition) {
popout.triggerScreen = barWindow.screen;
return "center";
}
const explicit = position === "left" || position === "center" || position === "right";
const section = explicit ? position : (clockButtonRef?.section || "center");
const clockAnchor = clockButtonRef?.visualContent ? {
"pos": clockButtonRef.visualContent.mapToItem(null, 0, 0),
"width": clockButtonRef.visualWidth
} : null;
let anchor;
if (!explicit && section !== "center" && clockAnchor)
anchor = clockAnchor;
else
anchor = dashSectionAnchor(section) || clockAnchor;
if (!anchor) {
popout.triggerScreen = barWindow.screen;
return section;
}
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
const pos = SettingsData.getPopupTriggerPosition(anchor.pos, barWindow.screen, barWindow.effectiveBarThickness, anchor.width, barConfig?.spacing ?? 4, barPosition, barConfig);
popout.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, barConfig?.spacing ?? 4, barConfig);
return section;
}
function triggerDashTab(tabId, position) {
const loader = PopoutService.dankDashPopoutLoader; const loader = PopoutService.dankDashPopoutLoader;
if (!loader) if (!loader)
return false; return false;
@@ -78,38 +133,7 @@ Item {
return false; return false;
} }
let section = "center"; const section = positionDash(loader.item, position);
if (clockButtonRef && clockButtonRef.visualContent && loader.item.setTriggerPosition) {
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
section = clockButtonRef.section || "center";
let triggerPos, triggerWidth;
if (section === "center") {
const centerSection = barWindow.isVertical ? (barWindow.axis?.edge === "left" ? topBarContent.vCenterSection : topBarContent.vCenterSection) : topBarContent.hCenterSection;
if (centerSection) {
if (barWindow.isVertical) {
const centerY = centerSection.height / 2;
triggerPos = centerSection.mapToItem(null, 0, centerY);
triggerWidth = centerSection.height;
} else {
triggerPos = centerSection.mapToItem(null, 0, 0);
triggerWidth = centerSection.width;
}
} else {
triggerPos = clockButtonRef.visualContent.mapToItem(null, 0, 0);
triggerWidth = clockButtonRef.visualWidth;
}
} else {
triggerPos = clockButtonRef.visualContent.mapToItem(null, 0, 0);
triggerWidth = clockButtonRef.visualWidth;
}
const pos = SettingsData.getPopupTriggerPosition(triggerPos, barWindow.screen, barWindow.effectiveBarThickness, triggerWidth, barConfig?.spacing ?? 4, barPosition, barConfig);
loader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, barConfig?.spacing ?? 4, barConfig);
} else {
loader.item.triggerScreen = barWindow.screen;
}
if (loader.item.requestTab) if (loader.item.requestTab)
loader.item.requestTab(tabId); loader.item.requestTab(tabId);
PopoutManager.requestPopout(loader.item, undefined, (barConfig?.id ?? "default") + "-" + section + "-" + tabId); PopoutManager.requestPopout(loader.item, undefined, (barConfig?.id ?? "default") + "-" + section + "-" + tabId);
@@ -808,16 +832,30 @@ Item {
const pos = section.mapToItem(barWindow.hostWindow.contentItem, 0, 0); const pos = section.mapToItem(barWindow.hostWindow.contentItem, 0, 0);
const implW = section.implicitWidth || 0; const implW = section.implicitWidth || 0;
const implH = section.implicitHeight || 0; const implH = section.implicitHeight || 0;
const contentSize = isCenter ? (section.contentSize || 0) : 0;
const offsetX = isCenter && !barWindow.isVertical ? (section.width - implW) / 2 : 0; let offsetX = isCenter && !barWindow.isVertical ? (section.width - implW) / 2 : 0;
const offsetY = !barWindow.isVertical ? (section.height - implH) / 2 : (isCenter ? (section.height - implH) / 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; const edgePad = 2;
return { return {
"x": pos.x + offsetX - edgePad, "x": pos.x + offsetX - edgePad,
"y": pos.y + offsetY - edgePad, "y": pos.y + offsetY - edgePad,
"w": implW + edgePad * 2, "w": w + edgePad * 2,
"h": implH + edgePad * 2 "h": h + edgePad * 2
}; };
} }
+9 -2
View File
@@ -20,14 +20,21 @@ PanelWindow {
readonly property int barPos: body.barPos readonly property int barPos: body.barPos
readonly property bool barRevealed: body.barRevealed readonly property bool barRevealed: body.barRevealed
property alias controlCenterButtonRef: body.controlCenterButtonRef
property alias clockButtonRef: body.clockButtonRef
property alias systemUpdateButtonRef: body.systemUpdateButtonRef
function triggerSystemUpdate() { function triggerSystemUpdate() {
body.triggerSystemUpdate(); body.triggerSystemUpdate();
} }
function triggerControlCenter() { function triggerControlCenter() {
body.triggerControlCenter(); body.triggerControlCenter();
} }
function triggerDashTab(tabId) { function triggerDashTab(tabId, position) {
body.triggerDashTab(tabId); return body.triggerDashTab(tabId, position);
}
function positionDash(popout, position) {
return body.positionDash(popout, position);
} }
function triggerWallpaperBrowser() { function triggerWallpaperBrowser() {
body.triggerWallpaperBrowser(); body.triggerWallpaperBrowser();
@@ -35,44 +35,42 @@ BasePill {
} }
} }
onRightClicked: (rx, ry) => { onRightClicked: {
const screen = root.parentScreen || Screen; const screen = root.parentScreen || Screen;
if (!screen) if (!screen)
return; return;
const globalPos = root.visualContent.mapToItem(null, 0, 0);
const isVertical = root.axis?.isVertical ?? false; const isVertical = root.axis?.isVertical ?? false;
const edge = root.axis?.edge ?? "top"; const edge = root.axis?.edge ?? "top";
const gap = Math.max(Theme.spacingXS, root.barSpacing ?? Theme.spacingXS); const gap = Math.max(Theme.spacingXS, root.barSpacing ?? Theme.spacingXS);
const barOffset = root.barThickness + root.barSpacing + gap; const barOffset = root.barThickness + root.barSpacing + gap;
const localPos = root.visualContent.mapToItem(null, root.visualContent.width / 2, root.visualContent.height / 2);
let anchorX; let anchorX;
let anchorY; let anchorY;
let anchorEdge;
if (isVertical) { if (isVertical) {
anchorY = globalPos.y - (screen.y || 0) + root.visualContent.height / 2; anchorX = edge === "left" ? barOffset : screen.width - barOffset;
if (edge === "left") { anchorY = localPos.y;
anchorX = barOffset;
anchorEdge = "top";
} else {
anchorX = screen.width - barOffset;
anchorEdge = "top";
}
} else { } else {
anchorX = globalPos.x - (screen.x || 0) + root.visualContent.width / 2; anchorX = localPos.x;
if (edge === "bottom") { anchorY = edge === "bottom" ? screen.height - barOffset : barOffset;
anchorY = screen.height - barOffset;
anchorEdge = "bottom";
} else {
anchorY = barOffset;
anchorEdge = "top";
}
} }
dndPopupLoader.active = true; dndPopupLoader.active = true;
const popup = dndPopupLoader.item; const popup = dndPopupLoader.item;
if (!popup) if (!popup)
return; 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 { Loader {
@@ -31,14 +31,16 @@ PanelWindow {
} }
property point anchorPos: Qt.point(0, 0) property point anchorPos: Qt.point(0, 0)
property string anchorEdge: "top" property bool isVertical: false
property string edge: "top"
visible: false visible: false
function showAt(x, y, targetScreen, edge) { function showAt(x, y, vertical, barEdge, targetScreen) {
if (targetScreen) if (targetScreen)
root.screen = targetScreen; root.screen = targetScreen;
anchorPos = Qt.point(x, y); anchorPos = Qt.point(x, y);
anchorEdge = edge || "top"; isVertical = vertical ?? false;
edge = barEdge ?? "top";
visible = true; visible = true;
} }
@@ -66,21 +68,19 @@ PanelWindow {
visible: root.visible visible: root.visible
x: { x: {
const left = 10; if (root.isVertical) {
const right = root.width - width - 10; if (root.edge === "left")
const want = root.anchorPos.x - width / 2; return Math.min(root.width - width - 10, root.anchorPos.x);
return Math.max(left, Math.min(right, want)); return Math.max(10, root.anchorPos.x - width);
}
return Math.max(10, Math.min(root.width - width - 10, root.anchorPos.x - width / 2));
} }
y: { y: {
switch (root.anchorEdge) { if (root.isVertical)
case "bottom":
return Math.max(10, root.anchorPos.y - height);
case "left":
case "right":
return Math.max(10, Math.min(root.height - height - 10, root.anchorPos.y - height / 2)); return Math.max(10, Math.min(root.height - height - 10, root.anchorPos.y - height / 2));
default: if (root.edge === "bottom")
return Math.min(root.height - height - 10, root.anchorPos.y); return Math.max(10, root.anchorPos.y - height);
} return Math.min(root.height - height - 10, root.anchorPos.y);
} }
onDismissed: root.closeMenu() onDismissed: root.closeMenu()
@@ -375,6 +375,8 @@ Item {
visible: DesktopService.autostartAvailable visible: DesktopService.autostartAvailable
SettingsCard { SettingsCard {
settingKey: "autostartAddEntry"
tags: ["autostart", "add", "entry", "command", "startup"]
width: parent.width width: parent.width
iconName: "add_circle" iconName: "add_circle"
title: I18n.tr("Add Entry") title: I18n.tr("Add Entry")
@@ -764,6 +766,8 @@ Item {
} }
SettingsCard { SettingsCard {
settingKey: "autostartTrayIconFix"
tags: ["tray", "icons", "fix", "systemd"]
width: parent.width width: parent.width
iconName: "handyman" iconName: "handyman"
title: I18n.tr("Tray Icon Fix") title: I18n.tr("Tray Icon Fix")
@@ -463,6 +463,7 @@ Item {
} }
SettingsCard { SettingsCard {
settingKey: "barEnable"
iconName: selectedBarConfig?.enabled ? "visibility" : "visibility_off" iconName: selectedBarConfig?.enabled ? "visibility" : "visibility_off"
title: I18n.tr("Enable Bar") title: I18n.tr("Enable Bar")
visible: !dankBarTab.appearanceOnly && selectedBarId !== "default" visible: !dankBarTab.appearanceOnly && selectedBarId !== "default"
@@ -651,6 +652,8 @@ Item {
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
SettingsToggleRow { SettingsToggleRow {
settingKey: "barAutoHide"
tags: ["autohide", "auto-hide", "reveal", "intellihide"]
text: I18n.tr("Auto-hide") text: I18n.tr("Auto-hide")
description: I18n.tr("Automatically hide the bar when the pointer moves away") description: I18n.tr("Automatically hide the bar when the pointer moves away")
checked: selectedBarConfig?.autoHide ?? false checked: selectedBarConfig?.autoHide ?? false
@@ -700,6 +703,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "barAutoHideStrict"
tags: ["autohide", "strict", "popout"]
width: parent.width - parent.leftPadding 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") 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") description: I18n.tr("Hide the bar when the pointer leaves even if a popout is still open")
@@ -713,6 +718,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "barHideWhenWindowsOpen"
tags: ["hide", "windows", "empty", "workspace"]
width: parent.width - parent.leftPadding width: parent.width - parent.leftPadding
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
text: I18n.tr("Hide When Windows Open") text: I18n.tr("Hide When Windows Open")
@@ -734,6 +741,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "barManualVisibility"
tags: ["manual", "show", "hide", "ipc", "toggle"]
text: I18n.tr("Manual Show/Hide") text: I18n.tr("Manual Show/Hide")
description: I18n.tr("Toggle bar visibility manually via IPC") description: I18n.tr("Toggle bar visibility manually via IPC")
checked: selectedBarConfig?.visible ?? true checked: selectedBarConfig?.visible ?? true
@@ -753,6 +762,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "barClickThrough"
tags: ["clickthrough", "click", "through", "mouse", "input", "mask", "passthrough"]
text: I18n.tr("Click Through") text: I18n.tr("Click Through")
description: I18n.tr("Mouse clicks pass through the bar to windows behind it") description: I18n.tr("Mouse clicks pass through the bar to windows behind it")
checked: selectedBarConfig?.clickThrough ?? false checked: selectedBarConfig?.clickThrough ?? false
@@ -908,6 +919,8 @@ Item {
SettingsSliderRow { SettingsSliderRow {
id: exclusiveZoneSlider id: exclusiveZoneSlider
settingKey: "barExclusiveZone"
tags: ["exclusive", "zone", "reserved", "offset"]
visible: !SettingsData.frameEnabled visible: !SettingsData.frameEnabled
text: I18n.tr("Exclusive Zone Offset") text: I18n.tr("Exclusive Zone Offset")
description: I18n.tr("Fine-tune the space reserved for the bar from the screen edge") description: I18n.tr("Fine-tune the space reserved for the bar from the screen edge")
@@ -931,6 +944,8 @@ Item {
SettingsSliderRow { SettingsSliderRow {
id: sizeSlider id: sizeSlider
settingKey: "barSize"
tags: ["size", "thickness", "height", "inner"]
visible: !SettingsData.frameEnabled visible: !SettingsData.frameEnabled
text: I18n.tr("Size") text: I18n.tr("Size")
description: I18n.tr("Adjust the bar height via inner padding") description: I18n.tr("Adjust the bar height via inner padding")
@@ -1152,6 +1167,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "barSquareCorners"
tags: ["square", "corners", "rounding"]
text: I18n.tr("Square Corners") text: I18n.tr("Square Corners")
description: I18n.tr("Remove corner rounding from the bar") description: I18n.tr("Remove corner rounding from the bar")
visible: !SettingsData.frameEnabled visible: !SettingsData.frameEnabled
@@ -1162,6 +1179,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "barNoBackground"
tags: ["transparent", "background", "invisible"]
text: I18n.tr("No Background") text: I18n.tr("No Background")
description: I18n.tr("Make the bar background fully transparent") description: I18n.tr("Make the bar background fully transparent")
visible: !SettingsData.frameEnabled visible: !SettingsData.frameEnabled
@@ -1172,6 +1191,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "barMaximizeWidgetIcons"
tags: ["maximize", "icons", "stretch"]
text: I18n.tr("Maximize Widget Icons") text: I18n.tr("Maximize Widget Icons")
description: I18n.tr("Stretch widget icons to fill the available bar height") description: I18n.tr("Stretch widget icons to fill the available bar height")
checked: selectedBarConfig?.maximizeWidgetIcons ?? false checked: selectedBarConfig?.maximizeWidgetIcons ?? false
@@ -1181,6 +1202,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "barMaximizeWidgetText"
tags: ["maximize", "text", "stretch"]
text: I18n.tr("Maximize Widget Text") text: I18n.tr("Maximize Widget Text")
description: I18n.tr("Stretch widget text to fill the available bar height") description: I18n.tr("Stretch widget text to fill the available bar height")
checked: selectedBarConfig?.maximizeWidgetText ?? false checked: selectedBarConfig?.maximizeWidgetText ?? false
@@ -1190,6 +1213,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "barRemoveWidgetPadding"
tags: ["padding", "compact", "widgets"]
text: I18n.tr("Remove Widget Padding") text: I18n.tr("Remove Widget Padding")
description: I18n.tr("Remove inner padding from all widgets") description: I18n.tr("Remove inner padding from all widgets")
checked: selectedBarConfig?.removeWidgetPadding ?? false checked: selectedBarConfig?.removeWidgetPadding ?? false
@@ -1206,6 +1231,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "barGothCorners"
tags: ["goth", "corners", "concave", "cutout"]
text: I18n.tr("Goth Corners") text: I18n.tr("Goth Corners")
description: I18n.tr("Apply inverse concave corner cutouts to the bar") description: I18n.tr("Apply inverse concave corner cutouts to the bar")
visible: !SettingsData.frameEnabled visible: !SettingsData.frameEnabled
@@ -1302,6 +1329,8 @@ Item {
} }
SettingsToggleCard { SettingsToggleCard {
settingKey: "barMaximizeDetection"
tags: ["maximize", "gaps", "border", "fullscreen"]
iconName: "fit_screen" iconName: "fit_screen"
title: I18n.tr("Maximize Detection") title: I18n.tr("Maximize Detection")
description: I18n.tr("Remove gaps and border when windows are maximized") description: I18n.tr("Remove gaps and border when windows are maximized")
@@ -1844,6 +1873,8 @@ Item {
SettingsToggleCard { SettingsToggleCard {
iconName: "mouse" iconName: "mouse"
settingKey: "barScrollWheel"
tags: ["scroll", "wheel", "workspace", "axis"]
title: I18n.tr("Scroll Wheel") title: I18n.tr("Scroll Wheel")
description: I18n.tr("Control workspaces and columns by scrolling on the bar") description: I18n.tr("Control workspaces and columns by scrolling on the bar")
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
@@ -267,6 +267,8 @@ Item {
spacing: Theme.spacingXL spacing: Theme.spacingXL
SettingsCard { SettingsCard {
settingKey: "defaultAppsInternet"
tags: ["browser", "mail", "email", "web"]
title: I18n.tr("Internet", "Internet") title: I18n.tr("Internet", "Internet")
iconName: "public" iconName: "public"
@@ -293,6 +295,8 @@ Item {
} }
SettingsCard { SettingsCard {
settingKey: "defaultAppsUtilities"
tags: ["file", "manager", "terminal", "editor"]
title: I18n.tr("Utilities", "Utilities") title: I18n.tr("Utilities", "Utilities")
iconName: "terminal" iconName: "terminal"
@@ -316,6 +320,8 @@ Item {
} }
SettingsCard { SettingsCard {
settingKey: "defaultAppsDocuments"
tags: ["pdf", "text", "reader", "office"]
title: I18n.tr("Documents", "Documents") title: I18n.tr("Documents", "Documents")
iconName: "edit_document" iconName: "edit_document"
@@ -333,6 +339,8 @@ Item {
} }
SettingsCard { SettingsCard {
settingKey: "defaultAppsMultimedia"
tags: ["image", "video", "music", "viewer", "player"]
title: I18n.tr("Multimedia", "Multimedia") title: I18n.tr("Multimedia", "Multimedia")
iconName: "movie" iconName: "movie"
AppSelector { AppSelector {
@@ -166,6 +166,8 @@ Item {
spacing: Theme.spacingXL spacing: Theme.spacingXL
SettingsCard { SettingsCard {
settingKey: "desktopWidgetsManage"
tags: ["desktop", "widgets", "clock", "conky"]
width: parent.width width: parent.width
iconName: "widgets" iconName: "widgets"
title: I18n.tr("Desktop Widgets") title: I18n.tr("Desktop Widgets")
@@ -203,6 +205,8 @@ Item {
} }
SettingsCard { SettingsCard {
settingKey: "desktopWidgetGroups"
tags: ["groups", "profiles", "layouts"]
width: parent.width width: parent.width
iconName: "folder" iconName: "folder"
title: I18n.tr("Groups") title: I18n.tr("Groups")
@@ -17,6 +17,7 @@ Singleton {
readonly property var wlrOutputs: WlrOutputService.outputs readonly property var wlrOutputs: WlrOutputService.outputs
property var outputs: ({}) property var outputs: ({})
property var savedOutputs: ({}) property var savedOutputs: ({})
property var savedParsedOutputs: ({})
property var allOutputs: buildAllOutputsMap() property var allOutputs: buildAllOutputsMap()
property var includeStatus: ({ property var includeStatus: ({
@@ -942,15 +943,18 @@ Singleton {
const paths = getConfigPaths(); const paths = getConfigPaths();
if (!paths) { if (!paths) {
savedOutputs = {}; savedOutputs = {};
savedParsedOutputs = {};
return; return;
} }
Proc.runCommand("load-saved-outputs", ["cat", paths.outputsFile], (content, exitCode) => { Proc.runCommand("load-saved-outputs", ["cat", paths.outputsFile], (content, exitCode) => {
if (exitCode !== 0 || !content.trim()) { if (exitCode !== 0 || !content.trim()) {
savedOutputs = {}; savedOutputs = {};
savedParsedOutputs = {};
return; return;
} }
const parsed = parseOutputsConfig(content); const parsed = parseOutputsConfig(content);
savedParsedOutputs = parsed;
const filtered = filterDisconnectedOnly(parsed); const filtered = filterDisconnectedOnly(parsed);
savedOutputs = filtered; savedOutputs = filtered;
@@ -1123,20 +1127,7 @@ Singleton {
const name = match[1]; const name = match[1];
const body = match[2]; const body = match[2];
if (body.trim() === "off") { const disabled = /^\s*off\s*$/m.test(body);
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 modeMatch = body.match(/mode\s+"(\d+)x(\d+)@([\d.]+)"/);
const posMatch = body.match(/position\s+x=(-?\d+)\s+y=(-?\d+)/); const posMatch = body.match(/position\s+x=(-?\d+)\s+y=(-?\d+)/);
const scaleMatch = body.match(/scale\s+([\d.]+)/); const scaleMatch = body.match(/scale\s+([\d.]+)/);
@@ -1146,6 +1137,7 @@ Singleton {
result[name] = { result[name] = {
"name": name, "name": name,
"disabled": disabled,
"logical": { "logical": {
"x": posMatch ? parseInt(posMatch[1]) : 0, "x": posMatch ? parseInt(posMatch[1]) : 0,
"y": posMatch ? parseInt(posMatch[2]) : 0, "y": posMatch ? parseInt(posMatch[2]) : 0,
@@ -1809,6 +1801,41 @@ Singleton {
return normalized; 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() { function buildOutputsWithPendingChanges() {
const result = {}; const result = {};
@@ -1818,7 +1845,10 @@ Singleton {
} }
for (const outputName in outputs) { 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) { for (const outputName in pendingChanges) {
@@ -1830,6 +1860,8 @@ Singleton {
result[outputName].logical.y = changes.position.y; result[outputName].logical.y = changes.position.y;
} }
if (changes.mode !== undefined && result[outputName].modes) { 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++) { for (var i = 0; i < result[outputName].modes.length; i++) {
if (formatMode(result[outputName].modes[i]) === changes.mode) { if (formatMode(result[outputName].modes[i]) === changes.mode) {
result[outputName].current_mode = i; result[outputName].current_mode = i;
+2
View File
@@ -617,6 +617,8 @@ Item {
} }
SettingsSliderRow { SettingsSliderRow {
settingKey: "dockExclusiveZone"
tags: ["exclusive", "zone", "reserved", "offset"]
text: I18n.tr("Exclusive Zone Offset") text: I18n.tr("Exclusive Zone Offset")
visible: !root.connectedFrameModeActive || root.connectedPersistentDockActive visible: !root.connectedFrameModeActive || root.connectedPersistentDockActive
value: SettingsData.dockBottomGap value: SettingsData.dockBottomGap
@@ -38,6 +38,8 @@ Item {
settingKey: "mediaPlayer" settingKey: "mediaPlayer"
SettingsToggleRow { SettingsToggleRow {
settingKey: "mediaWaveProgress"
tags: ["wave", "progress", "animated"]
text: I18n.tr("Wave Progress Bars") text: I18n.tr("Wave Progress Bars")
description: I18n.tr("Use animated wave progress bars for media playback") description: I18n.tr("Use animated wave progress bars for media playback")
checked: SettingsData.waveProgressEnabled checked: SettingsData.waveProgressEnabled
@@ -45,6 +47,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "mediaScrollTitle"
tags: ["scroll", "title", "marquee"]
text: I18n.tr("Scroll song title") text: I18n.tr("Scroll song title")
description: I18n.tr("Scroll title if it doesn't fit in widget") description: I18n.tr("Scroll title if it doesn't fit in widget")
checked: SettingsData.scrollTitleEnabled checked: SettingsData.scrollTitleEnabled
@@ -52,6 +56,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "mediaVisualizer"
tags: ["visualizer", "cava", "spectrum"]
text: I18n.tr("Audio Visualizer") text: I18n.tr("Audio Visualizer")
description: I18n.tr("Show cava audio visualizer in media widget") description: I18n.tr("Show cava audio visualizer in media widget")
checked: SettingsData.audioVisualizerEnabled checked: SettingsData.audioVisualizerEnabled
@@ -59,6 +65,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "mediaAdaptiveWidth"
tags: ["adaptive", "width", "shrink"]
text: I18n.tr("Adaptive Media Width") 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") description: I18n.tr("Shrink the media widget to fit shorter song titles while still respecting the configured maximum size")
checked: SettingsData.mediaAdaptiveWidthEnabled checked: SettingsData.mediaAdaptiveWidthEnabled
@@ -66,6 +74,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "mediaAlbumArtAccent"
tags: ["album", "art", "accent", "colors"]
text: I18n.tr("Use album art accent") text: I18n.tr("Use album art accent")
description: I18n.tr("Use colors extracted from album art instead of system theme colors") description: I18n.tr("Use colors extracted from album art instead of system theme colors")
checked: SettingsData.mediaUseAlbumArtAccent checked: SettingsData.mediaUseAlbumArtAccent
@@ -136,6 +146,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "mediaDeviceScrollVolume"
tags: ["device", "scroll", "volume"]
text: I18n.tr("Device list 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") description: I18n.tr("Allow adjusting device volume by scrolling on the right half of items in the device list")
checked: SettingsData.audioDeviceScrollVolumeEnabled checked: SettingsData.audioDeviceScrollVolumeEnabled
+1
View File
@@ -80,6 +80,7 @@ Item {
SettingsCard { SettingsCard {
tab: "mux" tab: "mux"
tags: ["mux", "session", "filter", "exclude", "hide"] tags: ["mux", "session", "filter", "exclude", "hide"]
settingKey: "muxSessionFilter"
title: I18n.tr("Session Filter") title: I18n.tr("Session Filter")
iconName: "filter_list" iconName: "filter_list"
@@ -89,6 +89,8 @@ Item {
} }
SettingsDropdownRow { SettingsDropdownRow {
settingKey: "systemUpdaterCheckInterval"
tags: ["interval", "poll", "frequency"]
text: I18n.tr("Check interval") text: I18n.tr("Check interval")
description: I18n.tr("How often the server polls for new updates.") description: I18n.tr("How often the server polls for new updates.")
options: root.intervalOptions.map(o => o.label).concat([root.customIntervalLabel]) options: root.intervalOptions.map(o => o.label).concat([root.customIntervalLabel])
@@ -157,6 +159,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "systemUpdaterCheckOnStart"
tags: ["startup", "check", "boot"]
text: I18n.tr("Check on startup") 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.") 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 checked: SettingsData.updaterCheckOnStart
@@ -164,6 +168,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "systemUpdaterFlatpak"
tags: ["flatpak", "include"]
text: I18n.tr("Include Flatpak updates") text: I18n.tr("Include Flatpak updates")
description: I18n.tr("Apply Flatpak updates alongside system updates when running 'Update All'.") description: I18n.tr("Apply Flatpak updates alongside system updates when running 'Update All'.")
visible: (SystemUpdateService.backends || []).some(b => b.repo === "flatpak") visible: (SystemUpdateService.backends || []).some(b => b.repo === "flatpak")
@@ -172,6 +178,8 @@ Item {
} }
SettingsToggleRow { SettingsToggleRow {
settingKey: "systemUpdaterAUR"
tags: ["aur", "paru", "yay"]
text: I18n.tr("Include AUR updates") text: I18n.tr("Include AUR updates")
description: I18n.tr("Run paru/yay with AUR enabled when 'Update All' is clicked.") 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") visible: (SystemUpdateService.backends || []).some(b => b.id === "paru" || b.id === "yay")
@@ -328,6 +336,8 @@ Item {
settingKey: "systemUpdaterAdvanced" settingKey: "systemUpdaterAdvanced"
SettingsToggleRow { SettingsToggleRow {
settingKey: "systemUpdaterCustomCommand"
tags: ["custom", "command", "terminal"]
text: I18n.tr("Use Custom Command") text: I18n.tr("Use Custom Command")
description: I18n.tr("Open a terminal and run a custom command instead of the in-shell upgrade flow.") description: I18n.tr("Open a terminal and run a custom command instead of the in-shell upgrade flow.")
checked: SettingsData.updaterUseCustomCommand checked: SettingsData.updaterUseCustomCommand
@@ -167,7 +167,13 @@ FloatingWindow {
width: parent.width width: parent.width
height: parent.height - searchField.height - Theme.spacingM height: parent.height - searchField.height - Theme.spacingM
spacing: Theme.spacingS 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 clip: true
delegate: Rectangle { delegate: Rectangle {
@@ -316,11 +322,21 @@ FloatingWindow {
function show() { function show() {
updateFilteredApps(); 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; visible = true;
Qt.callLater(() => searchField.forceActiveFocus()); Qt.callLater(() => searchField.forceActiveFocus());
} }
function hide() { 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; visible = false;
searchQuery = ""; searchQuery = "";
filteredApps = []; filteredApps = [];
@@ -330,6 +346,9 @@ FloatingWindow {
onVisibleChanged: { onVisibleChanged: {
if (!visible) { if (!visible) {
// Guard against visibility being cleared without going through hide()
// (e.g. window manager close, FloatingWindow.onClosed -> hide()).
appList.model = null;
searchQuery = ""; searchQuery = "";
filteredApps = []; filteredApps = [];
selectedIndex = -1; selectedIndex = -1;
+17 -9
View File
@@ -17,7 +17,22 @@ Singleton {
interval: 500 interval: 500
repeat: false repeat: false
running: true 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") readonly property string preferredBatteryOverride: Quickshell.env("DMS_PREFERRED_BATTERY")
@@ -206,14 +221,7 @@ Singleton {
} }
} }
const profileValue = BatteryService.isPluggedIn ? SettingsData.acProfileName : SettingsData.batteryProfileName; applyPowerProfile();
if (profileValue !== "") {
const targetProfile = parseInt(profileValue);
if (!isNaN(targetProfile) && PowerProfiles.profile !== targetProfile) {
PowerProfiles.profile = targetProfile;
}
}
previousPluggedState = isPluggedIn; previousPluggedState = isPluggedIn;
} }
-2
View File
@@ -1511,8 +1511,6 @@ window-rule {
if (outputSettings.disabled) { if (outputSettings.disabled) {
kdlContent += ` off\n`; kdlContent += ` off\n`;
kdlContent += `}\n\n`;
continue;
} }
if (output.configured_mode) { if (output.configured_mode) {
+135 -55
View File
@@ -1,14 +1,16 @@
#!/usr/bin/env bash #!/usr/bin/env bash
CONFIG_DIR="$1" CONFIG_DIR="$1"
# apply: setup symlinks and config dirs # apply: setup overrides and config dirs
# patch: refresh overrides in adw-gtk3 # patch: refresh overrides in adw-gtk3
# remove: remove all overrides # remove: remove all overrides
# assets: relink adw-gtk3 assets if DMS already manages gtk.css
MODE="${2:-apply}" MODE="${2:-apply}"
IS_LIGHT="${3:-light}" IS_LIGHT="${3:-light}"
SHELL_DIR="$4"
if [ -z "$CONFIG_DIR" ]; then if [ -z "$CONFIG_DIR" ]; then
echo "Usage: $0 <config_dir> [apply|patch|remove] [is_light] [shell_dir]" >&2 echo "Usage: $0 <config_dir> [apply|patch|remove|assets] [is_light] [shell_dir]" >&2
exit 1 exit 1
fi fi
@@ -33,6 +35,100 @@ get_adw_gtk3_dir() {
echo "$target" echo "$target"
} }
# The light template references adw-gtk3 image assets relative to gtk.css
# (check/radio glyphs, slider knobs); without them checked boxes render as
# solid blocks.
link_gtk3_assets() {
local gtk3_dir="$1"
local assets_link="$gtk3_dir/assets"
if [ -e "$assets_link" ] && [ ! -L "$assets_link" ]; then
echo "Leaving user-managed $assets_link in place"
return
fi
local candidates=(
"$HOME/.local/share/themes/adw-gtk3/gtk-3.0/assets"
"$HOME/.themes/adw-gtk3/gtk-3.0/assets"
"/usr/share/themes/adw-gtk3/gtk-3.0/assets"
"/usr/local/share/themes/adw-gtk3/gtk-3.0/assets"
)
local target=""
for c in "${candidates[@]}"; do
if [ -d "$c" ]; then
target="$c"
break
fi
done
if [ -z "$target" ] && [ -n "$SHELL_DIR" ] && [ -d "$SHELL_DIR/matugen/gtk3-assets" ]; then
target="$SHELL_DIR/matugen/gtk3-assets"
fi
if [ -z "$target" ]; then
return
fi
ln -sfn "$target" "$assets_link"
echo "Linked GTK3 assets: $assets_link -> $target"
}
DANK_IMPORT='@import url("dank-colors.css");'
DANK_IMPORT_RE='^@import url.*dank-colors\.css.*);$'
# gtk.css is DMS-managed if it is our symlink or carries our import line.
# User-managed symlinks (e.g. home-manager) are never touched.
dms_managed_css() {
local gtk_css="$1"
if [ -L "$gtk_css" ]; then
case "$(readlink "$gtk_css")" in
*dank-colors.css*) return 0 ;;
*) return 1 ;;
esac
fi
[ -f "$gtk_css" ] && grep -q "$DANK_IMPORT_RE" "$gtk_css"
}
inject_dank_import() {
local gtk_css="$1"
if [ -L "$gtk_css" ]; then
if ! dms_managed_css "$gtk_css"; then
echo "Warning: '$gtk_css' is a user-managed symlink; leaving it untouched" >&2
echo "Import dank-colors.css from your own stylesheet to use DMS colors" >&2
return 1
fi
rm "$gtk_css"
fi
if [ -f "$gtk_css" ] && grep -q "$DANK_IMPORT_RE" "$gtk_css"; then
echo "Dank import already present in '$gtk_css'"
return 0
fi
if [ -f "$gtk_css" ] && [ -s "$gtk_css" ]; then
sed -i "1i\\$DANK_IMPORT" "$gtk_css"
else
echo "$DANK_IMPORT" >"$gtk_css"
fi
echo "Added dank-colors import to '$gtk_css'"
}
remove_dank_import() {
local gtk_css="$1"
if [ -L "$gtk_css" ]; then
if dms_managed_css "$gtk_css"; then
rm "$gtk_css"
echo "Removed DMS-managed symlink '$gtk_css'"
fi
return 0
fi
if [ -f "$gtk_css" ] && grep -q "$DANK_IMPORT_RE" "$gtk_css"; then
sed -i "/$DANK_IMPORT_RE/d" "$gtk_css"
echo "Removed dank-colors import from '$gtk_css'"
fi
}
remove_gtk3_patch() { remove_gtk3_patch() {
local theme_dir="$1" local theme_dir="$1"
local css_variant="$2" local css_variant="$2"
@@ -47,13 +143,14 @@ remove_gtk3_colors() {
local gtk3_dir="$config_dir/gtk-3.0" local gtk3_dir="$config_dir/gtk-3.0"
# remove global override # remove global override
remove_dank_import "$gtk3_dir/gtk.css"
if [ ! -f "${gtk3_dir}/dank-colors.css" ]; then if [ ! -f "${gtk3_dir}/dank-colors.css" ]; then
echo "Nothing to remove at '${gtk3_dir}'" echo "Nothing to remove at '${gtk3_dir}'"
else else
if rm "${gtk3_dir}/dank-colors.css"; then if rm "${gtk3_dir}/dank-colors.css"; then
echo "Removed GTK3 override form '${gtk3_dir}'" echo "Removed GTK3 override from '${gtk3_dir}'"
else else
echo "Failed to removed GTK3 override from '${gtk3_dir}'" echo "Failed to remove GTK3 override from '${gtk3_dir}'"
fi fi
fi fi
@@ -61,9 +158,9 @@ remove_gtk3_colors() {
for variant in light dark; do for variant in light dark; do
local adw_gtk3_dir && adw_gtk3_dir=$(get_adw_gtk3_dir "$variant") local adw_gtk3_dir && adw_gtk3_dir=$(get_adw_gtk3_dir "$variant")
if [ -z "$adw_gtk3_dir" ]; then if [ -z "$adw_gtk3_dir" ] || [[ "$adw_gtk3_dir" =~ ^/usr ]]; then
echo "Error: No version of adw-gtk3 ${variant} was found" >&2 echo "No user version of adw-gtk3 ${variant} found, nothing to unpatch"
exit 1 continue
fi fi
for css_variant in light dark; do for css_variant in light dark; do
@@ -79,6 +176,10 @@ remove_gtk3_colors() {
do_patch() { do_patch() {
local theme_dir="$1" local theme_dir="$1"
local variant="$2" local variant="$2"
if [ -z "$theme_dir" ] || [[ "$theme_dir" =~ ^/usr ]]; then
echo "Skipping '$variant' patch: no user copy of adw-gtk3 for this variant"
return 0
fi
local css_variant="" local css_variant=""
[ "$variant" = "dark" ] && css_variant="-${variant}" [ "$variant" = "dark" ] && css_variant="-${variant}"
if { if {
@@ -102,14 +203,9 @@ patch_gtk3_colors() {
[ "$is_light" = "false" ] && variant="dark" [ "$is_light" = "false" ] && variant="dark"
local adw_gtk3_dir && adw_gtk3_dir=$(get_adw_gtk3_dir "$variant") local adw_gtk3_dir && adw_gtk3_dir=$(get_adw_gtk3_dir "$variant")
if [ -z "$adw_gtk3_dir" ]; then if [ -z "$adw_gtk3_dir" ] || [[ "$adw_gtk3_dir" =~ ^/usr ]]; then
echo "Warning: No version of adw-gtk3 ${variant} was found" >&2 echo "No user version of adw-gtk3 ${variant} was found, skipping patch"
exit 1 exit 2
fi
if [[ "$adw_gtk3_dir" =~ ^/usr ]]; then
echo "Warning: No user version of adw-gtk3 ${variant} was found." >&2
exit 1
fi fi
if [ ! -f "${gtk3_dir}/dank-colors.css" ]; then if [ ! -f "${gtk3_dir}/dank-colors.css" ]; then
@@ -146,34 +242,23 @@ apply_gtk3_colors() {
exit 1 exit 1
fi fi
if [ -L "$gtk3_override" ]; then inject_dank_import "$gtk3_override" || exit 1
rm "$gtk3_override"
elif [ -f "$gtk3_override" ]; then
mv "$gtk3_override" "$gtk3_override.backup.$(date +%s)"
echo "Backed up existing gtk.css"
fi
ln -s "dank-colors.css" "$gtk3_override"
echo "Created symlink: $gtk3_override -> dank-colors.css"
link_gtk3_assets "$gtk3_dir" link_gtk3_assets "$gtk3_dir"
return return
fi fi
# Else ensure there's no global override # adw-gtk3 carries the colors; ensure there's no DMS global override
if [ -L "$gtk3_override" ]; then remove_dank_import "$gtk3_override"
rm "$gtk3_override"
elif [ -f "$gtk3_override" ]; then
mv "$gtk3_override" "$gtk3_override.backup.$(date +%s)"
echo "Backed up and removed existing gtk.css"
fi
# Backup adw-gtk3 stylesheets # Backup pristine adw-gtk3 stylesheets once
for variant in light dark; do for variant in light dark; do
local adw_gtk3_dir && adw_gtk3_dir="$(get_adw_gtk3_dir "$variant")" local adw_gtk3_dir && adw_gtk3_dir="$(get_adw_gtk3_dir "$variant")"
cp "$adw_gtk3_dir/gtk-3.0/gtk.css" "$adw_gtk3_dir/gtk-3.0/gtk.css.backup.$(date +%s)" for css in gtk.css gtk-dark.css; do
cp "$adw_gtk3_dir/gtk-3.0/gtk-dark.css" "$adw_gtk3_dir/gtk-3.0/gtk-dark.css.backup.$(date +%s)" if [ -f "$adw_gtk3_dir/$css" ] && [ ! -f "$adw_gtk3_dir/$css.dms-backup" ]; then
cp "$adw_gtk3_dir/$css" "$adw_gtk3_dir/$css.dms-backup"
fi
done
done done
} }
@@ -184,6 +269,8 @@ remove_gtk4_colors() {
local dank_colors="$gtk4_dir/dank-colors.css" local dank_colors="$gtk4_dir/dank-colors.css"
local gtk_css="$gtk4_dir/gtk.css" local gtk_css="$gtk4_dir/gtk.css"
remove_dank_import "$gtk_css"
if [ ! -f "$dank_colors" ]; then if [ ! -f "$dank_colors" ]; then
echo "Nothing to remove in '$gtk4_dir'" echo "Nothing to remove in '$gtk4_dir'"
return return
@@ -191,13 +278,6 @@ remove_gtk4_colors() {
rm "$dank_colors" rm "$dank_colors"
echo "Removed 'dank-colors.css' from '$gtk4_dir'" echo "Removed 'dank-colors.css' from '$gtk4_dir'"
local gtk4_import="@import url(\"dank-colors.css\");"
if [ -f "$gtk_css" ] && grep -q '^@import url.*dank-colors\.css.*);$' "$gtk_css"; then
sed -i "/$gtk4_import/d" "$gtk_css"
echo "Removed gtk4 import in '$gtk_css'"
fi
} }
apply_gtk4_colors() { apply_gtk4_colors() {
@@ -206,7 +286,6 @@ apply_gtk4_colors() {
local gtk4_dir="$config_dir/gtk-4.0" local gtk4_dir="$config_dir/gtk-4.0"
local dank_colors="$gtk4_dir/dank-colors.css" local dank_colors="$gtk4_dir/dank-colors.css"
local gtk_css="$gtk4_dir/gtk.css" local gtk_css="$gtk4_dir/gtk.css"
local gtk4_import="@import url(\"dank-colors.css\");"
if [ ! -f "$dank_colors" ]; then if [ ! -f "$dank_colors" ]; then
echo "Error: GTK4 dank-colors.css not found at $dank_colors" >&2 echo "Error: GTK4 dank-colors.css not found at $dank_colors" >&2
@@ -214,21 +293,16 @@ apply_gtk4_colors() {
exit 1 exit 1
fi fi
if [ -f "$gtk_css" ] && grep -q '^@import url.*dank-colors\.css.*);$' "$gtk_css"; then inject_dank_import "$gtk_css" || exit 1
echo "GTK4 import already exists"
return
fi
if [ -f "$gtk_css" ] && [ -s "$gtk_css" ]; then
sed -i "1i\\$gtk4_import" "$gtk_css"
else
echo "$gtk4_import" >"$gtk_css"
fi
echo "Updated GTK4 CSS import"
} }
case "$MODE" in case "$MODE" in
patch) patch)
# Only refresh themes the user opted into via 'apply'
if ! dms_managed_css "$CONFIG_DIR/gtk-4.0/gtk.css"; then
echo "DMS GTK theming is not applied, skipping patch"
exit 2
fi
patch_gtk3_colors "$CONFIG_DIR" "$IS_LIGHT" patch_gtk3_colors "$CONFIG_DIR" "$IS_LIGHT"
echo "GTK3 colors patched successfully" echo "GTK3 colors patched successfully"
;; ;;
@@ -244,8 +318,14 @@ case "$MODE" in
echo "GTK colors applied successfully" echo "GTK colors applied successfully"
;; ;;
assets)
# Repair pass at startup; only acts when DMS already manages gtk.css
if dms_managed_css "$CONFIG_DIR/gtk-3.0/gtk.css"; then
link_gtk3_assets "$CONFIG_DIR/gtk-3.0"
fi
;;
*) *)
echo "Usage: $0 <config_dir> [apply|patch|remove] [is_light] [shell_dir]" >&2 echo "Usage: $0 <config_dir> [apply|patch|remove|assets] [is_light] [shell_dir]" >&2
exit 1 exit 1
;; ;;
esac esac
File diff suppressed because it is too large Load Diff