/* * 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 #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=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) { 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) { STR_PRINTF(" #%u", arg - (cmd->code & BRL_MSK_ARG) + 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); }