1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-01 19:18:28 -04:00

Compare commits

..

5 Commits

Author SHA1 Message Date
bbedward 64461c534f qs/socket: improve resilience of socket detection and connection
related #2369
port 1.5
2026-07-27 13:40:34 -04:00
bbedward 73da4879f6 frame: consolidate and simplify inset logic 2026-07-27 13:10:47 -04:00
euletheia 8594a414ec fix(BatteryService): fix typo in notification check (#2944) 2026-07-27 11:33:04 -04:00
euletheia df396bfa43 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.
2026-07-27 11:13:47 -04:00
euletheia 33677150b1 feat(ipc): add 'dismiss' function to notifications ipc (#2942)
Allow to dismiss the last visible notification only instead of resorting
directly to dismissAllPopups.
2026-07-27 11:13:07 -04:00
12 changed files with 108 additions and 80 deletions
+12 -4
View File
@@ -2556,11 +2556,19 @@ Singleton {
return edges;
}
function frameEdgeInsetForSide(screen, side) {
if (!frameEnabled || !screen)
readonly property real frameBarContentGap: frameBarInsetPadding < 0 ? frameThickness : frameBarInsetPadding
readonly property real frameBarContentGapExtra: Math.max(0, frameBarContentGap - frameThickness)
function frameEdgeReservation(screen, edge) {
if (!screen)
return 0;
const edges = getActiveBarEdgesForScreen(screen);
return edges.includes(side) ? frameBarSize : frameThickness;
return getActiveBarEdgesForScreen(screen).includes(edge) ? frameBarSize : frameThickness;
}
function frameEdgeInsetForSide(screen, side) {
if (!frameEnabled)
return 0;
return frameEdgeReservation(screen, side);
}
function setMatugenScheme(scheme) {
+9
View File
@@ -64,6 +64,10 @@ DankModal {
NotificationService.dismissAllPopups();
}
function dismissLastNotification() {
NotificationService.dismissLastNotification();
}
modalWidth: Math.min(500, screenWidth - 48)
modalHeight: Math.min(700, screenHeight * 0.85)
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
@@ -190,6 +194,11 @@ DankModal {
return "NOTIFICATION_MODAL_DISMISS_ALL_POPUPS_SUCCESS";
}
function dismiss(): string {
notificationModal.dismissLastNotification();
return "NOTIFICATION_DISMISS_SUCCESS";
}
target: "notifications"
}
@@ -38,11 +38,10 @@ Item {
readonly property real _barInsetPaddingRaw: SettingsData.barInsetPaddingSyncAll ? SettingsData.barInsetPaddingShared : (barConfig?.barInsetPadding ?? -1)
readonly property real _barInsetPaddingAuto: _barIsVertical ? Theme.spacingXS : _edgeBaseMargin
readonly property real _barInsetPadding: _barInsetPaddingRaw < 0 ? _barInsetPaddingAuto : _barInsetPaddingRaw
// Connected-frame Bar Inset Padding: absolute free-end gap the hosted bar spans full-width into
// (auto < 0 = frameThickness so widgets align with the interior cutout, 0 = edge-to-edge). The extra
// beyond frameThickness is what an adjacent bar end adds on top of its corner alignment.
readonly property real _frameInsetResolved: SettingsData.frameBarInsetPadding < 0 ? SettingsData.frameThickness : SettingsData.frameBarInsetPadding
readonly property real _frameInsetExtra: Math.max(0, _frameInsetResolved - SettingsData.frameThickness)
// Hosted bars span their edge fully; frameBarContentGap is the free-end gap measured from the
// screen edge (auto = frameThickness, aligning widgets with the interior cutout).
readonly property real _frameInsetResolved: SettingsData.frameBarContentGap
readonly property real _frameInsetExtra: SettingsData.frameBarContentGapExtra
// Horizontal bars span the full width and own the corners; the perpendicular vertical bar
// tucks in below/above. Where they meet, inset the corner widget so it centres in the
+1 -2
View File
@@ -21,11 +21,10 @@ QtObject {
readonly property bool frameExclusionActive: CompositorService.frameWindowVisibleForScreen(screen)
readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(screen)
readonly property bool connectedBarActiveOnEdge: usesConnectedFrameChrome && !!screen && SettingsData.getActiveBarEdgesForScreen(screen).includes(edge)
readonly property real connectedJoinInset: {
if (usesConnectedFrameChrome)
return connectedBarActiveOnEdge ? SettingsData.frameBarSize : SettingsData.frameThickness;
return SettingsData.frameEdgeReservation(screen, edge);
if (frameExclusionActive)
return SettingsData.frameEdgeInsetForSide(screen, edge);
return 0;
+1 -1
View File
@@ -34,7 +34,7 @@ Scope {
}
function exclusionSizeForEdge(edge) {
return root.barEdges.includes(edge) ? SettingsData.frameBarSize : SettingsData.frameThickness;
return SettingsData.frameEdgeReservation(root.screen, edge);
}
Loader {
+16 -4
View File
@@ -262,10 +262,22 @@ PanelWindow {
return false;
}
readonly property int cutoutTopInset: win._regionInt(barEdges.includes("top") ? SettingsData.frameBarSize : SettingsData.frameThickness)
readonly property int cutoutBottomInset: win._regionInt(barEdges.includes("bottom") ? SettingsData.frameBarSize : SettingsData.frameThickness)
readonly property int cutoutLeftInset: win._regionInt(barEdges.includes("left") ? SettingsData.frameBarSize : SettingsData.frameThickness)
readonly property int cutoutRightInset: win._regionInt(barEdges.includes("right") ? SettingsData.frameBarSize : SettingsData.frameThickness)
readonly property int cutoutTopInset: {
SettingsData.barConfigs;
return win._regionInt(SettingsData.frameEdgeReservation(win.targetScreen, "top"));
}
readonly property int cutoutBottomInset: {
SettingsData.barConfigs;
return win._regionInt(SettingsData.frameEdgeReservation(win.targetScreen, "bottom"));
}
readonly property int cutoutLeftInset: {
SettingsData.barConfigs;
return win._regionInt(SettingsData.frameEdgeReservation(win.targetScreen, "left"));
}
readonly property int cutoutRightInset: {
SettingsData.barConfigs;
return win._regionInt(SettingsData.frameEdgeReservation(win.targetScreen, "right"));
}
readonly property int cutoutWidth: Math.max(0, win._windowRegionWidth - win.cutoutLeftInset - win.cutoutRightInset)
readonly property int cutoutHeight: Math.max(0, win._windowRegionHeight - win.cutoutTopInset - win.cutoutBottomInset)
readonly property int cutoutRadius: {
@@ -360,8 +360,7 @@ QtObject {
function _frameEdgeInset(side) {
if (!manager.modelData)
return 0;
const edges = SettingsData.getActiveBarEdgesForScreen(manager.modelData);
const raw = edges.includes(side) ? SettingsData.frameBarSize : SettingsData.frameThickness;
const raw = SettingsData.frameEdgeReservation(manager.modelData, side);
const dpr = CompositorService.getScreenScale(manager.modelData);
return Math.max(0, Math.round(Theme.px(raw, dpr)));
}
+1 -1
View File
@@ -13,7 +13,7 @@ Item {
LayoutMirroring.childrenInherit: true
// Bar Inset Padding: resolve the "auto" sentinel (stored < 0) to the frame thickness for the slider display.
readonly property int frameInsetPaddingDisplay: SettingsData.frameBarInsetPadding < 0 ? Math.round(SettingsData.frameThickness) : SettingsData.frameBarInsetPadding
readonly property int frameInsetPaddingDisplay: Math.round(SettingsData.frameBarContentGap)
DankFlickable {
anchors.fill: parent
+25 -4
View File
@@ -136,7 +136,7 @@ Singleton {
function sendAlert(title, message, isWarning, category, notificationType) {
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 {
if (isWarning) {
ToastService.showWarning(title, message, "", category);
@@ -150,7 +150,7 @@ Singleton {
if (isCharging && batteryLevel >= SettingsData.batteryChargeLimit) {
if (!_hasNotifiedChargeLimit && SettingsData.batteryNotifyChargeLimit) {
_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) {
_hasNotifiedChargeLimit = false;
@@ -166,7 +166,7 @@ Singleton {
if (isCriticalBattery) {
if (!_hasNotifiedCriticalBattery && SettingsData.batteryNotifyCritical) {
_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;
}
@@ -179,7 +179,7 @@ Singleton {
if (isLowBattery) {
if (!_hasNotifiedLowBattery && SettingsData.batteryNotifyLow) {
_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) {
@@ -223,6 +223,27 @@ Singleton {
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;
}
+30 -53
View File
@@ -11,7 +11,7 @@ Singleton {
id: root
readonly property var log: Log.scoped("DMSService")
property bool dmsAvailable: false
readonly property bool dmsAvailable: isConnected
property var capabilities: []
property int apiVersion: 0
property string cliVersion: ""
@@ -21,7 +21,7 @@ Singleton {
property var availableThemes: []
property var installedThemes: []
property bool isConnected: false
property bool isConnecting: false
readonly property bool isConnecting: requestSocket.connected && !requestSocket.linkUp
property bool subscribeConnected: false
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
@@ -72,9 +72,10 @@ Singleton {
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "wallpaper", "bluetooth", "bluetooth.pairing", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "sysupdate"]
Component.onCompleted: {
if (socketPath && socketPath.length > 0) {
detectUpdateCommand();
}
if (!socketPath || socketPath.length === 0)
return;
detectUpdateCommand();
requestSocket.connected = true;
}
function detectUpdateCommand() {
@@ -82,12 +83,6 @@ Singleton {
checkAurHelper.running = true;
}
function startSocketConnection() {
if (socketPath && socketPath.length > 0) {
testProcess.running = true;
}
}
Process {
id: checkAurHelper
command: ["sh", "-c", "command -v paru || command -v yay"]
@@ -105,7 +100,6 @@ Singleton {
} else {
updateCommand = "dms update";
checkingUpdateCommand = false;
startSocketConnection();
}
}
}
@@ -114,7 +108,6 @@ Singleton {
if (exitCode !== 0) {
updateCommand = "dms update";
checkingUpdateCommand = false;
startSocketConnection();
}
}
}
@@ -135,7 +128,6 @@ Singleton {
updateCommand = "dms update";
}
checkingUpdateCommand = false;
startSocketConnection();
}
}
@@ -143,52 +135,27 @@ Singleton {
if (exitCode !== 0) {
updateCommand = "dms update";
checkingUpdateCommand = false;
startSocketConnection();
}
}
}
Process {
id: testProcess
command: ["test", "-S", root.socketPath]
onExited: exitCode => {
if (exitCode === 0) {
root.dmsAvailable = true;
connectSocket();
} else {
root.dmsAvailable = false;
}
}
}
function connectSocket() {
if (!dmsAvailable || isConnected || isConnecting) {
return;
}
isConnecting = true;
requestSocket.connected = true;
}
DankSocket {
id: requestSocket
path: root.socketPath
connected: false
onConnectionStateChanged: {
if (connected) {
if (linkUp) {
root.isConnected = true;
root.isConnecting = false;
root.connectionStateChanged();
subscribeSocket.connected = true;
} else {
root.isConnected = false;
root.isConnecting = false;
root.apiVersion = 0;
root.capabilities = [];
root.connectionStateChanged();
return;
}
root.isConnected = false;
root.apiVersion = 0;
root.capabilities = [];
root.failPendingRequests();
root.connectionStateChanged();
}
parser: SplitParser {
@@ -219,10 +186,10 @@ Singleton {
connected: false
onConnectionStateChanged: {
root.subscribeConnected = connected;
if (connected) {
sendSubscribeRequest();
}
root.subscribeConnected = linkUp;
if (!linkUp)
return;
sendSubscribeRequest();
}
parser: SplitParser {
@@ -432,10 +399,20 @@ Singleton {
function handleResponse(response) {
const callback = pendingRequests[response.id];
if (!callback)
return;
delete pendingRequests[response.id];
callback(response);
}
if (callback) {
delete pendingRequests[response.id];
callback(response);
function failPendingRequests() {
const pending = pendingRequests;
pendingRequests = {};
clipboardRequestIds = {};
for (const id in pending) {
pending[id]({
"error": "not connected to DMS socket"
});
}
}
@@ -889,6 +889,12 @@ Singleton {
NotifWrapper {}
}
function dismissLastNotification() {
const w = visibleNotifications[visibleNotifications.length - 1];
if (w)
dismissNotification(w);
}
function dismissAllPopups() {
for (const w of visibleNotifications) {
if (w) {
+2 -4
View File
@@ -584,11 +584,9 @@ Item {
}
function _frameEdgeInset(side) {
if (!root.frameOwnsConnectedChrome || !root.screen)
if (!root.frameOwnsConnectedChrome)
return 0;
const edges = SettingsData.getActiveBarEdgesForScreen(root.screen);
const raw = edges.includes(side) ? SettingsData.frameBarSize : SettingsData.frameThickness;
return Math.max(0, raw);
return Math.max(0, SettingsData.frameEdgeReservation(root.screen, side));
}
function _edgeGapFor(side, popupGap) {