diff --git a/config.c b/config.c index a367ed88..2f7a14ca 100644 --- a/config.c +++ b/config.c @@ -501,30 +501,70 @@ str_to_pt_or_px(const char *s, struct pt_or_px *res, struct config *conf, return true; } +static void NOINLINE +free_argv(struct argv *argv) +{ + if (argv->args == NULL) + return; + for (char **a = argv->args; *a != NULL; a++) + free(*a); + free(argv->args); + argv->args = NULL; +} + +static void NOINLINE +clone_argv(struct argv *dst, const struct argv *src) +{ + if (src->args == NULL) { + dst->args = NULL; + return; + } + + size_t count = 0; + for (char **args = src->args; *args != NULL; args++) + count++; + + dst->args = xmalloc((count + 1) * sizeof(dst->args[0])); + for (char **args_src = src->args, **args_dst = dst->args; + *args_src != NULL; args_src++, + args_dst++) + { + *args_dst = xstrdup(*args_src); + } + dst->args[count] = NULL; +} + +static void +spawn_template_free(struct config_spawn_template *template) +{ + free_argv(&template->argv); +} + +static void +spawn_template_clone(struct config_spawn_template *dst, + const struct config_spawn_template *src) +{ + clone_argv(&dst->argv, &src->argv); +} + static bool NOINLINE str_to_spawn_template(struct config *conf, const char *s, struct config_spawn_template *template, const char *path, int lineno, const char *section, const char *key) { - free(template->raw_cmd); - free(template->argv); + spawn_template_free(template); - template->raw_cmd = NULL; - template->argv = NULL; - - char *raw_cmd = xstrdup(s); char **argv = NULL; - if (!tokenize_cmdline(raw_cmd, &argv)) { + if (!tokenize_cmdline(s, &argv)) { LOG_AND_NOTIFY_ERR( "%s:%d: [%s]: %s: syntax error in command line", path, lineno, section, key); return false; } - template->raw_cmd = raw_cmd; - template->argv = argv; + template->argv.args = argv; return true; } @@ -1525,12 +1565,12 @@ has_key_binding_collisions(struct config *conf, bool sym = combo1->sym == combo2->sym; if (shift && alt && ctrl && meta && sym) { - bool has_pipe = combo1->pipe.cmd != NULL; + bool has_pipe = combo1->pipe.argv.args != NULL; LOG_AND_NOTIFY_ERR("%s:%d: %s already mapped to '%s%s%s%s'", path, lineno, combo2->text, action_map[combo1->action], has_pipe ? " [" : "", - has_pipe ? combo1->pipe.cmd : "", + has_pipe ? combo1->pipe.argv.args[0] : "", has_pipe ? "]" : ""); return true; } @@ -1585,11 +1625,10 @@ argv_compare(char *const *argv1, char *const *argv2) * - argv: allocatd array containing {"cmd", "arg1", "arg2", NULL}. Caller frees. */ static ssize_t -pipe_argv_from_string(const char *value, char **cmd, char ***argv, +pipe_argv_from_string(const char *value, char ***argv, struct config *conf, const char *path, unsigned lineno) { - *cmd = NULL; *argv = NULL; if (value[0] != '[') @@ -1602,11 +1641,11 @@ pipe_argv_from_string(const char *value, char **cmd, char ***argv, } size_t pipe_len = pipe_cmd_end - value - 1; - *cmd = xstrndup(&value[1], pipe_len); + char *cmd = xstrndup(&value[1], pipe_len); - if (!tokenize_cmdline(*cmd, argv)) { + if (!tokenize_cmdline(cmd, argv)) { LOG_AND_NOTIFY_ERR("%s:%d: syntax error in command line", path, lineno); - free(*cmd); + free(cmd); return -1; } @@ -1617,6 +1656,7 @@ pipe_argv_from_string(const char *value, char **cmd, char ***argv, remove_len++; } + free(cmd); return remove_len; } @@ -1627,11 +1667,10 @@ parse_key_binding_section( struct config_key_binding_list *bindings, struct config *conf, const char *path, unsigned lineno) { - char *pipe_cmd; char **pipe_argv; ssize_t pipe_remove_len = pipe_argv_from_string( - value, &pipe_cmd, &pipe_argv, conf, path, lineno); + value, &pipe_argv, conf, path, lineno); if (pipe_remove_len < 0) return false; @@ -1653,17 +1692,12 @@ parse_key_binding_section( if (binding->action != action) continue; - if (binding->pipe.master_copy) { - free(binding->pipe.cmd); - free(binding->pipe.argv); - } + if (binding->pipe.master_copy) + free_argv(&binding->pipe.argv); binding->action = BIND_ACTION_NONE; - binding->pipe.cmd = NULL; - binding->pipe.argv = NULL; } free(pipe_argv); - free(pipe_cmd); return true; } @@ -1675,7 +1709,6 @@ parse_key_binding_section( path, lineno)) { free(pipe_argv); - free(pipe_cmd); free_key_combo_list(&key_combos); return false; } @@ -1685,18 +1718,14 @@ parse_key_binding_section( struct config_key_binding *binding = &bindings->arr[i]; if (binding->action == action && - ((binding->pipe.argv == NULL && pipe_argv == NULL) || - (binding->pipe.argv != NULL && pipe_argv != NULL && - argv_compare(binding->pipe.argv, pipe_argv) == 0))) + ((binding->pipe.argv.args == NULL && pipe_argv == NULL) || + (binding->pipe.argv.args != NULL && pipe_argv != NULL && + argv_compare(binding->pipe.argv.args, pipe_argv) == 0))) { - if (binding->pipe.master_copy) { - free(binding->pipe.cmd); - free(binding->pipe.argv); - } + if (binding->pipe.master_copy) + free_argv(&binding->pipe.argv); binding->action = BIND_ACTION_NONE; - binding->pipe.cmd = NULL; - binding->pipe.argv = NULL; } } @@ -1714,8 +1743,9 @@ parse_key_binding_section( .modifiers = combo->modifiers, .sym = combo->sym, .pipe = { - .cmd = pipe_cmd, - .argv = pipe_argv, + .argv = { + .args = pipe_argv, + }, .master_copy = first, }, }; @@ -1731,7 +1761,6 @@ parse_key_binding_section( LOG_AND_NOTIFY_ERR("%s:%u: [%s]: %s: invalid action", path, lineno, section, key); - free(pipe_cmd); free(pipe_argv); return false; } @@ -1936,12 +1965,12 @@ has_mouse_binding_collisions(struct config *conf, const struct key_combo_list *k bool count = combo1->count == combo2->m.count; if (shift && alt && ctrl && meta && button && count) { - bool has_pipe = combo1->pipe.cmd != NULL; + bool has_pipe = combo1->pipe.argv.args != NULL; LOG_AND_NOTIFY_ERR("%s:%d: %s already mapped to '%s%s%s%s'", path, lineno, combo2->text, binding_action_map[combo1->action], has_pipe ? " [" : "", - has_pipe ? combo1->pipe.cmd : "", + has_pipe ? combo1->pipe.argv.args[0] : "", has_pipe ? "]" : ""); return true; } @@ -1957,11 +1986,10 @@ parse_section_mouse_bindings( const char *key, const char *value, struct config *conf, const char *path, unsigned lineno, bool errors_are_fatal) { - char *pipe_cmd; char **pipe_argv; ssize_t pipe_remove_len = pipe_argv_from_string( - value, &pipe_cmd, &pipe_argv, conf, path, lineno); + value, &pipe_argv, conf, path, lineno); if (pipe_remove_len < 0) return false; @@ -1985,17 +2013,12 @@ parse_section_mouse_bindings( &conf->bindings.mouse.arr[i]; if (binding->action == action) { - if (binding->pipe.master_copy) { - free(binding->pipe.cmd); - free(binding->pipe.argv); - } + if (binding->pipe.master_copy) + free_argv(&binding->pipe.argv); binding->action = BIND_ACTION_NONE; - binding->pipe.cmd = NULL; - binding->pipe.argv = NULL; } } free(pipe_argv); - free(pipe_cmd); return true; } @@ -2004,7 +2027,6 @@ parse_section_mouse_bindings( has_mouse_binding_collisions(conf, &key_combos, path, lineno)) { free(pipe_argv); - free(pipe_cmd); free_key_combo_list(&key_combos); return false; } @@ -2014,17 +2036,13 @@ parse_section_mouse_bindings( struct config_mouse_binding *binding = &conf->bindings.mouse.arr[i]; if (binding->action == action && - ((binding->pipe.argv == NULL && pipe_argv == NULL) || - (binding->pipe.argv != NULL && pipe_argv != NULL && - argv_compare(binding->pipe.argv, pipe_argv) == 0))) + ((binding->pipe.argv.args == NULL && pipe_argv == NULL) || + (binding->pipe.argv.args != NULL && pipe_argv != NULL && + argv_compare(binding->pipe.argv.args, pipe_argv) == 0))) { - if (binding->pipe.master_copy) { - free(binding->pipe.cmd); - free(binding->pipe.argv); - } + if (binding->pipe.master_copy) + free_argv(&binding->pipe.argv); binding->action = BIND_ACTION_NONE; - binding->pipe.cmd = NULL; - binding->pipe.argv = NULL; } } @@ -2044,8 +2062,9 @@ parse_section_mouse_bindings( .button = combo->m.button, .count = combo->m.count, .pipe = { - .cmd = pipe_cmd, - .argv = pipe_argv, + .argv = { + .args = pipe_argv, + }, .master_copy = first, }, }; @@ -2060,7 +2079,6 @@ parse_section_mouse_bindings( LOG_AND_NOTIFY_ERR("%s:%u: [mouse-bindings]: %s: invalid key", path, lineno, key); free(pipe_argv); - free(pipe_cmd); return false; } @@ -2594,8 +2612,7 @@ config_load(struct config *conf, const char *conf_path, .urgent = false, .notify = false, .command = { - .raw_cmd = NULL, - .argv = NULL, + .argv = {.args = NULL}, }, .command_focused = false, }, @@ -2671,8 +2688,7 @@ config_load(struct config *conf, const char *conf_path, .selection_target = SELECTION_TARGET_PRIMARY, .hold_at_exit = false, .notify = { - .raw_cmd = NULL, - .argv = NULL, + .argv = {.args = NULL}, }, .tweak = { @@ -2717,12 +2733,9 @@ config_load(struct config *conf, const char *conf_path, } } - conf->notify.raw_cmd = xstrdup( - "notify-send -a ${app-id} -i ${app-id} ${title} ${body}"); - tokenize_cmdline(conf->notify.raw_cmd, &conf->notify.argv); - - conf->url.launch.raw_cmd = xstrdup("xdg-open ${url}"); - tokenize_cmdline(conf->url.launch.raw_cmd, &conf->url.launch.argv); + tokenize_cmdline("notify-send -a ${app-id} -i ${app-id} ${title} ${body}", + &conf->notify.argv.args); + tokenize_cmdline("xdg-open ${url}", &conf->url.launch.argv.args); static const wchar_t *url_protocols[] = { L"http://", @@ -2847,36 +2860,145 @@ config_override_apply(struct config *conf, config_override_t *overrides, bool er return true; } -static void NOINLINE -free_spawn_template(struct config_spawn_template *template) +static void +binding_pipe_free(struct config_binding_pipe *pipe) { - free(template->raw_cmd); - free(template->argv); + if (pipe->master_copy) + free_argv(&pipe->argv); +} + +static void +binding_pipe_clone(struct config_binding_pipe *dst, + const struct config_binding_pipe *src) +{ + xassert(src->master_copy); + clone_argv(&dst->argv, &src->argv); } static void NOINLINE -binding_pipe_free(struct config_binding_pipe *pipe) +key_binding_list_free(struct config_key_binding_list *bindings) { - if (pipe->master_copy) { - free(pipe->cmd); - free(pipe->argv); + for (size_t i = 0; i < bindings->count; i++) + binding_pipe_free(&bindings->arr[i].pipe); + free(bindings->arr); +} + +static void NOINLINE +key_binding_list_clone(struct config_key_binding_list *dst, + const struct config_key_binding_list *src) +{ + struct argv *last_master_argv = NULL; + + dst->count = src->count; + dst->arr = xmalloc(src->count * sizeof(dst->arr[0])); + + for (size_t i = 0; i < src->count; i++) { + const struct config_key_binding *old = &src->arr[i]; + struct config_key_binding *new = &dst->arr[i]; + + *new = *old; + + if (old->pipe.argv.args == NULL) + continue; + + if (old->pipe.master_copy) { + binding_pipe_clone(&new->pipe, &old->pipe); + last_master_argv = &new->pipe.argv; + } else { + xassert(last_master_argv != NULL); + new->pipe.argv = *last_master_argv; + } } } static void -key_binding_free(struct config_key_binding *binding) -{ - binding_pipe_free(&binding->pipe); -} - -static void -key_binding_list_free(struct config_key_binding_list *bindings) +mouse_binding_list_free(struct config_mouse_binding_list *bindings) { for (size_t i = 0; i < bindings->count; i++) - key_binding_free(&bindings->arr[i]); + binding_pipe_free(&bindings->arr[i].pipe); free(bindings->arr); } +static void NOINLINE +mouse_binding_list_clone(struct config_mouse_binding_list *dst, + const struct config_mouse_binding_list *src) +{ + struct argv *last_master_argv = NULL; + + dst->count = src->count; + dst->arr = xmalloc(src->count * sizeof(dst->arr[0])); + + for (size_t i = 0; i < src->count; i++) { + const struct config_mouse_binding *old = &src->arr[i]; + struct config_mouse_binding *new = &dst->arr[i]; + + *new = *old; + + if (old->pipe.argv.args == NULL) + continue; + + if (old->pipe.master_copy) { + binding_pipe_clone(&new->pipe, &old->pipe); + last_master_argv = &new->pipe.argv; + } else { + xassert(last_master_argv != NULL); + new->pipe.argv = *last_master_argv; + } + } +} + +struct config * +config_clone(const struct config *old) +{ + struct config *conf = xmalloc(sizeof(*conf)); + *conf = *old; + + conf->term = xstrdup(old->term); + conf->shell = xstrdup(old->shell); + conf->title = xstrdup(old->title); + conf->app_id = xstrdup(old->app_id); + conf->word_delimiters = xwcsdup(old->word_delimiters); + conf->scrollback.indicator.text = xwcsdup(old->scrollback.indicator.text); + conf->server_socket_path = xstrdup(old->server_socket_path); + spawn_template_clone(&conf->bell.command, &old->bell.command); + spawn_template_clone(&conf->notify, &old->notify); + + for (size_t i = 0; i < ALEN(conf->fonts); i++) { + struct config_font_list *dst = &conf->fonts[i]; + const struct config_font_list *src = &old->fonts[i]; + + dst->count = src->count; + dst->arr = xmalloc(dst->count * sizeof(dst->arr[0])); + + for (size_t j = 0; j < dst->count; j++) + dst->arr[j].pattern = xstrdup(src->arr[j].pattern); + } + + conf->url.label_letters = xwcsdup(old->url.label_letters); + spawn_template_clone(&conf->url.launch, &old->url.launch); + conf->url.protocols = xmalloc( + old->url.prot_count * sizeof(conf->url.protocols[0])); + for (size_t i = 0; i < old->url.prot_count; i++) + conf->url.protocols[i] = xwcsdup(old->url.protocols[i]); + + key_binding_list_clone(&conf->bindings.key, &old->bindings.key); + key_binding_list_clone(&conf->bindings.search, &old->bindings.search); + key_binding_list_clone(&conf->bindings.url, &old->bindings.url); + mouse_binding_list_clone(&conf->bindings.mouse, &old->bindings.mouse); + + conf->notifications.length = 0; + conf->notifications.head = conf->notifications.tail = 0; + tll_foreach(old->notifications, it) { + struct user_notification notif = { + .kind = it->item.kind, + .text = xstrdup(it->item.text), + }; + tll_push_back(conf->notifications, notif); + } + + return conf; +} + void config_free(struct config conf) { @@ -2885,15 +3007,15 @@ config_free(struct config conf) free(conf.title); free(conf.app_id); free(conf.word_delimiters); - free_spawn_template(&conf.bell.command); + spawn_template_free(&conf.bell.command); free(conf.scrollback.indicator.text); - free_spawn_template(&conf.notify); + spawn_template_free(&conf.notify); for (size_t i = 0; i < ALEN(conf.fonts); i++) config_font_list_destroy(&conf.fonts[i]); free(conf.server_socket_path); free(conf.url.label_letters); - free_spawn_template(&conf.url.launch); + spawn_template_free(&conf.url.launch); for (size_t i = 0; i < conf.url.prot_count; i++) free(conf.url.protocols[i]); free(conf.url.protocols); @@ -2901,10 +3023,7 @@ config_free(struct config conf) key_binding_list_free(&conf.bindings.key); key_binding_list_free(&conf.bindings.search); key_binding_list_free(&conf.bindings.url); - - for (size_t i = 0; i < conf.bindings.mouse.count; i++) - binding_pipe_free(&conf.bindings.mouse.arr[i].pipe); - free(conf.bindings.mouse.arr); + mouse_binding_list_free(&conf.bindings.mouse); user_notifications_free(&conf.notifications); } diff --git a/config.h b/config.h index bdb1a109..c3c36b6a 100644 --- a/config.h +++ b/config.h @@ -37,9 +37,12 @@ struct config_key_modifiers { bool meta; }; +struct argv { + char **args; +}; + struct config_binding_pipe { - char *cmd; - char **argv; + struct argv argv; bool master_copy; }; @@ -63,8 +66,7 @@ DEFINE_LIST(struct config_mouse_binding); typedef tll(char *) config_override_t; struct config_spawn_template { - char *raw_cmd; - char **argv; + struct argv argv; }; struct config { @@ -263,6 +265,7 @@ bool config_load( user_notifications_t *initial_user_notifications, config_override_t *overrides, bool errors_are_fatal); void config_free(struct config conf); +struct config *config_clone(const struct config *old); bool config_font_parse(const char *pattern, struct config_font *font); void config_font_list_destroy(struct config_font_list *font_list); diff --git a/input.c b/input.c index 50478da3..e33fdff4 100644 --- a/input.c +++ b/input.c @@ -519,7 +519,7 @@ convert_key_binding(const struct seat *seat, .sym = sym, .key_codes = key_codes_for_xkb_sym(seat->kbd.xkb_keymap, sym), .action = conf_binding->action, - .pipe_argv = conf_binding->pipe.argv, + .pipe_argv = conf_binding->pipe.argv.args, }; tll_push_back(*bindings, binding); } @@ -568,7 +568,7 @@ convert_mouse_binding(struct seat *seat, .mods = conf_modifiers_to_mask(seat, &conf_binding->modifiers), .button = conf_binding->button, .count = conf_binding->count, - .pipe_argv = conf_binding->pipe.argv, + .pipe_argv = conf_binding->pipe.argv.args, }; tll_push_back(seat->mouse.bindings, binding); } @@ -1970,7 +1970,8 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, if (match != NULL) { consumed = execute_binding( - seat, term, match->action, match->pipe.argv, serial); + seat, term, match->action, match->pipe.argv.args, + serial); } } } diff --git a/notify.c b/notify.c index e4f0ad14..a90cd317 100644 --- a/notify.c +++ b/notify.c @@ -27,7 +27,7 @@ notify_notify(const struct terminal *term, const char *title, const char *body) if (title == NULL || body == NULL) return; - if (term->conf->notify.argv == NULL) + if (term->conf->notify.argv.args == NULL) return; char **argv = NULL; diff --git a/slave.c b/slave.c index 8e800214..ba2d0867 100644 --- a/slave.c +++ b/slave.c @@ -293,9 +293,7 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv, char **shell_argv = NULL; if (argc == 0) { - char *shell_copy = xstrdup(conf_shell); - if (!tokenize_cmdline(shell_copy, &_shell_argv)) { - free(shell_copy); + if (!tokenize_cmdline(conf_shell, &_shell_argv)) { (void)!write(fork_pipe[1], &errno, sizeof(errno)); _exit(0); } diff --git a/spawn.c b/spawn.c index 22f0d1ed..8adef19d 100644 --- a/spawn.c +++ b/spawn.c @@ -114,7 +114,7 @@ spawn_expand_template(const struct config_spawn_template *template, *argc = 0; *argv = NULL; - for (; template->argv[*argc] != NULL; (*argc)++) + for (; template->argv.args[*argc] != NULL; (*argc)++) ; #define append(s, n) \ @@ -133,7 +133,7 @@ spawn_expand_template(const struct config_spawn_template *template, char *expanded = NULL; char *start = NULL; - char *last_end = template->argv[i]; + char *last_end = template->argv.args[i]; while ((start = strstr(last_end, "${")) != NULL) { /* Append everything from the last template's end to this @@ -173,7 +173,9 @@ spawn_expand_template(const struct config_spawn_template *template, last_end = end + 1; } - append(last_end, template->argv[i] + strlen(template->argv[i]) - last_end); + append( + last_end, + template->argv.args[i] + strlen(template->argv.args[i]) - last_end); (*argv)[i] = expanded; } (*argv)[*argc] = NULL; diff --git a/terminal.c b/terminal.c index b916868b..9e0795a1 100644 --- a/terminal.c +++ b/terminal.c @@ -2677,11 +2677,11 @@ term_bell(struct terminal *term) notify_notify(term, "Bell", "Bell in terminal"); } - if ((term->conf->bell.command.argv != NULL) && + if ((term->conf->bell.command.argv.args != NULL) && (!term->kbd_focus || term->conf->bell.command_focused)) { int devnull = open("/dev/null", O_RDONLY); - spawn(term->reaper, NULL, term->conf->bell.command.argv, devnull, -1, -1); + spawn(term->reaper, NULL, term->conf->bell.command.argv.args, devnull, -1, -1); if (devnull >= 0) close(devnull); diff --git a/tokenize.c b/tokenize.c index 35d52345..e29fed58 100644 --- a/tokenize.c +++ b/tokenize.c @@ -6,9 +6,10 @@ #define LOG_MODULE "tokenize" #define LOG_ENABLE_DBG 0 #include "log.h" +#include "xmalloc.h" static bool -push_argv(char ***argv, size_t *size, char *arg, size_t *argc) +push_argv(char ***argv, size_t *size, const char *arg, size_t len, size_t *argc) { if (arg != NULL && arg[0] == '%') return true; @@ -24,21 +25,23 @@ push_argv(char ***argv, size_t *size, char *arg, size_t *argc) *size = new_size; } - (*argv)[(*argc)++] = arg; + (*argv)[(*argc)++] = arg != NULL ? xstrndup(arg, len) : NULL; return true; } bool -tokenize_cmdline(char *cmdline, char ***argv) +tokenize_cmdline(const char *cmdline, char ***argv) { *argv = NULL; size_t argv_size = 0; + const char *final_end = cmdline + strlen(cmdline) + 1; + bool first_token_is_quoted = cmdline[0] == '"' || cmdline[0] == '\''; char delim = first_token_is_quoted ? cmdline[0] : ' '; - char *p = first_token_is_quoted ? &cmdline[1] : &cmdline[0]; - char *search_start = p; + const char *p = first_token_is_quoted ? &cmdline[1] : &cmdline[0]; + const char *search_start = p; size_t idx = 0; while (*p != '\0') { @@ -51,8 +54,8 @@ tokenize_cmdline(char *cmdline, char ***argv) return false; } - if (!push_argv(argv, &argv_size, p, &idx) || - !push_argv(argv, &argv_size, NULL, &idx)) + if (!push_argv(argv, &argv_size, p, final_end - p, &idx) || + !push_argv(argv, &argv_size, NULL, 0, &idx)) { goto err; } else @@ -68,9 +71,9 @@ tokenize_cmdline(char *cmdline, char ***argv) continue; } - *end = '\0'; + //*end = '\0'; - if (!push_argv(argv, &argv_size, p, &idx)) + if (!push_argv(argv, &argv_size, p, end - p, &idx)) goto err; p = end + 1; @@ -88,7 +91,7 @@ tokenize_cmdline(char *cmdline, char ***argv) search_start = p; } - if (!push_argv(argv, &argv_size, NULL, &idx)) + if (!push_argv(argv, &argv_size, NULL, 0, &idx)) goto err; return true; diff --git a/tokenize.h b/tokenize.h index f9579255..9e5de6c9 100644 --- a/tokenize.h +++ b/tokenize.h @@ -2,4 +2,4 @@ #include -bool tokenize_cmdline(char *cmdline, char ***argv); +bool tokenize_cmdline(const char *cmdline, char ***argv);