commit d8a020cbfd74329471c99d0cf15c3854ad0f4867
Author: Robert Russell <robertrussell.72001@gmail.com>
Date: Mon, 29 Aug 2022 21:58:20 -0700
Initial commit
Diffstat:
| A | .gitignore | | | 2 | ++ |
| A | Makefile | | | 5 | +++++ |
| A | vhidkb.c | | | 213 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 220 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+vhidkb
+\ No newline at end of file
diff --git a/Makefile b/Makefile
@@ -0,0 +1,5 @@
+vhidkb: vhidkb.c
+ gcc -Wall -o $@ $^
+sgid: vhidkb
+ chgrp uhid vhidkb
+ chmod g+s vhidkb
diff --git a/vhidkb.c b/vhidkb.c
@@ -0,0 +1,213 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/uhid.h>
+
+#define USAGE "usage: %s [-h] [-d DEVICE] [-t DELAY]"
+
+typedef uint8_t u8;
+typedef uint32_t u32;
+
+/* This describes the report format of a 256-key NKRO keyboard. To understand
+ * this, read the HID specification available on USB's website (which
+ * unfortunately is of very low quality). */
+u8 rdesc[] = {
+ 0x05, 0x01, /* [Global] Usage Page 0x01 (Generic Desktop) */
+ 0x09, 0x06, /* [Local] Usage 0x06 (Keyboard) */
+ 0xa1, 0x01, /* [Main] Collection 0x01 (Application) */
+ 0x05, 0x07, /* [Global] Usage Page 0x07 (Keyboard) */
+ 0x15, 0x00, /* [Global] Logical Minimum 0 */
+ 0x25, 0x01, /* [Global] Logical Minimum 1 */
+ 0x75, 0x01, /* [Global] Report Size 1 */
+ /* Modifiers field */
+ 0x19, 0xe0, /* [Local] Usage Minimum 0xe0 (Left Control) */
+ 0x29, 0xe7, /* [Local] Usage Maximum 0xe7 (Right GUI) */
+ 0x95, 0x08, /* [Global] Report Count 8 */
+ 0x81, 0x02, /* [Main] Input (Variable) */
+ /* Keys field */
+ 0x19, 0x00, /* [Local] Usage Minimum 0x00 */
+ 0x29, 0xff, /* [Local] Usage Maximum 0xff */
+ 0x96, 0x00, 0x01, /* [Global] Report Count 256 */
+ 0x81, 0x02, /* [Main] Input (Variable) */
+ 0xc0, /* [Main] End Collection */
+};
+
+void
+fatalf(char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ fprintf(stderr, "error: ");
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+ va_end(args);
+ exit(1);
+}
+
+void
+write_event(int fd, struct uhid_event *e) {
+ ssize_t n = write(fd, e, sizeof *e);
+ if (n < 0)
+ fatalf("write: %s", strerror(errno));
+ else if (n != sizeof *e)
+ fatalf("write: incomplete write");
+}
+
+/* TODO: ensure sizeof rdesc < HID_MAX_DESCRIPTOR_SIZE */
+void
+send_create2(int fd) {
+ struct uhid_event e = {0};
+ e.type = UHID_CREATE2;
+ strcpy((char *)e.u.create2.name, "vhidkb");
+ e.u.create2.rd_size = sizeof rdesc;
+ e.u.create2.bus = BUS_VIRTUAL;
+ e.u.create2.vendor = 0; /* TODO */
+ e.u.create2.product = 0; /* TODO */
+ e.u.create2.version = 0;
+ e.u.create2.country = 0;
+ memcpy(e.u.create2.rd_data, rdesc, sizeof rdesc);
+
+ write_event(fd, &e);
+}
+
+void
+send_input2(int fd, u8 *keys) {
+ struct uhid_event e = {0};
+ e.type = UHID_INPUT2;
+ e.u.input2.size = 1 + 32;
+ e.u.input2.data[0] = keys[28]; /* modifiers field */
+ memcpy(&e.u.input2.data[1], keys, 32); /* keys field */
+
+ write_event(fd, &e);
+}
+
+void
+send_get_report_reply(int fd, u8 *keys, struct uhid_get_report_req req) {
+ if (req.rnum != 0 || req.rtype != UHID_INPUT_REPORT) {
+ fprintf(stderr, "warning: ignoring unexpected UHID event\n");
+ return;
+ }
+
+ struct uhid_event e = {0};
+ e.type = UHID_GET_REPORT_REPLY;
+ e.u.get_report_reply.id = req.id;
+ e.u.get_report_reply.err = 0;
+ e.u.get_report_reply.size = 1 + 32;
+ e.u.get_report_reply.data[0] = keys[28]; /* modifiers field */
+ memcpy(&e.u.get_report_reply.data[1], keys, 32); /* keys field */
+
+ write_event(fd, &e);
+}
+
+int
+main(int argc, char **argv) {
+ char *dev = "/dev/uhid";
+ long delayus = 100000;
+
+ int opt;
+ while ((opt = getopt(argc, argv, "hd:t:")) != -1) {
+ switch (opt) {
+ case 'h':
+ printf(USAGE, argv[0]);
+ return 0;
+ case 'd':
+ dev = optarg;
+ break;
+ case 't':
+ delayus = strtol(optarg, 0, 10);
+ if (delayus >= 1000000L)
+ delayus = 999999L; /* Max POSIX usleep argument */
+ break;
+ default:
+ fatalf("unknown option: %c\n" USAGE, opt, argv[0]);
+ }
+ }
+
+ if (optind < argc)
+ fatalf("unexpected arguments\n" USAGE, argv[0]);
+
+ int fd = open(dev, O_RDWR);
+ if (fd < 0)
+ fatalf("open: %s", strerror(errno));
+
+ send_create2(fd);
+
+ u8 keys[32] = {0};
+
+ struct pollfd pfds[2];
+ pfds[0].fd = delayus < 0 ? 0 : -1;
+ pfds[0].events = POLLIN;
+ pfds[1].fd = fd;
+ pfds[1].events = POLLIN;
+
+ for (;;) {
+ int ret = poll(pfds, 2, -1);
+ if (ret < 0)
+ fatalf("poll: %s", strerror(errno));
+
+ if ((pfds[0].revents & ~(POLLIN|POLLHUP))
+ || (pfds[1].revents & ~POLLIN))
+ fatalf("poll: unexpected event");
+
+ if (pfds[0].revents & POLLIN) {
+ u8 buf[64];
+ ssize_t n = read(0, buf, sizeof buf);
+ if (n < 0)
+ fatalf("read: %s", strerror(errno));
+ if (n == 0)
+ break;
+ for (size_t i = 0; i < n; i++) {
+ u8 k = buf[i];
+ keys[k / 8] ^= 1 << (k % 8);
+ send_input2(fd, keys);
+ }
+ } else if (pfds[0].revents & POLLHUP) {
+ break;
+ }
+
+ if (pfds[1].revents & POLLIN) {
+ struct uhid_event e;
+ ssize_t n = read(fd, &e, sizeof e);
+ if (n < 0)
+ fatalf("read: %s", strerror(errno));
+ if (n == 0)
+ fatalf("HUP on UHID device");
+ if (n < sizeof e.type)
+ fatalf("UHID event too small");
+ switch (e.type) {
+ case UHID_START:
+ case UHID_STOP:
+ /* Ignore */
+ break;
+ case UHID_OPEN:
+ if (delayus >= 0) {
+ usleep(delayus);
+ pfds[0].fd = 0;
+ }
+ break;
+ case UHID_CLOSE:
+ if (delayus >= 0)
+ pfds[0].fd = -1;
+ break;
+ case UHID_GET_REPORT:
+ /* Note that struct uhid_event is packed. */
+ if (n < sizeof e.type + sizeof e.u.get_report)
+ fatalf("UHID event too small");
+ send_get_report_reply(fd, keys, e.u.get_report);
+ break;
+ case UHID_OUTPUT:
+ case UHID_SET_REPORT:
+ fprintf(stderr, "warning: ignoring unexpected UHID event\n");
+ break;
+ default:
+ fatalf("invalid UHID event type");
+ }
+ }
+ }
+}