/* * 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-2021 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 . */ #include "prologue.h" #include #include #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; }