44 lines
1.6 KiB
Go
44 lines
1.6 KiB
Go
// 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,
|
|
})
|
|
}
|