| /* |
| * 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-ce.h" |
| |
| #define PROBE_RETRY_LIMIT 2 |
| #define PROBE_INPUT_TIMEOUT 1000 |
| #define MAXIMUM_RESPONSE_SIZE (0XFF + 4) |
| #define MAXIMUM_CELL_COUNT 140 |
| |
| BEGIN_KEY_NAME_TABLE(navigation) |
| KEY_NAME_ENTRY(CE_KEY_PadLeft1, "PadLeft1"), |
| KEY_NAME_ENTRY(CE_KEY_PadUp1, "PadUp1"), |
| KEY_NAME_ENTRY(CE_KEY_PadCenter1, "PadCenter1"), |
| KEY_NAME_ENTRY(CE_KEY_PadDown1, "PadDown1"), |
| KEY_NAME_ENTRY(CE_KEY_PadRight1, "PadRight1"), |
| |
| KEY_NAME_ENTRY(CE_KEY_LeftUpper1, "LeftUpper1"), |
| KEY_NAME_ENTRY(CE_KEY_LeftMiddle1, "LeftMiddle1"), |
| KEY_NAME_ENTRY(CE_KEY_LeftLower1, "LeftLower1"), |
| KEY_NAME_ENTRY(CE_KEY_RightUpper1, "RightUpper1"), |
| KEY_NAME_ENTRY(CE_KEY_RightMiddle1, "RightMiddle1"), |
| KEY_NAME_ENTRY(CE_KEY_RightLower1, "RightLower1"), |
| |
| KEY_NAME_ENTRY(CE_KEY_PadLeft2, "PadLeft2"), |
| KEY_NAME_ENTRY(CE_KEY_PadUp2, "PadUp2"), |
| KEY_NAME_ENTRY(CE_KEY_PadCenter2, "PadCenter2"), |
| KEY_NAME_ENTRY(CE_KEY_PadDown2, "PadDown2"), |
| KEY_NAME_ENTRY(CE_KEY_PadRight2, "PadRight2"), |
| |
| KEY_NAME_ENTRY(CE_KEY_LeftUpper2, "LeftUpper2"), |
| KEY_NAME_ENTRY(CE_KEY_LeftMiddle2, "LeftMiddle2"), |
| KEY_NAME_ENTRY(CE_KEY_LeftLower2, "LeftLower2"), |
| KEY_NAME_ENTRY(CE_KEY_RightUpper2, "RightUpper2"), |
| KEY_NAME_ENTRY(CE_KEY_RightMiddle2, "RightMiddle2"), |
| KEY_NAME_ENTRY(CE_KEY_RightLower2, "RightLower2"), |
| |
| KEY_GROUP_ENTRY(CE_GRP_RoutingKey, "RoutingKey"), |
| END_KEY_NAME_TABLE |
| |
| BEGIN_KEY_NAME_TABLE(novem) |
| KEY_NAME_ENTRY(0X03, "Dot7"), |
| KEY_NAME_ENTRY(0X07, "Dot3"), |
| KEY_NAME_ENTRY(0X0B, "Dot2"), |
| KEY_NAME_ENTRY(0X0F, "Dot1"), |
| KEY_NAME_ENTRY(0X13, "Dot4"), |
| KEY_NAME_ENTRY(0X17, "Dot5"), |
| KEY_NAME_ENTRY(0X1B, "Dot6"), |
| KEY_NAME_ENTRY(0X1F, "Dot8"), |
| |
| KEY_NAME_ENTRY(0X10, "LeftSpace"), |
| KEY_NAME_ENTRY(0X18, "RightSpace"), |
| END_KEY_NAME_TABLE |
| |
| BEGIN_KEY_NAME_TABLES(all) |
| KEY_NAME_TABLE(navigation), |
| END_KEY_NAME_TABLES |
| |
| BEGIN_KEY_NAME_TABLES(novem) |
| KEY_NAME_TABLE(novem), |
| END_KEY_NAME_TABLES |
| |
| DEFINE_KEY_TABLE(all) |
| DEFINE_KEY_TABLE(novem) |
| |
| BEGIN_KEY_TABLE_LIST |
| &KEY_TABLE_DEFINITION(all), |
| &KEY_TABLE_DEFINITION(novem), |
| END_KEY_TABLE_LIST |
| |
| typedef struct { |
| unsigned char identifier; |
| unsigned char cellCount; |
| const KeyTableDefinition *ktd; |
| } ModelEntry; |
| |
| static const ModelEntry modelTable[] = { |
| { .identifier = 0X68, |
| .cellCount = 0, |
| .ktd = &KEY_TABLE_DEFINITION(novem) |
| }, |
| |
| { .identifier = 0X70, |
| .cellCount = 0, |
| .ktd = &KEY_TABLE_DEFINITION(all) |
| }, |
| |
| { .identifier = 0X72, |
| .cellCount = 20, |
| .ktd = &KEY_TABLE_DEFINITION(all) |
| }, |
| |
| { .identifier = 0X74, |
| .cellCount = 40, |
| .ktd = &KEY_TABLE_DEFINITION(all) |
| }, |
| |
| { .identifier = 0X76, |
| .cellCount = 60, |
| .ktd = &KEY_TABLE_DEFINITION(all) |
| }, |
| |
| { .identifier = 0X78, |
| .cellCount = 80, |
| .ktd = &KEY_TABLE_DEFINITION(all) |
| }, |
| |
| { .identifier = 0X7A, |
| .cellCount = 100, |
| .ktd = &KEY_TABLE_DEFINITION(all) |
| }, |
| |
| { .identifier = 0X7C, |
| .cellCount = 120, |
| .ktd = &KEY_TABLE_DEFINITION(all) |
| }, |
| |
| { .identifier = 0X7E, |
| .cellCount = 140, |
| .ktd = &KEY_TABLE_DEFINITION(all) |
| }, |
| |
| { .identifier = 0 } |
| }; |
| |
| struct BrailleDataStruct { |
| const ModelEntry *model; |
| unsigned char forceRewrite; |
| unsigned char acknowledgementPending; |
| unsigned char textCells[MAXIMUM_CELL_COUNT]; |
| }; |
| |
| static const ModelEntry * |
| getModelEntry (unsigned char identifier) { |
| const ModelEntry *model = modelTable; |
| |
| while (model->identifier) { |
| if (identifier == model->identifier) return model; |
| model += 1; |
| } |
| |
| logMessage(LOG_WARNING, "unknown %s model: 0X%02X", |
| STRINGIFY(DRIVER_NAME), identifier); |
| return NULL; |
| } |
| |
| static int |
| setModel (BrailleDisplay *brl, unsigned char identifier) { |
| const ModelEntry *model = getModelEntry(identifier); |
| |
| if (model) { |
| logMessage(LOG_NOTICE, "%s Model: 0X%02X, %u cells", |
| STRINGIFY(DRIVER_NAME), model->identifier, model->cellCount); |
| |
| brl->data->model = model; |
| brl->textColumns = model->cellCount; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| 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 type, size_t size, const unsigned char *data) { |
| unsigned char bytes[size + 5]; |
| unsigned char *byte = bytes; |
| |
| *byte++ = CE_PKT_BEGIN; |
| *byte++ = brl->data->model->identifier; |
| *byte++ = size + 1; |
| *byte++ = type; |
| byte = mempcpy(byte, data, size); |
| *byte++ = CE_PKT_END; |
| |
| return writeBytes(brl, bytes, byte-bytes); |
| } |
| |
| static BraillePacketVerifierResult |
| verifyPacket ( |
| BrailleDisplay *brl, |
| unsigned char *bytes, size_t size, |
| size_t *length, void *data |
| ) { |
| const unsigned char byte = bytes[size-1]; |
| |
| if (size == 1) { |
| switch (byte) { |
| case CE_RSP_Identity: |
| *length = 2; |
| break; |
| |
| case CE_PKT_BEGIN: |
| *length = 3; |
| break; |
| |
| default: |
| return BRL_PVR_INVALID; |
| } |
| } else { |
| switch (bytes[0]) { |
| case CE_PKT_BEGIN: |
| if (size == 2) { |
| if (byte != brl->data->model->identifier) { |
| if (!setModel(brl, byte)) return BRL_PVR_INVALID; |
| brl->resizeRequired = 1; |
| } |
| } else if (size == 3) { |
| *length += byte + 1; |
| } else if (size == *length) { |
| if (byte != CE_PKT_END) return BRL_PVR_INVALID; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| return BRL_PVR_INCLUDE; |
| } |
| |
| static size_t |
| readPacket (BrailleDisplay *brl, void *packet, size_t size) { |
| return readBraillePacket(brl, NULL, packet, size, verifyPacket, NULL); |
| } |
| |
| static int |
| connectResource (BrailleDisplay *brl, const char *identifier) { |
| static const SerialParameters serialParameters = { |
| SERIAL_DEFAULT_PARAMETERS, |
| .baud = 19200, |
| .parity = SERIAL_PARITY_ODD |
| }; |
| |
| BEGIN_USB_CHANNEL_DEFINITIONS |
| { /* all models */ |
| .vendor=0X0403, .product=0X6001, |
| .configuration=1, .interface=0, .alternative=0, |
| .inputEndpoint=1, .outputEndpoint=2, |
| .serial = &serialParameters |
| }, |
| END_USB_CHANNEL_DEFINITIONS |
| |
| GioDescriptor descriptor; |
| gioInitializeDescriptor(&descriptor); |
| |
| descriptor.usb.channelDefinitions = usbChannelDefinitions; |
| |
| descriptor.bluetooth.channelNumber = 1; |
| |
| if (connectBrailleResource(brl, identifier, &descriptor, NULL)) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| writeIdentifyRequest (BrailleDisplay *brl) { |
| static const unsigned char bytes[] = {CE_REQ_Identify}; |
| return writeBytes(brl, bytes, sizeof(bytes)); |
| } |
| |
| static BrailleResponseResult |
| isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) { |
| const unsigned char *bytes = packet; |
| |
| return (bytes[0] == CE_RSP_Identity)? BRL_RSP_DONE: BRL_RSP_UNEXPECTED; |
| } |
| |
| 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)) { |
| unsigned char response[MAXIMUM_RESPONSE_SIZE]; |
| |
| if (probeBrailleDisplay(brl, PROBE_RETRY_LIMIT, NULL, PROBE_INPUT_TIMEOUT, |
| writeIdentifyRequest, |
| readPacket, &response, sizeof(response), |
| isIdentityResponse)) { |
| if (setModel(brl, response[1])) { |
| setBrailleKeyTable(brl, brl->data->model->ktd); |
| makeOutputTable(dotsTable_ISO11548_1); |
| |
| brl->data->forceRewrite = 1; |
| brl->data->acknowledgementPending = 0; |
| |
| return 1; |
| } |
| } |
| |
| disconnectBrailleResource(brl, NULL); |
| } |
| |
| free(brl->data); |
| } else { |
| logMallocError(); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| brl_destruct (BrailleDisplay *brl) { |
| disconnectBrailleResource(brl, NULL); |
| |
| if (brl->data) { |
| free(brl->data); |
| brl->data = NULL; |
| } |
| } |
| |
| static int |
| brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) { |
| if (!brl->data->acknowledgementPending) { |
| 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 (!writePacket(brl, CE_PKT_REQ_Write, brl->textColumns, cells)) return 0; |
| brl->data->acknowledgementPending = 1; |
| } |
| } |
| |
| return 1; |
| } |
| |
| static int |
| brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) { |
| unsigned char packet[MAXIMUM_RESPONSE_SIZE]; |
| size_t size; |
| |
| while ((size = readPacket(brl, packet, sizeof(packet)))) { |
| switch (packet[0]) { |
| case CE_PKT_BEGIN: { |
| const unsigned char *bytes = &packet[4]; |
| size_t count = packet[2] - 1; |
| |
| switch (packet[3]) { |
| case CE_PKT_RSP_NavigationKey: |
| if (count == 1) { |
| KeyGroup group; |
| unsigned char key = bytes[0]; |
| int press = !(key & CE_KEY_RELEASE); |
| key &= ~CE_KEY_RELEASE; |
| |
| if ((key >= CE_KEY_ROUTING_MIN) && (key <= CE_KEY_ROUTING_MAX)) { |
| group = CE_GRP_RoutingKey; |
| key -= CE_KEY_ROUTING_MIN; |
| } else { |
| group = CE_GRP_NavigationKey; |
| } |
| |
| enqueueKeyEvent(brl, group, key, press); |
| continue; |
| } |
| break; |
| |
| case CE_PKT_RSP_Confirmation: |
| if (count > 0) { |
| switch (bytes[0]) { |
| case 0X7D: |
| brl->data->forceRewrite = 1; |
| case 0X7E: |
| brl->data->acknowledgementPending = 0; |
| continue; |
| |
| default: |
| break; |
| } |
| } |
| break; |
| |
| case CE_PKT_RSP_KeyboardKey: |
| while (count--) enqueueCommand(BRL_CMD_BLK(PASSAT) | BRL_ARG_PUT(*bytes++)); |
| continue; |
| |
| default: |
| break; |
| } |
| |
| break; |
| } |
| |
| default: |
| break; |
| } |
| |
| logUnexpectedPacket(packet, size); |
| } |
| |
| return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL; |
| } |