Commit | Line | Data |
---|---|---|
5148fa52 DH |
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 | } |