// Cthulhu screen reader. // Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "strings" "time" "git.stormux.org/storm/cthulhu/internal/atspi" ) // presentAnnouncement speaks an object:announcement payload, mirroring Orca: // the provider's text is presented verbatim. All provider announcements queue // under the reader's input-driven interruption policy. Announcements commonly come from // providers that never own focus (a compositor announcing a workspace // switch, for example), so no focus or foreground ownership is required and // none is taken. The caller's lock gate has already run; the speech output // gate still rejects the request while the screen is locked. func (handler *accessibilityEventHandler) presentAnnouncement(event atspi.Event) { text := strings.TrimSpace(event.Payload) if text == "" { return } state := handler.state // Some providers force re-emission of an unchanged message by altering // trailing whitespace. Treat one trimmed repeat from the same provider // inside the live-region dedupe window as the same announcement. if text == state.lastAnnouncement && event.Source.BusName == state.lastAnnouncementBus && time.Since(state.lastAnnouncementAt) < 300*time.Millisecond { return } state.lastAnnouncement, state.lastAnnouncementBus, state.lastAnnouncementAt = text, event.Source.BusName, time.Now() handler.speech.Enqueue(speechRequest{ Text: text, Role: "system", Cause: atspi.AnnouncementEvent, Append: true, BypassFocusGate: true, }) }