1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * UHID Example |
4 | * |
5 | * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com> |
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 | |
12 | /* |
13 | * UHID Example |
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 | * |
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 | * |
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 | |
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: |
55 | * |
56 | * INPUT(1)[INPUT] |
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 ) |
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 ) |
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 |
104 | * LED.NumLock ---> LED.NumLock |
105 | * LED.CapsLock ---> LED.CapsLock |
106 | * LED.ScrollLock ---> LED.ScrollLock |
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[] = { |
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 */ |
157 | }; |
158 | |
159 | static int uhid_write(int fd, const struct uhid_event *ev) |
160 | { |
161 | ssize_t ret; |
162 | |
163 | ret = write(fd: fd, buf: ev, n: sizeof(*ev)); |
164 | if (ret < 0) { |
165 | fprintf(stderr, format: "Cannot write to uhid: %m\n" ); |
166 | return -errno; |
167 | } else if (ret != sizeof(*ev)) { |
168 | fprintf(stderr, format: "Wrong size written to uhid: %zd != %zu\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(s: &ev, c: 0, n: sizeof(ev)); |
181 | ev.type = UHID_CREATE; |
182 | strcpy(dest: (char*)ev.u.create.name, src: "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: &ev); |
192 | } |
193 | |
194 | static void destroy(int fd) |
195 | { |
196 | struct uhid_event ev; |
197 | |
198 | memset(s: &ev, c: 0, n: sizeof(ev)); |
199 | ev.type = UHID_DESTROY; |
200 | |
201 | uhid_write(fd, ev: &ev); |
202 | } |
203 | |
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, format: "LED output report received with flags %x\n" , |
222 | ev->u.output.data[1]); |
223 | } |
224 | |
225 | static int event(int fd) |
226 | { |
227 | struct uhid_event ev; |
228 | ssize_t ret; |
229 | |
230 | memset(s: &ev, c: 0, n: sizeof(ev)); |
231 | ret = read(fd: fd, buf: &ev, nbytes: sizeof(ev)); |
232 | if (ret == 0) { |
233 | fprintf(stderr, format: "Read HUP on uhid-cdev\n" ); |
234 | return -EFAULT; |
235 | } else if (ret < 0) { |
236 | fprintf(stderr, format: "Cannot read uhid-cdev: %m\n" ); |
237 | return -errno; |
238 | } else if (ret != sizeof(ev)) { |
239 | fprintf(stderr, format: "Invalid size read from uhid-dev: %zd != %zu\n" , |
240 | ret, sizeof(ev)); |
241 | return -EFAULT; |
242 | } |
243 | |
244 | switch (ev.type) { |
245 | case UHID_START: |
246 | fprintf(stderr, format: "UHID_START from uhid-dev\n" ); |
247 | break; |
248 | case UHID_STOP: |
249 | fprintf(stderr, format: "UHID_STOP from uhid-dev\n" ); |
250 | break; |
251 | case UHID_OPEN: |
252 | fprintf(stderr, format: "UHID_OPEN from uhid-dev\n" ); |
253 | break; |
254 | case UHID_CLOSE: |
255 | fprintf(stderr, format: "UHID_CLOSE from uhid-dev\n" ); |
256 | break; |
257 | case UHID_OUTPUT: |
258 | fprintf(stderr, format: "UHID_OUTPUT from uhid-dev\n" ); |
259 | handle_output(ev: &ev); |
260 | break; |
261 | case UHID_OUTPUT_EV: |
262 | fprintf(stderr, format: "UHID_OUTPUT_EV from uhid-dev\n" ); |
263 | break; |
264 | default: |
265 | fprintf(stderr, format: "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(s: &ev, c: 0, n: sizeof(ev)); |
283 | ev.type = UHID_INPUT; |
284 | ev.u.input.size = 5; |
285 | |
286 | ev.u.input.data[0] = 0x1; |
287 | if (btn1_down) |
288 | ev.u.input.data[1] |= 0x1; |
289 | if (btn2_down) |
290 | ev.u.input.data[1] |= 0x2; |
291 | if (btn3_down) |
292 | ev.u.input.data[1] |= 0x4; |
293 | |
294 | ev.u.input.data[2] = abs_hor; |
295 | ev.u.input.data[3] = abs_ver; |
296 | ev.u.input.data[4] = wheel; |
297 | |
298 | return uhid_write(fd, ev: &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: buf, nbytes: sizeof(buf)); |
307 | if (ret == 0) { |
308 | fprintf(stderr, format: "Read HUP on stdin\n" ); |
309 | return -EFAULT; |
310 | } else if (ret < 0) { |
311 | fprintf(stderr, format: "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, format: "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, termios_p: &state); |
396 | if (ret) { |
397 | fprintf(stderr, format: "Cannot get tty state\n" ); |
398 | } else { |
399 | state.c_lflag &= ~ICANON; |
400 | state.c_cc[VMIN] = 1; |
401 | ret = tcsetattr(STDIN_FILENO, TCSANOW, termios_p: &state); |
402 | if (ret) |
403 | fprintf(stderr, format: "Cannot set tty state\n" ); |
404 | } |
405 | |
406 | if (argc >= 2) { |
407 | if (!strcmp(s1: argv[1], s2: "-h" ) || !strcmp(s1: argv[1], s2: "--help" )) { |
408 | fprintf(stderr, format: "Usage: %s [%s]\n" , argv[0], path); |
409 | return EXIT_SUCCESS; |
410 | } else { |
411 | path = argv[1]; |
412 | } |
413 | } |
414 | |
415 | fprintf(stderr, format: "Open uhid-cdev %s\n" , path); |
416 | fd = open(file: path, O_RDWR | O_CLOEXEC); |
417 | if (fd < 0) { |
418 | fprintf(stderr, format: "Cannot open uhid-cdev %s: %m\n" , path); |
419 | return EXIT_FAILURE; |
420 | } |
421 | |
422 | fprintf(stderr, format: "Create uhid device\n" ); |
423 | ret = create(fd); |
424 | if (ret) { |
425 | close(fd: 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, format: "Press 'q' to quit...\n" ); |
435 | while (1) { |
436 | ret = poll(fds: pfds, nfds: 2, timeout: -1); |
437 | if (ret < 0) { |
438 | fprintf(stderr, format: "Cannot poll for fds: %m\n" ); |
439 | break; |
440 | } |
441 | if (pfds[0].revents & POLLHUP) { |
442 | fprintf(stderr, format: "Received HUP on stdin\n" ); |
443 | break; |
444 | } |
445 | if (pfds[1].revents & POLLHUP) { |
446 | fprintf(stderr, format: "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, format: "Destroy uhid device\n" ); |
463 | destroy(fd); |
464 | return EXIT_SUCCESS; |
465 | } |
466 | |