How to Take Ownership of a Hid Device

How can you take ownership of a hid device?

I have done this - my specific application was a daemon that read events from a USB HID barcode reader (which presents as a USB HID keyboard device).

To do this I used the event device interface, opening the /dev/input/event* device corresponding to the device I was after. You can then issue the EVIOCGRAB ioctl on the device, which grabs it for exclusive use, and read events (which represent keypresses, mouse movements etc) from the device as they become available.

(When the device is grabbed for exclusive use, only your application will see events from it).

HID software device (emulated)

Windows doesn't have a way; you will have to simulate mouse and keyboard events instead. Linux has uinput, which will allow you to inject input events directly into the kernel.

How to tell what device triggered a particular event in Delphi?

There's no way to tell from within OnClick. However, you can also attach events to a control that will fire when the mouse rolls over it, which would probably be more appropriate for what you're trying to do anyway. Take a look at the OnMouseEnter and OnMouseLeave events. Also, if you really want something specific to happen when the mouse is clicked, you can attach it to OnMouseUp.

Need to intercept HID Keyboard events (and then block them)

So I whipped up a proof-of-concept app according to that post I found here

It does exactly what I require - just though I'd share my solution anyway.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <dirent.h>
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/time.h>
#include <termios.h>
#include <signal.h>

int main(int argc, char* argv[])
{
struct input_event ev[64];
int fevdev = -1;
int result = 0;
int size = sizeof(struct input_event);
int rd;
int value;
char name[256] = "Unknown";
char *device = "/dev/input/event3";

fevdev = open(device, O_RDONLY);
if (fevdev == -1) {
printf("Failed to open event device.\n");
exit(1);
}

result = ioctl(fevdev, EVIOCGNAME(sizeof(name)), name);
printf ("Reading From : %s (%s)\n", device, name);

printf("Getting exclusive access: ");
result = ioctl(fevdev, EVIOCGRAB, 1);
printf("%s\n", (result == 0) ? "SUCCESS" : "FAILURE");

while (1)
{
if ((rd = read(fevdev, ev, size * 64)) < size) {
break;
}

value = ev[0].value;

if (value != ' ' && ev[1].value == 1 && ev[1].type == 1) {
printf ("Code[%d]\n", (ev[1].code));
}
}

printf("Exiting.\n");
result = ioctl(fevdev, EVIOCGRAB, 1);
close(fevdev);
return 0;
}

Faking an input device for testing purpose

Create a group for users who are allowed to access the device, and an udev rule to set the ownership of that input event device to that group.


I use teensy (system) group:

sudo groupadd -r teensy

and add each user into it using e.g.

sudo usermod -a -g teensy my-user-name

or whatever graphical user interface I have available.

By managing which users and service daemons belong to the teensy group, you can easily manage the access to the devices.


For my Teensy microcontrollers (that have native USB, and I use for HID testing), I have the following /lib/udev/rules.d/49-teensy.rules:

ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", ENV{ID_MM_DEVICE_IGNORE}="1"
ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789A]?", ENV{MTP_NO_PROBE}="1"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789ABCD]?", GROUP:="teensy", MODE:="0660"
KERNEL=="ttyACM*", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", GROUP:="teensy", MODE:="0660"

You only need the third line (SUBSYSTEMS=="usb", one) for HID devices, though. Make sure the idVendor and idProduct match your USB HID device. You can use lsusb to list the currently connected USB devices vendor and product numbers. The matching uses glob patterns, just like file names.

After adding the above, don't forget running sudo udevadm control --reload-rules && sudo udevadm trigger to reload the rules. Next time you plug in your USB HID device, all members of your group (teensy in the above) can access it directly.


Note that by default in most distributions, udev also creates persistent symlinks in /dev/input/by-id/ using the USB device type and serial. In my case, one of my Teensy LC's (serial 4298820) with a combined keyboard-mouse-joystic device provides /dev/input/by-id/usb-Teensyduino_Keyboard_Mouse_Joystick_4298820-event-kbd for the keyboard event device, /dev/input/by-id/usb-Teensyduino_Keyboard_Mouse_Joystick_4298820-if01-event-mouse for the mouse event device, and /dev/input/by-id/usb-Teensyduino_Keyboard_Mouse_Joystick_4298820-if03-event-joystick and /dev/input/by-id/usb-Teensyduino_Keyboard_Mouse_Joystick_4298820-if04-event-joystick for the two joystick interfaces.

(By "persistent", I do not mean these symlinks always exist; I mean that whenever that particular device is plugged in, the symlink of exactly that name exists, and points to the actual Linux input event character device.)


The Linux uinput device can be used to implement a virtual input event device using a simple privileged daemon.

The process to create a new virtual USB input event device goes as follows.

  1. Open /dev/uinput for writing (or reading and writing):

    fd = open("/dev/uinput", O_RDWR);
    if (fd == -1) {
    fprintf(stderr, "Cannot open /dev/uinput: %s.\n", strerror(errno));
    exit(EXIT_FAILURE);
    }

    The above requires superuser privileges. However, immediately after opening the device, you can drop all privileges, and have your daemon/service run as a dedicated user instead.
     

  2. Use the UI_SET_EVBIT ioctl for each event type allowed.

    You will want to allow at least EV_SYN; and EV_KEY for keyboards and mouse buttons, and EV_REL for mouse movement, and so on.

    if (ioctl(fd, UI_SET_EVBIT, EV_SYN) == -1 ||
    ioctl(fd, UI_SET_EVBIT, EV_KEY) == -1 ||
    ioctl(fd, UI_SET_EVBIT, EV_REL) == -1) {
    fprintf(stderr, "Uinput event types not allowed: %s.\n", strerror(errno));
    close(fd);
    exit(EXIT_FAILURE);
    }

    I personally use a static constant array with the codes, for easier management.
     

  3. Use the UI_SET_KEYBIT ioctl for each key code the device may emit, and UI_SET_RELBIT ioctl for each relative movement code (mouse code). For example, to allow space, left mouse button, horizontal and vertical mouse movement, and mouse wheel:

    if (ioctl(fd, UI_SET_KEYBIT, KEY_SPACE) == -1 ||
    ioctl(fd, UI_SET_KEYBIT, BTN_LEFT) == -1 ||
    ioctl(fd, UI_SET_RELBIT, REL_X) == -1 ||
    ioctl(fd, UI_SET_RELBIT, REL_Y) == -1 ||
    ioctl(fd, UI_SET_RELBIT, REL_WHEEL) == -1) {
    fprintf(stderr, "Uinput event types not allowed: %s.\n", strerror(errno));
    close(fd);
    exit(EXIT_FAILURE);
    }

    Again, static const arrays (one for UI_SET_KEYBIT and one for UI_SET_RELBIT codes) is much easier to maintain.
     

  4. Define a struct uinput_user_dev, and write it to the device.

    If you have name containing the device name string, vendor and product with the USB vendor and product ID numbers, version with a version number (0 is fine), use

    struct uinput_user_dev  dev;

    memset(&dev, 0, sizeof dev);
    strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE);
    dev.id.bustype = BUS_USB;
    dev.id.vendor = vendor;
    dev.id.product = product;
    dev.id.version = version;

    if (write(fd, &dev, sizeof dev) != sizeof dev) {
    fprintf(stderr, "Cannot write an uinput device description: %s.\n", strerror(errno));
    close(fd);
    exit(EXIT_FAILURE);
    }

    Later kernels have an ioctl to do the same thing (apparently being involved in systemd development causes this kind of drain bamage);

    struct uinput_setup  dev;

    memset(&dev, 0, sizeof dev);
    strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE);
    dev.id.bustype = BUS_USB;
    dev.id.vendor = vendor;
    dev.id.product = product;
    dev.id.version = version;

    if (ioctl(fd, UI_DEV_SETUP, &dev) == -1) {
    fprintf(stderr, "Cannot write an uinput device description: %s.\n", strerror(errno));
    close(fd);
    exit(EXIT_FAILURE);
    }

    The idea seems to be that instead of using the former, you can try the latter first, and if it fails, do the former instead. You know, because a single interface might some day not be enough. (That's what the documentation and commit say, anyway.)

    I might sound a bit cranky, here, but that's just because I do subscribe to both the Unix philosophy and the KISS principle (or minimalist approach), and see such warts completely unnecessary. And too often coming from the same loosely related group of developers. Ahem. No personal insult intended; I just think they are doing poor job.
     

  5. Create the virtual device, by issuing an UI_DEV_CREATE ioctl:

    if (ioctl(fd, UI_DEV_CREATE) == -1) {
    fprintf(stderr, "Cannot create the virtual uinput device: %s.\n", strerror(errno));
    close(fd);
    exit(EXIT_FAILURE);
    }

    At this point, the kernel will construct the device, provide the corresponding event to the udev daemon, and the udev daemon will construct the device node and symlink(s) according to its configuration. All this will take a bit of time -- a fraction of a second in the real world, but enough that trying to emit events immediately might cause some of them to be lost.
     

  6. Emit the input events (struct input_event) by writing to the uinput device.

    You can write one or more struct input_events at a time, and should never see short writes (unless you try to write a partial event structure). Partial event structures are completely ignored. (See drivers/input/misc/uinput.c:uinput_write() uinput_inject_events() for how the kernel handles such writes.)

    Many actions consists of more than one struct input_event. For example, you might move the mouse diagonally (emitting both { .type == EV_REL, .code == REL_X, .value = xdelta } and { .type == EV_REL, .code == REL_Y, .value = ydelta } for that single movement). The synchronization events ({ .type == EV_SYN, .code == 0, .value == 0 }) are used as a sentinel or separator, denoting the end of related events.

    Because of this, you'll need to append an { .type == EV_SYN, .code == 0, .value == 0 } input event after each individual action (mouse movement, key press, key release, and so on). Think of it as the equivalent of a newline, for line-buffered input.

    For example, the following code moves the mouse diagonally down right by a single pixel.

    struct input_event  event[3];
    memset(event, 0, sizeof event);

    event[0].type = EV_REL;
    event[0].code = REL_X;
    event[0].value = +1; /* Right */

    event[1].type = EV_REL;
    event[1].code = REL_Y;
    event[1].value = +1; /* Down */

    event[2].type = EV_SYN;
    event[2].code = 0;
    event[2].value = 0;

    if (write(fd, event, sizeof event) != sizeof event)
    fprintf(stderr, "Failed to inject mouse movement event.\n");

    The failure case is not fatal; it only means the events were not injected (although I don't see how that could happen in current kernels; better be defensive, just in case). You can simply retry the same again, or ignore the failure (but letting the user know, so they can investigate, if it ever happens). So log it or output a warning, but no need for it to cause the daemon/service to exit.
     

  7. Destroy the device:

    ioctl(fd, UI_DEV_DESTROY);
    close(fd);

    The device does get automatically destroyed when the last duplicate of the original opened descriptor gets closed, but I recommend doing it explicitly as above.
     

Putting steps 1-5 in a function, you get something like

#define  _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/uinput.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

static const unsigned int allow_event_type[] = {
EV_KEY,
EV_SYN,
EV_REL,
};
#define ALLOWED_EVENT_TYPES (sizeof allow_event_type / sizeof allow_event_type[0])

static const unsigned int allow_key_code[] = {
KEY_SPACE,
BTN_LEFT,
BTN_MIDDLE,
BTN_RIGHT,
};
#define ALLOWED_KEY_CODES (sizeof allow_key_code / sizeof allow_key_code[0])

static const unsigned int allow_rel_code[] = {
REL_X,
REL_Y,
REL_WHEEL,
};
#define ALLOWED_REL_CODES (sizeof allow_rel_code / sizeof allow_rel_code[0])

static int uinput_open(const char *name, const unsigned int vendor, const unsigned int product, const unsigned int version)
{
struct uinput_user_dev dev;
int fd;
size_t i;

if (!name || strlen(name) < 1 || strlen(name) >= UINPUT_MAX_NAME_SIZE) {
errno = EINVAL;
return -1;
}

fd = open("/dev/uinput", O_RDWR);
if (fd == -1)
return -1;

memset(&dev, 0, sizeof dev);
strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE);
dev.id.bustype = BUS_USB;
dev.id.vendor = vendor;
dev.id.product = product;
dev.id.version = version;

do {

for (i = 0; i < ALLOWED_EVENT_TYPES; i++)
if (ioctl(fd, UI_SET_EVBIT, allow_event_type[i]) == -1)
break;
if (i < ALLOWED_EVENT_TYPES)
break;

for (i = 0; i < ALLOWED_KEY_CODES; i++)
if (ioctl(fd, UI_SET_KEYBIT, allow_key_code[i]) == -1)
break;
if (i < ALLOWED_KEY_CODES)
break;

for (i = 0; i < ALLOWED_REL_CODES; i++)
if (ioctl(fd, UI_SET_RELBIT, allow_rel_code[i]) == -1)
break;
if (i < ALLOWED_REL_CODES)
break;

if (write(fd, &dev, sizeof dev) != sizeof dev)
break;

if (ioctl(fd, UI_DEV_CREATE) == -1)
break;

/* Success. */
return fd;

} while (0);

/* FAILED: */
{
const int saved_errno = errno;
close(fd);
errno = saved_errno;
return -1;
}
}

static void uinput_close(const int fd)
{
ioctl(fd, UI_DEV_DESTROY);
close(fd);
}

which seem to work fine, and requires no libraries (other than the standard C library).

It is important to realize that the Linux input subsystem, including uinput and struct input_event, are binary interfaces to the Linux kernel, and therefore will be kept backwards compatible (except for pressing technical reasons, like security issues or serious conflicts with other parts of the kernel). (The desire to wrap everything under the freedesktop.org or systemd umbrella is not one.)

Mouse Detect, OS Boot

Modern BIOSes emulate the PS/2 ports 0x60 and 0x64 using SMM/SMI with a feature often called "USB Legacy Support" (see the EHCI specification for more info.)

When the OS itself loads and initializes the USB controller there is a controller ownership transition from BIOS-owned to OS-owned.



Related Topics



Leave a reply



Submit