I_LIBINPUTDEV = 4, /* nodes inside libinput: (the
device description */
I_EVENTTYPE = 4, /* event type (evdev:, libinput:,
- ...) */
+ hidraw:) */
I_EVENT = 6, /* event data */
};
struct libevdev *evdev_prev; /* previous value, used for EV_ABS
deltas */
struct libinput_device *device;
+ struct list hidraw_devices;
struct {
bool is_touch_device;
FILE *fp;
};
+struct hidraw {
+ struct list link;
+ struct record_device *device;
+ int fd;
+ char *name;
+};
+
struct record_context {
int timeout;
bool show_keycodes;
}
}
+static bool
+handle_hidraw(struct hidraw *hidraw)
+{
+ struct record_device *d = hidraw->device;
+ unsigned char report[4096];
+ const char *sep = "";
+ struct timespec ts;
+ struct timeval tv;
+ uint64_t time;
+
+ int rc = read(hidraw->fd, report, sizeof(report));
+ if (rc <= 0)
+ return false;
+
+ /* hidraw doesn't give us a timestamps, we have to make them up */
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ time = s2us(ts.tv_sec) + ns2us(ts.tv_nsec);
+
+ /* The first evdev event is guaranteed to have an event time earlier
+ than now, so we don't set the offset here, we rely on the evdev
+ events to do so. This potentially leaves us with multiple hidraw
+ events at timestap 0 but it's too niche to worry about. */
+ if (d->ctx->offset == 0)
+ time = 0;
+ else
+ time = time_offset(d->ctx, time);
+
+ tv = us2tv(time);
+
+ iprintf(d->fp, I_EVENTTYPE, "- hid:\n");
+ iprintf(d->fp, I_EVENT, "time: [%3lu, %6lu]\n", tv.tv_sec, tv.tv_usec);
+ iprintf(d->fp, I_EVENT, "%s: [", hidraw->name);
+
+ for (int byte = 0; byte < rc; byte++) {
+ if (byte % 16 == 0) {
+ iprintf(d->fp, I_NONE, "%s\n", sep);
+ iprintf(d->fp, I_EVENT, " ");
+ iprintf(d->fp, I_NONE, "0x%02x", report[byte]);
+ } else {
+ iprintf(d->fp, I_NONE, "%s0x%02x", sep, report[byte]);
+ }
+ sep = ", ";
+ }
+ iprintf(d->fp, I_NONE, "\n");
+ iprintf(d->fp, I_EVENT, "]\n");
+
+ return true;
+}
+
static bool
handle_libinput_events(struct record_context *ctx,
struct record_device *d,
handle_libinput_events(ctx, ctx->first_device, true);
}
+static void
+hidraw_dispatch(struct record_context *ctx, int fd, void *data)
+{
+ struct hidraw *hidraw = data;
+
+ ctx->had_events = true;
+ ctx->timestamps.had_events_since_last_time = true;
+ handle_hidraw(hidraw);
+}
+
static int
dispatch_sources(struct record_context *ctx)
{
arm_timer(timerfd);
list_for_each(d, &ctx->devices, link) {
+ struct hidraw *hidraw;
+
add_source(ctx, libevdev_get_fd(d->evdev), evdev_dispatch, d);
+
+ list_for_each(hidraw, &d->hidraw_devices, link) {
+ add_source(ctx, hidraw->fd, hidraw_dispatch, hidraw);
+ }
}
if (ctx->libinput) {
d->ctx = ctx;
d->devnode = safe_strdup(path);
+ list_init(&d->hidraw_devices);
+
fd = open(d->devnode, O_RDONLY|O_NONBLOCK);
if (fd < 0) {
fprintf(stderr,
return true;
}
+static bool
+init_hidraw(struct record_context *ctx)
+{
+ struct record_device *dev;
+
+ list_for_each(dev, &ctx->devices, link) {
+ char syspath[PATH_MAX];
+ DIR *dir;
+ struct dirent *entry;
+
+ snprintf(syspath,
+ sizeof(syspath),
+ "/sys/class/input/%s/device/device/hidraw",
+ safe_basename(dev->devnode));
+ dir = opendir(syspath);
+ if (!dir)
+ continue;
+
+ while ((entry = readdir(dir))) {
+ char hidraw_node[PATH_MAX];
+ int fd;
+ struct hidraw *hidraw = NULL;
+
+ if (!strstartswith(entry->d_name, "hidraw"))
+ continue;
+
+ snprintf(hidraw_node,
+ sizeof(hidraw_node),
+ "/dev/%s",
+ entry->d_name);
+ fd = open(hidraw_node, O_RDONLY|O_NONBLOCK);
+ if (fd == -1)
+ continue;
+
+ hidraw = zalloc(sizeof(*hidraw));
+ hidraw->fd = fd;
+ hidraw->name = strdup(entry->d_name);
+ hidraw->device = dev;
+ list_insert(&dev->hidraw_devices, &hidraw->link);
+ }
+ closedir(dir);
+ }
+
+ return true;
+}
+
static void
usage(void)
{
OPT_MULTIPLE,
OPT_ALL,
OPT_LIBINPUT,
+ OPT_HIDRAW,
OPT_GRAB,
};
{ "all", no_argument, 0, OPT_ALL },
{ "help", no_argument, 0, OPT_HELP },
{ "with-libinput", no_argument, 0, OPT_LIBINPUT },
+ { "with-hidraw", no_argument, 0, OPT_HIDRAW },
{ "grab", no_argument, 0, OPT_GRAB },
{ 0, 0, 0, 0 },
};
struct record_device *d;
const char *output_arg = NULL;
- bool all = false, with_libinput = false, grab = false;
+ bool all = false,
+ with_libinput = false,
+ with_hidraw = false,
+ grab = false;
int ndevices;
int rc = EXIT_FAILURE;
char **paths = NULL;
case OPT_LIBINPUT:
with_libinput = true;
break;
+ case OPT_HIDRAW:
+ with_hidraw = true;
+ fprintf(stderr, "# WARNING: do not type passwords while recording HID reports\n");
+ break;
case OPT_GRAB:
grab = true;
break;
if (with_libinput && !init_libinput(&ctx))
goto out;
+ if (with_hidraw && !init_hidraw(&ctx))
+ goto out;
+
rc = mainloop(&ctx);
out:
strv_free(paths);
list_for_each_safe(d, &ctx.devices, link) {
+ struct hidraw *hidraw;
+
+ list_for_each_safe(hidraw, &d->hidraw_devices, link) {
+ close(hidraw->fd);
+ list_remove(&hidraw->link);
+ free(hidraw->name);
+ free(hidraw);
+ }
+
if (d->device)
libinput_device_unref(d->device);
free(d->devnode);
.PD 1
Specifies the output file to use. If \fB\-\-autorestart\fR is given,
the filename is used as prefix only.
-Where \-\-output-file is not given and the first \fBor\fR last argument is
+Where \-\-output-file is not given and the first \fBor\fR last argument is
not an input device, the first \fBor\fR last argument will be the output
file.
.TP 8
See section
.B RECORDING LIBINPUT EVENTS
for more details.
+.TP 8
+.B \-\-with-hidraw
+Record hidraw events alongside device events.
+.B DO NOT TYPE SENSITIVE DATA.
+See
+.B RECORDING HID REPORTS
+for more details.
.SH RECORDING MULTIPLE DEVICES
Sometimes it is necessary to record the events from multiple devices
affect the running desktop session and does not (can not!) copy any
configuration options from that session.
+.SH RECORDING HID REPORTS
+When the \fB\-\-with-hidraw\fR commandline option is given,
+\fBlibinput\-record\fR searches for the hidraw node(s) of the given devices
+and prints any incoming HID reports from those devices.
+.PP
+HID reports are \fBnot obfuscated\fR and a sufficiently
+motivated person could recover the key strokes from the logs. Do not type
+passwords while recording HID reports.
+
.SH FILE FORMAT
The output file format is in YAML and intended to be both human-readable and
machine-parseable. Below is a short example YAML file, all keys are detailed
- ModelAppleTouchpad=1
- AttrSizeHint=32x32
events:
+ - hid:
+ time: [ 0, 0]
+ hidraw0: [1, 2, 3, 4]
- evdev:
- [ 0, 0, 3, 57, 1420] # EV_ABS / ABS_MT_TRACKING_ID 1420
- [ 0, 0, 3, 53, 1218] # EV_ABS / ABS_MT_POSITION_X 1218
input_event\fR in decimal format. The last item in the list is always the
\fBSYN_REPORT\fR of this event frame. The next event frame starts a new
\fBevdev\fR dictionary entry in the parent \fBevents\fR list.
-
+.TP 8
+.B { hid: "hidrawX": [ 12, 34, 56 ], ...] }
+The \fBhid\fR dictionary contains the hid reports in decimal format, with
+the hidraw node as key. The special key \fBtime\fR denotes the current time
+when the report was read from the kernel.
+.PP
+Note that the kernel does not provide timestamps for hidraw events and the
+timestamps provided are from \fBclock_gettime(3)\fR. They may be greater
+than a subsequent evdev event's timestamp.
.SH NOTES
.PP
This tool records events from the kernel and is independent of libinput. In