135 lines
4.5 KiB
Go
135 lines
4.5 KiB
Go
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.DocumentView(ctx, target)
|
|
if err != nil {
|
|
result.Err = err
|
|
return
|
|
}
|
|
node, caretOffset, 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, caretOffset, CaretNavigation
|
|
place.tableCell = atspi.ObjectID{}
|
|
remember_text_parent(doc, place)
|
|
snapshot, err := cursor.tree.Resolve(node.Reference)
|
|
result.DocumentPosition = DocumentPosition{Document: owner.TopLevel, Object: node.Reference, Snapshot: snapshot, Offset: caretOffset, 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 := caretNodeRanges(doc.Nodes)
|
|
position := -1
|
|
for index, candidate := range indices {
|
|
if doc.Nodes[candidate.index].Reference == node.Reference && caretOffset >= candidate.start && caretOffset < candidate.end {
|
|
position = index
|
|
break
|
|
}
|
|
}
|
|
if position >= 0 {
|
|
parts, err := collect_visual_line(deadline, reader, doc, indices, visual_line_owners(doc.Nodes), position, caretOffset, true)
|
|
if err == nil && len(parts) > 1 {
|
|
line, _, _ := assemble_visual_line(cursor.tree, doc, parts)
|
|
result.DocumentCaretLine = &line
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|