blob: 9562c30d89568cb7d1085ee46cfdbedd75e3012e [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <string.h>
#include <errno.h>
#include "log.h"
#include "brl_driver.h"
#include "brldefs-mm.h"
#define PROBE_RETRY_LIMIT 2
#define PROBE_INPUT_TIMEOUT 1000
#define START_INPUT_TIMEOUT 1000
#define MM_KEY_GROUP_ENTRY(s,n) BRL_KEY_GROUP_ENTRY(MM, s, n)
#define MM_KEY_NAME_ENTRY(s,k,n) BRL_KEY_NAME_ENTRY(MM, s, k, n)
#define MM_SHIFT_KEY_ENTRY(k,n) MM_KEY_NAME_ENTRY(SHIFT, k, n)
#define MM_DOT_KEY_ENTRY(k) MM_KEY_NAME_ENTRY(DOT, k, "dot" #k)
#define MM_EDIT_KEY_ENTRY(k,n) MM_KEY_NAME_ENTRY(EDIT, k, n)
#define MM_ARROW_KEY_ENTRY(k,n) MM_KEY_NAME_ENTRY(ARROW, k, n)
#define MM_DISPLAY_KEY_ENTRY(k,n) MM_KEY_NAME_ENTRY(DISPLAY, k, n)
BEGIN_KEY_NAME_TABLE(shift)
MM_SHIFT_KEY_ENTRY(F1, "PanLeft"),
MM_SHIFT_KEY_ENTRY(F3, "Extension"),
MM_SHIFT_KEY_ENTRY(F4, "PanRight"),
MM_SHIFT_KEY_ENTRY(F1, "F1"),
MM_SHIFT_KEY_ENTRY(F2, "F2"),
MM_SHIFT_KEY_ENTRY(F3, "F3"),
MM_SHIFT_KEY_ENTRY(F4, "F4"),
MM_SHIFT_KEY_ENTRY(CONTROL, "Control"),
MM_SHIFT_KEY_ENTRY(ALT, "Alt"),
MM_SHIFT_KEY_ENTRY(SELECT, "Select"),
MM_SHIFT_KEY_ENTRY(READ, "Read"),
END_KEY_NAME_TABLE
BEGIN_KEY_NAME_TABLE(dot)
MM_DOT_KEY_ENTRY(1),
MM_DOT_KEY_ENTRY(2),
MM_DOT_KEY_ENTRY(3),
MM_DOT_KEY_ENTRY(4),
MM_DOT_KEY_ENTRY(5),
MM_DOT_KEY_ENTRY(6),
MM_DOT_KEY_ENTRY(7),
MM_DOT_KEY_ENTRY(8),
END_KEY_NAME_TABLE
BEGIN_KEY_NAME_TABLE(edit)
MM_EDIT_KEY_ENTRY(ESC, "Escape"),
MM_EDIT_KEY_ENTRY(INF, "Info"),
MM_EDIT_KEY_ENTRY(BS, "Backspace"),
MM_EDIT_KEY_ENTRY(DEL, "Delete"),
MM_EDIT_KEY_ENTRY(INS, "Insert"),
MM_EDIT_KEY_ENTRY(CHANGE, "Change"),
MM_EDIT_KEY_ENTRY(OK, "OK"),
MM_EDIT_KEY_ENTRY(SET, "Set"),
END_KEY_NAME_TABLE
BEGIN_KEY_NAME_TABLE(arrow)
MM_ARROW_KEY_ENTRY(UP, "ArrowUp"),
MM_ARROW_KEY_ENTRY(DOWN, "ArrowDown"),
MM_ARROW_KEY_ENTRY(LEFT, "ArrowLeft"),
MM_ARROW_KEY_ENTRY(RIGHT, "ArrowRight"),
END_KEY_NAME_TABLE
BEGIN_KEY_NAME_TABLE(route)
MM_KEY_GROUP_ENTRY(ROUTE, "RoutingKey"),
END_KEY_NAME_TABLE
BEGIN_KEY_NAME_TABLE(display)
MM_DISPLAY_KEY_ENTRY(BACKWARD, "Backward"),
MM_DISPLAY_KEY_ENTRY(FORWARD, "Forward"),
MM_DISPLAY_KEY_ENTRY(LSCROLL, "ScrollLeft"),
MM_DISPLAY_KEY_ENTRY(RSCROLL, "ScrollRight"),
END_KEY_NAME_TABLE
BEGIN_KEY_NAME_TABLES(pocket)
KEY_NAME_TABLE(shift),
KEY_NAME_TABLE(dot),
KEY_NAME_TABLE(edit),
KEY_NAME_TABLE(arrow),
KEY_NAME_TABLE(route),
KEY_NAME_TABLE(display),
END_KEY_NAME_TABLES
BEGIN_KEY_NAME_TABLES(smart)
KEY_NAME_TABLE(shift),
KEY_NAME_TABLE(dot),
KEY_NAME_TABLE(edit),
KEY_NAME_TABLE(arrow),
KEY_NAME_TABLE(route),
KEY_NAME_TABLE(display),
END_KEY_NAME_TABLES
DEFINE_KEY_TABLE(pocket)
DEFINE_KEY_TABLE(smart)
BEGIN_KEY_TABLE_LIST
&KEY_TABLE_DEFINITION(pocket),
&KEY_TABLE_DEFINITION(smart),
END_KEY_TABLE_LIST
typedef struct {
const char *identityPrefix;
const char *modelName;
const KeyTableDefinition *keyTableDefinition;
} ModelEntry;
static const ModelEntry modelEntry_pocket = {
.identityPrefix = "BMpk",
.modelName = "Braille Memo Pocket",
.keyTableDefinition = &KEY_TABLE_DEFINITION(pocket)
};
static const ModelEntry modelEntry_smart = {
.identityPrefix = "BMsmart",
.modelName = "Braille Memo Smart",
.keyTableDefinition = &KEY_TABLE_DEFINITION(smart)
};
static const ModelEntry *const modelEntries[] = {
&modelEntry_pocket,
&modelEntry_smart,
NULL
};
struct BrailleDataStruct {
const ModelEntry *model;
unsigned char forceRewrite;
unsigned char textCells[MM_MAXIMUM_LINE_LENGTH];
};
static const unsigned char sizeTable[] = {16, 24, 32, 40, 46};
static const unsigned char sizeCount = ARRAY_COUNT(sizeTable);
static int
isValidSize (unsigned char size) {
return memchr(sizeTable, size, sizeCount) != NULL;
}
static int
writeBytes (BrailleDisplay *brl, const unsigned char *bytes, size_t count) {
return writeBraillePacket(brl, NULL, bytes, count);
}
static int
writePacket (
BrailleDisplay *brl,
unsigned char code, unsigned char subcode,
const unsigned char *data, size_t length
) {
unsigned char bytes[sizeof(MM_CommandHeader) + length];
unsigned char *byte = bytes;
*byte++ = MM_HEADER_ID1;
*byte++ = MM_HEADER_ID2;
*byte++ = code;
*byte++ = subcode;
*byte++ = (length >> 0) & 0XFF;
*byte++ = (length >> 8) & 0XFF;
if (data) byte = mempcpy(byte, data, length);
return writeBytes(brl, bytes, byte-bytes);
}
static BraillePacketVerifierResult
verifyPacket (
BrailleDisplay *brl,
unsigned char *bytes, size_t size,
size_t *length, void *data
) {
unsigned char byte = bytes[size-1];
switch (size) {
case 1:
switch (byte) {
case MM_HEADER_ACK:
case MM_HEADER_NAK:
*length = 1;
break;
case MM_HEADER_ID1:
*length = sizeof(MM_CommandHeader);
break;
default:
if (isValidSize(byte)) {
*length = 1;
break;
}
return BRL_PVR_INVALID;
}
break;
case 2:
if (byte != MM_HEADER_ID2) return BRL_PVR_INVALID;
break;
case 5:
*length += byte;
break;
case 6:
*length += byte << 8;
break;
default:
break;
}
return BRL_PVR_INCLUDE;
}
static size_t
readBytes (BrailleDisplay *brl, void *packet, size_t size) {
return readBraillePacket(brl, NULL, packet, size, verifyPacket, NULL);
}
static size_t
readPacket (BrailleDisplay *brl, MM_CommandPacket *packet) {
return readBytes(brl, packet, sizeof(*packet));
}
static int
startDisplayMode (BrailleDisplay *brl) {
static const unsigned char data[] = {MM_BLINK_NO, 0};
if (writePacket(brl, MM_CMD_StartDisplayMode, 0, data, sizeof(data))) {
if (awaitBrailleInput(brl, START_INPUT_TIMEOUT)) {
MM_CommandPacket response;
size_t size = readPacket(brl, &response);
if (size) {
if (response.fields.header.id1 == MM_HEADER_ACK) return 1;
logUnexpectedPacket(response.bytes, size);
}
}
}
return 0;
}
static int
endDisplayMode (BrailleDisplay *brl) {
return writePacket(brl, MM_CMD_EndDisplayMode, 0, NULL, 0);
}
static int
sendBrailleData (BrailleDisplay *brl, const unsigned char *cells, size_t count) {
return writePacket(brl, MM_CMD_SendBrailleData, 0, cells, count);
}
static int
connectResource (BrailleDisplay *brl, const char *identifier) {
static const SerialParameters serialParameters = {
SERIAL_DEFAULT_PARAMETERS,
.baud = 9600
};
BEGIN_USB_STRING_LIST(usbManufacturers_10C4_EA60)
"Silicon Labs",
END_USB_STRING_LIST
BEGIN_USB_CHANNEL_DEFINITIONS
{ /* Pocket */
.vendor=0X10C4, .product=0XEA60,
.manufacturers = usbManufacturers_10C4_EA60,
.configuration=1, .interface=0, .alternative=0,
.inputEndpoint=1, .outputEndpoint=1,
.serial=&serialParameters
},
{ /* Smart */
.vendor=0X1148, .product=0X0301,
.configuration=1, .interface=1, .alternative=0,
.inputEndpoint=3, .outputEndpoint=2,
.serial=&serialParameters
},
END_USB_CHANNEL_DEFINITIONS
GioDescriptor descriptor;
gioInitializeDescriptor(&descriptor);
descriptor.serial.parameters = &serialParameters;
descriptor.usb.channelDefinitions = usbChannelDefinitions;
descriptor.bluetooth.channelNumber = 1;
if (connectBrailleResource(brl, identifier, &descriptor, NULL)) {
return 1;
}
return 0;
}
static int
detectModel (BrailleDisplay *brl, const MM_IdentityPacket *identity) {
const ModelEntry *const *model = modelEntries;
while (*model) {
const char *prefix = (*model)->identityPrefix;
if (strncmp(identity->hardwareName, prefix, strlen(prefix)) == 0) {
brl->data->model = *model;
logMessage(LOG_INFO, "detected model: %s", brl->data->model->modelName);
return 1;
}
model += 1;
}
logMessage(LOG_WARNING, "unrecognized model: %s", identity->hardwareName);
brl->data->model = &modelEntry_pocket;
logMessage(LOG_INFO, "assumed model: %s", brl->data->model->modelName);
return 0;
}
static int
writeIdentifyRequest (BrailleDisplay *brl) {
return writePacket(brl, MM_CMD_QueryIdentity, 0, NULL, 0);
}
static BraillePacketVerifierResult
verifyIdentityResponse (
BrailleDisplay *brl,
unsigned char *bytes, size_t size,
size_t *length, void *data
) {
unsigned char byte = bytes[size-1];
switch (size) {
case 1:
switch (byte) {
case 0X01:
*length = sizeof(MM_IdentityPacket);
break;
default:
return BRL_PVR_INVALID;
}
break;
default:
break;
}
return BRL_PVR_INCLUDE;
}
static size_t
readIdentityResponse (BrailleDisplay *brl, void *packet, size_t size) {
return readBraillePacket(brl, NULL, packet, size, verifyIdentityResponse, NULL);
}
static BrailleResponseResult
isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) {
const MM_IdentityPacket *identity = packet;
if ((identity->lineLength == 0) || (identity->lineLength > MM_MAXIMUM_LINE_LENGTH)) return BRL_RSP_UNEXPECTED;
if ((identity->lineCount == 0) || (identity->lineCount > MM_MAXIMUM_LINE_COUNT)) return BRL_RSP_UNEXPECTED;
{
const char *byte = identity->hardwareName;
const char *end = byte + sizeof(identity->hardwareName);
while (byte < end) {
if (!*byte) break;
if (!iswprint(*byte)) return BRL_RSP_UNEXPECTED;
byte += 1;
}
}
return BRL_RSP_DONE;
}
static int
brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
if ((brl->data = malloc(sizeof(*brl->data)))) {
memset(brl->data, 0, sizeof(*brl->data));
if (connectResource(brl, device)) {
MM_IdentityPacket identity;
if (probeBrailleDisplay(brl, PROBE_RETRY_LIMIT, NULL, PROBE_INPUT_TIMEOUT,
writeIdentifyRequest,
readIdentityResponse, &identity, sizeof(identity),
isIdentityResponse)) {
detectModel(brl, &identity);
brl->textColumns = identity.lineLength;
if (startDisplayMode(brl)) {
setBrailleKeyTable(brl, brl->data->model->keyTableDefinition);
MAKE_OUTPUT_TABLE(0X80, 0X40, 0X20, 0X08, 0X04, 0X02, 0X10, 0X01);
brl->data->forceRewrite = 1;
return 1;
}
}
disconnectBrailleResource(brl, NULL);
}
free(brl->data);
} else {
logMallocError();
}
return 0;
}
static void
brl_destruct (BrailleDisplay *brl) {
disconnectBrailleResource(brl, endDisplayMode);
if (brl->data) {
free(brl->data);
brl->data = NULL;
}
}
static int
brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
if (cellsHaveChanged(brl->data->textCells, brl->buffer, brl->textColumns, NULL, NULL, &brl->data->forceRewrite)) {
unsigned char cells[brl->textColumns];
translateOutputCells(cells, brl->data->textCells, brl->textColumns);
if (!sendBrailleData(brl, cells, sizeof(cells))) return 0;
}
return 1;
}
static int
brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
MM_CommandPacket packet;
size_t size;
while ((size = readPacket(brl, &packet))) {
if ((packet.fields.header.id1 == MM_HEADER_ID1) &&
(packet.fields.header.id2 == MM_HEADER_ID2)) {
switch (packet.fields.header.code) {
case MM_CMD_KeyCombination:
switch (packet.fields.data.keys.group) {
case MM_GRP_SHIFT:
if (!packet.fields.data.keys.value) {
enqueueKeys(brl, packet.fields.data.keys.shift, MM_GRP_SHIFT, 0);
continue;
}
break;
case MM_GRP_DOT:
case MM_GRP_EDIT:
case MM_GRP_ARROW:
case MM_GRP_DISPLAY:
{
KeyNumberSet shift = 0;
enqueueUpdatedKeys(brl, packet.fields.data.keys.shift, &shift, MM_GRP_SHIFT, 0);
enqueueKeys(brl, packet.fields.data.keys.value, packet.fields.data.keys.group, 0);
enqueueUpdatedKeys(brl, 0, &shift, MM_GRP_SHIFT, 0);
continue;
}
case MM_GRP_ROUTE:
{
unsigned char key = packet.fields.data.keys.value;
if ((key > 0) && (key <= brl->textColumns)) {
KeyNumberSet shift = 0;
enqueueUpdatedKeys(brl, packet.fields.data.keys.shift, &shift, MM_GRP_SHIFT, 0);
enqueueKey(brl, packet.fields.data.keys.group, key-1);
enqueueUpdatedKeys(brl, 0, &shift, MM_GRP_SHIFT, 0);
continue;
}
break;
}
default:
break;
}
break;
case MM_CMD_ShiftPress:
case MM_CMD_ShiftRelease:
continue;
default:
break;
}
}
logUnexpectedPacket(packet.bytes, size);
}
return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL;
}