Add Remote Braille IPC to the host and both SDKs
remote.braille requests and events carry a typed op under a braille-stream lease: the controller's width and gestures for a controlled reader, rendered cells back to its child, and cells, local geometry and display gestures for a controller. Direction is validated per role; a full mailbox revokes the lease. No display owner is attached yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
477d860fb8
commit
2651b5e9bd
10 files changed
+476
-3
No files matched your search
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package tentaclewire
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const (
|
||||
RemoteBrailleMaxCells = 4096
|
||||
RemoteBrailleQueueSize = 16
|
||||
)
|
||||
|
||||
// RemoteBraille carries one Braille operation. Exactly one form is valid per
|
||||
// direction and role:
|
||||
//
|
||||
// - "size" (controlled child to host): Width is the controller's display
|
||||
// width; zero stops export.
|
||||
// - "gesture" (controlled child to host, or host to controller child):
|
||||
// Gesture is "pan-left", "pan-right" or "route"; Index is the route cell.
|
||||
// - "cells" (controller child to host, or host to controlled child): final
|
||||
// dot cells, 1 to 4,096 values of 0 to 255, never text to translate.
|
||||
// - "geometry" (controller child to host asks; host to controller child
|
||||
// answers with Width, zero meaning no local display).
|
||||
type RemoteBraille struct {
|
||||
Op string `json:"op"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Gesture string `json:"gesture,omitempty"`
|
||||
Index int `json:"index,omitempty"`
|
||||
Cells []int `json:"cells,omitempty"`
|
||||
}
|
||||
|
||||
type RemoteBrailleRequest struct {
|
||||
RemoteOperation
|
||||
Braille RemoteBraille `json:"braille"`
|
||||
}
|
||||
|
||||
func validCells(cells []int) bool {
|
||||
if len(cells) == 0 || len(cells) > RemoteBrailleMaxCells {
|
||||
return false
|
||||
}
|
||||
for _, cell := range cells {
|
||||
if cell < 0 || cell > 255 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ValidRemoteBraille checks representation only. Role and direction are
|
||||
// checked by ValidRemoteBrailleFor.
|
||||
func ValidRemoteBraille(value RemoteBraille) bool {
|
||||
switch value.Op {
|
||||
case "size":
|
||||
return value.Width >= 0 && value.Width <= RemoteBrailleMaxCells && value.Gesture == "" && value.Index == 0 && value.Cells == nil
|
||||
case "geometry":
|
||||
return value.Width >= 0 && value.Width <= RemoteBrailleMaxCells && value.Gesture == "" && value.Index == 0 && value.Cells == nil
|
||||
case "gesture":
|
||||
switch value.Gesture {
|
||||
case "pan-left", "pan-right":
|
||||
return value.Index == 0 && value.Width == 0 && value.Cells == nil
|
||||
case "route":
|
||||
return value.Index >= 0 && value.Index < RemoteBrailleMaxCells && value.Width == 0 && value.Cells == nil
|
||||
}
|
||||
return false
|
||||
case "cells":
|
||||
return validCells(value.Cells) && value.Width == 0 && value.Gesture == "" && value.Index == 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidRemoteBrailleFor checks direction: request is true for child-to-host.
|
||||
func ValidRemoteBrailleFor(role string, request bool, value RemoteBraille) bool {
|
||||
if !ValidRemoteBraille(value) {
|
||||
return false
|
||||
}
|
||||
switch {
|
||||
case role == "controlled" && request:
|
||||
return value.Op == "size" || value.Op == "gesture"
|
||||
case role == "controlled":
|
||||
return value.Op == "cells"
|
||||
case role == "controller" && request:
|
||||
return value.Op == "cells" || value.Op == "geometry" && value.Width == 0
|
||||
case role == "controller":
|
||||
return value.Op == "gesture" || value.Op == "geometry"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ValidRemoteBrailleRequest(request RemoteBrailleRequest) bool {
|
||||
return len(request.Lease) == 32 && request.Epoch != 0 && request.Sequence != 0 && ValidRemoteBraille(request.Braille)
|
||||
}
|
||||
|
||||
func (value *RemoteBraille) UnmarshalJSON(data []byte) error {
|
||||
var fields map[string]json.RawMessage
|
||||
if DecodeStrict(data, &fields) != nil || fields["op"] == nil {
|
||||
return ErrProtocol
|
||||
}
|
||||
type plain RemoteBraille
|
||||
var decoded plain
|
||||
if DecodeStrict(data, &decoded) != nil || !ValidRemoteBraille(RemoteBraille(decoded)) {
|
||||
return ErrProtocol
|
||||
}
|
||||
*value = RemoteBraille(decoded)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (request *RemoteBrailleRequest) UnmarshalJSON(data []byte) error {
|
||||
var fields map[string]json.RawMessage
|
||||
if DecodeStrict(data, &fields) != nil || len(fields) != 4 || fields["lease"] == nil || fields["epoch"] == nil ||
|
||||
fields["sequence"] == nil || fields["braille"] == nil {
|
||||
return ErrProtocol
|
||||
}
|
||||
type plain RemoteBrailleRequest
|
||||
var decoded plain
|
||||
if DecodeStrict(data, &decoded) != nil || !ValidRemoteBrailleRequest(RemoteBrailleRequest(decoded)) {
|
||||
return ErrProtocol
|
||||
}
|
||||
*request = RemoteBrailleRequest(decoded)
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user