/* * 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 . */ /* BrailleNote/braille.c - Braille display library * For Pulse Data International's Braille Note series * Author: Dave Mielke */ #include "prologue.h" #include #include #include #include "log.h" #include "device.h" #define BRL_HAVE_PACKET_IO #include "brl_driver.h" #include "brldefs-bn.h" #include "ttb.h" BEGIN_KEY_NAME_TABLE(all) KEY_NAME_ENTRY(BN_KEY_Dot1, "Dot1"), KEY_NAME_ENTRY(BN_KEY_Dot2, "Dot2"), KEY_NAME_ENTRY(BN_KEY_Dot3, "Dot3"), KEY_NAME_ENTRY(BN_KEY_Dot4, "Dot4"), KEY_NAME_ENTRY(BN_KEY_Dot5, "Dot5"), KEY_NAME_ENTRY(BN_KEY_Dot6, "Dot6"), KEY_NAME_ENTRY(BN_KEY_Space, "Space"), KEY_NAME_ENTRY(BN_KEY_Backspace, "Backspace"), KEY_NAME_ENTRY(BN_KEY_Enter, "Enter"), KEY_NAME_ENTRY(BN_KEY_Previous, "Previous"), KEY_NAME_ENTRY(BN_KEY_Back, "Back"), KEY_NAME_ENTRY(BN_KEY_Advance, "Advance"), KEY_NAME_ENTRY(BN_KEY_Next, "Next"), KEY_GROUP_ENTRY(BN_GRP_RoutingKeys, "RoutingKey"), END_KEY_NAME_TABLE BEGIN_KEY_NAME_TABLES(all) KEY_NAME_TABLE(all), END_KEY_NAME_TABLES DEFINE_KEY_TABLE(all) BEGIN_KEY_TABLE_LIST &KEY_TABLE_DEFINITION(all), END_KEY_TABLE_LIST typedef union { unsigned char bytes[3]; struct { unsigned char code; union { unsigned char dotKeys; unsigned char thumbKeys; unsigned char routingKey; unsigned char inputChar; unsigned char inputVKey; struct { unsigned char statusCells; unsigned char textCells; } description; } values; } data; } ResponsePacket; #ifdef HAVE_LINUX_VT_H #include #include #include #endif /* HAVE_LINUX_VT_H */ static int displayDescriptor = -1; static int displayTerminal; static unsigned char *cellBuffer = NULL; static unsigned int cellCount = 0; static unsigned char *statusArea; static int statusCells; static unsigned char *dataArea; static int dataCells; static int inputFlags = 0; static int readPacket (BrailleDisplay *brl, unsigned char *packet, int size) { int offset = 0; int length = 0; while (1) { unsigned char byte; if (!gioReadByte(brl->gioEndpoint, &byte, (offset > 0))) { if (offset > 0) logPartialPacket(packet, offset); return 0; } if (offset < size) { if (offset == 0) { switch (byte) { case BN_RSP_DISPLAY: length = 1; break; case BN_RSP_CHARACTER: case BN_RSP_SPACE: case BN_RSP_BACKSPACE: case BN_RSP_ENTER: case BN_RSP_THUMB: case BN_RSP_ROUTE: case BN_RSP_INPUT_CHAR: case BN_RSP_INPUT_VKEY: case BN_RSP_INPUT_RESET: case BN_RSP_QWERTY_KEY: case BN_RSP_QWERTY_MODS: length = 2; break; case BN_RSP_DESCRIBE: length = 3; break; default: logUnknownPacket(byte); offset = 0; length = 0; continue; } } packet[offset] = byte; } else { if (offset == size) logTruncatedPacket(packet, offset); logDiscardedByte(byte); } if (++offset == length) { if (offset > size) { offset = 0; length = 0; continue; } logInputPacket(packet, offset); return length; } } } static int getPacket (BrailleDisplay *brl, ResponsePacket *packet) { return readPacket(brl, packet->bytes, sizeof(*packet)); } static int writePacket (BrailleDisplay *brl, const unsigned char *packet, int size) { unsigned char buffer[1 + (size * 2)]; unsigned char *byte = buffer; *byte++ = BN_REQ_BEGIN; while (size > 0) { if ((*byte++ = *packet++) == BN_REQ_BEGIN) *byte++ = BN_REQ_BEGIN; --size; } return writeBraillePacket(brl, NULL, buffer, byte-buffer); } static int refreshCells (BrailleDisplay *brl) { unsigned char buffer[1 + cellCount]; unsigned char *byte = buffer; *byte++ = BN_REQ_WRITE; byte = translateOutputCells(byte, cellBuffer, cellCount); return writePacket(brl, buffer, byte-buffer); } static unsigned char getByte (BrailleDisplay *brl) { unsigned char byte; while (!awaitBrailleInput(brl, 1000000000)); gioReadByte(brl->gioEndpoint, &byte, 0); return byte; } static int getVirtualTerminal (void) { int vt = -1; #ifdef HAVE_LINUX_VT_H FILE *console = getConsole(); if (console) { int consoleDescriptor = fileno(console); struct vt_stat state; if (ioctl(consoleDescriptor, VT_GETSTATE, &state) != -1) { vt = state.v_active; } } #endif /* HAVE_LINUX_VT_H */ return vt; } static void setVirtualTerminal (int vt) { #ifdef HAVE_LINUX_VT_H FILE *console = getConsole(); if (console) { int consoleDescriptor = fileno(console); logMessage(LOG_DEBUG, "switching to virtual terminal %d", vt); if (ioctl(consoleDescriptor, VT_ACTIVATE, vt) != -1) { if (ioctl(consoleDescriptor, VT_WAITACTIVE, vt) != -1) { logMessage(LOG_INFO, "switched to virtual terminal %d", vt); } else { logSystemError("virtual console wait"); } } else { logSystemError("virtual console activate"); } } #endif /* HAVE_LINUX_VT_H */ } static void openVisualDisplay (void) { #ifdef HAVE_LINUX_VT_H if (displayDescriptor == -1) { FILE *console = getConsole(); if (console) { int consoleDescriptor = fileno(console); if (ioctl(consoleDescriptor, VT_OPENQRY, &displayTerminal) != -1) { char path[0X20]; snprintf(path, sizeof(path), "/dev/tty%d", displayTerminal); if ((displayDescriptor = open(path, O_WRONLY)) != -1) { logMessage(LOG_INFO, "visual display is %s", path); } } } } if (displayDescriptor != -1) { setVirtualTerminal(displayTerminal); } #endif /* HAVE_LINUX_VT_H */ } static void closeVisualDisplay (int vt) { if (displayDescriptor != -1) { if (getVirtualTerminal() == displayTerminal) { setVirtualTerminal(vt); } close(displayDescriptor); displayDescriptor = -1; displayTerminal = 0; } } static int writeVisualDisplay (unsigned char c) { if (displayDescriptor != -1) { if (write(displayDescriptor, &c, 1) == -1) { logSystemError("write"); return 0; } } return 1; } static int doVisualDisplay (BrailleDisplay *brl) { int vt = getVirtualTerminal(); const unsigned char end[] = {ESC, 0}; unsigned int state = 0; openVisualDisplay(); writeVisualDisplay(BN_RSP_DISPLAY); for (;;) { unsigned char character = getByte(brl); if (character == end[state]) { if (++state == sizeof(end)) break; } else { if (state > 0) { int i; for (i=0; idata.code == BN_RSP_DESCRIBE)? BRL_RSP_DONE: BRL_RSP_UNEXPECTED; } static int connectResource (BrailleDisplay *brl, const char *identifier) { static const SerialParameters serialParameters = { SERIAL_DEFAULT_PARAMETERS, .baud = 38400 }; BEGIN_USB_CHANNEL_DEFINITIONS { /* HumanWare APEX */ .vendor=0X1C71, .product=0XC004, .configuration=1, .interface=0, .alternative=0, .inputEndpoint=1, .outputEndpoint=2 }, END_USB_CHANNEL_DEFINITIONS GioDescriptor descriptor; gioInitializeDescriptor(&descriptor); descriptor.serial.parameters = &serialParameters; descriptor.usb.channelDefinitions = usbChannelDefinitions; if (connectBrailleResource(brl, identifier, &descriptor, NULL)) { return 1; } return 0; } static int virtualKeyToCommand (int vkey) { switch (vkey) { case 0X0D: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_ENTER; case 0X1B: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_ESCAPE; case 0X25: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_LEFT; case 0X26: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_UP; case 0X27: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_RIGHT; case 0X28: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_CURSOR_DOWN; case 0X2E: return BRL_CMD_BLK(PASSKEY) | BRL_KEY_DELETE; default: return BRL_CMD_NOOP; } } static int brl_construct (BrailleDisplay *brl, char **parameters, const char *device) { if (connectResource(brl, device)) { ResponsePacket response; if (probeBrailleDisplay(brl, 0, NULL, 100, writeIdentifyRequest, readResponse, &response, sizeof(response), isIdentityResponse)) { statusCells = response.data.values.description.statusCells; brl->textColumns = response.data.values.description.textCells; brl->textRows = 1; if ((statusCells == 5) && (brl->textColumns == 30)) { statusCells -= 2; brl->textColumns += 2; } dataCells = brl->textColumns * brl->textRows; cellCount = statusCells + dataCells; setBrailleKeyTable(brl, &KEY_TABLE_DEFINITION(all)); makeOutputTable(dotsTable_ISO11548_1); makeInputTable(); if ((cellBuffer = malloc(cellCount))) { memset(cellBuffer, 0, cellCount); statusArea = cellBuffer; dataArea = statusArea + statusCells; refreshCells(brl); return 1; } else { logSystemError("cell buffer allocation"); } } disconnectBrailleResource(brl, NULL); } return 0; } static void brl_destruct (BrailleDisplay *brl) { disconnectBrailleResource(brl, NULL); if (cellBuffer) { free(cellBuffer); cellBuffer = NULL; } } static ssize_t brl_readPacket (BrailleDisplay *brl, void *buffer, size_t size) { int count = readPacket(brl, buffer, size); if (!count) count = -1; return count; } static ssize_t brl_writePacket (BrailleDisplay *brl, const void *packet, size_t length) { return writePacket(brl, packet, length)? length: -1; } static int brl_reset (BrailleDisplay *brl) { return 0; } static int brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) { if (cellsHaveChanged(dataArea, brl->buffer, dataCells, NULL, NULL, NULL)) { refreshCells(brl); } return 1; } static int brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) { ResponsePacket packet; int size; while ((size = getPacket(brl, &packet))) { switch (packet.data.code) { case BN_RSP_ROUTE: enqueueKey(brl, BN_GRP_RoutingKeys, packet.data.values.routingKey); break; case BN_RSP_DISPLAY: doVisualDisplay(brl); break; case BN_RSP_INPUT_CHAR: { int command; switch (packet.data.values.inputChar) { case 0X08: command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_BACKSPACE; break; case 0X09: command = BRL_CMD_BLK(PASSKEY) | BRL_KEY_TAB; break; default: command = BRL_CMD_BLK(PASSCHAR) | packet.data.values.inputChar; break; } enqueueCommand(command | inputFlags); inputFlags = 0; break; } case BN_RSP_INPUT_VKEY: { unsigned char vkey = packet.data.values.inputVKey; switch (vkey) { case 0XA2: inputFlags |= BRL_FLG_INPUT_CONTROL; break; case 0XA4: inputFlags |= BRL_FLG_INPUT_META; break; case 0X91: inputFlags |= BRL_FLG_INPUT_SHIFT; break; default: { int command = virtualKeyToCommand(vkey); if (command) { enqueueCommand(command | inputFlags); } inputFlags = 0; break; } } break; } case BN_RSP_INPUT_RESET: inputFlags = 0; break; default: { const KeyGroup group = BN_GRP_NavigationKeys; KeyNumberSet keys = packet.data.values.dotKeys & 0X3F; KeyNumber base = BN_KEY_Dot1; KeyNumber modifier = 0; switch (packet.data.code) { case BN_RSP_CHARACTER: if (keys) break; case BN_RSP_SPACE: modifier = BN_KEY_Space; break; case BN_RSP_BACKSPACE: modifier = BN_KEY_Backspace; break; case BN_RSP_ENTER: modifier = BN_KEY_Enter; break; case BN_RSP_THUMB: keys = packet.data.values.thumbKeys & 0X0F; base = BN_KEY_Previous; break; default: logUnexpectedPacket(packet.bytes, size); continue; } if (modifier) enqueueKeyEvent(brl, group, modifier, 1); enqueueKeys(brl, keys, group, base); if (modifier) enqueueKeyEvent(brl, group, modifier, 0); break; } } } return (errno == EAGAIN)? EOF: BRL_CMD_RESTARTBRL; }