// 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 }