import QtQuick
import Quickshell
import Quickshell.Io
import "Model.js" as Model
Item {
id: root
property var shell: null
readonly property string home: Quickshell.env("HOME")
readonly property string configDir: home + "/.config/notify"
readonly property string mainConfigPath: configDir + "/notify.json"
readonly property string stateDir: home + "/.local/state/omarchy/notify-desktop"
readonly property int pollSeconds: {
var n = parseInt(Quickshell.env("NOTIFY_POLL_SECONDS"))
return (isFinite(n) && n > 0) ? n : 5
}
readonly property var envServer: ({
url: Quickshell.env("NOTIFY_URL") || "http://127.0.0.1:8080",
pin: Quickshell.env("NOTIFY_PIN") || "123456",
name: ""
})
property var servers: [envServer]
property string activeProfile: ""
property var scanQueue: []
property var currentServer: null
readonly property var icons: ({ heart: "\uF004", emergency: "\uF06A" })
function scanCommand() {
var dir = Model.shellQuote(root.configDir)
var main = Model.shellQuote(root.mainConfigPath)
return "mkdir -p " + dir + "; printf '@@CONFIG\\n'; cat " + main + " 2>/dev/null || true; printf '\\n'; "
+ "for f in " + dir + "/*.json; do "
+ "[ \"$f\" = " + main + " ] && continue; "
+ "[ -f \"$f\" ] || continue; printf '@@FILE %s\\n' \"${f##*/}\"; cat \"$f\"; printf '\\n'; "
+ "done"
}
function applyConfig(content) {
var dump = Model.parseDump(content)
var profile = Model.byName(dump.profiles, dump.active)
if (!profile && dump.profiles.length > 0) profile = dump.profiles[0]
root.activeProfile = profile ? profile.name : ""
root.servers = profile && profile.servers.length > 0 ? profile.servers : [root.envServer]
root.scanQueue = root.servers.slice()
}
function nextScan() {
if (root.scanQueue.length === 0) return
root.currentServer = root.scanQueue.shift()
var server = root.currentServer
var pin = (server && (server.pin || server.api_key)) || ""
pollProc.command = [
"curl", "-sS", "-m", "10",
"-H", "X-PIN: " + pin,
server.url + "/api/notifications/scan"
]
pollProc.running = true
}
Process {
id: configScanProc
command: ["sh", "-c", root.scanCommand()]
stdout: StdioCollector {
waitForEnd: true
onStreamFinished: {
root.applyConfig(text)
root.nextScan()
}
}
}
Process {
id: ensureDirsProc
command: ["mkdir", "-p", root.stateDir, root.configDir]
onExited: configScanProc.running = true
}
Component.onCompleted: ensureDirsProc.running = true
FileView {
id: latestFile
path: root.stateDir + "/latest.json"
atomicWrites: true
printErrors: false
}
Timer {
id: pollTimer
interval: root.pollSeconds * 1000
running: true
repeat: true
triggeredOnStart: true
onTriggered: if (!configScanProc.running && !pollProc.running) configScanProc.running = true
}
function labelFor(server) { return server && server.name ? server.name : "Notify" }
function showNotification(server, n) {
var argv = ["omarchy-notification-send", "-u", n.kind === "emergency" ? "critical" : "normal"]
if (n.kind === "emergency") argv.push("-g", root.icons.emergency)
argv.push("--app-name", n.kind === "emergency" ? "notify-send" : root.labelFor(server), n.message || "Notification")
try { Quickshell.execDetached(argv); return true } catch (e) { return false }
}
function handleScan(server, body) {
if (!server || !body) return
var items
try { items = JSON.parse(body) } catch (e) { return }
if (!Array.isArray(items) || items.length === 0) return
var shownIds = []
var lastShown = null
for (var i = 0; i < items.length; i++) {
var n = items[i]
if (n && typeof n.id === "number" && root.showNotification(server, n)) {
shownIds.push(n.id)
lastShown = n
}
}
if (shownIds.length > 0) {
latestFile.setText(JSON.stringify({ kind: String(lastShown.kind || ""), message: String(lastShown.message || ""), server: root.labelFor(server), ts: Date.now() }) + "\n")
var pin = (server && (server.pin || server.api_key)) || ""
ackProc.command = ["curl", "-sS", "-m", "10", "-X", "POST", "-H", "X-PIN: " + pin, "-H", "Content-Type: application/json", "-d", JSON.stringify({ ids: shownIds }), server.url + "/api/notifications/sent"]
ackProc.running = true
}
}
Process {
id: pollProc
property string buffer: ""
stdout: SplitParser { onRead: function(data) { pollProc.buffer += data } }
onRunningChanged: if (running) pollProc.buffer = ""
onExited: function(exitCode) {
if (exitCode === 0) root.handleScan(root.currentServer, pollProc.buffer)
pollProc.buffer = ""
if (!ackProc.running) root.nextScan()
}
}
Process {
id: ackProc
command: ["true"]
onExited: root.nextScan()
}
}