From dbfce2a4d8ea0bd4773eacf154fd3e3406d1be5e Mon Sep 17 00:00:00 2001 From: mmcc Date: Tue, 8 Dec 2015 00:51:17 +0000 Subject: [PATCH 1/2] Use ^= instead of a verbose alternative. ok nicm@ --- cmd-switch-client.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cmd-switch-client.c b/cmd-switch-client.c index dbdc5b63..4c847584 100644 --- a/cmd-switch-client.c +++ b/cmd-switch-client.c @@ -52,12 +52,8 @@ cmd_switch_client_exec(struct cmd *self, struct cmd_q *cmdq) if ((c = cmd_find_client(cmdq, args_get(args, 'c'), 0)) == NULL) return (CMD_RETURN_ERROR); - if (args_has(args, 'r')) { - if (c->flags & CLIENT_READONLY) - c->flags &= ~CLIENT_READONLY; - else - c->flags |= CLIENT_READONLY; - } + if (args_has(args, 'r')) + c->flags ^= CLIENT_READONLY; tablename = args_get(args, 'T'); if (tablename != NULL) { From d2fb0efcd197bf0d581a0f7b1e27223d095cb339 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 8 Dec 2015 01:10:31 +0000 Subject: [PATCH 2/2] Add hooks infrastructure, basic commands (set-hook, show-hooks) and a couple of not very useful client hooks. This will eventually let commands be run at various points and on notifications. Joint work with Thomas Adam. --- Makefile | 2 + cmd-attach-session.c | 5 +- cmd-detach-client.c | 12 ++-- cmd-set-hook.c | 116 ++++++++++++++++++++++++++++++++++++ cmd.c | 4 ++ hooks.c | 139 +++++++++++++++++++++++++++++++++++++++++++ server-client.c | 15 +++++ session.c | 4 ++ tmux.1 | 46 ++++++++++++++ tmux.c | 3 + tmux.h | 31 ++++++++-- 11 files changed, 364 insertions(+), 13 deletions(-) create mode 100644 cmd-set-hook.c create mode 100644 hooks.c diff --git a/Makefile b/Makefile index 10c6a783..a60f1f19 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,7 @@ SRCS= alerts.c \ cmd-send-keys.c \ cmd-set-buffer.c \ cmd-set-environment.c \ + cmd-set-hook.c \ cmd-set-option.c \ cmd-show-environment.c \ cmd-show-messages.c \ @@ -78,6 +79,7 @@ SRCS= alerts.c \ format.c \ grid-view.c \ grid.c \ + hooks.c \ input-keys.c \ input.c \ job.c \ diff --git a/cmd-attach-session.c b/cmd-attach-session.c index 5bde0d80..45b05b09 100644 --- a/cmd-attach-session.c +++ b/cmd-attach-session.c @@ -108,7 +108,7 @@ cmd_attach_session(struct cmd_q *cmdq, const char *tflag, int dflag, int rflag, TAILQ_FOREACH(c_loop, &clients, entry) { if (c_loop->session != s || c == c_loop) continue; - proc_send_s(c_loop->peer, MSG_DETACH, s->name); + server_client_detach(c, MSG_DETACH); } } @@ -139,7 +139,7 @@ cmd_attach_session(struct cmd_q *cmdq, const char *tflag, int dflag, int rflag, TAILQ_FOREACH(c_loop, &clients, entry) { if (c_loop->session != s || c == c_loop) continue; - proc_send_s(c_loop->peer, MSG_DETACH, s->name); + server_client_detach(c_loop, MSG_DETACH); } } @@ -159,6 +159,7 @@ cmd_attach_session(struct cmd_q *cmdq, const char *tflag, int dflag, int rflag, if (~c->flags & CLIENT_CONTROL) proc_send(c->peer, MSG_READY, -1, NULL, 0); + hooks_run(c->session->hooks, "client-attached", c); cmdq->client_exit = 0; } recalculate_sizes(); diff --git a/cmd-detach-client.c b/cmd-detach-client.c index f7369df0..d8128eae 100644 --- a/cmd-detach-client.c +++ b/cmd-detach-client.c @@ -72,9 +72,8 @@ cmd_detach_client_exec(struct cmd *self, struct cmd_q *cmdq) return (CMD_RETURN_ERROR); TAILQ_FOREACH(cloop, &clients, entry) { - if (cloop->session != s) - continue; - proc_send_s(cloop->peer, msgtype, cloop->session->name); + if (cloop->session == s) + server_client_detach(cloop, msgtype); } return (CMD_RETURN_STOP); } @@ -85,13 +84,12 @@ cmd_detach_client_exec(struct cmd *self, struct cmd_q *cmdq) if (args_has(args, 'a')) { TAILQ_FOREACH(cloop, &clients, entry) { - if (cloop->session == NULL || cloop == c) - continue; - proc_send_s(cloop->peer, msgtype, cloop->session->name); + if (cloop->session != NULL && cloop != c) + server_client_detach(cloop, msgtype); } return (CMD_RETURN_NORMAL); } - proc_send_s(c->peer, msgtype, c->session->name); + server_client_detach(c, msgtype); return (CMD_RETURN_STOP); } diff --git a/cmd-set-hook.c b/cmd-set-hook.c new file mode 100644 index 00000000..f35e7a0a --- /dev/null +++ b/cmd-set-hook.c @@ -0,0 +1,116 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2012 Thomas Adam + * + * Permission to use, copy, modify, and 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 MIND, 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 + +#include +#include + +#include "tmux.h" + +/* + * Set or show global or session hooks. + */ + +enum cmd_retval cmd_set_hook_exec(struct cmd *, struct cmd_q *); + +const struct cmd_entry cmd_set_hook_entry = { + "set-hook", NULL, + "gt:u", 1, 2, + "[-gu] " CMD_TARGET_SESSION_USAGE " hook-name [command]", + 0, + cmd_set_hook_exec +}; + +const struct cmd_entry cmd_show_hooks_entry = { + "show-hooks", NULL, + "gt:", 0, 1, + "[-g] " CMD_TARGET_SESSION_USAGE, + 0, + cmd_set_hook_exec +}; + +enum cmd_retval +cmd_set_hook_exec(struct cmd *self, struct cmd_q *cmdq) +{ + struct args *args = self->args; + struct session *s; + struct cmd_list *cmdlist; + struct hooks *hooks; + struct hook *hook; + char *cause, *tmp; + const char *name, *cmd; + + if (args_has(args, 'g')) + hooks = global_hooks; + else { + s = cmd_find_session(cmdq, args_get(args, 't'), 0); + if (s == NULL) + return (CMD_RETURN_ERROR); + hooks = s->hooks; + } + + if (self->entry == &cmd_show_hooks_entry) { + hook = hooks_first(hooks); + while (hook != NULL) { + tmp = cmd_list_print(hook->cmdlist); + cmdq_print(cmdq, "%s -> %s", hook->name, tmp); + free(tmp); + + hook = hooks_next(hook); + } + return (CMD_RETURN_NORMAL); + } + + name = args->argv[0]; + if (*name == '\0') { + cmdq_error(cmdq, "invalid hook name"); + return (CMD_RETURN_ERROR); + } + if (args->argc < 2) + cmd = NULL; + else + cmd = args->argv[1]; + + if (args_has(args, 'u')) { + if (cmd != NULL) { + cmdq_error(cmdq, "command passed to unset hook: %s", + name); + return (CMD_RETURN_ERROR); + } + if ((hook = hooks_find(hooks, name)) != NULL) + hooks_remove(hooks, hook); + return (CMD_RETURN_NORMAL); + } + + if (cmd == NULL) { + cmdq_error(cmdq, "no command to set hook: %s", name); + return (CMD_RETURN_ERROR); + } + if (cmd_string_parse(cmd, &cmdlist, NULL, 0, &cause) != 0) { + if (cause != NULL) { + cmdq_error(cmdq, "%s", cause); + free(cause); + } + return (CMD_RETURN_ERROR); + } + hooks_add(hooks, name, cmdlist); + cmd_list_free(cmdlist); + + return (CMD_RETURN_NORMAL); +} diff --git a/cmd.c b/cmd.c index 824d9caf..a950a49a 100644 --- a/cmd.c +++ b/cmd.c @@ -96,10 +96,12 @@ extern const struct cmd_entry cmd_send_prefix_entry; extern const struct cmd_entry cmd_server_info_entry; extern const struct cmd_entry cmd_set_buffer_entry; extern const struct cmd_entry cmd_set_environment_entry; +extern const struct cmd_entry cmd_set_hook_entry; extern const struct cmd_entry cmd_set_option_entry; extern const struct cmd_entry cmd_set_window_option_entry; extern const struct cmd_entry cmd_show_buffer_entry; extern const struct cmd_entry cmd_show_environment_entry; +extern const struct cmd_entry cmd_show_hooks_entry; extern const struct cmd_entry cmd_show_messages_entry; extern const struct cmd_entry cmd_show_options_entry; extern const struct cmd_entry cmd_show_window_options_entry; @@ -183,10 +185,12 @@ const struct cmd_entry *cmd_table[] = { &cmd_server_info_entry, &cmd_set_buffer_entry, &cmd_set_environment_entry, + &cmd_set_hook_entry, &cmd_set_option_entry, &cmd_set_window_option_entry, &cmd_show_buffer_entry, &cmd_show_environment_entry, + &cmd_show_hooks_entry, &cmd_show_messages_entry, &cmd_show_options_entry, &cmd_show_window_options_entry, diff --git a/hooks.c b/hooks.c new file mode 100644 index 00000000..95d6b9d8 --- /dev/null +++ b/hooks.c @@ -0,0 +1,139 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2012 Thomas Adam + * + * Permission to use, copy, modify, and 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 MIND, 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 + +#include +#include + +#include "tmux.h" + +struct hooks { + RB_HEAD(hooks_tree, hook) tree; + struct hooks *parent; +}; + +static int hooks_cmp(struct hook *, struct hook *); +RB_PROTOTYPE(hooks_tree, hook, entry, hooks_cmp); +RB_GENERATE(hooks_tree, hook, entry, hooks_cmp); + +struct hook *hooks_find1(struct hooks *, const char *); + +static int +hooks_cmp(struct hook *hook1, struct hook *hook2) +{ + return (strcmp(hook1->name, hook2->name)); +} + +struct hooks * +hooks_create(struct hooks *parent) +{ + struct hooks *hooks; + + hooks = xcalloc(1, sizeof *hooks); + RB_INIT(&hooks->tree); + hooks->parent = parent; + return (hooks); +} + +void +hooks_free(struct hooks *hooks) +{ + struct hook *hook, *hook1; + + RB_FOREACH_SAFE(hook, hooks_tree, &hooks->tree, hook1) + hooks_remove(hooks, hook); + free(hooks); +} + +struct hook * +hooks_first(struct hooks *hooks) +{ + return (RB_MIN(hooks_tree, &hooks->tree)); +} + +struct hook * +hooks_next(struct hook *hook) +{ + return (RB_NEXT(hooks_tree, &hooks->tree, hook)); +} + +void +hooks_add(struct hooks *hooks, const char *name, struct cmd_list *cmdlist) +{ + struct hook *hook; + + if ((hook = hooks_find1(hooks, name)) != NULL) + hooks_remove(hooks, hook); + + hook = xcalloc(1, sizeof *hook); + hook->name = xstrdup(name); + hook->cmdlist = cmdlist; + hook->cmdlist->references++; + RB_INSERT(hooks_tree, &hooks->tree, hook); +} + +void +hooks_remove(struct hooks *hooks, struct hook *hook) +{ + RB_REMOVE(hooks_tree, &hooks->tree, hook); + cmd_list_free(hook->cmdlist); + free((char *) hook->name); + free(hook); +} + +struct hook * +hooks_find1(struct hooks *hooks, const char *name) +{ + struct hook hook; + + hook.name = name; + return (RB_FIND(hooks_tree, &hooks->tree, &hook)); +} + +struct hook * +hooks_find(struct hooks *hooks, const char *name) +{ + struct hook hook0, *hook; + + hook0.name = name; + hook = RB_FIND(hooks_tree, &hooks->tree, &hook0); + while (hook == NULL) { + hooks = hooks->parent; + if (hooks == NULL) + break; + hook = RB_FIND(hooks_tree, &hooks->tree, &hook0); + } + return (hook); +} + +void +hooks_run(struct hooks *hooks, const char *name, struct client *c) +{ + struct hook *hook; + struct cmd_q *cmdq; + + hook = hooks_find(hooks, name); + if (hook == NULL) + return; + log_debug("running hook %s", name); + + cmdq = cmdq_new(c); + cmdq_run(cmdq, hook->cmdlist, NULL); + cmdq_free(cmdq); +} diff --git a/server-client.c b/server-client.c index d2abd356..c948e980 100644 --- a/server-client.c +++ b/server-client.c @@ -256,6 +256,19 @@ server_client_free(__unused int fd, __unused short events, void *arg) free(c); } +/* Detach a client. */ +void +server_client_detach(struct client *c, enum msgtype msgtype) +{ + struct session *s = c->session; + + if (s == NULL) + return; + + hooks_run(c->session->hooks, "client-detached", c); + proc_send_s(c->peer, msgtype, s->name); +} + /* Check for mouse keys. */ key_code server_client_check_mouse(struct client *c) @@ -995,6 +1008,8 @@ server_client_dispatch(struct imsg *imsg, void *arg) recalculate_sizes(); server_redraw_client(c); } + if (c->session != NULL) + hooks_run(c->session->hooks, "client-resized", c); break; case MSG_EXITING: if (datalen != 0) diff --git a/session.c b/session.c index c5721c9a..b3ae2b42 100644 --- a/session.c +++ b/session.c @@ -123,7 +123,9 @@ session_create(const char *name, int argc, char **argv, const char *path, s->environ = environ_create(); if (env != NULL) environ_copy(env, s->environ); + s->options = options_create(global_s_options); + s->hooks = hooks_create(global_hooks); s->tio = NULL; if (tio != NULL) { @@ -189,7 +191,9 @@ session_free(__unused int fd, __unused short events, void *arg) if (s->references == 0) { environ_free(s->environ); + options_free(s->options); + hooks_free(s->hooks); free(s->name); free(s); diff --git a/tmux.1 b/tmux.1 index 7d45688c..6971100e 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3193,6 +3193,52 @@ is used. .Fl v shows only the option value, not the name. .El +.Sh HOOKS +.Nm +allows commands to run on various triggers, called +.Em hooks . +Each hook has a +.Em name . +The following hooks are available: +.Bl -tag -width "XXXXXXXXXXXXXXXX" +.It client-attached +Run when a client is attached. +.It client-detached +Run when a client is detached +.It client-resized +Run when a client is resized. +.El +.Pp +Hooks are managed with these commands: +.Bl -tag -width Ds +.It Xo Ic set-hook +.Op Fl g +.Op Fl t Ar target-session +.Ar hook-name +.Ar command +.Xc +Sets hook +.Ar hook-name +to +.Ar command . +If +.Fl g +is given, +.Em hook-name +is added to the global list of hooks, otherwise it is added to the session +hooks (for +.Ar target-session +with +.Fl t ) . +Like options, session hooks inherit from the global ones. +.It Xo Ic show-hooks +.Op Fl g +.Op Fl t Ar target-session +.Xc +Shows the global list of hooks with +.Fl g , +otherwise the session hooks. +.Ed .Sh MOUSE SUPPORT If the .Ic mouse diff --git a/tmux.c b/tmux.c index 68f0092d..fe5e54a5 100644 --- a/tmux.c +++ b/tmux.c @@ -38,6 +38,7 @@ struct options *global_options; /* server options */ struct options *global_s_options; /* session options */ struct options *global_w_options; /* window options */ struct environ *global_environ; +struct hooks *global_hooks; struct timeval start_time; const char *socket_path; @@ -269,6 +270,8 @@ main(int argc, char **argv) flags |= CLIENT_UTF8; } + global_hooks = hooks_create(NULL); + global_environ = environ_create(); for (var = environ; *var != NULL; var++) environ_put(global_environ, *var); diff --git a/tmux.h b/tmux.h index a1e16955..fa589f57 100644 --- a/tmux.h +++ b/tmux.h @@ -691,6 +691,14 @@ struct grid { struct grid_line *linedata; }; +/* Hook data structures. */ +struct hook { + const char *name; + struct cmd_q *cmdq; + struct cmd_list *cmdlist; + RB_ENTRY(hook) entry; +}; + /* Option data structures. */ struct options_entry { char *name; @@ -1011,6 +1019,7 @@ struct session { struct winlink_stack lastw; struct winlinks windows; + struct hooks *hooks; struct options *options; #define SESSION_UNATTACHED 0x1 /* not attached to any clients */ @@ -1427,10 +1436,11 @@ struct options_table_entry { #define CMD_BUFFER_USAGE "[-b buffer-name]" /* tmux.c */ -extern struct options *global_options; -extern struct options *global_s_options; -extern struct options *global_w_options; -extern struct environ *global_environ; +extern struct hooks *global_hooks; +extern struct options *global_options; +extern struct options *global_s_options; +extern struct options *global_w_options; +extern struct environ *global_environ; extern struct timeval start_time; extern const char *socket_path; const char *getshell(void); @@ -1495,6 +1505,18 @@ void format_defaults_pane(struct format_tree *, void format_defaults_paste_buffer(struct format_tree *, struct paste_buffer *); +/* hooks.c */ +struct hook; +struct hooks *hooks_create(struct hooks *); +void hooks_free(struct hooks *); +struct hook *hooks_first(struct hooks *); +struct hook *hooks_next(struct hook *); +void hooks_add(struct hooks *, const char *, struct cmd_list *); +void hooks_copy(struct hooks *, struct hooks *); +void hooks_remove(struct hooks *, struct hook *); +struct hook *hooks_find(struct hooks *, const char *); +void hooks_run(struct hooks *, const char *, struct client *); + /* mode-key.c */ extern const struct mode_key_table mode_key_tables[]; extern struct mode_key_tree mode_key_tree_vi_edit; @@ -1782,6 +1804,7 @@ void server_client_create(int); int server_client_open(struct client *, char **); void server_client_unref(struct client *); void server_client_lost(struct client *); +void server_client_detach(struct client *, enum msgtype); void server_client_loop(void); void server_client_push_stdout(struct client *); void server_client_push_stderr(struct client *);