Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,225 @@
|
||||
//go:build brlapi && cgo
|
||||
|
||||
package braille
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lbrlapi
|
||||
#include <brlapi.h>
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct cth_brl cth_brl;
|
||||
struct cth_brl {
|
||||
brlapi_handle_t *handle;
|
||||
unsigned int columns, rows;
|
||||
int opened, entered, failed;
|
||||
};
|
||||
|
||||
// The default BrlAPI handler terminates the process and can dump a packet.
|
||||
// Record a failure instead; never print display contents or packet payloads.
|
||||
static void cth_exception(brlapi_handle_t *handle, int error,
|
||||
brlapi_packetType_t type, const void *packet, size_t size) {
|
||||
cth_brl *owner = brlapi__getClientData(handle);
|
||||
if (owner) owner->failed = 1;
|
||||
}
|
||||
|
||||
static cth_brl *cth_alloc(void) {
|
||||
cth_brl *s = calloc(1, sizeof(*s));
|
||||
if (!s) return NULL;
|
||||
s->handle = calloc(1, brlapi_getHandleSize());
|
||||
if (!s->handle) { free(s); return NULL; }
|
||||
return s;
|
||||
}
|
||||
|
||||
static int cth_open(cth_brl *s, int routing) {
|
||||
// NULL settings use BrlAPI's normal local connection/authentication.
|
||||
if (brlapi__openConnection(s->handle, NULL, NULL) < 0) return -1;
|
||||
s->opened = 1;
|
||||
brlapi__setClientData(s->handle, s);
|
||||
brlapi__setExceptionHandler(s->handle, cth_exception);
|
||||
if (brlapi__getDisplaySize(s->handle, &s->columns, &s->rows) < 0 || s->failed) return -1;
|
||||
if (!s->columns || !s->rows || s->columns > 4096 || s->rows > 4096/s->columns) return -2;
|
||||
// Screen readers take the root of their terminal path. BrlAPI prepends
|
||||
// WINDOWPATH or XDG_VTNR, so this follows the active graphical session.
|
||||
if (brlapi__enterTtyModeWithPath(s->handle, NULL, 0, NULL) < 0 || s->failed) return -3;
|
||||
s->entered = 1;
|
||||
if (brlapi__ignoreAllKeys(s->handle) < 0 || s->failed) return -4;
|
||||
brlapi_keyCode_t keys[] = {
|
||||
BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_FWINLT,
|
||||
BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_FWINRT
|
||||
};
|
||||
if (brlapi__acceptKeys(s->handle, brlapi_rangeType_key, keys, 2) < 0 || s->failed) return -4;
|
||||
if (routing) {
|
||||
brlapi_keyCode_t route = BRLAPI_KEY_TYPE_CMD | BRLAPI_KEY_CMD_ROUTE;
|
||||
if (brlapi__acceptKeys(s->handle, brlapi_rangeType_command, &route, 1) < 0 || s->failed) return -4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cth_write(cth_brl *s, const char *text, int size, int cursor,
|
||||
const unsigned char *andMask, const unsigned char *orMask) {
|
||||
if (s->failed) return -1;
|
||||
brlapi_writeArguments_t args = BRLAPI_WRITEARGUMENTS_INITIALIZER;
|
||||
args.regionBegin = 1;
|
||||
args.regionSize = s->columns * s->rows;
|
||||
args.text = text;
|
||||
args.textSize = size;
|
||||
args.charset = "UTF-8";
|
||||
args.cursor = cursor;
|
||||
args.andMask = andMask;
|
||||
args.orMask = orMask;
|
||||
int result = brlapi__write(s->handle, &args);
|
||||
return s->failed ? -1 : result;
|
||||
}
|
||||
|
||||
static int cth_read(cth_brl *s, char *error, size_t errorSize) {
|
||||
if (s->failed) return -1;
|
||||
brlapi_keyCode_t code;
|
||||
int result = brlapi__readKey(s->handle, 0, &code);
|
||||
if (s->failed) return -1;
|
||||
if (result < 0 && brlapi_errno == BRLAPI_ERROR_LIBCERR && brlapi_libcerrno == EINTR) return 0;
|
||||
if (result < 0) brlapi_strerror_r(&brlapi_error, error, errorSize);
|
||||
if (result <= 0) return result;
|
||||
if ((code & BRLAPI_KEY_TYPE_MASK) != BRLAPI_KEY_TYPE_CMD) return 0;
|
||||
if ((code & BRLAPI_KEY_CMD_BLK_MASK) == BRLAPI_KEY_CMD_ROUTE)
|
||||
return 0x10000 + (code & BRLAPI_KEY_CMD_ARG_MASK);
|
||||
switch (code & BRLAPI_KEY_CODE_MASK) {
|
||||
case BRLAPI_KEY_CMD_FWINLT: return 1;
|
||||
case BRLAPI_KEY_CMD_FWINRT: return 2;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int cth_close(cth_brl *s) {
|
||||
int result = 0;
|
||||
if (s->entered && !s->failed) result = brlapi__leaveTtyMode(s->handle);
|
||||
if (s->failed) result = -1;
|
||||
if (s->opened) brlapi__closeConnection(s->handle);
|
||||
free(s->handle);
|
||||
free(s);
|
||||
return result;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type native struct {
|
||||
mu sync.Mutex
|
||||
session *C.cth_brl
|
||||
cells int
|
||||
routing bool
|
||||
}
|
||||
|
||||
func New() Backend { return &native{} }
|
||||
|
||||
// NewRouting claims routing keys as well as pan commands for the live helper.
|
||||
func NewRouting() Backend { return &native{routing: true} }
|
||||
|
||||
func (n *native) Open() (Geometry, error) {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
if n.session != nil {
|
||||
return Geometry{}, errors.New("BrlAPI connection already open")
|
||||
}
|
||||
s := C.cth_alloc()
|
||||
if s == nil {
|
||||
return Geometry{}, errors.New("allocate BrlAPI connection")
|
||||
}
|
||||
routing := C.int(0)
|
||||
if n.routing {
|
||||
routing = 1
|
||||
}
|
||||
if code := C.cth_open(s, routing); code != 0 {
|
||||
C.cth_close(s)
|
||||
switch code {
|
||||
case -2:
|
||||
return Geometry{}, errors.New("unsupported braille display geometry")
|
||||
case -3:
|
||||
return Geometry{}, errors.New("acquire BrlAPI terminal path; check WINDOWPATH, XDG_VTNR, and terminal ownership")
|
||||
case -4:
|
||||
return Geometry{}, errors.New("configure BrlAPI commands")
|
||||
default:
|
||||
return Geometry{}, errors.New("BrlAPI connection or display query failed; check BRLTTY and authorization")
|
||||
}
|
||||
}
|
||||
n.session = s
|
||||
g := Geometry{Columns: int(s.columns), Rows: int(s.rows)}
|
||||
n.cells, _ = g.Cells()
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (n *native) Write(text string, cursor int) error {
|
||||
return n.WriteWithDots(text, cursor, nil)
|
||||
}
|
||||
|
||||
func (n *native) WriteWithDots(text string, cursor int, dots []byte) error {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
if n.session == nil {
|
||||
return errors.New("BrlAPI connection is closed")
|
||||
}
|
||||
if !utf8.ValidString(text) || utf8.RuneCountInString(text) != n.cells || cursor < 0 || cursor > n.cells {
|
||||
return errors.New("invalid braille frame")
|
||||
}
|
||||
if len(dots) != 0 && len(dots) != n.cells {
|
||||
return errors.New("invalid braille indicator length")
|
||||
}
|
||||
value := C.CString(text)
|
||||
defer C.free(unsafe.Pointer(value))
|
||||
andMask := make([]byte, n.cells)
|
||||
orMask := make([]byte, n.cells)
|
||||
for index, cell := range []rune(text) {
|
||||
andMask[index] = 0xff
|
||||
if len(dots) != 0 {
|
||||
orMask[index] = dots[index]
|
||||
}
|
||||
if cell >= 0x2800 && cell <= 0x28ff {
|
||||
andMask[index] = 0
|
||||
orMask[index] |= byte(cell - 0x2800)
|
||||
}
|
||||
}
|
||||
if C.cth_write(n.session, value, C.int(len(text)), C.int(cursor),
|
||||
(*C.uchar)(unsafe.Pointer(&andMask[0])), (*C.uchar)(unsafe.Pointer(&orMask[0]))) < 0 {
|
||||
return errors.New("BrlAPI display write failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *native) ReadKey() (Key, error) {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
if n.session == nil {
|
||||
return NoKey, errors.New("BrlAPI connection is closed")
|
||||
}
|
||||
var detail [256]C.char
|
||||
key := C.cth_read(n.session, &detail[0], C.size_t(len(detail)))
|
||||
if key < 0 {
|
||||
if detail[0] != 0 {
|
||||
return NoKey, errors.New("BrlAPI key read failed: " + C.GoString(&detail[0]))
|
||||
}
|
||||
return NoKey, errors.New("BrlAPI key read failed after protocol exception")
|
||||
}
|
||||
return Key(key), nil
|
||||
}
|
||||
|
||||
func (n *native) Close() error {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
if n.session == nil {
|
||||
return nil
|
||||
}
|
||||
result := C.cth_close(n.session)
|
||||
n.session = nil
|
||||
n.cells = 0
|
||||
if result < 0 {
|
||||
return errors.New("release BrlAPI terminal failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user