1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 13:32:50 -05:00

settings: optimize mem usage

- keep un-loaded unless called upon
This commit is contained in:
bbedward
2025-11-29 18:32:45 -05:00
parent e6c3ae9397
commit b11b375848
8 changed files with 400 additions and 335 deletions

View File

@@ -360,11 +360,33 @@ Item {
} }
} }
SettingsModal { LazyLoader {
id: settingsModal id: settingsModalLoader
active: false
Component.onCompleted: { Component.onCompleted: {
PopoutService.settingsModal = settingsModal; PopoutService.settingsModalLoader = settingsModalLoader;
}
onActiveChanged: {
if (active && item) {
PopoutService.settingsModal = item;
PopoutService._onSettingsModalLoaded();
}
}
SettingsModal {
id: settingsModal
property bool wasShown: false
onVisibleChanged: {
if (visible) {
wasShown = true;
} else if (wasShown) {
PopoutService.unloadSettings();
}
}
} }
} }
@@ -534,7 +556,6 @@ Item {
hyprKeybindsModalLoader: hyprKeybindsModalLoader hyprKeybindsModalLoader: hyprKeybindsModalLoader
dankBarRepeater: dankBarRepeater dankBarRepeater: dankBarRepeater
hyprlandOverviewLoader: hyprlandOverviewLoader hyprlandOverviewLoader: hyprlandOverviewLoader
settingsModal: settingsModal
} }
Variants { Variants {

View File

@@ -15,7 +15,6 @@ Item {
required property var hyprKeybindsModalLoader required property var hyprKeybindsModalLoader
required property var dankBarRepeater required property var dankBarRepeater
required property var hyprlandOverviewLoader required property var hyprlandOverviewLoader
required property var settingsModal
function getFirstBar() { function getFirstBar() {
if (!root.dankBarRepeater || root.dankBarRepeater.count === 0) if (!root.dankBarRepeater || root.dankBarRepeater.count === 0)
@@ -533,17 +532,17 @@ Item {
IpcHandler { IpcHandler {
function open(): string { function open(): string {
root.settingsModal.show(); PopoutService.openSettings();
return "SETTINGS_OPEN_SUCCESS"; return "SETTINGS_OPEN_SUCCESS";
} }
function close(): string { function close(): string {
root.settingsModal.hide(); PopoutService.closeSettings();
return "SETTINGS_CLOSE_SUCCESS"; return "SETTINGS_CLOSE_SUCCESS";
} }
function toggle(): string { function toggle(): string {
root.settingsModal.toggle(); PopoutService.toggleSettings();
return "SETTINGS_TOGGLE_SUCCESS"; return "SETTINGS_TOGGLE_SUCCESS";
} }
@@ -552,12 +551,17 @@ Item {
IpcHandler { IpcHandler {
function browse(type: string) { function browse(type: string) {
if (type === "wallpaper") { const modal = PopoutService.settingsModal;
root.settingsModal.wallpaperBrowser.allowStacking = false; if (modal) {
root.settingsModal.wallpaperBrowser.open(); if (type === "wallpaper") {
} else if (type === "profile") { modal.wallpaperBrowser.allowStacking = false;
root.settingsModal.profileBrowser.allowStacking = false; modal.wallpaperBrowser.open();
root.settingsModal.profileBrowser.open(); } else if (type === "profile") {
modal.profileBrowser.allowStacking = false;
modal.profileBrowser.open();
}
} else {
PopoutService.openSettings();
} }
} }

View File

@@ -160,6 +160,7 @@ Variants {
asynchronous: true asynchronous: true
smooth: true smooth: true
cache: true cache: true
sourceSize: Qt.size(modelData.width, modelData.height)
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode) fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode)
} }
@@ -171,6 +172,7 @@ Variants {
asynchronous: true asynchronous: true
smooth: true smooth: true
cache: true cache: true
sourceSize: Qt.size(modelData.width, modelData.height)
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode) fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SettingsData.wallpaperFillMode)
onStatusChanged: { onStatusChanged: {
@@ -218,14 +220,16 @@ Variants {
duration: 1000 duration: 1000
easing.type: Easing.InOutCubic easing.type: Easing.InOutCubic
onFinished: { onFinished: {
const tempSource = nextWallpaper.source;
if (tempSource && nextWallpaper.status === Image.Ready && !tempSource.toString().startsWith("#")) {
currentWallpaper.source = tempSource;
}
root.transitionProgress = 0.0;
currentWallpaper.opacity = 1;
nextWallpaper.opacity = 0;
Qt.callLater(() => { Qt.callLater(() => {
if (nextWallpaper.source && nextWallpaper.status === Image.Ready && !nextWallpaper.source.toString().startsWith("#")) {
currentWallpaper.source = nextWallpaper.source;
}
nextWallpaper.source = ""; nextWallpaper.source = "";
currentWallpaper.opacity = 1;
nextWallpaper.opacity = 0;
root.transitionProgress = 0.0;
}); });
} }
} }

View File

@@ -99,7 +99,7 @@ Rectangle {
backgroundColor: "transparent" backgroundColor: "transparent"
onClicked: { onClicked: {
root.settingsButtonClicked(); root.settingsButtonClicked();
settingsModal.show(); PopoutService.openSettings();
} }
} }

View File

@@ -301,7 +301,7 @@ DankPopout {
let settingsIndex = SettingsData.weatherEnabled ? 4 : 3; let settingsIndex = SettingsData.weatherEnabled ? 4 : 3;
if (index === settingsIndex) { if (index === settingsIndex) {
dashVisible = false; dashVisible = false;
settingsModal.show(); PopoutService.openSettings();
} }
} }
} }

View File

@@ -2,19 +2,16 @@ import QtQuick
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets
import Quickshell.Io
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
import qs.Modules
import qs.Services import qs.Services
Variants { Variants {
model: { model: {
if (SessionData.isGreeterMode) { if (SessionData.isGreeterMode) {
return Quickshell.screens return Quickshell.screens;
} }
return SettingsData.getFilteredScreens("wallpaper") return SettingsData.getFilteredScreens("wallpaper");
} }
PanelWindow { PanelWindow {
@@ -52,9 +49,9 @@ Variants {
target: SessionData target: SessionData
function onIsLightModeChanged() { function onIsLightModeChanged() {
if (SessionData.perModeWallpaper) { if (SessionData.perModeWallpaper) {
var newSource = SessionData.getMonitorWallpaper(modelData.name) || "" var newSource = SessionData.getMonitorWallpaper(modelData.name) || "";
if (newSource !== root.source) { if (newSource !== root.source) {
root.source = newSource root.source = newSource;
} }
} }
} }
@@ -62,19 +59,19 @@ Variants {
onTransitionTypeChanged: { onTransitionTypeChanged: {
if (transitionType === "random") { if (transitionType === "random") {
if (SessionData.includedTransitions.length === 0) { if (SessionData.includedTransitions.length === 0) {
actualTransitionType = "none" actualTransitionType = "none";
} else { } else {
actualTransitionType = SessionData.includedTransitions[Math.floor(Math.random() * SessionData.includedTransitions.length)] actualTransitionType = SessionData.includedTransitions[Math.floor(Math.random() * SessionData.includedTransitions.length)];
} }
} else { } else {
actualTransitionType = transitionType actualTransitionType = transitionType;
} }
} }
onActualTransitionTypeChanged: { onActualTransitionTypeChanged: {
if (actualTransitionType === "none") { if (actualTransitionType === "none") {
currentWallpaper.visible = true currentWallpaper.visible = true;
nextWallpaper.visible = false nextWallpaper.visible = false;
} }
} }
property real transitionProgress: 0 property real transitionProgress: 0
@@ -94,103 +91,110 @@ Variants {
property bool booting: !hasCurrent && nextWallpaper.status === Image.Ready property bool booting: !hasCurrent && nextWallpaper.status === Image.Ready
function getFillMode(modeName) { function getFillMode(modeName) {
switch(modeName) { switch (modeName) {
case "Stretch": return Image.Stretch case "Stretch":
case "Fit": return Image.Stretch;
case "PreserveAspectFit": return Image.PreserveAspectFit case "Fit":
case "Fill": case "PreserveAspectFit":
case "PreserveAspectCrop": return Image.PreserveAspectCrop return Image.PreserveAspectFit;
case "Tile": return Image.Tile case "Fill":
case "TileVertically": return Image.TileVertically case "PreserveAspectCrop":
case "TileHorizontally": return Image.TileHorizontally return Image.PreserveAspectCrop;
case "Pad": return Image.Pad case "Tile":
default: return Image.PreserveAspectCrop return Image.Tile;
case "TileVertically":
return Image.TileVertically;
case "TileHorizontally":
return Image.TileHorizontally;
case "Pad":
return Image.Pad;
default:
return Image.PreserveAspectCrop;
} }
} }
Component.onCompleted: { Component.onCompleted: {
if (source) { if (source) {
const formattedSource = source.startsWith("file://") ? source : "file://" + source const formattedSource = source.startsWith("file://") ? source : "file://" + source;
setWallpaperImmediate(formattedSource) setWallpaperImmediate(formattedSource);
} }
isInitialized = true isInitialized = true;
} }
onSourceChanged: { onSourceChanged: {
const isColor = source.startsWith("#") const isColor = source.startsWith("#");
if (!source) { if (!source) {
setWallpaperImmediate("") setWallpaperImmediate("");
} else if (isColor) { } else if (isColor) {
setWallpaperImmediate("") setWallpaperImmediate("");
} else { } else {
if (!isInitialized || !currentWallpaper.source) { if (!isInitialized || !currentWallpaper.source) {
setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source) setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source);
isInitialized = true isInitialized = true;
} else if (CompositorService.isNiri && SessionData.isSwitchingMode) { } else if (CompositorService.isNiri && SessionData.isSwitchingMode) {
setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source) setWallpaperImmediate(source.startsWith("file://") ? source : "file://" + source);
} else { } else {
changeWallpaper(source.startsWith("file://") ? source : "file://" + source) changeWallpaper(source.startsWith("file://") ? source : "file://" + source);
} }
} }
} }
function setWallpaperImmediate(newSource) { function setWallpaperImmediate(newSource) {
transitionAnimation.stop() transitionAnimation.stop();
root.transitionProgress = 0.0 root.transitionProgress = 0.0;
currentWallpaper.source = newSource currentWallpaper.source = newSource;
nextWallpaper.source = "" nextWallpaper.source = "";
currentWallpaper.visible = true currentWallpaper.visible = true;
nextWallpaper.visible = false nextWallpaper.visible = false;
} }
function changeWallpaper(newPath, force) { function changeWallpaper(newPath, force) {
if (!force && newPath === currentWallpaper.source) if (!force && newPath === currentWallpaper.source)
return return;
if (!newPath || newPath.startsWith("#")) if (!newPath || newPath.startsWith("#"))
return return;
if (root.transitioning) { if (root.transitioning) {
transitionAnimation.stop() transitionAnimation.stop();
root.transitionProgress = 0 root.transitionProgress = 0;
currentWallpaper.source = nextWallpaper.source currentWallpaper.source = nextWallpaper.source;
nextWallpaper.source = "" nextWallpaper.source = "";
} }
// If no current wallpaper, set immediately to avoid scaling issues // If no current wallpaper, set immediately to avoid scaling issues
if (!currentWallpaper.source) { if (!currentWallpaper.source) {
setWallpaperImmediate(newPath) setWallpaperImmediate(newPath);
return return;
} }
// If transition is "none", set immediately // If transition is "none", set immediately
if (root.transitionType === "random") { if (root.transitionType === "random") {
if (SessionData.includedTransitions.length === 0) { if (SessionData.includedTransitions.length === 0) {
root.actualTransitionType = "none" root.actualTransitionType = "none";
} else { } else {
root.actualTransitionType = SessionData.includedTransitions[Math.floor(Math.random() * SessionData.includedTransitions.length)] root.actualTransitionType = SessionData.includedTransitions[Math.floor(Math.random() * SessionData.includedTransitions.length)];
} }
} }
if (root.actualTransitionType === "none") { if (root.actualTransitionType === "none") {
setWallpaperImmediate(newPath) setWallpaperImmediate(newPath);
return return;
} }
if (root.actualTransitionType === "wipe") { if (root.actualTransitionType === "wipe") {
root.wipeDirection = Math.random() * 4 root.wipeDirection = Math.random() * 4;
} else if (root.actualTransitionType === "disc") { } else if (root.actualTransitionType === "disc") {
root.discCenterX = Math.random() root.discCenterX = Math.random();
root.discCenterY = Math.random() root.discCenterY = Math.random();
} else if (root.actualTransitionType === "stripes") { } else if (root.actualTransitionType === "stripes") {
root.stripesCount = Math.round(Math.random() * 20 + 4) root.stripesCount = Math.round(Math.random() * 20 + 4);
root.stripesAngle = Math.random() * 360 root.stripesAngle = Math.random() * 360;
} }
nextWallpaper.source = newPath nextWallpaper.source = newPath;
if (nextWallpaper.status === Image.Ready) { if (nextWallpaper.status === Image.Ready) {
transitionAnimation.start() transitionAnimation.start();
} }
} }
@@ -227,6 +231,7 @@ Variants {
asynchronous: true asynchronous: true
smooth: true smooth: true
cache: true cache: true
sourceSize: Qt.size(modelData.width, modelData.height)
fillMode: root.getFillMode(SettingsData.wallpaperFillMode) fillMode: root.getFillMode(SettingsData.wallpaperFillMode)
} }
@@ -239,20 +244,20 @@ Variants {
asynchronous: true asynchronous: true
smooth: true smooth: true
cache: true cache: true
sourceSize: Qt.size(modelData.width, modelData.height)
fillMode: root.getFillMode(SettingsData.wallpaperFillMode) fillMode: root.getFillMode(SettingsData.wallpaperFillMode)
onStatusChanged: { onStatusChanged: {
if (status !== Image.Ready) if (status !== Image.Ready)
return return;
if (root.actualTransitionType === "none") { if (root.actualTransitionType === "none") {
currentWallpaper.source = source currentWallpaper.source = source;
nextWallpaper.source = "" nextWallpaper.source = "";
root.transitionProgress = 0.0 root.transitionProgress = 0.0;
} else { } else {
visible = true visible = true;
if (!root.transitioning) { if (!root.transitioning) {
transitionAnimation.start() transitionAnimation.start();
} }
} }
} }
@@ -265,21 +270,21 @@ Variants {
sourceComponent: { sourceComponent: {
switch (root.actualTransitionType) { switch (root.actualTransitionType) {
case "fade": case "fade":
return fadeComp return fadeComp;
case "wipe": case "wipe":
return wipeComp return wipeComp;
case "disc": case "disc":
return discComp return discComp;
case "stripes": case "stripes":
return stripesComp return stripesComp;
case "iris bloom": case "iris bloom":
return irisComp return irisComp;
case "pixelate": case "pixelate":
return pixelateComp return pixelateComp;
case "portal": case "portal":
return portalComp return portalComp;
default: default:
return null return null;
} }
} }
} }
@@ -448,15 +453,17 @@ Variants {
duration: root.actualTransitionType === "none" ? 0 : 1000 duration: root.actualTransitionType === "none" ? 0 : 1000
easing.type: Easing.InOutCubic easing.type: Easing.InOutCubic
onFinished: { onFinished: {
const tempSource = nextWallpaper.source;
if (tempSource && nextWallpaper.status === Image.Ready && !tempSource.toString().startsWith("#")) {
currentWallpaper.source = tempSource;
}
root.transitionProgress = 0.0;
currentWallpaper.visible = root.actualTransitionType === "none";
Qt.callLater(() => { Qt.callLater(() => {
if (nextWallpaper.source && nextWallpaper.status === Image.Ready && !nextWallpaper.source.toString().startsWith("#")) { nextWallpaper.source = "";
currentWallpaper.source = nextWallpaper.source nextWallpaper.visible = false;
} });
nextWallpaper.source = ""
nextWallpaper.visible = false
currentWallpaper.visible = root.actualTransitionType === "none"
root.transitionProgress = 0.0
})
} }
} }

View File

@@ -1,11 +1,9 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
import Quickshell.Widgets
import qs.Common import qs.Common
import "../Common/markdown2html.js" as Markdown2Html import "../Common/markdown2html.js" as Markdown2Html
@@ -28,108 +26,106 @@ Singleton {
property int maxIngressPerSecond: 20 property int maxIngressPerSecond: 20
property double _lastIngressSec: 0 property double _lastIngressSec: 0
property int _ingressCountThisSec: 0 property int _ingressCountThisSec: 0
property int maxStoredNotifications: 300 property int maxStoredNotifications: 50
property var _dismissQueue: [] property var _dismissQueue: []
property int _dismissBatchSize: 8 property int _dismissBatchSize: 8
property int _dismissTickMs: 8 property int _dismissTickMs: 8
property bool _suspendGrouping: false property bool _suspendGrouping: false
property var _groupCache: ({ property var _groupCache: ({
"notifications": [], "notifications": [],
"popups": [] "popups": []
}) })
property bool _groupsDirty: false property bool _groupsDirty: false
Component.onCompleted: { Component.onCompleted: {
_recomputeGroups() _recomputeGroups();
} }
function _nowSec() { function _nowSec() {
return Date.now() / 1000.0 return Date.now() / 1000.0;
} }
function _ingressAllowed(notif) { function _ingressAllowed(notif) {
const t = _nowSec() const t = _nowSec();
if (t - _lastIngressSec >= 1.0) { if (t - _lastIngressSec >= 1.0) {
_lastIngressSec = t _lastIngressSec = t;
_ingressCountThisSec = 0 _ingressCountThisSec = 0;
} }
_ingressCountThisSec += 1 _ingressCountThisSec += 1;
if (notif.urgency === NotificationUrgency.Critical) { if (notif.urgency === NotificationUrgency.Critical) {
return true return true;
} }
return _ingressCountThisSec <= maxIngressPerSecond return _ingressCountThisSec <= maxIngressPerSecond;
} }
function _enqueuePopup(wrapper) { function _enqueuePopup(wrapper) {
if (notificationQueue.length >= maxQueueSize) { if (notificationQueue.length >= maxQueueSize) {
const gk = getGroupKey(wrapper) const gk = getGroupKey(wrapper);
let idx = notificationQueue.findIndex(w => w && getGroupKey(w) === gk && w.urgency !== NotificationUrgency.Critical) let idx = notificationQueue.findIndex(w => w && getGroupKey(w) === gk && w.urgency !== NotificationUrgency.Critical);
if (idx === -1) { if (idx === -1) {
idx = notificationQueue.findIndex(w => w && w.urgency !== NotificationUrgency.Critical) idx = notificationQueue.findIndex(w => w && w.urgency !== NotificationUrgency.Critical);
} }
if (idx === -1) { if (idx === -1) {
idx = 0 idx = 0;
} }
const victim = notificationQueue[idx] const victim = notificationQueue[idx];
if (victim) { if (victim) {
victim.popup = false victim.popup = false;
} }
notificationQueue.splice(idx, 1) notificationQueue.splice(idx, 1);
} }
notificationQueue = [...notificationQueue, wrapper] notificationQueue = [...notificationQueue, wrapper];
} }
function _initWrapperPersistence(wrapper) { function _initWrapperPersistence(wrapper) {
const timeoutMs = wrapper.timer ? wrapper.timer.interval : 5000 const timeoutMs = wrapper.timer ? wrapper.timer.interval : 5000;
const isCritical = wrapper.notification && wrapper.notification.urgency === NotificationUrgency.Critical const isCritical = wrapper.notification && wrapper.notification.urgency === NotificationUrgency.Critical;
wrapper.isPersistent = isCritical || (timeoutMs === 0) wrapper.isPersistent = isCritical || (timeoutMs === 0);
} }
function _trimStored() { function _trimStored() {
if (notifications.length > maxStoredNotifications) { if (notifications.length > maxStoredNotifications) {
const overflow = notifications.length - maxStoredNotifications const overflow = notifications.length - maxStoredNotifications;
const toDrop = [] const toDrop = [];
for (var i = notifications.length - 1; i >= 0 && toDrop.length < overflow; --i) { for (var i = notifications.length - 1; i >= 0 && toDrop.length < overflow; --i) {
const w = notifications[i] const w = notifications[i];
if (w && w.notification && w.urgency !== NotificationUrgency.Critical) { if (w && w.notification && w.urgency !== NotificationUrgency.Critical) {
toDrop.push(w) toDrop.push(w);
} }
} }
for (var i = notifications.length - 1; i >= 0 && toDrop.length < overflow; --i) { for (var i = notifications.length - 1; i >= 0 && toDrop.length < overflow; --i) {
const w = notifications[i] const w = notifications[i];
if (w && w.notification && toDrop.indexOf(w) === -1) { if (w && w.notification && toDrop.indexOf(w) === -1) {
toDrop.push(w) toDrop.push(w);
} }
} }
for (const w of toDrop) { for (const w of toDrop) {
try { try {
w.notification.dismiss() w.notification.dismiss();
} catch (e) { } catch (e) {}
}
} }
} }
} }
function onOverlayOpen() { function onOverlayOpen() {
popupsDisabled = true popupsDisabled = true;
addGate.stop() addGate.stop();
addGateBusy = false addGateBusy = false;
notificationQueue = [] notificationQueue = [];
for (const w of visibleNotifications) { for (const w of visibleNotifications) {
if (w) { if (w) {
w.popup = false w.popup = false;
} }
} }
visibleNotifications = [] visibleNotifications = [];
_recomputeGroupsLater() _recomputeGroupsLater();
} }
function onOverlayClose() { function onOverlayClose() {
popupsDisabled = false popupsDisabled = false;
processQueue() processQueue();
} }
Timer { Timer {
@@ -138,8 +134,8 @@ Singleton {
running: false running: false
repeat: false repeat: false
onTriggered: { onTriggered: {
addGateBusy = false addGateBusy = false;
processQueue() processQueue();
} }
} }
@@ -150,7 +146,7 @@ Singleton {
running: root.allWrappers.length > 0 || visibleNotifications.length > 0 running: root.allWrappers.length > 0 || visibleNotifications.length > 0
triggeredOnStart: false triggeredOnStart: false
onTriggered: { onTriggered: {
root.timeUpdateTick = !root.timeUpdateTick root.timeUpdateTick = !root.timeUpdateTick;
} }
} }
@@ -160,23 +156,21 @@ Singleton {
repeat: true repeat: true
running: false running: false
onTriggered: { onTriggered: {
let n = Math.min(_dismissBatchSize, _dismissQueue.length) let n = Math.min(_dismissBatchSize, _dismissQueue.length);
for (var i = 0; i < n; ++i) { for (var i = 0; i < n; ++i) {
const w = _dismissQueue.pop() const w = _dismissQueue.pop();
try { try {
if (w && w.notification) { if (w && w.notification) {
w.notification.dismiss() w.notification.dismiss();
} }
} catch (e) { } catch (e) {}
}
} }
if (_dismissQueue.length === 0) { if (_dismissQueue.length === 0) {
dismissPump.stop() dismissPump.stop();
_suspendGrouping = false _suspendGrouping = false;
bulkDismissing = false bulkDismissing = false;
popupsDisabled = false popupsDisabled = false;
_recomputeGroupsLater() _recomputeGroupsLater();
} }
} }
} }
@@ -212,52 +206,50 @@ Singleton {
persistenceSupported: true persistenceSupported: true
onNotification: notif => { onNotification: notif => {
notif.tracked = true notif.tracked = true;
if (!_ingressAllowed(notif)) { if (!_ingressAllowed(notif)) {
if (notif.urgency !== NotificationUrgency.Critical) { if (notif.urgency !== NotificationUrgency.Critical) {
try { try {
notif.dismiss() notif.dismiss();
} catch (e) { } catch (e) {}
return;
}
return
} }
} }
if (SettingsData.soundsEnabled && SettingsData.soundNewNotification) { if (SettingsData.soundsEnabled && SettingsData.soundNewNotification) {
if (notif.urgency === NotificationUrgency.Critical) { if (notif.urgency === NotificationUrgency.Critical) {
AudioService.playCriticalNotificationSound() AudioService.playCriticalNotificationSound();
} else { } else {
AudioService.playNormalNotificationSound() AudioService.playNormalNotificationSound();
} }
} }
const shouldShowPopup = !root.popupsDisabled && !SessionData.doNotDisturb const shouldShowPopup = !root.popupsDisabled && !SessionData.doNotDisturb;
const isTransient = notif.transient const isTransient = notif.transient;
const wrapper = notifComponent.createObject(root, { const wrapper = notifComponent.createObject(root, {
"popup": shouldShowPopup, "popup": shouldShowPopup,
"notification": notif "notification": notif
}) });
if (wrapper) { if (wrapper) {
root.allWrappers.push(wrapper) root.allWrappers.push(wrapper);
if (!isTransient) { if (!isTransient) {
root.notifications.push(wrapper) root.notifications.push(wrapper);
_trimStored() _trimStored();
} }
Qt.callLater(() => { Qt.callLater(() => {
_initWrapperPersistence(wrapper) _initWrapperPersistence(wrapper);
}) });
if (shouldShowPopup) { if (shouldShowPopup) {
_enqueuePopup(wrapper) _enqueuePopup(wrapper);
processQueue() processQueue();
} }
} }
_recomputeGroupsLater() _recomputeGroupsLater();
} }
} }
@@ -271,80 +263,80 @@ Singleton {
onPopupChanged: { onPopupChanged: {
if (!popup) { if (!popup) {
removeFromVisibleNotifications(wrapper) removeFromVisibleNotifications(wrapper);
} }
} }
readonly property Timer timer: Timer { readonly property Timer timer: Timer {
interval: { interval: {
if (!wrapper.notification) { if (!wrapper.notification) {
return 5000 return 5000;
} }
switch (wrapper.notification.urgency) { switch (wrapper.notification.urgency) {
case NotificationUrgency.Low: case NotificationUrgency.Low:
return SettingsData.notificationTimeoutLow return SettingsData.notificationTimeoutLow;
case NotificationUrgency.Critical: case NotificationUrgency.Critical:
return SettingsData.notificationTimeoutCritical return SettingsData.notificationTimeoutCritical;
default: default:
return SettingsData.notificationTimeoutNormal return SettingsData.notificationTimeoutNormal;
} }
} }
repeat: false repeat: false
running: false running: false
onTriggered: { onTriggered: {
if (interval > 0) { if (interval > 0) {
wrapper.popup = false wrapper.popup = false;
} }
} }
} }
readonly property date time: new Date() readonly property date time: new Date()
readonly property string timeStr: { readonly property string timeStr: {
root.timeUpdateTick root.timeUpdateTick;
root.clockFormatChanged root.clockFormatChanged;
const now = new Date() const now = new Date();
const diff = now.getTime() - time.getTime() const diff = now.getTime() - time.getTime();
const minutes = Math.floor(diff / 60000) const minutes = Math.floor(diff / 60000);
const hours = Math.floor(minutes / 60) const hours = Math.floor(minutes / 60);
if (hours < 1) { if (hours < 1) {
if (minutes < 1) { if (minutes < 1) {
return "now" return "now";
} }
return `${minutes}m ago` return `${minutes}m ago`;
} }
const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()) const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const timeDate = new Date(time.getFullYear(), time.getMonth(), time.getDate()) const timeDate = new Date(time.getFullYear(), time.getMonth(), time.getDate());
const daysDiff = Math.floor((nowDate - timeDate) / (1000 * 60 * 60 * 24)) const daysDiff = Math.floor((nowDate - timeDate) / (1000 * 60 * 60 * 24));
if (daysDiff === 0) { if (daysDiff === 0) {
return formatTime(time) return formatTime(time);
} }
if (daysDiff === 1) { if (daysDiff === 1) {
return `yesterday, ${formatTime(time)}` return `yesterday, ${formatTime(time)}`;
} }
return `${daysDiff} days ago` return `${daysDiff} days ago`;
} }
function formatTime(date) { function formatTime(date) {
let use24Hour = true let use24Hour = true;
try { try {
if (typeof SettingsData !== "undefined" && SettingsData.use24HourClock !== undefined) { if (typeof SettingsData !== "undefined" && SettingsData.use24HourClock !== undefined) {
use24Hour = SettingsData.use24HourClock use24Hour = SettingsData.use24HourClock;
} }
} catch (e) { } catch (e) {
use24Hour = true use24Hour = true;
} }
if (use24Hour) { if (use24Hour) {
return date.toLocaleTimeString(Qt.locale(), "HH:mm") return date.toLocaleTimeString(Qt.locale(), "HH:mm");
} else { } else {
return date.toLocaleTimeString(Qt.locale(), "h:mm AP") return date.toLocaleTimeString(Qt.locale(), "h:mm AP");
} }
} }
@@ -353,27 +345,27 @@ Singleton {
readonly property string body: notification.body readonly property string body: notification.body
readonly property string htmlBody: { readonly property string htmlBody: {
if (body && (body.includes('<') && body.includes('>'))) { if (body && (body.includes('<') && body.includes('>'))) {
return body return body;
} }
return Markdown2Html.markdownToHtml(body) return Markdown2Html.markdownToHtml(body);
} }
readonly property string appIcon: notification.appIcon readonly property string appIcon: notification.appIcon
readonly property string appName: { readonly property string appName: {
if (notification.appName == "") { if (notification.appName == "") {
const entry = DesktopEntries.heuristicLookup(notification.desktopEntry) const entry = DesktopEntries.heuristicLookup(notification.desktopEntry);
if (entry && entry.name) { if (entry && entry.name) {
return entry.name.toLowerCase() return entry.name.toLowerCase();
} }
} }
return notification.appName || "app" return notification.appName || "app";
} }
readonly property string desktopEntry: notification.desktopEntry readonly property string desktopEntry: notification.desktopEntry
readonly property string image: notification.image readonly property string image: notification.image
readonly property string cleanImage: { readonly property string cleanImage: {
if (!image) { if (!image) {
return "" return "";
} }
return Paths.strip(image) return Paths.strip(image);
} }
readonly property int urgency: notification.urgency readonly property int urgency: notification.urgency
readonly property list<NotificationAction> actions: notification.actions readonly property list<NotificationAction> actions: notification.actions
@@ -382,26 +374,26 @@ Singleton {
target: wrapper.notification.Retainable target: wrapper.notification.Retainable
function onDropped(): void { function onDropped(): void {
root.allWrappers = root.allWrappers.filter(w => w !== wrapper) root.allWrappers = root.allWrappers.filter(w => w !== wrapper);
root.notifications = root.notifications.filter(w => w !== wrapper) root.notifications = root.notifications.filter(w => w !== wrapper);
if (root.bulkDismissing) { if (root.bulkDismissing) {
return return;
} }
const groupKey = getGroupKey(wrapper) const groupKey = getGroupKey(wrapper);
const remainingInGroup = root.notifications.filter(n => getGroupKey(n) === groupKey) const remainingInGroup = root.notifications.filter(n => getGroupKey(n) === groupKey);
if (remainingInGroup.length <= 1) { if (remainingInGroup.length <= 1) {
clearGroupExpansionState(groupKey) clearGroupExpansionState(groupKey);
} }
cleanupExpansionStates() cleanupExpansionStates();
root._recomputeGroupsLater() root._recomputeGroupsLater();
} }
function onAboutToDestroy(): void { function onAboutToDestroy(): void {
wrapper.destroy() wrapper.destroy();
} }
} }
} }
@@ -414,146 +406,145 @@ Singleton {
function clearAllPopups() { function clearAllPopups() {
for (const w of visibleNotifications) { for (const w of visibleNotifications) {
if (w) { if (w) {
w.popup = false w.popup = false;
} }
} }
visibleNotifications = [] visibleNotifications = [];
notificationQueue = [] notificationQueue = [];
} }
function clearAllNotifications() { function clearAllNotifications() {
bulkDismissing = true bulkDismissing = true;
popupsDisabled = true popupsDisabled = true;
addGate.stop() addGate.stop();
addGateBusy = false addGateBusy = false;
notificationQueue = [] notificationQueue = [];
for (const w of allWrappers) { for (const w of allWrappers) {
if (w) { if (w) {
w.popup = false w.popup = false;
} }
} }
visibleNotifications = [] visibleNotifications = [];
_dismissQueue = notifications.slice() _dismissQueue = notifications.slice();
if (notifications.length) { if (notifications.length) {
notifications = [] notifications = [];
} }
expandedGroups = {} expandedGroups = {};
expandedMessages = {} expandedMessages = {};
_suspendGrouping = true _suspendGrouping = true;
if (!dismissPump.running && _dismissQueue.length) { if (!dismissPump.running && _dismissQueue.length) {
dismissPump.start() dismissPump.start();
} }
} }
function dismissNotification(wrapper) { function dismissNotification(wrapper) {
if (!wrapper || !wrapper.notification) { if (!wrapper || !wrapper.notification) {
return return;
} }
wrapper.popup = false wrapper.popup = false;
wrapper.notification.dismiss() wrapper.notification.dismiss();
} }
function disablePopups(disable) { function disablePopups(disable) {
popupsDisabled = disable popupsDisabled = disable;
if (disable) { if (disable) {
notificationQueue = [] notificationQueue = [];
for (const notif of visibleNotifications) { for (const notif of visibleNotifications) {
notif.popup = false notif.popup = false;
} }
visibleNotifications = [] visibleNotifications = [];
} }
} }
function processQueue() { function processQueue() {
if (addGateBusy) { if (addGateBusy) {
return return;
} }
if (popupsDisabled) { if (popupsDisabled) {
return return;
} }
if (SessionData.doNotDisturb) { if (SessionData.doNotDisturb) {
return return;
} }
if (notificationQueue.length === 0) { if (notificationQueue.length === 0) {
return return;
} }
const activePopupCount = visibleNotifications.filter(n => n && n.popup).length const activePopupCount = visibleNotifications.filter(n => n && n.popup).length;
if (activePopupCount >= 4) { if (activePopupCount >= 4) {
return return;
} }
const next = notificationQueue.shift() const next = notificationQueue.shift();
next.seq = ++seqCounter next.seq = ++seqCounter;
visibleNotifications = [...visibleNotifications, next] visibleNotifications = [...visibleNotifications, next];
next.popup = true next.popup = true;
if (next.timer.interval > 0) { if (next.timer.interval > 0) {
next.timer.start() next.timer.start();
} }
addGateBusy = true addGateBusy = true;
addGate.restart() addGate.restart();
} }
function removeFromVisibleNotifications(wrapper) { function removeFromVisibleNotifications(wrapper) {
visibleNotifications = visibleNotifications.filter(n => n !== wrapper) visibleNotifications = visibleNotifications.filter(n => n !== wrapper);
processQueue() processQueue();
} }
function releaseWrapper(w) { function releaseWrapper(w) {
visibleNotifications = visibleNotifications.filter(n => n !== w) visibleNotifications = visibleNotifications.filter(n => n !== w);
notificationQueue = notificationQueue.filter(n => n !== w) notificationQueue = notificationQueue.filter(n => n !== w);
if (w && w.destroy && !w.isPersistent && notifications.indexOf(w) === -1) { if (w && w.destroy && !w.isPersistent && notifications.indexOf(w) === -1) {
Qt.callLater(() => { Qt.callLater(() => {
try { try {
w.destroy() w.destroy();
} catch (e) { } catch (e) {}
});
}
})
} }
} }
function getGroupKey(wrapper) { function getGroupKey(wrapper) {
if (wrapper.desktopEntry && wrapper.desktopEntry !== "") { if (wrapper.desktopEntry && wrapper.desktopEntry !== "") {
return wrapper.desktopEntry.toLowerCase() return wrapper.desktopEntry.toLowerCase();
} }
return wrapper.appName.toLowerCase() return wrapper.appName.toLowerCase();
} }
function _recomputeGroups() { function _recomputeGroups() {
if (_suspendGrouping) { if (_suspendGrouping) {
_groupsDirty = true _groupsDirty = true;
return return;
} }
_groupCache = { _groupCache = {
"notifications": _calcGroupedNotifications(), "notifications": _calcGroupedNotifications(),
"popups": _calcGroupedPopups() "popups": _calcGroupedPopups()
} };
_groupsDirty = false _groupsDirty = false;
} }
function _recomputeGroupsLater() { function _recomputeGroupsLater() {
_groupsDirty = true _groupsDirty = true;
if (!groupsDebounce.running) { if (!groupsDebounce.running) {
groupsDebounce.start() groupsDebounce.start();
} }
} }
function _calcGroupedNotifications() { function _calcGroupedNotifications() {
const groups = {} const groups = {};
for (const notif of notifications) { for (const notif of notifications) {
if (!notif) continue if (!notif)
const groupKey = getGroupKey(notif) continue;
const groupKey = getGroupKey(notif);
if (!groups[groupKey]) { if (!groups[groupKey]) {
groups[groupKey] = { groups[groupKey] = {
"key": groupKey, "key": groupKey,
@@ -562,34 +553,35 @@ Singleton {
"latestNotification": null, "latestNotification": null,
"count": 0, "count": 0,
"hasInlineReply": false "hasInlineReply": false
} };
} }
groups[groupKey].notifications.unshift(notif) groups[groupKey].notifications.unshift(notif);
groups[groupKey].latestNotification = groups[groupKey].notifications[0] groups[groupKey].latestNotification = groups[groupKey].notifications[0];
groups[groupKey].count = groups[groupKey].notifications.length groups[groupKey].count = groups[groupKey].notifications.length;
if (notif.notification.hasInlineReply) { if (notif.notification.hasInlineReply) {
groups[groupKey].hasInlineReply = true groups[groupKey].hasInlineReply = true;
} }
} }
return Object.values(groups).sort((a, b) => { return Object.values(groups).sort((a, b) => {
const aUrgency = a.latestNotification.urgency || NotificationUrgency.Low const aUrgency = a.latestNotification.urgency || NotificationUrgency.Low;
const bUrgency = b.latestNotification.urgency || NotificationUrgency.Low const bUrgency = b.latestNotification.urgency || NotificationUrgency.Low;
if (aUrgency !== bUrgency) { if (aUrgency !== bUrgency) {
return bUrgency - aUrgency return bUrgency - aUrgency;
} }
return b.latestNotification.time.getTime() - a.latestNotification.time.getTime() return b.latestNotification.time.getTime() - a.latestNotification.time.getTime();
}) });
} }
function _calcGroupedPopups() { function _calcGroupedPopups() {
const groups = {} const groups = {};
for (const notif of popups) { for (const notif of popups) {
if (!notif) continue if (!notif)
const groupKey = getGroupKey(notif) continue;
const groupKey = getGroupKey(notif);
if (!groups[groupKey]) { if (!groups[groupKey]) {
groups[groupKey] = { groups[groupKey] = {
"key": groupKey, "key": groupKey,
@@ -598,92 +590,92 @@ Singleton {
"latestNotification": null, "latestNotification": null,
"count": 0, "count": 0,
"hasInlineReply": false "hasInlineReply": false
} };
} }
groups[groupKey].notifications.unshift(notif) groups[groupKey].notifications.unshift(notif);
groups[groupKey].latestNotification = groups[groupKey].notifications[0] groups[groupKey].latestNotification = groups[groupKey].notifications[0];
groups[groupKey].count = groups[groupKey].notifications.length groups[groupKey].count = groups[groupKey].notifications.length;
if (notif.notification.hasInlineReply) { if (notif.notification.hasInlineReply) {
groups[groupKey].hasInlineReply = true groups[groupKey].hasInlineReply = true;
} }
} }
return Object.values(groups).sort((a, b) => { return Object.values(groups).sort((a, b) => {
return b.latestNotification.time.getTime() - a.latestNotification.time.getTime() return b.latestNotification.time.getTime() - a.latestNotification.time.getTime();
}) });
} }
function toggleGroupExpansion(groupKey) { function toggleGroupExpansion(groupKey) {
let newExpandedGroups = {} let newExpandedGroups = {};
for (const key in expandedGroups) { for (const key in expandedGroups) {
newExpandedGroups[key] = expandedGroups[key] newExpandedGroups[key] = expandedGroups[key];
} }
newExpandedGroups[groupKey] = !newExpandedGroups[groupKey] newExpandedGroups[groupKey] = !newExpandedGroups[groupKey];
expandedGroups = newExpandedGroups expandedGroups = newExpandedGroups;
} }
function dismissGroup(groupKey) { function dismissGroup(groupKey) {
const group = groupedNotifications.find(g => g.key === groupKey) const group = groupedNotifications.find(g => g.key === groupKey);
if (group) { if (group) {
for (const notif of group.notifications) { for (const notif of group.notifications) {
if (notif && notif.notification) { if (notif && notif.notification) {
notif.notification.dismiss() notif.notification.dismiss();
} }
} }
} else { } else {
for (const notif of allWrappers) { for (const notif of allWrappers) {
if (notif && notif.notification && getGroupKey(notif) === groupKey) { if (notif && notif.notification && getGroupKey(notif) === groupKey) {
notif.notification.dismiss() notif.notification.dismiss();
} }
} }
} }
} }
function clearGroupExpansionState(groupKey) { function clearGroupExpansionState(groupKey) {
let newExpandedGroups = {} let newExpandedGroups = {};
for (const key in expandedGroups) { for (const key in expandedGroups) {
if (key !== groupKey && expandedGroups[key]) { if (key !== groupKey && expandedGroups[key]) {
newExpandedGroups[key] = true newExpandedGroups[key] = true;
} }
} }
expandedGroups = newExpandedGroups expandedGroups = newExpandedGroups;
} }
function cleanupExpansionStates() { function cleanupExpansionStates() {
const currentGroupKeys = new Set(groupedNotifications.map(g => g.key)) const currentGroupKeys = new Set(groupedNotifications.map(g => g.key));
const currentMessageIds = new Set() const currentMessageIds = new Set();
for (const group of groupedNotifications) { for (const group of groupedNotifications) {
for (const notif of group.notifications) { for (const notif of group.notifications) {
if (notif && notif.notification) { if (notif && notif.notification) {
currentMessageIds.add(notif.notification.id) currentMessageIds.add(notif.notification.id);
} }
} }
} }
let newExpandedGroups = {} let newExpandedGroups = {};
for (const key in expandedGroups) { for (const key in expandedGroups) {
if (currentGroupKeys.has(key) && expandedGroups[key]) { if (currentGroupKeys.has(key) && expandedGroups[key]) {
newExpandedGroups[key] = true newExpandedGroups[key] = true;
} }
} }
expandedGroups = newExpandedGroups expandedGroups = newExpandedGroups;
let newExpandedMessages = {} let newExpandedMessages = {};
for (const messageId in expandedMessages) { for (const messageId in expandedMessages) {
if (currentMessageIds.has(messageId) && expandedMessages[messageId]) { if (currentMessageIds.has(messageId) && expandedMessages[messageId]) {
newExpandedMessages[messageId] = true newExpandedMessages[messageId] = true;
} }
} }
expandedMessages = newExpandedMessages expandedMessages = newExpandedMessages;
} }
function toggleMessageExpansion(messageId) { function toggleMessageExpansion(messageId) {
let newExpandedMessages = {} let newExpandedMessages = {};
for (const key in expandedMessages) { for (const key in expandedMessages) {
newExpandedMessages[key] = expandedMessages[key] newExpandedMessages[key] = expandedMessages[key];
} }
newExpandedMessages[messageId] = !newExpandedMessages[messageId] newExpandedMessages[messageId] = !newExpandedMessages[messageId];
expandedMessages = newExpandedMessages expandedMessages = newExpandedMessages;
} }
Connections { Connections {
@@ -692,13 +684,13 @@ Singleton {
if (SessionData.doNotDisturb) { if (SessionData.doNotDisturb) {
// Hide all current popups when DND is enabled // Hide all current popups when DND is enabled
for (const notif of visibleNotifications) { for (const notif of visibleNotifications) {
notif.popup = false notif.popup = false;
} }
visibleNotifications = [] visibleNotifications = [];
notificationQueue = [] notificationQueue = [];
} else { } else {
// Re-enable popup processing when DND is disabled // Re-enable popup processing when DND is disabled
processQueue() processQueue();
} }
} }
} }
@@ -706,7 +698,7 @@ Singleton {
Connections { Connections {
target: typeof SettingsData !== "undefined" ? SettingsData : null target: typeof SettingsData !== "undefined" ? SettingsData : null
function onUse24HourClockChanged() { function onUse24HourClockChanged() {
root.clockFormatChanged = !root.clockFormatChanged root.clockFormatChanged = !root.clockFormatChanged;
} }
} }
} }

View File

@@ -16,6 +16,7 @@ Singleton {
property var systemUpdatePopout: null property var systemUpdatePopout: null
property var settingsModal: null property var settingsModal: null
property var settingsModalLoader: null
property var clipboardHistoryModal: null property var clipboardHistoryModal: null
property var spotlightModal: null property var spotlightModal: null
property var powerMenuModal: null property var powerMenuModal: null
@@ -191,14 +192,50 @@ Singleton {
} }
} }
property bool _settingsWantsOpen: false
property bool _settingsWantsToggle: false
function openSettings() { function openSettings() {
settingsModal?.show(); if (settingsModal) {
settingsModal.show();
} else if (settingsModalLoader) {
_settingsWantsOpen = true;
_settingsWantsToggle = false;
settingsModalLoader.activeAsync = true;
}
} }
function closeSettings() { function closeSettings() {
settingsModal?.close(); settingsModal?.close();
} }
function toggleSettings() {
if (settingsModal) {
settingsModal.toggle();
} else if (settingsModalLoader) {
_settingsWantsToggle = true;
_settingsWantsOpen = false;
settingsModalLoader.activeAsync = true;
}
}
function unloadSettings() {
if (settingsModalLoader) {
settingsModal = null;
settingsModalLoader.active = false;
}
}
function _onSettingsModalLoaded() {
if (_settingsWantsOpen) {
_settingsWantsOpen = false;
settingsModal?.show();
} else if (_settingsWantsToggle) {
_settingsWantsToggle = false;
settingsModal?.toggle();
}
}
function openClipboardHistory() { function openClipboardHistory() {
clipboardHistoryModal?.show(); clipboardHistoryModal?.show();
} }