blob: af5b2d2077ac986aeecc9968a7506d2c76bb9ca9 [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 <string.h>
#include "log.h"
#include "parse.h"
#include "bitmask.h"
#include "kbd.h"
#include "kbd_internal.h"
const KeyboardProperties anyKeyboard = {
.type = KBD_TYPE_ANY,
.vendor = 0,
.product = 0
};
int
parseKeyboardProperties (KeyboardProperties *properties, const char *string) {
enum {
KBD_PARM_TYPE,
KBD_PARM_VENDOR,
KBD_PARM_PRODUCT
};
static const char *const names[] = {"type", "vendor", "product", NULL};
char **parameters = getParameters(names, NULL, string);
int ok = 1;
logParameters(names, parameters, "Keyboard Property");
*properties = anyKeyboard;
if (*parameters[KBD_PARM_TYPE]) {
static const KeyboardType types[] = {
KBD_TYPE_ANY, KBD_TYPE_PS2, KBD_TYPE_USB, KBD_TYPE_BLUETOOTH, KBD_TYPE_INTERNAL
};
static const char *choices[] = {"any", "ps2", "usb", "bluetooth", "internal", NULL};
unsigned int choice;
if (validateChoice(&choice, parameters[KBD_PARM_TYPE], choices)) {
properties->type = types[choice];
} else {
logMessage(LOG_WARNING, "invalid keyboard type: %s", parameters[KBD_PARM_TYPE]);
ok = 0;
}
}
if (*parameters[KBD_PARM_VENDOR]) {
static const int minimum = 0;
static const int maximum = 0XFFFF;
int value;
if (validateInteger(&value, parameters[KBD_PARM_VENDOR], &minimum, &maximum)) {
properties->vendor = value;
} else {
logMessage(LOG_WARNING, "invalid keyboard vendor code: %s", parameters[KBD_PARM_VENDOR]);
ok = 0;
}
}
if (*parameters[KBD_PARM_PRODUCT]) {
static const int minimum = 0;
static const int maximum = 0XFFFF;
int value;
if (validateInteger(&value, parameters[KBD_PARM_PRODUCT], &minimum, &maximum)) {
properties->product = value;
} else {
logMessage(LOG_WARNING, "invalid keyboard product code: %s", parameters[KBD_PARM_PRODUCT]);
ok = 0;
}
}
deallocateStrings(parameters);
return ok;
}
int
checkKeyboardProperties (const KeyboardProperties *actual, const KeyboardProperties *required) {
if (!required) return 1;
if (!actual) actual = &anyKeyboard;
if (required->type != KBD_TYPE_ANY) {
if (required->type != actual->type) return 0;
}
if (required->vendor) {
if (required->vendor != actual->vendor) return 0;
}
if (required->product) {
if (required->product != actual->product) return 0;
}
return 1;
}
static void
logKeyEvent (const char *action, int code, int press) {
logMessage(LOG_CATEGORY(KEYBOARD_KEYS),
"%s %d: %s",
(press? "press": "release"), code, action);
}
static void
flushKeyEvents (KeyboardInstanceObject *kio) {
const KeyEventEntry *event = kio->events.buffer;
while (kio->events.count) {
logKeyEvent("flushing", event->code, event->press);
forwardKeyEvent(kio, event->code, event->press);
event += 1;
kio->events.count -= 1;
}
memset(kio->deferred.mask, 0, kio->deferred.size);
kio->deferred.modifiersOnly = 0;
}
KeyboardInstanceObject *
newKeyboardInstanceObject (KeyboardMonitorObject *kmo) {
KeyboardInstanceObject *kio;
unsigned int count = BITMASK_ELEMENT_COUNT(keyCodeCount, BITMASK_ELEMENT_SIZE(unsigned char));
size_t size = sizeof(*kio) + count;
if ((kio = malloc(size))) {
memset(kio, 0, size);
kio->kmo = kmo;
kio->actualProperties = anyKeyboard;
kio->events.buffer = NULL;
kio->events.size = 0;
kio->events.count = 0;
kio->deferred.modifiersOnly = 0;
kio->deferred.size = count;
if (newKeyboardInstanceExtension(&kio->kix)) {
if (enqueueItem(kmo->instanceQueue, kio)) {
return kio;
}
destroyKeyboardInstanceExtension(kio->kix);
}
free(kio);
} else {
logMallocError();
}
return NULL;
}
void
destroyKeyboardInstanceObject (KeyboardInstanceObject *kio) {
flushKeyEvents(kio);
if (kio->events.buffer) free(kio->events.buffer);
deleteItem(kio->kmo->instanceQueue, kio);
if (kio->kix) destroyKeyboardInstanceExtension(kio->kix);
free(kio);
}
void
destroyKeyboardMonitorObject (KeyboardMonitorObject *kmo) {
kmo->isActive = 0;
while (getQueueSize(kmo->instanceQueue) > 0) {
Element *element = getQueueHead(kmo->instanceQueue);
KeyboardInstanceObject *kio = getElementItem(element);
destroyKeyboardInstanceObject(kio);
}
if (kmo->instanceQueue) deallocateQueue(kmo->instanceQueue);
if (kmo->kmx) destroyKeyboardMonitorExtension(kmo->kmx);
free(kmo);
}
KeyboardMonitorObject *
newKeyboardMonitorObject (const KeyboardProperties *properties, KeyEventHandler handleKeyEvent) {
KeyboardMonitorObject *kmo;
if ((kmo = malloc(sizeof(*kmo)))) {
memset(kmo, 0, sizeof(*kmo));
kmo->requiredProperties = *properties;
kmo->handleKeyEvent = handleKeyEvent;
if (newKeyboardMonitorExtension(&kmo->kmx)) {
if ((kmo->instanceQueue = newQueue(NULL, NULL))) {
if (monitorKeyboards(kmo)) {
kmo->isActive = 1;
return kmo;
}
deallocateQueue(kmo->instanceQueue);
}
destroyKeyboardMonitorExtension(kmo->kmx);
}
free(kmo);
} else {
logMallocError();
}
return NULL;
}
void
handleKeyEvent (KeyboardInstanceObject *kio, int code, int press) {
KeyTableState state = KTS_UNBOUND;
logKeyEvent("received", code, press);
if (kio->kmo->isActive) {
if ((code >= 0) && (code < keyCodeCount)) {
const KeyValue *kv = &keyCodeMap[code];
if ((kv->group != KBD_GROUP(SPECIAL)) || (kv->number != KBD_KEY(SPECIAL, Unmapped))) {
if ((kv->group == KBD_GROUP(SPECIAL)) && (kv->number == KBD_KEY(SPECIAL, Ignore))) return;
state = kio->kmo->handleKeyEvent(kv->group, kv->number, press);
}
}
}
if (state == KTS_HOTKEY) {
logKeyEvent("ignoring", code, press);
} else {
typedef enum {
WKA_NONE,
WKA_CURRENT,
WKA_ALL
} WriteKeysAction;
WriteKeysAction action = WKA_NONE;
if (press) {
kio->deferred.modifiersOnly = state == KTS_MODIFIERS;
if (state == KTS_UNBOUND) {
action = WKA_ALL;
} else {
if (kio->events.count == kio->events.size) {
unsigned int newSize = kio->events.size? kio->events.size<<1: 0X1;
KeyEventEntry *newBuffer = realloc(kio->events.buffer, (newSize * sizeof(*newBuffer)));
if (newBuffer) {
kio->events.buffer = newBuffer;
kio->events.size = newSize;
} else {
logMallocError();
}
}
if (kio->events.count < kio->events.size) {
KeyEventEntry *event = &kio->events.buffer[kio->events.count++];
event->code = code;
event->press = press;
BITMASK_SET(kio->deferred.mask, code);
logKeyEvent("deferring", code, press);
} else {
logKeyEvent("discarding", code, press);
}
}
} else if (kio->deferred.modifiersOnly) {
kio->deferred.modifiersOnly = 0;
action = WKA_ALL;
} else if (BITMASK_TEST(kio->deferred.mask, code)) {
KeyEventEntry *to = kio->events.buffer;
const KeyEventEntry *from = to;
unsigned int count = kio->events.count;
while (count) {
if (from->code == code) {
logKeyEvent("dropping", from->code, from->press);
} else if (to != from) {
*to++ = *from;
} else {
to += 1;
}
from += 1, count -= 1;
}
kio->events.count = to - kio->events.buffer;
BITMASK_CLEAR(kio->deferred.mask, code);
} else {
action = WKA_CURRENT;
}
switch (action) {
case WKA_ALL:
flushKeyEvents(kio);
/* fall through */
case WKA_CURRENT:
logKeyEvent("forwarding", code, press);
forwardKeyEvent(kio, code, press);
case WKA_NONE:
break;
}
}
}