Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

HID: uhid: add example program

This adds an example user-space program that emulates a 3 button mouse
with wheel. It detects keyboard presses and moves the mouse accordingly.

It register a fake HID device to feed the raw HID reports into the kernel.
In this example, you could use uinput to get the same result, but this
shows how to get the same behavior with uhid so you don't need HID parsers
in user-space.

Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
  • Loading branch information...
commit 5148fa52a12fa1b97c730b2fe321f2aad7ea041c 1 parent d99b8ba
David Herrmann authored June 10, 2012 Jiri Kosina committed June 18, 2012
10  samples/uhid/Makefile
... ...
@@ -0,0 +1,10 @@
  1
+# kbuild trick to avoid linker error. Can be omitted if a module is built.
  2
+obj- := dummy.o
  3
+
  4
+# List of programs to build
  5
+hostprogs-y := uhid-example
  6
+
  7
+# Tell kbuild to always build the programs
  8
+always := $(hostprogs-y)
  9
+
  10
+HOSTCFLAGS_uhid-example.o += -I$(objtree)/usr/include
381  samples/uhid/uhid-example.c
... ...
@@ -0,0 +1,381 @@
  1
+/*
  2
+ * UHID Example
  3
+ *
  4
+ * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
  5
+ *
  6
+ * The code may be used by anyone for any purpose,
  7
+ * and can serve as a starting point for developing
  8
+ * applications using uhid.
  9
+ */
  10
+
  11
+/* UHID Example
  12
+ * This example emulates a basic 3 buttons mouse with wheel over UHID. Run this
  13
+ * program as root and then use the following keys to control the mouse:
  14
+ *   q: Quit the application
  15
+ *   1: Toggle left button (down, up, ...)
  16
+ *   2: Toggle right button
  17
+ *   3: Toggle middle button
  18
+ *   a: Move mouse left
  19
+ *   d: Move mouse right
  20
+ *   w: Move mouse up
  21
+ *   s: Move mouse down
  22
+ *   r: Move wheel up
  23
+ *   f: Move wheel down
  24
+ *
  25
+ * If uhid is not available as /dev/uhid, then you can pass a different path as
  26
+ * first argument.
  27
+ * If <linux/uhid.h> is not installed in /usr, then compile this with:
  28
+ *   gcc -o ./uhid_test -Wall -I./include ./samples/uhid/uhid-example.c
  29
+ * And ignore the warning about kernel headers. However, it is recommended to
  30
+ * use the installed uhid.h if available.
  31
+ */
  32
+
  33
+#include <errno.h>
  34
+#include <fcntl.h>
  35
+#include <poll.h>
  36
+#include <stdbool.h>
  37
+#include <stdio.h>
  38
+#include <stdlib.h>
  39
+#include <string.h>
  40
+#include <termios.h>
  41
+#include <unistd.h>
  42
+#include <linux/uhid.h>
  43
+
  44
+/* HID Report Desciptor
  45
+ * We emulate a basic 3 button mouse with wheel. This is the report-descriptor
  46
+ * as the kernel will parse it:
  47
+ *
  48
+ * INPUT[INPUT]
  49
+ *   Field(0)
  50
+ *     Physical(GenericDesktop.Pointer)
  51
+ *     Application(GenericDesktop.Mouse)
  52
+ *     Usage(3)
  53
+ *       Button.0001
  54
+ *       Button.0002
  55
+ *       Button.0003
  56
+ *     Logical Minimum(0)
  57
+ *     Logical Maximum(1)
  58
+ *     Report Size(1)
  59
+ *     Report Count(3)
  60
+ *     Report Offset(0)
  61
+ *     Flags( Variable Absolute )
  62
+ *   Field(1)
  63
+ *     Physical(GenericDesktop.Pointer)
  64
+ *     Application(GenericDesktop.Mouse)
  65
+ *     Usage(3)
  66
+ *       GenericDesktop.X
  67
+ *       GenericDesktop.Y
  68
+ *       GenericDesktop.Wheel
  69
+ *     Logical Minimum(-128)
  70
+ *     Logical Maximum(127)
  71
+ *     Report Size(8)
  72
+ *     Report Count(3)
  73
+ *     Report Offset(8)
  74
+ *     Flags( Variable Relative )
  75
+ *
  76
+ * This is the mapping that we expect:
  77
+ *   Button.0001 ---> Key.LeftBtn
  78
+ *   Button.0002 ---> Key.RightBtn
  79
+ *   Button.0003 ---> Key.MiddleBtn
  80
+ *   GenericDesktop.X ---> Relative.X
  81
+ *   GenericDesktop.Y ---> Relative.Y
  82
+ *   GenericDesktop.Wheel ---> Relative.Wheel
  83
+ *
  84
+ * This information can be verified by reading /sys/kernel/debug/hid/<dev>/rdesc
  85
+ * This file should print the same information as showed above.
  86
+ */
  87
+
  88
+static unsigned char rdesc[] = {
  89
+	0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01,
  90
+	0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03,
  91
+	0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01,
  92
+	0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81, 0x01,
  93
+	0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38,
  94
+	0x15, 0x80, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x03,
  95
+	0x81, 0x06, 0xc0, 0xc0,
  96
+};
  97
+
  98
+static int uhid_write(int fd, const struct uhid_event *ev)
  99
+{
  100
+	ssize_t ret;
  101
+
  102
+	ret = write(fd, ev, sizeof(*ev));
  103
+	if (ret < 0) {
  104
+		fprintf(stderr, "Cannot write to uhid: %m\n");
  105
+		return -errno;
  106
+	} else if (ret != sizeof(*ev)) {
  107
+		fprintf(stderr, "Wrong size written to uhid: %ld != %lu\n",
  108
+			ret, sizeof(ev));
  109
+		return -EFAULT;
  110
+	} else {
  111
+		return 0;
  112
+	}
  113
+}
  114
+
  115
+static int create(int fd)
  116
+{
  117
+	struct uhid_event ev;
  118
+
  119
+	memset(&ev, 0, sizeof(ev));
  120
+	ev.type = UHID_CREATE;
  121
+	strcpy((char*)ev.u.create.name, "test-uhid-device");
  122
+	ev.u.create.rd_data = rdesc;
  123
+	ev.u.create.rd_size = sizeof(rdesc);
  124
+	ev.u.create.bus = BUS_USB;
  125
+	ev.u.create.vendor = 0x15d9;
  126
+	ev.u.create.product = 0x0a37;
  127
+	ev.u.create.version = 0;
  128
+	ev.u.create.country = 0;
  129
+
  130
+	return uhid_write(fd, &ev);
  131
+}
  132
+
  133
+static void destroy(int fd)
  134
+{
  135
+	struct uhid_event ev;
  136
+
  137
+	memset(&ev, 0, sizeof(ev));
  138
+	ev.type = UHID_DESTROY;
  139
+
  140
+	uhid_write(fd, &ev);
  141
+}
  142
+
  143
+static int event(int fd)
  144
+{
  145
+	struct uhid_event ev;
  146
+	ssize_t ret;
  147
+
  148
+	memset(&ev, 0, sizeof(ev));
  149
+	ret = read(fd, &ev, sizeof(ev));
  150
+	if (ret == 0) {
  151
+		fprintf(stderr, "Read HUP on uhid-cdev\n");
  152
+		return -EFAULT;
  153
+	} else if (ret < 0) {
  154
+		fprintf(stderr, "Cannot read uhid-cdev: %m\n");
  155
+		return -errno;
  156
+	} else if (ret != sizeof(ev)) {
  157
+		fprintf(stderr, "Invalid size read from uhid-dev: %ld != %lu\n",
  158
+			ret, sizeof(ev));
  159
+		return -EFAULT;
  160
+	}
  161
+
  162
+	switch (ev.type) {
  163
+	case UHID_START:
  164
+		fprintf(stderr, "UHID_START from uhid-dev\n");
  165
+		break;
  166
+	case UHID_STOP:
  167
+		fprintf(stderr, "UHID_STOP from uhid-dev\n");
  168
+		break;
  169
+	case UHID_OPEN:
  170
+		fprintf(stderr, "UHID_OPEN from uhid-dev\n");
  171
+		break;
  172
+	case UHID_CLOSE:
  173
+		fprintf(stderr, "UHID_CLOSE from uhid-dev\n");
  174
+		break;
  175
+	case UHID_OUTPUT:
  176
+		fprintf(stderr, "UHID_OUTPUT from uhid-dev\n");
  177
+		break;
  178
+	case UHID_OUTPUT_EV:
  179
+		fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n");
  180
+		break;
  181
+	default:
  182
+		fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type);
  183
+	}
  184
+
  185
+	return 0;
  186
+}
  187
+
  188
+static bool btn1_down;
  189
+static bool btn2_down;
  190
+static bool btn3_down;
  191
+static signed char abs_hor;
  192
+static signed char abs_ver;
  193
+static signed char wheel;
  194
+
  195
+static int send_event(int fd)
  196
+{
  197
+	struct uhid_event ev;
  198
+
  199
+	memset(&ev, 0, sizeof(ev));
  200
+	ev.type = UHID_INPUT;
  201
+	ev.u.input.size = 4;
  202
+
  203
+	if (btn1_down)
  204
+		ev.u.input.data[0] |= 0x1;
  205
+	if (btn2_down)
  206
+		ev.u.input.data[0] |= 0x2;
  207
+	if (btn3_down)
  208
+		ev.u.input.data[0] |= 0x4;
  209
+
  210
+	ev.u.input.data[1] = abs_hor;
  211
+	ev.u.input.data[2] = abs_ver;
  212
+	ev.u.input.data[3] = wheel;
  213
+
  214
+	return uhid_write(fd, &ev);
  215
+}
  216
+
  217
+static int keyboard(int fd)
  218
+{
  219
+	char buf[128];
  220
+	ssize_t ret, i;
  221
+
  222
+	ret = read(STDIN_FILENO, buf, sizeof(buf));
  223
+	if (ret == 0) {
  224
+		fprintf(stderr, "Read HUP on stdin\n");
  225
+		return -EFAULT;
  226
+	} else if (ret < 0) {
  227
+		fprintf(stderr, "Cannot read stdin: %m\n");
  228
+		return -errno;
  229
+	}
  230
+
  231
+	for (i = 0; i < ret; ++i) {
  232
+		switch (buf[i]) {
  233
+		case '1':
  234
+			btn1_down = !btn1_down;
  235
+			ret = send_event(fd);
  236
+			if (ret)
  237
+				return ret;
  238
+			break;
  239
+		case '2':
  240
+			btn2_down = !btn2_down;
  241
+			ret = send_event(fd);
  242
+			if (ret)
  243
+				return ret;
  244
+			break;
  245
+		case '3':
  246
+			btn3_down = !btn3_down;
  247
+			ret = send_event(fd);
  248
+			if (ret)
  249
+				return ret;
  250
+			break;
  251
+		case 'a':
  252
+			abs_hor = -20;
  253
+			ret = send_event(fd);
  254
+			abs_hor = 0;
  255
+			if (ret)
  256
+				return ret;
  257
+			break;
  258
+		case 'd':
  259
+			abs_hor = 20;
  260
+			ret = send_event(fd);
  261
+			abs_hor = 0;
  262
+			if (ret)
  263
+				return ret;
  264
+			break;
  265
+		case 'w':
  266
+			abs_ver = -20;
  267
+			ret = send_event(fd);
  268
+			abs_ver = 0;
  269
+			if (ret)
  270
+				return ret;
  271
+			break;
  272
+		case 's':
  273
+			abs_ver = 20;
  274
+			ret = send_event(fd);
  275
+			abs_ver = 0;
  276
+			if (ret)
  277
+				return ret;
  278
+			break;
  279
+		case 'r':
  280
+			wheel = 1;
  281
+			ret = send_event(fd);
  282
+			wheel = 0;
  283
+			if (ret)
  284
+				return ret;
  285
+			break;
  286
+		case 'f':
  287
+			wheel = -1;
  288
+			ret = send_event(fd);
  289
+			wheel = 0;
  290
+			if (ret)
  291
+				return ret;
  292
+			break;
  293
+		case 'q':
  294
+			return -ECANCELED;
  295
+		default:
  296
+			fprintf(stderr, "Invalid input: %c\n", buf[i]);
  297
+		}
  298
+	}
  299
+
  300
+	return 0;
  301
+}
  302
+
  303
+int main(int argc, char **argv)
  304
+{
  305
+	int fd;
  306
+	const char *path = "/dev/uhid";
  307
+	struct pollfd pfds[2];
  308
+	int ret;
  309
+	struct termios state;
  310
+
  311
+	ret = tcgetattr(STDIN_FILENO, &state);
  312
+	if (ret) {
  313
+		fprintf(stderr, "Cannot get tty state\n");
  314
+	} else {
  315
+		state.c_lflag &= ~ICANON;
  316
+		state.c_cc[VMIN] = 1;
  317
+		ret = tcsetattr(STDIN_FILENO, TCSANOW, &state);
  318
+		if (ret)
  319
+			fprintf(stderr, "Cannot set tty state\n");
  320
+	}
  321
+
  322
+	if (argc >= 2) {
  323
+		if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
  324
+			fprintf(stderr, "Usage: %s [%s]\n", argv[0], path);
  325
+			return EXIT_SUCCESS;
  326
+		} else {
  327
+			path = argv[1];
  328
+		}
  329
+	}
  330
+
  331
+	fprintf(stderr, "Open uhid-cdev %s\n", path);
  332
+	fd = open(path, O_RDWR | O_CLOEXEC);
  333
+	if (fd < 0) {
  334
+		fprintf(stderr, "Cannot open uhid-cdev %s: %m\n", path);
  335
+		return EXIT_FAILURE;
  336
+	}
  337
+
  338
+	fprintf(stderr, "Create uhid device\n");
  339
+	ret = create(fd);
  340
+	if (ret) {
  341
+		close(fd);
  342
+		return EXIT_FAILURE;
  343
+	}
  344
+
  345
+	pfds[0].fd = STDIN_FILENO;
  346
+	pfds[0].events = POLLIN;
  347
+	pfds[1].fd = fd;
  348
+	pfds[1].events = POLLIN;
  349
+
  350
+	fprintf(stderr, "Press 'q' to quit...\n");
  351
+	while (1) {
  352
+		ret = poll(pfds, 2, -1);
  353
+		if (ret < 0) {
  354
+			fprintf(stderr, "Cannot poll for fds: %m\n");
  355
+			break;
  356
+		}
  357
+		if (pfds[0].revents & POLLHUP) {
  358
+			fprintf(stderr, "Received HUP on stdin\n");
  359
+			break;
  360
+		}
  361
+		if (pfds[1].revents & POLLHUP) {
  362
+			fprintf(stderr, "Received HUP on uhid-cdev\n");
  363
+			break;
  364
+		}
  365
+
  366
+		if (pfds[0].revents & POLLIN) {
  367
+			ret = keyboard(fd);
  368
+			if (ret)
  369
+				break;
  370
+		}
  371
+		if (pfds[1].revents & POLLIN) {
  372
+			ret = event(fd);
  373
+			if (ret)
  374
+				break;
  375
+		}
  376
+	}
  377
+
  378
+	fprintf(stderr, "Destroy uhid device\n");
  379
+	destroy(fd);
  380
+	return EXIT_SUCCESS;
  381
+}

0 notes on commit 5148fa5

Please sign in to comment.
Something went wrong with that request. Please try again.