blob: ad11ae27a506ffc85ec4715b7b584f65c03e94e1 [file] [log] [blame] [edit]
/*
* 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 "strfmt.h"
#include "brl_cmds.h"
#include "brl_custom.h"
#include "cmd.h"
#include "program.h"
const CommandEntry commandTable[] = {
#include "cmds.auto.h"
{.name=NULL, .code=EOF, .description=NULL}
};
const CommandModifierEntry commandModifierTable_toggle[] = {
{.name="on" , .bit=BRL_FLG_TOGGLE_ON },
{.name="off", .bit=BRL_FLG_TOGGLE_OFF},
{.name=NULL , .bit=0 }
};
const CommandModifierEntry commandModifierTable_motion[] = {
{.name="route", .bit=BRL_FLG_MOTION_ROUTE},
{.name=NULL , .bit=0 }
};
const CommandModifierEntry commandModifierTable_row[] = {
{.name="scaled", .bit=BRL_FLG_MOTION_SCALED},
{.name=NULL , .bit=0 }
};
const CommandModifierEntry commandModifierTable_vertical[] = {
{.name="toleft", .bit=BRL_FLG_MOTION_TOLEFT},
{.name=NULL , .bit=0 }
};
const CommandModifierEntry commandModifierTable_input[] = {
{.name="shift" , .bit=BRL_FLG_INPUT_SHIFT },
{.name="control", .bit=BRL_FLG_INPUT_CONTROL},
{.name="meta" , .bit=BRL_FLG_INPUT_META },
{.name="altgr" , .bit=BRL_FLG_INPUT_ALTGR },
{.name="gui" , .bit=BRL_FLG_INPUT_GUI },
{.name=NULL , .bit=0 }
};
const CommandModifierEntry commandModifierTable_character[] = {
{.name="upper" , .bit=BRL_FLG_INPUT_UPPER },
{.name="escaped", .bit=BRL_FLG_INPUT_ESCAPED},
{.name=NULL , .bit=0 }
};
const CommandModifierEntry commandModifierTable_braille[] = {
{.name="dot1" , .bit=BRL_DOT1},
{.name="dot2" , .bit=BRL_DOT2},
{.name="dot3" , .bit=BRL_DOT3},
{.name="dot4" , .bit=BRL_DOT4},
{.name="dot5" , .bit=BRL_DOT5},
{.name="dot6" , .bit=BRL_DOT6},
{.name="dot7" , .bit=BRL_DOT7},
{.name="dot8" , .bit=BRL_DOT8},
{.name="space", .bit=BRL_DOTC},
{.name=NULL , .bit=0 }
};
const CommandModifierEntry commandModifierTable_keyboard[] = {
{.name="release", .bit=BRL_FLG_KBD_RELEASE},
{.name="emul0" , .bit=BRL_FLG_KBD_EMUL0 },
{.name="emul1" , .bit=BRL_FLG_KBD_EMUL1 },
{.name=NULL , .bit=0 }
};
static int
compareCommandCodes (const void *element1, const void *element2) {
const CommandEntry *const *cmd1 = element1;
const CommandEntry *const *cmd2 = element2;
if ((*cmd1)->code < (*cmd2)->code) return -1;
if ((*cmd1)->code > (*cmd2)->code) return 1;
return 0;
}
int
getCommandCount (void) {
static int commandCount = -1;
if (commandCount < 0) {
const CommandEntry *cmd = commandTable;
while (cmd->name) cmd += 1;
commandCount = cmd - commandTable;
}
return commandCount;
}
const CommandEntry *
findCommandEntry (int code) {
static const CommandEntry **commandEntries = NULL;
int count = getCommandCount();
code &= BRL_MSK_CMD;
if (!commandEntries) {
const CommandEntry **entries = malloc(ARRAY_SIZE(entries, count));
if (!entries) {
logMallocError();
return NULL;
}
{
const CommandEntry *cmd = commandTable;
const CommandEntry **entry = entries;
while (cmd->name) *entry++ = cmd++;
}
qsort(entries, count, sizeof(*entries), compareCommandCodes);
commandEntries = entries;
registerProgramMemory("sorted-command-table", &commandEntries);
}
{
int first = 0;
int last = count - 1;
while (first <= last) {
int current = (first + last) / 2;
const CommandEntry *cmd = commandEntries[current];
if (code < cmd->code) {
last = current - 1;
} else {
first = current + 1;
}
}
if (last >= 0) {
const CommandEntry *cmd = commandEntries[last];
int blk = cmd->code & BRL_MSK_BLK;
int arg = cmd->code & BRL_MSK_ARG;
if (blk == (code & BRL_MSK_BLK)) {
if (arg == (code & BRL_MSK_ARG)) return cmd;
if (blk) {
int next = last + 1;
if (next == count) return cmd;
if (blk != (commandEntries[next]->code & BRL_MSK_BLK)) {
return cmd;
}
}
}
}
}
return NULL;
}
static
STR_BEGIN_FORMATTER(formatCommandModifiers, int command, const CommandModifierEntry *modifiers)
const CommandModifierEntry *modifier = modifiers;
while (modifier->name) {
if (command & modifier->bit) {
STR_PRINTF(" + %s", modifier->name);
}
modifier += 1;
}
STR_END_FORMATTER
STR_BEGIN_FORMATTER(describeCommand, int command, CommandDescriptionOption options)
unsigned int arg = BRL_ARG_GET(command);
unsigned int arg1 = BRL_CODE_GET(ARG, command);
unsigned int arg2 = BRL_CODE_GET(EXT, command);
const CommandEntry *cmd = findCommandEntry(command);
if (!cmd) {
STR_PRINTF("%s: %06X", gettext("unknown command"), command);
} else {
if (options & CDO_IncludeName) {
STR_PRINTF("%s: ", cmd->name);
}
if (cmd->isToggle && (command & BRL_FLG_TOGGLE_MASK)) {
const char *text = gettext(cmd->description);
size_t length = strlen(text);
char buffer[length + 1];
char *delimiter;
strcpy(buffer, text);
delimiter = strchr(buffer, '/');
if (delimiter) {
char *source;
char *target;
if (command & BRL_FLG_TOGGLE_ON) {
target = delimiter;
if (!(source = strchr(target, ' '))) source = target + strlen(target);
} else if (command & BRL_FLG_TOGGLE_OFF) {
{
char oldDelimiter = *delimiter;
*delimiter = 0;
target = strrchr(buffer, ' ');
*delimiter = oldDelimiter;
}
target = target? (target + 1): buffer;
source = delimiter + 1;
} else {
goto toggleReady;
}
memmove(target, source, (strlen(source) + 1));
}
toggleReady:
STR_PRINTF("%s", buffer);
} else {
STR_PRINTF("%s", gettext(cmd->description));
}
if (options & CDO_IncludeOperand) {
if (cmd->isCharacter) {
STR_PRINTF(" [U+%04X]", arg);
}
if (cmd->isBraille) {
int none = 1;
STR_PRINTF(" [");
{
static const int dots[] = {
BRL_DOTC,
BRL_DOT1, BRL_DOT2, BRL_DOT3, BRL_DOT4,
BRL_DOT5, BRL_DOT6, BRL_DOT7, BRL_DOT8,
0
};
const int *dot = dots;
while (*dot) {
if (command & *dot) {
none = 0;
if (dot == dots) {
STR_PRINTF("C");
} else {
unsigned int number = dot - dots;
STR_PRINTF("%u", number);
}
}
dot += 1;
}
}
if (none) STR_PRINTF("%s", gettext("space"));
STR_PRINTF("]");
}
if (cmd->isKeyboard) {
STR_PRINTF(" [\\X%02X]", arg1);
}
if (cmd->isColumn && !cmd->isRouting && (
(arg == BRL_MSK_ARG) /* key event processing */
||
((options & CDO_DefaultOperand) && !arg) /* key table listing */
)) {
STR_PRINTF(" %s", gettext("at cursor"));
} else if (cmd->isColumn || cmd->isRow || cmd->isOffset) {
int offset = arg - (cmd->code & BRL_MSK_ARG);
STR_PRINTF(" #%u", offset+1);
} else if (cmd->isRange) {
STR_PRINTF(" #%u-%u", arg1, arg2);
}
if (cmd->isInput) {
STR_FORMAT(formatCommandModifiers, command, commandModifierTable_input);
}
if (cmd->isCharacter) {
STR_FORMAT(formatCommandModifiers, command, commandModifierTable_character);
}
if (cmd->isKeyboard) {
STR_FORMAT(formatCommandModifiers, command, commandModifierTable_keyboard);
}
}
if (cmd->isMotion) {
STR_FORMAT(formatCommandModifiers, command, commandModifierTable_motion);
}
if (cmd->isRow) {
STR_FORMAT(formatCommandModifiers, command, commandModifierTable_row);
}
if (cmd->isVertical) {
STR_FORMAT(formatCommandModifiers, command, commandModifierTable_vertical);
}
}
STR_END_FORMATTER
static
STR_BEGIN_FORMATTER(formatCommand, int command)
STR_PRINTF("%06X (", command);
STR_FORMAT(describeCommand, command, CDO_IncludeName | CDO_IncludeOperand);
STR_PRINTF(")");
STR_END_FORMATTER
typedef struct {
int command;
} LogCommandData;
static
STR_BEGIN_FORMATTER(formatLogCommandData, const void *data)
const LogCommandData *cmd = data;
STR_PRINTF("command: ");
STR_FORMAT(formatCommand, cmd->command);
STR_END_FORMATTER
void
logCommand (int command) {
const LogCommandData cmd = {
.command = command
};
logData(LOG_DEBUG, formatLogCommandData, &cmd);
}
typedef struct {
int oldCommand;
int newCommand;
} LogTransformedCommandData;
static
STR_BEGIN_FORMATTER(formatLogTransformedCommandData, const void *data)
const LogTransformedCommandData *cmd = data;
STR_PRINTF("command: ");
STR_FORMAT(formatCommand, cmd->oldCommand);
STR_PRINTF(" -> ");
STR_FORMAT(formatCommand, cmd->newCommand);
STR_END_FORMATTER
void
logTransformedCommand (int oldCommand, int newCommand) {
const LogTransformedCommandData cmd = {
.oldCommand = oldCommand,
.newCommand = newCommand
};
logData(LOG_DEBUG, formatLogTransformedCommandData, &cmd);
}