| 1 | |
| 2 | |
| 3 | * UHID Example
|
| 4 | *
|
| 5 | * Copyright (c) 2012-2013 David Herrmann <[email protected]>
|
| 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,
|
| 114 | 0x09, 0x02,
|
| 115 | 0xa1, 0x01,
|
| 116 | 0x09, 0x01,
|
| 117 | 0xa1, 0x00,
|
| 118 | 0x85, 0x01,
|
| 119 | 0x05, 0x09,
|
| 120 | 0x19, 0x01,
|
| 121 | 0x29, 0x03,
|
| 122 | 0x15, 0x00,
|
| 123 | 0x25, 0x01,
|
| 124 | 0x95, 0x03,
|
| 125 | 0x75, 0x01,
|
| 126 | 0x81, 0x02,
|
| 127 | 0x95, 0x01,
|
| 128 | 0x75, 0x05,
|
| 129 | 0x81, 0x01,
|
| 130 | 0x05, 0x01,
|
| 131 | 0x09, 0x30,
|
| 132 | 0x09, 0x31,
|
| 133 | 0x09, 0x38,
|
| 134 | 0x15, 0x81,
|
| 135 | 0x25, 0x7f,
|
| 136 | 0x75, 0x08,
|
| 137 | 0x95, 0x03,
|
| 138 | 0x81, 0x06,
|
| 139 | 0xc0,
|
| 140 | 0xc0,
|
| 141 | 0x05, 0x01,
|
| 142 | 0x09, 0x06,
|
| 143 | 0xa1, 0x01,
|
| 144 | 0x85, 0x02,
|
| 145 | 0x05, 0x08,
|
| 146 | 0x19, 0x01,
|
| 147 | 0x29, 0x03,
|
| 148 | 0x15, 0x00,
|
| 149 | 0x25, 0x01,
|
| 150 | 0x95, 0x03,
|
| 151 | 0x75, 0x01,
|
| 152 | 0x91, 0x02,
|
| 153 | 0x95, 0x01,
|
| 154 | 0x75, 0x05,
|
| 155 | 0x91, 0x01,
|
| 156 | 0xc0,
|
| 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: %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(&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 |
|
| 204 | |
| 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 |
|
| 211 | if (ev->u.output.rtype != UHID_OUTPUT_REPORT)
|
| 212 | return;
|
| 213 |
|
| 214 | if (ev->u.output.size != 2)
|
| 215 | return;
|
| 216 |
|
| 217 | if (ev->u.output.data[0] != 0x2)
|
| 218 | return;
|
| 219 |
|
| 220 |
|
| 221 | fprintf(stderr, "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(&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: %zd != %zu\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");
|
| 259 | handle_output(&ev);
|
| 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;
|
| 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);
|
| 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 | }
|