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,133 @@
|
||||
package review
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/atspi"
|
||||
)
|
||||
|
||||
type documentBrailleRequest struct {
|
||||
ctx context.Context
|
||||
focus, document, target atspi.ObjectID
|
||||
line string
|
||||
start, offset int
|
||||
done chan error
|
||||
}
|
||||
|
||||
// RouteDocumentBraille shares the navigation FIFO. The display session may
|
||||
// wait, but input and event callbacks never wait for native caret placement.
|
||||
func (worker *Worker) RouteDocumentBraille(ctx context.Context, epoch uint64, focus, document, target atspi.ObjectID, line string, start, offset int) error {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
route := &documentBrailleRequest{ctx: ctx, focus: focus, document: document, target: target, line: line, start: start, offset: offset, done: make(chan error, 1)}
|
||||
select {
|
||||
case worker.requests <- request{brailleRoute: route, documentEpoch: epoch, queuedAt: time.Now()}:
|
||||
default:
|
||||
return ErrRequestQueueFull
|
||||
}
|
||||
select {
|
||||
case err := <-route.done:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (worker *Worker) route_document_braille(ctx context.Context, cursor *DocumentCursor, request request) (result Result) {
|
||||
route := request.brailleRoute
|
||||
result = Result{DocumentBrailleRoute: true, DocumentCaretCommand: CaretReadLine, DocumentFocus: route.focus, DocumentEpoch: request.documentEpoch}
|
||||
defer func() { route.done <- result.Err }()
|
||||
if cursor == nil || worker.focus() != route.focus || !worker.CurrentDocumentResult(result) {
|
||||
result.Err = atspi.ErrStaleObject
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, worker.captureTimeout)
|
||||
stop := context.AfterFunc(route.ctx, cancel)
|
||||
defer stop()
|
||||
defer cancel()
|
||||
if result.Err = route.ctx.Err(); result.Err != nil {
|
||||
return
|
||||
}
|
||||
target, err := cursor.tree.Reference(route.target)
|
||||
if err != nil {
|
||||
result.Err = err
|
||||
return
|
||||
}
|
||||
owner, err := cursor.tree.DocumentContext(target)
|
||||
if err != nil || owner.TopLevel.ID() != route.document {
|
||||
result.Err = atspi.ErrStaleObject
|
||||
return
|
||||
}
|
||||
router, ok := worker.nativeCapturerFor(route.focus).(interface {
|
||||
RouteBraille(context.Context, atspi.BrailleRoute) error
|
||||
})
|
||||
if !ok {
|
||||
result.Err = atspi.ErrNoDocumentCaret
|
||||
return
|
||||
}
|
||||
reader, ok := worker.nativeCapturerFor(route.focus).(DocumentCaretReader)
|
||||
if !ok {
|
||||
result.Err = atspi.ErrNoDocumentCaret
|
||||
return
|
||||
}
|
||||
doc, err := cursor.tree.Document(ctx, target)
|
||||
if err != nil {
|
||||
result.Err = err
|
||||
return
|
||||
}
|
||||
node, err := native_caret_node(ctx, reader, doc, route.target, route.offset)
|
||||
if err != nil {
|
||||
result.Err = err
|
||||
return
|
||||
}
|
||||
if node.Hidden {
|
||||
result.Err = atspi.ErrStaleObject
|
||||
return
|
||||
}
|
||||
result.Err = router.RouteBraille(ctx, atspi.BrailleRoute{Focus: route.focus, Position: target, Line: route.line, Start: route.start, Offset: route.offset})
|
||||
if result.Err != nil || worker.focus() != route.focus || !worker.CurrentDocumentResult(result) {
|
||||
if result.Err == nil {
|
||||
result.Err = atspi.ErrStaleObject
|
||||
}
|
||||
return
|
||||
}
|
||||
// Keep the document anchor; a routed inline child is not native focus.
|
||||
cursor.mu.Lock()
|
||||
if len(cursor.places) == 0 || cursor.active.ID() != route.document {
|
||||
cursor.mu.Unlock()
|
||||
result.Err = atspi.ErrStaleObject
|
||||
return
|
||||
}
|
||||
place := &cursor.places[len(cursor.places)-1]
|
||||
place.object, place.offset, place.origin = node.Reference, route.offset, CaretNavigation
|
||||
place.tableCell = atspi.ObjectID{}
|
||||
snapshot, err := cursor.tree.Resolve(node.Reference)
|
||||
result.DocumentPosition = DocumentPosition{Document: owner.TopLevel, Object: node.Reference, Snapshot: snapshot, Offset: route.offset, Origin: CaretNavigation}
|
||||
cursor.mu.Unlock()
|
||||
result.Err, result.Moved = err, err == nil
|
||||
result.DocumentCaretContext = owner.Ancestors
|
||||
if result.Moved && worker.documentBraille.Load() {
|
||||
// Optional assembly refresh preserves surrounding inline fragments while
|
||||
// retaining the exact routed offset. It never repeats caret placement.
|
||||
deadline, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
defer cancel()
|
||||
indices, _ := caret_node_indices(doc.Nodes, node.Reference)
|
||||
position := -1
|
||||
for index, candidate := range indices {
|
||||
if doc.Nodes[candidate].Reference == node.Reference {
|
||||
position = index
|
||||
break
|
||||
}
|
||||
}
|
||||
if position >= 0 {
|
||||
parts, err := collect_visual_line(deadline, reader, doc, indices, visual_line_owners(doc.Nodes), position, route.offset, true)
|
||||
if err == nil && len(parts) > 1 {
|
||||
line, _, _ := assemble_visual_line(cursor.tree, doc, parts)
|
||||
result.DocumentCaretLine = &line
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in new issue
Block a user