blob: ab3fb4d226517c3a7d55ea297ad4b3037c13aa2c [file] [log] [blame] [edit]
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include "brldefs-hid.h"
#include "third_party/brltty/Headers/bitmask.h"
#include "third_party/brltty/Headers/brl_base.h"
#include "third_party/brltty/Headers/brl_driver.h"
#include "third_party/brltty/Headers/brl_types.h"
#include "third_party/brltty/Headers/brl_utils.h"
#include "third_party/brltty/Headers/gio_types.h"
#include "third_party/brltty/Headers/hid_defs.h"
#include "third_party/brltty/Headers/hid_items.h"
#include "third_party/brltty/Headers/hid_types.h"
#include "third_party/brltty/Headers/io_generic.h"
#include "third_party/brltty/Headers/ktb_types.h"
#include "third_party/brltty/Headers/log.h"
#include "third_party/brltty/Programs/gio_internal.h"
struct BrailleDataStruct {
struct {
unsigned char count;
KEYS_BITMASK(mask);
} pressedKeys;
struct {
unsigned char rewrite;
unsigned char cells[MAX_OUTPUT_SIZE];
} text;
struct {
// The HID report ID used by both input and output. Currently expects both
// to be the same.
uint32_t reportId;
// The size of the HID input report.
uint32_t inputSizeBytes;
// Map each input report bit to the HID usage it represents.
uint32_t inputReportUsages[MAX_INPUT_SIZE];
// Map each input report bit to the Key (internal number) it represents.
uint32_t inputReportKeys[MAX_INPUT_SIZE];
// The first (lowest) bit number of the contiguous group of routing keys.
uint32_t inputRoutingFirstBit;
} reportInfo;
};
// Parses the Braille display's HID report descriptor, in order to understand
// how to parse input reports and prepare output reports.
static int probeHidDisplay(BrailleDisplay *brl, HidItemsDescriptor *items) {
memset(brl->data->reportInfo.inputReportUsages, 0,
MAX_INPUT_SIZE * sizeof(brl->data->reportInfo.inputReportUsages[0]));
memset(brl->data->reportInfo.inputReportKeys, 0,
MAX_INPUT_SIZE * sizeof(brl->data->reportInfo.inputReportKeys[0]));
brl->data->reportInfo.inputRoutingFirstBit = -1;
// If you'd like to print all items in the HID report then call
// hid_inspect#hidListItems()
// These variables save attributes from the current stack of items:
// The current Usage Page.
uint32_t usagePage = 0;
// Number of bits per usage.
uint32_t reportSize = 0;
// Number of usages in this stack.
uint32_t reportCount = 0;
// The Usages in this stack, stored as a list of individual usages.
uint32_t usages[MAX_USAGE_COUNT];
uint32_t usageIndex = 0;
// The Usages in this stack, stored as a minimum and maximum value.
uint32_t usageMin = 0;
uint32_t usageMax = 0;
// The report ID. This is implicitly zero by default, until some value
// provided by the descriptor.
uint32_t reportId = 0;
// The current bit of the INPUT report, used for writing to the
// inputReportUsages map.
uint32_t inputReportBit = 0;
// These variables will be "learned" while parsing the report descriptor;
// set them to "unset" defaults so that parsing can detect if an inconsistency
// occurs.
// Stores the number of output cells so that brltty knows how to prepare
// output cells.
brl->textColumns = -1;
// The report ID that will be providing Braille Display input and output.
brl->data->reportInfo.reportId = -1;
const unsigned char *nextByte = items->bytes;
size_t bytesLeft = items->count;
HidItem item;
int parsingError = 0;
while (1) {
if (!hidNextItem(&item, &nextByte, &bytesLeft)) {
break;
}
if (item.tag == HID_ITM_UsagePage) {
usagePage = item.value.u;
}
if (item.tag == HID_ITM_Collection) {
// Collections help differentiate between groupings of usages, e.g.
// between multiple rows of output braille cells. All devices we've tested
// so far provide their BD usages in one collection, so this driver does
// not yet support differentiating between collections.
//
// The type of collection would have been specified by usage items before
// this collection item, so reset the usage data structure (since as noted
// above we are ignoring collection designations).
usageIndex = 0;
}
if (item.tag == HID_ITM_ReportID) {
reportId = item.value.u;
}
if (item.tag == HID_ITM_Usage) {
usageMin = usageMax = -1;
uint32_t usage = item.value.u;
usages[usageIndex++] = usage;
}
if (item.tag == HID_ITM_UsageMinimum) {
usageMin = item.value.u;
}
if (item.tag == HID_ITM_UsageMaximum) {
usageMax = item.value.u;
}
if (item.tag == HID_ITM_ReportSize) {
reportSize = item.value.u;
}
if (item.tag == HID_ITM_ReportCount) {
reportCount = item.value.u;
}
if (item.tag == HID_ITM_Input || item.tag == HID_ITM_Output ||
item.tag == HID_ITM_Feature) {
// Reset the usage index now that we're going to process the usages array.
usageIndex = 0;
// Set the BD report ID to the current reportId.
if (usagePage == HID_UPG_Braille) {
if (brl->data->reportInfo.reportId != -1 &&
brl->data->reportInfo.reportId != reportId) {
logMessage(LOG_ERR,
"Found multiple report IDs that include Braille usages");
parsingError = 1;
break;
}
brl->data->reportInfo.reportId = reportId;
}
}
if (item.tag == HID_ITM_Input) {
if (reportId == brl->data->reportInfo.reportId) {
// Skip past constant bits
if ((item.value.u & HID_USG_FLG_CONSTANT) == HID_USG_FLG_CONSTANT) {
inputReportBit += reportSize * reportCount;
continue;
}
// Skip past usages from unexpected pages.
if (usagePage != HID_UPG_Braille && usagePage != HID_UPG_Button) {
inputReportBit += reportSize * reportCount;
continue;
}
// Fail if we get a usage of unexpected type or size
if (reportSize != 1) {
logMessage(LOG_ERR, "Unexpected input item input size %u != 1",
reportSize);
parsingError = 1;
break;
}
if ((item.value.u & HID_USG_FLG_VARIABLE) != HID_USG_FLG_VARIABLE) {
logMessage(LOG_ERR, "Unexpected non-variable input item");
parsingError = 1;
break;
}
// Fail if we get a usage range that doesn't match the report count
if (usageMin != -1 && (usageMin + reportCount - 1) != usageMax) {
logMessage(LOG_ERR, "Invalid usage range: min=%u max=%u count=%u",
usageMin, usageMax, reportCount);
parsingError = 1;
break;
}
if (inputReportBit > MAX_INPUT_SIZE) {
logMessage(LOG_ERR, "Unexpected input report with more than %u bits",
MAX_INPUT_SIZE);
parsingError = 1;
break;
}
for (int i = 0; i < reportCount; i++) {
if (usageMin != -1) {
brl->data->reportInfo.inputReportUsages[inputReportBit++] =
usageMin++;
} else {
brl->data->reportInfo.inputReportUsages[inputReportBit++] =
usages[i];
}
}
}
}
if (item.tag == HID_ITM_Output) {
if (usagePage == HID_UPG_Braille) {
if (reportId != brl->data->reportInfo.reportId) {
logMessage(LOG_ERR,
"Unexpected differing output and input report IDs");
parsingError = 1;
break;
}
if (reportSize != 8) {
logMessage(LOG_ERR, "Invalid output bit size %u", reportSize);
parsingError = 1;
break;
}
if (brl->textColumns != -1) {
logMessage(LOG_ERR, "Unexpected received multiple BD output reports");
parsingError = 1;
break;
}
brl->textColumns = reportCount;
}
}
}
free(items);
if (parsingError) {
logMessage(LOG_ERR, "There were parsing errors.");
return 0;
}
if (brl->data->reportInfo.reportId == -1) {
logMessage(LOG_ERR, "Could not find a Braille Display report ID");
return 0;
}
if (brl->textColumns == -1) {
logMessage(LOG_ERR, "Could not find the Braille Display output cell count");
return 0;
}
for (int i = 0; i < MAX_INPUT_SIZE; i++) {
logMessage(LOG_DEBUG, "bit=%d report=%i", i,
brl->data->reportInfo.inputReportUsages[i]);
// While parsing the descript we built up map inputReportUsages from
// bit->usage. However, brltty doesn't use usages to describe key events:
// brltty uses a key table that was provided by brl_construct. This logic
// builds up a new map from bit->key where the key values come from the key
// table that we provided to brltty. This allows the driver to map from
// INPUT bit to the brltty-known key name.
for (int j = 0; j < KEY_MAP_COUNT; j++) {
if (KEY_MAP[j][0] == brl->data->reportInfo.inputReportUsages[i]) {
brl->data->reportInfo.inputReportKeys[i] = KEY_MAP[j][1];
}
}
// Routing keys are handled differently. They all use the same usage,
// while their bit number (starting from the first one) defines the actual
// routing key number.
if (brl->data->reportInfo.inputReportUsages[i] == HID_USG_BRL_RouterKey) {
if (brl->data->reportInfo.inputRoutingFirstBit == -1) {
brl->data->reportInfo.inputRoutingFirstBit = i;
} else if (brl->data->reportInfo.inputReportUsages[i - 1] !=
HID_USG_BRL_RouterKey) {
// Expect that all routing key INPUTs are sent as a contiguous group, so
// return error if the descriptor describes something like "... ROUTING
// DOT1 ROUTING ...".
logMessage(LOG_ERR,
"Unexpected non-contiguous group of router keys at "
"%d with previous entry %u and first bit %d",
i, brl->data->reportInfo.inputReportUsages[i - 1],
brl->data->reportInfo.inputRoutingFirstBit);
return 0;
}
}
}
int inputSizeBytes = (inputReportBit + 7) / 8;
int hasNumberedReport = brl->data->reportInfo.reportId != 0;
if (hasNumberedReport != 0) {
// The first byte of input should contain the report ID, then all other
// bytes should be parsed as input.
brl->data->reportInfo.inputSizeBytes = inputSizeBytes + 1;
} else {
// The entire input report should be parsed as input.
brl->data->reportInfo.inputSizeBytes = inputSizeBytes;
}
// Zero-out the input key mask used to track the current state of input key
// presses.
BITMASK_ZERO(brl->data->pressedKeys.mask);
return 1;
}
// Enqueues a key event to brltty's internal key processing logic.
// brltty waits for all keys to be released, then looks at the combined set of
// key-down events to understand what key combination was pressed by looking
// up possible key combinations from the HID.ktb keytable.
static int handleKeyEvent(BrailleDisplay *brl, unsigned char key, int press) {
KeyGroup group;
if (key < HID_KEY_ROUTING) {
group = HID_GRP_NavigationKeys;
} else {
group = HID_GRP_RoutingKeys;
key -= HID_KEY_ROUTING;
}
return enqueueKeyEvent(brl, group, key, press);
}
// Possibly enqueues a key-down action.
static int handleKeyPress(BrailleDisplay *brl, unsigned char key) {
if (BITMASK_TEST(brl->data->pressedKeys.mask, key)) return 0;
BITMASK_SET(brl->data->pressedKeys.mask, key);
brl->data->pressedKeys.count += 1;
handleKeyEvent(brl, key, 1);
return 1;
}
// Possibly enqueues a key-action action.
static int handleKeyRelease(BrailleDisplay *brl, unsigned char key) {
if (!BITMASK_TEST(brl->data->pressedKeys.mask, key)) return 0;
BITMASK_CLEAR(brl->data->pressedKeys.mask, key);
brl->data->pressedKeys.count -= 1;
handleKeyEvent(brl, key, 0);
return 1;
}
// Parses a HID input report into brltty key actions.
// Called by brltty when it wants to parse an input byte array.
static void handlePressedKeysArray(BrailleDisplay *brl, unsigned char *keys) {
// Per HIDRAW spec if input descriptor report number is not zero then the
// first byte in the input should be the report number.
int hasNumberedReport = brl->data->reportInfo.reportId != 0;
if (hasNumberedReport && keys[0] != brl->data->reportInfo.reportId) {
logMessage(LOG_WARNING, "Unexpected input report %u", keys[0]);
return;
}
int numInputBytes = hasNumberedReport
? brl->data->reportInfo.inputSizeBytes - 1
: brl->data->reportInfo.inputSizeBytes;
const unsigned char *byte = hasNumberedReport ? keys + 1 : keys;
for (int byteNum = 0; byteNum < numInputBytes; byteNum++) {
for (int bit = 0; bit <= 7; bit++) {
int bitNum = byteNum * 8 + bit;
char key;
if (brl->data->reportInfo.inputReportUsages[bitNum] ==
HID_USG_BRL_RouterKey) {
int routingKeyNum = bitNum - brl->data->reportInfo.inputRoutingFirstBit;
key = HID_KEY_ROUTING + routingKeyNum;
} else {
key = brl->data->reportInfo.inputReportKeys[bitNum];
}
if (key != 0) {
if ((*byte) & (1 << bit)) {
logMessage(LOG_DEBUG, "Pressed bit %d usage %u", bitNum,
brl->data->reportInfo.inputReportUsages[bitNum]);
handleKeyPress(brl, key);
} else {
handleKeyRelease(brl, key);
}
}
}
byte++;
}
}
static int writeHidCells(BrailleDisplay *brl, const unsigned char *cells,
unsigned char cellCount) {
// HIDRAW expects the report ID in the first byte, followed by the
// output report.
int bufferSize = cellCount + 1;
unsigned char buffer[bufferSize];
buffer[0] = brl->data->reportInfo.reportId;
memcpy(buffer + 1, cells, cellCount);
return brl->gioEndpoint->handleMethods->writeData(
brl->gioEndpoint->handle, buffer, bufferSize, /*timeout=*/0);
}
// Standard functions expected by brltty for any brltty driver. These were
// essentially copied the Humanware driver with minimal modifications.
static int connectResource(BrailleDisplay *brl, const char *identifier) {
static const HidModelEntry hidModelTable[] = {
{
// Model name is not used to control driver behavior, so always
// expect "HID" as set by hid_android.c#getGenericHIDDeviceName().
.name = "HID",
},
{.name = NULL, .vendor = 0}};
GioDescriptor descriptor;
gioInitializeDescriptor(&descriptor);
descriptor.hid.modelTable = hidModelTable;
return connectBrailleResource(brl, identifier, &descriptor, NULL) ? 1 : 0;
}
static int brl_construct(BrailleDisplay *brl, char **parameters,
const char *device) {
if ((brl->data = malloc(sizeof(*brl->data)))) {
memset(brl->data, 0, sizeof(*brl->data));
if (connectResource(brl, device)) {
HidItemsDescriptor *items =
brl->gioEndpoint->handleMethods->getHidDescriptor(
brl->gioEndpoint->handle);
if (probeHidDisplay(brl, items)) {
setBrailleKeyTable(brl, &keyTableDefinition_HID);
makeOutputTable(dotsTable_ISO11548_1);
brl->data->text.rewrite = 1;
return 1;
}
disconnectBrailleResource(brl, NULL);
}
free(brl->data);
brl->data = NULL;
} else {
logMallocError();
}
return 0;
}
static void brl_destruct(BrailleDisplay *brl) {
disconnectBrailleResource(brl, NULL);
free(brl->data);
}
// Called by brltty when it wants to write output to the Braille display.
static int brl_writeWindow(BrailleDisplay *brl, const wchar_t *text) {
const size_t count = brl->textColumns;
if (cellsHaveChanged(brl->data->text.cells, brl->buffer, count, NULL, NULL,
&brl->data->text.rewrite)) {
unsigned char cells[count];
translateOutputCells(cells, brl->data->text.cells, count);
if (!writeHidCells(brl, cells, count)) return 0;
}
return 1;
}
static int brl_readCommand(BrailleDisplay *brl,
KeyTableCommandContext context) {
unsigned char packet[MAX_INPUT_SIZE];
while (1) {
size_t length = brl->gioEndpoint->handleMethods->readData(
brl->gioEndpoint->handle, packet, MAX_INPUT_SIZE,
/*initialTimeout unused*/ 0, /*subsequentTimeout unused*/ 0);
if (length == 0) {
break;
}
handlePressedKeysArray(brl, packet);
}
return EOF;
}