100 lines
4.2 KiB
Go
100 lines
4.2 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 (
|
|
"time"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
|
"git.stormux.org/storm/cthulhu/internal/presentation"
|
|
)
|
|
|
|
type guiTablePresentation struct {
|
|
options presentation.Options
|
|
table atspi.ObjectID
|
|
sequence uint64
|
|
newDescription bool
|
|
skip bool
|
|
}
|
|
|
|
func (state *eventLoopState) prepareGUITable(event atspi.Event, sequence uint64) guiTablePresentation {
|
|
if event.Type == atspi.WindowActivatedEvent {
|
|
state.tableDescriptionWindow = atspi.ObjectID{}
|
|
state.tableDescriptionText = ""
|
|
state.lastTable = atspi.ObjectID{}
|
|
state.lastTableCell = atspi.ObjectID{}
|
|
} else if isFocusGainEvent(event) && event.Source.Role != "table" && event.Source.Role != "table cell" {
|
|
state.lastTable = atspi.ObjectID{}
|
|
state.lastTableCell = atspi.ObjectID{}
|
|
}
|
|
result := guiTablePresentation{
|
|
options: state.presentationOptions,
|
|
table: atspi.NewObjectID(event.Source.BusName, event.Source.TablePath),
|
|
sequence: sequence,
|
|
}
|
|
result.newDescription = state.presentationOptions.SpeakDescription && event.Window.Valid() &&
|
|
(event.Source.Role == "table" || event.Source.Role == "table cell") &&
|
|
event.WindowDescription != "" && event.WindowDescription != event.Source.Description &&
|
|
(state.tableDescriptionWindow != event.Window || state.tableDescriptionText != event.WindowDescription)
|
|
sameRow := event.Source.Role == "table cell" &&
|
|
event.Source.TablePositionKnown && result.table.Valid() && result.table == state.lastTable &&
|
|
event.Source.TableRowIndex == state.lastTableRow
|
|
if sameRow {
|
|
unchangedRow := presentation.FocusTextWithOptions(event.Source, state.presentationOptions) == state.lastTableText
|
|
if unchangedRow || event.Source.ID() != state.lastTableCell {
|
|
result.options.ReadFullRowInGUITable = false
|
|
if result.options.Reading != nil {
|
|
reading := *result.options.Reading
|
|
reading.ReadFullRowInDocumentTable, reading.ReadFullRowInSpreadSheet = false, false
|
|
result.options.Reading = &reading
|
|
}
|
|
}
|
|
sameAction := sequence != 0 && sequence == state.lastTableSequence ||
|
|
sequence == 0 && time.Since(state.lastTableAt) < 75*time.Millisecond
|
|
result.skip = sameAction && !result.newDescription && unchangedRow &&
|
|
(state.lastTableFullRow || event.Source.TableColumnIndex == state.lastTableColumn)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (state *eventLoopState) decorateGUITable(event atspi.Event, spoken presentation.Speech, result guiTablePresentation) (presentation.Speech, bool) {
|
|
if event.Source.Role == "table cell" && event.Source.GUITable && result.table.Valid() &&
|
|
result.table != state.lastTable && event.Source.TableName != "" &&
|
|
(isFocusGainEvent(event) || isManagedDescendantEvent(event.Type)) &&
|
|
event.Type != atspi.WindowActivatedEvent {
|
|
spoken = spoken.Prepend(event.Source.TableName, ", ")
|
|
}
|
|
appendFocus := isFocusGainEvent(event) && event.Type != atspi.WindowActivatedEvent &&
|
|
state.tableDescriptionWindow.Valid() && event.Window == state.tableDescriptionWindow &&
|
|
time.Since(state.tableDescriptionAt) < 300*time.Millisecond
|
|
if result.newDescription {
|
|
if event.Type != atspi.WindowActivatedEvent {
|
|
spoken = spoken.Append(event.WindowDescription, ", ")
|
|
}
|
|
state.tableDescriptionWindow = event.Window
|
|
state.tableDescriptionText = event.WindowDescription
|
|
state.tableDescriptionAt = time.Now()
|
|
}
|
|
return spoken, appendFocus
|
|
}
|
|
|
|
func (state *eventLoopState) rememberGUITable(event atspi.Event, result guiTablePresentation) {
|
|
if event.Source.Role == "table cell" && event.Source.TablePositionKnown && result.table.Valid() {
|
|
state.lastTable = result.table
|
|
state.lastTableCell = event.Source.ID()
|
|
state.lastTableText = presentation.FocusTextWithOptions(event.Source, state.presentationOptions)
|
|
state.lastTableRow = event.Source.TableRowIndex
|
|
state.lastTableColumn = event.Source.TableColumnIndex
|
|
state.lastTableSequence = result.sequence
|
|
state.lastTableAt = time.Now()
|
|
state.lastTableFullRow = presentation.FullRowEnabled(event.Source, result.options) && event.Source.TableRowText != ""
|
|
} else if event.Source.Role == "table" && isFocusGainEvent(event) {
|
|
state.lastTable = event.Source.ID()
|
|
state.lastTableCell = atspi.ObjectID{}
|
|
state.lastTableRow = -1
|
|
}
|
|
}
|