| /* |
| * 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 <errno.h> |
| |
| #include "log.h" |
| #include "strfmt.h" |
| #include "io_hid.h" |
| #include "hid_internal.h" |
| #include "parse.h" |
| #include "device.h" |
| |
| typedef enum { |
| HID_PARM_ADDRESS, |
| HID_PARM_NAME, |
| |
| HID_PARM_MANUFACTURER, |
| HID_PARM_DESCRIPTION, |
| HID_PARM_SERIAL_NUMBER, |
| |
| HID_PARM_VENDOR, |
| HID_PARM_PRODUCT, |
| } HidDeviceParameter; |
| |
| static const char *const hidDeviceParameterNames[] = { |
| "address", |
| "name", |
| |
| "manufacturer", |
| "description", |
| "serialNumber", |
| |
| "vendor", |
| "product", |
| |
| NULL |
| }; |
| |
| static char ** |
| hidGetDeviceParameters (const char *string) { |
| if (!string) string = ""; |
| return getDeviceParameters(hidDeviceParameterNames, string); |
| } |
| |
| typedef struct { |
| STR_DECLARE_FORMATTER((*extendDeviceIdentifier), HidDevice *device); |
| } HidBusMethods; |
| |
| static |
| STR_BEGIN_FORMATTER(hidExtendUSBDeviceIdentifier, HidDevice *device) |
| { |
| const char *serialNumber = hidGetDeviceAddress(device); |
| |
| if (serialNumber && *serialNumber) { |
| STR_PRINTF( |
| "%s%c%s%c", |
| hidDeviceParameterNames[HID_PARM_SERIAL_NUMBER], |
| PARAMETER_ASSIGNMENT_CHARACTER, |
| serialNumber, DEVICE_PARAMETER_SEPARATOR |
| ); |
| } |
| } |
| STR_END_FORMATTER |
| |
| static const HidBusMethods hidUSBBusMethods = { |
| .extendDeviceIdentifier = hidExtendUSBDeviceIdentifier, |
| }; |
| |
| static |
| STR_BEGIN_FORMATTER(hidExtendBluetoothDeviceIdentifier, HidDevice *device) |
| { |
| const char *macAddress = hidGetDeviceAddress(device); |
| |
| if (macAddress && *macAddress) { |
| STR_PRINTF( |
| "%s%c%s%c", |
| hidDeviceParameterNames[HID_PARM_ADDRESS], |
| PARAMETER_ASSIGNMENT_CHARACTER, |
| macAddress, DEVICE_PARAMETER_SEPARATOR |
| ); |
| } |
| } |
| STR_END_FORMATTER |
| |
| static const HidBusMethods hidBluetoothBusMethods= { |
| .extendDeviceIdentifier = hidExtendBluetoothDeviceIdentifier, |
| }; |
| |
| void |
| hidInitializeUSBFilter (HidUSBFilter *filter) { |
| memset(filter, 0, sizeof(*filter)); |
| } |
| |
| void |
| hidInitializeBluetoothFilter (HidBluetoothFilter *filter) { |
| memset(filter, 0, sizeof(*filter)); |
| } |
| |
| int |
| hidParseDeviceIdentifier (HidDeviceIdentifier *identifier, const char *string) { |
| if (!string) return 0; |
| if (!*string) return 0; |
| if (strlen(string) > 4) return 0; |
| |
| char *end; |
| long unsigned int value = strtoul(string, &end, 0X10); |
| if (*end) return 0; |
| if (value > UINT16_MAX) return 0; |
| |
| *identifier = value; |
| return 1; |
| } |
| |
| int |
| hidMatchString (const char *actualString, const char *testString) { |
| size_t testLength = strlen(testString); |
| if (testLength > strlen(actualString)) return 0; |
| return strncasecmp(actualString, testString, testLength) == 0; |
| } |
| |
| struct HidDeviceStruct { |
| HidHandle *handle; |
| |
| const HidBusMethods *busMethods; |
| const HidHandleMethods *handleMethods; |
| }; |
| |
| static void |
| hidDestroyHandle (HidHandle *handle) { |
| HidDestroyHandleMethod *method = hidPackageDescriptor.handleMethods->destroyHandle; |
| |
| if (method) { |
| method(handle); |
| } else { |
| logUnsupportedOperation("hidCloseDevice"); |
| errno = ENOSYS; |
| } |
| } |
| |
| static HidDevice * |
| hidNewDevice (HidHandle *handle, const HidBusMethods *busMethods) { |
| if (handle) { |
| HidDevice *device; |
| |
| if ((device = malloc(sizeof(*device)))) { |
| memset(device, 0, sizeof(*device)); |
| device->handle = handle; |
| device->handleMethods = hidPackageDescriptor.handleMethods; |
| device->busMethods = busMethods; |
| return device; |
| } else { |
| logMallocError(); |
| } |
| |
| hidDestroyHandle(handle); |
| } |
| |
| return NULL; |
| } |
| |
| HidDevice * |
| hidOpenUSBDevice (const HidUSBFilter *filter) { |
| HidNewUSBHandleMethod *method = hidPackageDescriptor.newUSBHandle; |
| |
| if (!method) { |
| logUnsupportedOperation("hidOpenUSBDevice"); |
| errno = ENOSYS; |
| return NULL; |
| } |
| |
| return hidNewDevice(method(filter), &hidUSBBusMethods); |
| } |
| |
| HidDevice * |
| hidOpenBluetoothDevice (const HidBluetoothFilter *filter) { |
| HidNewBluetoothHandleMethod *method = hidPackageDescriptor.newBluetoothHandle; |
| |
| if (!method) { |
| logUnsupportedOperation("hidOpenBluetoothDevice"); |
| errno = ENOSYS; |
| return NULL; |
| } |
| |
| return hidNewDevice(method(filter), &hidBluetoothBusMethods); |
| } |
| |
| void |
| hidInitializeFilter (HidFilter *filter) { |
| memset(filter, 0, sizeof(*filter)); |
| } |
| |
| int |
| hidSetFilterIdentifiers ( |
| HidFilter *filter, const char *vendor, const char *product |
| ) { |
| typedef struct { |
| const char *name; |
| const char *operand; |
| HidDeviceIdentifier *identifier; |
| } IdentifierEntry; |
| |
| const IdentifierEntry identifierTable[] = { |
| { .name = "vendor", |
| .operand = vendor, |
| .identifier = &filter->common.vendorIdentifier, |
| }, |
| |
| { .name = "product", |
| .operand = product, |
| .identifier = &filter->common.productIdentifier, |
| }, |
| }; |
| |
| const IdentifierEntry *cur = identifierTable; |
| const IdentifierEntry *end = cur + ARRAY_COUNT(identifierTable); |
| |
| while (cur < end) { |
| if (cur->operand && *cur->operand) { |
| if (!hidParseDeviceIdentifier(cur->identifier, cur->operand)) { |
| logMessage(LOG_ERR, "invalid %s identifier: %s", cur->name, cur->operand); |
| return 0; |
| } |
| } |
| |
| cur += 1; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| hidCopyStringFilter (const void *from, void *to) { |
| const char *fromString = from; |
| if (!fromString) return 0; |
| if (!*fromString) return 0; |
| |
| const char **toString = to; |
| *toString = fromString; |
| return 1; |
| } |
| |
| static int |
| hidCopyIdentifierFilter (const void *from, void *to) { |
| const HidDeviceIdentifier *fromIdentifier = from; |
| if (!*fromIdentifier) return 0; |
| |
| HidDeviceIdentifier *toIdentifier = to; |
| *toIdentifier = *fromIdentifier; |
| return 1; |
| } |
| |
| static int |
| hidTestMacAddress (const void *from) { |
| const char *address = from; |
| const char *byte = address; |
| |
| unsigned int state = 0; |
| unsigned int octets = 0; |
| const char *digits = "0123456789ABCDEFabcdef"; |
| |
| while (*byte) { |
| if (!state) octets += 1; |
| |
| if (++state < 3) { |
| if (!strchr(digits, *byte)) return 0; |
| } else { |
| if (*byte != ':') return 0; |
| state = 0; |
| } |
| |
| byte += 1; |
| } |
| |
| return (octets == 6) && (state == 2); |
| } |
| |
| int |
| hidOpenDeviceWithFilter (HidDevice **device, const HidFilter *filter) { |
| unsigned char wantUSB = filter->flags.wantUSB; |
| unsigned char wantBluetooth = filter->flags.wantBluetooth; |
| |
| HidCommonProperties common; |
| memset(&common, 0, sizeof(common)); |
| |
| HidUSBProperties usb; |
| memset(&usb, 0, sizeof(usb)); |
| |
| HidBluetoothProperties bluetooth; |
| memset(&bluetooth, 0, sizeof(bluetooth)); |
| |
| typedef struct { |
| const char *name; |
| unsigned char *flag; |
| int (*copy) (const void *from, void *to); |
| int (*test) (const void *from); |
| const void *from; |
| void *to; |
| } FilterEntry; |
| |
| const FilterEntry filterTable[] = { |
| { .name = "vendor identifier", |
| .copy = hidCopyIdentifierFilter, |
| .from = &filter->common.vendorIdentifier, |
| .to = &common.vendorIdentifier, |
| }, |
| |
| { .name = "product identifier", |
| .copy = hidCopyIdentifierFilter, |
| .from = &filter->common.productIdentifier, |
| .to = &common.productIdentifier, |
| }, |
| |
| { .name = "manufacturer name", |
| .copy = hidCopyStringFilter, |
| .from = filter->usb.manufacturerName, |
| .to = &usb.manufacturerName, |
| .flag = &wantUSB, |
| }, |
| |
| { .name = "product description", |
| .copy = hidCopyStringFilter, |
| .from = filter->usb.productDescription, |
| .to = &usb.productDescription, |
| .flag = &wantUSB, |
| }, |
| |
| { .name = "serial number", |
| .copy = hidCopyStringFilter, |
| .from = filter->usb.serialNumber, |
| .to = &usb.serialNumber, |
| .flag = &wantUSB, |
| }, |
| |
| { .name = "MAC address", |
| .copy = hidCopyStringFilter, |
| .test = hidTestMacAddress, |
| .from = filter->bluetooth.macAddress, |
| .to = &bluetooth.macAddress, |
| .flag = &wantBluetooth, |
| }, |
| |
| { .name = "device name", |
| .copy = hidCopyStringFilter, |
| .from = filter->bluetooth.deviceName, |
| .to = &bluetooth.deviceName, |
| .flag = &wantBluetooth, |
| }, |
| }; |
| |
| const FilterEntry *cur = filterTable; |
| const FilterEntry *end = cur + ARRAY_COUNT(filterTable); |
| |
| while (cur < end) { |
| if (cur->copy(cur->from, cur->to)) { |
| if (cur->flag) *cur->flag = 1; |
| |
| if (cur->test) { |
| if (!cur->test(cur->from)) { |
| const char *operand = cur->from; |
| logMessage(LOG_ERR, "invalid %s: %s", cur->name, operand); |
| return 0; |
| } |
| } |
| } |
| |
| cur += 1; |
| } |
| |
| if (wantUSB && wantBluetooth) { |
| logMessage(LOG_ERR, "conflicting filter options"); |
| return 0; |
| } |
| |
| if (wantBluetooth) { |
| HidBluetoothFilter hbf = { |
| .common = common, |
| .bluetooth = bluetooth, |
| }; |
| *device = hidOpenBluetoothDevice(&hbf); |
| } else { |
| HidUSBFilter huf = { |
| .common = common, |
| .usb = usb, |
| }; |
| |
| *device = hidOpenUSBDevice(&huf); |
| } |
| |
| return 1; |
| } |
| |
| int |
| hidOpenDeviceWithParameters (HidDevice **device, const char *string) { |
| char **parameters = hidGetDeviceParameters(string); |
| |
| if (parameters) { |
| HidFilter filter = { |
| .usb = { |
| .manufacturerName = parameters[HID_PARM_MANUFACTURER], |
| .productDescription = parameters[HID_PARM_DESCRIPTION], |
| .serialNumber = parameters[HID_PARM_SERIAL_NUMBER], |
| }, |
| |
| .bluetooth = { |
| .macAddress = parameters[HID_PARM_ADDRESS], |
| .deviceName = parameters[HID_PARM_NAME], |
| }, |
| }; |
| |
| int ok = hidSetFilterIdentifiers( |
| &filter, |
| parameters[HID_PARM_VENDOR], |
| parameters[HID_PARM_PRODUCT] |
| ); |
| |
| if (ok) ok = hidOpenDeviceWithFilter(device, &filter); |
| deallocateStrings(parameters); |
| if (ok) return 1; |
| } |
| |
| return 0; |
| } |
| |
| void |
| hidCloseDevice (HidDevice *device) { |
| hidDestroyHandle(device->handle); |
| free(device); |
| } |
| |
| const HidItemsDescriptor * |
| hidGetItems (HidDevice *device) { |
| HidGetItemsMethod *method = device->handleMethods->getItems; |
| |
| if (!method) { |
| logUnsupportedOperation("hidGetItems"); |
| errno = ENOSYS; |
| return 0; |
| } |
| |
| return method(device->handle); |
| } |
| |
| int |
| hidGetReportSize ( |
| HidDevice *device, |
| HidReportIdentifier identifier, |
| HidReportSize *size |
| ) { |
| HidGetReportSizeMethod *method = device->handleMethods->getReportSize; |
| |
| if (!method) { |
| logUnsupportedOperation("hidGetReportSize"); |
| errno = ENOSYS; |
| return 0; |
| } |
| |
| return method(device->handle, identifier, size); |
| } |
| |
| static void |
| hidLogDataTransfer (const char *action, const unsigned char *data, size_t size, HidReportIdentifier identifier) { |
| logBytes(LOG_CATEGORY(HID_IO), |
| "%s: %02X", data, size, action, identifier |
| ); |
| } |
| |
| ssize_t |
| hidGetReport (HidDevice *device, unsigned char *buffer, size_t size) { |
| HidGetReportMethod *method = device->handleMethods->getReport; |
| |
| if (!method) { |
| logUnsupportedOperation("hidGetReport"); |
| errno = ENOSYS; |
| return 0; |
| } |
| |
| HidReportIdentifier identifier = *buffer; |
| ssize_t result = method(device->handle, buffer, size); |
| |
| if (result != -1) { |
| hidLogDataTransfer("get report", buffer, result, identifier); |
| } |
| |
| return result; |
| } |
| |
| ssize_t |
| hidSetReport (HidDevice *device, const unsigned char *report, size_t size) { |
| HidSetReportMethod *method = device->handleMethods->setReport; |
| |
| if (!method) { |
| logUnsupportedOperation("hidSetReport"); |
| errno = ENOSYS; |
| return 0; |
| } |
| |
| hidLogDataTransfer("set report", report+1, size-1, *report); |
| return method(device->handle, report, size); |
| } |
| |
| ssize_t |
| hidGetFeature (HidDevice *device, unsigned char *buffer, size_t size) { |
| HidGetFeatureMethod *method = device->handleMethods->getFeature; |
| |
| if (!method) { |
| logUnsupportedOperation("hidGetFeature"); |
| errno = ENOSYS; |
| return 0; |
| } |
| |
| HidReportIdentifier identifier = *buffer; |
| ssize_t result = method(device->handle, buffer, size); |
| |
| if (result != -1) { |
| hidLogDataTransfer("get feature", buffer, result, identifier); |
| } |
| |
| return result; |
| } |
| |
| ssize_t |
| hidSetFeature (HidDevice *device, const unsigned char *feature, size_t size) { |
| HidSetFeatureMethod *method = device->handleMethods->setFeature; |
| |
| if (!method) { |
| logUnsupportedOperation("hidSetFeature"); |
| errno = ENOSYS; |
| return 0; |
| } |
| |
| hidLogDataTransfer("set feature", feature+1, size-1, *feature); |
| return method(device->handle, feature, size); |
| } |
| |
| int |
| hidWriteData (HidDevice *device, const unsigned char *data, size_t size) { |
| HidWriteDataMethod *method = device->handleMethods->writeData; |
| |
| if (!method) { |
| logUnsupportedOperation("hidWriteData"); |
| errno = ENOSYS; |
| return 0; |
| } |
| |
| logBytes(LOG_CATEGORY(HID_IO), |
| "output", data, size |
| ); |
| |
| return method(device->handle, data, size); |
| } |
| |
| int |
| hidMonitorInput (HidDevice *device, AsyncMonitorCallback *callback, void *data) { |
| HidMonitorInputMethod *method = device->handleMethods->monitorInput; |
| |
| if (!method) { |
| logUnsupportedOperation("hidMonitorInput"); |
| errno = ENOSYS; |
| return 0; |
| } |
| |
| return method(device->handle, callback, data); |
| } |
| |
| int |
| hidAwaitInput (HidDevice *device, int timeout) { |
| HidAwaitInputMethod *method = device->handleMethods->awaitInput; |
| |
| if (!method) { |
| logUnsupportedOperation("hidAwaitInput"); |
| errno = ENOSYS; |
| return 0; |
| } |
| |
| return method(device->handle, timeout); |
| } |
| |
| ssize_t |
| hidReadData ( |
| HidDevice *device, unsigned char *buffer, size_t size, |
| int initialTimeout, int subsequentTimeout |
| ) { |
| HidReadDataMethod *method = device->handleMethods->readData; |
| |
| if (!method) { |
| logUnsupportedOperation("hidReadData"); |
| errno = ENOSYS; |
| return -1; |
| } |
| |
| ssize_t result = method(device->handle, buffer, size, initialTimeout, subsequentTimeout); |
| |
| if (result != -1) { |
| if (result > 0) { |
| logBytes(LOG_CATEGORY(HID_IO), |
| "input", buffer, result |
| ); |
| } |
| } |
| |
| return result; |
| } |
| |
| int |
| hidGetDeviceIdentifiers (HidDevice *device, HidDeviceIdentifier *vendor, HidDeviceIdentifier *product) { |
| HidGetDeviceIdentifiersMethod *method = device->handleMethods->getDeviceIdentifiers; |
| |
| if (!method) { |
| logUnsupportedOperation("hidGetDeviceIdentifiers"); |
| errno = ENOSYS; |
| return 0; |
| } |
| |
| return method(device->handle, vendor, product); |
| } |
| |
| const char * |
| hidGetDeviceAddress (HidDevice *device) { |
| HidGetDeviceAddressMethod *method = device->handleMethods->getDeviceAddress; |
| |
| if (!method) { |
| logUnsupportedOperation("hidGetDeviceAddress"); |
| errno = ENOSYS; |
| return NULL; |
| } |
| |
| return method(device->handle); |
| } |
| |
| const char * |
| hidGetDeviceName (HidDevice *device) { |
| HidGetDeviceNameMethod *method = device->handleMethods->getDeviceName; |
| |
| if (!method) { |
| logUnsupportedOperation("hidGetDeviceName"); |
| errno = ENOSYS; |
| return NULL; |
| } |
| |
| return method(device->handle); |
| } |
| |
| const char * |
| hidGetHostPath (HidDevice *device) { |
| HidGetHostPathMethod *method = device->handleMethods->getHostPath; |
| |
| if (!method) { |
| logUnsupportedOperation("hidGetHostPath"); |
| errno = ENOSYS; |
| return NULL; |
| } |
| |
| return method(device->handle); |
| } |
| |
| const char * |
| hidGetHostDevice (HidDevice *device) { |
| HidGetHostDeviceMethod *method = device->handleMethods->getHostDevice; |
| |
| if (!method) { |
| logUnsupportedOperation("hidGetHostDevice"); |
| errno = ENOSYS; |
| return NULL; |
| } |
| |
| return method(device->handle); |
| } |
| |
| const char * |
| hidCacheString ( |
| HidHandle *handle, char **cachedValue, |
| char *buffer, size_t size, |
| HidGetStringMethod *getString, void *data |
| ) { |
| if (!*cachedValue) { |
| if (!getString(handle, buffer, size, data)) return NULL; |
| char *value = strdup(buffer); |
| |
| if (!value) { |
| logMallocError(); |
| return NULL; |
| } |
| |
| *cachedValue = value; |
| } |
| |
| return *cachedValue; |
| } |
| |
| const char * |
| hidMakeDeviceIdentifier (HidDevice *device, char *buffer, size_t size) { |
| size_t length; |
| STR_BEGIN(buffer, size); |
| STR_PRINTF("%s%c", HID_DEVICE_QUALIFIER, PARAMETER_QUALIFIER_CHARACTER); |
| |
| { |
| HidDeviceIdentifier vendor; |
| HidDeviceIdentifier product; |
| |
| if (hidGetDeviceIdentifiers(device, &vendor, &product)) { |
| if (vendor) { |
| STR_PRINTF( |
| "%s%c%04X%c", |
| hidDeviceParameterNames[HID_PARM_VENDOR], |
| PARAMETER_ASSIGNMENT_CHARACTER, |
| vendor, DEVICE_PARAMETER_SEPARATOR |
| ); |
| } |
| |
| if (product) { |
| STR_PRINTF( |
| "%s%c%04X%c", |
| hidDeviceParameterNames[HID_PARM_PRODUCT], |
| PARAMETER_ASSIGNMENT_CHARACTER, |
| product, DEVICE_PARAMETER_SEPARATOR |
| ); |
| } |
| } |
| } |
| |
| STR_FORMAT(device->busMethods->extendDeviceIdentifier, device); |
| length = STR_LENGTH; |
| STR_END; |
| |
| { |
| char *last = &buffer[length] - 1; |
| if (*last == DEVICE_PARAMETER_SEPARATOR) *last = 0; |
| } |
| |
| return buffer; |
| } |
| |
| int |
| isHidDeviceIdentifier (const char **identifier) { |
| return hasQualifier(identifier, HID_DEVICE_QUALIFIER); |
| } |