/* * 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 . */ /* LogText/braille.c - Braille display library * For Tactilog's LogText * Author: Dave Mielke */ #include "prologue.h" #include #include #include #include #include #include #include "log.h" #include "device.h" #include "async_wait.h" #include "ascii.h" #define BRL_STATUS_FIELDS sfGeneric #define BRL_HAVE_STATUS_CELLS #include "brl_driver.h" #include "braille.h" #include "io_serial.h" static SerialDevice *serialDevice = NULL; #define screenHeight 25 #define screenWidth 80 typedef unsigned char ScreenImage[screenHeight][screenWidth]; static ScreenImage sourceImage; static ScreenImage targetImage; static const char *downloadPath = "logtext-download"; typedef enum { DEV_OFFLINE, DEV_ONLINE, DEV_READY } DeviceStatus; static DeviceStatus deviceStatus; static KeyTableCommandContext currentContext; static unsigned char currentLine; static unsigned char cursorRow; static unsigned char cursorColumn; #ifndef __MINGW32__ static int makeFifo (const char *path, mode_t mode) { struct stat status; if (lstat(path, &status) != -1) { if (S_ISFIFO(status.st_mode)) return 1; logMessage(LOG_ERR, "Download object not a FIFO: %s", path); } else if (errno == ENOENT) { mode_t mask = umask(0); int result = mkfifo(path, mode); int error = errno; umask(mask); if (result != -1) return 1; errno = error; logSystemError("Download FIFO creation"); } return 0; } #endif /* __MINGW32__ */ static int makeDownloadFifo (void) { #ifdef __MINGW32__ return 0; #else /* __MINGW32__ */ return makeFifo(downloadPath, S_IRUSR|S_IWUSR|S_IWGRP|S_IWOTH); #endif /* __MINGW32__ */ } static int brl_construct (BrailleDisplay *brl, char **parameters, const char *device) { { static TranslationTable outputTable = { #include "brl-out.h" }; setOutputTable(outputTable); makeInputTable(); { const unsigned char byte = 0XFF; if (memchr(outputTable, byte, sizeof(outputTable))) { outputTable[translateInputCell(byte)] = SUB; } } } if (!isSerialDeviceIdentifier(&device)) { unsupportedDeviceIdentifier(device); return 0; } makeDownloadFifo(); if ((serialDevice = serialOpenDevice(device))) { if (serialRestartDevice(serialDevice, 9600)) { brl->textRows = screenHeight; brl->textColumns = screenWidth; brl->buffer = &sourceImage[0][0]; memset(sourceImage, 0, sizeof(sourceImage)); deviceStatus = DEV_ONLINE; return 1; } serialCloseDevice(serialDevice); serialDevice = NULL; } return 0; } static void brl_destruct (BrailleDisplay *brl) { serialCloseDevice(serialDevice); serialDevice = NULL; } static int checkData (const unsigned char *data, unsigned int length) { if ((length < 5) || (length != (data[4] + 5))) { logMessage(LOG_ERR, "Bad length: %d", length); } else if (data[0] != 255) { logMessage(LOG_ERR, "Bad header: %d", data[0]); } else if ((data[1] < 1) || (data[1] > screenHeight)) { logMessage(LOG_ERR, "Bad line: %d", data[1]); } else if (data[2] > screenWidth) { logMessage(LOG_ERR, "Bad cursor: %d", data[2]); } else if ((data[3] < 1) || (data[3] > screenWidth)) { logMessage(LOG_ERR, "Bad column: %d", data[3]); } else if (data[4] > (screenWidth - (data[3] - 1))) { logMessage(LOG_ERR, "Bad count: %d", data[4]); } else { return 1; } return 0; } static int sendBytes (const unsigned char *bytes, size_t count) { if (serialWriteData(serialDevice, bytes, count) == -1) { logSystemError("LogText write"); return 0; } return 1; } static int sendData (unsigned char line, unsigned char column, unsigned char count) { unsigned char data[5 + count]; unsigned char *target = data; unsigned char *source = &targetImage[line][column]; *target++ = 0XFF; *target++ = line + 1; *target++ = (line == cursorRow)? cursorColumn+1: 0; *target++ = column + 1; *target++ = count; logBytes(LOG_DEBUG, "Output dots", source, count); target = translateOutputCells(target, source, count); count = target - data; logBytes(LOG_DEBUG, "LogText write", data, count); if (checkData(data, count)) { if (sendBytes(data, count)) { return 1; } } return 0; } static int sendLine (unsigned char line, int force) { unsigned char *source = &sourceImage[line][0]; unsigned char *target = &targetImage[line][0]; unsigned char start = 0; unsigned char count = screenWidth; while (count > 0) { if (source[count-1] != target[count-1]) break; --count; } while (start < count) { if (source[start] != target[start]) break; ++start; } if ((count -= start) || force) { logMessage(LOG_DEBUG, "LogText line: line=%d, column=%d, count=%d", line, start, count); memcpy(&target[start], &source[start], count); if (!sendData(line, start, count)) { return 0; } } return 1; } static int sendCurrentLine (void) { return sendLine(currentLine, 0); } static int sendCursorRow (void) { return sendLine(cursorRow, 1); } static int handleUpdate (unsigned char line) { logMessage(LOG_DEBUG, "Request line: (0X%2.2X) 0X%2.2X dec=%d", KEY_UPDATE, line, line); if (!line) return sendCursorRow(); if (line <= screenHeight) { currentLine = line - 1; return sendCurrentLine(); } logMessage(LOG_WARNING, "Invalid line request: %d", line); return 1; } static int brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) { if (deviceStatus == DEV_READY) { sendCurrentLine(); } return 1; } static int isOnline (void) { int online = serialTestLineDSR(serialDevice); if (online) { if (deviceStatus < DEV_ONLINE) { deviceStatus = DEV_ONLINE; logMessage(LOG_WARNING, "LogText online."); } } else { if (deviceStatus > DEV_OFFLINE) { deviceStatus = DEV_OFFLINE; logMessage(LOG_WARNING, "LogText offline."); } } return online; } static int brl_writeStatus (BrailleDisplay *brl, const unsigned char *status) { if (isOnline()) { if (status[GSC_FIRST] == GSC_MARKER) { unsigned char row = status[gscScreenCursorRow]; unsigned char column = status[gscScreenCursorColumn]; row = MAX(1, MIN(row, screenHeight)) - 1; column = MAX(1, MIN(column, screenWidth)) - 1; if (deviceStatus < DEV_READY) { memset(targetImage, 0, sizeof(targetImage)); currentContext = KTB_CTX_DEFAULT; currentLine = row; cursorRow = screenHeight; cursorColumn = screenWidth; deviceStatus = DEV_READY; } if ((row != cursorRow) || (column != cursorColumn)) { logMessage(LOG_DEBUG, "cursor moved: [%d,%d] -> [%d,%d]", cursorColumn, cursorRow, column, row); cursorRow = row; cursorColumn = column; sendCursorRow(); } } } return 1; } static int readKey (void) { unsigned char key; unsigned char arg; if (serialReadData(serialDevice, &key, 1, 0, 0) != 1) return EOF; switch (key) { default: arg = 0; break; case KEY_FUNCTION: case KEY_FUNCTION2: case KEY_UPDATE: while (serialReadData(serialDevice, &arg, 1, 0, 0) != 1) asyncWait(1); break; } { int result = COMPOUND_KEY(key, arg); logMessage(LOG_DEBUG, "Key read: %4.4X", result); return result; } } /*askUser static unsigned char *selectedLine; static void replaceCharacters (const unsigned char *address, size_t count) { translateInputCells(&selectedLine[cursorColumn], address, count); cursorColumn += count; } static void insertCharacters (const unsigned char *address, size_t count) { memmove(&selectedLine[cursorColumn+count], &selectedLine[cursorColumn], screenWidth-cursorColumn-count); replaceCharacters(address, count); } static void deleteCharacters (size_t count) { memmove(&selectedLine[cursorColumn], &selectedLine[cursorColumn+count], screenWidth-cursorColumn-count); memset(&selectedLine[screenWidth-count], translateInputCell(' '), count); } static void clearCharacters (void) { cursorColumn = 0; deleteCharacters(screenWidth); } static void selectLine (unsigned char line) { selectedLine = &sourceImage[cursorRow = line][0]; clearCharacters(); deviceStatus = DEV_ONLINE; } static unsigned char * askUser (const unsigned char *prompt) { unsigned char from; unsigned char to; selectLine(screenHeight-1); logMessage(LOG_DEBUG, "Prompt: %s", prompt); replaceCharacters(prompt, strlen(prompt)); from = to = ++cursorColumn; sendCursorRow(); while (1) { int key = readKey(); if (key == EOF) { asyncWait(1); continue; } if ((key & KEY_MASK) == KEY_UPDATE) { handleUpdate(key >> KEY_SHIFT); continue; } if (isgraph(key)) { if (to < screenWidth) { unsigned char character = key & KEY_MASK; insertCharacters(&character, 1); ++to; } else { ringConsoleBell(); } } else { switch (key) { case CR: if (to > from) { size_t length = to - from; unsigned char *response = malloc(length+1); if (response) { translateOutputCells(response, &selectedLine[from], length); response[length] = 0; logMessage(LOG_DEBUG, "Response: %s", response); return response; } else { logSystemError("Download file path allocation"); } } return NULL; case BS: if (cursorColumn > from) { --cursorColumn; deleteCharacters(1); --to; } else { ringConsoleBell(); } break; case DEL: if (cursorColumn < to) { deleteCharacters(1); --to; } else { ringConsoleBell(); } break; case KEY_FUNCTION_CURSOR_LEFT: if (cursorColumn > from) { --cursorColumn; } else { ringConsoleBell(); } break; case KEY_FUNCTION_CURSOR_LEFT_JUMP: if (cursorColumn > from) { cursorColumn = from; } else { ringConsoleBell(); } break; case KEY_FUNCTION_CURSOR_RIGHT: if (cursorColumn < to) { ++cursorColumn; } else { ringConsoleBell(); } break; case KEY_FUNCTION_CURSOR_RIGHT_JUMP: if (cursorColumn < to) { cursorColumn = to; } else { ringConsoleBell(); } break; default: ringConsoleBell(); break; } } sendCursorRow(); } } */ static void downloadFile (void) { if (makeDownloadFifo()) { int file = open(downloadPath, O_RDONLY); if (file != -1) { struct stat status; if (fstat(file, &status) != -1) { unsigned char buffer[0X400]; const unsigned char *address = buffer; int count = 0; while (1) { const unsigned char *newline; if (!count) { count = read(file, buffer, sizeof(buffer)); if (!count) { static const unsigned char fileTrailer[] = {0X1A}; sendBytes(fileTrailer, sizeof(fileTrailer)); break; } if (count == -1) { logSystemError("Download file read"); break; } address = buffer; } if ((newline = memchr(address, '\n', count))) { static const unsigned char lineTrailer[] = {CR, LF}; size_t length = newline - address; if (!sendBytes(address, length++)) break; if (!sendBytes(lineTrailer, sizeof(lineTrailer))) break; address += length; count -= length; } else { if (!sendBytes(address, count)) break; count = 0; } } } else { logSystemError("Download file status"); } if (close(file) == -1) { logSystemError("Download file close"); } } else { logSystemError("Download file open"); } } else { logMessage(LOG_WARNING, "Download path not specified."); } } static int brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) { int key = readKey(); if (context != currentContext) { logMessage(LOG_DEBUG, "Context switch: %d -> %d", currentContext, context); switch (currentContext = context) { case KTB_CTX_DEFAULT: deviceStatus = DEV_ONLINE; break; default: break; } } if (key != EOF) { switch (key) { case KEY_FUNCTION_ENTER: return BRL_CMD_KEY(ENTER); case KEY_FUNCTION_TAB: return BRL_CMD_KEY(TAB); case KEY_FUNCTION_CURSOR_UP: return BRL_CMD_KEY(CURSOR_UP); case KEY_FUNCTION_CURSOR_DOWN: return BRL_CMD_KEY(CURSOR_DOWN); case KEY_FUNCTION_CURSOR_LEFT: return BRL_CMD_KEY(CURSOR_LEFT); case KEY_FUNCTION_CURSOR_RIGHT: return BRL_CMD_KEY(CURSOR_RIGHT); case KEY_FUNCTION_CURSOR_UP_JUMP: return BRL_CMD_KEY(HOME); case KEY_FUNCTION_CURSOR_DOWN_JUMP: return BRL_CMD_KEY(END); case KEY_FUNCTION_CURSOR_LEFT_JUMP: return BRL_CMD_KEY(PAGE_UP); case KEY_FUNCTION_CURSOR_RIGHT_JUMP: return BRL_CMD_KEY(PAGE_DOWN); case KEY_FUNCTION_F1: return BRL_CMD_KFN(1); case KEY_FUNCTION_F2: return BRL_CMD_KFN(2); case KEY_FUNCTION_F3: return BRL_CMD_KFN(3); case KEY_FUNCTION_F4: return BRL_CMD_KFN(4); case KEY_FUNCTION_F5: return BRL_CMD_KFN(5); case KEY_FUNCTION_F6: return BRL_CMD_KFN(6); case KEY_FUNCTION_F7: return BRL_CMD_KFN(7); case KEY_FUNCTION_F9: return BRL_CMD_KFN(9); case KEY_FUNCTION_F10: return BRL_CMD_KFN(10); case KEY_COMMAND: { int command; while ((command = readKey()) == EOF) asyncWait(1); logMessage(LOG_DEBUG, "Received command: (0x%2.2X) 0x%4.4X", KEY_COMMAND, command); switch (command) { case KEY_COMMAND: /* pressing the escape command twice will pass it through */ return BRL_CMD_BLK(PASSDOTS) + translateInputCell(KEY_COMMAND); case KEY_COMMAND_SWITCHVT_PREV: return BRL_CMD_SWITCHVT_PREV; case KEY_COMMAND_SWITCHVT_NEXT: return BRL_CMD_SWITCHVT_NEXT; case KEY_COMMAND_SWITCHVT_1: return BRL_CMD_BLK(SWITCHVT) + 0; case KEY_COMMAND_SWITCHVT_2: return BRL_CMD_BLK(SWITCHVT) + 1; case KEY_COMMAND_SWITCHVT_3: return BRL_CMD_BLK(SWITCHVT) + 2; case KEY_COMMAND_SWITCHVT_4: return BRL_CMD_BLK(SWITCHVT) + 3; case KEY_COMMAND_SWITCHVT_5: return BRL_CMD_BLK(SWITCHVT) + 4; case KEY_COMMAND_SWITCHVT_6: return BRL_CMD_BLK(SWITCHVT) + 5; case KEY_COMMAND_SWITCHVT_7: return BRL_CMD_BLK(SWITCHVT) + 6; case KEY_COMMAND_SWITCHVT_8: return BRL_CMD_BLK(SWITCHVT) + 7; case KEY_COMMAND_SWITCHVT_9: return BRL_CMD_BLK(SWITCHVT) + 8; case KEY_COMMAND_SWITCHVT_10: return BRL_CMD_BLK(SWITCHVT) + 9; case KEY_COMMAND_PAGE_UP: return BRL_CMD_KEY(PAGE_UP); case KEY_COMMAND_PAGE_DOWN: return BRL_CMD_KEY(PAGE_DOWN); case KEY_COMMAND_PREFMENU: currentLine = 0; cursorRow = 0; cursorColumn = 31; sendCursorRow(); return BRL_CMD_PREFMENU; case KEY_COMMAND_PREFSAVE: return BRL_CMD_PREFSAVE; case KEY_COMMAND_PREFLOAD: return BRL_CMD_PREFLOAD; case KEY_COMMAND_FREEZE_ON: return BRL_CMD_FREEZE | BRL_FLG_TOGGLE_ON; case KEY_COMMAND_FREEZE_OFF: return BRL_CMD_FREEZE | BRL_FLG_TOGGLE_OFF; case KEY_COMMAND_RESTARTBRL: return BRL_CMD_RESTARTBRL; case KEY_COMMAND_DOWNLOAD: downloadFile(); break; default: logMessage(LOG_WARNING, "Unknown command: (0X%2.2X) 0X%4.4X", KEY_COMMAND, command); break; } break; } default: switch (key & KEY_MASK) { case KEY_UPDATE: handleUpdate(key >> KEY_SHIFT); break; case KEY_FUNCTION: logMessage(LOG_WARNING, "Unknown function: (0X%2.2X) 0X%4.4X", KEY_COMMAND, key>>KEY_SHIFT); break; default: { unsigned char dots = translateInputCell(key); logMessage(LOG_DEBUG, "Received character: 0X%2.2X dec=%d dots=%2.2X", key, key, dots); return BRL_CMD_BLK(PASSDOTS) + dots; } } break; } } return EOF; }