1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-28 23:42:51 -05:00

notifications: attempt to minimize rapid window creation/destruction

This commit is contained in:
bbedward
2025-12-17 16:10:25 -05:00
parent d97392d46e
commit d385a44949
2 changed files with 182 additions and 119 deletions

View File

@@ -175,7 +175,7 @@ PanelWindow {
function getLeftMargin() { function getLeftMargin() {
if (isTopCenter) if (isTopCenter)
return (screen.width - implicitWidth) / 2; return screen ? (screen.width - implicitWidth) / 2 : 0;
const popupPos = SettingsData.notificationPopupPosition; const popupPos = SettingsData.notificationPopupPosition;
const isLeft = popupPos === SettingsData.Position.Left || popupPos === SettingsData.Position.Bottom; const isLeft = popupPos === SettingsData.Position.Left || popupPos === SettingsData.Position.Bottom;
@@ -199,7 +199,8 @@ PanelWindow {
return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance; return barInfo.rightBar > 0 ? barInfo.rightBar : Theme.popupDistance;
} }
readonly property real dpr: CompositorService.getScreenScale(win.screen) readonly property bool screenValid: win.screen && !_isDestroying
readonly property real dpr: screenValid ? CompositorService.getScreenScale(win.screen) : 1
readonly property real alignedWidth: Theme.px(implicitWidth, dpr) readonly property real alignedWidth: Theme.px(implicitWidth, dpr)
readonly property real alignedHeight: Theme.px(implicitHeight, dpr) readonly property real alignedHeight: Theme.px(implicitHeight, dpr)
@@ -227,7 +228,7 @@ PanelWindow {
id: bgShadowLayer id: bgShadowLayer
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.snap(4, win.dpr) anchors.margins: Theme.snap(4, win.dpr)
layer.enabled: true layer.enabled: !win._isDestroying && win.screenValid
layer.smooth: false layer.smooth: false
layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr)) layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically layer.textureMirroring: ShaderEffectSource.MirrorVertically

View File

@@ -1,6 +1,4 @@
import QtQuick import QtQuick
import Quickshell
import qs.Common
import qs.Services import qs.Services
QtObject { QtObject {
@@ -12,6 +10,11 @@ QtObject {
property int maxTargetNotifications: 4 property int maxTargetNotifications: 4
property var popupWindows: [] // strong refs to windows (live until exitFinished) property var popupWindows: [] // strong refs to windows (live until exitFinished)
property var destroyingWindows: new Set() property var destroyingWindows: new Set()
property var pendingDestroys: []
property int destroyDelayMs: 100
property var pendingCreates: []
property int createDelayMs: 50
property bool createBusy: false
property Component popupComponent property Component popupComponent
popupComponent: Component { popupComponent: Component {
@@ -25,7 +28,7 @@ QtObject {
notificationConnections: Connections { notificationConnections: Connections {
function onVisibleNotificationsChanged() { function onVisibleNotificationsChanged() {
manager._sync(NotificationService.visibleNotifications) manager._sync(NotificationService.visibleNotifications);
} }
target: NotificationService target: NotificationService
@@ -33,239 +36,298 @@ QtObject {
property Timer sweeper property Timer sweeper
property Timer destroyTimer: Timer {
interval: destroyDelayMs
running: false
repeat: false
onTriggered: manager._processDestroyQueue()
}
function _processDestroyQueue() {
if (pendingDestroys.length === 0)
return;
const p = pendingDestroys.shift();
if (p && p.destroy) {
try {
p.destroy();
} catch (e) {}
}
if (pendingDestroys.length > 0)
destroyTimer.restart();
}
function _scheduleDestroy(p) {
if (!p)
return;
pendingDestroys.push(p);
if (!destroyTimer.running)
destroyTimer.restart();
}
property Timer createTimer: Timer {
interval: createDelayMs
running: false
repeat: false
onTriggered: manager._processCreateQueue()
}
function _processCreateQueue() {
createBusy = false;
if (pendingCreates.length === 0)
return;
const wrapper = pendingCreates.shift();
if (wrapper)
_doInsertNewestAtTop(wrapper);
if (pendingCreates.length > 0) {
createBusy = true;
createTimer.restart();
}
}
function _scheduleCreate(wrapper) {
if (!wrapper)
return;
pendingCreates.push(wrapper);
if (!createBusy) {
createBusy = true;
createTimer.restart();
}
}
sweeper: Timer { sweeper: Timer {
interval: 500 interval: 500
running: false running: false
repeat: true repeat: true
onTriggered: { onTriggered: {
const toRemove = [] const toRemove = [];
for (const p of popupWindows) { for (const p of popupWindows) {
if (!p) { if (!p) {
toRemove.push(p) toRemove.push(p);
continue continue;
} }
const isZombie = p.status === Component.Null || (!p.visible && !p.exiting) || (!p.notificationData && !p._isDestroying) || (!p.hasValidData && !p._isDestroying) const isZombie = p.status === Component.Null || (!p.visible && !p.exiting) || (!p.notificationData && !p._isDestroying) || (!p.hasValidData && !p._isDestroying);
if (isZombie) { if (isZombie) {
toRemove.push(p) toRemove.push(p);
if (p.forceExit) { if (p.forceExit) {
p.forceExit() p.forceExit();
} else if (p.destroy) { } else if (p.destroy) {
try { try {
p.destroy() p.destroy();
} catch (e) { } catch (e) {}
}
} }
} }
} }
if (toRemove.length) { if (toRemove.length) {
popupWindows = popupWindows.filter(p => toRemove.indexOf(p) === -1) popupWindows = popupWindows.filter(p => toRemove.indexOf(p) === -1);
const survivors = _active().sort((a, b) => a.screenY - b.screenY) const survivors = _active().sort((a, b) => a.screenY - b.screenY);
for (let k = 0; k < survivors.length; ++k) { for (let k = 0; k < survivors.length; ++k) {
survivors[k].screenY = topMargin + k * baseNotificationHeight survivors[k].screenY = topMargin + k * baseNotificationHeight;
} }
} }
if (popupWindows.length === 0) { if (popupWindows.length === 0) {
sweeper.stop() sweeper.stop();
} }
} }
} }
function _hasWindowFor(w) { function _hasWindowFor(w) {
return popupWindows.some(p => p && p.notificationData === w && !p._isDestroying && p.status !== Component.Null) return popupWindows.some(p => p && p.notificationData === w && !p._isDestroying && p.status !== Component.Null);
} }
function _isValidWindow(p) { function _isValidWindow(p) {
return p && p.status !== Component.Null && !p._isDestroying && p.hasValidData return p && p.status !== Component.Null && !p._isDestroying && p.hasValidData;
} }
function _canMakeRoomFor(wrapper) { function _canMakeRoomFor(wrapper) {
const activeWindows = _active() const activeWindows = _active();
if (activeWindows.length < maxTargetNotifications) { if (activeWindows.length < maxTargetNotifications) {
return true return true;
} }
if (!wrapper || !wrapper.notification) { if (!wrapper || !wrapper.notification) {
return false return false;
} }
const incomingUrgency = wrapper.notification.urgency || 0 const incomingUrgency = wrapper.notification.urgency || 0;
for (const p of activeWindows) { for (const p of activeWindows) {
if (!p.notificationData || !p.notificationData.notification) { if (!p.notificationData || !p.notificationData.notification) {
continue continue;
} }
const existingUrgency = p.notificationData.notification.urgency || 0 const existingUrgency = p.notificationData.notification.urgency || 0;
if (existingUrgency < incomingUrgency) { if (existingUrgency < incomingUrgency) {
return true return true;
} }
if (existingUrgency === incomingUrgency) { if (existingUrgency === incomingUrgency) {
const timer = p.notificationData.timer const timer = p.notificationData.timer;
if (timer && !timer.running) { if (timer && !timer.running) {
return true return true;
} }
} }
} }
return false return false;
} }
function _makeRoomForNew(wrapper) { function _makeRoomForNew(wrapper) {
const activeWindows = _active() const activeWindows = _active();
if (activeWindows.length < maxTargetNotifications) { if (activeWindows.length < maxTargetNotifications) {
return return;
} }
const toRemove = _selectPopupToRemove(activeWindows, wrapper) const toRemove = _selectPopupToRemove(activeWindows, wrapper);
if (toRemove && !toRemove.exiting) { if (toRemove && !toRemove.exiting) {
toRemove.notificationData.removedByLimit = true toRemove.notificationData.removedByLimit = true;
toRemove.notificationData.popup = false toRemove.notificationData.popup = false;
if (toRemove.notificationData.timer) { if (toRemove.notificationData.timer) {
toRemove.notificationData.timer.stop() toRemove.notificationData.timer.stop();
} }
} }
} }
function _selectPopupToRemove(activeWindows, incomingWrapper) { function _selectPopupToRemove(activeWindows, incomingWrapper) {
const incomingUrgency = (incomingWrapper && incomingWrapper.notification) ? incomingWrapper.notification.urgency || 0 : 0 const incomingUrgency = (incomingWrapper && incomingWrapper.notification) ? incomingWrapper.notification.urgency || 0 : 0;
const sortedWindows = activeWindows.slice().sort((a, b) => { const sortedWindows = activeWindows.slice().sort((a, b) => {
const aUrgency = (a.notificationData && a.notificationData.notification) ? a.notificationData.notification.urgency || 0 : 0 const aUrgency = (a.notificationData && a.notificationData.notification) ? a.notificationData.notification.urgency || 0 : 0;
const bUrgency = (b.notificationData && b.notificationData.notification) ? b.notificationData.notification.urgency || 0 : 0 const bUrgency = (b.notificationData && b.notificationData.notification) ? b.notificationData.notification.urgency || 0 : 0;
if (aUrgency !== bUrgency) { if (aUrgency !== bUrgency) {
return aUrgency - bUrgency return aUrgency - bUrgency;
} }
const aTimer = a.notificationData && a.notificationData.timer const aTimer = a.notificationData && a.notificationData.timer;
const bTimer = b.notificationData && b.notificationData.timer const bTimer = b.notificationData && b.notificationData.timer;
const aRunning = aTimer && aTimer.running const aRunning = aTimer && aTimer.running;
const bRunning = bTimer && bTimer.running const bRunning = bTimer && bTimer.running;
if (aRunning !== bRunning) { if (aRunning !== bRunning) {
return aRunning ? 1 : -1 return aRunning ? 1 : -1;
} }
return b.screenY - a.screenY return b.screenY - a.screenY;
}) });
return sortedWindows[0] return sortedWindows[0];
} }
function _sync(newWrappers) { function _sync(newWrappers) {
for (const w of newWrappers) { for (const w of newWrappers) {
if (w && !_hasWindowFor(w)) { if (w && !_hasWindowFor(w)) {
insertNewestAtTop(w) insertNewestAtTop(w);
} }
} }
for (const p of popupWindows.slice()) { for (const p of popupWindows.slice()) {
if (!_isValidWindow(p)) { if (!_isValidWindow(p)) {
continue continue;
} }
if (p.notificationData && newWrappers.indexOf(p.notificationData) === -1 && !p.exiting) { if (p.notificationData && newWrappers.indexOf(p.notificationData) === -1 && !p.exiting) {
p.notificationData.removedByLimit = true p.notificationData.removedByLimit = true;
p.notificationData.popup = false p.notificationData.popup = false;
} }
} }
} }
function insertNewestAtTop(wrapper) { function insertNewestAtTop(wrapper) {
if (!wrapper) { if (!wrapper)
return return;
if (createBusy || pendingCreates.length > 0) {
_scheduleCreate(wrapper);
return;
} }
_doInsertNewestAtTop(wrapper);
}
function _doInsertNewestAtTop(wrapper) {
if (!wrapper)
return;
for (const p of popupWindows) { for (const p of popupWindows) {
if (!_isValidWindow(p)) { if (!_isValidWindow(p))
continue continue;
} if (p.exiting)
if (p.exiting) { continue;
continue p.screenY = p.screenY + baseNotificationHeight;
}
p.screenY = p.screenY + baseNotificationHeight
} }
const notificationId = wrapper && wrapper.notification ? wrapper.notification.id : "" const notificationId = wrapper && wrapper.notification ? wrapper.notification.id : "";
const win = popupComponent.createObject(null, { const win = popupComponent.createObject(null, {
"notificationData": wrapper, "notificationData": wrapper,
"notificationId": notificationId, "notificationId": notificationId,
"screenY": topMargin, "screenY": topMargin,
"screen": manager.modelData "screen": manager.modelData
}) });
if (!win) { if (!win)
return return;
}
if (!win.hasValidData) { if (!win.hasValidData) {
win.destroy() win.destroy();
return return;
}
popupWindows.push(win)
if (!sweeper.running) {
sweeper.start()
} }
popupWindows.push(win);
createBusy = true;
createTimer.restart();
if (!sweeper.running)
sweeper.start();
} }
function _active() { function _active() {
return popupWindows.filter(p => _isValidWindow(p) && p.notificationData && p.notificationData.popup && !p.exiting) return popupWindows.filter(p => _isValidWindow(p) && p.notificationData && p.notificationData.popup && !p.exiting);
} }
function _bottom() { function _bottom() {
let b = null let b = null;
let maxY = -1 let maxY = -1;
for (const p of _active()) { for (const p of _active()) {
if (p.screenY > maxY) { if (p.screenY > maxY) {
maxY = p.screenY maxY = p.screenY;
b = p b = p;
} }
} }
return b return b;
} }
function _onPopupEntered(p) {} function _onPopupEntered(p) {
}
function _onPopupExitFinished(p) { function _onPopupExitFinished(p) {
if (!p) { if (!p) {
return return;
} }
const windowId = p.toString() const windowId = p.toString();
if (destroyingWindows.has(windowId)) { if (destroyingWindows.has(windowId)) {
return return;
} }
destroyingWindows.add(windowId) destroyingWindows.add(windowId);
const i = popupWindows.indexOf(p) const i = popupWindows.indexOf(p);
if (i !== -1) { if (i !== -1) {
popupWindows.splice(i, 1) popupWindows.splice(i, 1);
popupWindows = popupWindows.slice() popupWindows = popupWindows.slice();
} }
if (NotificationService.releaseWrapper && p.notificationData) { if (NotificationService.releaseWrapper && p.notificationData) {
NotificationService.releaseWrapper(p.notificationData) NotificationService.releaseWrapper(p.notificationData);
} }
Qt.callLater(() => { _scheduleDestroy(p);
if (p && p.destroy) { Qt.callLater(() => destroyingWindows.delete(windowId));
try { const survivors = _active().sort((a, b) => a.screenY - b.screenY);
p.destroy()
} catch (e) {
}
}
Qt.callLater(() => destroyingWindows.delete(windowId))
})
const survivors = _active().sort((a, b) => a.screenY - b.screenY)
for (let k = 0; k < survivors.length; ++k) { for (let k = 0; k < survivors.length; ++k) {
survivors[k].screenY = topMargin + k * baseNotificationHeight survivors[k].screenY = topMargin + k * baseNotificationHeight;
} }
} }
function cleanupAllWindows() { function cleanupAllWindows() {
sweeper.stop() sweeper.stop();
destroyTimer.stop();
createTimer.stop();
pendingDestroys = [];
pendingCreates = [];
createBusy = false;
for (const p of popupWindows.slice()) { for (const p of popupWindows.slice()) {
if (p) { if (p) {
try { try {
if (p.forceExit) { if (p.forceExit) {
p.forceExit() p.forceExit();
} else if (p.destroy) { } else if (p.destroy) {
p.destroy() p.destroy();
} }
} catch (e) { } catch (e) {}
}
} }
} }
popupWindows = [] popupWindows = [];
destroyingWindows.clear() destroyingWindows.clear();
} }
onPopupWindowsChanged: { onPopupWindowsChanged: {
if (popupWindows.length > 0 && !sweeper.running) { if (popupWindows.length > 0 && !sweeper.running) {
sweeper.start() sweeper.start();
} else if (popupWindows.length === 0 && sweeper.running) { } else if (popupWindows.length === 0 && sweeper.running) {
sweeper.stop() sweeper.stop();
} }
} }
} }