diff --git a/app/src/cli.c b/app/src/cli.c index 28474a9d..f94504c6 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -15,15 +15,47 @@ #define STR_IMPL_(x) #x #define STR(x) STR_IMPL_(x) +#define OPT_RENDER_EXPIRED_FRAMES 1000 +#define OPT_WINDOW_TITLE 1001 +#define OPT_PUSH_TARGET 1002 +#define OPT_ALWAYS_ON_TOP 1003 +#define OPT_CROP 1004 +#define OPT_RECORD_FORMAT 1005 +#define OPT_PREFER_TEXT 1006 +#define OPT_WINDOW_X 1007 +#define OPT_WINDOW_Y 1008 +#define OPT_WINDOW_WIDTH 1009 +#define OPT_WINDOW_HEIGHT 1010 +#define OPT_WINDOW_BORDERLESS 1011 +#define OPT_MAX_FPS 1012 +#define OPT_LOCK_VIDEO_ORIENTATION 1013 +#define OPT_DISPLAY_ID 1014 +#define OPT_ROTATION 1015 +#define OPT_RENDER_DRIVER 1016 +#define OPT_NO_MIPMAPS 1017 +#define OPT_CODEC_OPTIONS 1018 +#define OPT_FORCE_ADB_FORWARD 1019 +#define OPT_DISABLE_SCREENSAVER 1020 +#define OPT_SHORTCUT_MOD 1021 +#define OPT_NO_KEY_REPEAT 1022 +#define OPT_FORWARD_ALL_CLICKS 1023 +#define OPT_LEGACY_PASTE 1024 +#define OPT_ENCODER_NAME 1025 +#define OPT_POWER_OFF_ON_CLOSE 1026 +#define OPT_V4L2_SINK 1027 +#define OPT_DISPLAY_BUFFER 1028 +#define OPT_V4L2_BUFFER 1029 + struct sc_option { char shortopt; + int longopt_id; // either shortopt or longopt_id is non-zero const char *longopt; // no argument: argdesc == NULL && !optional_arg // optional argument: argdesc != NULL && optional_arg // required argument: argdesc != NULL && !optional_arg const char *argdesc; bool optional_arg; - const char *text; + const char *text; // if NULL, the option does not appear in the help }; #define MAX_EQUIVALENT_SHORTCUTS 3 @@ -32,8 +64,14 @@ struct sc_shortcut { const char *text; }; +struct sc_getopt_adapter { + char *optstring; + struct option *longopts; +}; + static const struct sc_option options[] = { { + .longopt_id = OPT_ALWAYS_ON_TOP, .longopt = "always-on-top", .text = "Make scrcpy window always on top (above other windows).", }, @@ -46,6 +84,7 @@ static const struct sc_option options[] = { "Default is " STR(DEFAULT_BIT_RATE) ".", }, { + .longopt_id = OPT_CODEC_OPTIONS, .longopt = "codec-options", .argdesc = "key[:type]=value[,...]", .text = "Set a list of comma-separated key:type=value options for the " @@ -57,6 +96,7 @@ static const struct sc_option options[] = { "", }, { + .longopt_id = OPT_CROP, .longopt = "crop", .argdesc = "width:height:x:y", .text = "Crop the device screen on the server.\n" @@ -65,10 +105,12 @@ static const struct sc_option options[] = { "Any --max-size value is cmoputed on the cropped size.", }, { + .longopt_id = OPT_DISABLE_SCREENSAVER, .longopt = "disable-screensaver", .text = "Disable screensaver while scrcpy is running.", }, { + .longopt_id = OPT_DISPLAY_ID, .longopt = "display", .argdesc = "id", .text = "Specify the display id to mirror.\n" @@ -78,6 +120,7 @@ static const struct sc_option options[] = { "Default is 0.", }, { + .longopt_id = OPT_DISPLAY_BUFFER, .longopt = "display-buffer", .argdesc = "ms", .text = "Add a buffering delay (in milliseconds) before displaying. " @@ -85,16 +128,19 @@ static const struct sc_option options[] = { "Default is 0 (no buffering).", }, { + .longopt_id = OPT_ENCODER_NAME, .longopt = "encoder", .argdesc = "name", .text = "Use a specific MediaCodec encoder (must be a H.264 encoder).", }, { + .longopt_id = OPT_FORCE_ADB_FORWARD, .longopt = "force-adb-forward", .text = "Do not attempt to use \"adb reverse\" to connect to the " "device.", }, { + .longopt_id = OPT_FORWARD_ALL_CLICKS, .longopt = "forward-all-clicks", .text = "By default, right-click triggers BACK (or POWER on) and " "middle-click triggers HOME. This option disables these " @@ -121,6 +167,7 @@ static const struct sc_option options[] = { .text = "Print this help.", }, { + .longopt_id = OPT_LEGACY_PASTE, .longopt = "legacy-paste", .text = "Inject computer clipboard text as a sequence of key events " "on Ctrl+v (like MOD+Shift+v).\n" @@ -128,6 +175,7 @@ static const struct sc_option options[] = { "expected when setting the device clipboard programmatically.", }, { + .longopt_id = OPT_LOCK_VIDEO_ORIENTATION, .longopt = "lock-video-orientation", .argdesc = "value", .optional_arg = true, @@ -141,6 +189,7 @@ static const struct sc_option options[] = { "\"initial\".", }, { + .longopt_id = OPT_MAX_FPS, .longopt = "max-fps", .argdesc = "value", .text = "Limit the frame rate of screen capture (officially supported " @@ -170,10 +219,12 @@ static const struct sc_option options[] = { "is enabled).", }, { + .longopt_id = OPT_NO_KEY_REPEAT, .longopt = "no-key-repeat", .text = "Do not forward repeated key events when a key is held down.", }, { + .longopt_id = OPT_NO_MIPMAPS, .longopt = "no-mipmaps", .text = "If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then " "mipmaps are automatically generated to improve downscaling " @@ -188,10 +239,12 @@ static const struct sc_option options[] = { STR(DEFAULT_LOCAL_PORT_RANGE_LAST) ".", }, { + .longopt_id = OPT_POWER_OFF_ON_CLOSE, .longopt = "power-off-on-close", .text = "Turn the device screen off when closing scrcpy.", }, { + .longopt_id = OPT_PREFER_TEXT, .longopt = "prefer-text", .text = "Inject alpha characters and space as text events instead of" "key events.\n" @@ -200,6 +253,7 @@ static const struct sc_option options[] = { "keys in games (typically WASD).", }, { + .longopt_id = OPT_PUSH_TARGET, .longopt = "push-target", .argdesc = "path", .text = "Set the target directory for pushing files to the device by " @@ -215,11 +269,13 @@ static const struct sc_option options[] = { "set, or by the file extension (.mp4 or .mkv).", }, { + .longopt_id = OPT_RECORD_FORMAT, .longopt = "record-format", .argdesc = "format", .text = "Force recording format (either mp4 or mkv).", }, { + .longopt_id = OPT_RENDER_DRIVER, .longopt = "render-driver", .argdesc = "name", .text = "Request SDL to use the given render driver (this is just a " @@ -229,6 +285,12 @@ static const struct sc_option options[] = { "", }, { + // deprecated + .longopt_id = OPT_RENDER_EXPIRED_FRAMES, + .longopt = "render-expired-frames", + }, + { + .longopt_id = OPT_ROTATION, .longopt = "rotation", .argdesc = "value", .text = "Set the initial display rotation.\n" @@ -243,6 +305,7 @@ static const struct sc_option options[] = { "are connected to adb.", }, { + .longopt_id = OPT_SHORTCUT_MOD, .longopt = "shortcut-mod", .argdesc = "key[+...][,...]", .text = "Specify the modifiers to use for scrcpy shortcuts.\n" @@ -268,6 +331,7 @@ static const struct sc_option options[] = { }, #ifdef HAVE_V4L2 { + .longopt_id = OPT_V4L2_SINK, .longopt = "v4l2-sink", .argdesc = "/dev/videoN", .text = "Output to v4l2loopback device.\n" @@ -275,6 +339,7 @@ static const struct sc_option options[] = { "--lock-video-orientation).", }, { + .longopt_id = OPT_V4L2_BUFFER, .longopt = "v4l2-buffer", .argdesc = "ms", .text = "Add a buffering delay (in milliseconds) before pushing " @@ -307,33 +372,39 @@ static const struct sc_option options[] = { "is plugged in.", }, { + .longopt_id = OPT_WINDOW_BORDERLESS, .longopt = "window-borderless", .text = "Disable window decorations (display borderless window)." }, { + .longopt_id = OPT_WINDOW_TITLE, .longopt = "window-title", .argdesc = "text", .text = "Set a custom window title.", }, { + .longopt_id = OPT_WINDOW_X, .longopt = "window-x", .argdesc = "value", .text = "Set the initial window horizontal position.\n" "Default is \"auto\".", }, { + .longopt_id = OPT_WINDOW_Y, .longopt = "window-y", .argdesc = "value", .text = "Set the initial window vertical position.\n" "Default is \"auto\".", }, { + .longopt_id = OPT_WINDOW_WIDTH, .longopt = "window-width", .argdesc = "value", .text = "Set the initial window width.\n" "Default is 0 (automatic).", }, { + .longopt_id = OPT_WINDOW_HEIGHT, .longopt = "window-height", .argdesc = "value", .text = "Set the initial window height.\n" @@ -453,6 +524,102 @@ static const struct sc_shortcut shortcuts[] = { }, }; +static char * +sc_getopt_adapter_create_optstring(void) { + struct sc_strbuf buf; + if (!sc_strbuf_init(&buf, 64)) { + return false; + } + + for (size_t i = 0; i < ARRAY_LEN(options); ++i) { + const struct sc_option *opt = &options[i]; + if (opt->shortopt) { + if (!sc_strbuf_append_char(&buf, opt->shortopt)) { + goto error; + } + // If there is an argument, add ':' + if (opt->argdesc) { + if (!sc_strbuf_append_char(&buf, ':')) { + goto error; + } + // If the argument is optional, add another ':' + if (opt->optional_arg && !sc_strbuf_append_char(&buf, ':')) { + goto error; + } + } + } + } + + return buf.s; + +error: + free(buf.s); + return NULL; +} + +static struct option * +sc_getopt_adapter_create_longopts(void) { + struct option *longopts = + malloc((ARRAY_LEN(options) + 1) * sizeof(*longopts)); + if (!longopts) { + return NULL; + } + + size_t out_idx = 0; + for (size_t i = 0; i < ARRAY_LEN(options); ++i) { + const struct sc_option *in = &options[i]; + if (!in->longopt) { + // The longopts array must only contain long options + continue; + } + struct option *out = &longopts[out_idx++]; + + out->name = in->longopt; + + if (!in->argdesc) { + assert(!in->optional_arg); + out->has_arg = no_argument; + } else if (in->optional_arg) { + out->has_arg = optional_argument; + } else { + out->has_arg = required_argument; + } + + out->flag = NULL; + + // Either shortopt or longopt_id is set, but not both + assert(!!in->shortopt ^ !!in->longopt_id); + out->val = in->shortopt ? in->shortopt : in->longopt_id; + } + + // The array must be terminated by a NULL item + longopts[out_idx] = (struct option) {0}; + + return longopts; +} + +static bool +sc_getopt_adapter_init(struct sc_getopt_adapter *adapter) { + adapter->optstring = sc_getopt_adapter_create_optstring(); + if (!adapter->optstring) { + return false; + } + + adapter->longopts = sc_getopt_adapter_create_longopts(); + if (!adapter->longopts) { + free(adapter->optstring); + return false; + } + + return true; +}; + +static void +sc_getopt_adapter_destroy(struct sc_getopt_adapter *adapter) { + free(adapter->optstring); + free(adapter->longopts); +} + static void print_option_usage_header(const struct sc_option *opt) { struct sc_strbuf buf; @@ -514,7 +681,11 @@ error: static void print_option_usage(const struct sc_option *opt, unsigned cols) { assert(cols > 8); // sc_str_wrap_lines() requires indent < columns - assert(opt->text); + + if (!opt->text) { + // Option not documented in help (for example because it is deprecated) + return; + } print_option_usage_header(opt); @@ -951,104 +1122,15 @@ guess_record_format(const char *filename) { return 0; } -#define OPT_RENDER_EXPIRED_FRAMES 1000 -#define OPT_WINDOW_TITLE 1001 -#define OPT_PUSH_TARGET 1002 -#define OPT_ALWAYS_ON_TOP 1003 -#define OPT_CROP 1004 -#define OPT_RECORD_FORMAT 1005 -#define OPT_PREFER_TEXT 1006 -#define OPT_WINDOW_X 1007 -#define OPT_WINDOW_Y 1008 -#define OPT_WINDOW_WIDTH 1009 -#define OPT_WINDOW_HEIGHT 1010 -#define OPT_WINDOW_BORDERLESS 1011 -#define OPT_MAX_FPS 1012 -#define OPT_LOCK_VIDEO_ORIENTATION 1013 -#define OPT_DISPLAY_ID 1014 -#define OPT_ROTATION 1015 -#define OPT_RENDER_DRIVER 1016 -#define OPT_NO_MIPMAPS 1017 -#define OPT_CODEC_OPTIONS 1018 -#define OPT_FORCE_ADB_FORWARD 1019 -#define OPT_DISABLE_SCREENSAVER 1020 -#define OPT_SHORTCUT_MOD 1021 -#define OPT_NO_KEY_REPEAT 1022 -#define OPT_FORWARD_ALL_CLICKS 1023 -#define OPT_LEGACY_PASTE 1024 -#define OPT_ENCODER_NAME 1025 -#define OPT_POWER_OFF_ON_CLOSE 1026 -#define OPT_V4L2_SINK 1027 -#define OPT_DISPLAY_BUFFER 1028 -#define OPT_V4L2_BUFFER 1029 - -bool -scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { - static const struct option long_options[] = { - {"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, - {"bit-rate", required_argument, NULL, 'b'}, - {"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS}, - {"crop", required_argument, NULL, OPT_CROP}, - {"disable-screensaver", no_argument, NULL, - OPT_DISABLE_SCREENSAVER}, - {"display", required_argument, NULL, OPT_DISPLAY_ID}, - {"display-buffer", required_argument, NULL, OPT_DISPLAY_BUFFER}, - {"encoder", required_argument, NULL, OPT_ENCODER_NAME}, - {"force-adb-forward", no_argument, NULL, - OPT_FORCE_ADB_FORWARD}, - {"forward-all-clicks", no_argument, NULL, - OPT_FORWARD_ALL_CLICKS}, - {"fullscreen", no_argument, NULL, 'f'}, - {"help", no_argument, NULL, 'h'}, - {"hid-keyboard", no_argument, NULL, 'K'}, - {"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE}, - {"lock-video-orientation", optional_argument, NULL, - OPT_LOCK_VIDEO_ORIENTATION}, - {"max-fps", required_argument, NULL, OPT_MAX_FPS}, - {"max-size", required_argument, NULL, 'm'}, - {"no-control", no_argument, NULL, 'n'}, - {"no-display", no_argument, NULL, 'N'}, - {"no-key-repeat", no_argument, NULL, OPT_NO_KEY_REPEAT}, - {"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS}, - {"port", required_argument, NULL, 'p'}, - {"prefer-text", no_argument, NULL, OPT_PREFER_TEXT}, - {"push-target", required_argument, NULL, OPT_PUSH_TARGET}, - {"record", required_argument, NULL, 'r'}, - {"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, - {"render-driver", required_argument, NULL, OPT_RENDER_DRIVER}, - {"render-expired-frames", no_argument, NULL, - OPT_RENDER_EXPIRED_FRAMES}, - {"rotation", required_argument, NULL, OPT_ROTATION}, - {"serial", required_argument, NULL, 's'}, - {"shortcut-mod", required_argument, NULL, OPT_SHORTCUT_MOD}, - {"show-touches", no_argument, NULL, 't'}, - {"stay-awake", no_argument, NULL, 'w'}, - {"turn-screen-off", no_argument, NULL, 'S'}, -#ifdef HAVE_V4L2 - {"v4l2-sink", required_argument, NULL, OPT_V4L2_SINK}, - {"v4l2-buffer", required_argument, NULL, OPT_V4L2_BUFFER}, -#endif - {"verbosity", required_argument, NULL, 'V'}, - {"version", no_argument, NULL, 'v'}, - {"window-title", required_argument, NULL, OPT_WINDOW_TITLE}, - {"window-x", required_argument, NULL, OPT_WINDOW_X}, - {"window-y", required_argument, NULL, OPT_WINDOW_Y}, - {"window-width", required_argument, NULL, OPT_WINDOW_WIDTH}, - {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, - {"window-borderless", no_argument, NULL, - OPT_WINDOW_BORDERLESS}, - {"power-off-on-close", no_argument, NULL, - OPT_POWER_OFF_ON_CLOSE}, - {NULL, 0, NULL, 0 }, - }; - +static bool +parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], + const char *optstring, const struct option *longopts) { struct scrcpy_options *opts = &args->opts; optind = 0; // reset to start from the first argument in tests int c; - while ((c = getopt_long(argc, argv, "b:fF:hKm:nNp:r:s:StvV:w", - long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, optstring, longopts, NULL)) != -1) { switch (c) { case 'b': if (!parse_bit_rate(optarg, &opts->bit_rate)) { @@ -1288,3 +1370,19 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { return true; } + +bool +scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { + struct sc_getopt_adapter adapter; + if (!sc_getopt_adapter_init(&adapter)) { + LOGW("Could not create getopt adapter"); + return false; + } + + bool ret = parse_args_with_getopt(args, argc, argv, adapter.optstring, + adapter.longopts); + + sc_getopt_adapter_destroy(&adapter); + + return ret; +}