diff --git a/Makefile b/Makefile index 99417ba2..aedabadf 100644 --- a/Makefile +++ b/Makefile @@ -104,6 +104,7 @@ SRCS= alerts.c \ server-fn.c \ server.c \ session.c \ + spawn.c \ status.c \ style.c \ tmux.c \ diff --git a/cmd-attach-session.c b/cmd-attach-session.c index 73ff530d..bcd6d895 100644 --- a/cmd-attach-session.c +++ b/cmd-attach-session.c @@ -87,7 +87,7 @@ cmd_attach_session(struct cmdq_item *item, const char *tflag, int dflag, if (wl != NULL) { if (wp != NULL) - window_set_active_pane(wp->window, wp); + window_set_active_pane(wp->window, wp, 1); session_set_current(s, wl); if (wp != NULL) cmd_find_from_winlink_pane(current, wl, wp, 0); diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 79bc1c9d..ab245302 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -72,7 +72,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) int size, percentage, dst_idx; enum layout_type type; struct layout_cell *lc; - int not_same_window; + int not_same_window, flags; if (self->entry == &cmd_join_pane_entry) not_same_window = 1; @@ -124,7 +124,11 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) else size = (dst_wp->sx * percentage) / 100; } - lc = layout_split_pane(dst_wp, type, size, args_has(args, 'b'), 0); + if (args_has(args, 'b')) + flags = SPAWN_BEFORE; + else + flags = 0; + lc = layout_split_pane(dst_wp, type, size, flags); if (lc == NULL) { cmdq_error(item, "create pane failed: pane too small"); return (CMD_RETURN_ERROR); @@ -145,7 +149,7 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) server_redraw_window(dst_w); if (!args_has(args, 'd')) { - window_set_active_pane(dst_w, src_wp); + window_set_active_pane(dst_w, src_wp, 1); session_select(dst_s, dst_idx); cmd_find_from_session(current, dst_s, 0); server_redraw_session(dst_s); diff --git a/cmd-kill-session.c b/cmd-kill-session.c index 00ea7789..dcef8097 100644 --- a/cmd-kill-session.c +++ b/cmd-kill-session.c @@ -61,12 +61,12 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item) RB_FOREACH_SAFE(sloop, sessions, &sessions, stmp) { if (sloop != s) { server_destroy_session(sloop); - session_destroy(sloop, __func__); + session_destroy(sloop, 1, __func__); } } } else { server_destroy_session(s); - session_destroy(s, __func__); + session_destroy(s, 1, __func__); } return (CMD_RETURN_NORMAL); } diff --git a/cmd-new-session.c b/cmd-new-session.c index e1be350b..f0a353d8 100644 --- a/cmd-new-session.c +++ b/cmd-new-session.c @@ -69,20 +69,17 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) struct args *args = self->args; struct client *c = item->client; struct session *s, *as, *groupwith; - struct window *w; struct environ *env; struct options *oo; struct termios tio, *tiop; struct session_group *sg; - const char *errstr, *template, *group, *prefix; - const char *path, *cmd, *tmp, *value; - char **argv, *cause, *cp, *newname, *cwd = NULL; - int detached, already_attached, idx, argc; - int is_control = 0; - u_int sx, sy, dsx = 80, dsy = 24; - struct environ_entry *envent; - struct cmd_find_state fs; + const char *errstr, *template, *group, *prefix, *tmp; + char *cause, *cwd = NULL, *cp, *newname = NULL; + int detached, already_attached, is_control = 0; + u_int sx, sy, dsx, dsy; + struct spawn_context sc; enum cmd_retval retval; + struct cmd_find_state fs; if (self->entry == &cmd_has_session_entry) { /* @@ -97,13 +94,12 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_ERROR); } - newname = NULL; if (args_has(args, 's')) { newname = format_single(item, args_get(args, 's'), c, NULL, NULL, NULL); if (!session_check_name(newname)) { cmdq_error(item, "bad session name: %s", newname); - goto error; + goto fail; } if ((as = session_find(newname)) != NULL) { if (args_has(args, 'A')) { @@ -114,7 +110,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) return (retval); } cmdq_error(item, "duplicate session: %s", newname); - goto error; + goto fail; } } @@ -125,7 +121,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if (groupwith == NULL) { if (!session_check_name(group)) { cmdq_error(item, "bad group name: %s", group); - goto error; + goto fail; } sg = session_group_find(group); } else @@ -173,7 +169,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if (server_client_check_nested(item->client)) { cmdq_error(item, "sessions should be nested with care, " "unset $TMUX to force"); - return (CMD_RETURN_ERROR); + goto fail; } if (tcgetattr(c->tty.fd, &tio) != 0) fatal("tcgetattr failed"); @@ -186,7 +182,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if (server_client_open(c, &cause) != 0) { cmdq_error(item, "open terminal failed: %s", cause); free(cause); - goto error; + goto fail; } } @@ -200,7 +196,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) dsx = strtonum(tmp, 1, USHRT_MAX, &errstr); if (errstr != NULL) { cmdq_error(item, "width %s", errstr); - goto error; + goto fail; } } } @@ -213,7 +209,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) dsy = strtonum(tmp, 1, USHRT_MAX, &errstr); if (errstr != NULL) { cmdq_error(item, "height %s", errstr); - goto error; + goto fail; } } } @@ -225,8 +221,8 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if (sy > 0 && options_get_number(global_s_options, "status")) sy--; } else { - value = options_get_string(global_s_options, "default-size"); - if (sscanf(value, "%ux%u", &sx, &sy) != 2) { + tmp = options_get_string(global_s_options, "default-size"); + if (sscanf(tmp, "%ux%u", &sx, &sy) != 2) { sx = 80; sy = 24; } @@ -240,59 +236,34 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) if (sy == 0) sy = 1; - /* Figure out the command for the new window. */ - argc = -1; - argv = NULL; - if (!args_has(args, 't') && args->argc != 0) { - argc = args->argc; - argv = args->argv; - } else if (sg == NULL && groupwith == NULL) { - cmd = options_get_string(global_s_options, "default-command"); - if (cmd != NULL && *cmd != '\0') { - argc = 1; - argv = (char **)&cmd; - } else { - argc = 0; - argv = NULL; - } - } - - path = NULL; - if (c != NULL && c->session == NULL) - envent = environ_find(c->environ, "PATH"); - else - envent = environ_find(global_environ, "PATH"); - if (envent != NULL) - path = envent->value; - - /* Construct the environment. */ - env = environ_create(); - if (c != NULL && !args_has(args, 'E')) - environ_update(global_s_options, c->environ, env); - - /* Set up the options. */ + /* Create the new session. */ oo = options_create(global_s_options); if (args_has(args, 'x') || args_has(args, 'y')) options_set_string(oo, "default-size", 0, "%ux%u", dsx, dsy); + env = environ_create(); + if (c != NULL && !args_has(args, 'E')) + environ_update(global_s_options, c->environ, env); + s = session_create(prefix, newname, cwd, env, oo, tiop); - /* Create the new session. */ - idx = -1 - options_get_number(global_s_options, "base-index"); - s = session_create(prefix, newname, argc, argv, path, cwd, env, oo, - tiop, idx, &cause); - environ_free(env); - if (s == NULL) { - cmdq_error(item, "create session failed: %s", cause); + /* Spawn the initial window. */ + memset(&sc, 0, sizeof sc); + sc.item = item; + sc.s = s; + + sc.name = args_get(args, 'n'); + sc.argc = args->argc; + sc.argv = args->argv; + + sc.idx = -1; + sc.cwd = args_get(args, 'c'); + + sc.flags = 0; + + if (spawn_window(&sc, &cause) == NULL) { + session_destroy(s, 0, __func__); + cmdq_error(item, "create window failed: %s", cause); free(cause); - goto error; - } - - /* Set the initial window name if one given. */ - if (argc >= 0 && (tmp = args_get(args, 'n')) != NULL) { - cp = format_single(item, tmp, c, s, NULL, NULL); - w = s->curw->window; - window_set_name(w, cp); - options_set_number(w->options, "automatic-rename", 0); - free(cp); + goto fail; } /* @@ -364,7 +335,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item) free(newname); return (CMD_RETURN_NORMAL); -error: +fail: free(cwd); free(newname); return (CMD_RETURN_ERROR); diff --git a/cmd-new-window.c b/cmd-new-window.c index 5baeff65..e3bacff8 100644 --- a/cmd-new-window.c +++ b/cmd-new-window.c @@ -53,87 +53,45 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; struct cmd_find_state *current = &item->shared->current; + struct spawn_context sc; + struct client *c = cmd_find_client(item, NULL, 1); struct session *s = item->target.s; struct winlink *wl = item->target.wl; - struct client *c = cmd_find_client(item, NULL, 1); int idx = item->target.idx; - const char *cmd, *path, *template, *tmp; - char **argv, *cause, *cp, *cwd, *name; - int argc, detached; - struct environ_entry *envent; + struct winlink *new_wl; + char *cause = NULL, *cp; + const char *template; struct cmd_find_state fs; - if (args_has(args, 'a') && wl != NULL) { - if ((idx = winlink_shuffle_up(s, wl)) == -1) { - cmdq_error(item, "no free window indexes"); - return (CMD_RETURN_ERROR); - } - } - detached = args_has(args, 'd'); - - if (args->argc == 0) { - cmd = options_get_string(s->options, "default-command"); - if (cmd != NULL && *cmd != '\0') { - argc = 1; - argv = (char **)&cmd; - } else { - argc = 0; - argv = NULL; - } - } else { - argc = args->argc; - argv = args->argv; + if (args_has(args, 'a') && (idx = winlink_shuffle_up(s, wl)) == -1) { + cmdq_error(item, "couldn't get a window index"); + return (CMD_RETURN_ERROR); } - path = NULL; - if (item->client != NULL && item->client->session == NULL) - envent = environ_find(item->client->environ, "PATH"); - else - envent = environ_find(s->environ, "PATH"); - if (envent != NULL) - path = envent->value; + memset(&sc, 0, sizeof sc); + sc.item = item; + sc.s = s; - if ((tmp = args_get(args, 'c')) != NULL) - cwd = format_single(item, tmp, c, s, NULL, NULL); - else - cwd = xstrdup(server_client_get_cwd(item->client, s)); + sc.name = args_get(args, 'n'); + sc.argc = args->argc; + sc.argv = args->argv; - if ((tmp = args_get(args, 'n')) != NULL) - name = format_single(item, tmp, c, s, NULL, NULL); - else - name = NULL; + sc.idx = idx; + sc.cwd = args_get(args, 'c'); - if (idx != -1) - wl = winlink_find_by_index(&s->windows, idx); - if (wl != NULL && args_has(args, 'k')) { - /* - * Can't use session_detach as it will destroy session if this - * makes it empty. - */ - notify_session_window("window-unlinked", s, wl->window); - wl->flags &= ~WINLINK_ALERTFLAGS; - winlink_stack_remove(&s->lastw, wl); - winlink_remove(&s->windows, wl); + sc.flags = 0; + if (args_has(args, 'd')) + sc.flags |= SPAWN_DETACHED; + if (args_has(args, 'k')) + sc.flags |= SPAWN_KILL; - /* Force select/redraw if current. */ - if (wl == s->curw) { - detached = 0; - s->curw = NULL; - } - } - - if (idx == -1) - idx = -1 - options_get_number(s->options, "base-index"); - wl = session_new(s, name, argc, argv, path, cwd, idx, - &cause); - if (wl == NULL) { + if ((new_wl = spawn_window(&sc, &cause)) == NULL) { cmdq_error(item, "create window failed: %s", cause); free(cause); - goto error; + return (CMD_RETURN_ERROR); } - if (!detached) { - session_select(s, wl->idx); - cmd_find_from_winlink(current, wl, 0); + if (!args_has(args, 'd') || new_wl == s->curw) { + cmd_find_from_winlink(current, new_wl, 0); server_redraw_session_group(s); } else server_status_session_group(s); @@ -141,20 +99,13 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'P')) { if ((template = args_get(args, 'F')) == NULL) template = NEW_WINDOW_TEMPLATE; - cp = format_single(item, template, c, s, wl, NULL); + cp = format_single(item, template, c, s, new_wl, NULL); cmdq_print(item, "%s", cp); free(cp); } - cmd_find_from_winlink(&fs, wl, 0); + cmd_find_from_winlink(&fs, new_wl, 0); hooks_insert(s->hooks, item, &fs, "after-new-window"); - free(name); - free(cwd); return (CMD_RETURN_NORMAL); - -error: - free(name); - free(cwd); - return (CMD_RETURN_ERROR); } diff --git a/cmd-queue.c b/cmd-queue.c index 97b3c1c9..03fd5f10 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -111,7 +111,7 @@ cmdq_remove(struct cmdq_item *item) if (item->client != NULL) server_client_unref(item->client); - if (item->type == CMDQ_COMMAND) + if (item->cmdlist != NULL) cmd_list_free(item->cmdlist); TAILQ_REMOVE(item->queue, item, entry); diff --git a/cmd-respawn-pane.c b/cmd-respawn-pane.c index eb4a7e09..44234315 100644 --- a/cmd-respawn-pane.c +++ b/cmd-respawn-pane.c @@ -20,7 +20,7 @@ #include #include -#include +#include #include "tmux.h" @@ -36,7 +36,7 @@ const struct cmd_entry cmd_respawn_pane_entry = { .args = { "c:kt:", 0, -1 }, .usage = "[-c start-directory] [-k] " CMD_TARGET_PANE_USAGE - " [command]", + " [command]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -48,53 +48,39 @@ static enum cmd_retval cmd_respawn_pane_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; - struct winlink *wl = item->target.wl; - struct window *w = wl->window; - struct window_pane *wp = item->target.wp; - struct client *c = cmd_find_client(item, NULL, 1); + struct spawn_context sc; struct session *s = item->target.s; - struct environ *env; - const char *path = NULL, *cp; - char *cause, *cwd = NULL; - u_int idx; - struct environ_entry *envent; + struct winlink *wl = item->target.wl; + struct window_pane *wp = item->target.wp; + char *cause = NULL; - if (!args_has(self->args, 'k') && wp->fd != -1) { - if (window_pane_index(wp, &idx) != 0) - fatalx("index not found"); - cmdq_error(item, "pane still active: %s:%d.%u", - s->name, wl->idx, idx); - return (CMD_RETURN_ERROR); - } + memset(&sc, 0, sizeof sc); + sc.item = item; + sc.s = s; + sc.wl = wl; - window_pane_reset_mode_all(wp); - screen_reinit(&wp->base); - input_init(wp); + sc.wp0 = wp; + sc.lc = NULL; - if (item->client != NULL && item->client->session == NULL) - envent = environ_find(item->client->environ, "PATH"); - else - envent = environ_find(s->environ, "PATH"); - if (envent != NULL) - path = envent->value; + sc.name = NULL; + sc.argc = args->argc; + sc.argv = args->argv; - if ((cp = args_get(args, 'c')) != NULL) - cwd = format_single(item, cp, c, s, NULL, NULL); + sc.idx = -1; + sc.cwd = args_get(args, 'c'); - env = environ_for_session(s, 0); - if (window_pane_spawn(wp, args->argc, args->argv, path, NULL, cwd, env, - s->tio, &cause) != 0) { + sc.flags = SPAWN_RESPAWN; + if (args_has(args, 'k')) + sc.flags |= SPAWN_KILL; + + if (spawn_pane(&sc, &cause) == NULL) { cmdq_error(item, "respawn pane failed: %s", cause); free(cause); - environ_free(env); - free(cwd); return (CMD_RETURN_ERROR); } - environ_free(env); - free(cwd); wp->flags |= PANE_REDRAW; - server_status_window(w); + server_status_window(wp->window); return (CMD_RETURN_NORMAL); } diff --git a/cmd-respawn-window.c b/cmd-respawn-window.c index 68791990..2ccb2fde 100644 --- a/cmd-respawn-window.c +++ b/cmd-respawn-window.c @@ -19,7 +19,7 @@ #include #include -#include +#include #include "tmux.h" @@ -48,64 +48,34 @@ static enum cmd_retval cmd_respawn_window_exec(struct cmd *self, struct cmdq_item *item) { struct args *args = self->args; + struct spawn_context sc; struct session *s = item->target.s; struct winlink *wl = item->target.wl; - struct window *w = wl->window; - struct window_pane *wp; - struct client *c = cmd_find_client(item, NULL, 1); - struct environ *env; - const char *path = NULL, *cp; - char *cause, *cwd = NULL; - struct environ_entry *envent; + char *cause = NULL; - if (!args_has(self->args, 'k')) { - TAILQ_FOREACH(wp, &w->panes, entry) { - if (wp->fd == -1) - continue; - cmdq_error(item, "window still active: %s:%d", s->name, - wl->idx); - return (CMD_RETURN_ERROR); - } - } + memset(&sc, 0, sizeof sc); + sc.item = item; + sc.s = s; + sc.wl = wl; - wp = TAILQ_FIRST(&w->panes); - TAILQ_REMOVE(&w->panes, wp, entry); - layout_free(w); - window_destroy_panes(w); - TAILQ_INSERT_HEAD(&w->panes, wp, entry); - window_pane_resize(wp, w->sx, w->sy); + sc.name = NULL; + sc.argc = args->argc; + sc.argv = args->argv; - if (item->client != NULL && item->client->session == NULL) - envent = environ_find(item->client->environ, "PATH"); - else - envent = environ_find(s->environ, "PATH"); - if (envent != NULL) - path = envent->value; + sc.idx = -1; + sc.cwd = args_get(args, 'c'); - if ((cp = args_get(args, 'c')) != NULL) - cwd = format_single(item, cp, c, s, NULL, NULL); + sc.flags = SPAWN_RESPAWN; + if (args_has(args, 'k')) + sc.flags |= SPAWN_KILL; - env = environ_for_session(s, 0); - if (window_pane_spawn(wp, args->argc, args->argv, path, NULL, cwd, env, - s->tio, &cause) != 0) { + if (spawn_window(&sc, &cause) == NULL) { cmdq_error(item, "respawn window failed: %s", cause); free(cause); - environ_free(env); - free(cwd); - server_destroy_pane(wp, 0); return (CMD_RETURN_ERROR); } - environ_free(env); - free(cwd); - layout_init(w, wp); - window_pane_reset_mode_all(wp); - screen_reinit(&wp->base); - input_init(wp); - window_set_active_pane(w, wp); - - recalculate_sizes(); - server_redraw_window(w); + server_redraw_window(wl->window); return (CMD_RETURN_NORMAL); } diff --git a/cmd-rotate-window.c b/cmd-rotate-window.c index 5a900a13..6dc0f2a8 100644 --- a/cmd-rotate-window.c +++ b/cmd-rotate-window.c @@ -77,7 +77,7 @@ cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item) if ((wp = TAILQ_PREV(w->active, window_panes, entry)) == NULL) wp = TAILQ_LAST(&w->panes, window_panes); - window_set_active_pane(w, wp); + window_set_active_pane(w, wp, 1); cmd_find_from_winlink_pane(current, wl, wp, 0); server_redraw_window(w); } else { @@ -105,7 +105,7 @@ cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item) if ((wp = TAILQ_NEXT(w->active, entry)) == NULL) wp = TAILQ_FIRST(&w->panes); - window_set_active_pane(w, wp); + window_set_active_pane(w, wp, 1); cmd_find_from_winlink_pane(current, wl, wp, 0); server_redraw_window(w); } diff --git a/cmd-select-pane.c b/cmd-select-pane.c index 2873737f..af0f033b 100644 --- a/cmd-select-pane.c +++ b/cmd-select-pane.c @@ -112,7 +112,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) else { server_unzoom_window(w); window_redraw_active_switch(w, lastwp); - if (window_set_active_pane(w, lastwp)) { + if (window_set_active_pane(w, lastwp, 1)) { cmd_find_from_winlink(current, wl, 0); cmd_select_pane_redraw(w); } @@ -194,7 +194,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); server_unzoom_window(wp->window); window_redraw_active_switch(w, wp); - if (window_set_active_pane(w, wp)) { + if (window_set_active_pane(w, wp, 1)) { cmd_find_from_winlink_pane(current, wl, wp, 0); hooks_insert(s->hooks, item, current, "after-select-pane"); cmd_select_pane_redraw(w); diff --git a/cmd-split-window.c b/cmd-split-window.c index 28821413..f8cab9f9 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -53,111 +53,87 @@ const struct cmd_entry cmd_split_window_entry = { static enum cmd_retval cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) { - struct cmd_find_state *current = &item->shared->current; struct args *args = self->args; + struct cmd_find_state *current = &item->shared->current; + struct spawn_context sc; struct client *c = cmd_find_client(item, NULL, 1); struct session *s = item->target.s; struct winlink *wl = item->target.wl; - struct window *w = wl->window; - struct window_pane *wp = item->target.wp, *new_wp = NULL; - struct environ *env; - const char *cmd, *path, *shell, *template, *tmp; - char **argv, *cause, *new_cause, *cp, *cwd; - u_int hlimit; - int argc, size, percentage, before; + struct window_pane *wp = item->target.wp, *new_wp; enum layout_type type; struct layout_cell *lc; - struct environ_entry *envent; - struct cmd_find_state fs; + struct cmd_find_state fs; + int size, percentage, flags; + const char *template; + char *cause, *cp; - server_unzoom_window(w); - - if (args->argc == 0) { - cmd = options_get_string(s->options, "default-command"); - if (cmd != NULL && *cmd != '\0') { - argc = 1; - argv = (char **)&cmd; - } else { - argc = 0; - argv = NULL; - } - } else { - argc = args->argc; - argv = args->argv; - } - - if ((tmp = args_get(args, 'c')) != NULL) - cwd = format_single(item, tmp, c, s, NULL, NULL); - else - cwd = xstrdup(server_client_get_cwd(item->client, s)); - - type = LAYOUT_TOPBOTTOM; if (args_has(args, 'h')) type = LAYOUT_LEFTRIGHT; - before = args_has(args, 'b'); - - size = -1; + else + type = LAYOUT_TOPBOTTOM; if (args_has(args, 'l')) { size = args_strtonum(args, 'l', 0, INT_MAX, &cause); if (cause != NULL) { - xasprintf(&new_cause, "size %s", cause); + cmdq_error(item, "create pane failed: -l %s", cause); free(cause); - cause = new_cause; - goto error; + return (CMD_RETURN_ERROR); } } else if (args_has(args, 'p')) { percentage = args_strtonum(args, 'p', 0, INT_MAX, &cause); if (cause != NULL) { - xasprintf(&new_cause, "percentage %s", cause); + cmdq_error(item, "create pane failed: -p %s", cause); free(cause); - cause = new_cause; - goto error; + return (CMD_RETURN_ERROR); } if (type == LAYOUT_TOPBOTTOM) size = (wp->sy * percentage) / 100; else size = (wp->sx * percentage) / 100; - } - hlimit = options_get_number(s->options, "history-limit"); - - shell = options_get_string(s->options, "default-shell"); - if (*shell == '\0' || areshell(shell)) - shell = _PATH_BSHELL; - - lc = layout_split_pane(wp, type, size, before, args_has(args, 'f')); - if (lc == NULL) { - cause = xstrdup("pane too small"); - goto error; - } - new_wp = window_add_pane(w, wp, before, args_has(args, 'f'), hlimit); - layout_make_leaf(lc, new_wp); - - path = NULL; - if (item->client != NULL && item->client->session == NULL) - envent = environ_find(item->client->environ, "PATH"); - else - envent = environ_find(s->environ, "PATH"); - if (envent != NULL) - path = envent->value; - - env = environ_for_session(s, 0); - if (window_pane_spawn(new_wp, argc, argv, path, shell, cwd, env, - s->tio, &cause) != 0) { - environ_free(env); - goto error; - } - environ_free(env); - - layout_fix_panes(w); - server_redraw_window(w); - - if (!args_has(args, 'd')) { - window_set_active_pane(w, new_wp); - session_select(s, wl->idx); - cmd_find_from_session(current, s, 0); - server_redraw_session(s); } else - server_status_session(s); + size = -1; + + server_unzoom_window(wp->window); + + flags = 0; + if (args_has(args, 'b')) + flags |= SPAWN_BEFORE; + if (args_has(args, 'f')) + flags |= SPAWN_FULLSIZE; + + lc = layout_split_pane(wp, type, size, flags); + if (lc == NULL) { + cmdq_error(item, "no space for new pane"); + return (CMD_RETURN_ERROR); + } + + memset(&sc, 0, sizeof sc); + sc.item = item; + sc.s = s; + sc.wl = wl; + + sc.wp0 = wp; + sc.lc = lc; + + sc.name = NULL; + sc.argc = args->argc; + sc.argv = args->argv; + + sc.idx = -1; + sc.cwd = args_get(args, 'c'); + + sc.flags = flags; + if (args_has(args, 'd')) + sc.flags |= SPAWN_DETACHED; + + if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { + cmdq_error(item, "create pane failed: %s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + if (!args_has(args, 'd')) + cmd_find_from_winlink_pane(current, wl, new_wp, 0); + server_redraw_window(wp->window); + server_status_session(s); if (args_has(args, 'P')) { if ((template = args_get(args, 'F')) == NULL) @@ -166,22 +142,9 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) cmdq_print(item, "%s", cp); free(cp); } - notify_window("window-layout-changed", w); cmd_find_from_winlink_pane(&fs, wl, new_wp, 0); hooks_insert(s->hooks, item, &fs, "after-split-window"); - free(cwd); return (CMD_RETURN_NORMAL); - -error: - if (new_wp != NULL) { - layout_close_pane(new_wp); - window_remove_pane(w, new_wp); - } - cmdq_error(item, "create pane failed: %s", cause); - free(cause); - - free(cwd); - return (CMD_RETURN_ERROR); } diff --git a/cmd-swap-pane.c b/cmd-swap-pane.c index 1de272c4..2ad05561 100644 --- a/cmd-swap-pane.c +++ b/cmd-swap-pane.c @@ -101,17 +101,17 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item) if (!args_has(self->args, 'd')) { if (src_w != dst_w) { - window_set_active_pane(src_w, dst_wp); - window_set_active_pane(dst_w, src_wp); + window_set_active_pane(src_w, dst_wp, 1); + window_set_active_pane(dst_w, src_wp, 1); } else { tmp_wp = dst_wp; - window_set_active_pane(src_w, tmp_wp); + window_set_active_pane(src_w, tmp_wp, 1); } } else { if (src_w->active == src_wp) - window_set_active_pane(src_w, dst_wp); + window_set_active_pane(src_w, dst_wp, 1); if (dst_w->active == dst_wp) - window_set_active_pane(dst_w, src_wp); + window_set_active_pane(dst_w, src_wp, 1); } if (src_w != dst_w) { if (src_w->last == src_wp) diff --git a/cmd-switch-client.c b/cmd-switch-client.c index 3e19346e..a4f55102 100644 --- a/cmd-switch-client.c +++ b/cmd-switch-client.c @@ -116,7 +116,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmdq_item *item) server_unzoom_window(wl->window); if (wp != NULL) { window_redraw_active_switch(wp->window, wp); - window_set_active_pane(wp->window, wp); + window_set_active_pane(wp->window, wp, 1); } session_set_current(s, wl); cmd_find_from_session(&item->shared->current, s, 0); diff --git a/layout.c b/layout.c index 7093c607..1a43014d 100644 --- a/layout.c +++ b/layout.c @@ -831,11 +831,12 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc) */ struct layout_cell * layout_split_pane(struct window_pane *wp, enum layout_type type, int size, - int insert_before, int full_size) + int flags) { struct layout_cell *lc, *lcparent, *lcnew, *lc1, *lc2; u_int sx, sy, xoff, yoff, size1, size2; u_int new_size, saved_size, resize_first = 0; + int full_size = (flags & SPAWN_FULLSIZE); /* * If full_size is specified, add a new cell at the top of the window @@ -876,7 +877,7 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, saved_size = sy; if (size < 0) size2 = ((saved_size + 1) / 2) - 1; - else if (insert_before) + else if (flags & SPAWN_BEFORE) size2 = saved_size - size - 1; else size2 = size; @@ -887,7 +888,7 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, size1 = saved_size - 1 - size2; /* Which size are we using? */ - if (insert_before) + if (flags & SPAWN_BEFORE) new_size = size2; else new_size = size1; @@ -903,7 +904,7 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, */ lcparent = lc->parent; lcnew = layout_create_cell(lcparent); - if (insert_before) + if (flags & SPAWN_BEFORE) TAILQ_INSERT_BEFORE(lc, lcnew, entry); else TAILQ_INSERT_AFTER(&lcparent->cells, lc, lcnew, entry); @@ -932,7 +933,7 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, layout_set_size(lcnew, size, sy, 0, 0); else if (lc->type == LAYOUT_TOPBOTTOM) layout_set_size(lcnew, sx, size, 0, 0); - if (insert_before) + if (flags & SPAWN_BEFORE) TAILQ_INSERT_HEAD(&lc->cells, lcnew, entry); else TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry); @@ -956,12 +957,12 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size, /* Create the new child cell. */ lcnew = layout_create_cell(lcparent); - if (insert_before) + if (flags & SPAWN_BEFORE) TAILQ_INSERT_HEAD(&lcparent->cells, lcnew, entry); else TAILQ_INSERT_TAIL(&lcparent->cells, lcnew, entry); } - if (insert_before) { + if (flags & SPAWN_BEFORE) { lc1 = lcnew; lc2 = lc; } else { diff --git a/server-fn.c b/server-fn.c index f947d8ff..c4ff4aa9 100644 --- a/server-fn.c +++ b/server-fn.c @@ -368,7 +368,7 @@ server_destroy_session_group(struct session *s) else { TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) { server_destroy_session(s); - session_destroy(s, __func__); + session_destroy(s, 1, __func__); } } } @@ -435,7 +435,7 @@ server_check_unattached(void) if (s->attached != 0) continue; if (options_get_number (s->options, "destroy-unattached")) - session_destroy(s, __func__); + session_destroy(s, 1, __func__); } } diff --git a/server.c b/server.c index dd09c3c4..3c54f453 100644 --- a/server.c +++ b/server.c @@ -297,7 +297,7 @@ server_send_exit(void) } RB_FOREACH_SAFE(s, sessions, &sessions, s1) - session_destroy(s, __func__); + session_destroy(s, 1, __func__); } /* Update socket execute permissions based on whether sessions are attached. */ diff --git a/session.c b/session.c index 43ca321a..dddf18b0 100644 --- a/session.c +++ b/session.c @@ -112,12 +112,10 @@ session_find_by_id(u_int id) /* Create a new session. */ struct session * -session_create(const char *prefix, const char *name, int argc, char **argv, - const char *path, const char *cwd, struct environ *env, struct options *oo, - struct termios *tio, int idx, char **cause) +session_create(const char *prefix, const char *name, const char *cwd, + struct environ *env, struct options *oo, struct termios *tio) { struct session *s; - struct winlink *wl; s = xcalloc(1, sizeof *s); s->references = 1; @@ -129,10 +127,7 @@ session_create(const char *prefix, const char *name, int argc, char **argv, TAILQ_INIT(&s->lastw); RB_INIT(&s->windows); - s->environ = environ_create(); - if (env != NULL) - environ_copy(env, s->environ); - + s->environ = env; s->options = oo; s->hooks = hooks_create(global_hooks); @@ -166,17 +161,6 @@ session_create(const char *prefix, const char *name, int argc, char **argv, fatal("gettimeofday failed"); session_update_activity(s, &s->creation_time); - if (argc >= 0) { - wl = session_new(s, NULL, argc, argv, path, cwd, idx, cause); - if (wl == NULL) { - session_destroy(s, __func__); - return (NULL); - } - session_select(s, RB_ROOT(&s->windows)->idx); - } - - log_debug("session %s created", s->name); - return (s); } @@ -220,7 +204,7 @@ session_free(__unused int fd, __unused short events, void *arg) /* Destroy a session. */ void -session_destroy(struct session *s, const char *from) +session_destroy(struct session *s, int notify, const char *from) { struct winlink *wl; @@ -228,7 +212,8 @@ session_destroy(struct session *s, const char *from) s->curw = NULL; RB_REMOVE(sessions, &sessions, s); - notify_session("session-closed", s); + if (notify) + notify_session("session-closed", s); free(s->tio); @@ -338,45 +323,6 @@ session_previous_session(struct session *s) return (s2); } -/* Create a new window on a session. */ -struct winlink * -session_new(struct session *s, const char *name, int argc, char **argv, - const char *path, const char *cwd, int idx, char **cause) -{ - struct window *w; - struct winlink *wl; - struct environ *env; - const char *shell; - u_int hlimit, sx, sy; - - if ((wl = winlink_add(&s->windows, idx)) == NULL) { - xasprintf(cause, "index in use: %d", idx); - return (NULL); - } - wl->session = s; - - shell = options_get_string(s->options, "default-shell"); - if (*shell == '\0' || areshell(shell)) - shell = _PATH_BSHELL; - - default_window_size(s, NULL, &sx, &sy, -1); - hlimit = options_get_number(s->options, "history-limit"); - env = environ_for_session(s, 0); - w = window_create_spawn(name, argc, argv, path, shell, cwd, env, s->tio, - sx, sy, hlimit, cause); - if (w == NULL) { - winlink_remove(&s->windows, wl); - environ_free(env); - return (NULL); - } - winlink_set_window(wl, w); - environ_free(env); - notify_session_window("window-linked", s, w); - - session_group_synchronize_from(s); - return (wl); -} - /* Attach a window to a session. */ struct winlink * session_attach(struct session *s, struct window *w, int idx, char **cause) @@ -412,7 +358,7 @@ session_detach(struct session *s, struct winlink *wl) session_group_synchronize_from(s); if (RB_EMPTY(&s->windows)) { - session_destroy(s, __func__); + session_destroy(s, 1, __func__); return (1); } return (0); diff --git a/spawn.c b/spawn.c new file mode 100644 index 00000000..0565fee3 --- /dev/null +++ b/spawn.c @@ -0,0 +1,434 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 Nicholas Marriott + * + * 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 +#include +#include +#include +#include + +#include "tmux.h" + +/* + * Set up the environment and create a new window and pane or a new pane. + * + * We need to set up the following items: + * + * - history limit, comes from the session; + * + * - base index, comes from the session; + * + * - current working directory, may be specified - if it isn't it comes from + * either the client or the session; + * + * - PATH variable, comes from the client if any, otherwise from the session + * environment; + * + * - shell, comes from default-shell; + * + * - termios, comes from the session; + * + * - remaining environment, comes from the session. + */ + +static void +spawn_log(const char *from, struct spawn_context *sc) +{ + struct session *s = sc->s; + struct winlink *wl = sc->wl; + struct window_pane *wp0 = sc->wp0; + char tmp[128]; + const char *name; + + log_debug("%s: %s, flags=%#x", from, sc->item->name, sc->flags); + + if (wl != NULL && wp0 != NULL) + xsnprintf(tmp, sizeof tmp, "wl=%d wp0=%%%u", wl->idx, wp0->id); + else if (wl != NULL) + xsnprintf(tmp, sizeof tmp, "wl=%d wp0=none", wl->idx); + else if (wp0 != NULL) + xsnprintf(tmp, sizeof tmp, "wl=none wp0=%%%u", wp0->id); + else + xsnprintf(tmp, sizeof tmp, "wl=none wp0=none"); + log_debug("%s: s=$%u %s idx=%d", from, s->id, tmp, sc->idx); + + name = sc->name; + if (name == NULL) + name = "none"; + log_debug("%s: name=%s", from, name); +} + +struct winlink * +spawn_window(struct spawn_context *sc, char **cause) +{ + struct session *s = sc->s; + struct window *w; + struct window_pane *wp; + struct winlink *wl; + int idx = sc->idx; + u_int sx, sy; + + spawn_log(__func__, sc); + + /* + * If the window already exists, we are respawning, so destroy all the + * panes except one. + */ + if (sc->flags & SPAWN_RESPAWN) { + w = sc->wl->window; + if (~sc->flags & SPAWN_KILL) { + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->fd != -1) + break; + } + if (wp != NULL) { + xasprintf(cause, "window %s:%d still active", + s->name, sc->wl->idx); + return (NULL); + } + } + + sc->wp0 = TAILQ_FIRST(&w->panes); + TAILQ_REMOVE(&w->panes, sc->wp0, entry); + + layout_free(w); + window_destroy_panes(w); + + TAILQ_INSERT_HEAD(&w->panes, sc->wp0, entry); + window_pane_resize(sc->wp0, w->sx, w->sy); + + layout_init(w, sc->wp0); + window_set_active_pane(w, sc->wp0, 0); + } + + /* + * Otherwise we have no window so we will need to create one. First + * check if the given index already exists and destroy it if so. + */ + if ((~sc->flags & SPAWN_RESPAWN) && idx != -1) { + wl = winlink_find_by_index(&s->windows, idx); + if (wl != NULL && (~sc->flags & SPAWN_KILL)) { + xasprintf(cause, "index %d in use", idx); + return (NULL); + } + if (wl != NULL) { + /* + * Can't use session_detach as it will destroy session + * if this makes it empty. + */ + wl->flags &= ~WINLINK_ALERTFLAGS; + notify_session_window("window-unlinked", s, wl->window); + winlink_stack_remove(&s->lastw, wl); + winlink_remove(&s->windows, wl); + + if (s->curw == wl) { + s->curw = NULL; + sc->flags &= ~SPAWN_DETACHED; + } + } + } + + /* Then create a window if needed. */ + if (~sc->flags & SPAWN_RESPAWN) { + if (idx == -1) + idx = -1 - options_get_number(s->options, "base-index"); + if ((sc->wl = winlink_add(&s->windows, idx)) == NULL) { + xasprintf(cause, "couldn't add window %d", idx); + return (NULL); + } + default_window_size(s, NULL, &sx, &sy, -1); + if ((w = window_create(sx, sy)) == NULL) { + winlink_remove(&s->windows, sc->wl); + xasprintf(cause, "couldn't create window %d", idx); + return (NULL); + } + sc->wl->session = s; + winlink_set_window(sc->wl, w); + } else + w = NULL; + sc->flags |= SPAWN_NONOTIFY; + + /* Spawn the pane. */ + wp = spawn_pane(sc, cause); + if (wp == NULL) { + if (~sc->flags & SPAWN_RESPAWN) { + window_destroy(w); + winlink_remove(&s->windows, sc->wl); + } + return (NULL); + } + + /* Set the name of the new window. */ + if (~sc->flags & SPAWN_RESPAWN) { + if (sc->name != NULL) { + w->name = xstrdup(sc->name); + options_set_number(w->options, "automatic-rename", 0); + } else + w->name = xstrdup(default_window_name(w)); + } + + /* Switch to the new window if required. */ + if (~sc->flags & SPAWN_DETACHED) + session_select(s, sc->wl->idx); + + /* Fire notification if new window. */ + if (~sc->flags & SPAWN_RESPAWN) + notify_session_window("window-linked", s, w); + + session_group_synchronize_from(s); + return (sc->wl); +} + +struct window_pane * +spawn_pane(struct spawn_context *sc, char **cause) +{ + struct cmdq_item *item = sc->item; + struct client *c = item->client; + struct session *s = sc->s; + struct window *w = sc->wl->window; + struct window_pane *new_wp; + struct environ *child; + struct environ_entry *ee; + char **argv, *cp, **argvp, *argv0, *cwd; + const char *cmd, *tmp; + int argc; + u_int idx; + struct termios now; + u_int hlimit; + struct winsize ws; + sigset_t set, oldset; + + spawn_log(__func__, sc); + + /* + * If we are respawning then get rid of the old process. Otherwise + * either create a new cell or assign to the one we are given. + */ + hlimit = options_get_number(s->options, "history-limit"); + if (sc->flags & SPAWN_RESPAWN) { + if (sc->wp0->fd != -1 && (~sc->flags & SPAWN_KILL)) { + window_pane_index(sc->wp0, &idx); + xasprintf(cause, "pane %s:%d.%u still active", + s->name, sc->wl->idx, idx); + return (NULL); + } + if (sc->wp0->fd != -1) { + bufferevent_free(sc->wp0->event); + close(sc->wp0->fd); + } + window_pane_reset_mode_all(sc->wp0); + screen_reinit(&sc->wp0->base); + input_init(sc->wp0); + new_wp = sc->wp0; + new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN); + } else if (sc->lc == NULL) { + new_wp = window_add_pane(w, NULL, hlimit, sc->flags); + layout_init(w, new_wp); + } else { + new_wp = window_add_pane(w, sc->wp0, hlimit, sc->flags); + layout_assign_pane(sc->lc, new_wp); + } + + /* + * Now we have a pane with nothing running in it ready for the new + * process. Work out the command and arguments. + */ + if (sc->argc == 0) { + cmd = options_get_string(s->options, "default-command"); + if (cmd != NULL && *cmd != '\0') { + argc = 1; + argv = (char **)&cmd; + } else { + argc = 0; + argv = NULL; + } + } else { + argc = sc->argc; + argv = sc->argv; + } + + /* + * Replace the stored arguments if there are new ones. If not, the + * existing ones will be used (they will only exist for respawn). + */ + if (argc > 0) { + cmd_free_argv(new_wp->argc, new_wp->argv); + new_wp->argc = argc; + new_wp->argv = cmd_copy_argv(argc, argv); + } + + /* + * Work out the current working directory. If respawning, use + * the pane's stored one unless specified. + */ + if (sc->cwd != NULL) + cwd = format_single(item, sc->cwd, c, s, NULL, NULL); + else if (~sc->flags & SPAWN_RESPAWN) + cwd = xstrdup(server_client_get_cwd(c, s)); + else + cwd = NULL; + if (cwd != NULL) { + free(new_wp->cwd); + new_wp->cwd = cwd; + } + + /* Create an environment for this pane. */ + child = environ_for_session(s, 0); + environ_set(child, "TMUX_PANE", "%%%u", new_wp->id); + + /* + * Then the PATH environment variable. The session one is replaced from + * the client if there is one because otherwise running "tmux new + * myprogram" wouldn't work if myprogram isn't in the session's path. + */ + if (c != NULL && c->session == NULL) { /* only unattached clients */ + ee = environ_find(c->environ, "PATH"); + if (ee != NULL) + environ_set(child, "PATH", "%s", ee->value); + } + if (environ_find(child, "PATH") == NULL) + environ_set(child, "%s", _PATH_DEFPATH); + + /* Then the shell. If respawning, use the old one. */ + if (~sc->flags & SPAWN_RESPAWN) { + tmp = options_get_string(s->options, "default-shell"); + if (*tmp == '\0' || areshell(tmp)) + tmp = _PATH_BSHELL; + free(new_wp->shell); + new_wp->shell = xstrdup(tmp); + } + environ_set(child, "SHELL", "%s", new_wp->shell); + + /* Log the arguments we are going to use. */ + log_debug("%s: shell=%s", __func__, new_wp->shell); + if (new_wp->argc != 0) { + cp = cmd_stringify_argv(new_wp->argc, new_wp->argv); + log_debug("%s: cmd=%s", __func__, cp); + free(cp); + } + if (cwd != NULL) + log_debug("%s: cwd=%s", __func__, cwd); + cmd_log_argv(new_wp->argc, new_wp->argv, __func__); + environ_log(child, "%s: environment ", __func__); + + /* Initialize the window size. */ + memset(&ws, 0, sizeof ws); + ws.ws_col = screen_size_x(&new_wp->base); + ws.ws_row = screen_size_y(&new_wp->base); + + /* Block signals until fork has completed. */ + sigfillset(&set); + sigprocmask(SIG_BLOCK, &set, &oldset); + + /* Fork the new process. */ + new_wp->pid = fdforkpty(ptm_fd, &new_wp->fd, new_wp->tty, NULL, &ws); + if (new_wp->pid == -1) { + xasprintf(cause, "fork failed: %s", strerror(errno)); + new_wp->fd = -1; + if (~sc->flags & SPAWN_RESPAWN) { + layout_close_pane(new_wp); + window_remove_pane(w, new_wp); + } + sigprocmask(SIG_SETMASK, &oldset, NULL); + return (NULL); + } + + /* In the parent process, everything is done now. */ + if (new_wp->pid != 0) { + new_wp->pipe_off = 0; + new_wp->flags &= ~PANE_EXITED; + + sigprocmask(SIG_SETMASK, &oldset, NULL); + window_pane_set_event(new_wp); + + if (sc->flags & SPAWN_RESPAWN) + return (new_wp); + if ((~sc->flags & SPAWN_DETACHED) || w->active == NULL) { + if (sc->flags & SPAWN_NONOTIFY) + window_set_active_pane(w, new_wp, 0); + else + window_set_active_pane(w, new_wp, 1); + } + if (~sc->flags & SPAWN_NONOTIFY) + notify_window("window-layout-changed", w); + return (new_wp); + } + + /* + * Child process. Change to the working directory or home if that + * fails. + */ + if (chdir(new_wp->cwd) != 0) { + if ((tmp = find_home()) == NULL || chdir(tmp) != 0) + chdir("/"); + } + + /* + * Update terminal escape characters from the session if available and + * force VERASE to tmux's \177. + */ + if (tcgetattr(STDIN_FILENO, &now) != 0) + _exit(1); + if (s->tio != NULL) + memcpy(now.c_cc, s->tio->c_cc, sizeof now.c_cc); + now.c_cc[VERASE] = '\177'; + if (tcsetattr(STDIN_FILENO, TCSANOW, &now) != 0) + _exit(1); + + /* Clean up file descriptors and signals and update the environment. */ + closefrom(STDERR_FILENO + 1); + proc_clear_signals(server_proc, 1); + sigprocmask(SIG_SETMASK, &oldset, NULL); + log_close(); + environ_push(child); + + /* + * If given multiple arguments, use execvp(). Copy the arguments to + * ensure they end in a NULL. + */ + if (new_wp->argc != 0 && new_wp->argc != 1) { + argvp = cmd_copy_argv(new_wp->argc, new_wp->argv); + execvp(argvp[0], argvp); + _exit(1); + } + + /* + * If one argument, pass it to $SHELL -c. Otherwise create a login + * shell. + */ + cp = strrchr(new_wp->shell, '/'); + if (new_wp->argc == 1) { + tmp = new_wp->argv[0]; + if (cp != NULL && cp[1] != '\0') + xasprintf(&argv0, "%s", cp + 1); + else + xasprintf(&argv0, "%s", new_wp->shell); + execl(new_wp->shell, argv0, "-c", tmp, (char *)NULL); + _exit(1); + } + if (cp != NULL && cp[1] != '\0') + xasprintf(&argv0, "-%s", cp + 1); + else + xasprintf(&argv0, "-%s", new_wp->shell); + execl(new_wp->shell, argv0, (char *)NULL); + _exit(1); +} diff --git a/tmux.h b/tmux.h index 31fbae32..fc85344d 100644 --- a/tmux.h +++ b/tmux.h @@ -233,8 +233,8 @@ enum { /* Termcap codes. */ enum tty_code_code { - TTYC_AX = 0, TTYC_ACSC, + TTYC_AX, TTYC_BCE, TTYC_BEL, TTYC_BLINK, @@ -814,7 +814,7 @@ struct window_pane { int argc; char **argv; char *shell; - const char *cwd; + char *cwd; pid_t pid; char tty[TTY_NAME_MAX]; @@ -1561,6 +1561,32 @@ struct options_table_entry { #define CMD_SRCDST_CLIENT_USAGE "[-s src-client] [-t dst-client]" #define CMD_BUFFER_USAGE "[-b buffer-name]" +/* Spawn common context. */ +struct spawn_context { + struct cmdq_item *item; + + struct session *s; + struct winlink *wl; + + struct window_pane *wp0; + struct layout_cell *lc; + + const char *name; + char **argv; + int argc; + + int idx; + const char *cwd; + + int flags; +#define SPAWN_KILL 0x1 +#define SPAWN_DETACHED 0x2 +#define SPAWN_RESPAWN 0x4 +#define SPAWN_BEFORE 0x8 +#define SPAWN_NONOTIFY 0x10 +#define SPAWN_FULLSIZE 0x20 +}; + /* tmux.c */ extern struct hooks *global_hooks; extern struct options *global_options; @@ -2232,17 +2258,17 @@ struct window *window_find_by_id_str(const char *); struct window *window_find_by_id(u_int); void window_update_activity(struct window *); struct window *window_create(u_int, u_int); -struct window *window_create_spawn(const char *, int, char **, const char *, - const char *, const char *, struct environ *, - struct termios *, u_int, u_int, u_int, char **); +void window_destroy(struct window *); +void window_pane_set_event(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_pane(struct window *, struct window_pane *); -int window_set_active_pane(struct window *, struct window_pane *); +int window_set_active_pane(struct window *, struct window_pane *, + int); void window_redraw_active_switch(struct window *, struct window_pane *); -struct window_pane *window_add_pane(struct window *, struct window_pane *, int, - int, u_int); +struct window_pane *window_add_pane(struct window *, struct window_pane *, + u_int, int); void window_resize(struct window *, u_int, u_int); int window_zoom(struct window_pane *); int window_unzoom(struct window *); @@ -2259,9 +2285,6 @@ void window_destroy_panes(struct window *); struct window_pane *window_pane_find_by_id_str(const char *); struct window_pane *window_pane_find_by_id(u_int); int window_pane_destroy_ready(struct window_pane *); -int window_pane_spawn(struct window_pane *, int, char **, - const char *, const char *, const char *, struct environ *, - struct termios *, char **); void window_pane_resize(struct window_pane *, u_int, u_int); void window_pane_alternate_on(struct window_pane *, struct grid_cell *, int); @@ -2319,7 +2342,7 @@ void layout_resize_pane_to(struct window_pane *, enum layout_type, u_int); void layout_assign_pane(struct layout_cell *, struct window_pane *); struct layout_cell *layout_split_pane(struct window_pane *, enum layout_type, - int, int, int); + int, int); void layout_close_pane(struct window_pane *); int layout_spread_cell(struct window *, struct layout_cell *); void layout_spread_out(struct window_pane *); @@ -2418,10 +2441,9 @@ int session_alive(struct session *); struct session *session_find(const char *); struct session *session_find_by_id_str(const char *); struct session *session_find_by_id(u_int); -struct session *session_create(const char *, const char *, int, char **, - const char *, const char *, struct environ *, - struct options *, struct termios *, int, char **); -void session_destroy(struct session *, const char *); +struct session *session_create(const char *, const char *, const char *, + struct environ *, struct options *, struct termios *); +void session_destroy(struct session *, int, const char *); void session_add_ref(struct session *, const char *); void session_remove_ref(struct session *, const char *); int session_check_name(const char *); @@ -2493,4 +2515,8 @@ void style_set(struct style *, const struct grid_cell *); void style_copy(struct style *, struct style *); int style_is_default(struct style *); +/* spawn.c */ +struct winlink *spawn_window(struct spawn_context *, char **); +struct window_pane *spawn_pane(struct spawn_context *, char **); + #endif /* TMUX_H */ diff --git a/window-tree.c b/window-tree.c index 120d22c8..fe0e5c14 100644 --- a/window-tree.c +++ b/window-tree.c @@ -1006,7 +1006,7 @@ window_tree_kill_each(__unused void* modedata, void* itemdata, case WINDOW_TREE_SESSION: if (s != NULL) { server_destroy_session(s); - session_destroy(s, __func__); + session_destroy(s, 1, __func__); } break; case WINDOW_TREE_WINDOW: diff --git a/window.c b/window.c index 3ec5d504..10571f99 100644 --- a/window.c +++ b/window.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -73,20 +72,10 @@ const struct window_mode *all_window_modes[] = { NULL }; -static void window_destroy(struct window *); - static struct window_pane *window_pane_create(struct window *, u_int, u_int, u_int); static void window_pane_destroy(struct window_pane *); -static void window_pane_read_callback(struct bufferevent *, void *); -static void window_pane_error_callback(struct bufferevent *, short, void *); - -static int winlink_next_index(struct winlinks *, int); - -static struct window_pane *window_pane_choose_best(struct window_pane **, - u_int); - RB_GENERATE(windows, window, entry, window_cmp); RB_GENERATE(winlinks, winlink, entry, winlink_cmp); RB_GENERATE(window_pane_tree, window_pane, tree_entry, window_pane_cmp); @@ -342,37 +331,7 @@ window_create(u_int sx, u_int sy) return (w); } -struct window * -window_create_spawn(const char *name, int argc, char **argv, const char *path, - const char *shell, const char *cwd, struct environ *env, - struct termios *tio, u_int sx, u_int sy, u_int hlimit, char **cause) -{ - struct window *w; - struct window_pane *wp; - - w = window_create(sx, sy); - wp = window_add_pane(w, NULL, 0, 0, hlimit); - layout_init(w, wp); - - if (window_pane_spawn(wp, argc, argv, path, shell, cwd, - env, tio, cause) != 0) { - window_destroy(w); - return (NULL); - } - - w->active = TAILQ_FIRST(&w->panes); - if (name != NULL) { - w->name = xstrdup(name); - options_set_number(w->options, "automatic-rename", 0); - } else - w->name = default_window_name(w); - - notify_window("window-pane-changed", w); - - return (w); -} - -static void +void window_destroy(struct window *w) { log_debug("window @%u destroyed (%d references)", w->id, w->references); @@ -463,17 +422,22 @@ window_has_pane(struct window *w, struct window_pane *wp) } int -window_set_active_pane(struct window *w, struct window_pane *wp) +window_set_active_pane(struct window *w, struct window_pane *wp, int notify) { - log_debug("%s: pane %%%u (was %%%u)", __func__, wp->id, w->active->id); + log_debug("%s: pane %%%u", __func__, wp->id); + if (wp == w->active) return (0); w->last = w->active; + w->active = wp; w->active->active_point = next_active_point++; w->active->flags |= PANE_CHANGED; + tty_update_window_offset(w); - notify_window("window-pane-changed", w); + + if (notify) + notify_window("window-pane-changed", w); return (1); } @@ -571,7 +535,7 @@ window_zoom(struct window_pane *wp) return (-1); if (w->active != wp) - window_set_active_pane(w, wp); + window_set_active_pane(w, wp, 1); TAILQ_FOREACH(wp1, &w->panes, entry) { wp1->saved_layout_cell = wp1->layout_cell; @@ -610,8 +574,8 @@ window_unzoom(struct window *w) } struct window_pane * -window_add_pane(struct window *w, struct window_pane *other, int before, - int full_size, u_int hlimit) +window_add_pane(struct window *w, struct window_pane *other, u_int hlimit, + int flags) { struct window_pane *wp; @@ -622,15 +586,15 @@ window_add_pane(struct window *w, struct window_pane *other, int before, if (TAILQ_EMPTY(&w->panes)) { log_debug("%s: @%u at start", __func__, w->id); TAILQ_INSERT_HEAD(&w->panes, wp, entry); - } else if (before) { + } else if (flags & SPAWN_BEFORE) { log_debug("%s: @%u before %%%u", __func__, w->id, wp->id); - if (full_size) + if (flags & SPAWN_FULLSIZE) TAILQ_INSERT_HEAD(&w->panes, wp, entry); else TAILQ_INSERT_BEFORE(other, wp, entry); } else { log_debug("%s: @%u after %%%u", __func__, w->id, wp->id); - if (full_size) + if (flags & SPAWN_FULLSIZE) TAILQ_INSERT_TAIL(&w->panes, wp, entry); else TAILQ_INSERT_AFTER(&w->panes, other, wp, entry); @@ -887,141 +851,6 @@ window_pane_destroy(struct window_pane *wp) free(wp); } -int -window_pane_spawn(struct window_pane *wp, int argc, char **argv, - const char *path, const char *shell, const char *cwd, struct environ *env, - struct termios *tio, char **cause) -{ - struct winsize ws; - char *argv0, *cmd, **argvp; - const char *ptr, *first, *home; - struct termios tio2; - sigset_t set, oldset; - - if (wp->fd != -1) { - bufferevent_free(wp->event); - close(wp->fd); - } - if (argc > 0) { - cmd_free_argv(wp->argc, wp->argv); - wp->argc = argc; - wp->argv = cmd_copy_argv(argc, argv); - } - if (shell != NULL) { - free(wp->shell); - wp->shell = xstrdup(shell); - } - if (cwd != NULL) { - free((void *)wp->cwd); - wp->cwd = xstrdup(cwd); - } - wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN); - - cmd = cmd_stringify_argv(wp->argc, wp->argv); - log_debug("%s: shell=%s", __func__, wp->shell); - log_debug("%s: cmd=%s", __func__, cmd); - log_debug("%s: cwd=%s", __func__, cwd); - cmd_log_argv(wp->argc, wp->argv, __func__); - environ_log(env, "%s: environment ", __func__); - - memset(&ws, 0, sizeof ws); - ws.ws_col = screen_size_x(&wp->base); - ws.ws_row = screen_size_y(&wp->base); - - sigfillset(&set); - sigprocmask(SIG_BLOCK, &set, &oldset); - switch (wp->pid = fdforkpty(ptm_fd, &wp->fd, wp->tty, NULL, &ws)) { - case -1: - wp->event = NULL; - wp->fd = -1; - - xasprintf(cause, "%s: %s", cmd, strerror(errno)); - free(cmd); - - sigprocmask(SIG_SETMASK, &oldset, NULL); - return (-1); - case 0: - proc_clear_signals(server_proc, 1); - sigprocmask(SIG_SETMASK, &oldset, NULL); - - cwd = NULL; - if (chdir(wp->cwd) == 0) - cwd = wp->cwd; - else if ((home = find_home()) != NULL && chdir(home) == 0) - cwd = home; - else - chdir("/"); - - if (tcgetattr(STDIN_FILENO, &tio2) != 0) - fatal("tcgetattr failed"); - if (tio != NULL) - memcpy(tio2.c_cc, tio->c_cc, sizeof tio2.c_cc); - tio2.c_cc[VERASE] = '\177'; - if (tcsetattr(STDIN_FILENO, TCSANOW, &tio2) != 0) - fatal("tcgetattr failed"); - - log_close(); - closefrom(STDERR_FILENO + 1); - - if (path != NULL) - environ_set(env, "PATH", "%s", path); - if (cwd != NULL) - environ_set(env, "PWD", "%s", cwd); - environ_set(env, "TMUX_PANE", "%%%u", wp->id); - environ_push(env); - - setenv("SHELL", wp->shell, 1); - ptr = strrchr(wp->shell, '/'); - - /* - * If given one argument, assume it should be passed to sh -c; - * with more than one argument, use execvp(). If there is no - * arguments, create a login shell. - */ - if (wp->argc > 0) { - if (wp->argc != 1) { - /* Copy to ensure argv ends in NULL. */ - argvp = cmd_copy_argv(wp->argc, wp->argv); - execvp(argvp[0], argvp); - fatal("execvp failed"); - } - first = wp->argv[0]; - - if (ptr != NULL && *(ptr + 1) != '\0') - xasprintf(&argv0, "%s", ptr + 1); - else - xasprintf(&argv0, "%s", wp->shell); - execl(wp->shell, argv0, "-c", first, (char *)NULL); - fatal("execl failed"); - } - if (ptr != NULL && *(ptr + 1) != '\0') - xasprintf(&argv0, "-%s", ptr + 1); - else - xasprintf(&argv0, "-%s", wp->shell); - execl(wp->shell, argv0, (char *)NULL); - fatal("execl failed"); - } - log_debug("%s: master=%s", __func__, ttyname(wp->fd)); - log_debug("%s: slave=%s", __func__, wp->tty); - - sigprocmask(SIG_SETMASK, &oldset, NULL); - setblocking(wp->fd, 0); - - wp->event = bufferevent_new(wp->fd, window_pane_read_callback, NULL, - window_pane_error_callback, wp); - if (wp->event == NULL) - fatalx("out of memory"); - - wp->pipe_off = 0; - wp->flags &= ~PANE_EXITED; - - bufferevent_setwatermark(wp->event, EV_READ, 0, READ_SIZE); - bufferevent_enable(wp->event, EV_READ|EV_WRITE); - - free(cmd); - return (0); -} - static void window_pane_read_callback(__unused struct bufferevent *bufev, void *data) { @@ -1056,6 +885,18 @@ window_pane_error_callback(__unused struct bufferevent *bufev, server_destroy_pane(wp, 1); } +void +window_pane_set_event(struct window_pane *wp) +{ + setblocking(wp->fd, 0); + + wp->event = bufferevent_new(wp->fd, window_pane_read_callback, + NULL, window_pane_error_callback, wp); + + bufferevent_setwatermark(wp->event, EV_READ, 0, READ_SIZE); + bufferevent_enable(wp->event, EV_READ|EV_WRITE); +} + void window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) { @@ -1604,6 +1445,8 @@ winlink_shuffle_up(struct session *s, struct winlink *wl) { int idx, last; + if (wl == NULL) + return (-1); idx = wl->idx + 1; /* Find the next free index. */