Newer
Older
/*
* 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-2018 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.
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#define RETAIN_CHORD_KEY 0
#define ON_FIRST_RELEASE 1
#define BRL_CMD_ALERT(alert) BRL_CMD_ARG(ALERT, ALERT_##alert)
ASYNC_ALARM_CALLBACK(handleKeyAutoreleaseAlarm) {
KeyTable *table = parameters->data;
asyncDiscardHandle(table->autorelease.alarm);
table->autorelease.alarm = NULL;
for (unsigned int index=0; index<table->pressedKeys.count; index+=1) {
const KeyValue *kv = &table->pressedKeys.table[index];
char key[0X40];
STR_BEGIN(key, sizeof(key));
STR_FORMAT(formatKeyName, table, kv);
STR_PRINTF(" (Grp:%u Num:%u)", kv->group, kv->number);
STR_END;
logMessage(LOG_WARNING, "autoreleasing key: %s", key);
}
resetKeyTable(table);
alert(ALERT_KEYS_AUTORELEASED);
}
static void
cancelAutoreleaseAlarm (KeyTable *table) {
if (table->autorelease.alarm) {
asyncCancelRequest(table->autorelease.alarm);
table->autorelease.alarm = NULL;
}
}
static void
setAutoreleaseAlarm (KeyTable *table) {
if (!table->autorelease.time || !table->pressedKeys.count) {
} else if (table->autorelease.alarm) {
asyncResetAlarmIn(table->autorelease.alarm, table->autorelease.time);
asyncNewRelativeAlarm(&table->autorelease.alarm, table->autorelease.time,
handleKeyAutoreleaseAlarm, table);
setKeyAutoreleaseTime (KeyTable *table, unsigned char setting) {
table->autorelease.time = setting? (5000 << (setting - 1)): 0;
static int
sortModifierKeys (const void *element1, const void *element2) {
const KeyValue *modifier1 = element1;
const KeyValue *modifier2 = element2;
return compareKeyValues(modifier1, modifier2);
}
static int
searchKeyBinding (const void *target, const void *element) {
const KeyBinding *reference = target;
const KeyBinding *const *binding = element;
return compareKeyBindings(reference, *binding);
}
static const KeyBinding *
findKeyBinding (KeyTable *table, unsigned char context, const KeyValue *immediate, int *isIncomplete) {
const KeyContext *ctx = getKeyContext(table, context);
(table->pressedKeys.count <= MAX_MODIFIERS_PER_COMBINATION)) {
KeyBinding target;
memset(&target, 0, sizeof(target));
if (immediate) {
target.keyCombination.immediateKey = *immediate;
target.keyCombination.flags |= KCF_IMMEDIATE_KEY;
target.keyCombination.modifierCount = table->pressedKeys.count;
unsigned int all = (1 << table->pressedKeys.count) - 1;
for (bits=0; bits<=all; bits+=1) {
{
unsigned int index;
unsigned int bit;
for (index=0, bit=1; index<table->pressedKeys.count; index+=1, bit<<=1) {
KeyValue *modifier = &target.keyCombination.modifierKeys[index];
if (bits & bit) modifier->number = KTB_KEY_ANY;
qsort(target.keyCombination.modifierKeys, table->pressedKeys.count, sizeof(*target.keyCombination.modifierKeys), sortModifierKeys);
const KeyBinding *const *binding = bsearch(&target, ctx->keyBindings.sorted, ctx->keyBindings.count, sizeof(*ctx->keyBindings.sorted), searchKeyBinding);
if ((*binding)->primaryCommand.value != EOF) return *binding;
if (!(target.keyCombination.flags & KCF_IMMEDIATE_KEY)) break;
if (target.keyCombination.immediateKey.number == KTB_KEY_ANY) break;
target.keyCombination.immediateKey.number = KTB_KEY_ANY;
}
}
return NULL;
}
static int
searchHotkeyEntry (const void *target, const void *element) {
const HotkeyEntry *reference = target;
const HotkeyEntry *const *hotkey = element;
return compareKeyValues(&reference->keyValue, &(*hotkey)->keyValue);
}
static const HotkeyEntry *
findHotkeyEntry (KeyTable *table, unsigned char context, const KeyValue *keyValue) {
const KeyContext *ctx = getKeyContext(table, context);
HotkeyEntry target = {
.keyValue = *keyValue
};
{
const HotkeyEntry *const *hotkey = bsearch(&target, ctx->hotkeys.sorted, ctx->hotkeys.count, sizeof(*ctx->hotkeys.sorted), searchHotkeyEntry);
static int
searchMappedKeyEntry (const void *target, const void *element) {
const MappedKeyEntry *reference = target;
const MappedKeyEntry *const *map = element;
return compareKeyValues(&reference->keyValue, &(*map)->keyValue);
}
static const MappedKeyEntry *
findMappedKeyEntry (const KeyContext *ctx, const KeyValue *keyValue) {
MappedKeyEntry target = {
.keyValue = *keyValue
};
{
const MappedKeyEntry *const *map = bsearch(&target, ctx->mappedKeys.sorted, ctx->mappedKeys.count, sizeof(*ctx->mappedKeys.sorted), searchMappedKeyEntry);
if (map) return *map;
}
}
return NULL;
}
makeKeyboardCommand (KeyTable *table, unsigned char context, int allowChords) {
if ((ctx = getKeyContext(table, context))) {
for (unsigned int pressedIndex=0; pressedIndex<table->pressedKeys.count; pressedIndex+=1) {
const KeyValue *keyValue = &table->pressedKeys.table[pressedIndex];
const MappedKeyEntry *map = findMappedKeyEntry(ctx, keyValue);
int space = bits & BRL_DOTC;
int dots = bits & (
BRL_DOT1 | BRL_DOT2 | BRL_DOT3 | BRL_DOT4 |
BRL_DOT5 | BRL_DOT6 | BRL_DOT7 | BRL_DOT8
);
if (!(allowChords && ((space | dots) == bits))) {
if (!space == !dots) return EOF;
if (dots) bits |= ctx->mappedKeys.superimpose;
bits &= ~BRL_DOTC;
}
static int
findPressedKey (KeyTable *table, const KeyValue *value, unsigned int *position) {
return findKeyValue(table->pressedKeys.table, table->pressedKeys.count, value, position);
}
static int
insertPressedKey (KeyTable *table, const KeyValue *value, unsigned int position) {
return insertKeyValue(&table->pressedKeys.table, &table->pressedKeys.count, &table->pressedKeys.size, value, position);
}
static void
removePressedKey (KeyTable *table, unsigned int position) {
removeKeyValue(table->pressedKeys.table, &table->pressedKeys.count, position);
}
static inline void
deleteExplicitKeyValue (KeyValue *values, unsigned int *count, const KeyValue *value) {
if (value->number != KTB_KEY_ANY) deleteKeyValue(values, count, value);
static int
sortKeyOffsets (const void *element1, const void *element2) {
const KeyValue *value1 = element1;
const KeyValue *value2 = element2;
if (value1->number < value2->number) return -1;
if (value1->number > value2->number) return 1;
return 0;
}
static void
addCommandArguments (KeyTable *table, int *command, const CommandEntry *entry, const KeyBinding *binding) {
if (entry->isOffset | entry->isColumn | entry->isRow | entry->isRange | entry->isKeyboard) {
unsigned int keyCount = table->pressedKeys.count;
KeyValue keyValues[keyCount];
copyKeyValues(keyValues, table->pressedKeys.table, keyCount);
{
int index;
for (index=0; index<binding->keyCombination.modifierCount; index+=1) {
deleteExplicitKeyValue(keyValues, &keyCount, &binding->keyCombination.modifierKeys[index]);
}
}
if (binding->keyCombination.flags & KCF_IMMEDIATE_KEY) {
deleteExplicitKeyValue(keyValues, &keyCount, &binding->keyCombination.immediateKey);
}
if (keyCount > 0) {
if (keyCount > 1) {
qsort(keyValues, keyCount, sizeof(*keyValues), sortKeyOffsets);
if (entry->isRange) *command |= BRL_EXT_PUT(keyValues[1].number);
*command += keyValues[0].number;
} else if (entry->isColumn) {
if (!entry->isRouting) *command |= BRL_MSK_ARG;
}
}
}
static int
processCommand (KeyTable *table, int command) {
int blk = command & BRL_MSK_BLK;
int arg = command & BRL_MSK_ARG;
switch (blk) {
unsigned char context = KTB_CTX_DEFAULT + arg;
const KeyContext *ctx = getKeyContext(table, context);
if (ctx) {
command = BRL_CMD_NOOP;
table->context.next = context;
if (!enqueueCommand(BRL_CMD_ALERT(TOGGLE_ON))) return 0;
} else {
table->context.persistent = context;
if (!enqueueCommand(BRL_CMD_ALERT(TOGGLE_OFF))) return 0;
case BRL_CMD_BLK(PASSCHAR):
case BRL_CMD_BLK(PASSDOTS):
case BRL_CMD_BLK(PASSKEY):
if (table->options.keyboardEnabledFlag && !*table->options.keyboardEnabledFlag) {
command = BRL_CMD_ALERT(COMMAND_REJECTED);
}
break;
static void
logKeyEvent (
KeyTable *table, const char *action,
unsigned char context, const KeyValue *keyValue, int command
) {
if (table->options.logKeyEventsFlag && *table->options.logKeyEventsFlag) {
char buffer[0X100];
STR_BEGIN(buffer, sizeof(buffer));
if (table->options.logLabel) STR_PRINTF("%s ", table->options.logLabel);
STR_FORMAT(formatKeyName, table, keyValue);
STR_PRINTF(" (Ctx:%u Grp:%u Num:%u)", context, keyValue->group, keyValue->number);
const CommandEntry *cmd = findCommandEntry(command);
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
const char *name = cmd? cmd->name: "?";
STR_PRINTF(" -> %s (Cmd:%06X)", name, command);
}
STR_END;
logMessage(categoryLogLevel, "%s", buffer);
}
}
static void setLongPressAlarm (KeyTable *table, unsigned char when);
ASYNC_ALARM_CALLBACK(handleLongPressAlarm) {
KeyTable *table = parameters->data;
int command = table->longPress.command;
asyncDiscardHandle(table->longPress.alarm);
table->longPress.alarm = NULL;
logKeyEvent(table, table->longPress.keyAction,
table->longPress.keyContext,
&table->longPress.keyValue,
command);
if (table->longPress.repeat) {
table->longPress.keyAction = "repeat";
setLongPressAlarm(table, prefs.autorepeatInterval);
}
table->release.command = BRL_CMD_NOOP;
processCommand(table, command);
}
static void
setLongPressAlarm (KeyTable *table, unsigned char when) {
asyncNewRelativeAlarm(&table->longPress.alarm, PREFERENCES_TIME(when),
handleLongPressAlarm, table);
case BRL_CMD_BLK(PASSCHAR):
case BRL_CMD_BLK(PASSDOTS):
return 1;
default:
switch (command & BRL_MSK_CMD) {
case BRL_CMD_LNUP:
case BRL_CMD_LNDN:
case BRL_CMD_PRDIFLN:
case BRL_CMD_NXDIFLN:
case BRL_CMD_CHRLT:
case BRL_CMD_CHRRT:
case BRL_CMD_MENU_PREV_ITEM:
case BRL_CMD_MENU_NEXT_ITEM:
case BRL_CMD_MENU_PREV_SETTING:
case BRL_CMD_MENU_NEXT_SETTING:
case BRL_CMD_KEY(BACKSPACE):
case BRL_CMD_KEY(DELETE):
case BRL_CMD_KEY(PAGE_UP):
case BRL_CMD_KEY(PAGE_DOWN):
case BRL_CMD_KEY(CURSOR_UP):
case BRL_CMD_KEY(CURSOR_DOWN):
case BRL_CMD_KEY(CURSOR_LEFT):
case BRL_CMD_KEY(CURSOR_RIGHT):
case BRL_CMD_SPEAK_PREV_CHAR:
case BRL_CMD_SPEAK_NEXT_CHAR:
case BRL_CMD_SPEAK_PREV_WORD:
case BRL_CMD_SPEAK_NEXT_WORD:
case BRL_CMD_SPEAK_PREV_LINE:
case BRL_CMD_SPEAK_NEXT_LINE:
return 1;
case BRL_CMD_FWINLT:
case BRL_CMD_FWINRT:
if (prefs.autorepeatPanning) return 1;
default:
break;
}
break;
}
}
processKeyEvent (
KeyTable *table, unsigned char context,
KeyGroup keyGroup, KeyNumber keyNumber, int press
) {
.group = keyGroup,
.number = keyNumber
};
KeyTableState state = KTS_UNBOUND;
int command = EOF;
const HotkeyEntry *hotkey;
table->context.current = table->context.next;
table->context.next = table->context.persistent;
}
if (context == KTB_CTX_DEFAULT) context = table->context.current;
if (!(hotkey = findHotkeyEntry(table, context, &keyValue))) {
const KeyValue anyKey = {
.group = keyValue.group,
.number = KTB_KEY_ANY
};
hotkey = findHotkeyEntry(table, context, &anyKey);
}
if (hotkey) {
const BoundCommand *cmd = press? &hotkey->pressCommand: &hotkey->releaseCommand;
if (cmd->value != BRL_CMD_NOOP) processCommand(table, (command = cmd->value));
state = KTS_HOTKEY;
int wasPressed = findPressedKey(table, &keyValue, &keyPosition);
if (wasPressed) removePressedKey(table, keyPosition);
if (press) {
int isIncomplete = 0;
const KeyBinding *binding = findKeyBinding(table, context, &keyValue, &isIncomplete);
int inserted = insertPressedKey(table, &keyValue, keyPosition);
if (RETAIN_CHORD_KEY && ((command = makeKeyboardCommand(table, context, 1)) != EOF)) {
binding = NULL;
isImmediate = 0;
} else if (binding) {
} else if ((binding = findKeyBinding(table, context, NULL, &isIncomplete))) {
command = binding->primaryCommand.value;
isImmediate = 0;
} else if ((command = makeKeyboardCommand(table, context, 0)) != EOF) {
} else if (context == KTB_CTX_DEFAULT) {
command = EOF;
} else if (!inserted) {
command = EOF;
} else {
removePressedKey(table, keyPosition);
binding = findKeyBinding(table, KTB_CTX_DEFAULT, &keyValue, &isIncomplete);
inserted = insertPressedKey(table, &keyValue, keyPosition);
if (binding) {
} else if ((binding = findKeyBinding(table, KTB_CTX_DEFAULT, NULL, &isIncomplete))) {
command = binding->primaryCommand.value;
isImmediate = 0;
} else {
command = EOF;
}
}
if (command == EOF) {
} else {
state = KTS_COMMAND;
}
if (!wasPressed) {
int secondaryCommand = BRL_CMD_NOOP;
resetLongPressData(table);
if (binding) {
addCommandArguments(table, &command, binding->primaryCommand.entry, binding);
secondaryCommand = binding->secondaryCommand.value;
addCommandArguments(table, &secondaryCommand, binding->secondaryCommand.entry, binding);
if (context == KTB_CTX_WAITING) {
table->release.command = BRL_CMD_NOOP;
} else {
if (secondaryCommand == BRL_CMD_NOOP) {
if (isRepeatableCommand(command)) {
secondaryCommand = command;
if (isImmediate) {
table->release.command = BRL_CMD_NOOP;
table->release.command = command;
command = BRL_CMD_NOOP;
if (secondaryCommand != BRL_CMD_NOOP) {
table->longPress.command = secondaryCommand;
table->longPress.repeat = isRepeatableCommand(secondaryCommand);
table->longPress.keyAction = "long";
table->longPress.keyContext = context;
table->longPress.keyValue = keyValue;
setLongPressAlarm(table, prefs.longPressTime);
}
}
processCommand(table, command);
}
if (ON_FIRST_RELEASE || (table->pressedKeys.count == 0)) {
int *cmd = &table->release.command;
if (*cmd != BRL_CMD_NOOP) {
processCommand(table, (command = *cmd));
*cmd = BRL_CMD_NOOP;
}
}
logKeyEvent(table, (press? "press": "release"), context, &keyValue, command);
void
releaseAllKeys (KeyTable *table) {
while (table->pressedKeys.count) {
const KeyValue *kv = &table->pressedKeys.table[0];
processKeyEvent(table, KTB_CTX_DEFAULT, kv->group, kv->number, 0);
}
}
setKeyTableLogLabel (KeyTable *table, const char *label) {
}
void
setLogKeyEventsFlag (KeyTable *table, const unsigned char *flag) {
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
table->options.logKeyEventsFlag = flag;
}
void
setKeyboardEnabledFlag (KeyTable *table, const unsigned char *flag) {
table->options.keyboardEnabledFlag = flag;
}
void
getKeyGroupCommands (KeyTable *table, KeyGroup group, int *commands, unsigned int size) {
const KeyContext *ctx = getKeyContext(table, KTB_CTX_DEFAULT);
if (ctx) {
unsigned int i;
for (i=0; i<size; i+=1) {
commands[i] = BRL_CMD_NOOP;
}
for (i=0; i<ctx->keyBindings.count; i+=1) {
const KeyBinding *binding = &ctx->keyBindings.table[i];
const KeyCombination *combination = &binding->keyCombination;
const KeyValue *key;
if (combination->flags & KCF_IMMEDIATE_KEY) {
if (combination->modifierCount != 0) continue;
key = &combination->immediateKey;
} else {
if (combination->modifierCount != 1) continue;
key = &combination->modifierKeys[0];
}
if (key->group == group) {
if (key->number != KTB_KEY_ANY) {
if (key->number < size) {
int command = binding->primaryCommand.value;
if (command != BRL_CMD_NOOP) {
commands[key->number] = command;
}
}
}
}
}
}