diff --git a/CHANGELOG.md b/CHANGELOG.md index c68ac569..208f0f04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,7 +64,8 @@ * Support for LS2 and LS3 (locking shift) escape sequences (https://codeberg.org/dnkl/foot/issues/581). * Support for overriding configuration options on the command line - (https://codeberg.org/dnkl/foot/issues/554). + (https://codeberg.org/dnkl/foot/issues/554, + https://codeberg.org/dnkl/foot/issues/600). * `underline-offset` option to `foot.ini` (https://codeberg.org/dnkl/foot/issues/490). * `csd.button-color` option to `foot.ini`. diff --git a/client-protocol.h b/client-protocol.h index 663199c9..c1291f28 100644 --- a/client-protocol.h +++ b/client-protocol.h @@ -4,32 +4,23 @@ #include #include -struct client_argv { +struct client_string { uint16_t len; - /* char arg[static len]; */ + /* char str[static len]; */ }; struct client_data { - unsigned width; - unsigned height; - uint8_t size_type:1; // Values correspond to enum conf_size_type - uint8_t maximized:1; - uint8_t fullscreen:1; - uint8_t hold:1; - uint8_t login_shell:1; - uint8_t no_wait:1; + bool hold:1; + bool no_wait:1; + uint8_t reserved:6; uint16_t cwd_len; - uint16_t term_len; - uint16_t title_len; - uint16_t app_id_len; - + uint16_t override_count; uint16_t argc; /* char cwd[static cwd_len]; */ - /* char term[static term_len]; */ - /* char title[static title_len]; */ - /* char app_id[static app_id_len]; */ + /* struct client_string overrides[static override_count]; */ + /* struct client_string argv[static argc]; */ +} __attribute__((packed)); - /* struct client_argv argv[static argc]; */ -}; +_Static_assert(sizeof(struct client_data) == 7, "protocol struct size error"); diff --git a/client.c b/client.c index 1445146c..7249c588 100644 --- a/client.c +++ b/client.c @@ -12,6 +12,8 @@ #include #include +#include + #define LOG_MODULE "foot-client" #define LOG_ENABLE_DBG 0 #include "log.h" @@ -23,6 +25,12 @@ #include "version.h" #include "xmalloc.h" +struct override { + size_t len; + char *str; +}; +typedef tll(struct override) override_list_t; + static volatile sig_atomic_t aborted = 0; static void @@ -61,11 +69,27 @@ print_usage(const char *prog_name) " -s,--server-socket=PATH path to the server UNIX domain socket (default=$XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock)\n" " -H,--hold remain open after child process exits\n" " -N,--no-wait detach the client process from the running terminal, exiting immediately\n" + " -o,--override=[section.]key=value override configuration option\n" " -d,--log-level={info|warning|error} log level (info)\n" " -l,--log-colorize=[{never|always|auto}] enable/disable colorization of log output on stderr\n" " -v,--version show the version number and quit\n"); } +static bool NOINLINE +push_override(override_list_t *overrides, const char *s, uint64_t *total_len) +{ + size_t len = strlen(s) + 1; + if (len >= 1 << (8 * sizeof(struct client_string))) { + LOG_ERR("override length overflow"); + return false; + } + + struct override o = {len, xstrdup(s)}; + tll_push_back(*overrides, o); + *total_len += sizeof(struct client_string) + o.len; + return true; +} + int main(int argc, char *const *argv) { @@ -89,6 +113,7 @@ main(int argc, char *const *argv) {"server-socket", required_argument, NULL, 's'}, {"hold", no_argument, NULL, 'H'}, {"no-wait", no_argument, NULL, 'N'}, + {"override", required_argument, NULL, 'o'}, {"log-level", required_argument, NULL, 'd'}, {"log-colorize", optional_argument, NULL, 'l'}, {"version", no_argument, NULL, 'v'}, @@ -96,78 +121,98 @@ main(int argc, char *const *argv) {NULL, no_argument, NULL, 0}, }; - const char *term = ""; - const char *title = ""; - const char *app_id = ""; const char *custom_cwd = NULL; - unsigned size_type = 0; // enum conf_size_type (without pulling in tllist/fcft via config.h) - unsigned width = 0; - unsigned height = 0; const char *server_socket_path = NULL; enum log_class log_level = LOG_CLASS_INFO; enum log_colorize log_colorize = LOG_COLORIZE_AUTO; - bool login_shell = false; - bool maximized = false; - bool fullscreen = false; bool hold = false; + + /* Used to format overrides */ bool no_wait = false; + char buf[1024]; + + /* Total packet length, not (yet) including overrides or argv[] */ + uint64_t total_len = 0; + + /* malloc:ed and needs to be in scope of all goto's */ + char *_cwd = NULL; + override_list_t overrides = tll_init(); + struct client_string *cargv = NULL; + while (true) { - int c = getopt_long(argc, argv, "+t:T:a:w:W:mFLD:s:HNd:l::vh", longopts, NULL); + int c = getopt_long(argc, argv, "+t:T:a:w:W:mFLD:s:HNo:d:l::vh", longopts, NULL); if (c == -1) break; switch (c) { case 't': - term = optarg; + snprintf(buf, sizeof(buf), "term=%s", optarg); + if (!push_override(&overrides, buf, &total_len)) + goto err; break; case 'T': - title = optarg; + snprintf(buf, sizeof(buf), "title=%s", optarg); + if (!push_override(&overrides, buf, &total_len)) + goto err; break; case 'a': - app_id = optarg; + snprintf(buf, sizeof(buf), "app-id=%s", optarg); + if (!push_override(&overrides, buf, &total_len)) + goto err; break; case 'L': - login_shell = true; + if (!push_override(&overrides, "login-shell=yes", &total_len)) + goto err; break; case 'D': { struct stat st; if (stat(optarg, &st) < 0 || !(st.st_mode & S_IFDIR)) { fprintf(stderr, "error: %s: not a directory\n", optarg); - return ret; + goto err; } custom_cwd = optarg; break; } - case 'w': + case 'w': { + unsigned width, height; if (sscanf(optarg, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) { fprintf(stderr, "error: invalid window-size-pixels: %s\n", optarg); - return ret; + goto err; } - size_type = 0; // CONF_SIZE_PX - break; - case 'W': + snprintf(buf, sizeof(buf), "initial-window-size-pixels=%ux%u", width, height); + if (!push_override(&overrides, buf, &total_len)) + goto err; + break; + } + + case 'W': { + unsigned width, height; if (sscanf(optarg, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) { fprintf(stderr, "error: invalid window-size-chars: %s\n", optarg); - return ret; + goto err; } - size_type = 1; // CONF_SIZE_CELLS + + snprintf(buf, sizeof(buf), "initial-window-size-chars=%ux%u", width, height); + if (!push_override(&overrides, buf, &total_len)) + goto err; break; + } case 'm': - maximized = true; - fullscreen = false; + if (!push_override(&overrides, "initial-window-mode=maximized", &total_len)) + goto err; break; case 'F': - fullscreen = true; - maximized = false; + if (!push_override(&overrides, "initial-window-mode=fullscreen", &total_len)) + goto err; break; case 's': @@ -182,6 +227,11 @@ main(int argc, char *const *argv) no_wait = true; break; + case 'o': + if (!push_override(&overrides, optarg, &total_len)) + goto err; + break; + case 'd': { int lvl = log_level_from_string(optarg); if (unlikely(lvl < 0)) { @@ -190,7 +240,7 @@ main(int argc, char *const *argv) "-d,--log-level: %s: argument must be one of %s\n", optarg, log_level_string_hint()); - return ret; + goto err; } log_level = lvl; break; @@ -205,20 +255,22 @@ main(int argc, char *const *argv) log_colorize = LOG_COLORIZE_ALWAYS; else { fprintf(stderr, "%s: argument must be one of 'never', 'always' or 'auto'\n", optarg); - return ret; + goto err; } break; case 'v': printf("footclient %s\n", version_and_features()); - return EXIT_SUCCESS; + ret = EXIT_SUCCESS; + goto err; case 'h': print_usage(prog_name); - return EXIT_SUCCESS; + ret = EXIT_SUCCESS; + goto err; case '?': - return ret; + goto err; } } @@ -227,10 +279,6 @@ main(int argc, char *const *argv) log_init(log_colorize, false, LOG_FACILITY_USER, log_level); - /* malloc:ed and needs to be in scope of all goto's */ - char *_cwd = NULL; - struct client_argv *cargv = NULL; - int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == -1) { LOG_ERRNO("failed to create socket"); @@ -290,37 +338,21 @@ main(int argc, char *const *argv) /* String lengths, including NULL terminator */ const size_t cwd_len = strlen(cwd) + 1; - const size_t term_len = strlen(term) + 1; - const size_t title_len = strlen(title) + 1; - const size_t app_id_len = strlen(app_id) + 1; + const size_t override_count = tll_length(overrides); const struct client_data data = { - .width = width, - .height = height, - .size_type = size_type, - .maximized = maximized, - .fullscreen = fullscreen, .hold = hold, - .login_shell = login_shell, .no_wait = no_wait, .cwd_len = cwd_len, - .term_len = term_len, - .title_len = title_len, - .app_id_len = app_id_len, + .override_count = override_count, .argc = argc, }; /* Total packet length, not (yet) including argv[] */ - uint64_t total_len = ( - sizeof(data) + - cwd_len + - term_len + - title_len + - app_id_len); - - cargv = xmalloc(argc * sizeof(cargv[0])); + total_len += sizeof(data) + cwd_len; /* Add argv[] size to total packet length */ + cargv = xmalloc(argc * sizeof(cargv[0])); for (size_t i = 0; i < argc; i++) { const size_t arg_len = strlen(argv[i]) + 1; @@ -336,9 +368,7 @@ main(int argc, char *const *argv) /* Check for size overflows */ if (total_len >= 1llu << (8 * sizeof(uint32_t)) || cwd_len >= 1 << (8 * sizeof(data.cwd_len)) || - term_len >= 1 << (8 * sizeof(data.term_len)) || - title_len >= 1 << (8 * sizeof(data.title_len)) || - app_id_len >= 1 << (8 * sizeof(data.app_id_len)) || + override_count > (size_t)(unsigned int)data.override_count || argc > (int)(unsigned int)data.argc) { LOG_ERR("size overflow"); @@ -348,21 +378,30 @@ main(int argc, char *const *argv) /* Send everything except argv[] */ if (send(fd, &(uint32_t){total_len}, sizeof(uint32_t), 0) != sizeof(uint32_t) || send(fd, &data, sizeof(data), 0) != sizeof(data) || - send(fd, cwd, cwd_len, 0) != cwd_len || - send(fd, term, term_len, 0) != term_len || - send(fd, title, title_len, 0) != title_len || - send(fd, app_id, app_id_len, 0) != app_id_len) + send(fd, cwd, cwd_len, 0) != cwd_len) { LOG_ERRNO("failed to send setup packet to server"); goto err; } + /* Send overrides */ + tll_foreach(overrides, it) { + const struct override *o = &it->item; + struct client_string s = {o->len}; + if (send(fd, &s, sizeof(s), 0) != sizeof(s) || + send(fd, o->str, o->len, 0) != o->len) + { + LOG_ERRNO("failed to send setup packet (overrides) to server"); + goto err; + } + } + /* Send argv[] */ for (size_t i = 0; i < argc; i++) { if (send(fd, &cargv[i], sizeof(cargv[i]), 0) != sizeof(cargv[i]) || send(fd, argv[i], cargv[i].len, 0) != cargv[i].len) { - LOG_ERRNO("failed to send setup packet to server"); + LOG_ERRNO("failed to send setup packet (argv) to server"); goto err; } } @@ -386,6 +425,10 @@ main(int argc, char *const *argv) ret = exit_code; err: + tll_foreach(overrides, it) { + free(it->item.str); + tll_remove(overrides, it); + } free(cargv); free(_cwd); if (fd != -1) diff --git a/completions/bash/footclient b/completions/bash/footclient index 6e065a97..8e0aa017 100644 --- a/completions/bash/footclient +++ b/completions/bash/footclient @@ -13,6 +13,7 @@ _footclient() "--log-level" "--log-colorize" "--maximized" + "--override" "--server-socket" "--term" "--title" @@ -61,7 +62,7 @@ _footclient() COMPREPLY=( $(compgen -W "error warning info" -- ${cur}) ) elif [[ ${prev} == '--log-colorize' ]] ; then COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) - elif [[ ${prev} =~ ^(--app-id|--help|--title|--version|--window-size-chars|--window-size-pixels|)$ ]] ; then + elif [[ ${prev} =~ ^(--app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|)$ ]] ; then : # don't autocomplete for these flags else # complete commands from $PATH diff --git a/completions/fish/footclient.fish b/completions/fish/footclient.fish index 826508ef..9b8caa84 100644 --- a/completions/fish/footclient.fish +++ b/completions/fish/footclient.fish @@ -11,6 +11,7 @@ complete -c footclient -x -s W -l window-size-chars complete -c footclient -F -s s -l server-socket -d "override the default path to the foot server socket ($XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock)" complete -c footclient -s H -l hold -d "remain open after child process exits" complete -c footclient -s N -l no-wait -d "detach the client process from the running terminal, exiting immediately" +complete -c footclient -x -s o -l override -d "configuration option to override, in form SECTION.KEY=VALUE" complete -c footclient -x -s d -l log-level -a "info warning error" -d "log-level (info)" complete -c footclient -x -s l -l log-colorize -a "always never auto" -d "enable or disable colorization of log output on stderr" complete -c footclient -s v -l version -d "show the version number and quit" diff --git a/completions/zsh/_footclient b/completions/zsh/_footclient index 411d4ec5..3889c488 100644 --- a/completions/zsh/_footclient +++ b/completions/zsh/_footclient @@ -14,6 +14,7 @@ _arguments \ '(-s --server-socket)'{-s,--server-socket}'[override the default path to the foot server socket ($XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock)]:server:_files' \ '(-H --hold)'{-H,--hold}'[remain open after child process exits]' \ '(-N --no-wait)'{-N,--no-wait}'[detach the client process from the running terminal, exiting immediately]' \ + '(-o --override)'{-o,--override}'[configuration option to override, in form SECTION.KEY=VALUE]:()' \ '(-d --log-level)'{-d,--log-level}'[log level (info)]:loglevel:(info warning error)' \ '(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \ '(-v --version)'{-v,--version}'[show the version number and quit]' \ diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 16d639fa..80203ad9 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -30,9 +30,9 @@ the foot command line Verify configuration and then exit with 0 if ok, otherwise exit with 230 (see *EXIT STATUS*). -*-o*,*--override* [_SECTION_.]_KEY_=_VALUE_ +*-o*,*--override*=[_SECTION_.]_KEY_=_VALUE_ Override an option set in the configuration file. If _SECTION_ is not - given, defaults to _main_. + given, defaults to _main_. *-f*,*--font*=_FONT_ Comma separated list of fonts to use, in fontconfig format (see diff --git a/doc/footclient.1.scd b/doc/footclient.1.scd index a9fb8c69..8cb05316 100644 --- a/doc/footclient.1.scd +++ b/doc/footclient.1.scd @@ -65,6 +65,10 @@ terminal has terminated. Detach the client process from the running terminal, exiting immediately. +*-o*,*--override*=[_SECTION_.]_KEY_=_VALUE_ + Override an option set in the configuration file. If _SECTION_ is not + given, defaults to _main_. + *-d*,*--log-level*={*info*,*warning*,*error*} Log level, used both for log output on stderr as well as syslog. Default: _info_. diff --git a/main.c b/main.c index df9edfd6..2c392fca 100644 --- a/main.c +++ b/main.c @@ -62,7 +62,7 @@ print_usage(const char *prog_name) "Options:\n" " -c,--config=PATH load configuration from PATH ($XDG_CONFIG_HOME/foot/foot.ini)\n" " -C,--check-config verify configuration, exit with 0 if ok, otherwise exit with 1\n" - " -o,--override [section.]key=value override configuration option\n" + " -o,--override=[section.]key=value override configuration option\n" " -f,--font=FONT comma separated list of fonts in fontconfig format (monospace)\n" " -t,--term=TERM value to set the environment variable TERM to (%s)\n" " -T,--title=TITLE initial window title (foot)\n" diff --git a/meson.build b/meson.build index 0c48929a..479f5747 100644 --- a/meson.build +++ b/meson.build @@ -211,6 +211,7 @@ executable( 'macros.h', 'util.h', version, + dependencies: [tllist], link_with: common, install: true) diff --git a/server.c b/server.c index 01c2801c..b8ae5685 100644 --- a/server.c +++ b/server.c @@ -120,11 +120,9 @@ instance_destroy(struct terminal_instance *instance, int exit_code) /* TODO: clone server conf completely, so that we can just call * conf_destroy() here */ if (instance->conf != NULL) { - free(instance->conf->term); - free(instance->conf->title); - free(instance->conf->app_id); + config_free(*instance->conf); + free(instance->conf); } - free(instance->conf); free(instance); } @@ -242,6 +240,7 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data) uint8_t *p = client->buffer.data; const uint8_t *end = &client->buffer.data[client->buffer.idx]; + config_override_t overrides = tll_init(); struct client_data cdata; CHECK_BUF(sizeof(cdata)); @@ -252,25 +251,25 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data) const char *cwd = (const char *)p; p += cdata.cwd_len; LOG_DBG("CWD = %.*s", cdata.cwd_len, cwd); - CHECK_BUF_AND_NULL(cdata.term_len); - const char *term_env = (const char *)p; p += cdata.term_len; - LOG_DBG("TERM = %.*s", cdata.term_len, term_env); - - CHECK_BUF_AND_NULL(cdata.title_len); - const char *title = (const char *)p; p += cdata.title_len; - LOG_DBG("title = %.*s", cdata.title_len, title); - - CHECK_BUF_AND_NULL(cdata.app_id_len); - const char *app_id = (const char *)p; p += cdata.app_id_len; - LOG_DBG("app-id = %.*s", cdata.app_id_len, app_id); - - argv = xcalloc(cdata.argc + 1, sizeof(argv[0])); - - for (uint16_t i = 0; i < cdata.argc; i++) { - struct client_argv arg; + /* Overrides */ + for (uint16_t i = 0; i < cdata.override_count; i++) { + struct client_string arg; CHECK_BUF(sizeof(arg)); - memcpy(&arg, p, sizeof(arg)); - p += sizeof(arg); + memcpy(&arg, p, sizeof(arg)); p += sizeof(arg); + + CHECK_BUF_AND_NULL(arg.len); + const char *str = (const char *)p; + p += arg.len; + + tll_push_back(overrides, xstrdup(str)); + } + + /* argv */ + argv = xcalloc(cdata.argc + 1, sizeof(argv[0])); + for (uint16_t i = 0; i < cdata.argc; i++) { + struct client_string arg; + CHECK_BUF(sizeof(arg)); + memcpy(&arg, p, sizeof(arg)); p += sizeof(arg); CHECK_BUF_AND_NULL(arg.len); argv[i] = (char *)p; p += arg.len; @@ -284,41 +283,21 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data) struct terminal_instance *instance = malloc(sizeof(struct terminal_instance)); const bool need_to_clone_conf = - strlen(term_env) > 0 || - strlen(title) > 0 || - strlen(app_id) > 0 || + tll_length(overrides)> 0 || cdata.hold != server->conf->hold_at_exit || - cdata.login_shell != server->conf->login_shell || - cdata.no_wait != server->conf->no_wait || - (cdata.maximized && server->conf->startup_mode != STARTUP_MAXIMIZED) || - (cdata.fullscreen && server->conf->startup_mode != STARTUP_FULLSCREEN) || - (cdata.width > 0 && cdata.height > 0); + cdata.no_wait != server->conf->no_wait; struct config *conf = NULL; if (need_to_clone_conf) { - conf = xmalloc(sizeof(*conf)); - *conf = *server->conf; + conf = config_clone(server->conf); - conf->term = strlen(term_env) > 0 - ? xstrdup(term_env) : xstrdup(server->conf->term); - conf->title = strlen(title) > 0 - ? xstrdup(title) : xstrdup(server->conf->title); - conf->app_id = strlen(app_id) > 0 - ? xstrdup(app_id) : xstrdup(server->conf->app_id); - conf->hold_at_exit = cdata.hold; - conf->login_shell = cdata.login_shell; - conf->no_wait = cdata.no_wait; + if (cdata.no_wait != server->conf->no_wait) + conf->no_wait = cdata.no_wait; - if (cdata.maximized) - conf->startup_mode = STARTUP_MAXIMIZED; - else if (cdata.fullscreen) - conf->startup_mode = STARTUP_FULLSCREEN; + if (cdata.hold != server->conf->hold_at_exit) + conf->hold_at_exit = cdata.hold; - if (cdata.width > 0 && cdata.height > 0) { - conf->size.type = cdata.size_type; - conf->size.width = cdata.width; - conf->size.height = cdata.height; - } + config_override_apply(conf, &overrides, false); } *instance = (struct terminal_instance) { @@ -349,6 +328,7 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data) instance->client = client; client->instance = instance; free(argv); + tll_free_and_free(overrides, free); } return true; @@ -357,6 +337,7 @@ shutdown: LOG_DBG("client FD=%d: disconnected", client->fd); free(argv); + tll_free_and_free(overrides, free); fdm_del(fdm, fd); client->fd = -1;