/* * 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-2021 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 . */ #include "prologue.h" #include #include "embed.h" #include "log.h" #include "log_history.h" #include "message.h" #include "defaults.h" #include "async_wait.h" #include "async_task.h" #include "utf8.h" #include "brl_utils.h" #include "brl_cmds.h" #include "spk.h" #include "ktb_types.h" #include "update.h" #include "cmd_queue.h" #include "api_control.h" #include "core.h" int messageHoldTimeout = DEFAULT_MESSAGE_HOLD_TIMEOUT; typedef struct { const char *mode; MessageOptions options; unsigned presented:1; unsigned deallocate:1; char text[0]; } MessageParameters; typedef struct { const wchar_t *start; size_t length; } MessageSegment; typedef struct { const MessageParameters *parameters; struct { const MessageSegment *first; const MessageSegment *current; const MessageSegment *last; } segments; int timeout; unsigned endWait:1; unsigned hold:1; unsigned touch:1; } MessageData; ASYNC_CONDITION_TESTER(testEndMessageWait) { const MessageData *mgd = data; return mgd->endWait; } static int handleMessageCommands (int command, void *data) { MessageData *mgd = data; switch (command & BRL_MSK_CMD) { case BRL_CMD_LNUP: case BRL_CMD_PRDIFLN: case BRL_CMD_FWINLTSKIP: case BRL_CMD_FWINLT: { if (mgd->segments.current > mgd->segments.first) { mgd->segments.current -= 1; mgd->endWait = 1; } mgd->hold = 1; return 1; } case BRL_CMD_LNDN: case BRL_CMD_NXDIFLN: case BRL_CMD_FWINRTSKIP: case BRL_CMD_FWINRT: { if ((mgd->hold = mgd->segments.current < mgd->segments.last)) { mgd->segments.current += 1; } break; } default: { int arg = command & BRL_MSK_ARG; switch (command & BRL_MSK_BLK) { case BRL_CMD_BLK(TOUCH_AT): if ((mgd->touch = arg != BRL_MSK_ARG)) return 1; mgd->timeout = 1000; break; default: mgd->hold = 0; break; } break; } } mgd->endWait = 1; return 1; } ASYNC_TASK_CALLBACK(presentMessage) { MessageParameters *mgp = data; #ifdef ENABLE_SPEECH_SUPPORT if (!(mgp->options & MSG_SILENT)) { if (isAutospeakActive()) { sayString(&spk, mgp->text, SAY_OPT_MUTE_FIRST); } } #endif /* ENABLE_SPEECH_SUPPORT */ if (canBraille()) { MessageData mgd = { .hold = 0, .touch = 0, .parameters = mgp }; const size_t characterCount = countUtf8Characters(mgp->text); MessageSegment messageSegments[characterCount]; wchar_t characters[characterCount + 1]; makeWcharsFromUtf8(mgp->text, characters, ARRAY_COUNT(characters)); const size_t brailleSize = textCount * brl.textRows; wchar_t brailleBuffer[brailleSize]; { const wchar_t *character = characters; const wchar_t *const end = character + characterCount; MessageSegment *segment = messageSegments; mgd.segments.current = mgd.segments.first = segment; while (*character) { /* strip leading spaces */ while ((character < end) && iswspace(*character)) character += 1; const size_t charactersLeft = end - character; if (!charactersLeft) break; segment->start = character; segment->length = MIN(charactersLeft, brailleSize); character += segment->length; segment += 1; } mgd.segments.last = segment - 1; } int wasLinked = api.isServerLinked(); if (wasLinked) api.unlinkServer(); suspendUpdates(); pushCommandEnvironment("message", NULL, NULL); pushCommandHandler("message", KTB_CTX_WAITING, handleMessageCommands, NULL, &mgd); while (1) { const MessageSegment *segment = mgd.segments.current; size_t cellCount = segment->length; int lastSegment = segment == mgd.segments.last; wmemcpy(brailleBuffer, segment->start, cellCount); brl.cursor = BRL_NO_CURSOR; if (!writeBrailleCharacters(mgp->mode, brailleBuffer, cellCount)) { mgp->presented = 0; break; } mgd.timeout = messageHoldTimeout - brl.writeDelay; drainBrailleOutput(&brl, 0); if (!mgd.hold && lastSegment && (mgp->options & MSG_NODELAY)) break; mgd.timeout = MAX(mgd.timeout, 0); while (1) { int timeout = mgd.timeout; mgd.timeout = -1; mgd.endWait = 0; int timedOut = !asyncAwaitCondition(timeout, testEndMessageWait, &mgd); if (mgd.segments.current != segment) break; if (mgd.hold || mgd.touch) { mgd.timeout = 1000000; } else if (timedOut) { if (lastSegment) goto DONE; mgd.segments.current += 1; mgd.timeout = messageHoldTimeout; break; } else if (mgd.timeout < 0) { goto DONE; } } } DONE: popCommandEnvironment(); resumeUpdates(1); if (wasLinked) api.linkServer(); } if (mgp->deallocate) free(mgp); } int message (const char *mode, const char *text, MessageOptions options) { if (options & MSG_LOG) pushLogMessage(text); int presented = 0; MessageParameters *mgp; size_t size = sizeof(*mgp) + strlen(text) + 1; if ((mgp = malloc(size))) { memset(mgp, 0, size); mgp->mode = mode? mode: ""; mgp->options = options; mgp->presented = 1; strcpy(mgp->text, text); if (mgp->options & MSG_SYNC) { mgp->deallocate = 0; presentMessage(mgp); if (mgp->presented) presented = 1; } else { mgp->deallocate = 1; if (asyncAddTask(NULL, presentMessage, mgp)) return 1; } free(mgp); } return presented; } void showMessage (const char *text) { message(NULL, text, 0); }