| /* |
| * 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 <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/ioctl.h> |
| #include <libudev.h> |
| #include <linux/hidraw.h> |
| #include <linux/input.h> |
| |
| #include "log.h" |
| #include "hid_types.h" |
| #include "hid_internal.h" |
| #include "hid_items.h" |
| #include "io_misc.h" |
| #include "async_handle.h" |
| #include "async_io.h" |
| |
| struct HidHandleStruct { |
| char *sysfsPath; |
| char *devicePath; |
| |
| int fileDescriptor; |
| AsyncHandle inputMonitor; |
| struct hidraw_devinfo deviceInformation; |
| |
| HidItemsDescriptor *hidItems; |
| char *deviceAddress; |
| char *deviceName; |
| char *hostPath; |
| |
| char strings[]; |
| }; |
| |
| static void |
| hidLinuxCancelInputMonitor (HidHandle *handle) { |
| if (handle->inputMonitor) { |
| asyncCancelRequest(handle->inputMonitor); |
| handle->inputMonitor = NULL; |
| } |
| } |
| |
| static void |
| hidLinuxDestroyHandle (HidHandle *handle) { |
| hidLinuxCancelInputMonitor(handle); |
| close(handle->fileDescriptor); |
| |
| if (handle->hidItems) free(handle->hidItems); |
| if (handle->deviceAddress) free(handle->deviceAddress); |
| if (handle->deviceName) free(handle->deviceName); |
| if (handle->hostPath) free(handle->hostPath); |
| |
| free(handle); |
| } |
| |
| static const HidItemsDescriptor * |
| hidLinuxGetItems (HidHandle *handle) { |
| if (handle->hidItems) return handle->hidItems; |
| int size; |
| |
| if (ioctl(handle->fileDescriptor, HIDIOCGRDESCSIZE, &size) != -1) { |
| struct hidraw_report_descriptor descriptor = { |
| .size = size |
| }; |
| |
| if (ioctl(handle->fileDescriptor, HIDIOCGRDESC, &descriptor) != -1) { |
| HidItemsDescriptor *items; |
| |
| if ((items = malloc(sizeof(*items) + size))) { |
| memset(items, 0, sizeof(*items)); |
| items->count = size; |
| memcpy(items->bytes, descriptor.value, size); |
| return (handle->hidItems = items); |
| } else { |
| logMallocError(); |
| } |
| } else { |
| logSystemError("ioctl[HIDIOCGRDESC]"); |
| } |
| } else { |
| logSystemError("ioctl[HIDIOCGRDESCSIZE]"); |
| } |
| |
| return NULL; |
| } |
| |
| static int |
| hidLinuxGetReportSize ( |
| HidHandle *handle, |
| HidReportIdentifier identifier, |
| HidReportSize *size |
| ) { |
| const HidItemsDescriptor *items = hidLinuxGetItems(handle); |
| if (!items) return 0; |
| return hidReportSize(items, identifier, size); |
| } |
| |
| static ssize_t |
| hidLinuxGetReport (HidHandle *handle, unsigned char *buffer, size_t size) { |
| int length; |
| |
| #ifdef HIDIOCGINPUT |
| length = ioctl(handle->fileDescriptor, HIDIOCGINPUT(size), buffer); |
| #else /* HIDIOCGINPUT */ |
| length = -1; |
| errno = ENOSYS; |
| #endif /* HIDIOCGINPUT */ |
| |
| if (length == -1) logSystemError("ioctl[HIDIOCGINPUT]"); |
| return length; |
| } |
| |
| static ssize_t |
| hidLinuxSetReport (HidHandle *handle, const unsigned char *report, size_t size) { |
| int count; |
| |
| #ifdef HIDIOCSOUTPUT |
| count = ioctl(handle->fileDescriptor, HIDIOCSOUTPUT(size), report); |
| #else /* HIDIOCSOUTPUT */ |
| count = write(handle->fileDescriptor, report, size); |
| #endif /* HIDIOCSOUTPUT */ |
| |
| if (count == -1) logSystemError("ioctl[HIDIOCSOUTPUT]"); |
| return count; |
| } |
| |
| static ssize_t |
| hidLinuxGetFeature (HidHandle *handle, unsigned char *buffer, size_t size) { |
| int result = ioctl(handle->fileDescriptor, HIDIOCGFEATURE(size), buffer); |
| |
| if (result == -1) { |
| logSystemError("ioctl[HIDIOCGFEATURE]"); |
| } |
| |
| return result; |
| } |
| |
| static ssize_t |
| hidLinuxSetFeature (HidHandle *handle, const unsigned char *feature, size_t size) { |
| int count = ioctl(handle->fileDescriptor, HIDIOCSFEATURE(size), feature); |
| if (count == -1) logSystemError("ioctl[HIDIOCSFEATURE]"); |
| return count; |
| } |
| |
| static int |
| hidLinuxWriteData (HidHandle *handle, const unsigned char *data, size_t size) { |
| return writeFile(handle->fileDescriptor, data, size); |
| } |
| |
| static int |
| hidLinuxMonitorInput (HidHandle *handle, AsyncMonitorCallback *callback, void *data) { |
| hidLinuxCancelInputMonitor(handle); |
| if (!callback) return 1; |
| return asyncMonitorFileInput(&handle->inputMonitor, handle->fileDescriptor, callback, data); |
| } |
| |
| static int |
| hidLinuxAwaitInput (HidHandle *handle, int timeout) { |
| return awaitFileInput(handle->fileDescriptor, timeout); |
| } |
| |
| static ssize_t |
| hidLinuxReadData ( |
| HidHandle *handle, unsigned char *buffer, size_t size, |
| int initialTimeout, int subsequentTimeout |
| ) { |
| return readFile(handle->fileDescriptor, buffer, size, initialTimeout, subsequentTimeout); |
| } |
| |
| static int |
| hidLinuxGetDeviceIdentifiers (HidHandle *handle, HidDeviceIdentifier *vendor, HidDeviceIdentifier *product) { |
| if (vendor) *vendor = handle->deviceInformation.vendor; |
| if (product) *product = handle->deviceInformation.product; |
| return 1; |
| } |
| |
| static int |
| hidLinuxGetRawName (HidHandle *handle, char *buffer, size_t size, void *data) { |
| // For USB, this will be the manufacturer string, a space, and the product string. |
| // For Bluetooth, this will be the name of the device. |
| int length = ioctl(handle->fileDescriptor, HIDIOCGRAWNAME(size), buffer); |
| |
| if (length == -1) { |
| logSystemError("ioctl[HIDIOCGRAWNAME]"); |
| length = 0; |
| } else if (length == size) { |
| length -= 1; |
| } |
| |
| buffer[length] = 0; |
| return !!length; |
| } |
| |
| static int |
| hidLinuxGetRawPhysical (HidHandle *handle, char *buffer, size_t size, void *data) { |
| // For USB, this will be the physical path (controller, hubs, ports, etc) to the device. |
| // For Bluetooth, this will be the address of the host controller. |
| int length = ioctl(handle->fileDescriptor, HIDIOCGRAWPHYS(size), buffer); |
| |
| if (length == -1) { |
| logSystemError("ioctl[HIDIOCGRAWPHYS]"); |
| length = 0; |
| } else if (length == size) { |
| length -= 1; |
| } |
| |
| buffer[length] = 0; |
| return !!length; |
| } |
| |
| static int |
| hidLinuxGetRawUnique (HidHandle *handle, char *buffer, size_t size, void *data) { |
| // For USB, this will be the serial number of the device. |
| // For Bluetooth, this will be the MAC (hardware) address of the device. |
| int length; |
| |
| #ifdef HIDIOCGRAWUNIQ |
| length = ioctl(handle->fileDescriptor, HIDIOCGRAWUNIQ(size), buffer); |
| #else /* HIDIOCGRAWUNIQ */ |
| length = -1; |
| errno = ENOSYS; |
| #endif /* HIDIOCGRAWUNIQ */ |
| |
| if (length == -1) { |
| logSystemError("ioctl[HIDIOCGRAWUNIQ]"); |
| length = 0; |
| } else if (length == size) { |
| length -= 1; |
| } |
| |
| buffer[length] = 0; |
| return !!length; |
| } |
| |
| static const char * |
| hidLinuxGetDeviceAddress (HidHandle *handle) { |
| char buffer[0X1000]; |
| |
| return hidCacheString( |
| handle, &handle->deviceAddress, |
| buffer, sizeof(buffer), |
| hidLinuxGetRawUnique, NULL |
| ); |
| } |
| |
| static const char * |
| hidLinuxGetDeviceName (HidHandle *handle) { |
| char buffer[0X1000]; |
| |
| return hidCacheString( |
| handle, &handle->deviceName, |
| buffer, sizeof(buffer), |
| hidLinuxGetRawName, NULL |
| ); |
| } |
| |
| static const char * |
| hidLinuxGetHostPath (HidHandle *handle) { |
| char buffer[0X1000]; |
| |
| return hidCacheString( |
| handle, &handle->hostPath, |
| buffer, sizeof(buffer), |
| hidLinuxGetRawPhysical, NULL |
| ); |
| } |
| |
| static const char * |
| hidLinuxGetHostDevice (HidHandle *handle) { |
| return handle->devicePath; |
| } |
| |
| static const HidHandleMethods hidLinuxHandleMethods = { |
| .destroyHandle = hidLinuxDestroyHandle, |
| |
| .getItems = hidLinuxGetItems, |
| |
| .getReportSize = hidLinuxGetReportSize, |
| .getReport = hidLinuxGetReport, |
| .setReport = hidLinuxSetReport, |
| .getFeature = hidLinuxGetFeature, |
| .setFeature = hidLinuxSetFeature, |
| |
| .writeData = hidLinuxWriteData, |
| .monitorInput = hidLinuxMonitorInput, |
| .awaitInput = hidLinuxAwaitInput, |
| .readData = hidLinuxReadData, |
| |
| .getDeviceIdentifiers = hidLinuxGetDeviceIdentifiers, |
| .getDeviceAddress = hidLinuxGetDeviceAddress, |
| .getDeviceName = hidLinuxGetDeviceName, |
| .getHostPath = hidLinuxGetHostPath, |
| .getHostDevice = hidLinuxGetHostDevice, |
| }; |
| |
| typedef int HidLinuxAttributeTester ( |
| struct udev_device *device, |
| const char *name, |
| const void *value |
| ); |
| |
| static int |
| hidLinuxTestString ( |
| struct udev_device *device, |
| const char *name, |
| const void *value |
| ) { |
| const char *testString = value; |
| if (!testString) return 1; |
| if (!*testString) return 1; |
| |
| const char *actualString = udev_device_get_sysattr_value(device, name); |
| if (!actualString) return 0; |
| if (!*actualString) return 0; |
| |
| return hidMatchString(actualString, testString); |
| } |
| |
| typedef struct { |
| const char *name; |
| const void *value; |
| HidLinuxAttributeTester *function; |
| } HidLinuxAttributeTest; |
| |
| static int |
| hidLinuxTestAttributes ( |
| struct udev_device *device, |
| const HidLinuxAttributeTest *tests, |
| size_t testCount |
| ) { |
| const HidLinuxAttributeTest *test = tests; |
| const HidLinuxAttributeTest *end = test + testCount; |
| |
| while (test < end) { |
| if (!test->function(device, test->name, test->value)) return 0; |
| test += 1; |
| } |
| |
| return 1; |
| } |
| |
| static HidHandle * |
| hidLinuxNewHandle (struct udev_device *device) { |
| const char *sysPath = udev_device_get_syspath(device); |
| const char *devPath = udev_device_get_devnode(device); |
| |
| size_t sysSize = strlen(sysPath) + 1; |
| size_t devSize = strlen(devPath) + 1; |
| HidHandle *handle = malloc(sizeof(*handle) + sysSize + devSize); |
| |
| if (handle) { |
| memset(handle, 0, sizeof(*handle)); |
| |
| { |
| char *string = handle->strings; |
| string = mempcpy((handle->sysfsPath = string), sysPath, sysSize); |
| string = mempcpy((handle->devicePath = string), devPath, devSize); |
| } |
| |
| if ((handle->fileDescriptor = open(devPath, (O_RDWR | O_NONBLOCK))) != -1) { |
| if (ioctl(handle->fileDescriptor, HIDIOCGRAWINFO, &handle->deviceInformation) != -1) { |
| return handle; |
| } else { |
| logSystemError("ioctl[HIDIOCGRAWINFO]"); |
| } |
| |
| close(handle->fileDescriptor); |
| } else { |
| logMessage(LOG_ERR, "device open error: %s: %s", devPath, strerror(errno)); |
| } |
| |
| free(handle); |
| } else { |
| logMallocError(); |
| } |
| |
| return NULL; |
| } |
| |
| typedef int HidLinuxPropertiesTester ( |
| HidHandle *handle, |
| struct udev_device *device, |
| const void *filter |
| ); |
| |
| static HidHandle * |
| hidLinuxFindDevice (HidLinuxPropertiesTester *testProperties, const void *filter) { |
| HidHandle *handle = NULL; |
| struct udev *udev = udev_new(); |
| |
| if (udev) { |
| struct udev_enumerate *enumeration = udev_enumerate_new(udev); |
| |
| if (enumeration) { |
| udev_enumerate_add_match_subsystem(enumeration, "hidraw"); |
| udev_enumerate_scan_devices(enumeration); |
| |
| struct udev_list_entry *deviceList = udev_enumerate_get_list_entry(enumeration); |
| struct udev_list_entry *deviceEntry; |
| |
| udev_list_entry_foreach(deviceEntry, deviceList) { |
| const char *sysPath = udev_list_entry_get_name(deviceEntry); |
| struct udev_device *hidDevice = udev_device_new_from_syspath(udev, sysPath); |
| |
| if (hidDevice) { |
| if ((handle = hidLinuxNewHandle(hidDevice))) { |
| if (!testProperties(handle, hidDevice, filter)) { |
| hidLinuxDestroyHandle(handle); |
| handle = NULL; |
| } |
| } |
| |
| udev_device_unref(hidDevice); |
| } |
| |
| if (handle) break; |
| } |
| |
| udev_enumerate_unref(enumeration); |
| enumeration = NULL; |
| } |
| |
| udev_unref(udev); |
| udev = NULL; |
| } |
| |
| return handle; |
| } |
| |
| static int |
| hidLinuxTestCommonProperties (HidHandle *handle, const HidCommonProperties *common) { |
| if (common->vendorIdentifier) { |
| if (handle->deviceInformation.vendor != common->vendorIdentifier) { |
| return 0; |
| } |
| } |
| |
| if (common->productIdentifier) { |
| if (handle->deviceInformation.product != common->productIdentifier) { |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| static int |
| hidLinuxTestUSBProperties (HidHandle *handle, struct udev_device *hidDevice, const void *filter) { |
| if (handle->deviceInformation.bustype != BUS_USB) return 0; |
| |
| const HidUSBFilter *huf = filter; |
| if (!hidLinuxTestCommonProperties(handle, &huf->common)) return 0; |
| |
| struct udev_device *usbDevice = udev_device_get_parent_with_subsystem_devtype(hidDevice, "usb", "usb_device"); |
| if (!usbDevice) return 0; |
| const HidUSBProperties *test = &huf->usb; |
| |
| const HidLinuxAttributeTest tests[] = { |
| { .name = "manufacturer", |
| .value = test->manufacturerName, |
| .function = hidLinuxTestString |
| }, |
| |
| { .name = "product", |
| .value = test->productDescription, |
| .function = hidLinuxTestString |
| }, |
| |
| { .name = "serial", |
| .value = test->serialNumber, |
| .function = hidLinuxTestString |
| }, |
| }; |
| |
| return hidLinuxTestAttributes(usbDevice, tests, ARRAY_COUNT(tests)); |
| } |
| |
| static HidHandle * |
| hidLinuxNewUSBHandle (const HidUSBFilter *filter) { |
| return hidLinuxFindDevice(hidLinuxTestUSBProperties, filter); |
| } |
| |
| static int |
| hidLinuxTestBluetoothProperties (HidHandle *handle, struct udev_device *hidDevice, const void *filter) { |
| if (handle->deviceInformation.bustype != BUS_BLUETOOTH) return 0; |
| |
| const HidBluetoothFilter *hbf = filter; |
| if (!hidLinuxTestCommonProperties(handle, &hbf->common)) return 0; |
| const HidBluetoothProperties *test = &hbf->bluetooth; |
| |
| { |
| const char *testAddress = test->macAddress; |
| |
| if (testAddress && *testAddress) { |
| const char *actualAddress = hidLinuxGetDeviceAddress(handle); |
| if (!actualAddress) return 0; |
| if (strcasecmp(actualAddress, testAddress) != 0) return 0; |
| } |
| } |
| |
| { |
| const char *testName = test->deviceName; |
| |
| if (testName && *testName) { |
| const char *actualName = hidLinuxGetDeviceName(handle); |
| if (!actualName) return 0; |
| if (!hidMatchString(actualName, testName)) return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| static HidHandle * |
| hidLinuxNewBluetoothHandle (const HidBluetoothFilter *filter) { |
| return hidLinuxFindDevice(hidLinuxTestBluetoothProperties, filter); |
| } |
| |
| const HidPackageDescriptor hidPackageDescriptor = { |
| .packageName = "Linux", |
| .handleMethods = &hidLinuxHandleMethods, |
| |
| .newUSBHandle = hidLinuxNewUSBHandle, |
| .newBluetoothHandle = hidLinuxNewBluetoothHandle, |
| }; |