feat(notify): Desktop-Benachrichtigung für den Ruhezeit-Toast
quiet_mode = "toast" war bisher nur eine Protokollzeile. Jetzt zeigt der Daemon eine Desktop-Benachrichtigung — über die Bordmittel des Systems, exec-basiert wie osbuiltin, ohne Bibliothek und ohne CGO: notify-send (Linux), osascript display notification (macOS), PowerShell Toast (Windows; Text base64, kein Quoting-Problem). Best effort: fehlt das Werkzeug, bleibt es bei der Protokollzeile plus Start-Warnung; `talk doctor` zeigt den Weg unter „Toast". Der Aufruf läuft asynchron, ein hängender Notification-Daemon blockiert die Queue nie. Queue: OnToast-Hook, gefeuert bei Toast-Verwurf beim Eintritt und im Worker (nie unter dem Mutex). Tests: notify-Paket mit fake notify-send im PATH, Prozessgrenze: Ruhezeit + toast → fake notify-send bekommt Titel und Text, der Provider keinen Treffer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
a9ca32bed9
commit
9e2aa506a6
7 files changed
+279
-6
No files matched your search
@@ -115,6 +115,7 @@ type Queue struct {
|
||||
limit *RateLimiter
|
||||
speak Speaker
|
||||
log io.Writer
|
||||
toast func(Item) // Ruhezeit-Toast (optional; gesetzt vor dem ersten Enqueue)
|
||||
|
||||
mu sync.Mutex
|
||||
notEmpty *sync.Cond // Signal: neues Item; Broadcast: Close/Slot frei
|
||||
@@ -146,6 +147,18 @@ func New(laws Laws, speak Speaker, log io.Writer) *Queue {
|
||||
return q
|
||||
}
|
||||
|
||||
// OnToast setzt die Benachrichtigung, die in der Ruhezeit mit quiet_mode =
|
||||
// "toast" statt Audio ausgelöst wird (Daemon: Desktop-Notification). Ohne
|
||||
// Setzen bleibt es bei der Protokollzeile. Vor dem ersten Enqueue setzen.
|
||||
func (q *Queue) OnToast(fn func(Item)) { q.toast = fn }
|
||||
|
||||
// fireToast ruft die Toast-Benachrichtigung auf (nie unter dem Mutex).
|
||||
func (q *Queue) fireToast(item Item) {
|
||||
if q.toast != nil {
|
||||
q.toast(item)
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue stellt item in die Warteschlange und liefert die Aufnahme-
|
||||
// Entscheidung: Aufgenommene tragen ihre ID (bei leerer ID neu vergeben:
|
||||
// kurz und zufällig). Ruhezeit-Verwurf/Toast und low über dem Fenster-Budget
|
||||
@@ -176,6 +189,7 @@ func (q *Queue) Enqueue(item Item) (Admission, error) {
|
||||
q.mu.Unlock()
|
||||
q.logf("queue: ansage (%q, priorität %s) durch toast ersetzt — ruhezeit %s aktiv (modus toast)",
|
||||
item.Text, item.Priority, q.laws.Quiet)
|
||||
q.fireToast(item)
|
||||
return Admission{Discarded: DiscardQuietHours, Toast: true}, nil
|
||||
case verdictDiscardRate:
|
||||
q.mu.Unlock()
|
||||
@@ -365,6 +379,7 @@ func (q *Queue) loop() {
|
||||
case verdictToast:
|
||||
q.logf("queue: ansage %s (%q) durch toast ersetzt — ruhezeit %s aktiv (modus toast)",
|
||||
item.ID, item.Text, q.laws.Quiet)
|
||||
q.fireToast(item)
|
||||
case verdictDiscardExpired:
|
||||
q.logf("queue: ansage %s (%q) verworfen — %s gewartet, älter als max_age %s",
|
||||
item.ID, item.Text, time.Since(item.enqueued).Round(time.Second), q.laws.MaxAge)
|
||||
|
||||
Reference in new issue
Block a user