From eafd2bed5d29eca91c7ced6e38f4e5c2baafecf5 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 17 Jun 2026 07:43:55 +0000 Subject: [PATCH 1/4] FIx a couple of minor errors in use of layout_cell_is_tiled. --- layout.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/layout.c b/layout.c index 1eb36477..cc9f2351 100644 --- a/layout.c +++ b/layout.c @@ -635,7 +635,7 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc, lc->parent = lcparent->parent; if (lc->parent == NULL) { - if (!layout_cell_is_tiled(lc)) { + if (layout_cell_is_tiled(lc)) { lc->xoff = 0; lc->yoff = 0; } @@ -1062,8 +1062,8 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc) count = 0; previous = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (!layout_cell_is_tiled(lc) && - !layout_cell_has_tiled_child(lc)) + if (!layout_cell_is_tiled(lcchild) && + !layout_cell_has_tiled_child(lcchild)) continue; count++; if (lc->type == LAYOUT_LEFTRIGHT) @@ -1083,8 +1083,8 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc) /* Resize children into the new size. */ idx = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (!layout_cell_is_tiled(lc) && - !layout_cell_has_tiled_child(lc)) + if (!layout_cell_is_tiled(lcchild) && + !layout_cell_has_tiled_child(lcchild)) continue; if (lc->type == LAYOUT_TOPBOTTOM) { lcchild->sx = lc->sx; From d4defd943af173046483275caeff967f336a7b54 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 17 Jun 2026 07:52:21 +0000 Subject: [PATCH 2/4] Add -M to move-pane and default bindings for M-drag. From Michael Grant. --- cmd-join-pane.c | 74 +++++++++++++++++++++++++++++++++++++++++++++-- cmd-resize-pane.c | 26 +++++++++++------ key-bindings.c | 2 ++ tmux.1 | 6 +++- 4 files changed, 96 insertions(+), 12 deletions(-) diff --git a/cmd-join-pane.c b/cmd-join-pane.c index 30f491f6..23ee30aa 100644 --- a/cmd-join-pane.c +++ b/cmd-join-pane.c @@ -31,6 +31,9 @@ */ static enum cmd_retval cmd_join_pane_exec(struct cmd *, struct cmdq_item *); +static enum cmd_retval cmd_join_pane_mouse_update(struct cmdq_item *); +static void cmd_join_pane_mouse_move(struct client *, + struct mouse_event *); const struct cmd_entry cmd_join_pane_entry = { .name = "join-pane", @@ -50,8 +53,8 @@ const struct cmd_entry cmd_move_pane_entry = { .name = "move-pane", .alias = "movep", - .args = { "bdfhvl:L::P:R::s:t:U::X:Y:z:", 0, 0, NULL }, - .usage = "[-bdfhv] [-D lines] [-l size] [-L columns] [-P position] " + .args = { "bdfhMvl:L::P:R::s:t:U::X:Y:z:", 0, 0, NULL }, + .usage = "[-bdfhMv] [-D lines] [-l size] [-L columns] [-P position] " "[-R columns] " CMD_SRCDST_PANE_USAGE " [-U lines] " "[-X x-position] [-Y y-position] [-z z-index]", @@ -255,6 +258,71 @@ cmd_join_pane_move(struct cmdq_item *item, struct args *args, return (CMD_RETURN_NORMAL); } +static enum cmd_retval +cmd_join_pane_mouse_update(struct cmdq_item *item) +{ + struct cmd_find_state *target = cmdq_get_target(item); + struct key_event *event = cmdq_get_event(item); + struct client *c = cmdq_get_client(item); + struct session *s = target->s; + struct winlink *wl; + struct window *w; + struct window_pane *wp; + + if (!event->m.valid) + return (CMD_RETURN_NORMAL); + wp = cmd_mouse_pane(&event->m, &s, &wl); + if (wp == NULL || c == NULL || c->session != s) + return (CMD_RETURN_NORMAL); + if (!window_pane_is_floating(wp)) + return (CMD_RETURN_NORMAL); + + w = wl->window; + window_redraw_active_switch(w, wp); + window_set_active_pane(w, wp, 1); + + c->tty.mouse_drag_update = cmd_join_pane_mouse_move; + cmd_join_pane_mouse_move(c, &event->m); + return (CMD_RETURN_NORMAL); +} + +static void +cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m) +{ + struct winlink *wl; + struct window *w; + struct window_pane *wp; + struct layout_cell *lc; + int y, ly, x, lx; + + wp = cmd_mouse_pane(m, NULL, &wl); + if (wp == NULL) { + c->tty.mouse_drag_update = NULL; + return; + } + w = wl->window; + lc = wp->layout_cell; + + y = m->y + m->oy; x = m->x + m->ox; + if (m->statusat == 0 && y >= (int)m->statuslines) + y -= m->statuslines; + else if (m->statusat > 0 && y >= m->statusat) + y = m->statusat - 1; + ly = m->ly + m->oy; lx = m->lx + m->ox; + if (m->statusat == 0 && ly >= (int)m->statuslines) + ly -= m->statuslines; + else if (m->statusat > 0 && ly >= m->statusat) + ly = m->statusat - 1; + + if (x != lx || y != ly) { + lc->xoff += x - lx; + lc->yoff += y - ly; + layout_fix_panes(w, NULL); + server_redraw_window(w); + server_redraw_window_borders(w); + } +} + static enum cmd_retval cmd_join_pane_zindex(struct cmdq_item *item, struct winlink *wl, struct window_pane *wp, const char *s) @@ -315,6 +383,8 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item) server_unzoom_window(dst_w); if (cmd_get_entry(self) == &cmd_move_pane_entry) { + if (args_has(args, 'M')) + return (cmd_join_pane_mouse_update(item)); if (!window_pane_is_floating(dst_wp)) { cmdq_error(item, "pane is not floating"); return (CMD_RETURN_ERROR); diff --git a/cmd-resize-pane.c b/cmd-resize-pane.c index 5ea3b03b..bc91ab2b 100644 --- a/cmd-resize-pane.c +++ b/cmd-resize-pane.c @@ -31,9 +31,9 @@ static enum cmd_retval cmd_resize_pane_exec(struct cmd *, struct cmdq_item *); static enum cmd_retval cmd_resize_pane_mouse_update(struct cmd *, struct cmdq_item *); -static void cmd_resize_pane_mouse_update_floating(struct client *, - struct mouse_event *); -static void cmd_resize_pane_mouse_update_tiled(struct client *, +static void cmd_resize_pane_mouse_resize_move_floating( + struct client *, struct mouse_event *); +static void cmd_resize_pane_mouse_resize_tiled(struct client *, struct mouse_event *); const struct cmd_entry cmd_resize_pane_entry = { @@ -205,21 +205,29 @@ cmd_resize_pane_mouse_update(__unused struct cmd *self, struct cmdq_item *item) return (CMD_RETURN_NORMAL); if (!window_pane_is_floating(wp)) { - c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_tiled; - cmd_resize_pane_mouse_update_tiled(c, &event->m); + c->tty.mouse_drag_update = cmd_resize_pane_mouse_resize_tiled; + cmd_resize_pane_mouse_resize_tiled(c, &event->m); return (CMD_RETURN_NORMAL); } window_redraw_active_switch(w, wp); window_set_active_pane(w, wp, 1); - c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_floating; - cmd_resize_pane_mouse_update_floating(c, &event->m); + c->tty.mouse_drag_update = cmd_resize_pane_mouse_resize_move_floating; + cmd_resize_pane_mouse_resize_move_floating(c, &event->m); return (CMD_RETURN_NORMAL); } +/* + * Resizes or moves the pane by dragging. Resize a floating pane by dragging + * the borders or corners. Grabbing an edge only resizes that axis (special + * case). Moves the pane if dragging the top border. Since characters are + * generally rectangular, to make it easier to grab the corner, the character + * next to the corner is also considered the corner. + */ static void -cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) +cmd_resize_pane_mouse_resize_move_floating(struct client *c, + struct mouse_event *m) { struct winlink *wl; struct window *w; @@ -345,7 +353,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m) } static void -cmd_resize_pane_mouse_update_tiled(struct client *c, struct mouse_event *m) +cmd_resize_pane_mouse_resize_tiled(struct client *c, struct mouse_event *m) { struct winlink *wl; struct window *w; diff --git a/key-bindings.c b/key-bindings.c index 08d4c5f6..2e5cc193 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -450,6 +450,7 @@ key_bindings_init(void) /* Mouse button 1 drag on pane. */ "bind -n MouseDrag1Pane { if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -M } }", + "bind -n M-MouseDrag1Pane { move-pane -M }", /* Mouse wheel up on pane. */ "bind -n WheelUpPane { if -F '#{||:#{alternate_on},#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -e } }", @@ -468,6 +469,7 @@ key_bindings_init(void) /* Mouse button 1 drag on border. */ "bind -n MouseDrag1Border { resize-pane -M }", + "bind -n M-MouseDrag1Border { move-pane -M }", /* Mouse button 1 down on status line. */ "bind -n MouseDown1Status { switch-client -t= }", diff --git a/tmux.1 b/tmux.1 index 535dcb73..1842ebca 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3313,7 +3313,7 @@ or reverses the sort order. .Tg movep .It Xo Ic move\-pane -.Op Fl bdfhv +.Op Fl bdfhMv .Op Fl D Op Ar lines .Op Fl l Ar size .Op Fl L Op Ar columns @@ -3385,6 +3385,10 @@ move it to an absolute position. moves the pane to the given .Ar z-index , where zero is the front. +.Pp +.Fl M +begins a mouse drag (only valid if bound to a mouse key binding, see +.Sx MOUSE SUPPORT ) . .Tg movew .It Xo Ic move\-window .Op Fl abrdk From 1ba8fa6f04f0a0f54ffcfac4a534478a86deb8c7 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 17 Jun 2026 12:32:54 +0000 Subject: [PATCH 3/4] Fix shifts to 1ULL and check scrollbar even if pane is not being redrawn. --- server-client.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/server-client.c b/server-client.c index 5972be61..6c014510 100644 --- a/server-client.c +++ b/server-client.c @@ -2087,11 +2087,11 @@ server_client_check_redraw(struct client *c) if (wp->flags & (PANE_REDRAW)) { log_debug("%s: pane %%%u needs redraw", c->name, wp->id); - c->redraw_panes |= (1 << bit); + c->redraw_panes |= (1ULL << bit); } else if (wp->flags & PANE_REDRAWSCROLLBAR) { log_debug("%s: pane %%%u scrollbar " "needs redraw", c->name, wp->id); - c->redraw_scrollbars |= (1 << bit); + c->redraw_scrollbars |= (1ULL << bit); } if (++bit == 64) { /* @@ -2128,12 +2128,13 @@ server_client_check_redraw(struct client *c) if (wp->flags & PANE_REDRAW) redraw_pane = 1; else if (c->flags & CLIENT_REDRAWPANES) { - if (c->redraw_panes & (1 << bit)) + if (c->redraw_panes & (1ULL << bit)) redraw_pane = 1; - } else if (c->flags & CLIENT_REDRAWSCROLLBARS) { - if (c->redraw_scrollbars & (1 << bit)) - redraw_scrollbar_only = 1; } + if (!redraw_pane && + (c->flags & CLIENT_REDRAWSCROLLBARS) && + (c->redraw_scrollbars & (1ULL << bit))) + redraw_scrollbar_only = 1; bit++; if (!redraw_pane && !redraw_scrollbar_only) continue; From 3485e1c0893f91a0144936495c1a7925dbf189f9 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 17 Jun 2026 13:22:48 +0000 Subject: [PATCH 4/4] Tidy up server_client_check_redraw. Get rid of the bitmask tracking panes which want redraw for deferred clients - if they are deferred then they can just have a full redraw instead. Also return earlier if no redraw is actually needed, and improve the comments. --- screen-write.c | 6 +- server-client.c | 159 ++++++++++++++++++------------------------------ tmux.h | 11 +--- 3 files changed, 64 insertions(+), 112 deletions(-) diff --git a/screen-write.c b/screen-write.c index df5b33d6..3fcd787d 100644 --- a/screen-write.c +++ b/screen-write.c @@ -148,10 +148,10 @@ screen_write_set_client_cb(struct tty_ctx *ttyctx, struct client *c) if (wp->flags & (PANE_REDRAW|PANE_DROP)) return (-1); - if (c->flags & CLIENT_REDRAWPANES) { + if (c->flags & CLIENT_REDRAWWINDOW) { /* - * Redraw is already deferred to redraw another pane - redraw - * this one also when that happens. + * Redraw is already deferred to redraw the window - redraw this + * one also when that happens. */ log_debug("%s: adding %%%u to deferred redraw", __func__, wp->id); diff --git a/server-client.c b/server-client.c index 6c014510..0aa27970 100644 --- a/server-client.c +++ b/server-client.c @@ -2026,31 +2026,23 @@ server_client_check_redraw(struct client *c) struct tty *tty = &c->tty; struct window *w = c->session->curw->window; struct window_pane *wp; - int needed, tty_flags, mode = tty->mode; + int needed, tflags, mode = tty->mode; uint64_t client_flags = 0; - int redraw_pane, redraw_scrollbar_only; - u_int bit = 0; struct timeval tv = { .tv_usec = 1000 }; static struct event ev; - size_t left; + size_t n; if (c->flags & (CLIENT_CONTROL|CLIENT_SUSPENDED)) return; if (c->flags & CLIENT_ALLREDRAWFLAGS) { - log_debug("%s: redraw%s%s%s%s%s%s", c->name, + log_debug("%s: redraw%s%s%s%s", c->name, (c->flags & CLIENT_REDRAWWINDOW) ? " window" : "", (c->flags & CLIENT_REDRAWSTATUS) ? " status" : "", (c->flags & CLIENT_REDRAWBORDERS) ? " borders" : "", - (c->flags & CLIENT_REDRAWOVERLAY) ? " overlay" : "", - (c->flags & CLIENT_REDRAWPANES) ? " panes" : "", - (c->flags & CLIENT_REDRAWSCROLLBARS) ? " scrollbars" : ""); + (c->flags & CLIENT_REDRAWOVERLAY) ? " overlay" : ""); } - /* - * If there is outstanding data, defer the redraw until it has been - * consumed. We can just add a timer to get out of the event loop and - * end up back here. - */ + /* Work out if a redraw is actually needed. */ needed = 0; if (c->flags & CLIENT_ALLREDRAWFLAGS) needed = 1; @@ -2058,22 +2050,31 @@ server_client_check_redraw(struct client *c) TAILQ_FOREACH(wp, &w->panes, entry) { if (wp->flags & PANE_REDRAW) { needed = 1; - client_flags |= CLIENT_REDRAWPANES; + client_flags |= CLIENT_REDRAWWINDOW; break; } if (wp->flags & PANE_REDRAWSCROLLBAR) { needed = 1; - client_flags |= CLIENT_REDRAWSCROLLBARS; + client_flags |= CLIENT_REDRAWWINDOW; /* no break - later panes may need redraw */ } } } - left = EVBUFFER_LENGTH(tty->out); - if (needed && (left != 0 || (tty->flags & TTY_BLOCK))) { - if (left != 0) { - log_debug("%s: redraw deferred (%zu left)", c->name, - left); - } else + if (!needed) { + c->flags &= ~CLIENT_STATUSFORCE; + return; + } + + /* + * If there is outstanding data, defer the redraw until it has been + * consumed. We can just add a timer to get out of the event loop and + * end up back here. + */ + n = EVBUFFER_LENGTH(tty->out); + if (n != 0 || (tty->flags & TTY_BLOCK)) { + if (n != 0) + log_debug("%s: redraw deferred (%zu left)", c->name, n); + else log_debug("%s: redraw deferred (blocked)", c->name); if (!evtimer_initialized(&ev)) evtimer_set(&ev, server_client_redraw_timer, NULL); @@ -2081,77 +2082,37 @@ server_client_check_redraw(struct client *c) log_debug("redraw timer started"); evtimer_add(&ev, &tv); } - - if (~c->flags & CLIENT_REDRAWWINDOW) { - TAILQ_FOREACH(wp, &w->panes, entry) { - if (wp->flags & (PANE_REDRAW)) { - log_debug("%s: pane %%%u needs redraw", - c->name, wp->id); - c->redraw_panes |= (1ULL << bit); - } else if (wp->flags & PANE_REDRAWSCROLLBAR) { - log_debug("%s: pane %%%u scrollbar " - "needs redraw", c->name, wp->id); - c->redraw_scrollbars |= (1ULL << bit); - } - if (++bit == 64) { - /* - * If more that 64 panes, give up and - * just redraw the window. - */ - client_flags &= ~(CLIENT_REDRAWPANES| - CLIENT_REDRAWSCROLLBARS); - client_flags |= CLIENT_REDRAWWINDOW; - break; - } - } - if (c->redraw_panes != 0) - c->flags |= CLIENT_REDRAWPANES; - if (c->redraw_scrollbars != 0) - c->flags |= CLIENT_REDRAWSCROLLBARS; - } c->flags |= client_flags; return; - } else if (needed) - log_debug("%s: redraw needed", c->name); - - tty_flags = tty->flags & (TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR); - tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE))|TTY_NOCURSOR; - - if (~c->flags & CLIENT_REDRAWWINDOW) { - /* - * If not redrawing the entire window, check whether each pane - * needs to be redrawn. - */ - TAILQ_FOREACH(wp, &w->panes, entry) { - redraw_pane = 0; - redraw_scrollbar_only = 0; - if (wp->flags & PANE_REDRAW) - redraw_pane = 1; - else if (c->flags & CLIENT_REDRAWPANES) { - if (c->redraw_panes & (1ULL << bit)) - redraw_pane = 1; - } - if (!redraw_pane && - (c->flags & CLIENT_REDRAWSCROLLBARS) && - (c->redraw_scrollbars & (1ULL << bit))) - redraw_scrollbar_only = 1; - bit++; - if (!redraw_pane && !redraw_scrollbar_only) - continue; - if (redraw_scrollbar_only) { - log_debug("%s: redrawing (scrollbar only) pane " - "%%%u", __func__, wp->id); - } else { - log_debug("%s: redrawing pane %%%u", __func__, - wp->id); - } - screen_redraw_pane(c, wp, redraw_scrollbar_only); - } - c->redraw_panes = 0; - c->redraw_scrollbars = 0; - c->flags &= ~(CLIENT_REDRAWPANES|CLIENT_REDRAWSCROLLBARS); } + /* Unfreeze the tty and turn off the cursor. */ + log_debug("%s: redraw needed", c->name); + tflags = tty->flags & (TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR); + tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE))|TTY_NOCURSOR; + + /* + * If not redrawing the entire window, check whether each pane needs to + * be redrawn. + */ + if (~c->flags & CLIENT_REDRAWWINDOW) { + TAILQ_FOREACH(wp, &w->panes, entry) { + if (wp->flags & PANE_REDRAW) { + log_debug("%s: redraw pane %%%u", __func__, + wp->id); + screen_redraw_pane(c, wp, 0); + } else if (wp->flags & PANE_REDRAWSCROLLBAR) { + log_debug("%s: redraw scrollbar %%%u", __func__, + wp->id); + screen_redraw_pane(c, wp, 1); + } + } + } + + /* + * Set titles etc and do the redraw if there are redraw flags (and we + * aren't here just to redraw panes). + */ if (c->flags & CLIENT_ALLREDRAWFLAGS) { if (options_get_number(s->options, "set-titles")) { server_client_set_title(c); @@ -2161,22 +2122,18 @@ server_client_check_redraw(struct client *c) screen_redraw_screen(c); } - tty->flags = (tty->flags & ~TTY_NOCURSOR)|(tty_flags & TTY_NOCURSOR); + /* Put the tty back how it was. */ + tty->flags = (tty->flags & ~TTY_NOCURSOR)|(tflags & TTY_NOCURSOR); tty_update_mode(tty, mode, NULL); - tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR))| - tty_flags; + tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR))|tflags; + /* + * All the redraw flags can now be cleared. Also record how many bytes + * were written. + */ c->flags &= ~(CLIENT_ALLREDRAWFLAGS|CLIENT_STATUSFORCE); - - if (needed) { - /* - * We would have deferred the redraw unless the output buffer - * was empty, so we can record how many bytes the redraw - * generated. - */ - c->redraw = EVBUFFER_LENGTH(tty->out); - log_debug("%s: redraw added %zu bytes", c->name, c->redraw); - } + c->redraw = EVBUFFER_LENGTH(tty->out); + log_debug("%s: redraw added %zu bytes", c->name, c->redraw); } /* Set client title. */ diff --git a/tmux.h b/tmux.h index fbf0e2ef..7398fec1 100644 --- a/tmux.h +++ b/tmux.h @@ -2096,7 +2096,7 @@ struct client { #define CLIENT_CONTROL_NOOUTPUT 0x4000000 #define CLIENT_DEFAULTSOCKET 0x8000000 #define CLIENT_STARTSERVER 0x10000000 -#define CLIENT_REDRAWPANES 0x20000000 +/* 0x20000000 unused */ #define CLIENT_NOFORK 0x40000000 #define CLIENT_ACTIVEPANE 0x80000000ULL #define CLIENT_CONTROL_PAUSEAFTER 0x100000000ULL @@ -2105,16 +2105,14 @@ struct client { /* 0x800000000ULL unused */ #define CLIENT_BRACKETPASTING 0x1000000000ULL #define CLIENT_ASSUMEPASTING 0x2000000000ULL -#define CLIENT_REDRAWSCROLLBARS 0x4000000000ULL +/* 0x4000000000ULL unused */ #define CLIENT_NO_DETACH_ON_DESTROY 0x8000000000ULL #define CLIENT_ALLREDRAWFLAGS \ (CLIENT_REDRAWWINDOW| \ CLIENT_REDRAWSTATUS| \ CLIENT_REDRAWSTATUSALWAYS| \ CLIENT_REDRAWBORDERS| \ - CLIENT_REDRAWOVERLAY| \ - CLIENT_REDRAWPANES| \ - CLIENT_REDRAWSCROLLBARS) + CLIENT_REDRAWOVERLAY) #define CLIENT_UNATTACHEDFLAGS \ (CLIENT_DEAD| \ CLIENT_SUSPENDED| \ @@ -2141,9 +2139,6 @@ struct client { key_code last_key; time_t paste_time; - uint64_t redraw_panes; - uint64_t redraw_scrollbars; - int message_ignore_keys; int message_ignore_styles; char *message_string;