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.
pull/125/merge
nicm 2015-12-08 01:10:31 +00:00
parent dbfce2a4d8
commit d2fb0efcd1
11 changed files with 364 additions and 13 deletions

View File

@ -56,6 +56,7 @@ SRCS= alerts.c \
cmd-send-keys.c \ cmd-send-keys.c \
cmd-set-buffer.c \ cmd-set-buffer.c \
cmd-set-environment.c \ cmd-set-environment.c \
cmd-set-hook.c \
cmd-set-option.c \ cmd-set-option.c \
cmd-show-environment.c \ cmd-show-environment.c \
cmd-show-messages.c \ cmd-show-messages.c \
@ -78,6 +79,7 @@ SRCS= alerts.c \
format.c \ format.c \
grid-view.c \ grid-view.c \
grid.c \ grid.c \
hooks.c \
input-keys.c \ input-keys.c \
input.c \ input.c \
job.c \ job.c \

View File

@ -108,7 +108,7 @@ cmd_attach_session(struct cmd_q *cmdq, const char *tflag, int dflag, int rflag,
TAILQ_FOREACH(c_loop, &clients, entry) { TAILQ_FOREACH(c_loop, &clients, entry) {
if (c_loop->session != s || c == c_loop) if (c_loop->session != s || c == c_loop)
continue; 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) { TAILQ_FOREACH(c_loop, &clients, entry) {
if (c_loop->session != s || c == c_loop) if (c_loop->session != s || c == c_loop)
continue; 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) if (~c->flags & CLIENT_CONTROL)
proc_send(c->peer, MSG_READY, -1, NULL, 0); proc_send(c->peer, MSG_READY, -1, NULL, 0);
hooks_run(c->session->hooks, "client-attached", c);
cmdq->client_exit = 0; cmdq->client_exit = 0;
} }
recalculate_sizes(); recalculate_sizes();

View File

@ -72,9 +72,8 @@ cmd_detach_client_exec(struct cmd *self, struct cmd_q *cmdq)
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
TAILQ_FOREACH(cloop, &clients, entry) { TAILQ_FOREACH(cloop, &clients, entry) {
if (cloop->session != s) if (cloop->session == s)
continue; server_client_detach(cloop, msgtype);
proc_send_s(cloop->peer, msgtype, cloop->session->name);
} }
return (CMD_RETURN_STOP); return (CMD_RETURN_STOP);
} }
@ -85,13 +84,12 @@ cmd_detach_client_exec(struct cmd *self, struct cmd_q *cmdq)
if (args_has(args, 'a')) { if (args_has(args, 'a')) {
TAILQ_FOREACH(cloop, &clients, entry) { TAILQ_FOREACH(cloop, &clients, entry) {
if (cloop->session == NULL || cloop == c) if (cloop->session != NULL && cloop != c)
continue; server_client_detach(cloop, msgtype);
proc_send_s(cloop->peer, msgtype, cloop->session->name);
} }
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
proc_send_s(c->peer, msgtype, c->session->name); server_client_detach(c, msgtype);
return (CMD_RETURN_STOP); return (CMD_RETURN_STOP);
} }

116
cmd-set-hook.c Normal file
View File

@ -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);
}

4
cmd.c
View File

@ -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_server_info_entry;
extern const struct cmd_entry cmd_set_buffer_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_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_option_entry;
extern const struct cmd_entry cmd_set_window_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_buffer_entry;
extern const struct cmd_entry cmd_show_environment_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_messages_entry;
extern const struct cmd_entry cmd_show_options_entry; extern const struct cmd_entry cmd_show_options_entry;
extern const struct cmd_entry cmd_show_window_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_server_info_entry,
&cmd_set_buffer_entry, &cmd_set_buffer_entry,
&cmd_set_environment_entry, &cmd_set_environment_entry,
&cmd_set_hook_entry,
&cmd_set_option_entry, &cmd_set_option_entry,
&cmd_set_window_option_entry, &cmd_set_window_option_entry,
&cmd_show_buffer_entry, &cmd_show_buffer_entry,
&cmd_show_environment_entry, &cmd_show_environment_entry,
&cmd_show_hooks_entry,
&cmd_show_messages_entry, &cmd_show_messages_entry,
&cmd_show_options_entry, &cmd_show_options_entry,
&cmd_show_window_options_entry, &cmd_show_window_options_entry,

139
hooks.c Normal file
View File

@ -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);
}

View File

@ -256,6 +256,19 @@ server_client_free(__unused int fd, __unused short events, void *arg)
free(c); 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. */ /* Check for mouse keys. */
key_code key_code
server_client_check_mouse(struct client *c) server_client_check_mouse(struct client *c)
@ -995,6 +1008,8 @@ server_client_dispatch(struct imsg *imsg, void *arg)
recalculate_sizes(); recalculate_sizes();
server_redraw_client(c); server_redraw_client(c);
} }
if (c->session != NULL)
hooks_run(c->session->hooks, "client-resized", c);
break; break;
case MSG_EXITING: case MSG_EXITING:
if (datalen != 0) if (datalen != 0)

View File

@ -123,7 +123,9 @@ session_create(const char *name, int argc, char **argv, const char *path,
s->environ = environ_create(); s->environ = environ_create();
if (env != NULL) if (env != NULL)
environ_copy(env, s->environ); environ_copy(env, s->environ);
s->options = options_create(global_s_options); s->options = options_create(global_s_options);
s->hooks = hooks_create(global_hooks);
s->tio = NULL; s->tio = NULL;
if (tio != NULL) { if (tio != NULL) {
@ -189,7 +191,9 @@ session_free(__unused int fd, __unused short events, void *arg)
if (s->references == 0) { if (s->references == 0) {
environ_free(s->environ); environ_free(s->environ);
options_free(s->options); options_free(s->options);
hooks_free(s->hooks);
free(s->name); free(s->name);
free(s); free(s);

46
tmux.1
View File

@ -3193,6 +3193,52 @@ is used.
.Fl v .Fl v
shows only the option value, not the name. shows only the option value, not the name.
.El .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 .Sh MOUSE SUPPORT
If the If the
.Ic mouse .Ic mouse

3
tmux.c
View File

@ -38,6 +38,7 @@ struct options *global_options; /* server options */
struct options *global_s_options; /* session options */ struct options *global_s_options; /* session options */
struct options *global_w_options; /* window options */ struct options *global_w_options; /* window options */
struct environ *global_environ; struct environ *global_environ;
struct hooks *global_hooks;
struct timeval start_time; struct timeval start_time;
const char *socket_path; const char *socket_path;
@ -269,6 +270,8 @@ main(int argc, char **argv)
flags |= CLIENT_UTF8; flags |= CLIENT_UTF8;
} }
global_hooks = hooks_create(NULL);
global_environ = environ_create(); global_environ = environ_create();
for (var = environ; *var != NULL; var++) for (var = environ; *var != NULL; var++)
environ_put(global_environ, *var); environ_put(global_environ, *var);

31
tmux.h
View File

@ -691,6 +691,14 @@ struct grid {
struct grid_line *linedata; 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. */ /* Option data structures. */
struct options_entry { struct options_entry {
char *name; char *name;
@ -1011,6 +1019,7 @@ struct session {
struct winlink_stack lastw; struct winlink_stack lastw;
struct winlinks windows; struct winlinks windows;
struct hooks *hooks;
struct options *options; struct options *options;
#define SESSION_UNATTACHED 0x1 /* not attached to any clients */ #define SESSION_UNATTACHED 0x1 /* not attached to any clients */
@ -1427,10 +1436,11 @@ struct options_table_entry {
#define CMD_BUFFER_USAGE "[-b buffer-name]" #define CMD_BUFFER_USAGE "[-b buffer-name]"
/* tmux.c */ /* tmux.c */
extern struct options *global_options; extern struct hooks *global_hooks;
extern struct options *global_s_options; extern struct options *global_options;
extern struct options *global_w_options; extern struct options *global_s_options;
extern struct environ *global_environ; extern struct options *global_w_options;
extern struct environ *global_environ;
extern struct timeval start_time; extern struct timeval start_time;
extern const char *socket_path; extern const char *socket_path;
const char *getshell(void); const char *getshell(void);
@ -1495,6 +1505,18 @@ void format_defaults_pane(struct format_tree *,
void format_defaults_paste_buffer(struct format_tree *, void format_defaults_paste_buffer(struct format_tree *,
struct paste_buffer *); 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 */ /* mode-key.c */
extern const struct mode_key_table mode_key_tables[]; extern const struct mode_key_table mode_key_tables[];
extern struct mode_key_tree mode_key_tree_vi_edit; 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 **); int server_client_open(struct client *, char **);
void server_client_unref(struct client *); void server_client_unref(struct client *);
void server_client_lost(struct client *); void server_client_lost(struct client *);
void server_client_detach(struct client *, enum msgtype);
void server_client_loop(void); void server_client_loop(void);
void server_client_push_stdout(struct client *); void server_client_push_stdout(struct client *);
void server_client_push_stderr(struct client *); void server_client_push_stderr(struct client *);