blob: 832bd40c55981a2d1b452127c34895cb8297cca3 [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 <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);
}