mirror of
https://github.com/torvalds/linux.git
synced 2024-11-23 12:42:02 +00:00
5148fa52a1
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>
382 lines
8.1 KiB
C
382 lines
8.1 KiB
C
/*
|
|
* UHID Example
|
|
*
|
|
* Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
|
|
*
|
|
* The code may be used by anyone for any purpose,
|
|
* and can serve as a starting point for developing
|
|
* applications using uhid.
|
|
*/
|
|
|
|
/* UHID Example
|
|
* This example emulates a basic 3 buttons mouse with wheel over UHID. Run this
|
|
* program as root and then use the following keys to control the mouse:
|
|
* q: Quit the application
|
|
* 1: Toggle left button (down, up, ...)
|
|
* 2: Toggle right button
|
|
* 3: Toggle middle button
|
|
* a: Move mouse left
|
|
* d: Move mouse right
|
|
* w: Move mouse up
|
|
* s: Move mouse down
|
|
* r: Move wheel up
|
|
* f: Move wheel down
|
|
*
|
|
* If uhid is not available as /dev/uhid, then you can pass a different path as
|
|
* first argument.
|
|
* If <linux/uhid.h> is not installed in /usr, then compile this with:
|
|
* gcc -o ./uhid_test -Wall -I./include ./samples/uhid/uhid-example.c
|
|
* And ignore the warning about kernel headers. However, it is recommended to
|
|
* use the installed uhid.h if available.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
#include <linux/uhid.h>
|
|
|
|
/* HID Report Desciptor
|
|
* We emulate a basic 3 button mouse with wheel. This is the report-descriptor
|
|
* as the kernel will parse it:
|
|
*
|
|
* INPUT[INPUT]
|
|
* Field(0)
|
|
* Physical(GenericDesktop.Pointer)
|
|
* Application(GenericDesktop.Mouse)
|
|
* Usage(3)
|
|
* Button.0001
|
|
* Button.0002
|
|
* Button.0003
|
|
* Logical Minimum(0)
|
|
* Logical Maximum(1)
|
|
* Report Size(1)
|
|
* Report Count(3)
|
|
* Report Offset(0)
|
|
* Flags( Variable Absolute )
|
|
* Field(1)
|
|
* Physical(GenericDesktop.Pointer)
|
|
* Application(GenericDesktop.Mouse)
|
|
* Usage(3)
|
|
* GenericDesktop.X
|
|
* GenericDesktop.Y
|
|
* GenericDesktop.Wheel
|
|
* Logical Minimum(-128)
|
|
* Logical Maximum(127)
|
|
* Report Size(8)
|
|
* Report Count(3)
|
|
* Report Offset(8)
|
|
* Flags( Variable Relative )
|
|
*
|
|
* This is the mapping that we expect:
|
|
* Button.0001 ---> Key.LeftBtn
|
|
* Button.0002 ---> Key.RightBtn
|
|
* Button.0003 ---> Key.MiddleBtn
|
|
* GenericDesktop.X ---> Relative.X
|
|
* GenericDesktop.Y ---> Relative.Y
|
|
* GenericDesktop.Wheel ---> Relative.Wheel
|
|
*
|
|
* This information can be verified by reading /sys/kernel/debug/hid/<dev>/rdesc
|
|
* This file should print the same information as showed above.
|
|
*/
|
|
|
|
static unsigned char rdesc[] = {
|
|
0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01,
|
|
0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03,
|
|
0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01,
|
|
0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81, 0x01,
|
|
0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38,
|
|
0x15, 0x80, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x03,
|
|
0x81, 0x06, 0xc0, 0xc0,
|
|
};
|
|
|
|
static int uhid_write(int fd, const struct uhid_event *ev)
|
|
{
|
|
ssize_t ret;
|
|
|
|
ret = write(fd, ev, sizeof(*ev));
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Cannot write to uhid: %m\n");
|
|
return -errno;
|
|
} else if (ret != sizeof(*ev)) {
|
|
fprintf(stderr, "Wrong size written to uhid: %ld != %lu\n",
|
|
ret, sizeof(ev));
|
|
return -EFAULT;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int create(int fd)
|
|
{
|
|
struct uhid_event ev;
|
|
|
|
memset(&ev, 0, sizeof(ev));
|
|
ev.type = UHID_CREATE;
|
|
strcpy((char*)ev.u.create.name, "test-uhid-device");
|
|
ev.u.create.rd_data = rdesc;
|
|
ev.u.create.rd_size = sizeof(rdesc);
|
|
ev.u.create.bus = BUS_USB;
|
|
ev.u.create.vendor = 0x15d9;
|
|
ev.u.create.product = 0x0a37;
|
|
ev.u.create.version = 0;
|
|
ev.u.create.country = 0;
|
|
|
|
return uhid_write(fd, &ev);
|
|
}
|
|
|
|
static void destroy(int fd)
|
|
{
|
|
struct uhid_event ev;
|
|
|
|
memset(&ev, 0, sizeof(ev));
|
|
ev.type = UHID_DESTROY;
|
|
|
|
uhid_write(fd, &ev);
|
|
}
|
|
|
|
static int event(int fd)
|
|
{
|
|
struct uhid_event ev;
|
|
ssize_t ret;
|
|
|
|
memset(&ev, 0, sizeof(ev));
|
|
ret = read(fd, &ev, sizeof(ev));
|
|
if (ret == 0) {
|
|
fprintf(stderr, "Read HUP on uhid-cdev\n");
|
|
return -EFAULT;
|
|
} else if (ret < 0) {
|
|
fprintf(stderr, "Cannot read uhid-cdev: %m\n");
|
|
return -errno;
|
|
} else if (ret != sizeof(ev)) {
|
|
fprintf(stderr, "Invalid size read from uhid-dev: %ld != %lu\n",
|
|
ret, sizeof(ev));
|
|
return -EFAULT;
|
|
}
|
|
|
|
switch (ev.type) {
|
|
case UHID_START:
|
|
fprintf(stderr, "UHID_START from uhid-dev\n");
|
|
break;
|
|
case UHID_STOP:
|
|
fprintf(stderr, "UHID_STOP from uhid-dev\n");
|
|
break;
|
|
case UHID_OPEN:
|
|
fprintf(stderr, "UHID_OPEN from uhid-dev\n");
|
|
break;
|
|
case UHID_CLOSE:
|
|
fprintf(stderr, "UHID_CLOSE from uhid-dev\n");
|
|
break;
|
|
case UHID_OUTPUT:
|
|
fprintf(stderr, "UHID_OUTPUT from uhid-dev\n");
|
|
break;
|
|
case UHID_OUTPUT_EV:
|
|
fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n");
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool btn1_down;
|
|
static bool btn2_down;
|
|
static bool btn3_down;
|
|
static signed char abs_hor;
|
|
static signed char abs_ver;
|
|
static signed char wheel;
|
|
|
|
static int send_event(int fd)
|
|
{
|
|
struct uhid_event ev;
|
|
|
|
memset(&ev, 0, sizeof(ev));
|
|
ev.type = UHID_INPUT;
|
|
ev.u.input.size = 4;
|
|
|
|
if (btn1_down)
|
|
ev.u.input.data[0] |= 0x1;
|
|
if (btn2_down)
|
|
ev.u.input.data[0] |= 0x2;
|
|
if (btn3_down)
|
|
ev.u.input.data[0] |= 0x4;
|
|
|
|
ev.u.input.data[1] = abs_hor;
|
|
ev.u.input.data[2] = abs_ver;
|
|
ev.u.input.data[3] = wheel;
|
|
|
|
return uhid_write(fd, &ev);
|
|
}
|
|
|
|
static int keyboard(int fd)
|
|
{
|
|
char buf[128];
|
|
ssize_t ret, i;
|
|
|
|
ret = read(STDIN_FILENO, buf, sizeof(buf));
|
|
if (ret == 0) {
|
|
fprintf(stderr, "Read HUP on stdin\n");
|
|
return -EFAULT;
|
|
} else if (ret < 0) {
|
|
fprintf(stderr, "Cannot read stdin: %m\n");
|
|
return -errno;
|
|
}
|
|
|
|
for (i = 0; i < ret; ++i) {
|
|
switch (buf[i]) {
|
|
case '1':
|
|
btn1_down = !btn1_down;
|
|
ret = send_event(fd);
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
case '2':
|
|
btn2_down = !btn2_down;
|
|
ret = send_event(fd);
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
case '3':
|
|
btn3_down = !btn3_down;
|
|
ret = send_event(fd);
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
case 'a':
|
|
abs_hor = -20;
|
|
ret = send_event(fd);
|
|
abs_hor = 0;
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
case 'd':
|
|
abs_hor = 20;
|
|
ret = send_event(fd);
|
|
abs_hor = 0;
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
case 'w':
|
|
abs_ver = -20;
|
|
ret = send_event(fd);
|
|
abs_ver = 0;
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
case 's':
|
|
abs_ver = 20;
|
|
ret = send_event(fd);
|
|
abs_ver = 0;
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
case 'r':
|
|
wheel = 1;
|
|
ret = send_event(fd);
|
|
wheel = 0;
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
case 'f':
|
|
wheel = -1;
|
|
ret = send_event(fd);
|
|
wheel = 0;
|
|
if (ret)
|
|
return ret;
|
|
break;
|
|
case 'q':
|
|
return -ECANCELED;
|
|
default:
|
|
fprintf(stderr, "Invalid input: %c\n", buf[i]);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int fd;
|
|
const char *path = "/dev/uhid";
|
|
struct pollfd pfds[2];
|
|
int ret;
|
|
struct termios state;
|
|
|
|
ret = tcgetattr(STDIN_FILENO, &state);
|
|
if (ret) {
|
|
fprintf(stderr, "Cannot get tty state\n");
|
|
} else {
|
|
state.c_lflag &= ~ICANON;
|
|
state.c_cc[VMIN] = 1;
|
|
ret = tcsetattr(STDIN_FILENO, TCSANOW, &state);
|
|
if (ret)
|
|
fprintf(stderr, "Cannot set tty state\n");
|
|
}
|
|
|
|
if (argc >= 2) {
|
|
if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
|
|
fprintf(stderr, "Usage: %s [%s]\n", argv[0], path);
|
|
return EXIT_SUCCESS;
|
|
} else {
|
|
path = argv[1];
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "Open uhid-cdev %s\n", path);
|
|
fd = open(path, O_RDWR | O_CLOEXEC);
|
|
if (fd < 0) {
|
|
fprintf(stderr, "Cannot open uhid-cdev %s: %m\n", path);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
fprintf(stderr, "Create uhid device\n");
|
|
ret = create(fd);
|
|
if (ret) {
|
|
close(fd);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
pfds[0].fd = STDIN_FILENO;
|
|
pfds[0].events = POLLIN;
|
|
pfds[1].fd = fd;
|
|
pfds[1].events = POLLIN;
|
|
|
|
fprintf(stderr, "Press 'q' to quit...\n");
|
|
while (1) {
|
|
ret = poll(pfds, 2, -1);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Cannot poll for fds: %m\n");
|
|
break;
|
|
}
|
|
if (pfds[0].revents & POLLHUP) {
|
|
fprintf(stderr, "Received HUP on stdin\n");
|
|
break;
|
|
}
|
|
if (pfds[1].revents & POLLHUP) {
|
|
fprintf(stderr, "Received HUP on uhid-cdev\n");
|
|
break;
|
|
}
|
|
|
|
if (pfds[0].revents & POLLIN) {
|
|
ret = keyboard(fd);
|
|
if (ret)
|
|
break;
|
|
}
|
|
if (pfds[1].revents & POLLIN) {
|
|
ret = event(fd);
|
|
if (ret)
|
|
break;
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "Destroy uhid device\n");
|
|
destroy(fd);
|
|
return EXIT_SUCCESS;
|
|
}
|