A controlled reader renders accepted application frames in a new virtual-only cthulhu-braille mode at the controller's width and applies its pan and route gestures; reader UI, flashes and secure frames are never exported, and hidden frames are unroutable. A controller reader reports its display width, shows peer cells as an untranslated raw frame and forwards local pan and routing keys. The physical helper now reports its width and edge pans. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
89 lines
2.9 KiB
Go
89 lines
2.9 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
|
"git.stormux.org/storm/cthulhu/internal/braille"
|
|
)
|
|
|
|
type brailleRouter interface {
|
|
RouteBraille(context.Context, atspi.BrailleRoute) error
|
|
}
|
|
|
|
func (worker *brailleWorker) route(ctx context.Context, event braille.RoutingEvent) {
|
|
worker.mu.Lock()
|
|
if callback, gesture, index, ok := worker.remote_gesture_locked(event); ok {
|
|
worker.mu.Unlock()
|
|
callback(gesture, index) // Must not block.
|
|
return
|
|
}
|
|
if event.Pan != "" {
|
|
worker.mu.Unlock()
|
|
return
|
|
}
|
|
frame := worker.frame
|
|
length := utf8.RuneCountInString(frame.Text)
|
|
if worker.secure || worker.stopping || frame.ID == 0 || event.ID != frame.ID || event.Offset < 0 ||
|
|
(frame.Virtual == nil && event.Offset >= length) || (frame.Virtual != nil && event.Offset > length) {
|
|
worker.mu.Unlock()
|
|
return
|
|
}
|
|
if route := frame.Virtual; route != nil {
|
|
if worker.ocr == nil || event.Offset < route.Start || event.Offset > route.End {
|
|
worker.mu.Unlock()
|
|
return
|
|
}
|
|
routeCtx, cancel := context.WithTimeout(ctx, time.Second)
|
|
worker.routeCancel = cancel
|
|
worker.mu.Unlock()
|
|
defer cancel()
|
|
worker.ocr.RouteBraille(routeCtx, route.Generation, route.SourceStart+event.Offset-route.Start)
|
|
return
|
|
}
|
|
if worker.router == nil && frame.Document == nil {
|
|
worker.mu.Unlock()
|
|
return
|
|
}
|
|
var target *braille.TextRoute
|
|
for _, route := range frame.Routes {
|
|
if event.Offset >= route.Start && event.Offset < route.End {
|
|
target = &route
|
|
break
|
|
}
|
|
}
|
|
if target == nil {
|
|
worker.mu.Unlock()
|
|
return
|
|
}
|
|
routeCtx, cancel := context.WithTimeout(ctx, time.Second)
|
|
worker.routeCancel = cancel
|
|
worker.mu.Unlock()
|
|
defer cancel()
|
|
if document := frame.Document; document != nil {
|
|
if worker.document == nil {
|
|
return
|
|
}
|
|
worker.logger.Debug("routing document braille", "frame_id", frame.ID, "document_epoch", document.Epoch,
|
|
"source_bus", target.BusName, "source_path", target.Path, "source_start", target.SourceStart,
|
|
"native_offset", target.SourceStart+min(event.Offset-target.Start, target.TextLength))
|
|
err := worker.document.RouteDocumentBraille(routeCtx, document.Epoch,
|
|
atspi.NewObjectID(document.FocusBus, document.FocusPath), atspi.NewObjectID(document.DocumentBus, document.DocumentPath),
|
|
atspi.NewObjectID(target.BusName, target.Path), target.SourceText, target.SourceStart,
|
|
target.SourceStart+min(event.Offset-target.Start, target.TextLength))
|
|
if err != nil && routeCtx.Err() == nil {
|
|
worker.logger.Debug("document braille routing rejected", "error", err)
|
|
}
|
|
return
|
|
}
|
|
err := worker.router.RouteBraille(routeCtx, atspi.BrailleRoute{
|
|
Focus: atspi.NewObjectID(target.BusName, target.Path), Line: target.SourceText, Start: target.SourceStart,
|
|
Offset: target.SourceStart + min(event.Offset-target.Start, target.TextLength),
|
|
})
|
|
if err != nil && routeCtx.Err() == nil {
|
|
worker.logger.Debug("braille routing rejected", "error", err)
|
|
}
|
|
}
|