Create option parser interface
This is an interface that combines option parsing with help text formatting.pull/185/head
parent
33f98048d5
commit
125121613e
|
@ -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 <stdio.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
|
||||||
|
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[]);
|
|
@ -93,6 +93,7 @@ sources = [
|
||||||
'src/util.c',
|
'src/util.c',
|
||||||
'src/json-ipc.c',
|
'src/json-ipc.c',
|
||||||
'src/ctl-server.c',
|
'src/ctl-server.c',
|
||||||
|
'src/option-parser.c',
|
||||||
]
|
]
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
95
src/main.c
95
src/main.c
|
@ -52,6 +52,7 @@
|
||||||
#include "usdt.h"
|
#include "usdt.h"
|
||||||
#include "ctl-server.h"
|
#include "ctl-server.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "option-parser.h"
|
||||||
|
|
||||||
#ifdef ENABLE_PAM
|
#ifdef ENABLE_PAM
|
||||||
#include "pam_auth.h"
|
#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 =
|
fprintf(stream, "Usage: wayvnc [options] [address [port]]\n\n");
|
||||||
"Usage: wayvnc [options] [address [port]]\n"
|
option_parser_print_usage(parser, stream);
|
||||||
"\n"
|
fprintf(stream, "\n");
|
||||||
" -C,--config=<path> Select a config file.\n"
|
|
||||||
" -g,--gpu Enable features that need GPU.\n"
|
|
||||||
" -o,--output=<name> Select output to capture.\n"
|
|
||||||
" -k,--keyboard=<layout>[-<variant>] Select keyboard layout with an\n"
|
|
||||||
" optional variant.\n"
|
|
||||||
" -s,--seat=<name> Select seat by name.\n"
|
|
||||||
" -S,--socket=<path> Wayvnc control socket path.\n"
|
|
||||||
" Default: $XDG_RUNTIME_DIR/wayvncctl\n"
|
|
||||||
" -r,--render-cursor Enable overlay cursor rendering.\n"
|
|
||||||
" -f,--max-fps=<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=<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);
|
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1011,32 +987,49 @@ int main(int argc, char* argv[])
|
||||||
int max_rate = 30;
|
int max_rate = 30;
|
||||||
bool disable_input = false;
|
bool disable_input = false;
|
||||||
|
|
||||||
static const char* shortopts = "C:go:k:s:S:rf:hpudVvL:";
|
|
||||||
int drm_fd MAYBE_UNUSED = -1;
|
int drm_fd MAYBE_UNUSED = -1;
|
||||||
|
|
||||||
int log_level = NVNC_LOG_WARNING;
|
int log_level = NVNC_LOG_WARNING;
|
||||||
|
|
||||||
static const struct option longopts[] = {
|
static const struct wv_option opts[] = {
|
||||||
{ "config", required_argument, NULL, 'C' },
|
{ 'C', "config", "<path>",
|
||||||
{ "gpu", no_argument, NULL, 'g' },
|
"Select a config file." },
|
||||||
{ "output", required_argument, NULL, 'o' },
|
{ 'g', "gpu", NULL,
|
||||||
{ "keyboard", required_argument, NULL, 'k' },
|
"Enable features that need GPU." },
|
||||||
{ "seat", required_argument, NULL, 's' },
|
{ 'o', "output", "<name>",
|
||||||
{ "socket", required_argument, NULL, 'S' },
|
"Select output to capture." },
|
||||||
{ "render-cursor", no_argument, NULL, 'r' },
|
{ 'k', "keyboard", "<layout>[-<variant>]",
|
||||||
{ "max-fps", required_argument, NULL, 'f' },
|
"Select keyboard layout with an optional variant." },
|
||||||
{ "help", no_argument, NULL, 'h' },
|
{ 's', "seat", "<name>",
|
||||||
{ "show-performance", no_argument, NULL, 'p' },
|
"Select seat by name." },
|
||||||
{ "unix-socket", no_argument, NULL, 'u' },
|
{ 'S', "socket", "<path>",
|
||||||
{ "disable-input", no_argument, NULL, 'd' },
|
"Control socket path." },
|
||||||
{ "version", no_argument, NULL, 'V' },
|
{ 'r', "render-cursor", NULL,
|
||||||
{ "verbose", no_argument, NULL, 'v' },
|
"Enable overlay cursor rendering." },
|
||||||
{ "log-level", required_argument, NULL, 'L' },
|
{ 'f', "max-fps", "<fps>",
|
||||||
{ NULL, 0, NULL, 0 }
|
"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", "<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) {
|
while (1) {
|
||||||
int c = getopt_long(argc, argv, shortopts, longopts, NULL);
|
int c = option_parser_getopt(&option_parser, argc, argv);
|
||||||
if (c < 0)
|
if (c < 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1082,15 +1075,15 @@ int main(int argc, char* argv[])
|
||||||
if (log_level < 0) {
|
if (log_level < 0) {
|
||||||
fprintf(stderr, "Invalid log level: %s\n",
|
fprintf(stderr, "Invalid log level: %s\n",
|
||||||
optarg);
|
optarg);
|
||||||
return wayvnc_usage(stderr, 1);
|
return wayvnc_usage(&option_parser, stderr, 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'V':
|
case 'V':
|
||||||
return show_version();
|
return show_version();
|
||||||
case 'h':
|
case 'h':
|
||||||
return wayvnc_usage(stdout, 0);
|
return wayvnc_usage(&option_parser, stdout, 0);
|
||||||
default:
|
default:
|
||||||
return wayvnc_usage(stderr, 1);
|
return wayvnc_usage(&option_parser, stderr, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
Loading…
Reference in New Issue