blob: bc053c78473df1d5aac47f113e2b9c008ee0a988 [file] [edit]
/*
* 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 <stdio.h>
#include <string.h>
#include <errno.h>
#include "log.h"
#include "ascii.h"
#include "brl_driver.h"
#include "io_serial.h"
static SerialDevice *serialDevice = NULL;
static unsigned int charactersPerSecond;
static unsigned char *outputBuffer = NULL;
static int
readBytes (unsigned char *buffer, int size, size_t *length) {
*length = 0;
while (*length < size) {
unsigned char byte;
if (!serialReadChunk(serialDevice, buffer, length, 1, 0, 100)) {
return 0;
}
byte = buffer[*length - 1];
if ((*length == 1) && (byte == ASCII_ACK)) {
*length = 0;
continue;
}
if (byte == ASCII_CR) {
logBytes(LOG_DEBUG, "Read", buffer, *length);
return 1;
}
}
return 0;
}
static int
writeBytes (BrailleDisplay *brl, const unsigned char *bytes, int count) {
logBytes(LOG_DEBUG, "Write", bytes, count);
if (serialWriteData(serialDevice, bytes, count) == -1) return 0;
brl->writeDelay += (count * 1000 / charactersPerSecond) + 1;
return 1;
}
static int
writeAcknowledgement (BrailleDisplay *brl) {
static const unsigned char acknowledgement[] = {ASCII_ACK};
return writeBytes(brl, acknowledgement, sizeof(acknowledgement));
}
static int
writeCells (BrailleDisplay *brl) {
static const unsigned char header[] = {'D'};
static const unsigned char trailer[] = {ASCII_CR};
unsigned char buffer[sizeof(header) + brl->textColumns + sizeof(trailer)];
unsigned char *byte = buffer;
byte = mempcpy(byte, header, sizeof(header));
byte = translateOutputCells(byte, outputBuffer, brl->textColumns);
byte = mempcpy(byte, trailer, sizeof(trailer));
return writeBytes(brl, buffer, byte-buffer);
}
static int
writeString (BrailleDisplay *brl, const char *string) {
return writeBytes(brl, (const unsigned char *)string, strlen(string));
}
static int
skipCharacter (unsigned char character, const unsigned char **bytes, int *count) {
int found = 0;
while (*count) {
if (**bytes != character) break;
found = 1;
++*bytes, --*count;
}
return found;
}
static int
interpretNumber (int *number, const unsigned char **bytes, int *count) {
int ok = skipCharacter('0', bytes, count);
*number = 0;
while (*count) {
static unsigned char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
const unsigned char *digit = memchr(digits, **bytes, sizeof(digits));
if (!digit) break;
*number = (*number * 10) + (digit - digits);
ok = 1;
++*bytes, --*count;
}
return ok;
}
static int
identifyDisplay (BrailleDisplay *brl) {
static const unsigned char identify[] = {'I', ASCII_CR};
if (writeBytes(brl, identify, sizeof(identify))) {
if (serialAwaitInput(serialDevice, 1000)) {
unsigned char identity[0X100];
size_t length;
if (readBytes(identity, sizeof(identity), &length)) {
static const unsigned char prefix[] = {'b', 'r', 'a', 'u', 'd', 'i', ' '};
if ((length >= sizeof(prefix)) &&
(memcmp(identity, prefix, sizeof(prefix)) == 0)) {
const unsigned char *bytes = memchr(identity, ',', length);
if (bytes) {
int count = length - (bytes - identity);
int cells;
++bytes, --count;
skipCharacter(' ', &bytes, &count);
if (interpretNumber(&cells, &bytes, &count)) {
if (!count) {
logMessage(LOG_INFO, "Detected: %.*s", (int)length, identity);
brl->textColumns = cells;
brl->textRows = 1;
return 1;
}
}
}
}
logUnexpectedPacket(identity, length);
}
}
}
return 0;
}
static int
setTable (BrailleDisplay *brl, int table) {
char buffer[0X10];
snprintf(buffer, sizeof(buffer), "L%d\r", table);
return writeString(brl, buffer);
}
static int
brl_construct (BrailleDisplay *brl, char **parameters, const char *device) {
if (!isSerialDeviceIdentifier(&device)) {
unsupportedDeviceIdentifier(device);
return 0;
}
if ((serialDevice = serialOpenDevice(device))) {
static const unsigned int baud = 9600;
charactersPerSecond = baud / 10;
if (serialRestartDevice(serialDevice, baud)) {
if (identifyDisplay(brl)) {
MAKE_OUTPUT_TABLE(0X01, 0X02, 0X04, 0X10, 0X20, 0X40, 0X08, 0X80);
if ((outputBuffer = malloc(brl->textColumns))) {
if (setTable(brl, 0)) {
memset(outputBuffer, 0, brl->textColumns);
writeCells(brl);
return 1;
}
free(outputBuffer);
outputBuffer = NULL;
} else {
logSystemError("Output buffer allocation");
}
}
}
serialCloseDevice(serialDevice);
serialDevice = NULL;
}
return 0;
}
static void
brl_destruct (BrailleDisplay *brl) {
if (outputBuffer) {
free(outputBuffer);
outputBuffer = NULL;
}
if (serialDevice) {
serialCloseDevice(serialDevice);
serialDevice = NULL;
}
}
static int
brl_writeWindow (BrailleDisplay *brl, const wchar_t *text) {
if (cellsHaveChanged(outputBuffer, brl->buffer, brl->textColumns, NULL, NULL, NULL)) {
writeCells(brl);
}
return 1;
}
static int
brl_readCommand (BrailleDisplay *brl, KeyTableCommandContext context) {
unsigned char buffer[0X100];
size_t length;
while (readBytes(buffer, sizeof(buffer), &length)) {
const unsigned char *bytes = buffer;
int count = length;
if (count > 0) {
unsigned char category = *bytes++;
--count;
switch (category) {
case 'F': {
int keys;
writeAcknowledgement(brl);
if (interpretNumber(&keys, &bytes, &count)) {
if (!count) {
switch (keys) {
case 1: return BRL_CMD_TOP_LEFT;
case 2: return BRL_CMD_FWINLT;
case 3: return BRL_CMD_LNDN;
case 4: return BRL_CMD_LNUP;
case 5: return BRL_CMD_FWINRT;
case 6: return BRL_CMD_BOT_LEFT;
case 23: return BRL_CMD_LNBEG;
case 56: return BRL_CMD_LNEND;
case 14: return BRL_CMD_CSRVIS;
case 25: return BRL_CMD_DISPMD;
case 26: return BRL_CMD_INFO;
case 36: return BRL_CMD_HOME;
}
}
}
break;
}
case 'K': {
int key;
writeAcknowledgement(brl);
if (interpretNumber(&key, &bytes, &count)) {
if (!count) {
if ((key > 0) && (key <= brl->textColumns)) return BRL_CMD_BLK(ROUTE) + (key - 1);
}
}
break;
}
}
}
logUnexpectedPacket(buffer, length);
}
if (errno == EAGAIN) return EOF;
return BRL_CMD_RESTARTBRL;
}