blob: 9c6a9bfa0d91dfb49916c20165581ca1bb8dbd45 [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>.
*/
#include "prologue.h"
#include <string.h>
#include <errno.h>
#include "log.h"
#include "ctb_translate.h"
#include "brl_dots.h"
#include "file.h"
#include "parse.h"
#include "utf8.h"
static int
putExternalRequests (BrailleContractionData *bcd) {
typedef enum {
REQ_TEXT,
REQ_NUMBER
} ExternalRequestType;
typedef struct {
const char *name;
ExternalRequestType type;
union {
struct {
const wchar_t *start;
size_t count;
} text;
unsigned int number;
} value;
} ExternalRequestEntry;
const ExternalRequestEntry externalRequestTable[] = {
{ .name = "cursor-position",
.type = REQ_NUMBER,
.value.number = bcd->input.cursor? bcd->input.cursor-bcd->input.begin+1: 0
},
{ .name = "expand-current-word",
.type = REQ_NUMBER,
.value.number = prefs.expandCurrentWord
},
{ .name = "capitalization-mode",
.type = REQ_NUMBER,
.value.number = prefs.capitalizationMode
},
{ .name = "maximum-length",
.type = REQ_NUMBER,
.value.number = getOutputCount(bcd)
},
{ .name = "text",
.type = REQ_TEXT,
.value.text = {
.start = bcd->input.begin,
.count = getInputCount(bcd)
}
},
{ .name = NULL }
};
FILE *stream = bcd->table->data.external.standardInput;
const ExternalRequestEntry *req = externalRequestTable;
while (req->name) {
if (fputs(req->name, stream) == EOF) goto outputError;
if (fputc('=', stream) == EOF) goto outputError;
switch (req->type) {
case REQ_TEXT: {
const wchar_t *character = req->value.text.start;
const wchar_t *end = character + req->value.text.count;
while (character < end) {
Utf8Buffer utf8;
size_t utfs = convertWcharToUtf8(*character++, utf8);
if (!utfs) return 0;
if (fputs(utf8, stream) == EOF) goto outputError;
}
break;
}
case REQ_NUMBER:
if (fprintf(stream, "%u", req->value.number) == EOF) goto outputError;
break;
default:
logMessage(LOG_WARNING, "unimplemented external contraction request property type: %s: %u (%s)", bcd->table->data.external.command, req->type, req->name);
return 0;
}
if (fputc('\n', stream) == EOF) goto outputError;
req += 1;
}
if (fflush(stream) == EOF) goto outputError;
return 1;
outputError:
logMessage(LOG_WARNING, "external contraction output error: %s: %s", bcd->table->data.external.command, strerror(errno));
return 0;
}
static const unsigned char brfTable[0X40] = {
/* 0X20 */ 0,
/* 0X21 ! */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_6,
/* 0X22 " */ BRL_DOT_5,
/* 0X23 # */ BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
/* 0X24 $ */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_6,
/* 0X25 % */ BRL_DOT_1 | BRL_DOT_4 | BRL_DOT_6,
/* 0X26 & */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_6,
/* 0X27 ' */ BRL_DOT_3,
/* 0X28 ( */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_5 | BRL_DOT_6,
/* 0X29 ) */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
/* 0X2A * */ BRL_DOT_1 | BRL_DOT_6,
/* 0X2B + */ BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_6,
/* 0X2C , */ BRL_DOT_6,
/* 0X2D - */ BRL_DOT_3 | BRL_DOT_6,
/* 0X2E . */ BRL_DOT_4 | BRL_DOT_6,
/* 0X2F / */ BRL_DOT_3 | BRL_DOT_4,
/* 0X30 0 */ BRL_DOT_3 | BRL_DOT_5 | BRL_DOT_6,
/* 0X31 1 */ BRL_DOT_2,
/* 0X32 2 */ BRL_DOT_2 | BRL_DOT_3,
/* 0X33 3 */ BRL_DOT_2 | BRL_DOT_5,
/* 0X34 4 */ BRL_DOT_2 | BRL_DOT_5 | BRL_DOT_6,
/* 0X35 5 */ BRL_DOT_2 | BRL_DOT_6,
/* 0X36 6 */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_5,
/* 0X37 7 */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_5 | BRL_DOT_6,
/* 0X38 8 */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_6,
/* 0X39 9 */ BRL_DOT_3 | BRL_DOT_5,
/* 0X3A : */ BRL_DOT_1 | BRL_DOT_5 | BRL_DOT_6,
/* 0X3B ; */ BRL_DOT_5 | BRL_DOT_6,
/* 0X3C < */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_6,
/* 0X3D = */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
/* 0X3E > */ BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5,
/* 0X3F ? */ BRL_DOT_1 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
/* 0X40 @ */ BRL_DOT_4,
/* 0X41 A */ BRL_DOT_1,
/* 0X42 B */ BRL_DOT_1 | BRL_DOT_2,
/* 0X43 C */ BRL_DOT_1 | BRL_DOT_4,
/* 0X44 D */ BRL_DOT_1 | BRL_DOT_4 | BRL_DOT_5,
/* 0X45 E */ BRL_DOT_1 | BRL_DOT_5,
/* 0X46 F */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4,
/* 0X47 G */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5,
/* 0X48 H */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_5,
/* 0X49 I */ BRL_DOT_2 | BRL_DOT_4,
/* 0X4A J */ BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5,
/* 0X4B K */ BRL_DOT_1 | BRL_DOT_3,
/* 0X4C L */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3,
/* 0X4D M */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_4,
/* 0X4E N */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5,
/* 0X4F O */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_5,
/* 0X50 P */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4,
/* 0X51 Q */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5,
/* 0X52 R */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_5,
/* 0X53 S */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4,
/* 0X54 T */ BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5,
/* 0X55 U */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_6,
/* 0X56 V */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_3 | BRL_DOT_6,
/* 0X57 W */ BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
/* 0X58 X */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_6,
/* 0X59 Y */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
/* 0X5A Z */ BRL_DOT_1 | BRL_DOT_3 | BRL_DOT_5 | BRL_DOT_6,
/* 0X5B [ */ BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_6,
/* 0X5C \ */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_5 | BRL_DOT_6,
/* 0X5D ] */ BRL_DOT_1 | BRL_DOT_2 | BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6,
/* 0X5E ^ */ BRL_DOT_4 | BRL_DOT_5,
/* 0X5F _ */ BRL_DOT_4 | BRL_DOT_5 | BRL_DOT_6
};
static int
handleExternalResponse_brf (BrailleContractionData *bcd, const char *value) {
int useDot7 = prefs.capitalizationMode == CTB_CAP_DOT7;
while (*value && (bcd->output.current < bcd->output.end)) {
unsigned char brf = *value++ & 0XFF;
unsigned char dots = 0;
unsigned char superimpose = 0;
if ((brf >= 0X60) && (brf <= 0X7F)) {
brf -= 0X20;
} else if ((brf >= 0X41) && (brf <= 0X5A)) {
if (useDot7) superimpose |= BRL_DOT_7;
}
if ((brf >= 0X20) && (brf <= 0X5F)) dots = brfTable[brf - 0X20] | superimpose;
*bcd->output.current++ = dots;
}
return 1;
}
static int
handleExternalResponse_consumedLength (BrailleContractionData *bcd, const char *value) {
int length;
if (!isInteger(&length, value)) return 0;
if (length < 1) return 0;
if (length > getInputCount(bcd)) return 0;
bcd->input.current = bcd->input.begin + length;
return 1;
}
static int
handleExternalResponse_outputOffsets (BrailleContractionData *bcd, const char *value) {
if (bcd->input.offsets) {
int previous = CTB_NO_OFFSET;
unsigned int count = getInputCount(bcd);
unsigned int index = 0;
while (*value && (index < count)) {
int offset;
{
char *delimiter = strchr(value, ',');
if (delimiter) {
int ok;
{
char oldDelimiter = *delimiter;
*delimiter = 0;
ok = isInteger(&offset, value);
*delimiter = oldDelimiter;
}
if (!ok) return 0;
value = delimiter + 1;
} else if (isInteger(&offset, value)) {
value += strlen(value);
} else {
return 0;
}
}
if (offset < ((index == 0)? 0: previous)) return 0;
if (offset >= getOutputCount(bcd)) return 0;
bcd->input.offsets[index++] = (offset == previous)? CTB_NO_OFFSET: offset;
previous = offset;
}
}
return 1;
}
typedef struct {
const char *name;
int (*handler) (BrailleContractionData *bcd, const char *value);
unsigned stop:1;
} ExternalResponseEntry;
static const ExternalResponseEntry externalResponseTable[] = {
{ .name = "brf",
.stop = 1,
.handler = handleExternalResponse_brf
},
{ .name = "consumed-length",
.handler = handleExternalResponse_consumedLength
},
{ .name = "output-offsets",
.handler = handleExternalResponse_outputOffsets
},
{ .name = NULL }
};
static int
getExternalResponses (BrailleContractionData *bcd) {
FILE *stream = bcd->table->data.external.standardOutput;
while (readLine(stream, &bcd->table->data.external.input.buffer, &bcd->table->data.external.input.size, NULL)) {
int ok = 0;
int stop = 0;
char *delimiter = strchr(bcd->table->data.external.input.buffer, '=');
if (delimiter) {
const char *value = delimiter + 1;
const ExternalResponseEntry *rsp = externalResponseTable;
char oldDelimiter = *delimiter;
*delimiter = 0;
while (rsp->name) {
if (strcmp(bcd->table->data.external.input.buffer, rsp->name) == 0) {
if (rsp->handler(bcd, value)) ok = 1;
if (rsp->stop) stop = 1;
break;
}
rsp += 1;
}
*delimiter = oldDelimiter;
}
if (!ok) logMessage(LOG_WARNING, "unexpected external contraction response: %s: %s", bcd->table->data.external.command, bcd->table->data.external.input.buffer);
if (stop) return 1;
}
logMessage(LOG_WARNING, "incomplete external contraction response: %s", bcd->table->data.external.command);
return 0;
}
static int
contractText_external (BrailleContractionData *bcd) {
setOffset(bcd);
while (++bcd->input.current < bcd->input.end) clearOffset(bcd);
if (startContractionCommand(bcd->table)) {
if (putExternalRequests(bcd)) {
if (getExternalResponses(bcd)) {
return 1;
}
}
}
stopContractionCommand(bcd->table);
return 0;
}
static void
finishCharacterEntry_external (BrailleContractionData *bcd, CharacterEntry *entry) {
}
static const ContractionTableTranslationMethods externalTranslationMethods = {
.contractText = contractText_external,
.finishCharacterEntry = finishCharacterEntry_external
};
const ContractionTableTranslationMethods *
getContractionTableTranslationMethods_external (void) {
return &externalTranslationMethods;
}