Files
cthulhu/internal/app/browser_button_names.go

70 lines
2.9 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
"git.stormux.org/storm/cthulhu/internal/input"
)
const browserButtonNameDelay = 350 * time.Millisecond
// The event loop retains only the latest focused web button name. Focus and
// Braille update immediately; speech waits for the label to settle. Document
// generation and input identity prevent a delayed name from interrupting a
// later action, including leaving and returning to the same object.
type pendingBrowserButtonName struct {
pendingPresentation
document atspi.ObjectID
generation uint64
inputSequence uint64
}
func (handler *accessibilityEventHandler) defer_browser_button_name(event atspi.Event, activity input.Activity) bool {
browser := handler.browser
if event.Type != atspi.AccessibleNameEvent || event.Source.LiveRegion || event.Source.Editable ||
browser == nil || !browser.ready || !browser.document.Valid() ||
event.Source.BusName != browser.document.BusName || event.Source.ID() != browser.focus ||
event.Source.ID() != handler.state.focusedID {
return false
}
switch event.Source.Role {
case "button", "push button", "toggle button":
default:
return false
}
pending := &handler.state.pending.browserButtonName
// Duplicate notifications must not postpone an already settled label.
if pending.event.Source.ID() == event.Source.ID() && pending.event.Source.Name == event.Source.Name &&
pending.document == browser.document && pending.generation == browser.generation &&
pending.inputSequence == activity.TitleInterruptionSequence {
return true
}
pending.schedule(event, input.NoIntent, browserButtonNameDelay)
pending.document, pending.generation = browser.document, browser.generation
pending.inputSequence = activity.TitleInterruptionSequence
handler.logger.Debug("coalesced focused web button name", "bus", event.Source.BusName, "source_path", event.Source.Path)
return true
}
func (handler *accessibilityEventHandler) flush_browser_button_name() {
pending := &handler.state.pending.browserButtonName
document, generation, inputSequence := pending.document, pending.generation, pending.inputSequence
event, _ := pending.take()
browser := handler.browser
if event.Type == "" || handler.focusGate.Locked() || handler.focusGate.Suspended() ||
browser == nil || !browser.ready || browser.document != document || browser.generation != generation ||
browser.RecentInput().TitleInterruptionSequence != inputSequence || browser.focus != event.Source.ID() ||
handler.state.focusedID != event.Source.ID() || handler.state.current.ID() != event.Source.ID() ||
handler.state.owner.window != eventWindow(event) {
return
}
// State events may have refreshed the same control during the quiet period.
event.Source = handler.state.current
handler.speak(event)
}