pragma Singleton pragma ComponentBehavior: Bound import QtQuick import Quickshell import qs.Common import qs.Common.settings Singleton { id: root readonly property int levelInfo: 0 readonly property int levelWarn: 1 readonly property int levelError: 2 property string currentMessage: "" property int currentLevel: levelInfo property bool toastVisible: false property var toastQueue: [] property string currentDetails: "" property string currentCommand: "" property bool hasDetails: false property string wallpaperErrorStatus: "" property int maxQueueSize: 3 property var lastErrorTime: ({}) property int errorThrottleMs: 1000 property string currentCategory: "" readonly property var stickyCategories: ["greeter-autologin-sync"] function isStickyCategory(category) { return category && stickyCategories.indexOf(category) >= 0; } function showToast(message, level = levelInfo, details = "", command = "", category = "") { const now = Date.now(); const messageKey = message + level; if (level === levelError) { const lastTime = lastErrorTime[messageKey] || 0; if (now - lastTime < errorThrottleMs) { return; } lastErrorTime[messageKey] = now; } if (category && level === levelError) { if (currentCategory === category && toastVisible && currentLevel === levelError) { currentMessage = message; currentDetails = details || ""; currentCommand = command || ""; hasDetails = currentDetails.length > 0 || currentCommand.length > 0; resetToastState(); if (hasDetails) { toastTimer.interval = 8000; } else { toastTimer.interval = 5000; } toastTimer.restart(); return; } toastQueue = toastQueue.filter(t => t.category !== category); } const isDuplicate = toastQueue.some(toast => toast.message === message && toast.level === level); if (isDuplicate) { return; } if (toastQueue.length >= maxQueueSize) { if (level === levelError) { toastQueue = toastQueue.filter(t => t.level !== levelError).slice(0, maxQueueSize - 1); } else { return; } } toastQueue.push({ "message": message, "level": level, "details": details, "command": command, "category": category }); if (!toastVisible) { processQueue(); } } function showInfo(message, details = "", command = "", category = "") { showToast(message, levelInfo, details, command, category); } function showWarning(message, details = "", command = "", category = "") { showToast(message, levelWarn, details, command, category); } function showError(message, details = "", command = "", category = "") { showToast(message, levelError, details, command, category); } function dismissCategory(category) { if (!category) { return; } if (currentCategory === category && toastVisible) { hideToast(); return; } toastQueue = toastQueue.filter(t => t.category !== category); } function hideToast() { toastVisible = false; currentMessage = ""; currentDetails = ""; currentCommand = ""; currentCategory = ""; hasDetails = false; currentLevel = levelInfo; toastTimer.stop(); resetToastState(); if (toastQueue.length > 0) { processQueue(); } } function processQueue() { if (toastQueue.length === 0) { return; } const toast = toastQueue.shift(); currentMessage = toast.message; currentLevel = toast.level; currentDetails = toast.details || ""; currentCommand = toast.command || ""; currentCategory = toast.category || ""; hasDetails = currentDetails.length > 0 || currentCommand.length > 0; toastVisible = true; resetToastState(); if (isStickyCategory(toast.category)) { toastTimer.stop(); } else if (toast.level === levelError && hasDetails) { toastTimer.interval = 8000; toastTimer.start(); } else { toastTimer.interval = toast.level === levelError ? 5000 : toast.level === levelWarn ? 3000 : 1500; toastTimer.start(); } } signal resetToastState function stopTimer() { toastTimer.stop(); } function restartTimer() { if (isStickyCategory(currentCategory)) { return; } if (hasDetails && currentLevel === levelError) { toastTimer.interval = 8000; toastTimer.restart(); } } function clearWallpaperError() { wallpaperErrorStatus = ""; } Timer { id: toastTimer interval: 5000 running: false repeat: false onTriggered: hideToast() } Connections { target: SessionData function onLoadErrorOccurred(file, message) { root.showError(I18n.tr("Failed to parse %1").arg(file), message); } } Connections { target: Processes function onToastRequested(severity, title, body, command, category) { switch (severity) { case root.levelWarn: root.showWarning(title, body, command, category); break; case root.levelError: root.showError(title, body, command, category); break; default: root.showInfo(title, body, command, category); break; } } function onToastCategoryDismissed(category) { root.dismissCategory(category); } } }