Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,94 @@
|
||||
// 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/input"
|
||||
)
|
||||
|
||||
// Inline completions are selected by the browser. Echo the inserted portion
|
||||
// outside that selection; it contains the user's edit, not the suggested suffix.
|
||||
func autocompleteInsertion(event atspi.Event) string {
|
||||
text := []rune(event.Payload)
|
||||
start, end := event.Source.SelectionStart, event.Source.SelectionEnd
|
||||
if end <= start || end <= event.Detail1 || start >= event.Detail1+len(text) {
|
||||
return event.Payload
|
||||
}
|
||||
left := max(0, start-event.Detail1)
|
||||
right := min(len(text), end-event.Detail1)
|
||||
return string(text[:left]) + string(text[right:])
|
||||
}
|
||||
|
||||
func recentInput(source any) input.Activity {
|
||||
if provider, ok := source.(interface{ RecentInput() input.Activity }); ok {
|
||||
return provider.RecentInput()
|
||||
}
|
||||
return input.Activity{}
|
||||
}
|
||||
|
||||
type autocompleteEcho struct {
|
||||
sequence uint64
|
||||
source atspi.ObjectID
|
||||
}
|
||||
|
||||
// autocompleteAvailability presents the selected address when it changes.
|
||||
// Chromium can send several insertion and popup events for the same suggestion.
|
||||
type autocompleteAvailability struct {
|
||||
entry atspi.ObjectID
|
||||
lastAddress string
|
||||
}
|
||||
|
||||
func supportsAutocomplete(snapshot atspi.Snapshot) bool {
|
||||
return snapshot.Autocomplete || snapshot.Role == "autocomplete"
|
||||
}
|
||||
|
||||
func (availability *autocompleteAvailability) focus(event atspi.Event) {
|
||||
if !isFocusGainEvent(event) || event.Source.AutocompletePopup {
|
||||
return
|
||||
}
|
||||
entry := atspi.ObjectID{}
|
||||
if supportsAutocomplete(event.Source) {
|
||||
entry = event.Source.ID()
|
||||
}
|
||||
if entry != availability.entry {
|
||||
availability.entry = entry
|
||||
availability.lastAddress = ""
|
||||
}
|
||||
}
|
||||
|
||||
func (availability *autocompleteAvailability) offered(event atspi.Event, activity input.Activity) string {
|
||||
if !availability.entry.Valid() || event.Type != atspi.TextInsertedEvent ||
|
||||
!supportsAutocomplete(event.Source) || event.Source.ID() != availability.entry ||
|
||||
(activity.Intent != input.TypingIntent && activity.Intent != input.DeleteIntent) {
|
||||
return ""
|
||||
}
|
||||
address := strings.TrimSpace(event.Source.Text)
|
||||
if event.Source.SelectionEnd <= event.Source.SelectionStart ||
|
||||
event.Source.SelectionEnd > len([]rune(address)) || address == "" ||
|
||||
address == availability.lastAddress {
|
||||
return ""
|
||||
}
|
||||
availability.lastAddress = address
|
||||
return address
|
||||
}
|
||||
|
||||
func (echo *autocompleteEcho) insertion(event atspi.Event, activity input.Activity) string {
|
||||
text := autocompleteInsertion(event)
|
||||
if text == "" && activity.Sequence != 0 &&
|
||||
(activity.Sequence != echo.sequence || event.Source.ID() != echo.source) &&
|
||||
event.Detail1 == event.Source.SelectionStart {
|
||||
// Chromium can retain an unchanged typed prefix and emit only a replacement
|
||||
// of the selected suffix. Recover that character from accessible text once
|
||||
// for this key press; a normal insertion already covers the other case.
|
||||
text = event.Source.CompletionPrefixCharacter
|
||||
}
|
||||
if text != "" {
|
||||
echo.sequence, echo.source = activity.Sequence, event.Source.ID()
|
||||
}
|
||||
return text
|
||||
}
|
||||
Reference in new issue
Block a user