pragma Singleton import QtQuick import Quickshell import Quickshell.Io import qs.Commons Singleton { id: root // Queue of pending toast messages property var messageQueue: [] property bool isShowingToast: false // Reference to all toast instances (set by ToastOverlay) property var allToasts: [] // Properties for command checking property var commandCheckCallback: null property string commandCheckSuccessMessage: "" property string commandCheckFailMessage: "" // Properties for command running property var commandRunCallback: null property string commandRunSuccessMessage: "" property string commandRunFailMessage: "" // Properties for delayed toast property string delayedToastMessage: "" property string delayedToastType: "notice" // Process for command checking Process { id: commandCheckProcess command: ["which", "test"] onExited: function (exitCode) { if (exitCode === 0) { showNotice(commandCheckSuccessMessage) if (commandCheckCallback) commandCheckCallback() } else { showWarning(commandCheckFailMessage) } } stdout: StdioCollector {} stderr: StdioCollector {} } // Process for command running Process { id: commandRunProcess command: ["echo", "test"] onExited: function (exitCode) { if (exitCode === 0) { showNotice(commandRunSuccessMessage) if (commandRunCallback) commandRunCallback() } else { showWarning(commandRunFailMessage) } } stdout: StdioCollector {} stderr: StdioCollector {} } // Timer for delayed toast Timer { id: delayedToastTimer interval: 1000 repeat: false onTriggered: { showToast(delayedToastMessage, delayedToastType) } } // Methods to show different types of messages function showNotice(label, description = "", persistent = false, duration = 3000) { showToast(label, description, "notice", persistent, duration) } function showWarning(label, description = "", persistent = false, duration = 4000) { showToast(label, description, "warning", persistent, duration) } // Utility function to check if a command exists and show appropriate toast function checkCommandAndToast(command, successMessage, failMessage, onSuccess = null) { // Store callback for use in the process commandCheckCallback = onSuccess commandCheckSuccessMessage = successMessage commandCheckFailMessage = failMessage // Start the command check process commandCheckProcess.command = ["which", command] commandCheckProcess.running = true } // Simple function to show a random toast (useful for testing or fun messages) function showRandomToast() { var messages = [{ "type": "notice", "text": "Everything is working smoothly!" }, { "type": "notice", "text": "Noctalia is looking great today!" }, { "type": "notice", "text": "Your desktop setup is amazing!" }, { "type": "warning", "text": "Don't forget to take a break!" }, { "type": "notice", "text": "Configuration saved successfully!" }, { "type": "warning", "text": "Remember to backup your settings!" }] var randomMessage = messages[Math.floor(Math.random() * messages.length)] showToast(randomMessage.text, randomMessage.type) } // Convenience function for quick notifications function quickNotice(message) { showNotice(message, false, 2000) // Short duration } function quickWarning(message) { showWarning(message, false, 3000) // Medium duration } // Generic command runner with toast feedback function runCommandWithToast(command, args, successMessage, failMessage, onSuccess = null) { // Store callback for use in the process commandRunCallback = onSuccess commandRunSuccessMessage = successMessage commandRunFailMessage = failMessage // Start the command run process commandRunProcess.command = [command].concat(args || []) commandRunProcess.running = true } // Check if a file/directory exists function checkPathAndToast(path, successMessage, failMessage, onSuccess = null) { runCommandWithToast("test", ["-e", path], successMessage, failMessage, onSuccess) } // Show toast after a delay (useful for delayed feedback) function delayedToast(message, type = "notice", delayMs = 1000) { delayedToastMessage = message delayedToastType = type delayedToastTimer.interval = delayMs delayedToastTimer.restart() } // Generic method to show a toast function showToast(label, description = "", type = "notice", persistent = false, duration = 3000) { var toastData = { "label": label, "description": description, "type": type, "persistent": persistent, "duration": duration, "timestamp": Date.now() } // If there's already a toast showing, instantly start hide animation and show new one if (isShowingToast) { // Instantly start hide animation of current toast for (var i = 0; i < allToasts.length; i++) { allToasts[i].hide() } // Clear the queue since we're showing the new toast immediately messageQueue = [] } // Add to queue messageQueue.push(toastData) // Always process immediately for instant display processQueue() } // Process the message queue function processQueue() { if (messageQueue.length === 0 || allToasts.length === 0) { // Added this so we don't accidentally get duplicate toasts // if it causes issues, remove it and we'll find a different solution if (allToasts.length === 0 && messageQueue.length > 0) { messageQueue = [] } isShowingToast = false return } var toastData = messageQueue.shift() isShowingToast = true // Configure and show toast on all screens for (var i = 0; i < allToasts.length; i++) { var toast = allToasts[i] toast.label = toastData.label toast.description = toastData.description toast.type = toastData.type toast.persistent = toastData.persistent toast.duration = toastData.duration toast.show() } } // Called when a toast is dismissed function onToastDismissed() { // Check if all toasts are dismissed var allDismissed = true for (var i = 0; i < allToasts.length; i++) { if (allToasts[i].visible) { allDismissed = false break } } if (allDismissed) { isShowingToast = false // Small delay before showing next toast Qt.callLater(function () { processQueue() }) } } // Clear all pending messages function clearQueue() { messageQueue = [] } // Hide current toast function hideCurrentToast() { if (isShowingToast) { for (var i = 0; i < allToasts.length; i++) { allToasts[i].hide() } } } }