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,118 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package atspi
|
||||
|
||||
/*
|
||||
#include <atspi/atspi.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func (adapter *Adapter) readNativeDocumentLine(ctx context.Context, id ObjectID, snapshot Snapshot, text *C.AtspiText, offset, count int) (DocumentCaretText, error) {
|
||||
source := nativeReviewText{text: text}
|
||||
line, err := source.line(ctx, offset)
|
||||
if err != nil {
|
||||
return DocumentCaretText{}, err
|
||||
}
|
||||
result := DocumentCaretText{Start: line.Start, End: line.End, Text: line.Text, CharacterCount: count}
|
||||
if snapshot.Editable || snapshot.Role == "password text" || snapshot.HTMLTag == "pre" || snapshot.HTMLTag == "code" || count > maxReviewLineCharacters ||
|
||||
len(strings.Fields(line.Text)) > 1 || line.Start == 0 && line.End == count {
|
||||
return result, nil
|
||||
}
|
||||
if cached, ok := adapter.documentLineCache[id]; ok && cached.CharacterCount == count {
|
||||
if cached.End > 0 {
|
||||
return cached, nil
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
adapter.treeMutex.Lock()
|
||||
tree := adapter.documentTree
|
||||
adapter.treeMutex.Unlock()
|
||||
if tree != nil {
|
||||
if ref, err := tree.Reference(id); err == nil {
|
||||
if owner, err := tree.DocumentContext(ref); err == nil {
|
||||
for _, ancestor := range owner.Ancestors {
|
||||
if ancestor.HTMLTag == "pre" || ancestor.HTMLTag == "code" || strings.Contains(" "+ancestor.XMLRoles+" ", " code ") {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var nativeErr *C.GError
|
||||
value := C.atspi_text_get_text(text, 0, C.gint(count), &nativeErr)
|
||||
if value != nil {
|
||||
defer C.g_free(C.gpointer(value))
|
||||
}
|
||||
if nativeErr != nil {
|
||||
C.g_error_free(nativeErr)
|
||||
return result, nil
|
||||
}
|
||||
content := strings.ToValidUTF8(C.GoString(value), "�")
|
||||
if utf8.RuneCountInString(content) != count {
|
||||
return result, nil
|
||||
}
|
||||
probe, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
repair, probeErr := documentLinesAreTokens(probe, source, content)
|
||||
cancel()
|
||||
if probeErr != nil {
|
||||
return result, ctx.Err()
|
||||
}
|
||||
if adapter.documentLineCache == nil || len(adapter.documentLineCache) >= 64 {
|
||||
adapter.documentLineCache = make(map[ObjectID]DocumentCaretText)
|
||||
}
|
||||
cached := DocumentCaretText{CharacterCount: count}
|
||||
if repair {
|
||||
cached.End, cached.Text = count, content
|
||||
result = cached
|
||||
}
|
||||
adapter.documentLineCache[id] = cached
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Repair only owned web text, on the GLib owner. Native selections use the
|
||||
// same logical line as virtual browsing, including braille cursor placement.
|
||||
func (adapter *Adapter) repairNativeCaretLine(source *C.AtspiAccessible, snapshot *Snapshot, offset int) {
|
||||
if !snapshot.TextCaretKnown || snapshot.Editable || snapshot.Role == "password text" || offset < 0 {
|
||||
return
|
||||
}
|
||||
adapter.treeMutex.Lock()
|
||||
tree := adapter.documentTree
|
||||
adapter.treeMutex.Unlock()
|
||||
if tree == nil {
|
||||
return
|
||||
}
|
||||
ref, err := tree.Reference(snapshot.ID())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = tree.DocumentContext(ref); err != nil {
|
||||
return
|
||||
}
|
||||
text := C.atspi_accessible_get_text_iface(source)
|
||||
if text == nil {
|
||||
return
|
||||
}
|
||||
defer C.g_object_unref(C.gpointer(text))
|
||||
count := int(C.atspi_text_get_character_count(text, nil))
|
||||
if count <= 0 || offset > count {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
line, err := adapter.readNativeDocumentLine(ctx, snapshot.ID(), *snapshot, text, min(offset, count-1), count)
|
||||
if err != nil || line.Start < 0 || line.End <= line.Start {
|
||||
return
|
||||
}
|
||||
snapshot.Text = sanitizeSnapshotField(line.Text)
|
||||
snapshot.BrailleLine, snapshot.BrailleLineStart, snapshot.BrailleCaret = line.Text, line.Start, offset
|
||||
snapshot.CharacterCount = count
|
||||
}
|
||||
Reference in new issue
Block a user