diff --git a/cmd-split-window.c b/cmd-split-window.c index 97fa9ae3..7bf9bb5c 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -38,8 +38,8 @@ const struct cmd_entry cmd_new_pane_entry = { .name = "new-pane", .alias = "newp", - .args = { "bc:de:EfF:hIkl:Lm:p:PR:s:S:t:vx:X:y:Y:Z", 0, -1, NULL }, - .usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] " + .args = { "bBc:de:EfF:hIkl:Lm:p:PR:s:S:t:vx:X:y:Y:Z", 0, -1, NULL }, + .usage = "[-bBdefhIklPvZ] [-c start-directory] [-e environment] " "[-F format] [-l size] [-m message] [-p percentage] " "[-s style] [-S active-border-style] " "[-R inactive-border-style] [-x width] [-y height] " @@ -238,5 +238,15 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) environ_free(sc.environ); if (input) return (CMD_RETURN_WAIT); + + /* + * With -B, block this command queue item until the pane's command + * exits; window_pane_block_finish() then sets the client return code + * and continues the queue (see server_child_exited()). + */ + if (args_has(args, 'B')) { + new_wp->block_item = item; + return (CMD_RETURN_WAIT); + } return (CMD_RETURN_NORMAL); } diff --git a/server.c b/server.c index 49cac8bb..7b77da73 100644 --- a/server.c +++ b/server.c @@ -497,6 +497,8 @@ server_child_exited(pid_t pid, int status) log_debug("%%%u exited", wp->id); wp->flags |= PANE_EXITED; + window_pane_block_finish(wp); + if (window_pane_destroy_ready(wp)) server_destroy_pane(wp, 1); break; diff --git a/tmux.1 b/tmux.1 index f0dafb1a..dbc50469 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3330,7 +3330,7 @@ but a different format may be specified with .Fl F . .Tg newp .It Xo Ic new\-pane -.Op Fl bdefhIkPvZ +.Op Fl bBdefhIkPvZ .Op Fl c Ar start\-directory .Op Fl e Ar environment .Op Fl F Ar format @@ -3403,6 +3403,22 @@ but also sets the option for this pane to .Ar message . .Pp +.Fl B +blocks until the +.Ar shell\-command +running in the new pane exits, then returns its exit status as the exit +code of the issuing client. +This makes it possible to run a command in a pane from a script and act on +its result, for example a +.Xr whiptail 1 +dialog. +Only the command queue of the issuing client waits; the server and other +clients are unaffected and the new pane remains interactive. +If the pane is killed or terminated by a signal, the status is +.Li 128 +plus the signal number, as for +.Ic run\-shell . +.Pp .Fl E , or an empty .Ar shell\-command , diff --git a/tmux.h b/tmux.h index 870e778b..c40e1cd9 100644 --- a/tmux.h +++ b/tmux.h @@ -1294,6 +1294,7 @@ struct window_pane { char tty[TTY_NAME_MAX]; int status; struct timeval dead_time; + struct cmdq_item *block_item; /* new-pane -B: waiting for pane exit */ int fd; struct bufferevent *event; @@ -3422,6 +3423,7 @@ struct window *window_find_by_id(u_int); void window_update_activity(struct window *); struct window *window_create(u_int, u_int, u_int, u_int); void window_pane_set_event(struct window_pane *); +void window_pane_block_finish(struct window_pane *); struct window_pane *window_get_active_at(struct window *, u_int, u_int); struct window_pane *window_find_string(struct window *, const char *); int window_has_floating_panes(struct window *); diff --git a/window.c b/window.c index 0d86ac50..b12d1396 100644 --- a/window.c +++ b/window.c @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -380,6 +381,15 @@ window_pane_destroy_ready(struct window_pane *wp) if (~wp->flags & PANE_EXITED) return (0); + + /* + * If a command queue item is blocked on this pane (new-pane -B), wait + * for the child's exit status to be reaped before destroying it, so + * the correct return code is reported. The PTY EOF and SIGCHLD paths + * otherwise race and the pane could be freed with no status. + */ + if (wp->block_item != NULL && (~wp->flags & PANE_STATUSREADY)) + return (0); return (1); } @@ -1064,12 +1074,43 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) return (wp); } +/* + * Finish a new-pane -B wait: if this pane has a blocked command queue item, + * set the issuing client's return code from the pane's exit status and continue + * the queue. Idempotent and safe to call from any pane teardown path. + */ +void +window_pane_block_finish(struct window_pane *wp) +{ + struct cmdq_item *item = wp->block_item; + struct client *c; + int retcode = 0; + + if (item == NULL) + return; + wp->block_item = NULL; + + if (wp->flags & PANE_STATUSREADY) { + if (WIFEXITED(wp->status)) + retcode = WEXITSTATUS(wp->status); + else if (WIFSIGNALED(wp->status)) + retcode = WTERMSIG(wp->status) + 128; + } + + c = cmdq_get_client(item); + if (c != NULL && c->session == NULL) + c->retval = retcode; + cmdq_continue(item); +} + static void window_pane_destroy(struct window_pane *wp) { struct window_pane_resize *r; struct window_pane_resize *r1; + window_pane_block_finish(wp); + window_pane_reset_mode_all(wp); free(wp->searchstr);