vhidkb

virtual HID keyboard
git clone git://git.rr3.xyz/vhidkb
Log | Files | Refs | README | LICENSE

commit d8a020cbfd74329471c99d0cf15c3854ad0f4867
Author: Robert Russell <robertrussell.72001@gmail.com>
Date:   Mon, 29 Aug 2022 21:58:20 -0700

Initial commit

Diffstat:
A.gitignore | 2++
AMakefile | 5+++++
Avhidkb.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"); + } + } + } +}