| /* |
| * 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; |
| } |