| /* |
| * 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 "log.h" |
| #include "cmd_queue.h" |
| #include "cmd_enqueue.h" |
| #include "cmd_utils.h" |
| #include "brl_cmds.h" |
| #include "cmd.h" |
| #include "queue.h" |
| #include "async_handle.h" |
| #include "async_alarm.h" |
| #include "prefs.h" |
| #include "ktb_types.h" |
| #include "scr.h" |
| #include "core.h" |
| |
| #define LOG_LEVEL LOG_DEBUG |
| |
| typedef struct CommandEnvironmentStruct CommandEnvironment; |
| typedef struct CommandHandlerLevelStruct CommandHandlerLevel; |
| |
| struct CommandHandlerLevelStruct { |
| CommandHandlerLevel *previousLevel; |
| const char *levelName; |
| |
| CommandHandler *handleCommand; |
| CommandDataDestructor *destroyData; |
| void *handlerData; |
| KeyTableCommandContext commandContext; |
| }; |
| |
| struct CommandEnvironmentStruct { |
| CommandEnvironment *previousEnvironment; |
| const char *environmentName; |
| |
| CommandHandlerLevel *handlerStack; |
| CommandPreprocessor *preprocessCommand; |
| CommandPostprocessor *postprocessCommand; |
| unsigned handlingCommand:1; |
| }; |
| |
| static CommandEnvironment *commandEnvironmentStack = NULL; |
| static unsigned int commandQueueSuspendCount = 0; |
| |
| static CommandHandlerLevel ** |
| getCommandHandlerTop (void) { |
| if (!commandEnvironmentStack) return NULL; |
| return &commandEnvironmentStack->handlerStack; |
| } |
| |
| KeyTableCommandContext |
| getCurrentCommandContext (void) { |
| CommandHandlerLevel **top = getCommandHandlerTop(); |
| KeyTableCommandContext context = top && *top? (*top)->commandContext: KTB_CTX_DEFAULT; |
| |
| if (context == KTB_CTX_DEFAULT) context = getScreenCommandContext(); |
| return context; |
| } |
| |
| static int |
| toPreferredCommand (int command) { |
| int preferred = command; |
| |
| int cmd = command & BRL_MSK_CMD; |
| int blk = command & BRL_MSK_BLK; |
| |
| if (blk) { |
| } else { |
| if (prefs.skipIdenticalLines) { |
| switch (cmd) { |
| case BRL_CMD_LNUP: |
| preferred = BRL_CMD_PRDIFLN; |
| break; |
| |
| case BRL_CMD_LNDN: |
| preferred = BRL_CMD_NXDIFLN; |
| break; |
| |
| case BRL_CMD_PRDIFLN: |
| preferred = BRL_CMD_LNUP; |
| break; |
| |
| case BRL_CMD_NXDIFLN: |
| preferred = BRL_CMD_LNDN; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| if (prefs.skipBlankBrailleWindows) { |
| switch (cmd) { |
| case BRL_CMD_FWINLT: |
| preferred = BRL_CMD_FWINLTSKIP; |
| break; |
| |
| case BRL_CMD_FWINRT: |
| preferred = BRL_CMD_FWINRTSKIP; |
| break; |
| |
| case BRL_CMD_FWINLTSKIP: |
| preferred = BRL_CMD_FWINLT; |
| break; |
| |
| case BRL_CMD_FWINRTSKIP: |
| preferred = BRL_CMD_FWINRT; |
| break; |
| |
| default: |
| break; |
| } |
| } |
| } |
| |
| if (preferred == command) { |
| logCommand(command); |
| } else { |
| preferred |= (command & ~BRL_MSK_CMD); |
| logTransformedCommand(command, preferred); |
| command = preferred; |
| } |
| |
| return command; |
| } |
| |
| int |
| handleCommand (int command) { |
| const CommandEnvironment *env = commandEnvironmentStack; |
| const CommandHandlerLevel *chl = env->handlerStack; |
| |
| while (chl) { |
| if (chl->handleCommand(command, chl->handlerData)) return 1; |
| chl = chl->previousLevel; |
| } |
| |
| logMessage(LOG_WARNING, "%s: %04X", gettext("unhandled command"), command); |
| return 0; |
| } |
| |
| typedef struct { |
| int command; |
| } CommandQueueItem; |
| |
| static void |
| deallocateCommandQueueItem (void *item, void *data) { |
| CommandQueueItem *cmd = item; |
| |
| free(cmd); |
| } |
| |
| static Queue * |
| createCommandQueue (void *data) { |
| return newQueue(deallocateCommandQueueItem, NULL); |
| } |
| |
| static Queue * |
| getCommandQueue (int create) { |
| static Queue *commands = NULL; |
| |
| return getProgramQueue(&commands, "command-queue", create, |
| createCommandQueue, NULL); |
| } |
| |
| static int |
| dequeueCommand (Queue *queue) { |
| CommandQueueItem *item; |
| |
| if ((item = dequeueItem(queue))) { |
| int command = item->command; |
| |
| free(item); |
| item = NULL; |
| |
| return command; |
| } |
| |
| return EOF; |
| } |
| |
| static void setCommandAlarm (void *data); |
| static AsyncHandle commandAlarm = NULL; |
| |
| ASYNC_ALARM_CALLBACK(handleCommandAlarm) { |
| Queue *queue = getCommandQueue(0); |
| |
| asyncDiscardHandle(commandAlarm); |
| commandAlarm = NULL; |
| |
| if (queue) { |
| int command = dequeueCommand(queue); |
| |
| if (command != EOF) { |
| command = toPreferredCommand(command); |
| const CommandEntry *cmd = findCommandEntry(command); |
| |
| CommandEnvironment *env = commandEnvironmentStack; |
| env->handlingCommand = 1; |
| |
| void *pre = env->preprocessCommand? env->preprocessCommand(): NULL; |
| int handled = handleCommand(command); |
| |
| if (env->postprocessCommand) { |
| env->postprocessCommand(pre, command, cmd, handled); |
| } |
| |
| env->handlingCommand = 0; |
| } |
| } |
| |
| setCommandAlarm(parameters->data); |
| } |
| |
| static void |
| setCommandAlarm (void *data) { |
| if (!commandAlarm && !commandQueueSuspendCount) { |
| const CommandEnvironment *env = commandEnvironmentStack; |
| |
| if (env && !env->handlingCommand) { |
| Queue *queue = getCommandQueue(0); |
| |
| if (queue && (getQueueSize(queue) > 0)) { |
| asyncNewRelativeAlarm(&commandAlarm, 0, handleCommandAlarm, data); |
| } |
| } |
| } |
| } |
| |
| static void |
| cancelCommandAlarm (void) { |
| if (commandAlarm) { |
| asyncCancelRequest(commandAlarm); |
| commandAlarm = NULL; |
| } |
| } |
| |
| int |
| enqueueCommand (int command) { |
| if (command == EOF) return 1; |
| |
| { |
| Queue *queue = getCommandQueue(1); |
| |
| if (queue) { |
| CommandQueueItem *item = malloc(sizeof(CommandQueueItem)); |
| |
| if (item) { |
| item->command = command; |
| |
| if (enqueueItem(queue, item)) { |
| setCommandAlarm(NULL); |
| return 1; |
| } |
| |
| free(item); |
| } else { |
| logMallocError(); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| int |
| pushCommandHandler ( |
| const char *name, |
| KeyTableCommandContext context, |
| CommandHandler *handler, |
| CommandDataDestructor *destructor, |
| void *data |
| ) { |
| CommandHandlerLevel *chl; |
| |
| if ((chl = malloc(sizeof(*chl)))) { |
| memset(chl, 0, sizeof(*chl)); |
| chl->levelName = name; |
| chl->handleCommand = handler; |
| chl->destroyData = destructor; |
| chl->handlerData = data; |
| chl->commandContext = context; |
| |
| { |
| CommandHandlerLevel **top = getCommandHandlerTop(); |
| |
| chl->previousLevel = *top; |
| *top = chl; |
| } |
| |
| logMessage(LOG_LEVEL, "pushed command handler: %s", chl->levelName); |
| return 1; |
| } else { |
| logMallocError(); |
| } |
| |
| return 0; |
| } |
| |
| int |
| popCommandHandler (void) { |
| CommandHandlerLevel **top = getCommandHandlerTop(); |
| CommandHandlerLevel *chl = *top; |
| |
| if (!chl) return 0; |
| *top = chl->previousLevel; |
| |
| logMessage(LOG_LEVEL, "popped command handler: %s", chl->levelName); |
| if (chl->destroyData) chl->destroyData(chl->handlerData); |
| free(chl); |
| return 1; |
| } |
| |
| int |
| pushCommandEnvironment ( |
| const char *name, |
| CommandPreprocessor *preprocessCommand, |
| CommandPostprocessor *postprocessCommand |
| ) { |
| CommandEnvironment *env; |
| |
| if ((env = malloc(sizeof(*env)))) { |
| memset(env, 0, sizeof(*env)); |
| env->environmentName = name; |
| env->handlerStack = NULL; |
| env->preprocessCommand = preprocessCommand; |
| env->postprocessCommand = postprocessCommand; |
| env->handlingCommand = 0; |
| |
| env->previousEnvironment = commandEnvironmentStack; |
| commandEnvironmentStack = env; |
| setCommandAlarm(NULL); |
| |
| logMessage(LOG_LEVEL, "pushed command environment: %s", env->environmentName); |
| return 1; |
| } else { |
| logMallocError(); |
| } |
| |
| return 0; |
| } |
| |
| int |
| popCommandEnvironment (void) { |
| CommandEnvironment *env = commandEnvironmentStack; |
| |
| if (!env) return 0; |
| while (popCommandHandler()); |
| commandEnvironmentStack = env->previousEnvironment; |
| |
| { |
| const CommandEnvironment *env = commandEnvironmentStack; |
| |
| if (!env || env->handlingCommand) { |
| cancelCommandAlarm(); |
| } |
| } |
| |
| logMessage(LOG_LEVEL, "popped command environment: %s", env->environmentName); |
| free(env); |
| return 1; |
| } |
| |
| int |
| beginCommandQueue (void) { |
| commandEnvironmentStack = NULL; |
| commandQueueSuspendCount = 0; |
| |
| return pushCommandEnvironment("initial", NULL, NULL); |
| } |
| |
| void |
| endCommandQueue (void) { |
| while (popCommandEnvironment()); |
| } |
| |
| void |
| suspendCommandQueue (void) { |
| if (!commandQueueSuspendCount++) cancelCommandAlarm(); |
| } |
| |
| void |
| resumeCommandQueue (void) { |
| if (!--commandQueueSuspendCount) setCommandAlarm(NULL); |
| } |