summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornicm <nicm>2015-12-08 01:10:31 +0000
committernicm <nicm>2015-12-08 01:10:31 +0000
commitd2fb0efcd197bf0d581a0f7b1e27223d095cb339 (patch)
tree3de4e6a7451d355ff752da7ec6013ea57c95b54c
parentdbfce2a4d8ea0bd4773eacf154fd3e3406d1be5e (diff)
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.
-rw-r--r--Makefile2
-rw-r--r--cmd-attach-session.c5
-rw-r--r--cmd-detach-client.c12
-rw-r--r--cmd-set-hook.c116
-rw-r--r--cmd.c4
-rw-r--r--hooks.c139
-rw-r--r--server-client.c15
-rw-r--r--session.c4
-rw-r--r--tmux.146
-rw-r--r--tmux.c3
-rw-r--r--tmux.h31
11 files changed, 364 insertions, 13 deletions
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 <thomas@xteddy.org>
+ *
+ * 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 <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#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 <thomas@xteddy.org>
+ *
+ * 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 <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#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 *);