Files
Chase CichorzandClaude Opus 5.5 551ef86d5f Export and display Remote Braille through the reader
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>
2026-11-06 16:06:38 -05:00

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()
}