Files
cthulhu/internal/app/event_text_selection.go

75 lines
2.8 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/input"
)
func (handler *accessibilityEventHandler) handleSelectionAndClipboard(event atspi.Event, activity input.Activity, intent input.Intent) bool {
state := handler.state
logger := handler.logger
speech := handler.speech
if activity.Clipboard == input.PasteAction && activity.Sequence == state.lastPasteSequence &&
event.Source.ID() == state.lastPasteID && time.Since(state.lastPasteAt) < time.Second &&
(event.Type == atspi.TextInsertedEvent || event.Type == atspi.TextDeletedEvent ||
event.Type == atspi.TextSelectionChangedEvent || event.Type == atspi.CaretEvent) {
if event.Type == atspi.CaretEvent {
state.suppressNextCaretPath = ""
} else if event.Type == atspi.TextSelectionChangedEvent {
state.selection.changed(event.Source)
}
logger.Debug("skipped text event following paste confirmation", "type", event.Type)
return true
}
if event.Type == atspi.TextSelectionChangedEvent {
if event.Source.ID() != state.focusedID || !event.Source.Editable || event.Source.ReadOnly ||
event.Source.Role == "password text" || event.Source.Autocomplete {
return true
}
fragmentLimit := selectionFragmentLimit
if activity.Selecting && intent == input.LineIntent {
fragmentLimit = lineSelectionFragmentLimit
}
message := state.selection.changedWithLimit(event.Source, fragmentLimit)
if message != "" {
state.pending.caret.clear()
state.selectionCaretID = event.Source.ID()
state.selectionCaretSequence = activity.Sequence
state.selectionCaretAt = time.Now()
if activity.Clipboard != input.CutAction && activity.Clipboard != input.PasteAction {
speech.Enqueue(speechRequest{Text: message, Role: "system", Cause: event.Type})
}
}
return true
}
if activity.Sequence != 0 && activity.Sequence != state.lastClipboardSequence &&
event.Source.ID() == state.focusedID && event.Source.Editable && !event.Source.ReadOnly &&
!event.Source.Autocomplete && event.Source.Role != "password text" {
message := ""
if event.Type == atspi.TextDeletedEvent && activity.Clipboard == input.CutAction {
message = "cut"
} else if event.Type == atspi.TextInsertedEvent && activity.Clipboard == input.PasteAction {
message = "pasted from clipboard"
}
if message != "" {
state.lastClipboardSequence = activity.Sequence
if activity.Clipboard == input.PasteAction {
state.lastPasteSequence = activity.Sequence
state.lastPasteID = event.Source.ID()
state.lastPasteAt = time.Now()
state.pending.caret.clear()
}
speech.Enqueue(speechRequest{Text: message, Role: "system", Cause: "clipboard"})
return true
}
}
return false
}