blob: ece25ce2823f302428131049f73ad260d5f84912 [file] [log] [blame]
/*
* 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>.
*/
/* LogText/braille.c - Braille display library
* For Tactilog's LogText
* Author: Dave Mielke <dave@mielke.cc>
*/
#include "prologue.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "log.h"
#include "file.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) {
lockUmask();
mode_t mask = umask(0);
int result = mkfifo(path, mode);
int error = errno;
umask(mask);
unlockUmask();
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)] = ASCII_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[] = {ASCII_CR, ASCII_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;
}