| /* |
| * 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 "report.h" |
| #include "api_control.h" |
| #include "queue.h" |
| #include "async_handle.h" |
| #include "async_alarm.h" |
| #include "brl_base.h" |
| #include "brl_utils.h" |
| #include "brl_dots.h" |
| #include "kbd_keycodes.h" |
| #include "io_generic.h" |
| #include "cmd_queue.h" |
| #include "ktb.h" |
| |
| const DotsTable dotsTable_ISO11548_1 = { |
| BRL_DOT_1, BRL_DOT_2, BRL_DOT_3, BRL_DOT_4, |
| BRL_DOT_5, BRL_DOT_6, BRL_DOT_7, BRL_DOT_8 |
| }; |
| |
| const DotsTable dotsTable_rotated = { |
| BRL_DOT_8, BRL_DOT_6, BRL_DOT_5, BRL_DOT_7, |
| BRL_DOT_3, BRL_DOT_2, BRL_DOT_4, BRL_DOT_1 |
| }; |
| |
| void |
| makeTranslationTable (const DotsTable dots, TranslationTable table) { |
| int byte; |
| |
| for (byte=0; byte<TRANSLATION_TABLE_SIZE; byte+=1) { |
| unsigned char cell = 0; |
| int dot; |
| |
| for (dot=0; dot<DOTS_TABLE_SIZE; dot+=1) { |
| if (byte & dotsTable_ISO11548_1[dot]) { |
| cell |= dots[dot]; |
| } |
| } |
| |
| table[byte] = cell; |
| } |
| } |
| |
| void |
| reverseTranslationTable (const TranslationTable from, TranslationTable to) { |
| int byte; |
| memset(to, 0, sizeof(TranslationTable)); |
| for (byte=TRANSLATION_TABLE_SIZE-1; byte>=0; byte--) to[from[byte]] = byte; |
| } |
| |
| static inline void * |
| translateCells ( |
| const TranslationTable table, |
| unsigned char *target, const unsigned char *source, size_t count |
| ) { |
| if (table) { |
| while (count--) *target++ = table[*source++]; |
| return target; |
| } |
| |
| if (target == source) return target + count; |
| return mempcpy(target, source, count); |
| } |
| |
| static inline unsigned char |
| translateCell (const TranslationTable table, unsigned char cell) { |
| return table? table[cell]: cell; |
| } |
| |
| static TranslationTable internalOutputTable; |
| static const unsigned char *outputTable; |
| |
| void |
| setOutputTable (const TranslationTable table) { |
| outputTable = table; |
| } |
| |
| void |
| makeOutputTable (const DotsTable dots) { |
| if (memcmp(dots, dotsTable_ISO11548_1, DOTS_TABLE_SIZE) == 0) { |
| outputTable = NULL; |
| } else { |
| makeTranslationTable(dots, internalOutputTable); |
| outputTable = internalOutputTable; |
| } |
| } |
| |
| void * |
| translateOutputCells (unsigned char *target, const unsigned char *source, size_t count) { |
| return translateCells(outputTable, target, source, count); |
| } |
| |
| unsigned char |
| translateOutputCell (unsigned char cell) { |
| return translateCell(outputTable, cell); |
| } |
| |
| static TranslationTable internalInputTable; |
| static const unsigned char *inputTable; |
| |
| void |
| makeInputTable (void) { |
| if (outputTable) { |
| reverseTranslationTable(outputTable, internalInputTable); |
| inputTable = internalInputTable; |
| } else { |
| inputTable = NULL; |
| } |
| } |
| |
| void * |
| translateInputCells (unsigned char *target, const unsigned char *source, size_t count) { |
| return translateCells(inputTable, target, source, count); |
| } |
| |
| unsigned char |
| translateInputCell (unsigned char cell) { |
| return translateCell(inputTable, cell); |
| } |
| |
| int |
| awaitBrailleInput (BrailleDisplay *brl, int timeout) { |
| return gioAwaitInput(brl->gioEndpoint, timeout); |
| } |
| |
| int |
| connectBrailleResource ( |
| BrailleDisplay *brl, |
| const char *identifier, |
| const GioDescriptor *descriptor, |
| BrailleSessionInitializer *initializeSession |
| ) { |
| if ((brl->gioEndpoint = gioConnectResource(identifier, descriptor))) { |
| if (!initializeSession || initializeSession(brl)) { |
| if (gioDiscardInput(brl->gioEndpoint)) { |
| return 1; |
| } |
| } |
| |
| gioDisconnectResource(brl->gioEndpoint); |
| brl->gioEndpoint = NULL; |
| } |
| |
| return 0; |
| } |
| |
| void |
| disconnectBrailleResource ( |
| BrailleDisplay *brl, |
| BrailleSessionEnder *endSession |
| ) { |
| if (brl->gioEndpoint) { |
| if (endSession) endSession(brl); |
| drainBrailleOutput(brl, 0); |
| gioDisconnectResource(brl->gioEndpoint); |
| brl->gioEndpoint = NULL; |
| } |
| } |
| |
| size_t |
| readBraillePacket ( |
| BrailleDisplay *brl, |
| GioEndpoint *endpoint, |
| void *packet, size_t size, |
| BraillePacketVerifier *verifyPacket, void *data |
| ) { |
| if (!endpoint) endpoint = brl->gioEndpoint; |
| |
| unsigned char *bytes = packet; |
| size_t count = 0; |
| size_t length = 1; |
| int started = 0; |
| |
| while (1) { |
| unsigned char byte; |
| |
| if (!gioReadByte(endpoint, &byte, started)) { |
| if (count > 0) logPartialPacket(bytes, count); |
| return 0; |
| } |
| |
| gotByte: |
| if (count < size) { |
| bytes[count++] = byte; |
| BraillePacketVerifierResult result = verifyPacket(brl, bytes, count, &length, data); |
| |
| switch (result) { |
| case BRL_PVR_EXCLUDE: |
| count -= 1; |
| /* fall through */ |
| case BRL_PVR_INCLUDE: |
| started = 1; |
| break; |
| |
| case BRL_PVR_IGNORE: |
| count -= 1; |
| continue; |
| |
| default: |
| logMessage(LOG_WARNING, "unimplemented braille packet verifier result: %u", result); |
| /* fall through */ |
| case BRL_PVR_INVALID: |
| started = 0; |
| length = 1; |
| |
| if (--count) { |
| logShortPacket(bytes, count); |
| count = 0; |
| goto gotByte; |
| } |
| |
| logIgnoredByte(byte); |
| continue; |
| } |
| |
| if (count >= length) { |
| logInputPacket(bytes, length); |
| return length; |
| } |
| } else { |
| if (count++ == size) logTruncatedPacket(bytes, size); |
| logDiscardedByte(byte); |
| } |
| } |
| } |
| |
| int |
| writeBraillePacket ( |
| BrailleDisplay *brl, GioEndpoint *endpoint, |
| const void *packet, size_t size |
| ) { |
| if (!endpoint) endpoint = brl->gioEndpoint; |
| logOutputPacket(packet, size); |
| if (gioWriteData(endpoint, packet, size) == -1) return 0; |
| |
| if (endpoint == brl->gioEndpoint) { |
| brl->writeDelay += gioGetMillisecondsToTransfer(endpoint, size); |
| } |
| |
| return 1; |
| } |
| |
| typedef struct { |
| GioEndpoint *endpoint; |
| unsigned int type; |
| size_t size; |
| unsigned char packet[]; |
| } BrailleMessage; |
| |
| static void |
| logBrailleMessage (BrailleMessage *msg, const char *action) { |
| logBytes(LOG_CATEGORY(OUTPUT_PACKETS), "%s", msg->packet, msg->size, action); |
| } |
| |
| static void |
| deallocateBrailleMessage (BrailleMessage *msg) { |
| free(msg); |
| } |
| |
| static void |
| cancelBrailleMessageAlarm (BrailleDisplay *brl) { |
| if (brl->acknowledgements.alarm) { |
| asyncCancelRequest(brl->acknowledgements.alarm); |
| brl->acknowledgements.alarm = NULL; |
| } |
| } |
| |
| static void setBrailleMessageAlarm (BrailleDisplay *brl); |
| |
| static int |
| writeNextBrailleMessage (BrailleDisplay *brl) { |
| int ok = 1; |
| |
| if (brl->acknowledgements.messages) { |
| BrailleMessage *msg = dequeueItem(brl->acknowledgements.messages); |
| |
| if (msg) { |
| int written; |
| |
| logBrailleMessage(msg, "dequeued"); |
| written = writeBraillePacket(brl, msg->endpoint, msg->packet, msg->size); |
| |
| deallocateBrailleMessage(msg); |
| msg = NULL; |
| |
| if (written) { |
| setBrailleMessageAlarm(brl); |
| return 1; |
| } |
| |
| ok = 0; |
| } |
| } |
| |
| cancelBrailleMessageAlarm(brl); |
| return ok; |
| } |
| |
| int |
| acknowledgeBrailleMessage (BrailleDisplay *brl) { |
| logMessage(LOG_CATEGORY(OUTPUT_PACKETS), "acknowledged"); |
| brl->acknowledgements.missing.count = 0; |
| return writeNextBrailleMessage(brl); |
| } |
| |
| ASYNC_ALARM_CALLBACK(handleBrailleMessageTimeout) { |
| BrailleDisplay *brl = parameters->data; |
| |
| asyncDiscardHandle(brl->acknowledgements.alarm); |
| brl->acknowledgements.alarm = NULL; |
| |
| if ((brl->acknowledgements.missing.count += 1) < brl->acknowledgements.missing.limit) { |
| logMessage(LOG_WARNING, "missing braille message acknowledgement"); |
| writeNextBrailleMessage(brl); |
| } else { |
| logMessage(LOG_WARNING, "too many missing braille message acknowledgements"); |
| brl->hasFailed = 1; |
| } |
| } |
| |
| static void |
| setBrailleMessageAlarm (BrailleDisplay *brl) { |
| if (brl->acknowledgements.alarm) { |
| asyncResetAlarmIn(brl->acknowledgements.alarm, brl->acknowledgements.missing.timeout); |
| } else { |
| asyncNewRelativeAlarm(&brl->acknowledgements.alarm, brl->acknowledgements.missing.timeout, |
| handleBrailleMessageTimeout, brl); |
| } |
| } |
| |
| static int |
| findOldBrailleMessage (const void *item, void *data) { |
| const BrailleMessage *old = item; |
| const BrailleMessage *new = data; |
| |
| return old->type == new->type; |
| } |
| |
| static void |
| deallocateBrailleMessageItem (void *item, void *data) { |
| BrailleMessage *msg = item; |
| |
| deallocateBrailleMessage(msg); |
| } |
| |
| int |
| writeBrailleMessage ( |
| BrailleDisplay *brl, GioEndpoint *endpoint, |
| unsigned int type, |
| const void *packet, size_t size |
| ) { |
| if (brl->acknowledgements.alarm) { |
| BrailleMessage *msg; |
| |
| if (!brl->acknowledgements.messages) { |
| if (!(brl->acknowledgements.messages = newQueue(deallocateBrailleMessageItem, NULL))) { |
| return 0; |
| } |
| } |
| |
| if ((msg = malloc(sizeof(*msg) + size))) { |
| memset(msg, 0, sizeof(*msg)); |
| msg->endpoint = endpoint; |
| msg->type = type; |
| msg->size = size; |
| memcpy(msg->packet, packet, size); |
| |
| { |
| Element *element = findElement(brl->acknowledgements.messages, findOldBrailleMessage, msg); |
| |
| if (element) { |
| logBrailleMessage(getElementItem(element), "unqueued"); |
| deleteElement(element); |
| } |
| } |
| |
| if (enqueueItem(brl->acknowledgements.messages, msg)) { |
| logBrailleMessage(msg, "enqueued"); |
| return 1; |
| } |
| |
| logBrailleMessage(msg, "discarded"); |
| free(msg); |
| } else { |
| logMallocError(); |
| } |
| } else if (writeBraillePacket(brl, endpoint, packet, size)) { |
| setBrailleMessageAlarm(brl); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| void |
| endBrailleMessages (BrailleDisplay *brl) { |
| cancelBrailleMessageAlarm(brl); |
| |
| if (brl->acknowledgements.messages) { |
| deallocateQueue(brl->acknowledgements.messages); |
| brl->acknowledgements.messages = NULL; |
| } |
| } |
| |
| int |
| getBrailleReportSize (BrailleDisplay *brl, unsigned char identifier, HidReportSize *size) { |
| return gioGetHidReportSize(brl->gioEndpoint, identifier, size); |
| } |
| |
| int |
| getBrailleReportSizes (BrailleDisplay *brl, const BrailleReportSizeEntry *table) { |
| const BrailleReportSizeEntry *entry = table; |
| |
| while (entry->identifier) { |
| HidReportSize size; |
| if (!getBrailleReportSize(brl, entry->identifier, &size)) return 0; |
| |
| if (entry->input) *entry->input = size.input; |
| if (entry->output) *entry->output = size.output; |
| if (entry->feature) *entry->feature = size.feature; |
| |
| entry += 1; |
| } |
| |
| return 1; |
| } |
| |
| int |
| probeBrailleDisplay ( |
| BrailleDisplay *brl, unsigned int retryLimit, |
| GioEndpoint *endpoint, int inputTimeout, |
| BrailleRequestWriter *writeRequest, |
| BraillePacketReader *readPacket, void *responsePacket, size_t responseSize, |
| BrailleResponseHandler *handleResponse |
| ) { |
| unsigned int retryCount = 0; |
| |
| if (!endpoint) endpoint = brl->gioEndpoint; |
| |
| while (writeRequest(brl)) { |
| drainBrailleOutput(brl, 0); |
| |
| while (gioAwaitInput(endpoint, inputTimeout)) { |
| size_t size = readPacket(brl, responsePacket, responseSize); |
| if (!size) break; |
| |
| { |
| BrailleResponseResult result = handleResponse(brl, responsePacket, size); |
| |
| switch (result) { |
| case BRL_RSP_DONE: |
| return 1; |
| |
| case BRL_RSP_UNEXPECTED: |
| logUnexpectedPacket(responsePacket, size); |
| case BRL_RSP_CONTINUE: |
| break; |
| |
| default: |
| logMessage(LOG_WARNING, "unimplemented braille response result: %u", result); |
| /* fall through */ |
| case BRL_RSP_FAIL: |
| return 0; |
| } |
| } |
| } |
| |
| if (errno != EAGAIN) |
| #ifdef ETIMEDOUT |
| if (errno != ETIMEDOUT) |
| #endif /* ETIMEDOUT */ |
| break; |
| |
| if (retryCount == retryLimit) break; |
| retryCount += 1; |
| } |
| |
| return 0; |
| } |
| |
| void |
| releaseBrailleKeys (BrailleDisplay *brl) { |
| releaseAllKeys(brl->keyTable); |
| } |
| |
| KeyNumberSet |
| mapKeyNumbers (KeyNumberSet fromKeys, const KeyNumberMapEntry *map, size_t count) { |
| KeyNumberSet toKeys = fromKeys; |
| const KeyNumberMapEntry *end = map + count; |
| |
| while (map < end) { |
| KeyNumber key = map->from; |
| int yes = (key == KTB_KEY_ANY)? 0: isKeyNumberIncluded(fromKeys, key); |
| |
| setKeyNumberIncluded(&toKeys, map->to, yes); |
| map += 1; |
| } |
| |
| return toKeys; |
| } |
| |
| void |
| remapKeyNumbers (KeyNumberSet *keys, const KeyNumberMapEntry *map, size_t count) { |
| *keys = mapKeyNumbers(*keys, map, count); |
| } |
| |
| struct KeyNumberSetMapStruct { |
| const KeyNumberSetMapEntry *linear; |
| const KeyNumberSetMapEntry **sorted; |
| size_t count; |
| }; |
| |
| static int |
| compareKeyNumberSets (KeyNumberSet keys1, KeyNumberSet keys2) { |
| if (keys1 < keys2) return -1; |
| if (keys1 > keys2) return 1; |
| return 0; |
| } |
| |
| static int |
| sortKeyNumberSets (const void *element1, const void *element2) { |
| const KeyNumberSetMapEntry *const *entry1 = element1; |
| const KeyNumberSetMapEntry *const *entry2 = element2; |
| return compareKeyNumberSets((*entry1)->from, (*entry2)->from); |
| } |
| |
| static int |
| searchKeyNumberSetMapEntry (const void *target, const void *element) { |
| const KeyNumberSet *reference = target; |
| const KeyNumberSetMapEntry *const *entry = element; |
| return compareKeyNumberSets(*reference, (*entry)->from); |
| } |
| |
| KeyNumberSetMap * |
| newKeyNumberSetMap (const KeyNumberSetMapEntry *entries, size_t count) { |
| KeyNumberSetMap *map; |
| |
| if ((map = malloc(sizeof(*map)))) { |
| memset(map, 0, sizeof(*map)); |
| |
| map->linear = entries; |
| map->count = count; |
| |
| if (count < 4) { |
| map->sorted = NULL; |
| return map; |
| } |
| |
| if ((map->sorted = malloc(ARRAY_SIZE(map->sorted, count)))) { |
| for (size_t i=0; i<count; i+=1) { |
| map->sorted[i] = &entries[i]; |
| } |
| |
| qsort(map->sorted, map->count, sizeof(*map->sorted), sortKeyNumberSets); |
| return map; |
| } |
| |
| free(map); |
| } |
| |
| logMallocError(); |
| return NULL; |
| } |
| |
| void |
| destroyKeyNumberSetMap (KeyNumberSetMap *map) { |
| if (map->sorted) free(map->sorted); |
| free(map); |
| } |
| |
| KeyNumberSet |
| mapKeyNumberSet (KeyNumberSet keys, const KeyNumberSetMap *map) { |
| if (map) { |
| if (map->sorted) { |
| const KeyNumberSetMapEntry *const *entry = bsearch( |
| &keys, map->sorted, map->count, |
| sizeof(*map->sorted), searchKeyNumberSetMapEntry |
| ); |
| |
| if (entry) return (*entry)->to; |
| } else { |
| const KeyNumberSetMapEntry *entry = map->linear; |
| const KeyNumberSetMapEntry *end = entry + map->count; |
| |
| while (entry < end) { |
| if (keys == entry->from) return entry->to; |
| entry += 1; |
| } |
| } |
| } |
| |
| return keys; |
| } |
| |
| void |
| remapKeyNumberSet (KeyNumberSet *keys, const KeyNumberSetMap *map) { |
| *keys = mapKeyNumberSet(*keys, map); |
| } |
| |
| typedef struct { |
| const KeyGroup group; |
| |
| KeyNumberSet set; |
| } IncludeKeyNumberData; |
| |
| static int |
| includeKeyNumber (const KeyNameEntry *kne, void *data) { |
| if (kne) { |
| IncludeKeyNumberData *ikn = data; |
| |
| if (kne->value.group == ikn->group) ikn->set |= KEY_NUMBER_BIT(kne->value.number); |
| } |
| |
| return 1; |
| } |
| |
| KeyNumberSet |
| makeKeyNumberSet (KEY_NAME_TABLES_REFERENCE keys, KeyGroup group) { |
| IncludeKeyNumberData ikn = { |
| .group = group, |
| |
| .set = 0 |
| }; |
| |
| forEachKeyName(keys, includeKeyNumber, &ikn); |
| return ikn.set; |
| } |
| |
| int |
| enqueueKeyEvent ( |
| BrailleDisplay *brl, |
| KeyGroup group, KeyNumber number, int press |
| ) { |
| report(REPORT_BRAILLE_KEY_EVENT, NULL); |
| if (api.handleKeyEvent(group, number, press)) return 1; |
| |
| if (brl->keyTable) { |
| processKeyEvent(brl->keyTable, getCurrentCommandContext(), group, number, press); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| int |
| enqueueKeyEvents ( |
| BrailleDisplay *brl, |
| KeyNumberSet set, KeyGroup group, KeyNumber number, int press |
| ) { |
| while (set) { |
| if (set & 0X1) { |
| if (!enqueueKeyEvent(brl, group, number, press)) { |
| return 0; |
| } |
| } |
| |
| set >>= 1; |
| number += 1; |
| } |
| |
| return 1; |
| } |
| |
| int |
| enqueueKey ( |
| BrailleDisplay *brl, |
| KeyGroup group, KeyNumber number |
| ) { |
| if (enqueueKeyEvent(brl, group, number, 1)) { |
| if (enqueueKeyEvent(brl, group, number, 0)) { |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int |
| enqueueKeys ( |
| BrailleDisplay *brl, |
| KeyNumberSet set, KeyGroup group, KeyNumber number |
| ) { |
| KeyNumber stack[UINT8_MAX + 1]; |
| unsigned char count = 0; |
| |
| while (set) { |
| if (set & 0X1) { |
| if (!enqueueKeyEvent(brl, group, number, 1)) return 0; |
| stack[count++] = number; |
| } |
| |
| set >>= 1; |
| number += 1; |
| } |
| |
| while (count) { |
| if (!enqueueKeyEvent(brl, group, stack[--count], 0)) { |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| int |
| enqueueUpdatedKeys ( |
| BrailleDisplay *brl, |
| KeyNumberSet new, KeyNumberSet *old, KeyGroup group, KeyNumber number |
| ) { |
| KeyNumberSet bit = KEY_NUMBER_BIT(0); |
| KeyNumber stack[UINT8_MAX + 1]; |
| unsigned char count = 0; |
| |
| while (*old != new) { |
| if ((new & bit) && !(*old & bit)) { |
| stack[count++] = number; |
| *old |= bit; |
| } else if (!(new & bit) && (*old & bit)) { |
| if (!enqueueKeyEvent(brl, group, number, 0)) return 0; |
| *old &= ~bit; |
| } |
| |
| number += 1; |
| bit <<= 1; |
| } |
| |
| while (count) { |
| if (!enqueueKeyEvent(brl, group, stack[--count], 1)) { |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| int |
| enqueueUpdatedKeyGroup ( |
| BrailleDisplay *brl, |
| unsigned int count, |
| const unsigned char *new, |
| unsigned char *old, |
| KeyGroup group |
| ) { |
| KeyNumber pressStack[count]; |
| unsigned char pressCount = 0; |
| KeyNumber base = 0; |
| |
| while (base < count) { |
| KeyNumber number = base; |
| unsigned char bit = 1; |
| |
| while (*old != *new) { |
| unsigned char isPressed = *new & bit; |
| unsigned char wasPressed = *old & bit; |
| |
| if (isPressed && !wasPressed) { |
| *old |= bit; |
| pressStack[pressCount++] = number; |
| } else if (wasPressed && !isPressed) { |
| *old &= ~bit; |
| enqueueKeyEvent(brl, group, number, 0); |
| } |
| |
| if (++number == count) break; |
| bit <<= 1; |
| } |
| |
| old += 1; |
| new += 1; |
| base += 8; |
| } |
| |
| while (pressCount > 0) { |
| enqueueKeyEvent(brl, group, pressStack[--pressCount], 1); |
| } |
| |
| return 1; |
| } |
| |
| int |
| enqueueXtScanCode ( |
| BrailleDisplay *brl, |
| unsigned char key, unsigned char escape, |
| KeyGroup group00, KeyGroup groupE0, KeyGroup groupE1 |
| ) { |
| KeyGroup group; |
| |
| switch (escape) { |
| case XT_MOD_00: group = group00; break; |
| case XT_MOD_E0: group = groupE0; break; |
| case XT_MOD_E1: group = groupE1; break; |
| |
| default: |
| logMessage(LOG_WARNING, "unsupported XT scan code: %02X %02X", escape, key); |
| return 0; |
| } |
| |
| return enqueueKey(brl, group, key); |
| } |