75 lines
3.0 KiB
Go
75 lines
3.0 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"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
|
"git.stormux.org/storm/cthulhu/internal/config"
|
|
"git.stormux.org/storm/cthulhu/internal/input"
|
|
"git.stormux.org/storm/cthulhu/internal/presentation"
|
|
)
|
|
|
|
// handleBrowserTab presents a selected tab while leaving native focus in chrome.
|
|
func (handler *accessibilityEventHandler) handleBrowserTab(event atspi.Event, activity input.Activity) bool {
|
|
state := handler.state
|
|
browser := handler.browser
|
|
objectReview := handler.objectReview
|
|
speech := handler.speech
|
|
brailleOutput := handler.brailleOutput
|
|
live := handler.live
|
|
if event.Source.BrowserTab && (event.Type == atspi.SelectedStateEvent || event.Type == atspi.SelectionChangedEvent) {
|
|
// Python's web page-switch handler presents the tab without moving
|
|
// the locus of focus. Repeated Ctrl+T can keep the same address
|
|
// control focused and produce no further focus events at all.
|
|
selected := event.Type == atspi.SelectedStateEvent && event.Detail1 != 0 ||
|
|
event.Type == atspi.SelectionChangedEvent && event.Source.Selected &&
|
|
strings.EqualFold(event.Source.ToolkitName, "Gecko")
|
|
if !selected || event.Source.StatesKnown && !event.Source.Selected ||
|
|
event.Source.Defunct || !state.owner.containsLoad(event) || event.Source.ID() == state.selectedBrowserTab {
|
|
return true
|
|
}
|
|
state.selectedBrowserTab = event.Source.ID()
|
|
state.tabFocusWindow, state.tabFocusSequence = eventWindow(event), activity.Sequence
|
|
state.pending.caret.clear()
|
|
state.documentLineFocus, state.documentLineRequest, state.documentLineEvent = atspi.ObjectID{}, 0, atspi.Event{}
|
|
state.loadingDocument, state.loadedDocument = atspi.ObjectID{}, atspi.ObjectID{}
|
|
state.chromeLoadedDocument, state.chromeLoadedWindow = atspi.ObjectID{}, atspi.ObjectID{}
|
|
if browser != nil {
|
|
browser.clear()
|
|
}
|
|
if objectReview != nil {
|
|
focus := atspi.ObjectID{}
|
|
if state.current.Autocomplete {
|
|
focus = state.current.ID()
|
|
}
|
|
objectReview.ResetDocument(focus)
|
|
}
|
|
spoken := presentation.FocusSpeechWithOptions(event.Source, state.presentationOptions)
|
|
text := spoken.Text
|
|
if text != "" {
|
|
speech.Enqueue(speechRequest{Text: text, Chunks: spoken.Chunks, Role: event.Source.Role, Cause: event.Type})
|
|
}
|
|
if brailleOutput != nil {
|
|
brailleOptions := config.DefaultSettings().Braille
|
|
textAttributes := config.DefaultTextAttributes()
|
|
if live != nil {
|
|
settings := live.Load()
|
|
brailleOptions, textAttributes = settings.Braille, settings.TextAttributes
|
|
}
|
|
brailleOutput.EnqueueFrame(brailleFocusFrameWithOptions(event.Source, brailleOptions, textAttributes))
|
|
}
|
|
return true
|
|
}
|
|
if event.Source.Autocomplete && eventWindow(event) == state.tabFocusWindow &&
|
|
activity.Sequence != 0 && activity.Sequence == state.tabFocusSequence &&
|
|
(event.Type == atspi.CaretEvent || event.Type == "object:state-changed:expanded") {
|
|
return true // Automatic address reset must not cut off the selected tab.
|
|
}
|
|
return false
|
|
}
|