| /* |
| * 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 "strfmt.h" |
| #include "bitfield.h" |
| #include "parse.h" |
| |
| #define BRL_STATUS_FIELDS sfTime, sfSpace, sfCursorAndWindowColumn3, sfSpace, sfCursorAndWindowRow2, sfSpace, sfScreenNumber, sfSpace, sfStateLetter |
| #define BRL_HAVE_STATUS_CELLS |
| |
| typedef enum { |
| PARM_DISPLAY, |
| } DP_DriverParameter; |
| |
| #define BRLPARMS "display" |
| #include "brl_driver.h" |
| #include "brldefs-dp.h" |
| |
| #define PROBE_RETRY_LIMIT 2 |
| #define PROBE_INPUT_TIMEOUT 1000 |
| |
| #define GRAPHIC_HORIZONTAL_SPACING 1 |
| #define GRAPHIC_VERTICAL_SPACING 2 |
| |
| #define KEY_ENTRY(s,t,k,n) {.value = {.group=DP_GRP_##s, .number=DP_##t##_##k}, .name=n} |
| #define SCROLL_KEY_ENTRY(k,n) KEY_ENTRY(ScrollKeys, SCL, k, n) |
| #define KEYBOARD_KEY_ENTRY(k,n) KEY_ENTRY(PerkinsKeys, KBD, k, n) |
| #define PANNING_KEY_ENTRY(k,n) KEY_ENTRY(PerkinsKeys, PAN, k, n) |
| #define NAVIGATION_KEY_ENTRY(k,n) KEY_ENTRY(PerkinsKeys, NAV, k, n) |
| |
| BEGIN_KEY_NAME_TABLE(scroll) |
| SCROLL_KEY_ENTRY(LEFT_PREV, "LeftPrev"), |
| SCROLL_KEY_ENTRY(LEFT_NEXT, "LeftNext"), |
| SCROLL_KEY_ENTRY(RIGHT_PREV, "RightPrev"), |
| SCROLL_KEY_ENTRY(RIGHT_NEXT, "RightNext"), |
| END_KEY_NAME_TABLE |
| |
| BEGIN_KEY_NAME_TABLE(keyboard) |
| KEYBOARD_KEY_ENTRY(DOT1, "Dot1"), |
| KEYBOARD_KEY_ENTRY(DOT2, "Dot2"), |
| KEYBOARD_KEY_ENTRY(DOT3, "Dot3"), |
| KEYBOARD_KEY_ENTRY(DOT4, "Dot4"), |
| KEYBOARD_KEY_ENTRY(DOT5, "Dot5"), |
| KEYBOARD_KEY_ENTRY(DOT6, "Dot6"), |
| KEYBOARD_KEY_ENTRY(DOT7, "Dot7"), |
| KEYBOARD_KEY_ENTRY(DOT8, "Dot8"), |
| |
| KEYBOARD_KEY_ENTRY(SPACE, "Space"), |
| KEYBOARD_KEY_ENTRY(SHIFT_LEFT, "LeftShift"), |
| KEYBOARD_KEY_ENTRY(SHIFT_RIGHT, "RightShift"), |
| KEYBOARD_KEY_ENTRY(CONTROL_LEFT, "LeftControl"), |
| KEYBOARD_KEY_ENTRY(CONTROL_RIGHT, "RightControl"), |
| END_KEY_NAME_TABLE |
| |
| BEGIN_KEY_NAME_TABLE(panning) |
| PANNING_KEY_ENTRY(LEFT, "PanLeft"), |
| PANNING_KEY_ENTRY(RIGHT, "PanRight"), |
| END_KEY_NAME_TABLE |
| |
| BEGIN_KEY_NAME_TABLE(navigation) |
| NAVIGATION_KEY_ENTRY(CENTER, "NavCenter"), |
| NAVIGATION_KEY_ENTRY(LEFT, "NavLeft"), |
| NAVIGATION_KEY_ENTRY(RIGHT, "NavRight"), |
| NAVIGATION_KEY_ENTRY(UP, "NavUp"), |
| NAVIGATION_KEY_ENTRY(DOWN, "NavDown"), |
| END_KEY_NAME_TABLE |
| |
| BEGIN_KEY_NAME_TABLE(function) |
| KEY_GROUP_ENTRY(DP_GRP_FunctionKeys, "FunctionKey"), |
| END_KEY_NAME_TABLE |
| |
| BEGIN_KEY_NAME_TABLE(routing) |
| KEY_GROUP_ENTRY(DP_GRP_RoutingKeys, "RoutingKey"), |
| END_KEY_NAME_TABLE |
| |
| BEGIN_KEY_NAME_TABLES(all) |
| KEY_NAME_TABLE(scroll), |
| KEY_NAME_TABLE(keyboard), |
| KEY_NAME_TABLE(panning), |
| KEY_NAME_TABLE(navigation), |
| KEY_NAME_TABLE(routing), |
| KEY_NAME_TABLE(function), |
| END_KEY_NAME_TABLES |
| |
| BEGIN_KEY_NAME_TABLES(panfn4) |
| KEY_NAME_TABLE(panning), |
| KEY_NAME_TABLE(function), |
| END_KEY_NAME_TABLES |
| |
| DEFINE_KEY_TABLE(all) |
| DEFINE_KEY_TABLE(panfn4) |
| |
| BEGIN_KEY_TABLE_LIST |
| &KEY_TABLE_DEFINITION(all), |
| &KEY_TABLE_DEFINITION(panfn4), |
| END_KEY_TABLE_LIST |
| |
| typedef struct { |
| unsigned char *cells; |
| unsigned char destination; |
| } ExternalRowEntry; |
| |
| typedef struct { |
| unsigned char *cells; |
| |
| const ExternalRowEntry *upperRow; |
| const ExternalRowEntry *lowerRow; |
| |
| unsigned char upperShift; |
| unsigned char lowerShift; |
| |
| unsigned char upperMask; |
| unsigned char lowerMask; |
| |
| unsigned char hasChanged; |
| } InternalRowEntry; |
| |
| struct BrailleDataStruct { |
| DP_BoardInformation boardInformation; |
| unsigned char firmwareVersion[8]; |
| unsigned char deviceName[10]; |
| const KeyNameEntry *keyNameTable[7]; |
| |
| struct { |
| unsigned char scroll[4]; |
| unsigned char perkins[4]; |
| unsigned char routing[8]; |
| unsigned char function[4]; |
| } keys; |
| |
| struct { |
| unsigned char destination; |
| unsigned char refreshTime; |
| |
| unsigned char horizontalSpacing; |
| unsigned char verticalSpacing; |
| |
| unsigned char cellWidth; |
| unsigned char cellHeight; |
| |
| unsigned char externalColumns; |
| unsigned char externalRows; |
| |
| unsigned char internalColumns; |
| unsigned char internalRows; |
| } display; |
| |
| struct { |
| unsigned char *externalCells; |
| ExternalRowEntry *externalRows; |
| |
| unsigned char *internalCells; |
| InternalRowEntry *internalRows; |
| |
| unsigned char *statusCells; |
| } arrays;; |
| }; |
| |
| static void |
| setExternalDisplayProperties (BrailleDisplay *brl, const DP_DisplayDescriptor *display) { |
| { |
| unsigned char dotsPerCell = brl->data->boardInformation.dotsPerCell; |
| |
| unsigned char *width = &brl->data->display.cellWidth; |
| unsigned char *height = &brl->data->display.cellHeight; |
| |
| switch (dotsPerCell) { |
| default: |
| logMessage(LOG_WARNING, "unexpected dots per cell: %u", dotsPerCell); |
| /* fall through */ |
| case DP_DPC_8: |
| *width = 2; |
| *height = 4; |
| break; |
| |
| case DP_DPC_6: |
| *width = 2; |
| *height = 3; |
| break; |
| } |
| } |
| |
| brl->data->display.refreshTime = display->refreshTime; |
| brl->data->display.externalColumns = display->columnCount; |
| brl->data->display.externalRows = display->rowCount; |
| } |
| |
| static unsigned char |
| toInternalDimension (unsigned char externalCount, unsigned char externalDots, unsigned char internalDots, unsigned char internalSpacing) { |
| return (((externalCount * externalDots) - internalDots) / (internalDots + internalSpacing)) + 1; |
| } |
| |
| static void |
| setInternalDisplayProperties (BrailleDisplay *brl) { |
| brl->data->display.internalColumns = toInternalDimension( |
| brl->data->display.externalColumns, |
| brl->data->display.cellWidth, |
| 2, brl->data->display.horizontalSpacing |
| ); |
| |
| brl->data->display.internalRows = toInternalDimension( |
| brl->data->display.externalRows, |
| brl->data->display.cellHeight, |
| 4, brl->data->display.verticalSpacing |
| ); |
| |
| logMessage(LOG_CATEGORY(BRAILLE_DRIVER), |
| "display properties: ghsp:%u gvsp:%u cell:%ux%u disp:%ux%u core:%ux%u", |
| brl->data->display.horizontalSpacing, brl->data->display.verticalSpacing, |
| brl->data->display.cellWidth, brl->data->display.cellHeight, |
| brl->data->display.externalColumns, brl->data->display.externalRows, |
| brl->data->display.internalColumns, brl->data->display.internalRows |
| ); |
| |
| brl->textColumns = brl->data->display.internalColumns; |
| brl->textRows = brl->data->display.internalRows; |
| } |
| |
| static void |
| useTextDisplay (BrailleDisplay *brl) { |
| logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "using text display"); |
| |
| brl->data->display.destination = 0; |
| brl->data->display.horizontalSpacing = 0; |
| brl->data->display.verticalSpacing = 0; |
| |
| setExternalDisplayProperties(brl, &brl->data->boardInformation.text); |
| setInternalDisplayProperties(brl); |
| |
| brl->cellSize = brl->data->display.cellWidth * brl->data->display.cellHeight; |
| } |
| |
| static void |
| useGraphicDisplay (BrailleDisplay *brl) { |
| logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "using graphic display"); |
| |
| if (brl->data->boardInformation.features & DP_HAS_TEXT_DISPLAY) { |
| brl->data->display.destination = brl->data->boardInformation.text.rowCount; |
| } else { |
| brl->data->display.destination = 1; |
| } |
| |
| brl->data->display.horizontalSpacing = GRAPHIC_HORIZONTAL_SPACING; |
| brl->data->display.verticalSpacing = GRAPHIC_VERTICAL_SPACING; |
| |
| setExternalDisplayProperties(brl, &brl->data->boardInformation.graphic); |
| setInternalDisplayProperties(brl); |
| |
| if (brl->data->boardInformation.features & DP_HAS_TEXT_DISPLAY) { |
| brl->statusColumns = brl->data->boardInformation.text.columnCount; |
| brl->statusRows = 1; |
| } |
| } |
| |
| static int |
| selectDisplay (BrailleDisplay *brl, const char *parameter) { |
| typedef struct { |
| const char *name; // must be first |
| void (*useDisplay) (BrailleDisplay *brl); |
| unsigned char featureBit; |
| } ChoiceEntry; |
| |
| static const ChoiceEntry choiceTable[] = { |
| { .name = "default" }, |
| |
| { .name = "text", |
| .useDisplay = useTextDisplay, |
| .featureBit = DP_HAS_TEXT_DISPLAY, |
| }, |
| |
| { .name = "graphic", |
| .useDisplay = useGraphicDisplay, |
| .featureBit = DP_HAS_GRAPHIC_DISPLAY, |
| }, |
| |
| { .name = NULL } |
| }; |
| |
| unsigned char features = brl->data->boardInformation.features; |
| unsigned int choiceIndex; |
| |
| if (validateChoiceEx(&choiceIndex, parameter, choiceTable, sizeof(choiceTable[0]))) { |
| const ChoiceEntry *choice = &choiceTable[choiceIndex]; |
| |
| if (features & choice->featureBit) { |
| choice->useDisplay(brl); |
| return 1; |
| } |
| |
| if (choice->featureBit) { |
| logMessage(LOG_WARNING, "no %s display", choice->name); |
| } |
| } else { |
| logMessage(LOG_WARNING, "invalid display setting: %s", parameter); |
| } |
| |
| if (features & DP_HAS_GRAPHIC_DISPLAY) { |
| useGraphicDisplay(brl); |
| } else if (features & DP_HAS_TEXT_DISPLAY) { |
| useTextDisplay(brl); |
| } else { |
| logMessage(LOG_WARNING, "no supported display"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| processParameters (BrailleDisplay *brl, char **parameters) { |
| if (!selectDisplay(brl, parameters[PARM_DISPLAY])) return 0; |
| return 1; |
| } |
| |
| static ExternalRowEntry * |
| getExternalRow (BrailleDisplay *brl, unsigned int index) { |
| return &brl->data->arrays.externalRows[index]; |
| } |
| |
| static void |
| initializeExternalRows (BrailleDisplay *brl) { |
| unsigned char *cells = brl->data->arrays.externalCells; |
| unsigned char destination = brl->data->display.destination; |
| |
| for (unsigned int index=0; index<brl->data->display.externalRows; index+=1) { |
| ExternalRowEntry *row = getExternalRow(brl, index); |
| |
| row->cells = cells; |
| cells += brl->data->display.externalColumns; |
| |
| row->destination = destination; |
| destination += 1; |
| } |
| } |
| |
| static InternalRowEntry * |
| getInternalRow (BrailleDisplay *brl, unsigned int index) { |
| return &brl->data->arrays.internalRows[index]; |
| } |
| |
| static void |
| initializeInternalRows (BrailleDisplay *brl) { |
| unsigned char *cells = brl->data->arrays.internalCells + brl->data->display.verticalSpacing; |
| |
| const unsigned char cellHeight = brl->data->display.cellHeight; |
| const unsigned char rowHeight = cellHeight + brl->data->display.verticalSpacing; |
| const unsigned char cellMask = (1 << cellHeight) - 1; |
| |
| for (unsigned int index=0; index<brl->data->display.internalRows; index+=1) { |
| InternalRowEntry *row = getInternalRow(brl, index); |
| |
| row->cells = cells; |
| cells += brl->data->display.internalColumns; |
| |
| { |
| unsigned char offset = rowHeight * index; |
| row->upperRow = getExternalRow(brl, (offset / cellHeight)); |
| row->upperShift = offset % cellHeight; |
| row->upperMask = (cellMask << row->upperShift) & cellMask; |
| row->upperMask |= row->upperMask << 4; |
| |
| offset += 3; |
| row->lowerRow = getExternalRow(brl, (offset / cellHeight)); |
| row->lowerShift = cellHeight - (offset % cellHeight) - 1; |
| row->lowerMask = cellMask >> row->lowerShift; |
| row->lowerMask |= row->lowerMask << 4; |
| } |
| |
| row->hasChanged = 1; |
| } |
| } |
| |
| static int |
| makeArrays (BrailleDisplay *brl) { |
| if ((brl->data->arrays.externalCells = calloc(brl->data->display.externalRows, brl->data->display.externalColumns))) { |
| if ((brl->data->arrays.internalCells = calloc(brl->data->display.internalRows, brl->data->display.internalColumns))) { |
| if ((brl->data->arrays.externalRows = malloc(ARRAY_SIZE(brl->data->arrays.externalRows, brl->data->display.externalRows)))) { |
| if ((brl->data->arrays.internalRows = malloc(ARRAY_SIZE(brl->data->arrays.internalRows, brl->data->display.internalRows)))) { |
| int statusCellsAllocated = !brl->statusColumns; |
| |
| if (!statusCellsAllocated) { |
| if ((brl->data->arrays.statusCells = calloc(brl->statusColumns, 1))) { |
| statusCellsAllocated = 1; |
| } |
| } |
| |
| if (statusCellsAllocated) { |
| initializeExternalRows(brl); |
| initializeInternalRows(brl); |
| return 1; |
| } |
| |
| free(brl->data->arrays.internalRows); |
| } |
| |
| free(brl->data->arrays.externalRows); |
| } |
| |
| free(brl->data->arrays.internalCells); |
| } |
| |
| free(brl->data->arrays.externalCells); |
| } |
| |
| logMallocError(); |
| return 0; |
| } |
| |
| static void |
| deallocateArrays (BrailleDisplay *brl) { |
| free(brl->data->arrays.statusCells); |
| |
| free(brl->data->arrays.internalRows); |
| free(brl->data->arrays.internalCells); |
| |
| free(brl->data->arrays.externalRows); |
| free(brl->data->arrays.externalCells); |
| } |
| |
| static uint16_t |
| getUint16 (const unsigned char bytes[2]) { |
| union { |
| const unsigned char *ofBytes; |
| const uint16_t *ofUint16; |
| } address; |
| |
| address.ofBytes = bytes; |
| return getBigEndian16(*address.ofUint16); |
| } |
| |
| static void |
| putUint16 (unsigned char bytes[2], uint16_t value) { |
| union { |
| unsigned char *ofBytes; |
| uint16_t *ofUint16; |
| } address; |
| |
| address.ofBytes = bytes; |
| putBigEndian16(address.ofUint16, value); |
| } |
| |
| static unsigned char |
| makePacketChecksum (const DP_Packet *packet) { |
| unsigned char checksum = 0XA5; |
| |
| { |
| const unsigned char *byte = &packet->fields.destination; |
| const unsigned char *end = byte + getUint16(packet->fields.length) - 1; |
| while (byte < end) checksum ^= *byte++; |
| } |
| |
| return checksum; |
| } |
| |
| static int |
| writePacket (BrailleDisplay *brl, const DP_Packet *packet) { |
| size_t size = getUint16(packet->fields.length); |
| size += &packet->fields.destination - packet->bytes; |
| |
| unsigned int type = getUint16(packet->fields.command) << 8; |
| type |= packet->fields.destination; |
| |
| return writeBrailleMessage(brl, NULL, type, packet, size); |
| } |
| |
| static int |
| writeRequest (BrailleDisplay *brl, uint16_t command, uint8_t destination, const void *data, size_t size) { |
| if (!data) size = 0; |
| DP_Packet packet; |
| |
| packet.fields.sync[0] = DP_PSB_SYNC1; |
| packet.fields.sync[1] = DP_PSB_SYNC2; |
| |
| packet.fields.destination = destination; |
| putUint16(packet.fields.command, command); |
| packet.fields.seq = 0; |
| |
| uint8_t *checksum = mempcpy(packet.fields.data, data, size); |
| uint16_t length = (checksum - &packet.fields.destination) + 1; |
| putUint16(packet.fields.length, length); |
| *checksum = makePacketChecksum(&packet); |
| |
| return writePacket(brl, &packet); |
| } |
| |
| static int |
| verifyPacketChecksum (const DP_Packet *packet, unsigned char received) { |
| unsigned char expected = makePacketChecksum(packet); |
| if (received == expected) return 1; |
| |
| logMessage(LOG_WARNING, |
| "checksum mismatch: Received:%02X Expected:%02X", |
| received, expected |
| ); |
| |
| return 0; |
| } |
| |
| static int |
| verifyPacketLength (const DP_Packet *packet, const BrailleDisplay *brl) { |
| uint16_t received = getUint16(packet->fields.length); |
| uint16_t command = getUint16(packet->fields.command); |
| uint16_t expected = 5; |
| |
| switch (command) { |
| case DP_RSP_FIRMWARE_VERSION: |
| expected += sizeof(brl->data->firmwareVersion); |
| break; |
| |
| case DP_RSP_DEVICE_NAME: |
| expected += sizeof(brl->data->deviceName); |
| break; |
| |
| case DP_RSP_BOARD_INFORMATION: |
| expected += sizeof(brl->data->boardInformation); |
| break; |
| |
| case DP_RSP_DISPLAY_LINE: |
| expected += 1; |
| break; |
| |
| case DP_NTF_DISPLAY_LINE: |
| expected += 1; |
| break; |
| |
| case DP_NTF_KEYS_SCROLL: |
| expected += sizeof(brl->data->keys.scroll); |
| break; |
| |
| case DP_NTF_KEYS_PERKINS: |
| expected += sizeof(brl->data->keys.perkins); |
| break; |
| |
| case DP_NTF_KEYS_ROUTING: |
| expected += sizeof(brl->data->keys.routing); |
| break; |
| |
| case DP_NTF_KEYS_FUNCTION: |
| expected += sizeof(brl->data->keys.function); |
| break; |
| |
| case DP_NTF_ERROR: |
| expected += 1; |
| break; |
| } |
| |
| if (received != expected) { |
| logMessage(LOG_WARNING, |
| "length mismatch (command %04X): Received:%u Expected:%u", |
| getUint16(packet->fields.command), received, expected |
| ); |
| } |
| |
| return 1; |
| } |
| |
| 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: |
| if (byte != DP_PSB_SYNC1) return BRL_PVR_INVALID; |
| *length = 4; |
| break; |
| |
| case 2: |
| if (byte != DP_PSB_SYNC2) return BRL_PVR_INVALID; |
| break; |
| |
| case 4: |
| *length += getUint16(&bytes[2]); |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (size == *length) { |
| const void *packet = bytes; |
| verifyPacketChecksum(packet, byte); |
| verifyPacketLength(packet, brl); |
| } |
| |
| 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 |
| writeCells (BrailleDisplay *brl, unsigned char destination, const unsigned char *cells, unsigned int count) { |
| unsigned char data[1 + count]; |
| unsigned char *byte = data; |
| |
| *byte++ = 0; |
| byte = mempcpy(byte, cells, count); |
| |
| return writeRequest(brl, DP_REQ_DISPLAY_LINE, destination, data, (byte - data)); |
| } |
| |
| static int |
| writeStatusCells (BrailleDisplay *brl) { |
| return writeCells(brl, 0, brl->data->arrays.statusCells, brl->statusColumns); |
| } |
| |
| static int |
| brl_writeStatus (BrailleDisplay *brl, const unsigned char *cells) { |
| translateOutputCells(brl->data->arrays.statusCells, cells, brl->statusColumns); |
| return writeStatusCells(brl); |
| } |
| |
| static int |
| writeExternalRow (BrailleDisplay *brl, const ExternalRowEntry *row) { |
| return writeCells(brl, row->destination, row->cells, brl->data->display.externalColumns); |
| } |
| |
| static int |
| refreshCells (BrailleDisplay *brl) { |
| const ExternalRowEntry *row = brl->data->arrays.externalRows; |
| const ExternalRowEntry *end = row + brl->data->display.externalRows; |
| |
| while (row < end) { |
| if (!writeExternalRow(brl, row)) return 0; |
| row += 1; |
| } |
| |
| if (!brl->statusColumns) return 1; |
| return writeStatusCells(brl); |
| } |
| |
| static unsigned int |
| getExternalCellOffset (BrailleDisplay *brl, unsigned int index) { |
| return index * (brl->data->display.cellWidth + brl->data->display.horizontalSpacing); |
| } |
| |
| static unsigned char |
| getExternalCell (BrailleDisplay *brl, const ExternalRowEntry *row, unsigned int index) { |
| unsigned int offset = getExternalCellOffset(brl, index); |
| index = offset / 2; |
| unsigned char cell = row->cells[index]; |
| |
| if (offset % 2) { |
| cell >>= 4; |
| cell |= row->cells[index + 1] << 4; |
| } |
| |
| return cell; |
| } |
| |
| static void |
| putExternalCell (BrailleDisplay *brl, const ExternalRowEntry *row, unsigned int index, unsigned char cell) { |
| unsigned int offset = getExternalCellOffset(brl, index); |
| index = offset / 2; |
| |
| if (offset % 2) { |
| unsigned char *dots = &row->cells[index]; |
| *dots &= 0X0F; |
| *dots |= cell << 4; |
| |
| dots += 1; |
| *dots &= 0XF0; |
| *dots |= cell >> 4; |
| } else { |
| row->cells[index] = cell; |
| } |
| } |
| |
| static int |
| writeInternalCells (BrailleDisplay *brl, const InternalRowEntry *internalRow, unsigned int from, unsigned int to) { |
| int upperUpdated = 0; |
| int lowerUpdated = 0; |
| |
| while (from < to) { |
| unsigned char newCell = translateOutputCell(internalRow->cells[from]); |
| |
| { |
| const ExternalRowEntry *upperRow = internalRow->upperRow; |
| unsigned char upperCell = getExternalCell(brl, upperRow, from); |
| unsigned char changedDots = (upperCell ^ (newCell << internalRow->upperShift)) & internalRow->upperMask; |
| |
| if (changedDots) { |
| putExternalCell(brl, upperRow, from, (upperCell ^ changedDots)); |
| upperUpdated = 1; |
| } |
| } |
| |
| if (internalRow->lowerRow != internalRow->upperRow) { |
| const ExternalRowEntry *lowerRow = internalRow->lowerRow; |
| unsigned char lowerCell = getExternalCell(brl, lowerRow, from); |
| unsigned char changedDots = (lowerCell ^ (newCell >> internalRow->lowerShift)) & internalRow->lowerMask; |
| |
| if (changedDots) { |
| putExternalCell(brl, lowerRow, from, (lowerCell ^ changedDots)); |
| lowerUpdated = 1; |
| } |
| } |
| |
| from += 1; |
| } |
| |
| if (upperUpdated) { |
| if (!writeExternalRow(brl, internalRow->upperRow)) { |
| return 0; |
| } |
| } |
| |
| if (lowerUpdated) { |
| if (!writeExternalRow(brl, internalRow->lowerRow)) { |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| static int |
| brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) { |
| unsigned char *cells = brl->buffer; |
| unsigned char rowLength = brl->data->display.internalColumns; |
| |
| for (unsigned int rowIndex=0; rowIndex<brl->data->display.internalRows; rowIndex+=1) { |
| InternalRowEntry *row = getInternalRow(brl, rowIndex); |
| |
| unsigned int from; |
| unsigned int to; |
| |
| int rowHasChanged = cellsHaveChanged( |
| row->cells, cells, rowLength, |
| &from, &to, &row->hasChanged |
| ); |
| |
| if (rowHasChanged) { |
| if (!writeInternalCells(brl, row, from, to)) { |
| return 0; |
| } |
| } |
| |
| cells += rowLength; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| getDataSize (const DP_Packet *packet) { |
| return getUint16(packet->fields.length) |
| - 1 // checksum |
| - (packet->fields.data - &packet->fields.destination) // header |
| ; |
| } |
| |
| static void |
| reportRequestError (unsigned char code) { |
| static const char *const reasons[] = { |
| [DP_ERR_LENGTH] = "unexpected length", |
| [DP_ERR_COMMAND] = "unrecognized command", |
| [DP_ERR_CHECKSUM] = "incorrect checksum", |
| [DP_ERR_PARAMETER] = "invalid parameter", |
| [DP_ERR_TIMEOUT] = "read timed out", |
| }; |
| |
| const char *reason = NULL; |
| if (code < ARRAY_COUNT(reasons)) reason = reasons[code]; |
| if (!reason) reason = "unknown problem"; |
| |
| logMessage(LOG_WARNING, |
| "request rejected by device: %u (%s)", |
| code, reason |
| ); |
| } |
| |
| static void |
| reportDisplayError (unsigned char code) { |
| static const char *const reasons[] = { |
| [DP_DRC_ACK] = "positive acknowledgement", |
| [DP_DRC_NACK] = "negative acknowledgement", |
| [DP_DRC_WAIT] = "wait", |
| [DP_DRC_CHECKSUM] = "incorrect checksum", |
| }; |
| |
| const char *reason = NULL; |
| if (code < ARRAY_COUNT(reasons)) reason = reasons[code]; |
| if (!reason) reason = "unknown problem"; |
| |
| logMessage(LOG_WARNING, |
| "display rejected by device: %u (%s)", |
| code, reason |
| ); |
| } |
| |
| static void |
| saveField ( |
| const DP_Packet *packet, const char *label, |
| unsigned char *field, int fieldSize |
| ) { |
| int dataSize = getDataSize(packet); |
| |
| if (dataSize > fieldSize) dataSize = fieldSize; |
| memcpy(field, packet->fields.data, dataSize); |
| while (dataSize < fieldSize) field[dataSize++] = ' '; |
| |
| logMessage(LOG_CATEGORY(BRAILLE_DRIVER), |
| "%s: %.*s", label, fieldSize, field |
| ); |
| } |
| |
| static unsigned char |
| reverseByteBits (unsigned char fromByte) { |
| unsigned char toByte = 0; |
| |
| unsigned char fromBit = 0X80; |
| unsigned char toBit = 0X01; |
| |
| while (fromBit) { |
| if (fromByte & fromBit) toByte |= toBit; |
| fromBit >>= 1; |
| toBit <<= 1; |
| } |
| |
| return toByte; |
| } |
| |
| static int |
| updateKeyGroup ( |
| BrailleDisplay *brl, const DP_Packet *packet, KeyGroup keyGroup, |
| unsigned char *array, size_t arraySize |
| ) { |
| int dataSize = getDataSize(packet); |
| |
| if (dataSize > 0) { |
| unsigned char data[arraySize]; |
| if (dataSize > arraySize) dataSize = arraySize; |
| |
| for (int i=0; i<dataSize; i+=1) { |
| data[i] = reverseByteBits(packet->fields.data[i]); |
| } |
| |
| while (dataSize < arraySize) { |
| data[dataSize++] = 0; |
| } |
| |
| if (!enqueueUpdatedKeyGroup(brl, (arraySize * 8), data, array, keyGroup)) { |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| static int |
| brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) { |
| DP_Packet packet; |
| size_t size; |
| |
| while ((size = readPacket(brl, packet.bytes, sizeof(packet)))) { |
| switch (getUint16(packet.fields.command)) { |
| case DP_RSP_FIRMWARE_VERSION: { |
| saveField( |
| &packet, "Firmware Version", |
| brl->data->firmwareVersion, |
| sizeof(brl->data->firmwareVersion) |
| ); |
| |
| acknowledgeBrailleMessage(brl); |
| continue; |
| } |
| |
| case DP_RSP_DEVICE_NAME: { |
| saveField( |
| &packet, "Device Name", |
| brl->data->deviceName, |
| sizeof(brl->data->deviceName) |
| ); |
| |
| acknowledgeBrailleMessage(brl); |
| continue; |
| } |
| |
| case DP_RSP_DISPLAY_LINE: { |
| unsigned char code = packet.fields.data[0]; |
| |
| if (code != DP_DRC_ACK) { |
| reportDisplayError(code); |
| acknowledgeBrailleMessage(brl); |
| } |
| |
| continue; |
| } |
| |
| case DP_NTF_DISPLAY_LINE: |
| acknowledgeBrailleMessage(brl); |
| continue; |
| |
| case DP_NTF_KEYS_SCROLL: { |
| updateKeyGroup( |
| brl, &packet, DP_GRP_ScrollKeys, |
| brl->data->keys.scroll, |
| sizeof(brl->data->keys.scroll) |
| ); |
| |
| continue; |
| } |
| |
| case DP_NTF_KEYS_PERKINS: { |
| updateKeyGroup( |
| brl, &packet, DP_GRP_PerkinsKeys, |
| brl->data->keys.perkins, |
| sizeof(brl->data->keys.perkins) |
| ); |
| |
| continue; |
| } |
| |
| case DP_NTF_KEYS_ROUTING: { |
| updateKeyGroup( |
| brl, &packet, DP_GRP_RoutingKeys, |
| brl->data->keys.routing, |
| sizeof(brl->data->keys.routing) |
| ); |
| |
| continue; |
| } |
| |
| case DP_NTF_KEYS_FUNCTION: { |
| updateKeyGroup( |
| brl, &packet, DP_GRP_FunctionKeys, |
| brl->data->keys.function, |
| sizeof(brl->data->keys.function) |
| ); |
| |
| continue; |
| } |
| |
| case DP_NTF_ERROR: { |
| reportRequestError(packet.fields.data[0]); |
| acknowledgeBrailleMessage(brl); |
| continue; |
| } |
| |
| default: |
| break; |
| } |
| |
| logUnexpectedPacket(packet.bytes, size); |
| } |
| |
| return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL; |
| } |
| |
| static const KeyNameEntry ** |
| makeKeyNameTable (BrailleDisplay *brl) { |
| typedef struct { |
| const char *type; |
| const KeyNameEntry *keyNames; |
| unsigned char featureBit; |
| } OptionalKeysDescriptor; |
| |
| static const OptionalKeysDescriptor optionalKeysTable[] = { |
| { .type = "scroll", |
| .keyNames = KEY_NAME_TABLE(scroll), |
| // not actually used |
| }, |
| |
| { .type = "keyboard", |
| .keyNames = KEY_NAME_TABLE(keyboard), |
| .featureBit = DP_HAS_PERKINS_KEYS, |
| }, |
| |
| { .type = "panning", |
| .keyNames = KEY_NAME_TABLE(panning), |
| .featureBit = DP_HAS_PANNING_KEYS, |
| }, |
| |
| { .type = "navigation", |
| .keyNames = KEY_NAME_TABLE(navigation), |
| .featureBit = DP_HAS_NAVIGATION_KEYS, |
| }, |
| |
| { .type = "routing", |
| .keyNames = KEY_NAME_TABLE(routing), |
| .featureBit = DP_HAS_ROUTING_KEYS, |
| }, |
| |
| { .type = "function", |
| .keyNames = KEY_NAME_TABLE(function), |
| .featureBit = DP_HAS_FUNCTION_KEYS, |
| }, |
| }; |
| |
| const KeyNameEntry **names = brl->data->keyNameTable; |
| const OptionalKeysDescriptor *okd = optionalKeysTable; |
| const OptionalKeysDescriptor *end = okd + ARRAY_COUNT(optionalKeysTable); |
| |
| while (okd < end) { |
| if (brl->data->boardInformation.features & okd->featureBit) { |
| char log[0X40]; |
| STR_BEGIN(log, sizeof(log)); |
| STR_PRINTF("has"); |
| |
| if (okd->featureBit == DP_HAS_FUNCTION_KEYS) { |
| STR_PRINTF(" %u", brl->data->boardInformation.functionKeyCount); |
| } |
| |
| STR_PRINTF(" %s keys", okd->type); |
| STR_END; |
| logMessage(LOG_CATEGORY(BRAILLE_DRIVER), "%s", log); |
| |
| *names++ = okd->keyNames; |
| } |
| |
| okd += 1; |
| } |
| |
| *names = LAST_KEY_NAME_TABLE; |
| return brl->data->keyNameTable; |
| } |
| |
| static void |
| setKeyTable (BrailleDisplay *brl) { |
| const KeyTableDefinition *ktd = &KEY_TABLE_DEFINITION(all); |
| brl->keyBindings = ktd->bindings; |
| brl->keyNames = makeKeyNameTable(brl); |
| } |
| |
| static int |
| connectResource (BrailleDisplay *brl, const char *identifier) { |
| static const SerialParameters serialParameters = { |
| SERIAL_DEFAULT_PARAMETERS, |
| .baud = 115200, |
| }; |
| |
| BEGIN_USB_CHANNEL_DEFINITIONS |
| { /* all models */ |
| .vendor=0X0403, .product=0X6010, |
| .configuration=1, .interface=0, .alternative=0, |
| .inputEndpoint=1, .outputEndpoint=2, |
| .serial=&serialParameters |
| }, |
| END_USB_CHANNEL_DEFINITIONS |
| |
| GioDescriptor descriptor; |
| gioInitializeDescriptor(&descriptor); |
| |
| descriptor.serial.parameters = &serialParameters; |
| |
| descriptor.usb.channelDefinitions = usbChannelDefinitions; |
| //descriptor.usb.options.readyDelay = 3000; |
| |
| if (connectBrailleResource(brl, identifier, &descriptor, NULL)) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| writeIdentifyRequest (BrailleDisplay *brl) { |
| return writeRequest(brl, DP_REQ_BOARD_INFORMATION, 0, NULL, 0); |
| } |
| |
| static BrailleResponseResult |
| isIdentityResponse (BrailleDisplay *brl, const void *packet, size_t size) { |
| const DP_Packet *response = packet; |
| |
| if (getUint16(response->fields.command) != DP_RSP_BOARD_INFORMATION) { |
| return BRL_RSP_UNEXPECTED; |
| } |
| |
| memcpy( |
| &brl->data->boardInformation, response->fields.data, |
| sizeof(brl->data->boardInformation) |
| ); |
| |
| { |
| DP_BoardInformation *info = &brl->data->boardInformation; |
| |
| if (info->features & DP_HAS_FUNCTION_KEYS) { |
| if (!info->functionKeyCount) { |
| info->functionKeyCount = 4; |
| } |
| } |
| } |
| |
| logBytes(LOG_CATEGORY(BRAILLE_DRIVER), |
| "Board Information", |
| &brl->data->boardInformation, |
| sizeof(brl->data->boardInformation) |
| ); |
| |
| acknowledgeBrailleMessage(brl); |
| 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)) { |
| DP_Packet response; |
| |
| int probed = probeBrailleDisplay( |
| brl, PROBE_RETRY_LIMIT, NULL, PROBE_INPUT_TIMEOUT, |
| writeIdentifyRequest, readPacket, |
| &response, sizeof(response), |
| isIdentityResponse |
| ); |
| |
| if (probed) { |
| if (processParameters(brl, parameters)) { |
| if (makeArrays(brl)) { |
| brl->acknowledgements.missing.timeout = (brl->data->display.refreshTime * 100) + 1000; |
| |
| if (writeRequest(brl, DP_REQ_FIRMWARE_VERSION, 0, NULL, 0)) { |
| if (writeRequest(brl, DP_REQ_DEVICE_NAME, 0, NULL, 0)) { |
| setKeyTable(brl); |
| |
| MAKE_OUTPUT_TABLE( |
| DP_DSP_DOT1, DP_DSP_DOT2, DP_DSP_DOT3, DP_DSP_DOT4, |
| DP_DSP_DOT5, DP_DSP_DOT6, DP_DSP_DOT7, DP_DSP_DOT8 |
| ); |
| |
| brl->refreshBrailleDisplay = refreshCells; |
| return 1; |
| } |
| } |
| |
| deallocateArrays(brl); |
| } |
| } |
| } |
| |
| disconnectBrailleResource(brl, NULL); |
| } |
| |
| free(brl->data); |
| } else { |
| logMallocError(); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| brl_destruct (BrailleDisplay *brl) { |
| endBrailleMessages(brl); |
| disconnectBrailleResource(brl, NULL); |
| |
| deallocateArrays(brl); |
| free(brl->data); |
| } |