1
0
mirror of https://github.com/tmate-io/tmate-ssh-server.git synced 2020-11-18 19:53:51 -08:00

Support for multiple key tables to commands to be bound to sequences of

keys. The default key bindings become the "prefix" table and -n the
"root" table. Keys may be bound in new tables with bind -T and
switch-client -T used to specify the table in which the next key should
be looked up. Based on a diff from Keith Amling.
This commit is contained in:
nicm 2015-04-20 15:34:56 +00:00
parent 3497843f02
commit bded743706
9 changed files with 394 additions and 197 deletions

View File

@ -33,8 +33,8 @@ enum cmd_retval cmd_bind_key_mode_table(struct cmd *, struct cmd_q *, int);
const struct cmd_entry cmd_bind_key_entry = { const struct cmd_entry cmd_bind_key_entry = {
"bind-key", "bind", "bind-key", "bind",
"cnrt:", 1, -1, "cnrt:T:", 1, -1,
"[-cnr] [-t mode-table] key command [arguments]", "[-cnr] [-t mode-table] [-T key-table] key command [arguments]",
0, 0,
cmd_bind_key_exec cmd_bind_key_exec
}; };
@ -46,6 +46,7 @@ cmd_bind_key_exec(struct cmd *self, struct cmd_q *cmdq)
char *cause; char *cause;
struct cmd_list *cmdlist; struct cmd_list *cmdlist;
int key; int key;
const char *tablename;
if (args_has(args, 't')) { if (args_has(args, 't')) {
if (args->argc != 2 && args->argc != 3) { if (args->argc != 2 && args->argc != 3) {
@ -68,6 +69,13 @@ cmd_bind_key_exec(struct cmd *self, struct cmd_q *cmdq)
if (args_has(args, 't')) if (args_has(args, 't'))
return (cmd_bind_key_mode_table(self, cmdq, key)); return (cmd_bind_key_mode_table(self, cmdq, key));
if (args_has(args, 'T'))
tablename = args_get(args, 'T');
else if (args_has(args, 'n'))
tablename = "root";
else
tablename = "prefix";
cmdlist = cmd_list_parse(args->argc - 1, args->argv + 1, NULL, 0, cmdlist = cmd_list_parse(args->argc - 1, args->argv + 1, NULL, 0,
&cause); &cause);
if (cmdlist == NULL) { if (cmdlist == NULL) {
@ -76,9 +84,7 @@ cmd_bind_key_exec(struct cmd *self, struct cmd_q *cmdq)
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
} }
if (!args_has(args, 'n')) key_bindings_add(tablename, key, args_has(args, 'r'), cmdlist);
key |= KEYC_PREFIX;
key_bindings_add(key, args_has(args, 'r'), cmdlist);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@ -33,8 +33,8 @@ enum cmd_retval cmd_list_keys_commands(struct cmd *, struct cmd_q *);
const struct cmd_entry cmd_list_keys_entry = { const struct cmd_entry cmd_list_keys_entry = {
"list-keys", "lsk", "list-keys", "lsk",
"t:", 0, 0, "t:T:", 0, 0,
"[-t key-table]", "[-t mode-table] [-T key-table]",
0, 0,
cmd_list_keys_exec cmd_list_keys_exec
}; };
@ -51,11 +51,12 @@ enum cmd_retval
cmd_list_keys_exec(struct cmd *self, struct cmd_q *cmdq) cmd_list_keys_exec(struct cmd *self, struct cmd_q *cmdq)
{ {
struct args *args = self->args; struct args *args = self->args;
struct key_table *table;
struct key_binding *bd; struct key_binding *bd;
const char *key; const char *key, *tablename, *r;
char tmp[BUFSIZ], flags[8]; char tmp[BUFSIZ];
size_t used; size_t used;
int width, keywidth; int repeat, width, tablewidth, keywidth;
if (self->entry == &cmd_list_commands_entry) if (self->entry == &cmd_list_commands_entry)
return (cmd_list_keys_commands(self, cmdq)); return (cmd_list_keys_commands(self, cmdq));
@ -63,47 +64,58 @@ cmd_list_keys_exec(struct cmd *self, struct cmd_q *cmdq)
if (args_has(args, 't')) if (args_has(args, 't'))
return (cmd_list_keys_table(self, cmdq)); return (cmd_list_keys_table(self, cmdq));
width = 0; tablename = args_get(args, 'T');
if (tablename != NULL && key_bindings_get_table(tablename, 0) == NULL) {
RB_FOREACH(bd, key_bindings, &key_bindings) { cmdq_error(cmdq, "table %s doesn't exist", tablename);
key = key_string_lookup_key(bd->key & ~KEYC_PREFIX); return (CMD_RETURN_ERROR);
if (key == NULL)
continue;
keywidth = strlen(key);
if (!(bd->key & KEYC_PREFIX)) {
if (bd->can_repeat)
keywidth += 4;
else
keywidth += 3;
} else if (bd->can_repeat)
keywidth += 3;
if (keywidth > width)
width = keywidth;
} }
RB_FOREACH(bd, key_bindings, &key_bindings) { repeat = 0;
key = key_string_lookup_key(bd->key & ~KEYC_PREFIX); tablewidth = keywidth = 0;
RB_FOREACH(table, key_tables, &key_tables) {
if (tablename != NULL && strcmp(table->name, tablename) != 0)
continue;
RB_FOREACH(bd, key_bindings, &table->key_bindings) {
key = key_string_lookup_key(bd->key);
if (key == NULL) if (key == NULL)
continue; continue;
*flags = '\0';
if (!(bd->key & KEYC_PREFIX)) {
if (bd->can_repeat) if (bd->can_repeat)
xsnprintf(flags, sizeof flags, "-rn "); repeat = 1;
else
xsnprintf(flags, sizeof flags, "-n ");
} else if (bd->can_repeat)
xsnprintf(flags, sizeof flags, "-r ");
used = xsnprintf(tmp, sizeof tmp, "%s%*s ", width = strlen(table->name);
flags, (int) (width - strlen(flags)), key); if (width > tablewidth)
if (used >= sizeof tmp) tablewidth =width;
width = strlen(key);
if (width > keywidth)
keywidth = width;
}
}
RB_FOREACH(table, key_tables, &key_tables) {
if (tablename != NULL && strcmp(table->name, tablename) != 0)
continue;
RB_FOREACH(bd, key_bindings, &table->key_bindings) {
key = key_string_lookup_key(bd->key);
if (key == NULL)
continue; continue;
cmd_list_print(bd->cmdlist, tmp + used, (sizeof tmp) - used); if (!repeat)
r = "";
else if (bd->can_repeat)
r = "-r ";
else
r = " ";
used = xsnprintf(tmp, sizeof tmp, "%s-T %-*s %-*s ", r,
(int)tablewidth, table->name, (int)keywidth, key);
if (used < sizeof tmp) {
cmd_list_print(bd->cmdlist, tmp + used,
(sizeof tmp) - used);
}
cmdq_print(cmdq, "bind-key %s", tmp); cmdq_print(cmdq, "bind-key %s", tmp);
} }
}
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@ -31,8 +31,8 @@ enum cmd_retval cmd_switch_client_exec(struct cmd *, struct cmd_q *);
const struct cmd_entry cmd_switch_client_entry = { const struct cmd_entry cmd_switch_client_entry = {
"switch-client", "switchc", "switch-client", "switchc",
"lc:npt:r", 0, 0, "lc:npt:rT:", 0, 0,
"[-lnpr] [-c target-client] [-t target-session]", "[-lnpr] [-c target-client] [-t target-session] [-T key-table]",
CMD_READONLY, CMD_READONLY,
cmd_switch_client_exec cmd_switch_client_exec
}; };
@ -46,7 +46,8 @@ cmd_switch_client_exec(struct cmd *self, struct cmd_q *cmdq)
struct winlink *wl = NULL; struct winlink *wl = NULL;
struct window *w = NULL; struct window *w = NULL;
struct window_pane *wp = NULL; struct window_pane *wp = NULL;
const char *tflag; const char *tflag, *tablename;
struct key_table *table;
if ((c = cmd_find_client(cmdq, args_get(args, 'c'), 0)) == NULL) if ((c = cmd_find_client(cmdq, args_get(args, 'c'), 0)) == NULL)
return (CMD_RETURN_ERROR); return (CMD_RETURN_ERROR);
@ -58,6 +59,18 @@ cmd_switch_client_exec(struct cmd *self, struct cmd_q *cmdq)
c->flags |= CLIENT_READONLY; c->flags |= CLIENT_READONLY;
} }
tablename = args_get(args, 'T');
if (tablename != NULL) {
table = key_bindings_get_table(tablename, 0);
if (table == NULL) {
cmdq_error(cmdq, "table %s doesn't exist", tablename);
return (CMD_RETURN_ERROR);
}
table->references++;
key_bindings_unref_table(c->keytable);
c->keytable = table;
}
tflag = args_get(args, 't'); tflag = args_get(args, 't');
if (args_has(args, 'n')) { if (args_has(args, 'n')) {
if ((s = session_next_session(c->session)) == NULL) { if ((s = session_next_session(c->session)) == NULL) {

View File

@ -31,8 +31,8 @@ enum cmd_retval cmd_unbind_key_mode_table(struct cmd *, struct cmd_q *, int);
const struct cmd_entry cmd_unbind_key_entry = { const struct cmd_entry cmd_unbind_key_entry = {
"unbind-key", "unbind", "unbind-key", "unbind",
"acnt:", 0, 1, "acnt:T:", 0, 1,
"[-acn] [-t mode-table] key", "[-acn] [-t mode-table] [-T key-table] key",
0, 0,
cmd_unbind_key_exec cmd_unbind_key_exec
}; };
@ -41,8 +41,8 @@ enum cmd_retval
cmd_unbind_key_exec(struct cmd *self, struct cmd_q *cmdq) cmd_unbind_key_exec(struct cmd *self, struct cmd_q *cmdq)
{ {
struct args *args = self->args; struct args *args = self->args;
struct key_binding *bd;
int key; int key;
const char *tablename;
if (!args_has(args, 'a')) { if (!args_has(args, 'a')) {
if (args->argc != 1) { if (args->argc != 1) {
@ -66,16 +66,31 @@ cmd_unbind_key_exec(struct cmd *self, struct cmd_q *cmdq)
return (cmd_unbind_key_mode_table(self, cmdq, key)); return (cmd_unbind_key_mode_table(self, cmdq, key));
if (key == KEYC_NONE) { if (key == KEYC_NONE) {
while (!RB_EMPTY(&key_bindings)) { tablename = args_get(args, 'T');
bd = RB_ROOT(&key_bindings); if (tablename == NULL) {
key_bindings_remove(bd->key); key_bindings_remove_table("root");
key_bindings_remove_table("prefix");
return (CMD_RETURN_NORMAL);
} }
if (key_bindings_get_table(tablename, 0) == NULL) {
cmdq_error(cmdq, "table %s doesn't exist", tablename);
return (CMD_RETURN_ERROR);
}
key_bindings_remove_table(tablename);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }
if (!args_has(args, 'n')) if (args_has(args, 'T')) {
key |= KEYC_PREFIX; tablename = args_get(args, 'T');
key_bindings_remove(key); if (key_bindings_get_table(tablename, 0) == NULL) {
cmdq_error(cmdq, "table %s doesn't exist", tablename);
return (CMD_RETURN_ERROR);
}
} else if (args_has(args, 'n'))
tablename = "root";
else
tablename = "prefix";
key_bindings_remove(tablename, key);
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
} }

View File

@ -545,7 +545,11 @@ format_defaults_client(struct format_tree *ft, struct client *c)
format_add(ft, "client_activity", "%lld", (long long) t); format_add(ft, "client_activity", "%lld", (long long) t);
format_add(ft, "client_activity_string", "%s", format_time_string(t)); format_add(ft, "client_activity_string", "%s", format_time_string(t));
format_add(ft, "client_prefix", "%d", !!(c->flags & CLIENT_PREFIX)); if (strcmp(c->keytable->name, "root") == 0)
format_add(ft, "client_prefix", "%d", 0);
else
format_add(ft, "client_prefix", "%d", 1);
format_add(ft, "client_key_table", "%s", c->keytable->name);
if (c->tty.flags & TTY_UTF8) if (c->tty.flags & TTY_UTF8)
format_add(ft, "client_utf8", "%d", 1); format_add(ft, "client_utf8", "%d", 1);

View File

@ -25,60 +25,120 @@
#include "tmux.h" #include "tmux.h"
RB_GENERATE(key_bindings, key_binding, entry, key_bindings_cmp); RB_GENERATE(key_bindings, key_binding, entry, key_bindings_cmp);
RB_GENERATE(key_tables, key_table, entry, key_table_cmp);
struct key_tables key_tables = RB_INITIALIZER(&key_tables);
struct key_bindings key_bindings; int
key_table_cmp(struct key_table *e1, struct key_table *e2)
{
return (strcmp(e1->name, e2->name));
}
int int
key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2) key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2)
{ {
int key1, key2; return (bd1->key - bd2->key);
key1 = bd1->key & ~KEYC_PREFIX;
key2 = bd2->key & ~KEYC_PREFIX;
if (key1 != key2)
return (key1 - key2);
if (bd1->key & KEYC_PREFIX && !(bd2->key & KEYC_PREFIX))
return (-1);
if (bd2->key & KEYC_PREFIX && !(bd1->key & KEYC_PREFIX))
return (1);
return (0);
} }
struct key_binding * struct key_table *
key_bindings_lookup(int key) key_bindings_get_table(const char *name, int create)
{ {
struct key_binding bd; struct key_table table_find, *table;
bd.key = key; table_find.name = name;
return (RB_FIND(key_bindings, &key_bindings, &bd)); table = RB_FIND(key_tables, &key_tables, &table_find);
if (table != NULL || !create)
return (table);
table = xmalloc(sizeof *table);
table->name = xstrdup(name);
RB_INIT(&table->key_bindings);
table->references = 1; /* one reference in key_tables */
RB_INSERT(key_tables, &key_tables, table);
return (table);
} }
void void
key_bindings_add(int key, int can_repeat, struct cmd_list *cmdlist) key_bindings_unref_table(struct key_table *table)
{ {
struct key_binding *bd; struct key_binding *bd;
key_bindings_remove(key); if (--table->references != 0)
return;
while (!RB_EMPTY(&table->key_bindings)) {
bd = RB_ROOT(&table->key_bindings);
RB_REMOVE(key_bindings, &table->key_bindings, bd);
cmd_list_free(bd->cmdlist);
free(bd);
}
free((void *)table->name);
free(table);
}
void
key_bindings_add(const char *name, int key, int can_repeat,
struct cmd_list *cmdlist)
{
struct key_table *table;
struct key_binding bd_find, *bd;
table = key_bindings_get_table(name, 1);
bd_find.key = key;
bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find);
if (bd != NULL) {
RB_REMOVE(key_bindings, &table->key_bindings, bd);
cmd_list_free(bd->cmdlist);
free(bd);
}
bd = xmalloc(sizeof *bd); bd = xmalloc(sizeof *bd);
bd->key = key; bd->key = key;
RB_INSERT(key_bindings, &key_bindings, bd); RB_INSERT(key_bindings, &table->key_bindings, bd);
bd->can_repeat = can_repeat; bd->can_repeat = can_repeat;
bd->cmdlist = cmdlist; bd->cmdlist = cmdlist;
} }
void void
key_bindings_remove(int key) key_bindings_remove(const char *name, int key)
{ {
struct key_binding *bd; struct key_table *table;
struct key_binding bd_find, *bd;
if ((bd = key_bindings_lookup(key)) == NULL) table = key_bindings_get_table(name, 0);
if (table == NULL)
return; return;
RB_REMOVE(key_bindings, &key_bindings, bd);
bd_find.key = key;
bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find);
if (bd == NULL)
return;
RB_REMOVE(key_bindings, &table->key_bindings, bd);
cmd_list_free(bd->cmdlist); cmd_list_free(bd->cmdlist);
free(bd); free(bd);
if (RB_EMPTY(&table->key_bindings)) {
RB_REMOVE(key_tables, &key_tables, table);
key_bindings_unref_table(table);
}
}
void
key_bindings_remove_table(const char *name)
{
struct key_table *table;
table = key_bindings_get_table(name, 0);
if (table != NULL) {
RB_REMOVE(key_tables, &key_tables, table);
key_bindings_unref_table(table);
}
} }
void void
@ -169,8 +229,6 @@ key_bindings_init(void)
int error; int error;
struct cmd_q *cmdq; struct cmd_q *cmdq;
RB_INIT(&key_bindings);
cmdq = cmdq_new(NULL); cmdq = cmdq_new(NULL);
for (i = 0; i < nitems(defaults); i++) { for (i = 0; i < nitems(defaults); i++) {
error = cmd_string_parse(defaults[i], &cmdlist, error = cmd_string_parse(defaults[i], &cmdlist,

View File

@ -30,6 +30,7 @@
#include "tmux.h" #include "tmux.h"
void server_client_key_table(struct client *, const char *);
void server_client_check_focus(struct window_pane *); void server_client_check_focus(struct window_pane *);
void server_client_check_resize(struct window_pane *); void server_client_check_resize(struct window_pane *);
int server_client_check_mouse(struct client *); int server_client_check_mouse(struct client *);
@ -45,6 +46,15 @@ void server_client_msg_command(struct client *, struct imsg *);
void server_client_msg_identify(struct client *, struct imsg *); void server_client_msg_identify(struct client *, struct imsg *);
void server_client_msg_shell(struct client *); void server_client_msg_shell(struct client *);
/* Set client key table. */
void
server_client_key_table(struct client *c, const char *name)
{
key_bindings_unref_table(c->keytable);
c->keytable = key_bindings_get_table(name, 1);
c->keytable->references++;
}
/* Create a new client. */ /* Create a new client. */
void void
server_client_create(int fd) server_client_create(int fd)
@ -93,6 +103,9 @@ server_client_create(int fd)
c->flags |= CLIENT_FOCUSED; c->flags |= CLIENT_FOCUSED;
c->keytable = key_bindings_get_table("root", 1);
c->keytable->references++;
evtimer_set(&c->repeat_timer, server_client_repeat_timer, c); evtimer_set(&c->repeat_timer, server_client_repeat_timer, c);
for (i = 0; i < ARRAY_LENGTH(&clients); i++) { for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
@ -164,6 +177,8 @@ server_client_lost(struct client *c)
evtimer_del(&c->repeat_timer); evtimer_del(&c->repeat_timer);
key_bindings_unref_table(c->keytable);
if (event_initialized(&c->identify_timer)) if (event_initialized(&c->identify_timer))
evtimer_del(&c->identify_timer); evtimer_del(&c->identify_timer);
@ -527,16 +542,19 @@ void
server_client_handle_key(struct client *c, int key) server_client_handle_key(struct client *c, int key)
{ {
struct mouse_event *m = &c->tty.mouse; struct mouse_event *m = &c->tty.mouse;
struct session *s; struct session *s = c->session;
struct window *w; struct window *w;
struct window_pane *wp; struct window_pane *wp;
struct timeval tv; struct timeval tv;
struct key_binding *bd; struct key_table *table = c->keytable;
int xtimeout, isprefix, ispaste; struct key_binding bd_find, *bd;
int xtimeout;
/* Check the client is good to accept input. */ /* Check the client is good to accept input. */
if ((c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0) if (s == NULL || (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0)
return; return;
w = s->curw->window;
wp = w->active;
/* No session, do nothing. */ /* No session, do nothing. */
if (c->session == NULL) if (c->session == NULL)
@ -552,7 +570,7 @@ server_client_handle_key(struct client *c, int key)
sizeof s->last_activity_time); sizeof s->last_activity_time);
memcpy(&s->activity_time, &c->activity_time, sizeof s->activity_time); memcpy(&s->activity_time, &c->activity_time, sizeof s->activity_time);
/* Special case: number keys jump to pane in identify mode. */ /* Number keys jump to pane in identify mode. */
if (c->flags & CLIENT_IDENTIFY && key >= '0' && key <= '9') { if (c->flags & CLIENT_IDENTIFY && key >= '0' && key <= '9') {
if (c->flags & CLIENT_READONLY) if (c->flags & CLIENT_READONLY)
return; return;
@ -593,74 +611,88 @@ server_client_handle_key(struct client *c, int key)
} else } else
m->valid = 0; m->valid = 0;
/* Is this a prefix key? */ /* Treat everything as a regular key when pasting is detected. */
if (key == options_get_number(&s->options, "prefix")) if (server_client_assume_paste(s)) {
isprefix = 1;
else if (key == options_get_number(&s->options, "prefix2"))
isprefix = 1;
else
isprefix = 0;
/* Treat prefix as a regular key when pasting is detected. */
ispaste = server_client_assume_paste(s);
if (ispaste)
isprefix = 0;
/* No previous prefix key. */
if (!(c->flags & CLIENT_PREFIX)) {
if (isprefix) {
c->flags |= CLIENT_PREFIX;
server_status_client(c);
return;
}
/* Try as a non-prefix key binding. */
if (ispaste || (bd = key_bindings_lookup(key)) == NULL) {
if (!(c->flags & CLIENT_READONLY)) if (!(c->flags & CLIENT_READONLY))
window_pane_key(wp, c, s, key, m); window_pane_key(wp, c, s, key, m);
} else
key_bindings_dispatch(bd, c, m);
return; return;
} }
/* Prefix key already pressed. Reset prefix and lookup key. */ retry:
c->flags &= ~CLIENT_PREFIX; /* Try to see if there is a key binding in the current table. */
bd_find.key = key;
bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find);
if (bd != NULL) {
/*
* Key was matched in this table. If currently repeating but a
* non-repeating binding was found, stop repeating and try
* again in the root table.
*/
if ((c->flags & CLIENT_REPEAT) && !bd->can_repeat) {
server_client_key_table(c, "root");
c->flags &= ~CLIENT_REPEAT;
server_status_client(c); server_status_client(c);
if ((bd = key_bindings_lookup(key | KEYC_PREFIX)) == NULL) { goto retry;
/* If repeating, treat this as a key, else ignore. */
if (c->flags & CLIENT_REPEAT) {
c->flags &= ~CLIENT_REPEAT;
if (isprefix)
c->flags |= CLIENT_PREFIX;
else if (!(c->flags & CLIENT_READONLY))
window_pane_key(wp, c, s, key, m);
}
return;
} }
/* If already repeating, but this key can't repeat, skip it. */ /*
if (c->flags & CLIENT_REPEAT && !bd->can_repeat) { * Take a reference to this table to make sure the key binding
c->flags &= ~CLIENT_REPEAT; * doesn't disappear.
if (isprefix) */
c->flags |= CLIENT_PREFIX; table->references++;
else if (!(c->flags & CLIENT_READONLY))
window_pane_key(wp, c, s, key, m);
return;
}
/* If this key can repeat, reset the repeat flags and timer. */ /*
* If this is a repeating key, start the timer. Otherwise reset
* the client back to the root table.
*/
xtimeout = options_get_number(&s->options, "repeat-time"); xtimeout = options_get_number(&s->options, "repeat-time");
if (xtimeout != 0 && bd->can_repeat) { if (xtimeout != 0 && bd->can_repeat) {
c->flags |= CLIENT_PREFIX|CLIENT_REPEAT; c->flags |= CLIENT_REPEAT;
tv.tv_sec = xtimeout / 1000; tv.tv_sec = xtimeout / 1000;
tv.tv_usec = (xtimeout % 1000) * 1000L; tv.tv_usec = (xtimeout % 1000) * 1000L;
evtimer_del(&c->repeat_timer); evtimer_del(&c->repeat_timer);
evtimer_add(&c->repeat_timer, &tv); evtimer_add(&c->repeat_timer, &tv);
} else {
c->flags &= ~CLIENT_REPEAT;
server_client_key_table(c, "root");
}
server_status_client(c);
/* Dispatch the key binding. */
key_bindings_dispatch(bd, c, m);
key_bindings_unref_table(table);
return;
} }
/* Dispatch the command. */ /*
key_bindings_dispatch(bd, c, m); * No match in this table. If repeating, switch the client back to the
* root table and try again.
*/
if (c->flags & CLIENT_REPEAT) {
server_client_key_table(c, "root");
c->flags &= ~CLIENT_REPEAT;
server_status_client(c);
goto retry;
}
/* If no match and we're not in the root table, that's it. */
if (strcmp(c->keytable->name, "root") != 0) {
server_client_key_table(c, "root");
server_status_client(c);
return;
}
/*
* No match, but in the root table. Prefix switches to the prefix table
* and everything else is passed through.
*/
if (key == options_get_number(&s->options, "prefix") ||
key == options_get_number(&s->options, "prefix2")) {
server_client_key_table(c, "prefix");
server_status_client(c);
} else if (!(c->flags & CLIENT_READONLY))
window_pane_key(wp, c, s, key, m);
} }
/* Client functions that need to happen every loop. */ /* Client functions that need to happen every loop. */
@ -848,9 +880,9 @@ server_client_repeat_timer(unused int fd, unused short events, void *data)
struct client *c = data; struct client *c = data;
if (c->flags & CLIENT_REPEAT) { if (c->flags & CLIENT_REPEAT) {
if (c->flags & CLIENT_PREFIX) server_client_key_table(c, "root");
c->flags &= ~CLIENT_REPEAT;
server_status_client(c); server_status_client(c);
c->flags &= ~(CLIENT_PREFIX|CLIENT_REPEAT);
} }
} }

106
tmux.1
View File

@ -838,6 +838,7 @@ Suspend a client by sending
.Op Fl lnpr .Op Fl lnpr
.Op Fl c Ar target-client .Op Fl c Ar target-client
.Op Fl t Ar target-session .Op Fl t Ar target-session
.Op Fl T Ar key-table
.Xc .Xc
.D1 (alias: Ic switchc ) .D1 (alias: Ic switchc )
Switch the current session for client Switch the current session for client
@ -855,6 +856,22 @@ respectively.
toggles whether a client is read-only (see the toggles whether a client is read-only (see the
.Ic attach-session .Ic attach-session
command). command).
.Pp
.Fl T
sets the client's key table; the next key from the client will be interpreted from
.Ar key-table .
This may be used to configure multiple prefix keys, or to bind commands to
sequences of keys.
For example, to make typing
.Ql abc
run the
.Ic list-keys
command:
.Bd -literal -offset indent
bind-key -Ttable2 c list-keys
bind-key -Ttable1 b switch-client -Ttable2
bind-key -Troot a switch-client -Ttable1
.Ed
.El .El
.Sh WINDOWS AND PANES .Sh WINDOWS AND PANES
A A
@ -1931,6 +1948,7 @@ Commands related to key bindings are as follows:
.It Xo Ic bind-key .It Xo Ic bind-key
.Op Fl cnr .Op Fl cnr
.Op Fl t Ar mode-table .Op Fl t Ar mode-table
.Op Fl T Ar key-table
.Ar key Ar command Op Ar arguments .Ar key Ar command Op Ar arguments
.Xc .Xc
.D1 (alias: Ic bind ) .D1 (alias: Ic bind )
@ -1938,16 +1956,40 @@ Bind key
.Ar key .Ar key
to to
.Ar command . .Ar command .
By default (without Keys are bound in a key table.
.Fl t ) By default (without -T), the key is bound in
the primary key bindings are modified (those normally activated with the prefix the
key); in this case, if .Em prefix
.Fl n key table.
is specified, it is not necessary to use the prefix key, This table is used for keys pressed after the prefix key (for example,
.Ar command by default
.Ql c
is bound to is bound to
.Ar key .Ic new-window
alone. in the
.Em prefix
table, so
.Ql C-b c
creates a new window).
The
.Em root
table is used for keys pressed without the prefix key: binding
.Ql c
to
.Ic new-window
in the
.Em root
table (not recommended) means a plain
.Ql c
will create a new window.
.Fl n
is an alias
for
.Fl T Ar root .
Keys may also be bound in custom key tables and the
.Ic switch-client
.Fl T
command used to switch to them from a key binding.
The The
.Fl r .Fl r
flag indicates this key may repeat, see the flag indicates this key may repeat, see the
@ -1962,22 +2004,33 @@ is bound in
.Ar mode-table : .Ar mode-table :
the binding for command mode with the binding for command mode with
.Fl c .Fl c
or for normal mode without. or for normal mode without. See the
.Sx WINDOWS AND PANES
section and the
.Ic list-keys
command for information on mode key bindings.
.Pp
To view the default bindings and possible commands, see the To view the default bindings and possible commands, see the
.Ic list-keys .Ic list-keys
command. command.
.It Ic list-keys Op Fl t Ar key-table .It Xo Ic list-keys
.Op Fl t Ar mode-table
.Op Fl T Ar key-table
.Xc
.D1 (alias: Ic lsk ) .D1 (alias: Ic lsk )
List all key bindings. List all key bindings.
Without Without
.Fl t .Fl T
the primary key bindings - those executed when preceded by the prefix key - all key tables are printed.
are printed. With
.Fl T
only
.Ar key-table .
.Pp .Pp
With With
.Fl t , .Fl t ,
the key bindings in the key bindings in
.Ar key-table .Ar mode-table
are listed; this may be one of: are listed; this may be one of:
.Em vi-edit , .Em vi-edit ,
.Em emacs-edit , .Em emacs-edit ,
@ -2022,31 +2075,22 @@ the secondary prefix key, to a window as if it was pressed.
.It Xo Ic unbind-key .It Xo Ic unbind-key
.Op Fl acn .Op Fl acn
.Op Fl t Ar mode-table .Op Fl t Ar mode-table
.Op Fl T Ar key-table
.Ar key .Ar key
.Xc .Xc
.D1 (alias: Ic unbind ) .D1 (alias: Ic unbind )
Unbind the command bound to Unbind the command bound to
.Ar key . .Ar key .
Without .Fl c ,
.Fl n ,
.Fl T
and
.Fl t .Fl t
the primary key bindings are modified; in this case, if are the same as for
.Fl n .Ic bind-key .
is specified, the command bound to
.Ar key
without a prefix (if any) is removed.
If If
.Fl a .Fl a
is present, all key bindings are removed. is present, all key bindings are removed.
.Pp
If
.Fl t
is present,
.Ar key
in
.Ar mode-table
is unbound: the binding for command mode with
.Fl c
or for normal mode without.
.El .El
.Sh OPTIONS .Sh OPTIONS
The appearance and behaviour of The appearance and behaviour of

31
tmux.h
View File

@ -89,10 +89,9 @@ extern char **environ;
#define KEYC_ESCAPE 0x2000 #define KEYC_ESCAPE 0x2000
#define KEYC_CTRL 0x4000 #define KEYC_CTRL 0x4000
#define KEYC_SHIFT 0x8000 #define KEYC_SHIFT 0x8000
#define KEYC_PREFIX 0x10000
/* Mask to obtain key w/o modifiers. */ /* Mask to obtain key w/o modifiers. */
#define KEYC_MASK_MOD (KEYC_ESCAPE|KEYC_CTRL|KEYC_SHIFT|KEYC_PREFIX) #define KEYC_MASK_MOD (KEYC_ESCAPE|KEYC_CTRL|KEYC_SHIFT)
#define KEYC_MASK_KEY (~KEYC_MASK_MOD) #define KEYC_MASK_KEY (~KEYC_MASK_MOD)
/* Is this a mouse key? */ /* Is this a mouse key? */
@ -1301,7 +1300,7 @@ struct client {
struct screen status; struct screen status;
#define CLIENT_TERMINAL 0x1 #define CLIENT_TERMINAL 0x1
#define CLIENT_PREFIX 0x2 /* 0x2 unused */
#define CLIENT_EXIT 0x4 #define CLIENT_EXIT 0x4
#define CLIENT_REDRAW 0x8 #define CLIENT_REDRAW 0x8
#define CLIENT_STATUS 0x10 #define CLIENT_STATUS 0x10
@ -1320,6 +1319,7 @@ struct client {
#define CLIENT_256COLOURS 0x20000 #define CLIENT_256COLOURS 0x20000
#define CLIENT_IDENTIFIED 0x40000 #define CLIENT_IDENTIFIED 0x40000
int flags; int flags;
struct key_table *keytable;
struct event identify_timer; struct event identify_timer;
@ -1440,7 +1440,7 @@ struct cmd_entry {
enum cmd_retval (*exec)(struct cmd *, struct cmd_q *); enum cmd_retval (*exec)(struct cmd *, struct cmd_q *);
}; };
/* Key binding. */ /* Key binding and key table. */
struct key_binding { struct key_binding {
int key; int key;
struct cmd_list *cmdlist; struct cmd_list *cmdlist;
@ -1449,6 +1449,15 @@ struct key_binding {
RB_ENTRY(key_binding) entry; RB_ENTRY(key_binding) entry;
}; };
RB_HEAD(key_bindings, key_binding); RB_HEAD(key_bindings, key_binding);
struct key_table {
const char *name;
struct key_bindings key_bindings;
u_int references;
RB_ENTRY(key_table) entry;
};
RB_HEAD(key_tables, key_table);
/* /*
* Option table entries. The option table is the user-visible part of the * Option table entries. The option table is the user-visible part of the
@ -1876,12 +1885,16 @@ void cmd_wait_for_flush(void);
int client_main(int, char **, int); int client_main(int, char **, int);
/* key-bindings.c */ /* key-bindings.c */
extern struct key_bindings key_bindings;
int key_bindings_cmp(struct key_binding *, struct key_binding *);
RB_PROTOTYPE(key_bindings, key_binding, entry, key_bindings_cmp); RB_PROTOTYPE(key_bindings, key_binding, entry, key_bindings_cmp);
struct key_binding *key_bindings_lookup(int); RB_PROTOTYPE(key_tables, key_table, entry, key_table_cmp);
void key_bindings_add(int, int, struct cmd_list *); extern struct key_tables key_tables;
void key_bindings_remove(int); int key_table_cmp(struct key_table *, struct key_table *);
int key_bindings_cmp(struct key_binding *, struct key_binding *);
struct key_table *key_bindings_get_table(const char *, int);
void key_bindings_unref_table(struct key_table *);
void key_bindings_add(const char *, int, int, struct cmd_list *);
void key_bindings_remove(const char *, int);
void key_bindings_remove_table(const char *);
void key_bindings_init(void); void key_bindings_init(void);
void key_bindings_dispatch(struct key_binding *, struct client *, void key_bindings_dispatch(struct key_binding *, struct client *,
struct mouse_event *); struct mouse_event *);