1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-07 05:58:28 -04:00

Compare commits

...

9 Commits

Author SHA1 Message Date
purian23 170721a1f1 fix(notifWidget): restore hand cursor on hover
Port 1.5

(cherry picked from commit a575363f89)
2026-08-07 03:45:00 +00:00
Huỳnh Thiện Lộc 7e0af52d5b fix(launcher): honor source badge setting (#3002)
(cherry picked from commit 7cb97f4462)
2026-08-06 00:42:38 -04:00
Huỳnh Thiện Lộc e1b6427929 fix(settings): hide removed widget tooltip (#3003)
(cherry picked from commit 82b1b36b1a)
2026-08-06 00:42:38 -04:00
Huỳnh Thiện Lộc 47bf6a8dfd fix(slideout): yield keyboard focus when modal is open on same screen (#2902)
DankSlideout always held WlrKeyboardFocus.OnDemand regardless of modals,
causing Notepad to steal focus back when Launcher was opened.

ModalManager: clone currentModalsByScreen on mutation so QML bindings
re-evaluate when modals open/close.
DankSlideout: set keyboardFocus to None while a modal is active on the
same screen, letting the modal (launcher) claim focus exclusively.

(cherry picked from commit 9981fcf529)
2026-08-06 00:42:38 -04:00
Thomas Kroll 84ac36295f fix(tray): don't dismiss click-opened tray menus from hover controller (#2979)
closeHoverSurfaces() called TrayMenuManager.closeAllMenus(), tearing down
every registered tray menu including ones opened by right-click. With
hoverPopouts enabled, moving the pointer after a right-click re-ran the
hit test and closed the menu before it could be reached.

Tag each menu with how it was opened and add closeHoverMenus(), which
only closes hover-opened menus. The hover controller now uses that.

Click-driven callers pass no argument, so byHover stays undefined and
`byHover === true` evaluates to false.

Fixes #2978

Claude-Session: https://claude.ai/code/session_01DYF9vdsaToU4Usu7kxPCNB

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 34626070af)
2026-08-06 00:42:38 -04:00
jack-mil ca839487de fix(mpris): remove required non-null artist/title from idle condition (#2993)
The current isIdle detection only works if the player has a title _and_ artist metadata,
which is not appropriate for all cases, e.g. MPV playing a video does not set artist.

In the current implementation, after a video stops playing in MPV, and the window closes
(but MPV and the MPRIS plugin server is still running as a background process), the playback controls remain
shown on the bar, and have non-functional controls.

It is sufficient to only check for the "Stopped" playing state. The MPRIS specification
states that Playback_Status==Stopped should be used to indicate that *NO* track is currently
playing. A compliant player should set that, and should also set the CanPlay/CanPause boolean
to false is not track is being played.

See: https://specifications.freedesktop.org/mpris/latest/Player_Interface.html#Enum:Playback_Status
(cherry picked from commit 49f968d26b)
2026-08-06 00:42:38 -04:00
bbedward dabbec1dce keybinds: fix labeling of spawn keybinds
fixes #2957

(cherry picked from commit 5bb884db57)
2026-08-06 00:42:37 -04:00
euletheia bd4d35d215 fix(BatteryService): fix typo in notification check (#2944)
(cherry picked from commit 8594a414ec)
2026-08-06 00:42:37 -04:00
euletheia cfdacd3ea3 fix(BatteryService): fix unresolved icons for notifications and dismiss alerts when plugged in (#2943)
* feat(BatteryService): dismiss sticky critical battery notifications

If critical notifications do not timeout, dismiss the battery-related
ones automatically when battery is plugged in.

* fix(BatteryService): use material icons for battery alerts

Since the previously specified icons are not resolved, instead reuse
icons from DMS first-party plugin for critical battery alerts.

(cherry picked from commit df396bfa43)
2026-08-06 00:42:37 -04:00
13 changed files with 69 additions and 16 deletions
+11 -2
View File
@@ -15,7 +15,11 @@ Singleton {
function openModal(modal) { function openModal(modal) {
PopoutManager.screenshotActive = false; PopoutManager.screenshotActive = false;
const screenName = modal.effectiveScreen?.name ?? "unknown"; const screenName = modal.effectiveScreen?.name ?? "unknown";
currentModalsByScreen[screenName] = modal; var next = {};
for (var k in currentModalsByScreen)
next[k] = currentModalsByScreen[k];
next[screenName] = modal;
currentModalsByScreen = next;
modalChanged(); modalChanged();
Qt.callLater(() => { Qt.callLater(() => {
if (!modal.allowStacking) if (!modal.allowStacking)
@@ -34,7 +38,12 @@ Singleton {
function closeModal(modal) { function closeModal(modal) {
const screenName = modal.effectiveScreen?.name ?? "unknown"; const screenName = modal.effectiveScreen?.name ?? "unknown";
if (currentModalsByScreen[screenName] === modal) { if (currentModalsByScreen[screenName] === modal) {
delete currentModalsByScreen[screenName]; var next = {};
for (var k in currentModalsByScreen) {
if (k !== screenName)
next[k] = currentModalsByScreen[k];
}
currentModalsByScreen = next;
modalChanged(); modalChanged();
} }
} }
+12
View File
@@ -22,6 +22,18 @@ Singleton {
activeTrayMenus = newMenus activeTrayMenus = newMenus
} }
function closeHoverMenus() {
for (const screenName in activeTrayMenus) {
const menu = activeTrayMenus[screenName]
if (!menu || menu.openedByHover !== true) continue
if (typeof menu.close === "function") {
menu.close()
} else if (menu.showMenu !== undefined) {
menu.showMenu = false
}
}
}
function closeAllMenus() { function closeAllMenus() {
for (const screenName in activeTrayMenus) { for (const screenName in activeTrayMenus) {
const menu = activeTrayMenus[screenName] const menu = activeTrayMenus[screenName]
@@ -7,6 +7,7 @@ Item {
property string source: "" property string source: ""
property int glyphSize: 14 property int glyphSize: 14
property bool badgeVisible: true
readonly property var sourceAsset: ({ readonly property var sourceAsset: ({
"flatpak": "../../assets/package-sources/flatpak.svg", "flatpak": "../../assets/package-sources/flatpak.svg",
@@ -17,7 +18,7 @@ Item {
readonly property string assetPath: sourceAsset[source] || "" readonly property string assetPath: sourceAsset[source] || ""
visible: SettingsData.dankLauncherV2ShowSourceBadges && assetPath.length > 0 visible: badgeVisible && SettingsData.dankLauncherV2ShowSourceBadges && assetPath.length > 0
implicitWidth: glyphSize implicitWidth: glyphSize
implicitHeight: glyphSize implicitHeight: glyphSize
@@ -178,7 +178,7 @@ Rectangle {
anchors.margins: Theme.spacingXS anchors.margins: Theme.spacingXS
source: root.item?.type === "app" ? (root.item.source || "") : "" source: root.item?.type === "app" ? (root.item.source || "") : ""
glyphSize: 16 glyphSize: 16
visible: !root.isSelected && !!source badgeVisible: !root.isSelected
} }
} }
} }
@@ -609,7 +609,7 @@ Item {
_closeHoverNotepad(); _closeHoverNotepad();
activeHoverTrigger = ""; activeHoverTrigger = "";
PopoutManager.dismissHoverPopoutForScreen(barWindow?.screen); PopoutManager.dismissHoverPopoutForScreen(barWindow?.screen);
TrayMenuManager.closeAllMenus(); TrayMenuManager.closeHoverMenus();
} }
function _beginSupersededCloseForActive() { function _beginSupersededCloseForActive() {
@@ -67,6 +67,7 @@ BasePill {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.MiddleButton acceptedButtons: Qt.MiddleButton
cursorShape: Qt.PointingHandCursor
onPressed: mouse => { onPressed: mouse => {
root.triggerRipple(this, mouse.x, mouse.y); root.triggerRipple(this, mouse.x, mouse.y);
SessionData.setDoNotDisturb(!SessionData.doNotDisturb); SessionData.setDoNotDisturb(!SessionData.doNotDisturb);
@@ -1484,6 +1484,7 @@ BasePill {
property bool isVertical: false property bool isVertical: false
property var axis: null property var axis: null
property bool showMenu: false property bool showMenu: false
property bool openedByHover: false
property var menuHandle: null property var menuHandle: null
ListModel { ListModel {
@@ -1493,7 +1494,8 @@ BasePill {
return entryStack.count ? entryStack.get(entryStack.count - 1).handle : null; return entryStack.count ? entryStack.get(entryStack.count - 1).handle : null;
} }
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) { function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj, byHover) {
openedByHover = byHover === true;
trayItem = item; trayItem = item;
anchorItem = anchor; anchorItem = anchor;
parentScreen = screen; parentScreen = screen;
@@ -2084,7 +2086,7 @@ BasePill {
} }
} }
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) { function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj, byHover) {
if (!screen) if (!screen)
return; return;
if (currentTrayMenu) { if (currentTrayMenu) {
@@ -2099,7 +2101,7 @@ BasePill {
currentTrayMenu = trayMenuComponent.createObject(null); currentTrayMenu = trayMenuComponent.createObject(null);
if (!currentTrayMenu) if (!currentTrayMenu)
return; return;
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj); currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj, byHover === true);
} }
function _trayLayoutRoot() { function _trayLayoutRoot() {
@@ -2147,7 +2149,7 @@ BasePill {
if (!hit?.trayItem?.hasMenu) if (!hit?.trayItem?.hasMenu)
return false; return false;
const anchor = hit.children?.length > 0 ? hit.children[0] : hit; const anchor = hit.children?.length > 0 ? hit.children[0] : hit;
showForTrayItem(hit.trayItem, anchor, parentScreen, isAtBottom, isVerticalOrientation, axis); showForTrayItem(hit.trayItem, anchor, parentScreen, isAtBottom, isVerticalOrientation, axis, true);
return true; return true;
} }
} }
@@ -18,6 +18,8 @@ Column {
id: sharedTooltip id: sharedTooltip
} }
Component.onDestruction: sharedTooltip.hide()
signal itemEnabledChanged(string sectionId, string itemId, bool enabled) signal itemEnabledChanged(string sectionId, string itemId, bool enabled)
signal itemOrderChanged(string sectionId, var orderedIds) signal itemOrderChanged(string sectionId, var orderedIds)
signal addWidget(string sectionId) signal addWidget(string sectionId)
@@ -1215,6 +1217,7 @@ Column {
iconSize: 18 iconSize: 18
iconColor: Theme.error iconColor: Theme.error
onClicked: { onClicked: {
sharedTooltip.hide();
root.removeWidget(root.sectionId, index); root.removeWidget(root.sectionId, index);
} }
onEntered: { onEntered: {
+25 -4
View File
@@ -143,7 +143,7 @@ Singleton {
function sendAlert(title, message, isWarning, category, notificationType) { function sendAlert(title, message, isWarning, category, notificationType) {
if (notificationType === 1) { if (notificationType === 1) {
Quickshell.execDetached(["notify-send", "-u", isWarning ? "critical" : "normal", "-a", "DMS", "-i", isWarning ? "battery-caution" : "battery-charging", title, message]); Quickshell.execDetached(["notify-send", "-u", isWarning ? "critical" : "normal", "-a", "DMS", "-i", isWarning ? "material:battery_alert" : "material:battery_charging_full", title, message]);
} else { } else {
if (isWarning) { if (isWarning) {
ToastService.showWarning(title, message, "", category); ToastService.showWarning(title, message, "", category);
@@ -157,7 +157,7 @@ Singleton {
if (isCharging && batteryLevel >= SettingsData.batteryChargeLimit) { if (isCharging && batteryLevel >= SettingsData.batteryChargeLimit) {
if (!_hasNotifiedChargeLimit && SettingsData.batteryNotifyChargeLimit) { if (!_hasNotifiedChargeLimit && SettingsData.batteryNotifyChargeLimit) {
_hasNotifiedChargeLimit = true; _hasNotifiedChargeLimit = true;
sendAlert(I18n.tr("Charge Limit Reached"), I18n.tr("Battery has charged to your set limit of %1%").arg(SettingsData.batteryChargeLimit), false, "battery-charge-limit", SettingsData.batteryChargeLimitNotificationType); sendAlert(I18n.tr("Charge Limit Reached"), I18n.tr("Battery has charged to your set limit of %1%").arg(SettingsData.batteryChargeLimit), false, "material:battery_profile", SettingsData.batteryChargeLimitNotificationType);
} }
} else if (!isCharging || batteryLevel < SettingsData.batteryChargeLimit - 2) { } else if (!isCharging || batteryLevel < SettingsData.batteryChargeLimit - 2) {
_hasNotifiedChargeLimit = false; _hasNotifiedChargeLimit = false;
@@ -173,7 +173,7 @@ Singleton {
if (isCriticalBattery) { if (isCriticalBattery) {
if (!_hasNotifiedCriticalBattery && SettingsData.batteryNotifyCritical) { if (!_hasNotifiedCriticalBattery && SettingsData.batteryNotifyCritical) {
_hasNotifiedCriticalBattery = true; _hasNotifiedCriticalBattery = true;
sendAlert(I18n.tr("Critical Battery"), I18n.tr("Battery is at %1% - Connect charger immediately!").arg(batteryLevel), true, "battery-critical", SettingsData.batteryCriticalNotificationType); sendAlert(I18n.tr("Critical Battery"), I18n.tr("Battery is at %1% - Connect charger immediately!").arg(batteryLevel), true, "material:battery_alert", SettingsData.batteryCriticalNotificationType);
} }
return; return;
} }
@@ -186,7 +186,7 @@ Singleton {
if (isLowBattery) { if (isLowBattery) {
if (!_hasNotifiedLowBattery && SettingsData.batteryNotifyLow) { if (!_hasNotifiedLowBattery && SettingsData.batteryNotifyLow) {
_hasNotifiedLowBattery = true; _hasNotifiedLowBattery = true;
sendAlert(I18n.tr("Low Battery"), I18n.tr("Battery is at %1% - Consider charging soon").arg(batteryLevel), true, "battery-low", SettingsData.batteryLowNotificationType); sendAlert(I18n.tr("Low Battery"), I18n.tr("Battery is at %1% - Consider charging soon").arg(batteryLevel), true, "material:battery_0_bar", SettingsData.batteryLowNotificationType);
} }
if (SettingsData.batteryAutoPowerSaver && PowerProfileWatcher.available) { if (SettingsData.batteryAutoPowerSaver && PowerProfileWatcher.available) {
@@ -230,6 +230,27 @@ Singleton {
applyPowerProfile(); applyPowerProfile();
if (isPluggedIn) {
const dismissLow = SettingsData.batteryLowNotificationType === 1 && SettingsData.notificationTimeoutNormal === 0;
const dismissCritical = SettingsData.batteryCriticalNotificationType === 1 && SettingsData.notificationTimeoutCritical === 0;
if (dismissLow || dismissCritical) {
const lowSummary = I18n.tr("Low Battery");
const criticalSummary = I18n.tr("Critical Battery");
for (const w of NotificationService.visibleNotifications) {
if (!w || !w.notification)
continue;
const summary = w.notification.summary;
if ((dismissLow && summary === lowSummary) || (dismissCritical && summary === criticalSummary)) {
NotificationService.dismissNotification(w);
}
}
}
}
previousPluggedState = isPluggedIn; previousPluggedState = isPluggedIn;
} }
+2
View File
@@ -388,6 +388,8 @@ Singleton {
const binds = bindsData[cat]; const binds = bindsData[cat];
for (var i = 0; i < binds.length; i++) { for (var i = 0; i < binds.length; i++) {
const bind = binds[i]; const bind = binds[i];
if (currentProvider === "hyprland" && bind.action && bind.action.startsWith("exec "))
bind.action = "spawn " + bind.action.slice(5);
const targetCat = Actions.isDmsAction(bind.action) ? "DMS" : cat; const targetCat = Actions.isDmsAction(bind.action) ? "DMS" : cat;
if (!processed[targetCat]) if (!processed[targetCat])
processed[targetCat] = []; processed[targetCat] = [];
+1 -1
View File
@@ -168,7 +168,7 @@ Singleton {
} }
function isIdle(player: MprisPlayer): bool { function isIdle(player: MprisPlayer): bool {
return player && player.playbackState === MprisPlaybackState.Stopped && !player.trackTitle && !player.trackArtist; return player && player.playbackState === MprisPlaybackState.Stopped;
} }
// Known "<title> | <App>" suffixes stripped for matching only; display keeps the full title // Known "<title> | <App>" suffixes stripped for matching only; display keeps the full title
+3 -1
View File
@@ -105,9 +105,11 @@ PanelWindow {
readonly property bool slideoutBlurActive: root.visible && BlurService.enabled && Theme.connectedSurfaceBlurEnabled readonly property bool slideoutBlurActive: root.visible && BlurService.enabled && Theme.connectedSurfaceBlurEnabled
readonly property string _slideoutScreenName: modelData?.name ?? ""
WlrLayershell.layer: (!suppressOverlayLayer && (triggerUsesOverlayLayer || CompositorService.framePeerSurfacesUseOverlayForScreen(modelData))) ? WlrLayershell.Overlay : WlrLayershell.Top WlrLayershell.layer: (!suppressOverlayLayer && (triggerUsesOverlayLayer || CompositorService.framePeerSurfacesUseOverlayForScreen(modelData))) ? WlrLayershell.Overlay : WlrLayershell.Top
WlrLayershell.exclusiveZone: 0 WlrLayershell.exclusiveZone: 0
WlrLayershell.keyboardFocus: isVisible ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None WlrLayershell.keyboardFocus: isVisible && !ModalManager.currentModalsByScreen[_slideoutScreenName] ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
readonly property real dpr: CompositorService.getScreenScale(root.screen) readonly property real dpr: CompositorService.getScreenScale(root.screen)
readonly property real alignedWidth: Theme.px(expandable && expandedWidth ? expandedWidthValue : slideoutWidth, dpr) readonly property real alignedWidth: Theme.px(expandable && expandedWidth ? expandedWidthValue : slideoutWidth, dpr)
+1 -1
View File
@@ -869,7 +869,7 @@ Item {
readonly property var tooltipTexts: ({ readonly property var tooltipTexts: ({
"dms": I18n.tr("DMS shell actions (launcher, clipboard, etc.)"), "dms": I18n.tr("DMS shell actions (launcher, clipboard, etc.)"),
"compositor": I18n.tr("Niri compositor actions (focus, move, etc.)"), "compositor": I18n.tr("Compositor actions (focus, move, etc.)", "keybind action type tooltip"),
"spawn": I18n.tr("Run a program (e.g., firefox, kitty)"), "spawn": I18n.tr("Run a program (e.g., firefox, kitty)"),
"shell": I18n.tr("Run a shell command (e.g., notify-send)") "shell": I18n.tr("Run a shell command (e.g., notify-send)")
}) })