--- /dev/null
+#include <errno.h>
+#include <poll.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/uhid.h>
+
+#define SLEEP_TIME_SEC 10
+
+/* mouse report descriptor
+ * you can use a tool like https://eleccelerator.com/usbdescreqparser/
+ * or rip a report descriptor with usbhid-dump
+ * don't try to make any sense of the numbers, it is mostly legacy
+ * convention */
+
+static unsigned char rdesc[] = {
+0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+0x09, 0x02, // Usage (Mouse)
+0xA1, 0x01, // Collection (Application)
+0x09, 0x01, // Usage (Pointer)
+0xA1, 0x00, // Collection (Physical)
+0x05, 0x09, // Usage Page (Button)
+0x19, 0x01, // Usage Minimum (0x01)
+0x29, 0x03, // Usage Maximum (0x03)
+0x15, 0x00, // Logical Minimum (0)
+0x25, 0x01, // Logical Maximum (1)
+0x75, 0x01, // Report Size (1)
+0x95, 0x03, // Report Count (3)
+0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+0x75, 0x05, // Report Size (5)
+0x95, 0x01, // Report Count (1)
+0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+0x09, 0x30, // Usage (X)
+0x09, 0x31, // Usage (Y)
+0x09, 0x38, // Usage (Wheel)
+0x15, 0x81, // Logical Minimum (-127)
+0x25, 0x7F, // Logical Maximum (127)
+0x75, 0x08, // Report Size (8)
+0x95, 0x03, // Report Count (3)
+0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
+0xC0, // End Collection
+0xC0, // End Collection
+
+// 52 bytes
+};
+
+/* input report: report id is 0 (not specified in report
+ * descriptor so assumed */
+typedef struct _report {
+ unsigned char buttons;
+ unsigned char x_pos;
+ unsigned char y_pos;
+ unsigned char wheel;
+} report_t;
+
+static report_t in_report;
+
+static int uhid_write(int fd, const struct uhid_event *ev)
+{
+ ssize_t ret;
+
+ ret = write(fd, ev, sizeof(*ev));
+ if (ret < 0) {
+ fprintf(stderr, "cannot write to uhid: %m\n");
+ return -errno;
+ } else if (ret != sizeof(*ev)) {
+ fprintf(stderr, "wrong size written to uhid: %ld != %lu\n",
+ ret, sizeof(ev));
+ return -EFAULT;
+ } else {
+ return 0;
+ }
+}
+
+static int create(int fd)
+{
+ struct uhid_event ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_CREATE;
+ strcpy((char*)ev.u.create.name, "mr_woggle's HID device");
+ ev.u.create.rd_data = rdesc;
+ ev.u.create.rd_size = sizeof(rdesc);
+ ev.u.create.bus = BUS_BLUETOOTH;
+ ev.u.create.vendor = 0x1234;
+ ev.u.create.product = 0xabcd;
+ ev.u.create.version = 1;
+ ev.u.create.country = 0;
+
+ return uhid_write(fd, &ev);
+}
+
+static int send_event(int fd)
+{
+ struct uhid_event ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.type = UHID_INPUT;
+
+ /* move mouse two pixels to the right */
+ in_report.x_pos = 2;
+
+ ev.u.input.size = 5; /* includes report ID */
+ memcpy(ev.u.input.data, (uint8_t *) &in_report, sizeof(in_report));
+
+ return uhid_write(fd, &ev);
+}
+
+
+int main(int argc, char **argv)
+{
+ int fd;
+ const char *path = "/dev/uhid";
+ int ret;
+
+ if (argc >= 2) {
+ if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+ fprintf(stderr, "Usage: %s [%s]\n", argv[0], path);
+ return EXIT_SUCCESS;
+ } else {
+ path = argv[1];
+ }
+ }
+
+ fd = open(path, O_RDWR | O_CLOEXEC);
+ if (fd < 0) {
+ fprintf(stderr, "cannot open uhid-cdev (try as root?) %s: %m\n", path);
+ return EXIT_FAILURE;
+ }
+
+ ret = create(fd);
+ if (ret) {
+ close(fd);
+ fprintf(stderr, "xannot create uhid device");
+ return EXIT_FAILURE;
+ }
+
+ fprintf(stderr, "created virtual HID device\n");
+ fprintf(stderr, "ctrl-c to quit\n");
+
+ while (1) {
+ send_event(fd);
+ sleep(SLEEP_TIME_SEC);
+ }
+
+ /* ugly exit */
+}