]>
Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
5148fa52 DH |
2 | /* |
3 | * UHID Example | |
4 | * | |
f5e4e7fd | 5 | * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com> |
5148fa52 DH |
6 | * |
7 | * The code may be used by anyone for any purpose, | |
8 | * and can serve as a starting point for developing | |
9 | * applications using uhid. | |
10 | */ | |
11 | ||
f5e4e7fd DH |
12 | /* |
13 | * UHID Example | |
5148fa52 DH |
14 | * This example emulates a basic 3 buttons mouse with wheel over UHID. Run this |
15 | * program as root and then use the following keys to control the mouse: | |
16 | * q: Quit the application | |
17 | * 1: Toggle left button (down, up, ...) | |
18 | * 2: Toggle right button | |
19 | * 3: Toggle middle button | |
20 | * a: Move mouse left | |
21 | * d: Move mouse right | |
22 | * w: Move mouse up | |
23 | * s: Move mouse down | |
24 | * r: Move wheel up | |
25 | * f: Move wheel down | |
26 | * | |
f5e4e7fd DH |
27 | * Additionally to 3 button mouse, 3 keyboard LEDs are also supported (LED_NUML, |
28 | * LED_CAPSL and LED_SCROLLL). The device doesn't generate any related keyboard | |
29 | * events, though. You need to manually write the EV_LED/LED_XY/1 activation | |
30 | * input event to the evdev device to see it being sent to this device. | |
31 | * | |
5148fa52 DH |
32 | * If uhid is not available as /dev/uhid, then you can pass a different path as |
33 | * first argument. | |
34 | * If <linux/uhid.h> is not installed in /usr, then compile this with: | |
35 | * gcc -o ./uhid_test -Wall -I./include ./samples/uhid/uhid-example.c | |
36 | * And ignore the warning about kernel headers. However, it is recommended to | |
37 | * use the installed uhid.h if available. | |
38 | */ | |
39 | ||
40 | #include <errno.h> | |
41 | #include <fcntl.h> | |
42 | #include <poll.h> | |
43 | #include <stdbool.h> | |
44 | #include <stdio.h> | |
45 | #include <stdlib.h> | |
46 | #include <string.h> | |
47 | #include <termios.h> | |
48 | #include <unistd.h> | |
49 | #include <linux/uhid.h> | |
50 | ||
f5e4e7fd DH |
51 | /* |
52 | * HID Report Desciptor | |
53 | * We emulate a basic 3 button mouse with wheel and 3 keyboard LEDs. This is | |
54 | * the report-descriptor as the kernel will parse it: | |
5148fa52 | 55 | * |
f5e4e7fd | 56 | * INPUT(1)[INPUT] |
5148fa52 DH |
57 | * Field(0) |
58 | * Physical(GenericDesktop.Pointer) | |
59 | * Application(GenericDesktop.Mouse) | |
60 | * Usage(3) | |
61 | * Button.0001 | |
62 | * Button.0002 | |
63 | * Button.0003 | |
64 | * Logical Minimum(0) | |
65 | * Logical Maximum(1) | |
66 | * Report Size(1) | |
67 | * Report Count(3) | |
68 | * Report Offset(0) | |
69 | * Flags( Variable Absolute ) | |
70 | * Field(1) | |
71 | * Physical(GenericDesktop.Pointer) | |
72 | * Application(GenericDesktop.Mouse) | |
73 | * Usage(3) | |
74 | * GenericDesktop.X | |
75 | * GenericDesktop.Y | |
76 | * GenericDesktop.Wheel | |
77 | * Logical Minimum(-128) | |
78 | * Logical Maximum(127) | |
79 | * Report Size(8) | |
80 | * Report Count(3) | |
81 | * Report Offset(8) | |
82 | * Flags( Variable Relative ) | |
f5e4e7fd DH |
83 | * OUTPUT(2)[OUTPUT] |
84 | * Field(0) | |
85 | * Application(GenericDesktop.Keyboard) | |
86 | * Usage(3) | |
87 | * LED.NumLock | |
88 | * LED.CapsLock | |
89 | * LED.ScrollLock | |
90 | * Logical Minimum(0) | |
91 | * Logical Maximum(1) | |
92 | * Report Size(1) | |
93 | * Report Count(3) | |
94 | * Report Offset(0) | |
95 | * Flags( Variable Absolute ) | |
5148fa52 DH |
96 | * |
97 | * This is the mapping that we expect: | |
98 | * Button.0001 ---> Key.LeftBtn | |
99 | * Button.0002 ---> Key.RightBtn | |
100 | * Button.0003 ---> Key.MiddleBtn | |
101 | * GenericDesktop.X ---> Relative.X | |
102 | * GenericDesktop.Y ---> Relative.Y | |
103 | * GenericDesktop.Wheel ---> Relative.Wheel | |
f5e4e7fd DH |
104 | * LED.NumLock ---> LED.NumLock |
105 | * LED.CapsLock ---> LED.CapsLock | |
106 | * LED.ScrollLock ---> LED.ScrollLock | |
5148fa52 DH |
107 | * |
108 | * This information can be verified by reading /sys/kernel/debug/hid/<dev>/rdesc | |
109 | * This file should print the same information as showed above. | |
110 | */ | |
111 | ||
112 | static unsigned char rdesc[] = { | |
f5e4e7fd DH |
113 | 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ |
114 | 0x09, 0x02, /* USAGE (Mouse) */ | |
115 | 0xa1, 0x01, /* COLLECTION (Application) */ | |
116 | 0x09, 0x01, /* USAGE (Pointer) */ | |
117 | 0xa1, 0x00, /* COLLECTION (Physical) */ | |
118 | 0x85, 0x01, /* REPORT_ID (1) */ | |
119 | 0x05, 0x09, /* USAGE_PAGE (Button) */ | |
120 | 0x19, 0x01, /* USAGE_MINIMUM (Button 1) */ | |
121 | 0x29, 0x03, /* USAGE_MAXIMUM (Button 3) */ | |
122 | 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ | |
123 | 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ | |
124 | 0x95, 0x03, /* REPORT_COUNT (3) */ | |
125 | 0x75, 0x01, /* REPORT_SIZE (1) */ | |
126 | 0x81, 0x02, /* INPUT (Data,Var,Abs) */ | |
127 | 0x95, 0x01, /* REPORT_COUNT (1) */ | |
128 | 0x75, 0x05, /* REPORT_SIZE (5) */ | |
129 | 0x81, 0x01, /* INPUT (Cnst,Var,Abs) */ | |
130 | 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ | |
131 | 0x09, 0x30, /* USAGE (X) */ | |
132 | 0x09, 0x31, /* USAGE (Y) */ | |
133 | 0x09, 0x38, /* USAGE (WHEEL) */ | |
134 | 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */ | |
135 | 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */ | |
136 | 0x75, 0x08, /* REPORT_SIZE (8) */ | |
137 | 0x95, 0x03, /* REPORT_COUNT (3) */ | |
138 | 0x81, 0x06, /* INPUT (Data,Var,Rel) */ | |
139 | 0xc0, /* END_COLLECTION */ | |
140 | 0xc0, /* END_COLLECTION */ | |
141 | 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ | |
142 | 0x09, 0x06, /* USAGE (Keyboard) */ | |
143 | 0xa1, 0x01, /* COLLECTION (Application) */ | |
144 | 0x85, 0x02, /* REPORT_ID (2) */ | |
145 | 0x05, 0x08, /* USAGE_PAGE (Led) */ | |
146 | 0x19, 0x01, /* USAGE_MINIMUM (1) */ | |
147 | 0x29, 0x03, /* USAGE_MAXIMUM (3) */ | |
148 | 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ | |
149 | 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ | |
150 | 0x95, 0x03, /* REPORT_COUNT (3) */ | |
151 | 0x75, 0x01, /* REPORT_SIZE (1) */ | |
152 | 0x91, 0x02, /* Output (Data,Var,Abs) */ | |
153 | 0x95, 0x01, /* REPORT_COUNT (1) */ | |
154 | 0x75, 0x05, /* REPORT_SIZE (5) */ | |
155 | 0x91, 0x01, /* Output (Cnst,Var,Abs) */ | |
156 | 0xc0, /* END_COLLECTION */ | |
5148fa52 DH |
157 | }; |
158 | ||
159 | static int uhid_write(int fd, const struct uhid_event *ev) | |
160 | { | |
161 | ssize_t ret; | |
162 | ||
163 | ret = write(fd, ev, sizeof(*ev)); | |
164 | if (ret < 0) { | |
165 | fprintf(stderr, "Cannot write to uhid: %m\n"); | |
166 | return -errno; | |
167 | } else if (ret != sizeof(*ev)) { | |
168 | fprintf(stderr, "Wrong size written to uhid: %ld != %lu\n", | |
169 | ret, sizeof(ev)); | |
170 | return -EFAULT; | |
171 | } else { | |
172 | return 0; | |
173 | } | |
174 | } | |
175 | ||
176 | static int create(int fd) | |
177 | { | |
178 | struct uhid_event ev; | |
179 | ||
180 | memset(&ev, 0, sizeof(ev)); | |
181 | ev.type = UHID_CREATE; | |
182 | strcpy((char*)ev.u.create.name, "test-uhid-device"); | |
183 | ev.u.create.rd_data = rdesc; | |
184 | ev.u.create.rd_size = sizeof(rdesc); | |
185 | ev.u.create.bus = BUS_USB; | |
186 | ev.u.create.vendor = 0x15d9; | |
187 | ev.u.create.product = 0x0a37; | |
188 | ev.u.create.version = 0; | |
189 | ev.u.create.country = 0; | |
190 | ||
191 | return uhid_write(fd, &ev); | |
192 | } | |
193 | ||
194 | static void destroy(int fd) | |
195 | { | |
196 | struct uhid_event ev; | |
197 | ||
198 | memset(&ev, 0, sizeof(ev)); | |
199 | ev.type = UHID_DESTROY; | |
200 | ||
201 | uhid_write(fd, &ev); | |
202 | } | |
203 | ||
f5e4e7fd DH |
204 | /* This parses raw output reports sent by the kernel to the device. A normal |
205 | * uhid program shouldn't do this but instead just forward the raw report. | |
206 | * However, for ducomentational purposes, we try to detect LED events here and | |
207 | * print debug messages for it. */ | |
208 | static void handle_output(struct uhid_event *ev) | |
209 | { | |
210 | /* LED messages are adverised via OUTPUT reports; ignore the rest */ | |
211 | if (ev->u.output.rtype != UHID_OUTPUT_REPORT) | |
212 | return; | |
213 | /* LED reports have length 2 bytes */ | |
214 | if (ev->u.output.size != 2) | |
215 | return; | |
216 | /* first byte is report-id which is 0x02 for LEDs in our rdesc */ | |
217 | if (ev->u.output.data[0] != 0x2) | |
218 | return; | |
219 | ||
220 | /* print flags payload */ | |
221 | fprintf(stderr, "LED output report received with flags %x\n", | |
222 | ev->u.output.data[1]); | |
223 | } | |
224 | ||
5148fa52 DH |
225 | static int event(int fd) |
226 | { | |
227 | struct uhid_event ev; | |
228 | ssize_t ret; | |
229 | ||
230 | memset(&ev, 0, sizeof(ev)); | |
231 | ret = read(fd, &ev, sizeof(ev)); | |
232 | if (ret == 0) { | |
233 | fprintf(stderr, "Read HUP on uhid-cdev\n"); | |
234 | return -EFAULT; | |
235 | } else if (ret < 0) { | |
236 | fprintf(stderr, "Cannot read uhid-cdev: %m\n"); | |
237 | return -errno; | |
238 | } else if (ret != sizeof(ev)) { | |
239 | fprintf(stderr, "Invalid size read from uhid-dev: %ld != %lu\n", | |
240 | ret, sizeof(ev)); | |
241 | return -EFAULT; | |
242 | } | |
243 | ||
244 | switch (ev.type) { | |
245 | case UHID_START: | |
246 | fprintf(stderr, "UHID_START from uhid-dev\n"); | |
247 | break; | |
248 | case UHID_STOP: | |
249 | fprintf(stderr, "UHID_STOP from uhid-dev\n"); | |
250 | break; | |
251 | case UHID_OPEN: | |
252 | fprintf(stderr, "UHID_OPEN from uhid-dev\n"); | |
253 | break; | |
254 | case UHID_CLOSE: | |
255 | fprintf(stderr, "UHID_CLOSE from uhid-dev\n"); | |
256 | break; | |
257 | case UHID_OUTPUT: | |
258 | fprintf(stderr, "UHID_OUTPUT from uhid-dev\n"); | |
f5e4e7fd | 259 | handle_output(&ev); |
5148fa52 DH |
260 | break; |
261 | case UHID_OUTPUT_EV: | |
262 | fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n"); | |
263 | break; | |
264 | default: | |
265 | fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type); | |
266 | } | |
267 | ||
268 | return 0; | |
269 | } | |
270 | ||
271 | static bool btn1_down; | |
272 | static bool btn2_down; | |
273 | static bool btn3_down; | |
274 | static signed char abs_hor; | |
275 | static signed char abs_ver; | |
276 | static signed char wheel; | |
277 | ||
278 | static int send_event(int fd) | |
279 | { | |
280 | struct uhid_event ev; | |
281 | ||
282 | memset(&ev, 0, sizeof(ev)); | |
283 | ev.type = UHID_INPUT; | |
f5e4e7fd | 284 | ev.u.input.size = 5; |
5148fa52 | 285 | |
f5e4e7fd | 286 | ev.u.input.data[0] = 0x1; |
5148fa52 | 287 | if (btn1_down) |
f5e4e7fd | 288 | ev.u.input.data[1] |= 0x1; |
5148fa52 | 289 | if (btn2_down) |
f5e4e7fd | 290 | ev.u.input.data[1] |= 0x2; |
5148fa52 | 291 | if (btn3_down) |
f5e4e7fd | 292 | ev.u.input.data[1] |= 0x4; |
5148fa52 | 293 | |
f5e4e7fd DH |
294 | ev.u.input.data[2] = abs_hor; |
295 | ev.u.input.data[3] = abs_ver; | |
296 | ev.u.input.data[4] = wheel; | |
5148fa52 DH |
297 | |
298 | return uhid_write(fd, &ev); | |
299 | } | |
300 | ||
301 | static int keyboard(int fd) | |
302 | { | |
303 | char buf[128]; | |
304 | ssize_t ret, i; | |
305 | ||
306 | ret = read(STDIN_FILENO, buf, sizeof(buf)); | |
307 | if (ret == 0) { | |
308 | fprintf(stderr, "Read HUP on stdin\n"); | |
309 | return -EFAULT; | |
310 | } else if (ret < 0) { | |
311 | fprintf(stderr, "Cannot read stdin: %m\n"); | |
312 | return -errno; | |
313 | } | |
314 | ||
315 | for (i = 0; i < ret; ++i) { | |
316 | switch (buf[i]) { | |
317 | case '1': | |
318 | btn1_down = !btn1_down; | |
319 | ret = send_event(fd); | |
320 | if (ret) | |
321 | return ret; | |
322 | break; | |
323 | case '2': | |
324 | btn2_down = !btn2_down; | |
325 | ret = send_event(fd); | |
326 | if (ret) | |
327 | return ret; | |
328 | break; | |
329 | case '3': | |
330 | btn3_down = !btn3_down; | |
331 | ret = send_event(fd); | |
332 | if (ret) | |
333 | return ret; | |
334 | break; | |
335 | case 'a': | |
336 | abs_hor = -20; | |
337 | ret = send_event(fd); | |
338 | abs_hor = 0; | |
339 | if (ret) | |
340 | return ret; | |
341 | break; | |
342 | case 'd': | |
343 | abs_hor = 20; | |
344 | ret = send_event(fd); | |
345 | abs_hor = 0; | |
346 | if (ret) | |
347 | return ret; | |
348 | break; | |
349 | case 'w': | |
350 | abs_ver = -20; | |
351 | ret = send_event(fd); | |
352 | abs_ver = 0; | |
353 | if (ret) | |
354 | return ret; | |
355 | break; | |
356 | case 's': | |
357 | abs_ver = 20; | |
358 | ret = send_event(fd); | |
359 | abs_ver = 0; | |
360 | if (ret) | |
361 | return ret; | |
362 | break; | |
363 | case 'r': | |
364 | wheel = 1; | |
365 | ret = send_event(fd); | |
366 | wheel = 0; | |
367 | if (ret) | |
368 | return ret; | |
369 | break; | |
370 | case 'f': | |
371 | wheel = -1; | |
372 | ret = send_event(fd); | |
373 | wheel = 0; | |
374 | if (ret) | |
375 | return ret; | |
376 | break; | |
377 | case 'q': | |
378 | return -ECANCELED; | |
379 | default: | |
380 | fprintf(stderr, "Invalid input: %c\n", buf[i]); | |
381 | } | |
382 | } | |
383 | ||
384 | return 0; | |
385 | } | |
386 | ||
387 | int main(int argc, char **argv) | |
388 | { | |
389 | int fd; | |
390 | const char *path = "/dev/uhid"; | |
391 | struct pollfd pfds[2]; | |
392 | int ret; | |
393 | struct termios state; | |
394 | ||
395 | ret = tcgetattr(STDIN_FILENO, &state); | |
396 | if (ret) { | |
397 | fprintf(stderr, "Cannot get tty state\n"); | |
398 | } else { | |
399 | state.c_lflag &= ~ICANON; | |
400 | state.c_cc[VMIN] = 1; | |
401 | ret = tcsetattr(STDIN_FILENO, TCSANOW, &state); | |
402 | if (ret) | |
403 | fprintf(stderr, "Cannot set tty state\n"); | |
404 | } | |
405 | ||
406 | if (argc >= 2) { | |
407 | if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) { | |
408 | fprintf(stderr, "Usage: %s [%s]\n", argv[0], path); | |
409 | return EXIT_SUCCESS; | |
410 | } else { | |
411 | path = argv[1]; | |
412 | } | |
413 | } | |
414 | ||
415 | fprintf(stderr, "Open uhid-cdev %s\n", path); | |
416 | fd = open(path, O_RDWR | O_CLOEXEC); | |
417 | if (fd < 0) { | |
418 | fprintf(stderr, "Cannot open uhid-cdev %s: %m\n", path); | |
419 | return EXIT_FAILURE; | |
420 | } | |
421 | ||
422 | fprintf(stderr, "Create uhid device\n"); | |
423 | ret = create(fd); | |
424 | if (ret) { | |
425 | close(fd); | |
426 | return EXIT_FAILURE; | |
427 | } | |
428 | ||
429 | pfds[0].fd = STDIN_FILENO; | |
430 | pfds[0].events = POLLIN; | |
431 | pfds[1].fd = fd; | |
432 | pfds[1].events = POLLIN; | |
433 | ||
434 | fprintf(stderr, "Press 'q' to quit...\n"); | |
435 | while (1) { | |
436 | ret = poll(pfds, 2, -1); | |
437 | if (ret < 0) { | |
438 | fprintf(stderr, "Cannot poll for fds: %m\n"); | |
439 | break; | |
440 | } | |
441 | if (pfds[0].revents & POLLHUP) { | |
442 | fprintf(stderr, "Received HUP on stdin\n"); | |
443 | break; | |
444 | } | |
445 | if (pfds[1].revents & POLLHUP) { | |
446 | fprintf(stderr, "Received HUP on uhid-cdev\n"); | |
447 | break; | |
448 | } | |
449 | ||
450 | if (pfds[0].revents & POLLIN) { | |
451 | ret = keyboard(fd); | |
452 | if (ret) | |
453 | break; | |
454 | } | |
455 | if (pfds[1].revents & POLLIN) { | |
456 | ret = event(fd); | |
457 | if (ret) | |
458 | break; | |
459 | } | |
460 | } | |
461 | ||
462 | fprintf(stderr, "Destroy uhid device\n"); | |
463 | destroy(fd); | |
464 | return EXIT_SUCCESS; | |
465 | } |