diff --git a/Makefile.am b/Makefile.am index 5caa4983..19220d8e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -135,6 +135,7 @@ dist_tmux_SOURCES = \ cmd-switch-client.c \ cmd-unbind-key.c \ cmd-unlink-window.c \ + cmd-wait-for.c \ cmd.c \ colour.c \ control.c \ diff --git a/client.c b/client.c index 612572d0..91f47650 100644 --- a/client.c +++ b/client.c @@ -270,7 +270,7 @@ client_main(int argc, char **argv, int flags) if (msg == MSG_COMMAND) { /* Fill in command line arguments. */ cmddata.pid = environ_pid; - cmddata.idx = environ_idx; + cmddata.session_id = environ_session_id; /* Prepare command for server. */ cmddata.argc = argc; diff --git a/cmd-if-shell.c b/cmd-if-shell.c index b921f418..b22e3269 100644 --- a/cmd-if-shell.c +++ b/cmd-if-shell.c @@ -59,19 +59,21 @@ cmd_if_shell_exec(struct cmd *self, struct cmd_q *cmdq) struct args *args = self->args; struct cmd_if_shell_data *cdata; char *shellcmd; - struct session *s; - struct winlink *wl; - struct window_pane *wp; + struct session *s = NULL; + struct winlink *wl = NULL; + struct window_pane *wp = NULL; struct format_tree *ft; - wl = cmd_find_pane(cmdq, args_get(args, 't'), &s, &wp); - if (wl == NULL) - return (CMD_RETURN_ERROR); + if (args_has(args, 't')) + wl = cmd_find_pane(cmdq, args_get(args, 't'), &s, &wp); ft = format_create(); - format_session(ft, s); - format_winlink(ft, s, wl); - format_window_pane(ft, wp); + if (s != NULL) + format_session(ft, s); + if (s != NULL && wl != NULL) + format_winlink(ft, s, wl); + if (wp != NULL) + format_window_pane(ft, wp); shellcmd = format_expand(ft, args->argv[0]); format_free(ft); diff --git a/cmd-queue.c b/cmd-queue.c index 4b00fceb..17992a37 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -151,6 +151,22 @@ cmdq_error(struct cmd_q *cmdq, const char *fmt, ...) free(msg); } +/* Print a guard line. */ +void +cmdq_guard(struct cmd_q *cmdq, const char *guard) +{ + struct client *c = cmdq->client; + + if (c == NULL || c->session == NULL) + return; + if (!(c->flags & CLIENT_CONTROL)) + return; + + evbuffer_add_printf(c->stdout_data, "%%%s %ld %u\n", guard, + (long) cmdq->time, cmdq->number); + server_push_stdout(c); +} + /* Add command list to queue and begin processing if needed. */ void cmdq_run(struct cmd_q *cmdq, struct cmd_list *cmdlist) @@ -179,16 +195,11 @@ cmdq_append(struct cmd_q *cmdq, struct cmd_list *cmdlist) int cmdq_continue(struct cmd_q *cmdq) { - struct client *c = cmdq->client; struct cmd_q_item *next; enum cmd_retval retval; - int guards, empty; + int empty; char s[1024]; - guards = 0; - if (c != NULL && c->session != NULL) - guards = c->flags & CLIENT_CONTROL; - notify_disable(); empty = TAILQ_EMPTY(&cmdq->queue); @@ -209,15 +220,15 @@ cmdq_continue(struct cmd_q *cmdq) log_debug("cmdq %p: %s (client %d)", cmdq, s, cmdq->client != NULL ? cmdq->client->ibuf.fd : -1); - if (guards) - cmdq_print(cmdq, "%%begin"); + cmdq->time = time(NULL); + cmdq->number++; + + cmdq_guard(cmdq, "begin"); retval = cmdq->cmd->entry->exec(cmdq->cmd, cmdq); - if (guards) { - if (retval == CMD_RETURN_ERROR) - cmdq_print(cmdq, "%%error"); - else - cmdq_print(cmdq, "%%end"); - } + if (retval == CMD_RETURN_ERROR) + cmdq_guard(cmdq, "error"); + else + cmdq_guard(cmdq, "end"); if (retval == CMD_RETURN_ERROR) break; diff --git a/cmd-run-shell.c b/cmd-run-shell.c index aaa310b9..b163542f 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -49,16 +49,17 @@ struct cmd_run_shell_data { char *cmd; struct cmd_q *cmdq; int bflag; - u_int wp_id; + int wp_id; }; void cmd_run_shell_print(struct job *job, const char *msg) { struct cmd_run_shell_data *cdata = job->data; - struct window_pane *wp; + struct window_pane *wp = NULL; - wp = window_pane_find_by_id(cdata->wp_id); + if (cdata->wp_id != -1) + wp = window_pane_find_by_id(cdata->wp_id); if (wp == NULL) { cmdq_print(cdata->cmdq, "%s", msg); return; @@ -76,26 +77,28 @@ cmd_run_shell_exec(struct cmd *self, struct cmd_q *cmdq) struct args *args = self->args; struct cmd_run_shell_data *cdata; char *shellcmd; - struct session *s; - struct winlink *wl; - struct window_pane *wp; + struct session *s = NULL; + struct winlink *wl = NULL; + struct window_pane *wp = NULL; struct format_tree *ft; - wl = cmd_find_pane(cmdq, args_get(args, 't'), &s, &wp); - if (wl == NULL) - return (CMD_RETURN_ERROR); + if (args_has(args, 't')) + wl = cmd_find_pane(cmdq, args_get(args, 't'), &s, &wp); ft = format_create(); - format_session(ft, s); - format_winlink(ft, s, wl); - format_window_pane(ft, wp); + if (s != NULL) + format_session(ft, s); + if (s != NULL && wl != NULL) + format_winlink(ft, s, wl); + if (wp != NULL) + format_window_pane(ft, wp); shellcmd = format_expand(ft, args->argv[0]); format_free(ft); cdata = xmalloc(sizeof *cdata); cdata->cmd = shellcmd; cdata->bflag = args_has(args, 'b'); - cdata->wp_id = wp->id; + cdata->wp_id = wp != NULL ? (int) wp->id : -1; cdata->cmdq = cmdq; cmdq->references++; diff --git a/cmd-select-pane.c b/cmd-select-pane.c index d24d7b3d..b8a12671 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -80,6 +80,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmd_q *cmdq) return (CMD_RETURN_ERROR); } + server_unzoom_window(wl->window); window_set_active_pane(wl->window, wl->window->last); server_status_window(wl->window); server_redraw_window_borders(wl->window); diff --git a/cmd-server-info.c b/cmd-server-info.c index b044649c..8eba172a 100644 --- a/cmd-server-info.c +++ b/cmd-server-info.c @@ -102,7 +102,7 @@ cmd_server_info_exec(unused struct cmd *self, struct cmd_q *cmdq) *strchr(tim, '\n') = '\0'; cmdq_print(cmdq, "%2u: %s: %u windows (created %s) [%ux%u] " - "[flags=0x%x]", s->idx, s->name, + "[flags=0x%x]", s->id, s->name, winlink_count(&s->windows), tim, s->sx, s->sy, s->flags); RB_FOREACH(wl, winlinks, &s->windows) { w = wl->window; diff --git a/cmd-wait-for.c b/cmd-wait-for.c new file mode 100644 index 00000000..d40ba49e --- /dev/null +++ b/cmd-wait-for.c @@ -0,0 +1,197 @@ +/* $Id$ */ + +/* + * Copyright (c) 2013 Nicholas Marriott + * Copyright (c) 2013 Thiago de Arruda + * + * 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" + +/* + * Block or wake a client on a named wait channel. + */ + +enum cmd_retval cmd_wait_for_exec(struct cmd *, struct cmd_q *); + +const struct cmd_entry cmd_wait_for_entry = { + "wait-for", "wait", + "LSU", 1, 1, + "[-LSU] channel", + 0, + NULL, + NULL, + cmd_wait_for_exec +}; + +struct wait_channel { + const char *name; + int locked; + + TAILQ_HEAD(, cmd_q) waiters; + TAILQ_HEAD(, cmd_q) lockers; + + RB_ENTRY(wait_channel) entry; +}; +RB_HEAD(wait_channels, wait_channel); +struct wait_channels wait_channels = RB_INITIALIZER(wait_channels); + +int wait_channel_cmp(struct wait_channel *, struct wait_channel *); +RB_PROTOTYPE(wait_channels, wait_channel, entry, wait_channel_cmp); +RB_GENERATE(wait_channels, wait_channel, entry, wait_channel_cmp); + +int +wait_channel_cmp(struct wait_channel *wc1, struct wait_channel *wc2) +{ + return (strcmp(wc1->name, wc2->name)); +} + +enum cmd_retval cmd_wait_for_signal(struct cmd_q *, const char *, + struct wait_channel *); +enum cmd_retval cmd_wait_for_wait(struct cmd_q *, const char *, + struct wait_channel *); +enum cmd_retval cmd_wait_for_lock(struct cmd_q *, const char *, + struct wait_channel *); +enum cmd_retval cmd_wait_for_unlock(struct cmd_q *, const char *, + struct wait_channel *); + +enum cmd_retval +cmd_wait_for_exec(struct cmd *self, struct cmd_q *cmdq) +{ + struct args *args = self->args; + const char *name = args->argv[0]; + struct wait_channel *wc, wc0; + + wc0.name = name; + wc = RB_FIND(wait_channels, &wait_channels, &wc0); + + if (args_has(args, 'S')) + return (cmd_wait_for_signal(cmdq, name, wc)); + if (args_has(args, 'L')) + return (cmd_wait_for_lock(cmdq, name, wc)); + if (args_has(args, 'U')) + return (cmd_wait_for_unlock(cmdq, name, wc)); + return (cmd_wait_for_wait(cmdq, name, wc)); +} + +enum cmd_retval +cmd_wait_for_signal(struct cmd_q *cmdq, const char *name, + struct wait_channel *wc) +{ + struct cmd_q *wq, *wq1; + + if (wc == NULL || TAILQ_EMPTY(&wc->waiters)) { + cmdq_error(cmdq, "no waiting clients on %s", name); + return (CMD_RETURN_ERROR); + } + + TAILQ_FOREACH_SAFE(wq, &wc->waiters, waitentry, wq1) { + TAILQ_REMOVE(&wc->waiters, wq, waitentry); + if (!cmdq_free(wq)) + cmdq_continue(wq); + } + + if (!wc->locked) { + RB_REMOVE(wait_channels, &wait_channels, wc); + free((void*) wc->name); + free(wc); + } + + return (CMD_RETURN_NORMAL); +} + +enum cmd_retval +cmd_wait_for_wait(struct cmd_q *cmdq, const char *name, + struct wait_channel *wc) +{ + if (cmdq->client == NULL || cmdq->client->session != NULL) { + cmdq_error(cmdq, "not able to wait"); + return (CMD_RETURN_ERROR); + } + + if (wc == NULL) { + wc = xmalloc(sizeof *wc); + wc->name = xstrdup(name); + wc->locked = 0; + TAILQ_INIT(&wc->waiters); + TAILQ_INIT(&wc->lockers); + RB_INSERT(wait_channels, &wait_channels, wc); + } + + TAILQ_INSERT_TAIL(&wc->waiters, cmdq, waitentry); + cmdq->references++; + + return (CMD_RETURN_WAIT); +} + +enum cmd_retval +cmd_wait_for_lock(struct cmd_q *cmdq, const char *name, + struct wait_channel *wc) +{ + if (cmdq->client == NULL || cmdq->client->session != NULL) { + cmdq_error(cmdq, "not able to lock"); + return (CMD_RETURN_ERROR); + } + + if (wc == NULL) { + wc = xmalloc(sizeof *wc); + wc->name = xstrdup(name); + wc->locked = 0; + TAILQ_INIT(&wc->waiters); + TAILQ_INIT(&wc->lockers); + RB_INSERT(wait_channels, &wait_channels, wc); + } + + if (wc->locked) { + TAILQ_INSERT_TAIL(&wc->lockers, cmdq, waitentry); + cmdq->references++; + return (CMD_RETURN_WAIT); + } + wc->locked = 1; + + return (CMD_RETURN_NORMAL); +} + +enum cmd_retval +cmd_wait_for_unlock(struct cmd_q *cmdq, const char *name, + struct wait_channel *wc) +{ + struct cmd_q *wq; + + if (wc == NULL || !wc->locked) { + cmdq_error(cmdq, "channel %s not locked", name); + return (CMD_RETURN_ERROR); + } + + if ((wq = TAILQ_FIRST(&wc->lockers)) != NULL) { + TAILQ_REMOVE(&wc->lockers, wq, waitentry); + if (!cmdq_free(wq)) + cmdq_continue(wq); + } else { + wc->locked = 0; + if (TAILQ_EMPTY(&wc->waiters)) { + RB_REMOVE(wait_channels, &wait_channels, wc); + free((void*) wc->name); + free(wc); + } + } + + return (CMD_RETURN_NORMAL); +} + diff --git a/cmd.c b/cmd.c index 0d6a85ff..c8e9702d 100644 --- a/cmd.c +++ b/cmd.c @@ -112,6 +112,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_switch_client_entry, &cmd_unbind_key_entry, &cmd_unlink_window_entry, + &cmd_wait_for_entry, NULL }; @@ -121,6 +122,7 @@ struct session *cmd_choose_session(int); struct client *cmd_choose_client(struct clients *); struct client *cmd_lookup_client(const char *); struct session *cmd_lookup_session(const char *, int *); +struct session *cmd_lookup_session_id(const char *); struct winlink *cmd_lookup_window(struct session *, const char *, int *); int cmd_lookup_index(struct session *, const char *, int *); struct window_pane *cmd_lookup_paneid(const char *); @@ -356,8 +358,8 @@ cmd_current_session(struct cmd_q *cmdq, int prefer_unattached) } /* Use the session from the TMUX environment variable. */ - if (data != NULL && data->pid == getpid() && data->idx != -1) { - s = session_find_by_index(data->idx); + if (data != NULL && data->pid == getpid() && data->session_id != -1) { + s = session_find_by_id(data->session_id); if (s != NULL) return (s); } @@ -549,6 +551,21 @@ cmd_lookup_client(const char *name) return (NULL); } +/* Find the target session or report an error and return NULL. */ +struct session * +cmd_lookup_session_id(const char *arg) +{ + char *endptr; + long id; + + if (arg[0] != '$') + return (NULL); + id = strtol(arg + 1, &endptr, 10); + if (arg[1] != '\0' && *endptr == '\0') + return (session_find_by_id(id)); + return (NULL); +} + /* Lookup a session by name. If no session is found, NULL is returned. */ struct session * cmd_lookup_session(const char *name, int *ambiguous) @@ -557,6 +574,10 @@ cmd_lookup_session(const char *name, int *ambiguous) *ambiguous = 0; + /* Look for $id first. */ + if ((s = cmd_lookup_session_id(name)) != NULL) + return (s); + /* * Look for matches. First look for exact matches - session names must * be unique so an exact match can't be ambigious and can just be diff --git a/configure.ac b/configure.ac index 84aec0fb..9e060df7 100644 --- a/configure.ac +++ b/configure.ac @@ -53,8 +53,11 @@ AM_CONDITIONAL(IS_DEBUG, test "x$found_debug" = xyes) AC_ARG_ENABLE( static, AC_HELP_STRING(--enable-static, create a static build), - [LDFLAGS="$LDFLAGS -static"] + found_static=$enable_static ) +if test "x$found_static" = xyes; then + LDFLAGS="$LDFLAGS -static" +fi # Is this gcc? AM_CONDITIONAL(IS_GCC, test "x$GCC" = xyes) diff --git a/control-notify.c b/control-notify.c index bb9708c8..90ee4ffa 100644 --- a/control-notify.c +++ b/control-notify.c @@ -45,7 +45,7 @@ control_notify_input(struct client *c, struct window_pane *wp, */ if (winlink_find_by_window(&c->session->windows, wp->window) != NULL) { message = evbuffer_new(); - evbuffer_add_printf(message, "%%output %%%u ", wp->id); + evbuffer_add_printf(message, "%%output %u ", wp->id); for (i = 0; i < len; i++) evbuffer_add_printf(message, "%02hhx", buf[i]); control_write_buffer(c, message); @@ -141,7 +141,7 @@ control_notify_window_renamed(struct window *w) continue; s = c->session; - control_write(c, "%%window-renamed %u %s", w->id, w->name); + control_write(c, "%%window-renamed %u %s", w->id, w->name); } } @@ -154,7 +154,7 @@ control_notify_attached_session_changed(struct client *c) return; s = c->session; - control_write(c, "%%session-changed %d %s", s->idx, s->name); + control_write(c, "%%session-changed %u %s", s->id, s->name); } void diff --git a/control.c b/control.c index 06f20bbb..c888877e 100644 --- a/control.c +++ b/control.c @@ -68,8 +68,13 @@ control_callback(struct client *c, int closed, unused void *data) } if (cmd_string_parse(line, &cmdlist, NULL, 0, &cause) != 0) { - control_write(c, "%%error in line \"%s\": %s", line, - cause); + c->cmdq->time = time(NULL); + c->cmdq->number++; + + cmdq_guard(c->cmdq, "begin"); + control_write(c, "parse error: %s", cause); + cmdq_guard(c->cmdq, "error"); + free(cause); } else { cmdq_run(c->cmdq, cmdlist); diff --git a/examples/tmux-zoom.sh b/examples/tmux-zoom.sh deleted file mode 100644 index 3a604564..00000000 --- a/examples/tmux-zoom.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2012 Juan Ignacio Pumarino, jipumarino@gmail.com -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -# Instructions -# ------------ -# -# 1. Install this script and give it execute permission somewhere in your PATH. -# For example: -# -# $ mkdir -p ~/bin -# $ wget https://raw.github.com/jipumarino/tmux-zoom/master/tmux-zoom.sh -O ~/bin/tmux-zoom.sh -# $ chmod +x ~/bin/tmux-zoom.sh -# -# 2. Add a shortcut in your ~/.tmux.conf file: -# -# bind C-k run "tmux-zoom.sh" -# -# 3. When using this shortcut, the current tmux pane will open in a new window by itself. -# Running it again in the zoomed window will return it to its original pane. You can have -# as many zoomed windows as you want. - -current=$(tmux display-message -p '#W-#I-#P') -list=$(tmux list-window) - -[[ "$current" =~ ^(.*)-([0-9]+)-([0-9]+) ]] -current_window=${BASH_REMATCH[1]} -current_pane=${BASH_REMATCH[2]}-${BASH_REMATCH[3]} -new_zoom_window=ZOOM-$current_pane - -if [[ $current_window =~ ZOOM-([0-9]+)-([0-9+]) ]]; then - old_zoom_window=ZOOM-${BASH_REMATCH[1]}-${BASH_REMATCH[2]} - tmux select-window -t ${BASH_REMATCH[1]} \; select-pane -t ${BASH_REMATCH[2]} \; swap-pane -s $old_zoom_window.1 \; kill-window -t $old_zoom_window -elif [[ $list =~ $new_zoom_window ]]; then - tmux select-window -t $new_zoom_window -else - tmux new-window -d -n $new_zoom_window \; swap-pane -s $new_zoom_window.1 \; select-window -t $new_zoom_window -fi diff --git a/format.c b/format.c index ad52caea..4d70d59b 100644 --- a/format.c +++ b/format.c @@ -280,6 +280,7 @@ format_session(struct format_tree *ft, struct session *s) format_add(ft, "session_windows", "%u", winlink_count(&s->windows)); format_add(ft, "session_width", "%u", s->sx); format_add(ft, "session_height", "%u", s->sy); + format_add(ft, "session_id", "%u", s->id); sg = session_group_find(s); format_add(ft, "session_grouped", "%d", sg != NULL); @@ -398,7 +399,8 @@ format_window_pane(struct format_tree *ft, struct window_pane *wp) struct grid_line *gl; unsigned long long size; u_int i, idx; - const char *cwd, *cmd; + const char *cwd; + char *cmd; size = 0; for (i = 0; i < gd->hsize; i++) { @@ -432,8 +434,10 @@ format_window_pane(struct format_tree *ft, struct window_pane *wp) format_add(ft, "pane_start_path", "%s", wp->cwd); if ((cwd = osdep_get_cwd(wp->fd)) != NULL) format_add(ft, "pane_current_path", "%s", cwd); - if ((cmd = osdep_get_name(wp->fd, wp->tty)) != NULL) + if ((cmd = osdep_get_name(wp->fd, wp->tty)) != NULL) { format_add(ft, "pane_current_command", "%s", cmd); + free(cmd); + } format_add(ft, "cursor_x", "%d", wp->base.cx); format_add(ft, "cursor_y", "%d", wp->base.cy); diff --git a/grid.c b/grid.c index b30127f3..551a7dc9 100644 --- a/grid.c +++ b/grid.c @@ -595,7 +595,7 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, GRID_DEBUG(gd, "px=%u, py=%u, nx=%u", px, py, nx); - if (*lastgc == NULL) { + if (lastgc != NULL && *lastgc == NULL) { memcpy(&lastgc1, &grid_default_cell, sizeof lastgc1); *lastgc = &lastgc1; } diff --git a/layout-custom.c b/layout-custom.c index c076232f..e32d9d9d 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -63,7 +63,7 @@ layout_dump(struct window *w) if (layout_append(w->layout_root, layout, sizeof layout) != 0) return (NULL); - xasprintf(&out, "%4x,%s", layout_checksum(layout), layout); + xasprintf(&out, "%04x,%s", layout_checksum(layout), layout); return (out); } @@ -206,11 +206,11 @@ layout_construct(struct layout_cell *lcparent, const char **layout) { struct layout_cell *lc, *lcchild; u_int sx, sy, xoff, yoff; + const char *saved; if (!isdigit((u_char) **layout)) return (NULL); - if (sscanf(*layout, "%ux%u,%u,%u,%*u", &sx, &sy, &xoff, &yoff) != 4 && - sscanf(*layout, "%ux%u,%u,%u", &sx, &sy, &xoff, &yoff) != 4) + if (sscanf(*layout, "%ux%u,%u,%u", &sx, &sy, &xoff, &yoff) != 4) return (NULL); while (isdigit((u_char) **layout)) @@ -231,9 +231,12 @@ layout_construct(struct layout_cell *lcparent, const char **layout) while (isdigit((u_char) **layout)) (*layout)++; if (**layout == ',') { + saved = *layout; (*layout)++; while (isdigit((u_char) **layout)) (*layout)++; + if (**layout == 'x') + *layout = saved; } lc = layout_create_cell(lcparent); diff --git a/screen-redraw.c b/screen-redraw.c index 899f741b..14b73164 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -273,6 +273,9 @@ screen_redraw_pane(struct client *c, struct window_pane *wp) { u_int i, yoff; + if (!window_pane_visible(wp)) + return; + yoff = wp->yoff; if (status_at_line(c) == 0) yoff++; diff --git a/server-client.c b/server-client.c index b905a7c7..77e6de78 100644 --- a/server-client.c +++ b/server-client.c @@ -153,7 +153,8 @@ server_client_lost(struct client *c) evbuffer_free (c->stdin_data); evbuffer_free (c->stdout_data); - evbuffer_free (c->stderr_data); + if (c->stderr_data != c->stdout_data) + evbuffer_free (c->stderr_data); status_free_jobs(&c->status_new); status_free_jobs(&c->status_old); @@ -955,6 +956,8 @@ server_client_msg_identify( if (data->flags & IDENTIFY_CONTROL) { c->stdin_callback = control_callback; + evbuffer_free(c->stderr_data); + c->stderr_data = c->stdout_data; c->flags |= CLIENT_CONTROL; if (data->flags & IDENTIFY_TERMIOS) evbuffer_add_printf(c->stdout_data, "\033P1000p"); diff --git a/server-fn.c b/server-fn.c index b09415e0..566925f0 100644 --- a/server-fn.c +++ b/server-fn.c @@ -39,7 +39,7 @@ server_fill_environ(struct session *s, struct environ *env) term = options_get_string(&s->options, "default-terminal"); environ_set(env, "TERM", term); - idx = s->idx; + idx = s->id; } else idx = -1; pid = getpid(); @@ -546,6 +546,10 @@ server_push_stderr(struct client *c) struct msg_stderr_data data; size_t size; + if (c->stderr_data == c->stdout_data) { + server_push_stdout(c); + return; + } size = EVBUFFER_LENGTH(c->stderr_data); if (size == 0) return; diff --git a/session.c b/session.c index 72e8fb05..74eb06a5 100644 --- a/session.c +++ b/session.c @@ -29,7 +29,7 @@ /* Global session list. */ struct sessions sessions; struct sessions dead_sessions; -u_int next_session; +u_int next_session_id; struct session_groups session_groups; struct winlink *session_next_alert(struct winlink *); @@ -69,14 +69,14 @@ session_find(const char *name) return (RB_FIND(sessions, &sessions, &s)); } -/* Find session by index. */ +/* Find session by id. */ struct session * -session_find_by_index(u_int idx) +session_find_by_id(u_int id) { struct session *s; RB_FOREACH(s, sessions, &sessions) { - if (s->idx == idx) + if (s->id == id) return (s); } return (NULL); @@ -120,13 +120,13 @@ session_create(const char *name, const char *cmd, const char *cwd, if (name != NULL) { s->name = xstrdup(name); - s->idx = next_session++; + s->id = next_session_id++; } else { s->name = NULL; do { - s->idx = next_session++; + s->id = next_session_id++; free (s->name); - xasprintf(&s->name, "%u", s->idx); + xasprintf(&s->name, "%u", s->id); } while (RB_FIND(sessions, &sessions, s) != NULL); } RB_INSERT(sessions, &sessions, s); diff --git a/tmux.1 b/tmux.1 index a65ce221..519bf6fa 100644 --- a/tmux.1 +++ b/tmux.1 @@ -23,7 +23,7 @@ .Sh SYNOPSIS .Nm tmux .Bk -words -.Op Fl 28lquvV +.Op Fl 28lCquvV .Op Fl c Ar shell-command .Op Fl f Ar file .Op Fl L Ar socket-name @@ -102,6 +102,11 @@ to assume the terminal supports 256 colours. Like .Fl 2 , but indicates that the terminal supports 88 colours. +.It Fl C +Start in control mode. +Given twice +.Xo ( Fl CC ) Xc +disables echo. .It Fl c Ar shell-command Execute .Ar shell-command @@ -369,9 +374,9 @@ Clients may be listed with the command. .Pp .Ar target-session -is either the name of a session (as listed by the +is the session id prefixed with a $, the name of a session (as listed by the .Ic list-sessions -command) or the name of a client with the same syntax as +command), or the name of a client with the same syntax as .Ar target-client , in which case the session attached to the client is used. When looking for the session name, @@ -1619,7 +1624,7 @@ is given in lines or cells (the default is 1). .Pp With .Fl Z , -the active pane is toggled between occupying the whole of the window and it's +the active pane is toggled between occupying the whole of the window and its normal position in the layout. .It Xo Ic respawn-pane .Op Fl k @@ -3085,6 +3090,7 @@ The following variables are available, where appropriate: .It Li "session_group" Ta "Number of session group" .It Li "session_grouped" Ta "1 if session in a group" .It Li "session_height" Ta "Height of session" +.It Li "session_id" Ta "Unique session ID" .It Li "session_name" Ta "Name of session" .It Li "session_width" Ta "Width of session" .It Li "session_windows" Ta "Number of windows in session" @@ -3553,6 +3559,23 @@ If the command doesn't return success, the exit status is also displayed. .It Ic server-info .D1 (alias: Ic info ) Show server information and terminal details. +.It Xo Ic wait-for +.Fl LSU +.Ar channel +.Xc +.D1 (alias: Ic wait ) +When used without options, prevents the client from exiting until woken using +.Ic wait-for +.Fl S +with the same channel. +When +.Fl L +is used, the channel is locked and any clients that try to lock the same +channel are made to wait until the channel is unlocked with +.Ic wait-for +.Fl U . +This command only works from outside +.Nm . .El .Sh TERMINFO EXTENSIONS .Nm @@ -3592,6 +3615,92 @@ option above and the .Xr xterm 1 man page. .El +.Sh CONTROL MODE +.Nm +offers a textual interface called +.Em control mode . +This allows applications to communicate with +.Nm +using a simple text-only protocol. +.Pp +In control mode, a client sends +.Nm +commands or command sequences terminated by newlines on standard input. +Each command will produce one block of output on standard output. +An output block consists of a +.Em %begin +line followed by the output (which may be empty). +The output block ends with a +.Em %end +or +.Em %error . +.Em %begin +and matching +.Em %end +or +.Em %error +have two arguments: an integer time (as seconds from epoch) and command number. +For example: +.Bd -literal -offset indent +%begin 1363006971 2 +0: ksh* (1 panes) [80x24] [layout b25f,80x24,0,0,2] @2 (active) +%end 1363006971 2 +.Ed +.Pp +In control mode, +.Nm +outputs notifications. +A notification will never occur inside an output block. +.Pp +The following notifications are defined: +.Pp +.Bl -tag -width Ds +.It Ic %exit Op Ar reason +The +.Nm +client is exiting immediately, either because it is not attached to any session +or an error occurred. +If present, +.Ar reason +describes why the client exited. +.It Ic %layout-change Ar window-id Ar window-layout +The layout of a window with ID +.Ar window-id +changed. +The new layout is +.Ar window-layout . +.It Ic %output Ar pane-id Ar value +A window pane produced output. +.Ar value +contains that output with each byte encoded as two hex digits. +.It Ic %session-changed Ar session-id Ar name +The client is now attached to the session with ID +.Ar session-id , +which is named +.Ar name . +.It Ic %session-renamed Ar name +The current session was renamed to +.Ar name . +.It Ic %sessions-changed +A session was created or destroyed. +.It Ic %unlinked-window-add Ar window-id +The window with ID +.Ar window-id +was created but is not linked to the current session. +.It Ic %window-add Ar window-id +The window with ID +.Ar window-id +was linked to the current session. +.It Ic %window-close Ar window-id +The window with ID +.Ar window-id +closed. +.It Ic %window-renamed Ar window-id Ar name +The window with ID +.Ar window-id +was renamed to +.Ar name . +.El .Sh FILES .Bl -tag -width "/etc/tmux.confXXX" -compact .It Pa ~/.tmux.conf diff --git a/tmux.c b/tmux.c index f6856605..8ea91ebe 100644 --- a/tmux.c +++ b/tmux.c @@ -48,7 +48,7 @@ char socket_path[MAXPATHLEN]; int login_shell; char *environ_path; pid_t environ_pid = -1; -int environ_idx = -1; +int environ_session_id = -1; __dead void usage(void); void parseenvironment(void); @@ -147,16 +147,16 @@ parseenvironment(void) { char *env, path[256]; long pid; - int idx; + int id; if ((env = getenv("TMUX")) == NULL) return; - if (sscanf(env, "%255[^,],%ld,%d", path, &pid, &idx) != 3) + if (sscanf(env, "%255[^,],%ld,%d", path, &pid, &id) != 3) return; environ_path = xstrdup(path); environ_pid = pid; - environ_idx = idx; + environ_session_id = id; } char * diff --git a/tmux.h b/tmux.h index c1ad6628..89860cb8 100644 --- a/tmux.h +++ b/tmux.h @@ -462,8 +462,8 @@ enum msgtype { * Don't forget to bump PROTOCOL_VERSION if any of these change! */ struct msg_command_data { - pid_t pid; /* PID from $TMUX or -1 */ - int idx; /* index from $TMUX or -1 */ + pid_t pid; /* from $TMUX or -1 */ + int session_id; /* from $TMUX or -1 */ int argc; char argv[COMMAND_LENGTH]; @@ -1086,7 +1086,7 @@ struct session_group { TAILQ_HEAD(session_groups, session_group); struct session { - u_int idx; + u_int id; char *name; char *cwd; @@ -1412,10 +1412,15 @@ struct cmd_q { struct cmd_q_item *item; struct cmd *cmd; + time_t time; + u_int number; + void (*emptyfn)(struct cmd_q *); void *data; struct msg_command_data *msgdata; + + TAILQ_ENTRY(cmd_q) waitentry; }; /* Command definition. */ @@ -1511,7 +1516,7 @@ extern char socket_path[MAXPATHLEN]; extern int login_shell; extern char *environ_path; extern pid_t environ_pid; -extern int environ_idx; +extern int environ_session_id; void logfile(const char *); const char *getshell(void); int checkshell(const char *); @@ -1835,6 +1840,7 @@ extern const struct cmd_entry cmd_switch_client_entry; extern const struct cmd_entry cmd_unbind_key_entry; extern const struct cmd_entry cmd_unlink_window_entry; extern const struct cmd_entry cmd_up_pane_entry; +extern const struct cmd_entry cmd_wait_for_entry; /* cmd-attach-session.c */ enum cmd_retval cmd_attach_session(struct cmd_q *, const char*, int, int); @@ -1850,6 +1856,7 @@ int cmdq_free(struct cmd_q *); void printflike2 cmdq_print(struct cmd_q *, const char *, ...); void printflike2 cmdq_info(struct cmd_q *, const char *, ...); void printflike2 cmdq_error(struct cmd_q *, const char *, ...); +void cmdq_guard(struct cmd_q *, const char *); void cmdq_run(struct cmd_q *, struct cmd_list *); void cmdq_append(struct cmd_q *, struct cmd_list *); int cmdq_continue(struct cmd_q *); @@ -2284,7 +2291,7 @@ int session_cmp(struct session *, struct session *); RB_PROTOTYPE(sessions, session, entry, session_cmp); int session_alive(struct session *); struct session *session_find(const char *); -struct session *session_find_by_index(u_int); +struct session *session_find_by_id(u_int); struct session *session_create(const char *, const char *, const char *, struct environ *, struct termios *, int, u_int, u_int, char **); diff --git a/tty-keys.c b/tty-keys.c index 575920e6..fab8c3fb 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -82,6 +82,13 @@ const struct tty_default_key_raw tty_default_raw_keys[] = { { "\033[C", KEYC_RIGHT }, { "\033[D", KEYC_LEFT }, + /* Other (xterm) "cursor" keys. */ + { "\033OH", KEYC_HOME }, + { "\033OF", KEYC_END }, + + { "\033[H", KEYC_HOME }, + { "\033[F", KEYC_END }, + /* rxvt-style arrow + modifier keys. */ { "\033Oa", KEYC_UP|KEYC_CTRL }, { "\033Ob", KEYC_DOWN|KEYC_CTRL }, diff --git a/window-choose.c b/window-choose.c index b56b2022..3c68d101 100644 --- a/window-choose.c +++ b/window-choose.c @@ -859,7 +859,7 @@ window_choose_add_session(struct window_pane *wp, struct client *c, struct window_choose_data *wcd; wcd = window_choose_data_create(TREE_SESSION, c, c->session); - wcd->idx = s->idx; + wcd->idx = s->id; wcd->tree_session = s; wcd->tree_session->references++;