70 lines
2.9 KiB
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)
|
|
}
|