Skip to content

Commit

Permalink
tools: record: allow for an output file without --o
Browse files Browse the repository at this point in the history
  libinput record touchpad.yml /dev/input/eventX
or just
  libinput record touchpad.yml
are simpler invocations and since we're quite limited in what we can record
(i.e. only device files) we can just check the argument list to figure out
whether there is something to record to.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
  • Loading branch information
whot committed Mar 16, 2020
1 parent c48e5cf commit 6ed3a0b
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 5 deletions.
94 changes: 90 additions & 4 deletions tools/libinput-record.c
Expand Up @@ -2411,6 +2411,28 @@ usage(void)
program_invocation_short_name);
}

enum ftype {
F_FILE = 8,
F_DEVICE,
F_NOEXIST,
};

static inline enum ftype is_char_dev(const char *path)
{
struct stat st;

if (strneq(path, "/dev", 4))
return F_DEVICE;

if (stat(path, &st) != 0) {
if (errno == ENOENT)
return F_NOEXIST;
return F_FILE;
}

return S_ISCHR(st.st_mode) ? F_DEVICE : F_FILE;
}

enum options {
OPT_AUTORESTART,
OPT_HELP,
Expand Down Expand Up @@ -2491,6 +2513,72 @@ main(int argc, char **argv)
}
}

ndevices = argc - optind;

/* We allow for multiple arguments after the options, *one* of which
* may be the output file. That one must be the first or the last to
* prevent users from running
* libinput record /dev/input/event0 output.yml /dev/input/event1
* because this will only backfire anyway.
*/
if (ndevices >= 1 && output_arg == NULL) {
char *first, *last;
enum ftype ftype_first;

first = argv[optind];
last = argv[argc - 1];

ftype_first = is_char_dev(first);
if (ndevices == 1) {
/* arg is *not* a char device, so let's assume it's
* the output file */
if (ftype_first != F_DEVICE) {
output_arg = first;
optind++;
ndevices--;
}
/* multiple arguments, yay */
} else {
enum ftype ftype_last = is_char_dev(last);
/*
first is device, last is file -> last
first is device, last is device -> noop
first is device, last !exist -> last
first is file, last is device -> first
first is file, last is file -> error
first is file, last !exist -> error
first !exist, last is device -> first
first !exist, last is file -> error
first !exit, last !exist -> error
*/
#define _m(f, l) (((f) << 8) | (l))
switch (_m(ftype_first, ftype_last)) {
case _m(F_FILE, F_DEVICE):
case _m(F_FILE, F_NOEXIST):
case _m(F_NOEXIST, F_DEVICE):
output_arg = first;
optind++;
ndevices--;
break;
case _m(F_DEVICE, F_FILE):
case _m(F_DEVICE, F_NOEXIST):
output_arg = last;
ndevices--;
break;
case _m(F_DEVICE, F_DEVICE):
break;
case _m(F_FILE, F_FILE):
case _m(F_NOEXIST, F_FILE):
case _m(F_NOEXIST, F_NOEXIST):
fprintf(stderr, "Ambiguous device vs output file list. Please use --output-file.\n");
rc = EXIT_INVALID_USAGE;
goto out;
}
#undef _m
}
}


if (ctx.timeout > 0 && output_arg == NULL) {
fprintf(stderr,
"Option --autorestart requires --output-file\n");
Expand All @@ -2499,15 +2587,13 @@ main(int argc, char **argv)

ctx.outfile = safe_strdup(output_arg);

ndevices = argc - optind;

if (all) {
char **devices; /* NULL-terminated */
char **d;

if (output_arg == NULL) {
fprintf(stderr,
"Option --all requires --output-file\n");
"Option --all requires an output file\n");
rc = EXIT_INVALID_USAGE;
goto out;
}
Expand All @@ -2527,7 +2613,7 @@ main(int argc, char **argv)
} else if (ndevices > 1) {
if (ndevices > 1 && output_arg == NULL) {
fprintf(stderr,
"Recording multiple devices requires --output-file\n");
"Recording multiple devices requires an output file\n");
rc = EXIT_INVALID_USAGE;
goto out;
}
Expand Down
15 changes: 14 additions & 1 deletion tools/libinput-record.man
Expand Up @@ -10,7 +10,17 @@ prints them in a format that can later be replayed with the \fBlibinput
replay(1)\fR tool. This tool needs to run as root to read from the device.
.PP
The output of this tool is YAML, see \fBFILE FORMAT\fR for more details.
By default it prints to stdout unless the \fB-o\fR option is given.
By default it prints to stdout unless an output file is provided. For
example, these are valid invocations:

.B libinput record /dev/input/event3 touchpad.yml

.B libinput record recording.yml

.B libinput record --all all-devices.yml

.B libinput record /dev/input/event3 /dev/input/event4 tp-and-keyboard.yml

.PP
The events recorded are independent of libinput itself, updating or
removing libinput will not change the event stream.
Expand Down Expand Up @@ -44,6 +54,9 @@ greater than 0.
.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
not an input device, the first \fBor\fR last argument will be the output
file.
.TP 8
.B \-\-show\-keycodes
Show keycodes as-is in the recording. By default, common keys are obfuscated
Expand Down
6 changes: 6 additions & 0 deletions tools/test-tool-option-parsing.py
Expand Up @@ -269,6 +269,7 @@ def test_multiple_deprecated(self):

def test_all(self):
self.run_command_success(['--all', '-o', self.outfile])
self.run_command_success(['--all', self.outfile])

def test_autorestart(self):
self.run_command_success(['--autorestart=2'])
Expand All @@ -280,9 +281,14 @@ def test_outfile(self):

def test_device_single(self):
self.run_command_success(['/dev/input/event0'])
self.run_command_success(['/dev/input/event0', self.outfile])
self.run_command_success([self.outfile, '/dev/input/event0'])
self.run_command_success([self.outfile, '/dev/input/event0'])

def test_device_multiple(self):
self.run_command_success(['-o', self.outfile, '/dev/input/event0', '/dev/input/event1'])
self.run_command_success([self.outfile, '/dev/input/event0', '/dev/input/event1'])
self.run_command_success(['/dev/input/event0', '/dev/input/event1', self.outfile])


if __name__ == '__main__':
Expand Down

0 comments on commit 6ed3a0b

Please sign in to comment.