#define LOG_MODULE "pacdrive" // clang-format off // Don't format because the order is important here #include #include // clang-format on #include #include #include #include #include "geninput/hid.h" #include "geninput/io-thread.h" #include "geninput/pacdrive.h" #include "util/defs.h" #include "util/log.h" #include "util/mem.h" /* The PacDrive appears to have a malformed descriptor for its OUT report. Since PacDrives are fairly prevalent, we have to have a special-case HID driver for them that uses a hard-coded report structure. */ struct pac_report { uint8_t report_id; uint8_t static_00; uint8_t static_dd; uint8_t leds_hi; uint8_t leds_lo; }; struct pac { struct hid_fd super; HANDLE fd; OVERLAPPED ovl; struct pac_report io_buf; uint16_t leds; }; static struct hid_light pac_lights[16]; static const wchar_t pac_name[] = L"PacDrive Shim"; static bool pac_get_device_usage(const struct hid *hid, uint32_t *usage); static bool pac_get_name(const struct hid *hid, wchar_t *chars, size_t *nchars); static bool pac_get_controls( const struct hid *hid, struct hid_control *controls, size_t *ncontrols); static bool pac_get_lights( const struct hid *hid, struct hid_light *lights, size_t *nlights); static bool pac_get_value(struct hid *hid, size_t control_no, int32_t *value); static bool pac_set_light(struct hid *hid, size_t light_no, uint32_t intensity); static bool pac_handle_event(struct hid_fd *hid_fd, OVERLAPPED *ovl, size_t nbytes); static void pac_close(struct hid *hid); static const struct hid_fd_vtbl pac_vtbl = { {/* .close = */ pac_close, /* .get_device_usage = */ pac_get_device_usage, /* .get_name = */ pac_get_name, /* .get_controls = */ pac_get_controls, /* .get_lights = */ pac_get_lights, /* .get_value = */ pac_get_value, /* .set_light = */ pac_set_light}, /* .handle_event = */ pac_handle_event}; bool pac_open( struct hid_fd **hid_out, const char *dev_node, HANDLE iocp, uintptr_t iocp_ctx) { HIDD_ATTRIBUTES attrs; HANDLE fd; struct pac *pac; int i; fd = CreateFile( dev_node, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (fd == INVALID_HANDLE_VALUE) { goto open_fail; } if (!HidD_GetAttributes(fd, &attrs)) { goto attrs_fail; } if (attrs.VendorID != 0xD209 || (attrs.ProductID & 0xFFF8) != 0x1500) { goto id_fail; } pac = xcalloc(sizeof(*pac)); pac->super.vptr = &pac_vtbl; pac->fd = fd; CreateIoCompletionPort(fd, iocp, iocp_ctx, 0); if (!WriteFile(fd, &pac->io_buf, sizeof(pac->io_buf), NULL, &pac->ovl) && GetLastError() != ERROR_IO_PENDING) { log_warning( "%s: Initial WriteFile failed: %08x", dev_node, (unsigned int) GetLastError()); goto write_fail; } for (i = 0; i < lengthof(pac_lights); i++) { pac_lights[i].usage = 0x0008004B; /* LED, Generic Indicator */ pac_lights[i].value_min = 0; pac_lights[i].value_max = 1; } log_misc("Opened PacDrive on %s", dev_node); *hid_out = &pac->super; return true; write_fail: free(pac); id_fail: attrs_fail: CloseHandle(fd); open_fail: return false; } static bool pac_get_device_usage(const struct hid *hid, uint32_t *usage) { *usage = 0x00010000; return true; } static bool pac_get_name(const struct hid *hid, wchar_t *chars, size_t *nchars) { log_assert(nchars != NULL); if (chars != NULL) { if (*nchars < lengthof(pac_name)) { return false; } memcpy(chars, pac_name, sizeof(pac_name)); } *nchars = lengthof(pac_name); return true; } static bool pac_get_controls( const struct hid *hid, struct hid_control *controls, size_t *ncontrols) { log_assert(ncontrols != NULL); *ncontrols = 0; return true; } static bool pac_get_lights(const struct hid *hid, struct hid_light *lights, size_t *nlights) { log_assert(nlights != NULL); if (lights != NULL) { if (*nlights < lengthof(pac_lights)) { return false; } memcpy(lights, pac_lights, sizeof(pac_lights)); } *nlights = lengthof(pac_lights); return true; } static bool pac_get_value(struct hid *hid, size_t control_no, int32_t *value) { return false; } static bool pac_set_light(struct hid *hid, size_t light_no, uint32_t intensity) { struct pac *pac = containerof(hid, struct pac, super); if (light_no >= lengthof(pac_lights)) { return false; } if (intensity != 0) { pac->leds |= (1 << light_no); } else { pac->leds &= ~(1 << light_no); } return true; } static bool pac_handle_event(struct hid_fd *hid_fd, OVERLAPPED *ovl, size_t nbytes) { struct pac *pac = containerof(hid_fd, struct pac, super); /* OUT report successfully sent, send another */ pac->io_buf.leds_hi = HIBYTE(pac->leds); pac->io_buf.leds_lo = LOBYTE(pac->leds); memset(&pac->ovl, 0, sizeof(pac->ovl)); if (!WriteFile( pac->fd, &pac->io_buf, sizeof(pac->io_buf), NULL, &pac->ovl) && GetLastError() != ERROR_IO_PENDING) { log_warning("WriteFile failed: %08x", (unsigned int) GetLastError()); return false; } return true; } static void pac_close(struct hid *hid) { struct pac *pac = containerof(hid, struct pac, super); CloseHandle(pac->fd); free(pac); }