Hello,
I am trying to simulate a keyboard using the uhid interface. The fake
device shows up in dmesg, but attempting to press the "a" key does
nothing I can see. No input appears in the active terminal, nor in an
xev window.
The program receives UHID_OPEN, UHID_START, and UHID_CLOSE.
Many different report descriptors were tried. The one currently
uncommented is the one distributed with the USB implementor forum's
HID report descriptor tool. The other is one found in a description of
a boot device compatible keyboard.
Thanks in advance,
R0b0t1
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <linux/uhid.h>
#define handle_error(m) \
do { perror(m); exit(EXIT_FAILURE); } while (0)
static unsigned char rdesc[] = {
0x05, 0x01,
0x09, 0x06,
0xa1, 0x01,
0x05, 0x07,
0x19, 0xe0,
0x29, 0xe7,
0x15, 0x00,
0x25, 0x01,
0x75, 0x01,
0x95, 0x08,
0x81, 0x02,
0x95, 0x01,
0x75, 0x08,
0x81, 0x03,
0x95, 0x05,
0x75, 0x01,
0x05, 0x08,
0x19, 0x01,
0x29, 0x05,
0x91, 0x02,
0x95, 0x01,
0x75, 0x03,
0x91, 0x03,
0x95, 0x06,
0x75, 0x08,
0x15, 0x00,
0x25, 0x65,
0x05, 0x07,
0x19, 0x00,
0x29, 0x65,
0x81, 0x00,
0xc0
};
/*
static unsigned char rdesc[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xa1, 0x01, // Collection (Application)
// Modifier report.
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x05, 0x07, // Usage Page (Key Codes)
0x19, 0xe0, // Usage Minimum (224)
0x29, 0xe7, // Usage Maximum (231)
0x15, 0x00, // Logical Minimum (0) - Shorten (0x14)?
0x25, 0x01, // Logical Maximum (1)
0x81, 0x02, // Input (Data, Variable, Absolute)
// Reserved byte.
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x01, // Input (Constant)
// LED report.
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x05, 0x08, // Usage Page (LEDs)
0x19, 0x01, // Usage Minimum (1)
0x29, 0x05, // Usage Maximum (5)
0x91, 0x02, // Output (Data, Variable, Absolute)
// LED report padding.
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x91, 0x01, // Output (Constant)
// Key code report.
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xff, 0x00, // Logical Maximum (255)
0x05, 0x07, // Usage Page (Key Codes)
0x19, 0x00, // Usage Minimum (0)
0x29, 0xff, // Usage Maximum (255)
0x81, 0x00, // Input (Data, Array)
0xc0 // End Collection
};
*/
static int
uhid_write(int fd, const struct uhid_event *he)
{
ssize_t r = write(fd, he, sizeof(*he));
if (r < 0) {
fprintf(stderr, "write: cannot write to uhid: %m\n");
return -errno;
} else if (r != sizeof(*he)) {
fprintf(stderr, "write: wrong size written to uhid: %ld != %lu\n",
r, sizeof(*he));
return -EFAULT;
} else return 0;
}
static int
uhid_event(int fd)
{
struct uhid_event he;
ssize_t sz;
if ((sz = read(fd, &he, sizeof(he))) == -1)
handle_error("read");
switch (he.type) {
case UHID_START:
fprintf(stderr, "UHID_START from uhid-dev.\n");
break;
case UHID_STOP:
fprintf(stderr, "UHID_STOP from uhid-dev.\n");
break;
case UHID_OPEN:
fprintf(stderr, "UHID_OPEN from uhid-dev.\n");
return 0;
case UHID_CLOSE:
fprintf(stderr, "UHID_CLOSE from uhid-dev.\n");
break;
case UHID_OUTPUT:
fprintf(stderr, "UHID_OUTPUT from uhid-dev.\n");
// TODO: Handle output.
break;
case UHID_OUTPUT_EV:
fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev.\n");
break;
default:
fprintf(stderr, "Invalid event from uhid-dev: %u.\n", he.type);
}
return 1;
}
int
main(int argc, char *argv[])
{
int fd;
ssize_t sz;
struct uhid_event he;
if ((fd = open("/dev/uhid", O_RDWR | O_CLOEXEC)) == -1)
handle_error("open");
struct pollfd fds[1] = {
{.fd = fd, .events = POLLIN}
};
memset(&he, 0, sizeof(he));
he.type = UHID_CREATE2;
strcpy((char *)he.u.create.name, "kb-daemon-device");
memcpy(he.u.create2.rd_data, rdesc, sizeof(rdesc));
he.u.create2.rd_size = sizeof(rdesc);
he.u.create2.bus = BUS_USB;
he.u.create2.vendor = 0x15d9;
he.u.create2.product = 0x0a37;
he.u.create2.version = 0;
he.u.create2.country = 0;
if (uhid_write(fd, &he))
handle_error("uhid_write");
char cont = 1;
while (cont) {
int n = ppoll(fds, sizeof(fds), NULL, NULL);
if (n == -1)
handle_error("ppoll");
for (int i = 0; i < n; i++) {
if (fds[i].fd == fd &&
fds[i].revents & POLLIN)
cont = uhid_event(fds[i].fd);
}
}
memset(&he, 0, sizeof(he));
he.type = UHID_INPUT2;
he.u.input2.size = 8;
he.u.input2.data[0] = 0x00;
he.u.input2.data[1] = 0x00;
he.u.input2.data[2] = 0x04;
he.u.input2.data[3] = 0x00;
he.u.input2.data[4] = 0x00;
he.u.input2.data[5] = 0x00;
he.u.input2.data[6] = 0x00;
he.u.input2.data[7] = 0x00;
if (uhid_write(fd, &he))
handle_error("uhid_write");
memset(&he, 0, sizeof(he));
he.type = UHID_INPUT2;
he.u.input2.size = 8;
he.u.input2.data[0] = 0x00;
he.u.input2.data[1] = 0x00;
he.u.input2.data[2] = 0x00;
he.u.input2.data[3] = 0x00;
he.u.input2.data[4] = 0x00;
he.u.input2.data[5] = 0x00;
he.u.input2.data[6] = 0x00;
he.u.input2.data[7] = 0x00;
if (uhid_write(fd, &he))
handle_error("uhid_write");
memset(&he, 0, sizeof(he));
he.type = UHID_DESTROY;
if (uhid_write(fd, &he))
handle_error("uhid_write");
uhid_event(fd);
return EXIT_SUCCESS;
}