diff --git a/tools/libinput-record.c b/tools/libinput-record.c index 6d45efc5..453c3ebc 100644 --- a/tools/libinput-record.c +++ b/tools/libinput-record.c @@ -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, @@ -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"); @@ -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; } @@ -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; } diff --git a/tools/libinput-record.man b/tools/libinput-record.man index a537d15f..e85eb10d 100644 --- a/tools/libinput-record.man +++ b/tools/libinput-record.man @@ -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. @@ -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 diff --git a/tools/test-tool-option-parsing.py b/tools/test-tool-option-parsing.py index e96d1abf..07760951 100755 --- a/tools/test-tool-option-parsing.py +++ b/tools/test-tool-option-parsing.py @@ -269,6 +269,7 @@ class TestRecord(TestLibinputTool): 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']) @@ -280,9 +281,14 @@ class TestRecord(TestLibinputTool): 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__':