From f58b8d0d6abb2477b584547a4e72cc362ecbbcdb Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Dec 2025 08:04:35 +0000 Subject: [PATCH 1/5] Setting working directory after fork means there is a race with pane_current_path (especially on platforms with systemd which have to take time to do some additional faffing around). To avoid this, change it before fork and back in the parent afterwards. GitHub issue 4719. --- spawn.c | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/spawn.c b/spawn.c index d06d3969..3b5fae26 100644 --- a/spawn.c +++ b/spawn.c @@ -213,7 +213,9 @@ spawn_pane(struct spawn_context *sc, char **cause) struct environ *child; struct environ_entry *ee; char **argv, *cp, **argvp, *argv0, *cwd, *new_cwd; - const char *cmd, *tmp; + char path[PATH_MAX]; + const char *cmd, *tmp, *home = find_home(); + const char *actual_cwd = NULL; int argc; u_int idx; struct termios now; @@ -368,6 +370,16 @@ spawn_pane(struct spawn_context *sc, char **cause) goto complete; } + /* Store current working directory and change to new one. */ + if (getcwd(path, sizeof path) != NULL) { + if (chdir(new_wp->cwd) == 0) + actual_cwd = new_wp->cwd; + else if (home != NULL && chdir(home) == 0) + actual_cwd = home; + else if (chdir("/") == 0) + actual_cwd = "/"; + } + /* Fork the new process. */ new_wp->pid = fdforkpty(ptm_fd, &new_wp->fd, new_wp->tty, NULL, &ws); if (new_wp->pid == -1) { @@ -383,22 +395,23 @@ spawn_pane(struct spawn_context *sc, char **cause) return (NULL); } - /* In the parent process, everything is done now. */ - if (new_wp->pid != 0) + /* + * In the parent process, everything is done now. Change the working + * directory back. + */ + if (new_wp->pid != 0) { + if (actual_cwd != NULL && + chdir(path) != 0 && + (home == NULL || chdir(home) != 0)) + chdir("/"); goto complete; + } /* - * Child process. Change to the working directory or home if that - * fails. + * Child process. Set PWD to the working directory. */ - if (chdir(new_wp->cwd) == 0) - environ_set(child, "PWD", 0, "%s", new_wp->cwd); - else if ((tmp = find_home()) != NULL && chdir(tmp) == 0) - environ_set(child, "PWD", 0, "%s", tmp); - else if (chdir("/") == 0) - environ_set(child, "PWD", 0, "/"); - else - fatal("chdir failed"); + if (actual_cwd != NULL) + environ_set(child, "PWD", 0, "%s", actual_cwd); /* * Update terminal escape characters from the session if available and From afa05ae15ed7129690e42a6f207b6488ff86bee9 Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 8 Dec 2025 21:32:50 +0000 Subject: [PATCH 2/5] Use correct style for bottom line when pane status line is on, GitHub issue 4732. --- screen-redraw.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/screen-redraw.c b/screen-redraw.c index db518708..0dda2fea 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -197,9 +197,11 @@ screen_redraw_pane_border(struct screen_redraw_ctx *ctx, struct window_pane *wp, } else { /* sb_pos == PANE_SCROLLBARS_RIGHT */ if ((wp->xoff == 0 || px >= wp->xoff) && (px <= ex || (sb_w != 0 && px < ex + sb_w))) { - if (wp->yoff != 0 && py == wp->yoff - 1) + if (pane_status != PANE_STATUS_BOTTOM && + wp->yoff != 0 && + py == wp->yoff - 1) return (SCREEN_REDRAW_BORDER_TOP); - if (py == ey) + if (pane_status != PANE_STATUS_TOP && py == ey) return (SCREEN_REDRAW_BORDER_BOTTOM); } } @@ -380,7 +382,6 @@ screen_redraw_check_cell(struct screen_redraw_ctx *ctx, u_int px, u_int py, /* Check if CELL_SCROLLBAR */ if (window_pane_show_scrollbar(wp, pane_scrollbars)) { - if (pane_status == PANE_STATUS_TOP) line = wp->yoff - 1; else From 52e2a7d990b8721e8ac2deba65f259b454f8d192 Mon Sep 17 00:00:00 2001 From: nicm Date: Tue, 9 Dec 2025 08:13:59 +0000 Subject: [PATCH 3/5] Fix key code for M-BSpace, GitHub issue 4717. --- tty-keys.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tty-keys.c b/tty-keys.c index 7474621c..c59e6a68 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -909,9 +909,15 @@ first_key: * used. termios should have a better idea. */ bspace = tty->tio.c_cc[VERASE]; - if (bspace != _POSIX_VDISABLE && key == bspace) { - log_debug("%s: key %#llx is backspace", c->name, key); - key = KEYC_BSPACE; + if (bspace != _POSIX_VDISABLE) { + if (key == bspace) { + log_debug("%s: key %#llx is BSpace", c->name, key); + key = KEYC_BSPACE; + } + if (key == (bspace|KEYC_META)) { + log_debug("%s: key %#llx is M-BSpace", c->name, key); + key = KEYC_BSPACE|KEYC_META; + } } /* @@ -1304,7 +1310,7 @@ tty_keys_clipboard(struct tty *tty, const char *buf, size_t len, size_t *size) struct client *c = tty->client; size_t end, terminator = 0, needed; char *copy, *out; - int outlen; + int outlen; struct input_request_clipboard_data cd; *size = 0; From 672e89a6407e248bfc0098bbdbea5d6d7dd09401 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 10 Dec 2025 21:24:43 +0000 Subject: [PATCH 4/5] Add a scroll-to-mouse command for copy mode to scroll to the mouse position and bind to the scrollbar, brings the scrollbar keys into line with the other mouse keys. From Michael Grant, GitHub issue 4731. --- key-bindings.c | 6 +++--- tmux.1 | 15 ++++++++++----- window-copy.c | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/key-bindings.c b/key-bindings.c index a5e1f9c8..22e5cf59 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -480,9 +480,9 @@ key_bindings_init(void) "bind -n M-MouseDown3Pane { display-menu -t= -xM -yM -T '#[align=centre]#{pane_index} (#{pane_id})' " DEFAULT_PANE_MENU " }", /* Mouse on scrollbar. */ - "bind -n MouseDown1ScrollbarUp { copy-mode -u }", - "bind -n MouseDown1ScrollbarDown { copy-mode -d }", - "bind -n MouseDrag1ScrollbarSlider { copy-mode -S }", + "bind -n MouseDown1ScrollbarUp { if -Ft= '#{pane_in_mode}' { send -X page-up } {copy-mode -u } }", + "bind -n MouseDown1ScrollbarDown { if -Ft= '#{pane_in_mode}' { send -X page-down } {copy-mode -d } }", + "bind -n MouseDrag1ScrollbarSlider { if -Ft= '#{pane_in_mode}' { send -X scroll-to-mouse } { copy-mode -S } }", /* Copy mode (emacs) keys. */ "bind -Tcopy-mode C-Space { send -X begin-selection }", diff --git a/tmux.1 b/tmux.1 index dac3a648..dc1bfc00 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2211,6 +2211,13 @@ but also exit copy mode if the cursor reaches the bottom. Scroll so that the current line becomes the middle one while keeping the cursor on that line. .It Xo +.Ic scroll-to-mouse +.Xc +Scroll pane in copy-mode when bound to a mouse drag event. +.Fl e +causes copy mode to exit when at the bottom. +.Pp +.It Xo .Ic scroll-top .Xc Scroll down until the current line is at the top while keeping the cursor on @@ -2447,12 +2454,10 @@ cancels copy mode and any other modes. .Fl M begins a mouse drag (only valid if bound to a mouse key binding, see .Sx MOUSE SUPPORT ) . +.Pp .Fl S -scrolls when bound to a mouse drag event; for example, -.Ic copy-mode -Se -is bound to -.Ar MouseDrag1ScrollbarSlider -by default. +enters copy-mode and scrolls when bound to a mouse drag event; See +.Ic scroll-to-mouse . .Pp .Fl s copies from diff --git a/window-copy.c b/window-copy.c index 2d528496..f8700555 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1479,6 +1479,20 @@ window_copy_cmd_scroll_middle(struct window_copy_cmd_state *cs) return (window_copy_cmd_scroll_to(cs, mid_value)); } +/* Scroll the pane to the mouse in the scrollbar. */ +static enum window_copy_cmd_action +window_copy_cmd_scroll_to_mouse(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_pane *wp = wme->wp; + struct client *c = cs->c; + struct mouse_event *m = cs->m; + int scroll_exit = args_has(cs->wargs, 'e'); + + window_copy_scroll(wp, c->tty.mouse_slider_mpos, m->y, scroll_exit); + return (WINDOW_COPY_CMD_NOTHING); +} + /* Scroll line containing the cursor to the top. */ static enum window_copy_cmd_action window_copy_cmd_scroll_top(struct window_copy_cmd_state *cs) @@ -3044,6 +3058,11 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_scroll_middle }, + { .command = "scroll-to-mouse", + .args = { "e", 0, 0, NULL }, + .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, + .f = window_copy_cmd_scroll_to_mouse + }, { .command = "scroll-top", .args = { "", 0, 0, NULL }, .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, From 21c27fdcaea6a8c679c557e38ec09b865f01bd62 Mon Sep 17 00:00:00 2001 From: bket Date: Thu, 11 Dec 2025 04:17:17 +0000 Subject: [PATCH 5/5] Simplify argument move using TAILQ_CONCAT() Replace the manual loop moving each argument from cmd->arguments to last->arguments with a single TAILQ_CONCAT() call. This makes the code clearer and more efficient, while preserving identical behavior. OK nicm@ --- cmd-parse.y | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cmd-parse.y b/cmd-parse.y index 4cd8a420..215943b1 100644 --- a/cmd-parse.y +++ b/cmd-parse.y @@ -758,7 +758,7 @@ static int cmd_parse_expand_alias(struct cmd_parse_command *cmd, struct cmd_parse_input *pi, struct cmd_parse_result *pr) { - struct cmd_parse_argument *arg, *arg1, *first; + struct cmd_parse_argument *first; struct cmd_parse_commands *cmds; struct cmd_parse_command *last; char *alias, *name, *cause; @@ -798,10 +798,7 @@ cmd_parse_expand_alias(struct cmd_parse_command *cmd, TAILQ_REMOVE(&cmd->arguments, first, entry); cmd_parse_free_argument(first); - TAILQ_FOREACH_SAFE(arg, &cmd->arguments, entry, arg1) { - TAILQ_REMOVE(&cmd->arguments, arg, entry); - TAILQ_INSERT_TAIL(&last->arguments, arg, entry); - } + TAILQ_CONCAT(&last->arguments, &cmd->arguments, entry); cmd_parse_log_commands(cmds, __func__); pi->flags |= CMD_PARSE_NOALIAS;