Build the Panama Hyprland desktop
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
pragma Singleton
|
||||
|
||||
// Curated shell feedback. This is deliberately not a notification store: one
|
||||
// event is visible, at most four wait behind it, and expired entries vanish.
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property int ambientPriority: 10
|
||||
readonly property int importantPriority: 50
|
||||
readonly property int criticalPriority: 90
|
||||
|
||||
property var activeEvent: null
|
||||
property var pendingEvents: []
|
||||
property int sequence: 0
|
||||
|
||||
readonly property bool active: root.activeEvent !== null
|
||||
readonly property int queueLength: root.pendingEvents.length
|
||||
|
||||
signal eventPublished(var event)
|
||||
signal eventDismissed(string key)
|
||||
|
||||
Timer {
|
||||
id: expiryTimer
|
||||
repeat: false
|
||||
onTriggered: root.dismiss()
|
||||
}
|
||||
|
||||
function publish(candidate: var): bool {
|
||||
const event = root.normalize(candidate);
|
||||
if (!event)
|
||||
return false;
|
||||
|
||||
// DND is allowed to quiet device ambience, but it must never hide a
|
||||
// privacy transition or the result of something the user initiated.
|
||||
if (Notifs.doNotDisturb && event.priority < root.importantPriority)
|
||||
return false;
|
||||
|
||||
if (root.activeEvent && root.activeEvent.key === event.key) {
|
||||
root.activeEvent = event;
|
||||
root.armExpiry();
|
||||
root.eventPublished(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
const withoutEquivalent = root.pendingEvents.filter(item => item.key !== event.key);
|
||||
if (!root.activeEvent || event.priority > root.activeEvent.priority) {
|
||||
if (root.activeEvent)
|
||||
withoutEquivalent.push(root.activeEvent);
|
||||
root.pendingEvents = root.trimQueue(withoutEquivalent);
|
||||
root.activeEvent = event;
|
||||
root.armExpiry();
|
||||
} else {
|
||||
withoutEquivalent.push(event);
|
||||
root.pendingEvents = root.trimQueue(withoutEquivalent);
|
||||
}
|
||||
|
||||
root.eventPublished(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
function dismiss(): void {
|
||||
if (!root.activeEvent)
|
||||
return;
|
||||
const dismissedKey = root.activeEvent.key;
|
||||
expiryTimer.stop();
|
||||
root.activeEvent = null;
|
||||
root.eventDismissed(dismissedKey);
|
||||
root.advance();
|
||||
}
|
||||
|
||||
function invoke(): void {
|
||||
if (!root.activeEvent)
|
||||
return;
|
||||
|
||||
const action = root.activeEvent.actionId;
|
||||
const data = root.activeEvent.actionData;
|
||||
root.dismiss();
|
||||
|
||||
if (action === "focus-workspace") {
|
||||
FocusSession.activateWorkspace();
|
||||
} else if (action === "open-workspace") {
|
||||
const workspaceId = Number(data);
|
||||
if (Number.isInteger(workspaceId) && workspaceId > 0)
|
||||
Hyprland.dispatch(`hl.dsp.focus({ workspace = ${workspaceId} })`);
|
||||
} else if (action === "open-activity") {
|
||||
ShellState.open("activity");
|
||||
} else if (action === "open-path" && data) {
|
||||
Quickshell.execDetached(["xdg-open", data]);
|
||||
}
|
||||
}
|
||||
|
||||
function reset(): void {
|
||||
expiryTimer.stop();
|
||||
root.activeEvent = null;
|
||||
root.pendingEvents = [];
|
||||
}
|
||||
|
||||
function normalize(candidate: var): var {
|
||||
if (!candidate || !candidate.key || !candidate.title)
|
||||
return null;
|
||||
|
||||
root.sequence++;
|
||||
const priority = Number(candidate.priority ?? root.ambientPriority);
|
||||
const duration = Number(candidate.durationMs ?? 3200);
|
||||
return {
|
||||
key: String(candidate.key),
|
||||
icon: String(candidate.icon ?? "dialog-information-symbolic"),
|
||||
glyph: String(candidate.glyph ?? ""),
|
||||
title: String(candidate.title),
|
||||
detail: String(candidate.detail ?? ""),
|
||||
tone: String(candidate.tone ?? "accent"),
|
||||
priority: Number.isFinite(priority) ? priority : root.ambientPriority,
|
||||
durationMs: Math.max(1200, Number.isFinite(duration) ? duration : 3200),
|
||||
actionId: String(candidate.actionId ?? ""),
|
||||
actionData: String(candidate.actionData ?? ""),
|
||||
monitorName: String(candidate.monitorName ?? ""),
|
||||
sequence: root.sequence
|
||||
};
|
||||
}
|
||||
|
||||
function trimQueue(events: var): var {
|
||||
const sorted = events.slice().sort((a, b) => {
|
||||
if (a.priority !== b.priority)
|
||||
return b.priority - a.priority;
|
||||
return a.sequence - b.sequence;
|
||||
});
|
||||
return sorted.slice(0, 4);
|
||||
}
|
||||
|
||||
function advance(): void {
|
||||
if (root.pendingEvents.length === 0)
|
||||
return;
|
||||
const next = root.pendingEvents[0];
|
||||
root.pendingEvents = root.pendingEvents.slice(1);
|
||||
root.activeEvent = next;
|
||||
root.armExpiry();
|
||||
}
|
||||
|
||||
function armExpiry(): void {
|
||||
expiryTimer.stop();
|
||||
if (!root.activeEvent)
|
||||
return;
|
||||
expiryTimer.interval = root.activeEvent.durationMs;
|
||||
expiryTimer.start();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user