From 125121613ec2a37b732c7bd68783a59613d6df33 Mon Sep 17 00:00:00 2001 From: Andri Yngvason Date: Sun, 13 Nov 2022 15:43:46 +0000 Subject: [PATCH] Create option parser interface This is an interface that combines option parsing with help text formatting. --- include/option-parser.h | 39 +++++++++ meson.build | 1 + src/main.c | 95 ++++++++++------------ src/option-parser.c | 172 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 256 insertions(+), 51 deletions(-) create mode 100644 include/option-parser.h create mode 100644 src/option-parser.c diff --git a/include/option-parser.h b/include/option-parser.h new file mode 100644 index 0000000..d63f5a8 --- /dev/null +++ b/include/option-parser.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 Andri Yngvason + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include +#include + +struct wv_option { + char short_opt; + const char* long_opt; + const char* schema; + const char* help; +}; + +struct option_parser { + const struct wv_option* options; + char short_opts[128]; + struct option long_opts[128]; + int n_opts; +}; + +void option_parser_init(struct option_parser* self, const struct wv_option* options); + +void option_parser_print_usage(struct option_parser* self, FILE* stream); +int option_parser_getopt(struct option_parser* self, int argc, char* argv[]); diff --git a/meson.build b/meson.build index 5c13b6c..a63a680 100644 --- a/meson.build +++ b/meson.build @@ -93,6 +93,7 @@ sources = [ 'src/util.c', 'src/json-ipc.c', 'src/ctl-server.c', + 'src/option-parser.c', ] dependencies = [ diff --git a/src/main.c b/src/main.c index d7007ba..2ff2f6e 100644 --- a/src/main.c +++ b/src/main.c @@ -52,6 +52,7 @@ #include "usdt.h" #include "ctl-server.h" #include "util.h" +#include "option-parser.h" #ifdef ENABLE_PAM #include "pam_auth.h" @@ -738,36 +739,11 @@ void on_capture_done(struct screencopy* sc) } } -int wayvnc_usage(FILE* stream, int rc) +int wayvnc_usage(struct option_parser* parser, FILE* stream, int rc) { - static const char* usage = -"Usage: wayvnc [options] [address [port]]\n" -"\n" -" -C,--config= Select a config file.\n" -" -g,--gpu Enable features that need GPU.\n" -" -o,--output= Select output to capture.\n" -" -k,--keyboard=[-] Select keyboard layout with an\n" -" optional variant.\n" -" -s,--seat= Select seat by name.\n" -" -S,--socket= Wayvnc control socket path.\n" -" Default: $XDG_RUNTIME_DIR/wayvncctl\n" -" -r,--render-cursor Enable overlay cursor rendering.\n" -" -f,--max-fps= Set the rate limit (default 30).\n" -" -p,--show-performance Show performance counters.\n" -" -u,--unix-socket Create a UNIX domain socket\n" -" instead of TCP.\n" -" -d,--disable-input Disable all remote input\n" -" -V,--version Show version info.\n" -" -v,--verbose Be more verbose. Same as setting\n" -" --log-level=info.\n" -" -L,--log-level= Set log level. The levels are:\n" -" error, warning, info, debug,\n" -" trace and quiet.\n" -" -h,--help Get help (this text).\n" -"\n"; - - fprintf(stream, "%s", usage); - + fprintf(stream, "Usage: wayvnc [options] [address [port]]\n\n"); + option_parser_print_usage(parser, stream); + fprintf(stream, "\n"); return rc; } @@ -1011,32 +987,49 @@ int main(int argc, char* argv[]) int max_rate = 30; bool disable_input = false; - static const char* shortopts = "C:go:k:s:S:rf:hpudVvL:"; int drm_fd MAYBE_UNUSED = -1; int log_level = NVNC_LOG_WARNING; - static const struct option longopts[] = { - { "config", required_argument, NULL, 'C' }, - { "gpu", no_argument, NULL, 'g' }, - { "output", required_argument, NULL, 'o' }, - { "keyboard", required_argument, NULL, 'k' }, - { "seat", required_argument, NULL, 's' }, - { "socket", required_argument, NULL, 'S' }, - { "render-cursor", no_argument, NULL, 'r' }, - { "max-fps", required_argument, NULL, 'f' }, - { "help", no_argument, NULL, 'h' }, - { "show-performance", no_argument, NULL, 'p' }, - { "unix-socket", no_argument, NULL, 'u' }, - { "disable-input", no_argument, NULL, 'd' }, - { "version", no_argument, NULL, 'V' }, - { "verbose", no_argument, NULL, 'v' }, - { "log-level", required_argument, NULL, 'L' }, - { NULL, 0, NULL, 0 } + static const struct wv_option opts[] = { + { 'C', "config", "", + "Select a config file." }, + { 'g', "gpu", NULL, + "Enable features that need GPU." }, + { 'o', "output", "", + "Select output to capture." }, + { 'k', "keyboard", "[-]", + "Select keyboard layout with an optional variant." }, + { 's', "seat", "", + "Select seat by name." }, + { 'S', "socket", "", + "Control socket path." }, + { 'r', "render-cursor", NULL, + "Enable overlay cursor rendering." }, + { 'f', "max-fps", "", + "Set rate limit (default 30)." }, + { 'p', "performance", NULL, + "Show performance counters." }, + { 'u', "unix-socket", NULL, + "Create unix domain socket." }, + { 'd', "disable-input", NULL, + "Disable all remote input." }, + { 'V', "version", NULL, + "Show version info." }, + { 'v', "verbose", NULL, + "Be more verbose. Same as setting --log-level=info" }, + { 'L', "log-level", "", + "Set log level. The levels are: error, warning, info, debug trace and quiet." }, + { 'h', "help", NULL, + "Get help (this text)." }, + { '\0', NULL, NULL, NULL } }; + struct option_parser option_parser; + option_parser_init(&option_parser, opts); + while (1) { - int c = getopt_long(argc, argv, shortopts, longopts, NULL); + int c = option_parser_getopt(&option_parser, argc, argv); if (c < 0) break; @@ -1082,15 +1075,15 @@ int main(int argc, char* argv[]) if (log_level < 0) { fprintf(stderr, "Invalid log level: %s\n", optarg); - return wayvnc_usage(stderr, 1); + return wayvnc_usage(&option_parser, stderr, 1); } break; case 'V': return show_version(); case 'h': - return wayvnc_usage(stdout, 0); + return wayvnc_usage(&option_parser, stdout, 0); default: - return wayvnc_usage(stderr, 1); + return wayvnc_usage(&option_parser, stderr, 1); } } diff --git a/src/option-parser.c b/src/option-parser.c new file mode 100644 index 0000000..9f3abba --- /dev/null +++ b/src/option-parser.c @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2022 Andri Yngvason + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include "option-parser.h" + +#include +#include +#include +#include + +static int count_options(const struct wv_option* opts) +{ + int n = 0; + while (opts[n].short_opt || opts[n].long_opt) + n++; + return n; +} + +void option_parser_init(struct option_parser* self, + const struct wv_option* options) +{ + memset(self, 0, sizeof(*self)); + + self->options = options; + self->n_opts = count_options(options); + + int short_opt_index = 0; + int long_opt_index = 0; + + for (int i = 0; i < self->n_opts; ++i) { + assert(options[i].short_opt); // TODO: Make this optional? + + self->short_opts[short_opt_index++] = options[i].short_opt; + if (options[i].schema) + self->short_opts[short_opt_index++] = ':'; + + if (!options[i].long_opt) + continue; + + const struct wv_option* src_opt = &options[i]; + struct option* dst_opt = &self->long_opts[long_opt_index++]; + + dst_opt->val = src_opt->short_opt; + dst_opt->name = src_opt->long_opt; + dst_opt->has_arg = src_opt->schema ? + required_argument : no_argument; + } +} + +static int get_left_col_width(const struct wv_option* opts, int n) +{ + int max_width = 0; + + for (int i = 0; i < n; ++i) { + int width = 0; + + if (opts[i].short_opt) + width += 2; + + if (opts[i].long_opt) + width += 2 + strlen(opts[i].long_opt); + + if (opts[i].short_opt && opts[i].long_opt) + width += 1; // for ',' + + if (opts[i].schema) { + width += strlen(opts[i].schema); + + if (opts[i].long_opt) + width += 1; // for '=' + } + + if (width > max_width) + max_width = width; + } + + return max_width; +} + +static void reflow_text(char* dst, const char* src, int width) +{ + int line_len = 0; + int last_space_pos = 0; + + int dst_len = 0; + int i = 0; + + while (src[i]) { + char c = src[i]; + + if (line_len > width) { + assert(last_space_pos > 0); + + dst_len -= i - last_space_pos; + dst[dst_len++] = '\n'; + i = last_space_pos + 1; + line_len = 0; + continue; + } + + if (c == ' ') + last_space_pos = i; + + dst[dst_len++] = c; + ++i; + ++line_len; + } + + dst[dst_len] = '\0'; +} + +static void format_option(const struct wv_option* opt, int left_col_width, + FILE* stream) +{ + assert(opt->help); + + int n_chars = fprintf(stream, " "); + if (opt->short_opt) + n_chars += fprintf(stream, "-%c", opt->short_opt); + if (opt->long_opt) + n_chars += fprintf(stream, "%s--%s", + opt->short_opt ? "," : "", opt->long_opt); + if (opt->schema) + n_chars += fprintf(stream, "%s%s", + opt->long_opt ? "=" : "", opt->schema); + + n_chars += fprintf(stream, "%*s", left_col_width - n_chars + 8, ""); + + int right_col_width = 80 - 8 - left_col_width; + assert(right_col_width >= 0); + + char help[256]; + reflow_text(help, opt->help, right_col_width); + + char* line = strtok(help, "\n"); + fprintf(stream, "%s\n", line); + + while (true) { + line = strtok(NULL, "\n"); + if (!line) + break; + + fprintf(stream, "%*s%s\n", left_col_width + 8, "", line); + } +} + +void option_parser_print_usage(struct option_parser* self, FILE* stream) +{ + int left_col_width = get_left_col_width(self->options, self->n_opts); + + for (int i = 0; i < self->n_opts; ++i) { + format_option(&self->options[i], left_col_width, stream); + } +} + +int option_parser_getopt(struct option_parser* self, int argc, char* argv[]) +{ + return getopt_long(argc, argv, self->short_opts, self->long_opts, NULL); +}