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>
120 lines
3.3 KiB
Go
120 lines
3.3 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package braille
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
// VirtualComputerTable is the explicit table used to turn ordinary
|
|
// (uncontracted) frames into cells for a remote display. The physical display
|
|
// leaves that translation to BRLTTY; a virtual display has no BRLTTY, so the
|
|
// policy is stated here rather than guessed from local configuration.
|
|
const VirtualComputerTable = "en-us-comp8.ctb"
|
|
|
|
// VirtualCursorDots marks the cursor cell with dots 7 and 8. It is a static
|
|
// overlay, not BRLTTY's configured cursor shape or blink timing.
|
|
const VirtualCursorDots = 0xc0
|
|
|
|
// VirtualRequest is one line from the parent. Exactly one field is set.
|
|
type VirtualRequest struct {
|
|
Width *int `json:"width,omitempty"`
|
|
Frame *Frame `json:"frame,omitempty"`
|
|
Pan string `json:"pan,omitempty"`
|
|
Route *int `json:"route,omitempty"`
|
|
}
|
|
|
|
// VirtualResponse is one line to the parent: new cells, or a routing event
|
|
// against the frame ID the parent supplied.
|
|
type VirtualResponse struct {
|
|
Cells []int `json:"cells,omitempty"`
|
|
Route *RoutingEvent `json:"route,omitempty"`
|
|
}
|
|
|
|
// RunVirtual renders frames into a virtual viewport without opening BrlAPI.
|
|
// It shares the physical helper's viewport, liblouis translation, wrapping,
|
|
// panning and routing maps, so remote cells match local behaviour apart from
|
|
// the explicit uncontracted table and static cursor. It exits on EOF.
|
|
func RunVirtual(ctx context.Context, input io.Reader, output io.Writer) error {
|
|
scanner := bufio.NewScanner(input)
|
|
scanner.Buffer(make([]byte, 4096), 65536)
|
|
encoder := json.NewEncoder(output)
|
|
v := viewport{}
|
|
var current *Frame
|
|
emit := func() error {
|
|
if v.width == 0 || current == nil {
|
|
return nil
|
|
}
|
|
cells, err := v.rawCells(VirtualCursorDots)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
values := make([]int, len(cells))
|
|
for index, cell := range cells {
|
|
values[index] = int(cell)
|
|
}
|
|
return encoder.Encode(VirtualResponse{Cells: values})
|
|
}
|
|
render := func() error {
|
|
if v.width == 0 || current == nil {
|
|
return nil
|
|
}
|
|
frame := *current
|
|
if !frame.Contract {
|
|
frame.Contract, frame.Table = true, VirtualComputerTable
|
|
}
|
|
if err := v.setFrame(frame); err != nil {
|
|
return err
|
|
}
|
|
return emit()
|
|
}
|
|
for scanner.Scan() {
|
|
if ctx.Err() != nil {
|
|
return nil
|
|
}
|
|
var request VirtualRequest
|
|
if err := json.Unmarshal(scanner.Bytes(), &request); err != nil {
|
|
return errors.New("invalid virtual braille request")
|
|
}
|
|
var err error
|
|
switch {
|
|
case request.Width != nil:
|
|
if *request.Width < 0 || *request.Width > 4096 {
|
|
return errors.New("invalid virtual braille width")
|
|
}
|
|
v.width = *request.Width
|
|
err = render()
|
|
case request.Frame != nil:
|
|
frame := *request.Frame
|
|
current = &frame
|
|
err = render()
|
|
case request.Pan == "left" || request.Pan == "right":
|
|
key := PanLeft
|
|
if request.Pan == "right" {
|
|
key = PanRight
|
|
}
|
|
if current != nil && v.width > 0 && v.pan(key) {
|
|
err = emit()
|
|
}
|
|
case request.Route != nil:
|
|
if current != nil && v.width > 0 {
|
|
if event, ok := v.route(RouteBase + Key(*request.Route)); ok {
|
|
err = encoder.Encode(VirtualResponse{Route: &event})
|
|
}
|
|
}
|
|
default:
|
|
return errors.New("invalid virtual braille request")
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return scanner.Err()
|
|
}
|