| /* |
| * 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>. |
| */ |
| |
| /* MiniBraille/braille.c - Braille display library |
| * the following Tieman B.V. braille terminals are supported |
| * |
| * - MiniBraille v 1.5 (20 braille cells + 2 status) |
| * (probably other versions too) |
| * |
| * Brailcom o.p.s. <technik@brailcom.cz> |
| * |
| * Thanks to Tieman B.V., which gives me protocol information. Author. |
| */ |
| |
| #include "prologue.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <time.h> |
| |
| #include "log.h" |
| #include "timing.h" |
| #include "ascii.h" |
| #include "message.h" |
| |
| #define BRL_STATUS_FIELDS sfCursorAndWindowColumn2, sfCursorAndWindowRow2, sfStateDots |
| #define BRL_HAVE_STATUS_CELLS |
| #include "brl_driver.h" |
| |
| #include "io_serial.h" |
| static SerialDevice *serialDevice = NULL; |
| static const unsigned int serialBaud = 9600; |
| static unsigned int serialCharactersPerSecond; |
| |
| #define KEY_F1 0x01 |
| #define KEY_F2 0x02 |
| #define KEY_LEFT 0x04 |
| #define KEY_UP 0x08 |
| #define KEY_CENTER 0x10 |
| #define KEY_DOWN 0x20 |
| #define KEY_RIGHT 0x40 |
| |
| #define POST_COMMAND_DELAY 30 |
| |
| static unsigned char textCells[20]; |
| static unsigned char statusCells[2]; |
| static int refreshNeeded; |
| |
| static int |
| writeData (BrailleDisplay *brl, const unsigned char *bytes, int count) { |
| ssize_t result = serialWriteData(serialDevice, bytes, count); |
| |
| if (result == -1) { |
| logSystemError("write"); |
| return 0; |
| } |
| |
| drainBrailleOutput(brl, 0); |
| brl->writeDelay += (result * 1000 / serialCharactersPerSecond) + POST_COMMAND_DELAY; |
| return 1; |
| } |
| |
| static int |
| writeCells (BrailleDisplay *brl) { |
| static const unsigned char beginSequence[] = {ASCII_ESC, 'Z', '1'}; |
| static const unsigned char endSequence[] = {ASCII_CR}; |
| |
| unsigned char buffer[sizeof(beginSequence) + sizeof(statusCells) + sizeof(textCells) + sizeof(endSequence)]; |
| unsigned char *byte = buffer; |
| |
| byte = mempcpy(byte, beginSequence, sizeof(beginSequence)); |
| byte = translateOutputCells(byte, statusCells, sizeof(statusCells)); |
| byte = translateOutputCells(byte, textCells, sizeof(textCells)); |
| byte = mempcpy(byte, endSequence, sizeof(endSequence)); |
| |
| return writeData(brl, buffer, byte-buffer); |
| } |
| |
| static void |
| updateCells (unsigned char *target, const unsigned char *source, size_t count) { |
| if (cellsHaveChanged(target, source, count, NULL, NULL, NULL)) { |
| refreshNeeded = 1; |
| } |
| } |
| |
| static void |
| clearCells (unsigned char *cells, size_t count) { |
| memset(cells, 0, count); |
| refreshNeeded = 1; |
| } |
| |
| static int |
| beep (BrailleDisplay *brl) { |
| static const unsigned char sequence[] = {ASCII_ESC, 'B', ASCII_CR}; |
| return writeData(brl, sequence, sizeof(sequence)); |
| } |
| |
| static int |
| inputFunction_showTime (BrailleDisplay *brl) { |
| time_t clock = time(NULL); |
| const struct tm *local = localtime(&clock); |
| char text[sizeof(textCells) + 1]; |
| strftime(text, sizeof(text), "%Y-%m-%d %H:%M:%S", local); |
| message(NULL, text, 0); |
| return BRL_CMD_NOOP; |
| } |
| |
| static unsigned char cursorDots; |
| static unsigned char cursorOffset; |
| |
| static void |
| putCursor (BrailleDisplay *brl) { |
| brl->buffer[cursorOffset] = cursorDots; |
| } |
| |
| static int |
| inputFunction_incrementCursor (BrailleDisplay *brl) { |
| if (++cursorOffset < sizeof(textCells)) return BRL_CMD_NOOP; |
| |
| cursorOffset = 0; |
| return BRL_CMD_FWINRT; |
| } |
| |
| static int |
| inputFunction_decrementCursor (BrailleDisplay *brl) { |
| if (cursorOffset) { |
| --cursorOffset; |
| return BRL_CMD_NOOP; |
| } |
| |
| cursorOffset = sizeof(textCells) - 1; |
| return BRL_CMD_FWINLT; |
| } |
| |
| typedef struct InputModeStruct InputMode; |
| |
| typedef enum { |
| IBT_unbound = 0, /* automatically set if not explicitly initialized */ |
| IBT_command, |
| IBT_block, |
| IBT_function, |
| IBT_submode |
| } InputBindingType; |
| |
| typedef union { |
| int command; |
| int block; |
| int (*function) (BrailleDisplay *brl); |
| const InputMode *submode; |
| } InputBindingValue; |
| |
| typedef struct { |
| InputBindingType type; |
| InputBindingValue value; |
| } InputBinding; |
| |
| struct InputModeStruct { |
| InputBinding keyF1, keyF2, keyLeft, keyUp, keyCenter, keyDown, keyRight; |
| |
| unsigned temporary:1; |
| void (*modifyWindow) (BrailleDisplay *brl); |
| const char *name; |
| }; |
| |
| #define BIND(k,t,v) .key##k = {.type = IBT_##t, .value.t = (v)} |
| #define BIND_COMMAND(k,c) BIND(k, command, BRL_CMD_##c) |
| #define BIND_BLOCK(k,b) BIND(k, block, BRL_CMD_BLK(b)) |
| #define BIND_FUNCTION(k,f) BIND(k, function, inputFunction_##f) |
| #define BIND_SUBMODE(k,m) BIND(k, submode, &inputMode_##m) |
| |
| static const InputMode inputMode_char_f1 = { |
| BIND_BLOCK(F1, SETLEFT), |
| BIND_BLOCK(F2, DESCCHAR), |
| BIND_BLOCK(Left, CLIP_ADD), |
| BIND_BLOCK(Up, CLIP_NEW), |
| BIND_BLOCK(Center, ROUTE), |
| BIND_BLOCK(Down, COPY_RECT), |
| BIND_BLOCK(Right, COPY_LINE), |
| |
| .temporary = 1, |
| .name = "Char-F1" |
| }; |
| |
| static const InputMode inputMode_f1_f1 = { |
| BIND_COMMAND(F1, HELP), |
| BIND_COMMAND(F2, LEARN), |
| BIND_COMMAND(Left, INFO), |
| BIND_FUNCTION(Right, showTime), |
| BIND_COMMAND(Up, PREFLOAD), |
| BIND_COMMAND(Down, PREFMENU), |
| BIND_COMMAND(Center, PREFSAVE), |
| |
| .temporary = 1, |
| .name = "F1-F1" |
| }; |
| |
| static const InputMode inputMode_f1_f2 = { |
| BIND_COMMAND(F1, FREEZE), |
| BIND_COMMAND(F2, DISPMD), |
| BIND_COMMAND(Left, ATTRVIS), |
| BIND_COMMAND(Right, CSRVIS), |
| BIND_COMMAND(Up, SKPBLNKWINS), |
| BIND_COMMAND(Down, SKPIDLNS), |
| BIND_COMMAND(Center, SIXDOTS), |
| |
| .temporary = 1, |
| .name = "F1-F2" |
| }; |
| |
| static const InputMode inputMode_f1_left = { |
| |
| .temporary = 1, |
| .name = "F1-Left" |
| }; |
| |
| static const InputMode inputMode_f1_right = { |
| BIND_COMMAND(F2, AUTOSPEAK), |
| BIND_COMMAND(Left, SAY_ABOVE), |
| BIND_COMMAND(Right, SAY_BELOW), |
| BIND_COMMAND(Up, MUTE), |
| BIND_COMMAND(Down, SAY_LINE), |
| BIND_COMMAND(Center, SPKHOME), |
| |
| .temporary = 1, |
| .name = "F1-Right" |
| }; |
| |
| static const InputMode inputMode_f1_up = { |
| BIND_COMMAND(F1, PRSEARCH), |
| BIND_COMMAND(F2, NXSEARCH), |
| BIND_COMMAND(Left, ATTRUP), |
| BIND_COMMAND(Right, ATTRDN), |
| BIND_COMMAND(Up, PRPGRPH), |
| BIND_COMMAND(Down, NXPGRPH), |
| BIND_COMMAND(Center, CSRJMP_VERT), |
| |
| .temporary = 1, |
| .name = "F1-Up" |
| }; |
| |
| static const InputMode inputMode_f1_down = { |
| BIND_COMMAND(F1, PRPROMPT), |
| BIND_COMMAND(F2, NXPROMPT), |
| BIND_COMMAND(Left, FWINLTSKIP), |
| BIND_COMMAND(Right, FWINRTSKIP), |
| BIND_COMMAND(Up, PRDIFLN), |
| BIND_COMMAND(Down, NXDIFLN), |
| BIND_COMMAND(Center, PASTE), |
| |
| .temporary = 1, |
| .name = "F1-Down" |
| }; |
| |
| static const InputMode inputMode_f1_center = { |
| BIND_SUBMODE(F1, char_f1), |
| BIND_FUNCTION(Left, decrementCursor), |
| BIND_FUNCTION(Right, incrementCursor), |
| BIND_COMMAND(Up, LNUP), |
| BIND_COMMAND(Down, LNDN), |
| |
| .temporary = 0, |
| .modifyWindow = putCursor, |
| .name = "F1-Center" |
| }; |
| |
| static const InputMode inputMode_f1 = { |
| BIND_SUBMODE(F1, f1_f1), |
| BIND_SUBMODE(F2, f1_f2), |
| BIND_SUBMODE(Left, f1_left), |
| BIND_SUBMODE(Right, f1_right), |
| BIND_SUBMODE(Up, f1_up), |
| BIND_SUBMODE(Down, f1_down), |
| BIND_SUBMODE(Center, f1_center), |
| |
| .temporary = 1, |
| .name = "F1" |
| }; |
| |
| static const InputMode inputMode_f2 = { |
| BIND_COMMAND(F1, TOP_LEFT), |
| BIND_COMMAND(F2, BOT_LEFT), |
| BIND_COMMAND(Left, LNBEG), |
| BIND_COMMAND(Right, LNEND), |
| BIND_COMMAND(Up, TOP), |
| BIND_COMMAND(Down, BOT), |
| BIND_COMMAND(Center, CSRTRK), |
| |
| .temporary = 1, |
| .name = "F2" |
| }; |
| |
| static const InputMode inputMode_basic = { |
| BIND_SUBMODE(F1, f1), |
| BIND_SUBMODE(F2, f2), |
| BIND_COMMAND(Left, FWINLT), |
| BIND_COMMAND(Right, FWINRT), |
| BIND_COMMAND(Up, LNUP), |
| BIND_COMMAND(Down, LNDN), |
| BIND_COMMAND(Center, RETURN), |
| |
| .temporary = 0, |
| .name = "Basic" |
| }; |
| |
| static const InputMode *inputMode; |
| static TimePeriod inputPeriod; |
| |
| static void |
| setInputMode (const InputMode *mode) { |
| if (mode->temporary) { |
| char title[sizeof(textCells) + 1]; |
| snprintf(title, sizeof(title), "%s Mode", mode->name); |
| message(NULL, title, MSG_NODELAY|MSG_SILENT); |
| } |
| |
| inputMode = mode; |
| startTimePeriod(&inputPeriod, 3000); |
| } |
| |
| static void |
| resetInputMode (void) { |
| setInputMode(&inputMode_basic); |
| } |
| |
| static int |
| brl_construct (BrailleDisplay *brl, char **parameters, const char *device) { |
| if (!isSerialDeviceIdentifier(&device)) { |
| unsupportedDeviceIdentifier(device); |
| return 0; |
| } |
| |
| if ((serialDevice = serialOpenDevice(device))) { |
| if (serialRestartDevice(serialDevice, serialBaud)) { |
| serialCharactersPerSecond = serialBaud / serialGetCharacterBits(serialDevice); |
| |
| /* hm, how to switch to 38400 ? |
| static const unsigned char sequence[] = {ASCII_ESC, 'V', ASCII_CR}; |
| writeData(brl, sequence, sizeof(sequence)); |
| serialDiscardInput(serialDevice); |
| serialSetBaud(serialDevice, 38400); |
| */ |
| |
| MAKE_OUTPUT_TABLE(0X01, 0X02, 0X04, 0X80, 0X40, 0X20, 0X08, 0X10); |
| clearCells(textCells, sizeof(textCells)); |
| clearCells(statusCells, sizeof(statusCells)); |
| resetInputMode(); |
| |
| cursorDots = 0XFF; |
| cursorOffset = sizeof(textCells) / 2; |
| |
| brl->textColumns = sizeof(textCells); |
| brl->textRows = 1; |
| brl->statusColumns = sizeof(statusCells); |
| brl->statusRows = 1; |
| |
| beep(brl); |
| return 1; |
| } |
| |
| serialCloseDevice(serialDevice); |
| serialDevice = NULL; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| brl_destruct (BrailleDisplay *brl) { |
| if (serialDevice) { |
| serialCloseDevice(serialDevice); |
| serialDevice = NULL; |
| } |
| } |
| |
| static int |
| brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) { |
| if (inputMode->modifyWindow) inputMode->modifyWindow(brl); |
| updateCells(textCells, brl->buffer, sizeof(textCells)); |
| if (refreshNeeded && !inputMode->temporary) { |
| writeCells(brl); |
| refreshNeeded = 0; |
| } |
| return 1; |
| } |
| |
| static int |
| brl_writeStatus (BrailleDisplay *brl, const unsigned char *s) { |
| updateCells(statusCells, s, sizeof(statusCells)); |
| return 1; |
| } |
| |
| static int |
| brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) { |
| unsigned char byte; |
| const InputMode *mode; |
| const InputBinding *binding; |
| |
| { |
| int result = serialReadData(serialDevice, &byte, 1, 0, 0); |
| |
| if (result == 0) { |
| if (inputMode->temporary) |
| if (afterTimePeriod(&inputPeriod, NULL)) |
| resetInputMode(); |
| |
| return EOF; |
| } |
| |
| if (result == -1) { |
| logSystemError("read"); |
| return BRL_CMD_RESTARTBRL; |
| } |
| } |
| |
| mode = inputMode; |
| if (mode->temporary) resetInputMode(); |
| |
| switch (byte) { |
| case KEY_F1: binding = &mode->keyF1; break; |
| case KEY_F2: binding = &mode->keyF2; break; |
| case KEY_LEFT: binding = &mode->keyLeft; break; |
| case KEY_RIGHT: binding = &mode->keyRight; break; |
| case KEY_UP: binding = &mode->keyUp; break; |
| case KEY_DOWN: binding = &mode->keyDown; break; |
| case KEY_CENTER: binding = &mode->keyCenter; break; |
| |
| default: |
| logMessage(LOG_WARNING, "unhandled key: %s -> %02X", mode->name, byte); |
| beep(brl); |
| return EOF; |
| } |
| |
| switch (binding->type) { |
| case IBT_unbound: |
| logMessage(LOG_WARNING, "unbound key: %s -> %02X", mode->name, byte); |
| beep(brl); |
| break; |
| |
| case IBT_command: |
| return binding->value.command; |
| |
| case IBT_block: |
| return binding->value.block + cursorOffset; |
| |
| case IBT_function: |
| return binding->value.function(brl); |
| |
| case IBT_submode: { |
| setInputMode(binding->value.submode); |
| break; |
| } |
| |
| default: |
| logMessage(LOG_WARNING, "unhandled input binding type: %02X", binding->type); |
| break; |
| } |
| |
| return BRL_CMD_NOOP; |
| } |