PDA

View Full Version : Intercepting USB traffic generated by the mouse



Absurd
03-29-2015, 12:57 PM
A couple of weeks ago, I posted this thread - Low level C library to interface with the mouse? (http://cboard.cprogramming.com/c-programming/166229-low-level-c-library-interface-mouse.html) - asking for help with tracking the mouse movement.
As it turned out, I need to go a little deeper than that.
I need an API that enables me to put my hands on the raw data generated by the mouse, before the OS converts it to (x,y) coordinates on the screen.
I made some research and found a great library - libpcap - which will, unfortunately, only sniff packets generated by my network devices.
This is the output I get from running the following code:


#include<stdio.h>
#include<pcap.h>


int main(int argc, char *argv[]) {

pcap_if_t *all_devices, *device;
char errbuf[PCAP_ERRBUF_SIZE];
int count=1;

if (pcap_findalldevs(&all_devices, errbuf)) {
printf("Error\n");
return 1;
}


for (device=all_devices; device!=NULL; device=device->next, ++count) {
printf("%d. %s - %s\n", count, device->name, device->description);
}

return 0;
}

Output:

1. wlan0 - (null)
2. bluetooth0 - Bluetooth adapter number 0
3. nflog - Linux netfilter log (NFLOG) interface
4. nfqueue - Linux netfilter queue (NFQUEUE) interface
5. vmnet1 - (null)
6. vmnet8 - (null)
7. any - Pseudo-device that captures on all interfaces
8. lo - (null)

I have no idea what 3,4,5,6 are, but I'm guessing they have nothing to do with my USB ports... :\

Any chance someone knows about how to monitor my mouse traffic?

Nominal Animal
03-29-2015, 01:54 PM
I need an API that enables me to put my hands on the raw data generated by the mouse, before the OS converts it to (x,y) coordinates on the screen.
Any chance someone knows about how to monitor my mouse traffic?
I'm assuming this question is still Linux-specific, as you mentioned in the previous thread you're using Ubuntu.

In Linux, you can simply read struct input_event structures (defined in <linux/input.h>) from the input device; I recommend opening it via the named symlink in /dev/input/by-path/ or /dev/input/by-id/, depending on exactly how you want to identify the mouse. If you only support one mouse, you can use /dev/input/mouse0.

Normally, reading the events does not consume them. You also cannot modify them; think of it as just getting copies of the input events as they happen.

If you wish to modify or translate the mouse movement and events somehow, you do that by telling the kernel you wish to consume the events, then forward or generate the modified/translated events back to the kernel using the uinput device.

Use the EVIOCGRAB ioctl (on the device file descriptor) to tell the kernel you consume the events. To forward the events (or feed the translated events back to the kernel input subsystem -- you can, after all, convert mouse movement to keyboard keys, if you want), you use the /dev/input/uinput device. You first fill in and write a struct uinput_user_dev structure (defined in <linux/uinput.h>) that describes the "fake" mouse/tablet/keyboard you pretend the translated/forwarded events to come from, then issue some ioctl()s to define the exact features your "fake" device supports; then you simply write your struct input_event events as they occur, and the kernel will process them as if they were coming from a real physical device.

Here (http://lkcl.net/software/uinput/) is a trivial mouse synthesis example. That is, it does not use any input devices as a source, it just creates a mouse that moves a bit and presses a button.

I've described the struct input fields here (http://stackoverflow.com/a/16695758), and shown how to read HID inputs here (http://stackoverflow.com/a/20946151).

If you want full example code, you need to start a thread in the Linux sub-forum (or ask a moderator to move this thread there). This stuff is delightfully simple and easy to accomplish in Linux.

Absurd
03-29-2015, 02:30 PM
I'm assuming this question is still Linux-specific, as you mentioned in the previous thread you're using Ubuntu.
Yep.


In Linux, you can simply read struct input_event structures (defined in <linux/input.h>) from the input device; I recommend opening it via the named symlink in /dev/input/by-path/ or /dev/input/by-id/, depending on exactly how you want to identify the mouse. If you only support one mouse, you can use /dev/input/mouse0.

Normally, reading the events does not consume them. You also cannot modify them; think of it as just getting copies of the input events as they happen.

If you wish to modify or translate the mouse movement and events somehow, you do that by telling the kernel you wish to consume the events, then forward or generate the modified/translated events back to the kernel using the uinput device.

This sounds like exactly what I need!
Later on I will probably also need to consume them (correct me if I'm wrong, on the screen it will look as if the mouse is not moving?), but for now, reading it will suffice.
I need to process this data and send it to my application.

I'm planning to support one mouse for now, it is a simple (Logitech) mouse connected with a usb dongle.
Since this is a laptop computer, I'm guessing /dev/input/mouse0 refers to the touchpad?
How can I find out which file in /dev is my USB port?


Use the EVIOCGRAB ioctl (on the device file descriptor) to tell the kernel you consume the events. To forward the events (or feed the translated events back to the kernel input subsystem -- you can, after all, convert mouse movement to keyboard keys, if you want), you use the /dev/input/uinput device. You first fill in and write a struct uinput_user_dev structure (defined in <linux/uinput.h>) that describes the "fake" mouse/tablet/keyboard you pretend the translated/forwarded events to come from, then issue some ioctl()s to define the exact features your "fake" device supports; then you simply write your struct input_event events as they occur, and the kernel will process them as if they were coming from a real physical device.

Here (http://lkcl.net/software/uinput/) is a trivial mouse synthesis example. That is, it does not use any input devices as a source, it just creates a mouse that moves a bit and presses a button.

I've described the struct input fields here (http://stackoverflow.com/a/16695758), and shown how to read HID inputs here (http://stackoverflow.com/a/20946151).

If you want full example code, you need to start a thread in the Linux sub-forum (or ask a moderator to move this thread there). This stuff is delightfully simple and easy to accomplish in Linux.

I'm not sure what EVIOCGRAB and ioctl are, I will have to read the documentation for input.h and uinput.h first, but thanks anyway! This looks very promising.

If a moderator can please move this thread to where it belongs it would be great (I'm starting to think this is more Linux appropriate than C per se)...

Nominal Animal
03-29-2015, 06:35 PM
Consider the following example code. It's long, but most of it is just helper functions that convert the numeric values back to their linux/input.h named constants. Save this as event.c:


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

static const char *rel_name(const int code)
{
static char buffer[32];
switch (code) {
case REL_X: return "X axis";
case REL_Y: return "Y axis";
case REL_Z: return "Z axis";
case REL_RX: return "Rotation around X axis";
case REL_RY: return "Rotation around Y axis";
case REL_RZ: return "Rotation around Z axis";
case REL_HWHEEL: return "Horizontal scroll wheel";
case REL_DIAL: return "Dial";
case REL_WHEEL: return "Scroll wheel";
case REL_MISC: return "Miscellaneous";
default:
snprintf(buffer, sizeof buffer, "Unknown (%d)", code & 65535);
return (const char *)buffer;
}
}

static const char *abs_name(const int code)
{
static char buffer[32];
switch (code) {
case ABS_X: return "ABS_X";
case ABS_Y: return "ABS_Y";
case ABS_Z: return "ABS_Z";
case ABS_RX: return "ABS_RX";
case ABS_RY: return "ABS_RY";
case ABS_RZ: return "ABS_RZ";
case ABS_THROTTLE: return "ABS_THROTTLE";
case ABS_RUDDER: return "ABS_RUDDER";
case ABS_WHEEL: return "ABS_WHEEL";
case ABS_GAS: return "ABS_GAS";
case ABS_BRAKE: return "ABS_BRAKE";
case ABS_HAT0X: return "ABS_HAT0X";
case ABS_HAT0Y: return "ABS_HAT0Y";
case ABS_HAT1X: return "ABS_HAT1X";
case ABS_HAT1Y: return "ABS_HAT1Y";
case ABS_HAT2X: return "ABS_HAT2X";
case ABS_HAT2Y: return "ABS_HAT2Y";
case ABS_HAT3X: return "ABS_HAT3X";
case ABS_HAT3Y: return "ABS_HAT3Y";
case ABS_PRESSURE: return "ABS_PRESSURE";
case ABS_DISTANCE: return "ABS_DISTANCE";
case ABS_TILT_X: return "ABS_TILT_X";
case ABS_TILT_Y: return "ABS_TILT_Y";
case ABS_TOOL_WIDTH: return "ABS_TOOL_WIDTH";
case ABS_VOLUME: return "ABS_VOLUME";
case ABS_MISC: return "ABS_MISC";
case ABS_MT_SLOT: return "ABS_MT_SLOT";
case ABS_MT_TOUCH_MAJOR: return "ABS_MT_TOUCH_MAJOR";
case ABS_MT_TOUCH_MINOR: return "ABS_MT_TOUCH_MINOR";
case ABS_MT_WIDTH_MAJOR: return "ABS_MT_WIDTH_MAJOR";
case ABS_MT_WIDTH_MINOR: return "ABS_MT_WIDTH_MINOR";
case ABS_MT_ORIENTATION: return "ABS_MT_ORIENTATION";
case ABS_MT_POSITION_X: return "ABS_MT_POSITION_X";
case ABS_MT_POSITION_Y: return "ABS_MT_POSITION_Y";
case ABS_MT_TOOL_TYPE: return "ABS_MT_TOOL_TYPE";
case ABS_MT_BLOB_ID: return "ABS_MT_BLOB_ID";
case ABS_MT_TRACKING_ID: return "ABS_MT_TRACKING_ID";
case ABS_MT_PRESSURE: return "ABS_MT_PRESSURE";
case ABS_MT_DISTANCE: return "ABS_MT_DISTANCE";
case ABS_MT_TOOL_X: return "ABS_MT_TOOL_X";
case ABS_MT_TOOL_Y: return "ABS_MT_TOOL_Y";
default:
snprintf(buffer, sizeof buffer, "Unknown (%d)", code & 65535);
return (const char *)buffer;
}
}

static const char *button_name(const int code)
{
static char buffer[32];
switch (code) {
/* Generics */
case BTN_0: return "BTN_0";
case BTN_1: return "BTN_1";
case BTN_2: return "BTN_2";
case BTN_3: return "BTN_3";
case BTN_4: return "BTN_4";
case BTN_5: return "BTN_5";
case BTN_6: return "BTN_6";
case BTN_7: return "BTN_7";
case BTN_8: return "BTN_8";
case BTN_9: return "BTN_9";
/* Mice */
case BTN_LEFT: return "BTN_LEFT == BTN_MOUSE";
case BTN_RIGHT: return "BTN_RIGHT";
case BTN_MIDDLE: return "BTN_MIDDLE";
case BTN_SIDE: return "BTN_SIDE";
case BTN_EXTRA: return "BTN_EXTRA";
case BTN_FORWARD: return "BTN_FORWARD";
case BTN_BACK: return "BTN_BACK";
case BTN_TASK: return "BTN_TASK";
/* Joysticks */
case BTN_JOYSTICK: return "BTN_JOYSTICK == BTN_TRIGGER";
case BTN_THUMB: return "BTN_THUMB";
case BTN_THUMB2: return "BTN_THUMB2";
case BTN_TOP: return "BTN_TOP";
case BTN_TOP2: return "BTN_TOP2";
case BTN_PINKIE: return "BTN_PINKIE";
case BTN_BASE: return "BTN_BASE";
case BTN_BASE2: return "BTN_BASE2";
case BTN_BASE3: return "BTN_BASE3";
case BTN_BASE4: return "BTN_BASE4";
case BTN_BASE5: return "BTN_BASE5";
case BTN_BASE6: return "BTN_BASE6";
case BTN_DEAD: return "BTN_DEAD";
/* Gamepads */
case BTN_GAMEPAD: return "BTN_SOUTH == BTN_GAMEPAD == BTN_A";
case BTN_NORTH: return "BTN_NORTH == BTN_X";
case BTN_EAST: return "BTN_EAST == BTN_B";
case BTN_WEST: return "BTN_WEST == BTN_Y";
case BTN_C: return "BTN_C";
case BTN_Z: return "BTN_Z";
case BTN_TL: return "BTN_TL";
case BTN_TR: return "BTN_TR";
case BTN_TL2: return "BTN_TL2";
case BTN_TR2: return "BTN_TR2";
case BTN_SELECT: return "BTN_SELECT";
case BTN_START: return "BTN_START";
case BTN_MODE: return "BTN_MODE";
case BTN_THUMBL: return "BTN_THUMBL";
case BTN_THUMBR: return "BTN_THUMBR";
/* Digitizers, touchpads */
case BTN_TOOL_PEN: return "BTN_TOOL_PEN == BTN_DIGI";
case BTN_TOOL_RUBBER: return "BTN_TOOL_RUBBER";
case BTN_TOOL_BRUSH: return "BTN_TOOL_BRUSH";
case BTN_TOOL_PENCIL: return "BTN_TOOL_PENCIL";
case BTN_TOOL_AIRBRUSH: return "BTN_TOOL_AIRBRUSH";
case BTN_TOOL_FINGER: return "BTN_TOOL_FINGER";
case BTN_TOOL_MOUSE: return "BTN_TOOL_MOUSE";
case BTN_TOOL_LENS: return "BTN_TOOL_LENS";
case BTN_TOOL_QUINTTAP: return "BTN_TOOL_QUINTTAP";
case BTN_TOUCH: return "BTN_TOUCH";
case BTN_STYLUS: return "BTN_STYLUS";
case BTN_STYLUS2: return "BTN_STYLUS2";
case BTN_TOOL_DOUBLETAP: return "BTN_TOOL_DOUBLETAP";
case BTN_TOOL_TRIPLETAP: return "BTN_TOOL_TRIPLETAP";
case BTN_TOOL_QUADTAP: return "BTN_TOOL_QUADTAP";
case BTN_GEAR_DOWN: return "BTN_WHEEL == BTN_GEAR_DOWN";
case BTN_GEAR_UP: return "BTN_GEAR_UP";
default:
snprintf(buffer, sizeof buffer, "Unknown (%d)", code);
return (const char *)buffer;
}
}

static const char *button_state(const int value)
{
static char buffer[32];

switch (value) {
case 0: return "Released";
case 1: return "Pressed";
case 2: return "Autorepeat";
default:
snprintf(buffer, sizeof buffer, "Unknown (%d)", value);
return (const char *)buffer;
}
}

static const char *event_type(const int type)
{
static char buffer[64];

switch (type) {
case EV_SYN: return "EV_SYN (synchronization)";
case EV_KEY: return "EV_KEY (keyboard event)";
case EV_REL: return "EV_REL (mouse event, relative)";
case EV_ABS: return "EV_ABS (touch event, absolute)";
case EV_MSC: return "EV_MSC (miscellaneous)";
case EV_SW: return "EV_SW (switch)";
case EV_LED: return "EV_LED (LED)";
case EV_SND: return "EV_SND (sound)";
case EV_REP: return "EV_REP (autorepeat)";
case EV_FF: return "EV_FF (force feedback)";
case EV_PWR: return "EV_PWR (power event)";
case EV_FF_STATUS: return "EV_FF_STATUS (force feedback status)";
default:
snprintf(buffer, sizeof buffer, "Unknown (%d)", type & 65535);
return (const char *)buffer;
}
}

int main(int argc, char *argv[])
{
char device_name[64];
struct input_event ev;
struct tm tm;
char *truedev;
ssize_t n;
int fd;

if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [..-h | --help ]\n", argv[0]);
fprintf(stderr, " %s /dev/input/event0\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "This example program describes mouse events.\n");
fprintf(stderr, "\n");
return 0;
}

/* Open the specified device. */
do {
fd = open(argv[1], O_RDONLY);
} while (fd == -1 && errno == EINTR);
if (fd == -1) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return 1;
}

/* Use EVIOCGNAME to get the device name. (First we clear it, though.) */
memset(device_name, 0, sizeof device_name);
ioctl(fd, EVIOCGNAME(sizeof device_name - 1), device_name);

/* Obtain real, absolute path to the device. */
truedev = canonicalize_file_name(argv[1]);
if (truedev != NULL)
fprintf(stderr, "Reading from %s (%s)\n", truedev, argv[1]);
else
fprintf(stderr, "Reading from %s\n", argv[1]);

if (strlen(device_name) > 0)
fprintf(stderr, "EVIOCGNAME ioctl reports device is named '%s'.\n", device_name);

#ifdef GRAB_DEVICE
errno = 0;
if (ioctl(fd, EVIOCGRAB, 1) == 0)
fprintf(stderr, "Device grabbed (using EVIOCGRAB ioctl) successfully.\n");
else
fprintf(stderr, "Failed to grab device (%s).\n", strerror(errno));
#endif

fflush(stderr);

/* Read input events (forever). */
while (1) {

n = read(fd, &ev, sizeof ev);
if (n == -1) {
/* It is not an error if the read was interrupted. */
if (errno == EINTR)
continue;
/* Print an error message, and break out of loop. */
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
break;
} else
if (n == 0) {
/* End of input; device detached? */
fprintf(stderr, "%s: No more events.\n", argv[1]);
break;
} else
if (n != sizeof ev) {
/* This should never occur; input driver or kernel bug. */
fprintf(stderr, "%s: Invalid event (length %d, expected %d)\n",
argv[1], (int)n, (int)sizeof ev);
/* We just ignore those, and wait for next event. */
continue;
}

/*
* We have an event. Describe it.
*/

/* Convert ev.time.tv_sec to broken down local time.
* We also have the microseconds part in ev.time.tv_usec.
*/
localtime_r(&(ev.time.tv_sec), &tm);

printf("%04d-%02d-%02d %02d:%02d:%02d.%06d %s code:%d value:%d\n",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, (int)ev.time.tv_usec,
event_type(ev.type), (int)ev.code & 65535, (int)ev.value);

if (ev.type == EV_REL)
printf("\tcode == %d == %s\n\tvalue == %+d\n",
(int)ev.code & 65535, rel_name(ev.code), (int)ev.value);
else
if (ev.type == EV_ABS)
printf("\tcode == %d == %s\n\tvalue == %+d\n",
(int)ev.code & 65535, abs_name(ev.code), (int)ev.value);
else
if (ev.type == EV_KEY && ev.code >= 0x100 && ev.code <= 0x15F)
printf("\tcode ==%d == %s\n\tvalue == %d: %s\n",
(int)ev.code & 65535, button_name(ev.code),
(int)ev.value, button_state(ev.value));
else
if (ev.type == EV_SYN)
putchar('\n');

/* Make sure the output hits the terminal. */
fflush(stdout);
}

/* All done, apparently. Close device and exit. */
close(fd);
return 0;
}
and compile it using


gcc -Wall event.c -o event

if you want your mouse or keyboard to continue working as normally, just monitor what is happening; or,


gcc -Wall -DGRAB_DEVICE event.c -o event

if you want the program to grab/capture the events, so that whatever input device you're using, no longer affects your system in any other way (as long as this program is running).

Then, you can run it using e.g.


sudo ./event /dev/input/by-id/usb-*-event-mouse

As long as you don't use the grab/capture version and your keyboard event source, you can interrupt it by pressing Ctrl+C. Or you can just close the terminal window you're using, that'll close the program too.

You'll see how events are grouped. For example, if you move your mouse diagonally, you'll see two EV_REL events (one for the X axis, one for the Y axis), followed by an EV_SYN event which tells you this new set of changes reflect the input device state correctly. See e.g. Documentation/input/event-codes.txt (https://www.kernel.org/doc/Documentation/input/event-codes.txt) for better descriptions of the type codes.

On the laptop I'm using at the moment (HP EliteBook 820 G1), I've got 19 event devices, /dev/input/event0 through /dev/input/event18. (I've attached an USB mouse, to best match your configuration.) Of these, /dev/input/event4 is my USB mouse, /dev/input/event3 is the laptop keyboard, /dev/input/event6 is the laptop pointing stick, /dev/input/event8 is the laptop touchpad (but grabbed by X so no events are available by default), and /dev/input/event10 reports the built-in lis3lv02d accelerometer data. Lots of interesting event sources!

For easier testing, you could just install evtest,


sudo apt-get install evtest

and run it via


sudo evtest

as it not only lets you choose one of the event devices, it'll also describe each one. For me, it shows


No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0: Sleep Button
/dev/input/event1: Lid Switch
/dev/input/event2: Power Button
/dev/input/event3: AT Translated Set 2 keyboard
/dev/input/event4: Logitech USB Laser Mouse
/dev/input/event5: HP Wireless hotkeys
/dev/input/event6: PS/2 Generic Mouse
/dev/input/event7: HP HD Webcam
/dev/input/event8: SynPS/2 Synaptics TouchPad
/dev/input/event9: HP WMI hotkeys
/dev/input/event10: ST LIS3LV02DL Accelerometer
/dev/input/event11: Video Bus
/dev/input/event12: HDA Intel HDMI HDMI/DP,pcm=8
/dev/input/event13: HDA Intel HDMI HDMI/DP,pcm=7
/dev/input/event14: HDA Intel HDMI HDMI/DP,pcm=3
/dev/input/event15: HDA Intel PCH Headphone
/dev/input/event16: HDA Intel PCH Dock Line Out
/dev/input/event17: HDA Intel PCH Line
/dev/input/event18: HDA Intel PCH Mic
Select the device event number [0-18]:

Funny side note: If I run sudo ./event /dev/input/event15 , I get these events when plugging in my headphones, and taking it out:


Reading from /dev/input/event15 (/dev/input/event15)
EVIOCGNAME ioctl reports device is named 'HDA Intel PCH Headphone'.
2015-03-30 02:25:58.291744 EV_SW (switch) code:2 value:1
2015-03-30 02:25:58.291744 EV_SYN (synchronization) code:0 value:0

2015-03-30 02:26:00.900762 EV_SW (switch) code:2 value:0
2015-03-30 02:26:00.900762 EV_SYN (synchronization) code:0 value:0

The events are (ev.type == EV_SW && ev.code == SW_HEADPHONE_INSERT && ev.value == 1) for insert/inserted event, and (ev.type == EV_SW && ev.code == SW_HEADPHONE_INSERT && ev.value == 0) for remove/not inserted event. See /usr/include/linux/input.h for more named constants.

The corresponding output for sudo evtest /dev/input/event15 is


sudo evtest /dev/input/event15
Input driver version is 1.0.1
Input device ID: bus 0x0 vendor 0x0 product 0x0 version 0x0
Input device name: "HDA Intel PCH Headphone"
Supported events:
Event type 0 (EV_SYN)
Event type 5 (EV_SW)
Event code 2 (SW_HEADPHONE_INSERT)
Properties:
Testing ... (interrupt to exit)
Event: time 1427674370.781164, type 5 (EV_SW), code 2 (SW_HEADPHONE_INSERT), value 1
Event: time 1427674370.781164, -------------- SYN_REPORT ------------
Event: time 1427674373.182707, type 5 (EV_SW), code 2 (SW_HEADPHONE_INSERT), value 0
Event: time 1427674373.182707, -------------- SYN_REPORT ------------


The ioctls I mentioned earlier, and listed in linux/input.h and discussed often wrt. linux input devices, are nothing strange either. The EVIOCGNAME ioctl I'm using above to get the device name/description is a good example. (I clear the buffer to zeros first, then tell the ioctl the buffer is one char shorter, just to make sure the buffer will end with a zero. That way I can treat the buffer safely as a string.)

If you use -DGRAB_DEVICE during compiling, the code to use the EVIOCGRAB ioctl is compiled in; then, whatever events the device you're using will be consumed by the program, and not affect your cursor etc.

In general, ioctls are simply control operations. It takes the descriptor to the device file as the first parameter, the ioctl name as the second parameter, and usually an integer (constant) or a pointer to a buffer or structure or variable defining the control operation.

There are quite a few ioctls available, most notably to query exactly which events a device will emit (evtest will list these), dealing with multitouch slots, and so on. Those related to input devices are listed in /usr/include/linux/input.h, and start with EVIOC. Here are some of the most interesting ones:


ioctl(fd, EVIOCGRAB, 1) /* Grab device */
ioctl(fd, EVIOCGRAB, 0) /* Release device */
ioctl(fd, EVIOCGVERSION, int *) /* Get driver version */
ioctl(fd, EVIOCGID, struct input_id *) /* Get device ID */
ioctl(fd, EVIOCGNAME(len), char [len]) /* Get device name */
ioctl(fd, EVIOCGPHYS(len), char [len]) /* Get physical location */
ioctl(fd, EVIOCGUNIQ(len), char [len]) /* Get unique identifier */
ioctl(fd, EVIOCGPROP(len), unsigned char [len]) /* Get device properties */
ioctl(fd, EVIOCGKEYCODE_V2, struct input_keymap_entry *) /* Get keycode */
ioctl(fd, EVIOCSKEYCODE_V2, struct input_keymap_entry *) /* Set keycode */
ioctl(fd, EVIOCGABS(ABS_axis), struct input_absinfo *) /* Get absolute value and limits for one axis */
ioctl(fd, EVIOCSABS(ABS_axis), struct input_absinfo *) /* Set absolute value and limits for one axis */
ioctl(fd, EVIOCGREP, unsigned int [2]) /* Get repeat settings */
ioctl(fd, EVIOCSREP, unsigned int [2]) /* Set repeat settings */
ioctl(fd, EVIOCGBIT(0, EV_CNT), unsigned char [1 + EV_CNT/CHAR_BIT]) /* Get bitmap of event types (EV_) the device produces */
ioctl(fd, EVIOCGBIT(type, KEY_CNT), unsigned char [1 + KEY_CNT/CHAR_BIT]) /* Get bitmap of event codes (KEY_, ABS_, REL_, SW_, etc.) the device produces for event type 'type' */
/* Note: bit 'i' of unsigned char buffer 'buffer' is set, if and only if
* (buffer[i / CHAR_BIT] & (1U << (i % CHAR_BIT)))
* is true.
*/


Hope this helps. If you have any questions, or desire a more detailed example code, just ask, and I'll be happy to try to answer.

Absurd
03-30-2015, 09:48 AM
Oh WOW!
That is absolutley fantastic!
I still have a lot of reading and researching to process everything you proposed, but this looks like it should keep me busy for now.
If I'll run into problems I'll post back.
Thanks again!

Absurd
03-30-2015, 02:20 PM
OK, after sitting almost all day in front of the computer trying to figure out what each and every line in your code does, I was able to write some code lines of my own.

I found this (http://www.tldp.org/LDP/sag/html/dev-fs.html) and the Wiki (http://en.wikipedia.org/wiki/Device_file) entry for "Device File" to be quite informative, however I still have a couple of questions.
Here's my /dev folder:

14042

and my /dev/input folder in a tree structure:

14043

How did you know to use open() with "/dev/input/by-id/usb-Logitech_USB_Receiver-if01-event-mouse"? Why not the event-file associated with the mouse?
(EDIT: ignore this question, I just noticed that /dev/input/by-id/usb-Logitech_USB_Receiver-if01-event-mouse is in fact a symbolic link to the event-file)

What is the "/dev/usb/hiddev0" file and how is it different from the event-file associated with the mouse?




I noticed you wrote:

Then, you can run it using e.g.


sudo ./event /dev/input/by-id/usb-*-event-mouse

Does that mean I can always count on having the file /dev/input/by-id/usb-*-event-mouse? (whenever usb-mouse is attached, of-course...)
The reason I'm asking is because I don't plan on listening to anything but the mouse, so I think I'd rather have the path to the mouse-file hard coded to my code.






There are quite a few ioctls available, most notably to query exactly which events a device will emit (evtest will list these), dealing with multitouch slots, and so on. Those related to input devices are listed in /usr/include/linux/input.h, and start with EVIOC. Here are some of the most interesting ones:


ioctl(fd, EVIOCGRAB, 1) /* Grab device */
ioctl(fd, EVIOCGRAB, 0) /* Release device */
ioctl(fd, EVIOCGVERSION, int *) /* Get driver version */
ioctl(fd, EVIOCGID, struct input_id *) /* Get device ID */
ioctl(fd, EVIOCGNAME(len), char [len]) /* Get device name */
ioctl(fd, EVIOCGPHYS(len), char [len]) /* Get physical location */
ioctl(fd, EVIOCGUNIQ(len), char [len]) /* Get unique identifier */
ioctl(fd, EVIOCGPROP(len), unsigned char [len]) /* Get device properties */
ioctl(fd, EVIOCGKEYCODE_V2, struct input_keymap_entry *) /* Get keycode */
ioctl(fd, EVIOCSKEYCODE_V2, struct input_keymap_entry *) /* Set keycode */
ioctl(fd, EVIOCGABS(ABS_axis), struct input_absinfo *) /* Get absolute value and limits for one axis */
ioctl(fd, EVIOCSABS(ABS_axis), struct input_absinfo *) /* Set absolute value and limits for one axis */
ioctl(fd, EVIOCGREP, unsigned int [2]) /* Get repeat settings */
ioctl(fd, EVIOCSREP, unsigned int [2]) /* Set repeat settings */
ioctl(fd, EVIOCGBIT(0, EV_CNT), unsigned char [1 + EV_CNT/CHAR_BIT]) /* Get bitmap of event types (EV_) the device produces */
ioctl(fd, EVIOCGBIT(type, KEY_CNT), unsigned char [1 + KEY_CNT/CHAR_BIT]) /* Get bitmap of event codes (KEY_, ABS_, REL_, SW_, etc.) the device produces for event type 'type' */
/* Note: bit 'i' of unsigned char buffer 'buffer' is set, if and only if
* (buffer[i / CHAR_BIT] & (1U << (i % CHAR_BIT)))
* is true.
*/


Where did you get this information from?
Can you please give me a link to the source?

Nominal Animal
03-30-2015, 08:19 PM
How did you know to use open() with "/dev/input/by-id/usb-Logitech_USB_Receiver-if01-event-mouse"? Why not the event-file associated with the mouse?
I did. Udev names the by-id symlinks using the HID (Human Interface Device) name, and all USB mice will end up being named usb-something_or_other-event-mouse. If you have only one USB mouse, it will fit that pattern.

Here's a short list of what the input device node names (symlink targets) refer to:


eventN - input event device nodes (evdev)
These are the event input devices, pseudofiles that provide the input events as struct input_event structures, as defined in /usr/include/linux/input.h.
Normally, the kernel translates these to keyboard, joystick, and/or mouse events, for backwards compatibility with older software.
The input event device framework is old, stable, and very versatile, so I recommend using only these if you can.

jsN - joystick device nodes (joydev)
These are joystick devices, pseudofiles that provide joystick events as struct js_event structures, as defined in /usr/include/linux/joystick.h

mouseN - mouse device nodes (mousedev)
These are mouse devices, pseudofiles that provide mouse and digitizer tablet events in various formats. You can tell the kernel which format you want to use.

mice - Combined mouse device
This provides combination events from all mouse devices.



What is the "/dev/usb/hiddev0" file and how is it different from the event-file associated with the mouse?
udev -- the service in Linux that detects new devices and creates the device nodes and symlinks based on relatively straightforward rules -- does not have to put all input event devices into /dev/input/.

Things like USB-connected thermal, infrared, and magnetic sensors do often use the HID input event interface, because it is such an easy interface. Yet, it does not make much sense to call them "user input devices". So, udev puts them under /dev/usb/. In Ubuntu, if you belong to the plugdev group (run id -Gn to see the list of groups you belong to), you can open and read/control /dev/usb/hiddevN devices without superuser privileges.

So, your /dev/usb/hiddev0 could be an USB-connected sensor, or it could be part of the Logitech wireless input device (specifically the "connect" button, or connection quality or encryption interface -- basically the nitty-gritty details you might have to use a manufacturer-specific utility to take advantage of).

I recommend you find out what it is: just run sudo evtest /dev/usb/hiddev0 and see what it says.


Does that mean I can always count on having the file /dev/input/by-id/usb-*-event-mouse?
No, only when you have exactly one USB mouse connected. If you have several, it will expand to more than one parameter.


I think I'd rather have the path to the mouse-file hard coded to my code.
Noooooooooooooooooo!

Make it a required runtime parameter, instead; the last or only non-option parameter(s) on your command line. If you run your program/daemon with the input device nodes listed last, you can count how many there are, and either support the first, last, or all of them. It's not that much more complicated to do, and you save a lot of effort when you switch mice.

If your program is interactive, it is very easy to enumerate the event devices, and ask the user. For example, consider the following program:


#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/input.h>
#include <dirent.h>
#include <fnmatch.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

static const char *const input_device_dirs[] = {
"/dev/input",
NULL
};

int select_event_device(const char *const dirlist[],
int (*choose)(int fd,
const char *dir,
const char *dev,
const char *name,
int bustype,
int vendor,
int product,
void *custom),
void *custom)
{
char name_buf[256];
struct input_id id;
size_t d;
DIR *dir;
struct dirent *ent;
int fd, version, result;

/* No directory list specified? */
if (!dirlist || !choose) {
errno = EINVAL;
return -1;
}

/* Loop over all specified directories. */
for (d = 0; dirlist[d] != NULL; d++) {
dir = opendir(dirlist[d]);
if (dir == NULL)
continue;

while (1) {

errno = 0;
ent = readdir(dir);
if (ent == NULL)
break;

/* Device name must start with "event" or "hiddev". */
if (strncmp(ent->d_name, "event", 5) != 0 &&
strncmp(ent->d_name, "hiddev", 6) != 0)
continue;

/* Open device. */
do {
fd = openat(dirfd(dir), ent->d_name, O_RDONLY | O_NOCTTY);
} while (fd == -1 && errno == EINTR);
if (fd == -1)
continue;

/* Get driver version; verifies we have an event device we understand. */
version = 0;
if (ioctl(fd, EVIOCGVERSION, &version)) {
/* Not an event device. */
close(fd);
continue;
}
if (version != EV_VERSION)
continue;

/* Get device ID, if possible. */
memset(&id, 0, sizeof id);
ioctl(fd, EVIOCGID, &id);

/* Get device name, if possible. */
memset(name_buf, 0, sizeof name_buf);
ioctl(fd, EVIOCGNAME(sizeof name_buf - 1), name_buf);

/* Ask the callback function. */
result = choose(fd, dirlist[d], ent->d_name, name_buf, id.bustype, id.vendor, id.product, custom);
if (result == 1) {
closedir(dir);
errno = 0;
return fd;
} else
if (result == -1) {
result = errno;
close(fd);
closedir(dir);
errno = result;
return -1;
}

/* Nope, this was not chosen. */
close(fd);
}

closedir(dir);
}

/* No device selected. */
errno = 0;
return -1;
}

static const char *bus_name(const int bustype)
{
static char buffer[16];
switch (bustype) {
case 0: return "None, or built-in (0)";
case BUS_PCI: return "PCI (BUS_PCI)";
case BUS_ISAPNP: return "ISA PnP (BUS_ISAPNP)";
case BUS_USB: return "USB (BUS_USB)";
case BUS_HIL: return "HP-HIL (BUS_HIL)";
case BUS_BLUETOOTH: return "Bluetooth (BUS_BLUETOOTH)";
case BUS_VIRTUAL: return "Virtual (BUS_VIRTUAL)";
case BUS_ISA: return "ISA (BUS_ISA)";
case BUS_I8042: return "i8042 (BUS_I8042)";
case BUS_XTKBD: return "XT Keyboard (BUS_XTKBD)";
case BUS_RS232: return "Serial port (BUS_RS232)";
case BUS_GAMEPORT: return "Gameport (BUS_GAMEPORT)";
case BUS_PARPORT: return "Parallel port (BUS_PARPORT)";
case BUS_AMIGA: return "Amiga (BUS_AMIGA)";
case BUS_ADB: return "ADB (BUS_ADB)";
case BUS_I2C: return "I2C (BUS_I2C)";
case BUS_HOST: return "Host (BUS_HOST)";
case BUS_GSC: return "GSC (BUS_GSC)";
case BUS_ATARI: return "Atari (BUS_ATARI)";
case BUS_SPI: return "SPI (BUS_SPI)";
default:
snprintf(buffer, sizeof buffer, "%04x", bustype & 65535);
return (const char *)buffer;
}
}

/* Return: 1 for "return this device descriptor to original caller",
* 0 for "no, this device is not interesting",
* or -1 for "uh-oh, and error occurred; see errno".
*/
static int example_chooser(int fd, const char *dir, const char *dev,
const char *name,
int bustype, int vendor, int product, void *custom)
{
FILE *out = (FILE *)custom;

#if defined(MOUSE)
/* Only list event devices whose names contain 'mouse' */
if (fnmatch("*mouse*", name, FNM_CASEFOLD) == FNM_NOMATCH)
return 0;
#endif

fprintf(out, "%s/%s:\n", dir, dev);
fprintf(out, " Name: '%s'\n", name);
fprintf(out, " Bustype: %s\n", bus_name(bustype));
if (vendor || product)
fprintf(out, " Vendor: %04x\n"
" Product: %04x\n", vendor, product);
fputc('\n', out);
fflush(out);

/* Do not select this descriptor. */
return 0;
}

int main(int argc, char *argv[])
{
int result;

if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s -h | --help\n", argv[0]);
fprintf(stderr, " %s\n", argv[0]);
fprintf(stderr, " %s INPUT-DEVICE-DIR [ INPUT-DEVICE-DIR .. ]\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "This program outputs basic information on each event input device\n");
fprintf(stderr, "in the input device directories.\n");
fprintf(stderr, "\n");
return EXIT_FAILURE;
}

if (argc > 1)
result = select_event_device((const char *const *)(argv + 1), example_chooser, stdout);
else
result = select_event_device(input_device_dirs, example_chooser, stdout);

if (result != -1) {
fprintf(stderr, "Input device descriptor %d selected.\n", result);
close(result);
return EXIT_SUCCESS;
} else
if (errno != 0) {
fprintf(stderr, "Failed: %s.\n", strerror(errno));
return EXIT_FAILURE;
} else {
fprintf(stderr, "No input device selected.\n");
return EXIT_SUCCESS;
}
}

If you compile it with -DMOUSE, then only devices whose names include the word "mouse" are listed. Otherwise it lists all input event devices in the directories specified on the command line. If there are no command line parameters, it lists all input event devices in /dev/input/. You can run it (as root) with e.g. parameters /dev/input /dev/usb .

In real world use, you'd replace example_chooser() with a function that uses some user-specified criteria to return the (open file descriptor to the) first matching device. You could use fnmatch() to compare the device name to an user-specified string, and/or the bustype and vendor and product values (if bustype is BUS_USB, vendor=0000 to ffff, product=0000 to ffff).

If you want, you could modify it to instead construct a linked list node from the device (do not save the descriptor, as it is closed when you return from the chooser function automatically), and display say "name (bustype vendor:product)" or similar string for each, for the user to select from.

There are lots of possibilities, and it just depends on whether your target application is a demonstration program, a service daemon, a rarely-used utility, or a commonly-used utility, which interface makes the most sense. Myself, I switch mice and keyboards pretty regularly, and I'd prefer it to be easy to change when I change devices, but not have to select the same device every single time I run it.

Actually, if this is an user-specific program, you could create a subdirectory in the user home directory, ~/.yourprog, and put the symlink to the mouse device there: ~/.yourprog/mousedev -> /dev/input/by-id/usb-Logitech_USB_Receiver-if01-event-mouse. Then you can write a very simple shell script, that uses a variant of the above program, and zenity, to produce a nice GUI for the user to select an input device (using the devices' human readable names). If the user selects one, the script updates the symlink and runs your program. This is very common; for example, the Firefox browser uses a very similar approach to run.


Where did you get this information from?
Hey, I know my Linux stuff.

See /usr/include/linux/input.h (on your own Linux machine), descriptions in the Linux kernel Documentation/input/ (https://www.kernel.org/doc/Documentation/input/) directory, and the up-to-date evtest sources (http://cgit.freedesktop.org/evtest/tree/evtest.c).

Nominal Animal
03-30-2015, 11:09 PM
If this is some kind of user-specific application, you could use a symlink in per-user application directory ~/.yourapp/input-event-mouse -> /dev/input/eventN .

First, you'd need a simple privileged application to list current input devices. Save the following as list-evdevs.c:


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

struct slist {
struct slist *next;
size_t size;
char data[];
};

static struct slist *devices = NULL;


const char *canonical_path(const char *path)
{
struct slist *node, *check;
char *rpath;
size_t rpathlen;

errno = EINVAL;
rpath = realpath(path, NULL);
if (rpath == NULL) {
if (errno == EINVAL || errno == ENOENT ||
errno == EACCES || errno == ENOTDIR)
errno = 0;
return NULL;
}

rpathlen = strlen(rpath);
if (rpathlen < 1 || strcspn(rpath, "|\n") != rpathlen) {
free(rpath);
errno = 0;
return NULL;
}

node = realloc(rpath, sizeof *node + rpathlen + 1);
if (node == NULL) {
free(rpath);
errno = ENOMEM;
return NULL;
}
memmove(node->data, node, rpathlen + 1);

node->next = NULL;
node->size = rpathlen;

for (check = devices; check != NULL; check = check->next)
if (check->size == node->size && !strcmp(check->data, node->data)) {
free(node);
errno = 0;
}

node->next = devices;
devices = node;

errno = 0;
return node->data;
}


int main(int argc, char *argv[])
{
const char *path, *file;
char name[512];
struct input_id id;
int arg, fd, version;

if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s EVENT-DEVICE ...\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "This program lists all event input devices in format\n");
fprintf(stderr, " DEVPATH|BUSTYPE|VENDOR:PRODUCT|<NAME>|\n");
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}

for (arg = 1; arg < argc; arg++) {

path = canonical_path(argv[arg]);
if (path == NULL) {
const int cause = errno;
if (cause == 0)
continue;
fprintf(stderr, "%s: %s.\n", argv[arg], strerror(cause));
return EXIT_FAILURE;
}

/* Require /dev/ or /sys/ */
if (strncmp(path, "/dev/", 5) != 0 &&
strncmp(path, "/sys/", 5) != 0)
continue;

/* Find filename component, starting with last slash. */
file = strrchr(path, '/');
if (file == NULL || file[0] != '/')
continue;

/* Require event* or hiddev* */
if (strncmp(file, "/event", 6) != 0 &&
strncmp(file, "/hiddev", 7) != 0)
continue;

do {
fd = open(path, O_RDONLY | O_NOCTTY);
} while (fd == -1 && errno == EINTR);
if (fd == -1) {
const int cause = errno;
fflush(stdout);
fprintf(stderr, "%s: %s.\n", argv[arg], strerror(cause));
fflush(stderr);
continue;
}

version = 0;
errno = 0;
if (ioctl(fd, EVIOCGVERSION, &version) != 0) {
fflush(stdout);
fprintf(stderr, "%s: Not an input event device.\n", argv[arg]);
fflush(stderr);
close(fd);
continue;
}
if (version != EV_VERSION) {
fflush(stdout);
fprintf(stderr, "%s: Invalid version %x - expected %x\n", argv[arg], version, EV_VERSION);
fflush(stderr);
close(fd);
continue;
}

memset(&id, 0, sizeof id);
ioctl(fd, EVIOCGID, &id);

memset(name, 0, sizeof name);
ioctl(fd, EVIOCGNAME(sizeof name - 1), name);

if (close(fd)) {
fflush(stdout);
fprintf(stderr, "%s: %s.\n", argv[arg], strerror(EIO));
fflush(stderr);
}

switch (id.bustype) {
case 0:
if (id.vendor || id.product)
printf("%s|Built-in|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
else
printf("%s|||%s|\n", path, name);
break;
case BUS_PCI:
printf("%s|PCI|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_ISAPNP:
printf("%s|ISA-PnP|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_USB:
printf("%s|USB|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_HIL:
printf("%s|HIL|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_BLUETOOTH:
printf("%s|Bluetooth|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_VIRTUAL:
printf("%s|Virtual|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_ISA:
printf("%s|ISA|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_I8042:
printf("%s|i8042|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_XTKBD:
printf("%s|XT Keyboard|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_RS232:
printf("%s|Serial/RS232|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_GAMEPORT:
printf("%s|Gameport|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_PARPORT:
printf("%s|Parallel port|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_AMIGA:
printf("%s|Amiga|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_ADB:
printf("%s|ADB/Apple Desktop Bus|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_I2C:
printf("%s|I2C|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_HOST:
printf("%s|Host|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_GSC:
printf("%s|GSC|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_ATARI:
printf("%s|Atari|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;
case BUS_SPI:
printf("%s|SPI|%04x:%04x|%s|\n", path, id.vendor, id.product, name);
break;

default:
printf("%s|%d|%04x:%05x|%s|\n", path, id.bustype, id.vendor, id.product, name);
}
}

return EXIT_SUCCESS;
}

It will only check device nodes under /dev and /sys that begin with event or hiddev, so it should be safe to install setuid root:


gcc -Wall -O2 list-evdevs.c -o list-evdevs
sudo install -m 04755 -o root -g root list-evdevs /usr/local/bin/list-evdevs


Finally, let's assume your application is named mousewidget, and it uses ~/.mousewidget/mouse-event to point to the mouse device it reads/consumes events from.

The following script uses the above list-evdevs and zenity to pop up a simple, nice GUI, for the user to pick a mouse from. If the user cancels, the symlink is kept as is, otherwise the symlink is updated to point to the right device. If the symlink exists and points to a device, your application is started.


#!/bin/bash
appdir="$HOME/.mousewidget"
symlink="$appdir/mouse-event"

# Identify and parse event devices in all imaginable locations.
IFS=$'|'
args=()
while read DEV BUS ID NAME DUMMY ; do
case "$NAME" in

# Only include devices with "mouse" in their name.
*[Mm][Oo][Uu][Ss][Ee]*) args+=("$DEV" "$NAME")
;;

esac
done < <(/usr/local/bin/list-evdevs /dev/input/event* /dev/event* /dev/usb/hiddev* /dev/hiddev*)

# Use Zenity to pop up a selection list widget.
target=$(zenity --list \
--title="Select desired mouse" \
--text="" \
--column="Device" --column="Name" \
--hide-column=1 --print-column=1 \
"${args[@]}")

# Remove extra columns...
target="${target%%|*}"

# If the user selected one, update the symlink.
if [ -n "$target" ]; then
mkdir -m 0700 "$appdir" &>/dev/null
rm -f "$symlink" &>/dev/null
ln -sf "$target" "$symlink"
fi

# Abort if there is no symlink, or if symlink target no longer exists.
[ -e "$symlink" ] || exit 1

# Execute application
exec /usr/local/bin/yourapp arguments....


Some users like myself get fed up having to always select the same device, so you could have a secondary shell script that only pops up the question if the device is no longer available:

#!/bin/bash
appdir="$HOME/.mousewidget"
symlink="$appdir/mouse-event"

if [ ! -e "$symlink" ]; then
# Missing symlink, or dangling symlink.

# Identify and parse event devices in all imaginable locations.
IFS=$'|'
args=()
while read DEV BUS ID NAME DUMMY ; do
case "$NAME" in

# Only include devices with "mouse" in their name.
*[Mm][Oo][Uu][Ss][Ee]*) args+=("$DEV" "$NAME")
;;

esac
done < <(/usr/local/bin/list-evdevs /dev/input/event* /dev/event* /dev/usb/hiddev* /dev/hiddev*)

# Use Zenity to pop up a selection list widget.
target=$(zenity --list \
--title="Select desired mouse" \
--text="" \
--column="Device" --column="Name" \
--hide-column=1 --print-column=1 \
"${args[@]}")

# Remove extra columns...
target="${target%%|*}"

# If the user selected one, update the symlink.
if [ -n "$target" ]; then
mkdir -m 0700 "$appdir" &>/dev/null
rm -f "$symlink" &>/dev/null
ln -sf "$target" "$symlink"
fi
fi

# Abort if there is no symlink, or if symlink target no longer exists.
[ -e "$symlink" ] || exit 1

# Execute application
# exec /usr/local/bin/yourapp arguments....


zenity (https://help.gnome.org/users/zenity/stable/) is a very nice Gnome utility to create simple interactive windows from shell scripts. If you use KDE, it is trivial to rewrite the above to use kdialog (https://techbase.kde.org/Development/Tutorials/Shell_Scripting_with_KDE_Dialogs) instead. One could even use text-only dialog (http://invisible-island.net/dialog/dialog.html), or just a Bash loop -- none of these exclude the others, by the way; they are just helper scripts that give the native feel to such an utility, with minimal effort.

Hopefully you can see why I was so adamant about not hardcoding any paths -- other than maybe .mousewidget/mouse-event.

In general, if you use such a directory for user-specific configuration files and symlinks, the openat() (http://man7.org/linux/man-pages/man2/openat.2.html) and other *at() functions make it pretty easy, especially if you synthesize a thread-safe AT_HOME:


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

#ifndef AT_HOME
#define AT_HOME get_home_fd()

static int dir_to_descriptor(DIR *dir)
{
unsigned int skipped = 0U;
int fd;

errno = EINVAL;
if (dir == NULL)
return -1;
fd = dup(dirfd(dir));
if (fd == -1) {
const int cause = errno;
closedir(dir);
errno = cause;
return -1;
}

if (closedir(dir)) {
close(fd);
errno = EIO;
return -1;
}

while (1) {
if (fd == STDIN_FILENO)
skipped |= 1U;
else
if (fd == STDOUT_FILENO)
skipped |= 2U;
else
if (fd == STDERR_FILENO)
skipped |= 4U;
else
break;

fd = dup(fd);
}
if (fd == -1) {
if (skipped & 1U)
close(STDIN_FILENO);
if (skipped & 2U)
close(STDOUT_FILENO);
if (skipped & 4U)
close(STDERR_FILENO);

errno = EMFILE;
return -1;
}

return fd;
}

static int home_fd = -1;

static int get_home_fd(void)
{
int fd;

/* Use cached descriptor, if it exists. */
fd = __sync_add_and_fetch(&home_fd, 0);
if (fd != -1)
return fd;

/* Try the HOME environment variable. */
do {
const char *path = getenv("HOME");
DIR *dir;

if (path == NULL || *path != '/')
break;

dir = opendir(path);
if (dir == NULL)
break;

fd = dir_to_descriptor(dir);
if (fd == -1)
return -1;

if (__sync_bool_compare_and_swap(&home_fd, -1, fd))
return fd;

/* Another thread cached it first. */
close(fd);
return __sync_add_and_fetch(&home_fd, 0);

} while (0);

/* Try the passwd database for the current user. */
{
struct passwd buf, *pw;
DIR *dir;
size_t size = 16384;
char *data;
int result;

while (1) {

if (size > (size_t)1024576) {
errno = ENOMEM;
return -1;
}

data = malloc(size);
if (data == NULL) {
errno = ENOMEM;
return -1;
}

result = getpwuid_r(getuid(), &buf, data, size, &pw);
if (pw != NULL)
break;

free(data);
if (result != ERANGE) {
errno = ENOENT;
return -1;
}
size += 4096;
}

if (pw->pw_dir == NULL || pw->pw_dir[0] != '/') {
free(data);
errno = ENOENT;
return -1;
}

dir = opendir(pw->pw_dir);
free(data);
if (dir == NULL) {
errno = ENOENT;
return -1;
}

fd = dir_to_descriptor(dir);
if (fd == -1)
return -1;

if (__sync_bool_compare_and_swap(&home_fd, -1, fd))
return fd;

/* Another thread cached the descriptor before us. */
close(fd);
return __sync_add_and_fetch(&home_fd, 0);
}
}

#endif /* AT_HOME */


int main(void)
{
int devfd;

do {
devfd = openat(AT_HOME, ".mousewidget/mouse-event", O_RDONLY);
} while (devfd == -1 && errno == EINTR);
if (devfd == -1) {
fprintf(stderr, "Cannot open ~/.mousewidget/mouse-event: %s.\n", strerror(errno));
return EXIT_FAILURE;
}

printf("~/.mousewidget/mouse-event opened successfully..\n");

return EXIT_SUCCESS;
}

Above, openat(AT_HOME, path, flags) opens path relative to user home directory, if path is relative (does not start with a slash).

Questions?

Absurd
03-31-2015, 05:38 AM
Man, you are a genius, and I can't thank you enough.

To give some background; me and some student colleagues of mine are working together on some project, and I was given the task to interface with the USB - specifically with any mouse attached to it (it doesn't matter which, just the first one I encounter. If such does not exist I should terminate) - listening to its movements, and delivering this data forward in a format on which we haven't decided yet (a lot of stuff is still under discussion).
To simplify things, we decided (more accurately, I decided) that for now, the user will not be be given the ability to choose between the devices attached to the computer, nor he will even have to worry about this module, or even aware of its existence. It suppose to run in background, do its thing, and, as I mentioned, terminate with an error code if no mouse attached to the computer (or if it unexpectedly detached during execution).

This brings me back to what I want to do, which btw, thanks to you looks simpler than I thought at first.
If I want to interface with the mouse, I figured I'll have to know where to look for it.
So I was thinking /dev/input/by-id will be the way to go. That is why I asked if I can always count on having a named symlink in /dev/input/by-id/ (easier to look for human readable names than to traverse the eventX files and find out which one is associated with a USB-mouse). This way I could determine with absolute certainty if a mouse is present. Furthermore, if more than one is present, I get to choose any one of them.
The code you suggested will give me everything I need (I learned a lot from it).

---------------------------------



udev -- the service in Linux that detects new devices and creates the device nodes and symlinks based on relatively straightforward rules -- does not have to put all input event devices into /dev/input/.

Things like USB-connected thermal, infrared, and magnetic sensors do often use the HID input event interface, because it is such an easy interface. Yet, it does not make much sense to call them "user input devices". So, udev puts them under /dev/usb/. In Ubuntu, if you belong to the plugdev group (run id -Gn to see the list of groups you belong to), you can open and read/control /dev/usb/hiddevN devices without superuser privileges.
So to my best understanding, I shouldn't be concerned about it.





I recommend you find out what it is: just run sudo evtest /dev/usb/hiddev0 and see what it says.

I tried it, but I get this:

evtest: can't get version: Invalid argument

Also tried sudo evtest /dev/input/mouse1, but this gives:

evtest: can't get version: Inappropriate ioctl for device

This works with any of the eventX files, though.
For example, when I try: sudo evtest /dev/input/event5 - which is my USB-mouse, it works (fires up events).



mouseN - mouse device nodes (mousedev)
These are mouse devices, pseudofiles that provide mouse and digitizer tablet events in various formats. You can tell the kernel which format you want to use.
This might be useful. How can I do it? Is it complicated?

Nominal Animal
03-31-2015, 03:43 PM
I was given the task to interface with the USB - specifically with any mouse attached to it (it doesn't matter which, just the first one I encounter. If such does not exist I should terminate) - listening to its movements, and delivering this data forward in a format on which we haven't decided yet (a lot of stuff is still under discussion).
In that case, you can use e.g. this shell script to start your application:


#!/bin/bash
mice=()
for evdev in /dev/input/by-id/usb-*-event-mouse ; do
[ -e "$evdev" ] && mice+=("$evdev")
done
if [ ${#mice[@]} -lt 1 ]; then
echo "No mouse event devices found." >&2
exit 1
fi
if [ ${#mice[@]} -gt 1 ]; then
echo "Warning: ${#mice[@]} mouse event devices found (${mice[@]}); using ${mice[0]}." >&2
fi
exec yourprogram "${mice[0]}"

It will run yourprogram with the mouse device name as a command line parameter, if there is at least one USB mouse input event device. If there are more than one, it'll print a warning, but use the first one found (alphabetically).

Again, this is surprisingly easy; the difficult part, really, is to choose which one works best for your users. Realizing that there are multiple ways to accomplish a task, and that usually the best option is to write small, modular tools that each do one thing well, is a major hurdle for those coming from proprietary OSes where you have the vendor's way to do things, and frameworks you're supposed to stay in.


So to my best understanding, I shouldn't be concerned about it.
No, they're completely normal. The two Logitech wireless USB receivers I have both provide those.

For details on the /dev/hiddevN and /dev/hidrawN devices, and overall about how the Linux Input Subsystem works, see Documentation/hid/hiddev.txt (https://www.kernel.org/doc/Documentation/hid/hiddev.txt) and Documentation/hid/hidraw.txt (https://www.kernel.org/doc/Documentation/hid/hidraw.txt) files from the Linux kernel sources (more (https://www.kernel.org/doc/Documentation/hid/)).


I tried it, but I get this:
Yeah, my fault. I forgot evtest only supports input event devices, not all HID devices.


This might be useful. How can I do it? Is it complicated?
It's the old mouse device interface. Input event devices are easier and better.

When you open /dev/input/mice or one of the /dev/input/mouseN devices, they are initially in PS/2 mouse mode, providing a byte stream of three-byte PS/2 packets. If you write the six bytes "\xf3\xc8\xf3\x64\xf3\x50" to the device, it switches to the ImPS/2 mode (Microsoft Intellimouse mode); if you write the six bytes "\xf3\xc8\xf3\xc8\xf3\x50", it switches to the ExPS/2 mode (Microsoft Intellimouse Explorer mode). Both of the latter provide four-byte packets.

The Linux kernel mousedev driver only supports the poll (\xeb), get ID (\xf2), get info (\xe9), and reset (\xff) commands. These produce a response (one-byte ack, \xfa; followed by an ID byte for get ID, \x60\x03\xc8 for get info, and \xaa\x00 for reset) that does not conform to the above packet lengths. Only the reset, and occasionally poll, are useful.

I don't remember the exact format of the movement packets in the three modes, but you can easily check what the kernel driver provides by looking at drivers/input/mousedev.c:mousedev_packet() (https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/drivers/input/mousedev.c) function.

Absurd
04-01-2015, 04:12 AM
Once again, thank you!

Absurd
04-03-2015, 01:54 PM
Hi Nominal Animal.

Why is the following call to ioctl populates my array with zeroes?


ioctl(fd, EVIOCGPROP(len), unsigned char [len]) /* Get device properties */

When I try it like this:


int main(int argc, char *argv[]) {

int device_fd;
unsigned char device_properties[20]={0};
int i;

do {
device_fd=open(USB_PATH, O_RDONLY);

} while (device_fd==-1 && errno==EINTR);


if (device_fd==-1) {
fprintf(stderr, "open() error: %s\n", strerror(errno));
return 1;
}


/* device properties; this call populates 'device_properties' with zeros... */
if (ioctl(device_fd, EVIOCGPROP(sizeof(device_properties)-1), device_properties)==-1) {
fprintf(stderr, "ioctl() error: %s\n", strerror(errno));
return 1;
}

for (i=0; i<20; ++i) {
/* 'if' never evaluated to true */
if (device_properties[i]) {
printf("bits are on at index %d\n", i);
}
}

return 0;
}

Am I not using it correctly?

Nominal Animal
04-03-2015, 02:26 PM
ioctl(device_fd, EVIOCGPROP(sizeof(device_properties)-1), device_properties)
Remove the -1. You should not have it, because device_properties is a bitmap, not a string.

(I used the corresponding -1 for the strings (EVIOCGNAME, EVIOCGPHYS, and EVIOCGUNIQ ioctls), because I wanted to make sure there was a 0 at end, so I could safely treat them as strings. EVIOCGPROP fills in a bit map, similar to EVIOCGBIT.)

There are currently only four properties it could report:

Bit INPUT_PROP_POINTER. Set if device needs a pointer.
Bit INPUT_PROP_DIRECT. Set for a direct input device.
Bit INPUT_PROP_BUTTONPAD. Set if buttons are located underneath the pad.
Bit INPUT_PROP_SEMI_MT. Set if multiple touch reports only the rectangular area.

I don't have any devices that have any of those properties, so to me, it's quite natural you see all zeros.

Absurd
04-03-2015, 02:50 PM
Remove the -1. You should not have it, because device_properties is a bitmap, not a string.

:D
Yep. This was the result of a copy-paste from an early call to ioctl with EVIOCGNAME...



There are currently only four properties it could report:

Bit INPUT_PROP_POINTER. Set if device needs a pointer.
Bit INPUT_PROP_DIRECT. Set for a direct input device.
Bit INPUT_PROP_BUTTONPAD. Set if buttons are located underneath the pad.
Bit INPUT_PROP_SEMI_MT. Set if multiple touch reports only the rectangular area.

I don't have any devices that have any of those properties, so to me, it's quite natural you see all zeros.

Ow... When I saw "device properties" I had completely different thing in mind...
I thought this will provide information regarding the specification of the device, like, for example - in the case of a mouse - its resolution and stuff like that. (now that I think about, it sounds a bit like a naive expectation to have).

Say, if you don't mind me asking, how do you know all this stuff?
Is there a book you could recommend, or some other source online that will help me know, for example, what to expect from ioctl() when it is called with EVIOCGPROP as argument?
Or is it stuff you can only gain with experience?

Thanks a lot. appreciate it.

Nominal Animal
04-03-2015, 06:30 PM
I thought this will provide information regarding the specification of the device, like, for example - in the case of a mouse - its resolution and stuff like that.

No, you use EVIOCGABS(ABS_axis) ioctl() for that.


int fd; /* Open descriptor to the input event device */
struct input_absinfo a; /* See /usr/include/linux/input.h for fields */

if (!ioctl(fd, EVIOCGABS(ABS_X), &a)) {
/* Device x axis resolution is (a.resolution) steps per mm */
}
if (!ioctl(fd, EVIOCGABS(ABS_Y), &a))
/* Device y axis resolution is (a.resolution) steps per mm */
}
if (!ioctl(fd, EVIOCGABS(ABS_Z), &a))
/* Device z axis resolution is (a.resolution) steps per mm */
}
if (!ioctl(fd, EVIOCGABS(ABS_RX), &a))
/* Device rotation around x axis reports (a.resolution) steps per radian */
}
if (!ioctl(fd, EVIOCGABS(ABS_RY), &a))
/* Device rotation around y axis reports (a.resolution) steps per radian */
}
if (!ioctl(fd, EVIOCGABS(ABS_RZ), &a))
/* Device rotation around z axis reports (a.resolution) steps per radian */
}

I'm not sure if any actual mice report it, though. (And some devices, even if they report it, report the resolution as 0.)
(As far as I know, there is no EVIOCGREL or struct input_relinfo structure, to get information on relative axis -- and ABS_X == REL_X, so that does not make any difference either.)

I don't see how useful resolution information etc. would be, anyway. For example, X.org just uses configurable sensitivity and acceleration behaviour. Ssee man 1 xinput, or xinput --list and xinput --list-props N where N is one of the id= numbers listed.


Say, if you don't mind me asking, how do you know all this stuff?
Long experience, I guess. Having no life or hobbies helps.

I think my first encounter with the Linux input subsystem was the LinuxJournal, The Linux USB Input Subsystem, part I (http://www.linuxjournal.com/article/6396) and Using the Input Subsystem, part II (http://www.linuxjournal.com/article/6429) article pair. It's from 2003, but that just shows it is a stable interface; it's quite good an introduction even today.

I delved a bit deeper when I did my first microcontroller project: an USB keyboard, that is actually a beefy arcade joystick and some arcade buttons, for playing Linux and online flash games using a nice arcade stick. I used a Teensy++ 2.0, as it was cheap and has native USB support -- it really *is* an USB keyboard, no FTDI-USB-serial-conversion, load-this-driver stuff. No drivers, a real USB thing. Really fun, I recommend it. (I spent less than 50 total on it, all parts included, even the Baltic Birch solid wood 0.8m0.3m laminated board.)


Is there a book you could recommend, or some other source online that will help me
I listed some links earlier. linux input event uinput evdev are good search terms.

I find it very difficult to recommend anything larger (like books), because it depends so much on the reader whether an approach works or not. Besides, I hate "framework" thinking in general -- relying on one source or approach for all solutions -- and prefer things modular. Especially including potential information sources.

I do not bother to commit any of the details to memory, BTW. For each answer I've posted in this thread, I have at least once run less /usr/include/linux/input.h or grep -e EVIOC /usr/include/linux/input.h or used the search-on-this-page built-in to my browser on one of the pages I've linked here. I easily forget details, like whether it is memset(ptr, len, value) or memset(ptr, value, len). man 3 memset will tell you the latter is correct.

I hope you realize nobody writes this code (any low-level character-device code, really) or these answers straight out of their memory! Personally, I just sketch the general outline, then look up and fill the details. Usually end up writing a dozen-line test program, or modifying one already close enough, too, just to make sure I'm not talking out of my butt.


for example, what to expect from ioctl() when it is called with EVIOCGPROP as argument?
Documentation/input/event-codes.txt (https://www.kernel.org/doc/Documentation/input/event-codes.txt) from the kernel sources, and /usr/include/linux/input.h (lxr.free-electrons.com/source/include/uapi/linux/input.h) cover most of the stuff.

Or is it stuff you can only gain with experience?
It's not a complicated subsystem, it's just that when you start with it, you kinda have to learn a lot of stuff about detailed, low-level use of character devices, and nobody as yet has written a "Here is how you use the Linux Input Subsystem" book yet. Each of the parts are quite straightforward, but there are quite a few of them, making the learning curve quite steep, before you feel comfortable working with it. Most of it is just the sheer number of new things.

It's like this: You've just jumped into the deep end (of low-level userspace programming, dealing with character devices and so on), and although you do need to work your brain to move forward, it's not that bad at all. Beware, you might find you really like it. :)