From c46e2b2937bebb7e96252af47cec1f9a6a59899b Mon Sep 17 00:00:00 2001 From: Patrick Motard Date: Fri, 5 Dec 2025 14:08:22 -0600 Subject: [PATCH] Add -P flag to display-popup for passthrough/non-blocking mode Add a new -P flag to display-popup that makes the popup non-blocking: keyboard input is passed through to the underlying pane instead of being captured by the popup. This allows users to continue typing while the popup is displayed. Escape and Ctrl-C still close the popup in passthrough mode. This is useful for toast-style notifications that should not steal focus. Combined with -E, popups can auto-dismiss when their command exits while allowing uninterrupted typing. Example usage: display-popup -P -E "echo 'Task completed'; sleep 2" --- cmd-display-menu.c | 9 +++++++-- popup.c | 12 ++++++++++++ server-client.c | 13 ++++++++++--- tmux.1 | 12 +++++++++++- tmux.h | 1 + 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/cmd-display-menu.c b/cmd-display-menu.c index 3d8bed93..1f96d91c 100644 --- a/cmd-display-menu.c +++ b/cmd-display-menu.c @@ -54,8 +54,8 @@ const struct cmd_entry cmd_display_popup_entry = { .name = "display-popup", .alias = "popup", - .args = { "Bb:Cc:d:e:Eh:kNs:S:t:T:w:x:y:", 0, -1, NULL }, - .usage = "[-BCEkN] [-b border-lines] [-c target-client] " + .args = { "Bb:Cc:d:e:Eh:kNPs:S:t:T:w:x:y:", 0, -1, NULL }, + .usage = "[-BCEkNP] [-b border-lines] [-c target-client] " "[-d start-directory] [-e environment] [-h height] " "[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE " [-T title] [-w width] [-x position] [-y position] " @@ -504,6 +504,11 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item) flags = 0; flags |= POPUP_CLOSEANYKEY; } + if (args_has(args, 'P')) { + if (flags == -1) + flags = 0; + flags |= POPUP_PASSTHROUGH; + } if (modify) { popup_modify(tc, title, style, border_style, lines, flags); diff --git a/popup.c b/popup.c index 2146693a..3e1c89ad 100644 --- a/popup.c +++ b/popup.c @@ -153,6 +153,10 @@ popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy) if (pd->md != NULL) return (menu_mode_cb(c, pd->md, cx, cy)); + /* In passthrough mode, keep cursor in underlying pane. */ + if (pd->flags & POPUP_PASSTHROUGH) + return (NULL); + if (pd->border_lines == BOX_LINES_NONE) { *cx = pd->px + pd->s.cx; *cy = pd->py + pd->s.cy; @@ -504,6 +508,14 @@ popup_key_cb(struct client *c, void *data, struct key_event *event) return (0); } + /* Passthrough mode: pass non-mouse keys to underlying pane. */ + if ((pd->flags & POPUP_PASSTHROUGH) && !KEYC_IS_MOUSE(event->key)) { + /* Still allow Escape and Ctrl-C to close the popup. */ + if (event->key == '\033' || event->key == ('c'|KEYC_CTRL)) + return (1); + return (-1); + } + if (KEYC_IS_MOUSE(event->key)) { if (pd->dragging != OFF) { popup_handle_drag(c, pd, m); diff --git a/server-client.c b/server-client.c index 7f1942c7..8fcda8cd 100644 --- a/server-client.c +++ b/server-client.c @@ -2636,9 +2636,14 @@ server_client_handle_key(struct client *c, struct key_event *event) case 1: server_client_clear_overlay(c); return (0); + case -1: + break; /* passthrough to underlying pane */ + default: + server_client_clear_overlay(c); + break; } - } - server_client_clear_overlay(c); + } else + server_client_clear_overlay(c); if (c->prompt_string != NULL) { if (status_prompt_key(c, event->key) == 0) return (0); @@ -2920,6 +2925,8 @@ server_client_reset_state(struct client *c) if (c->overlay_draw != NULL) { if (c->overlay_mode != NULL) s = c->overlay_mode(c, c->overlay_data, &cx, &cy); + if (s == NULL && c->prompt_string == NULL) + s = wp->screen; } else if (c->prompt_string == NULL) s = wp->screen; else @@ -2948,7 +2955,7 @@ server_client_reset_state(struct client *c) cy = tty->sy - 1; } cx = c->prompt_cursor; - } else if (c->overlay_draw == NULL) { + } else if (c->overlay_draw == NULL || s == wp->screen) { cursor = 0; tty_window_offset(tty, &ox, &oy, &sx, &sy); if (wp->xoff + s->cx >= ox && wp->xoff + s->cx <= ox + sx && diff --git a/tmux.1 b/tmux.1 index 404909bd..267c0fba 100644 --- a/tmux.1 +++ b/tmux.1 @@ -7020,7 +7020,7 @@ forwards any input read from stdin to the empty pane given by .Ar target-pane . .Tg popup .It Xo Ic display-popup -.Op Fl BCEkN +.Op Fl BCEkNP .Op Fl b Ar border-lines .Op Fl c Ar target-client .Op Fl d Ar start-directory @@ -7053,6 +7053,7 @@ Only the .Fl EE , .Fl K , .Fl N , +.Fl P , .Fl s , and .Fl S @@ -7073,6 +7074,15 @@ allows any key to dismiss the popup instead of only .Ql Escape or .Ql C-c . +.Fl P +makes the popup non-blocking: keyboard input is sent to the underlying pane +instead of the popup, allowing the user to continue typing while the popup +is displayed. +.Ql Escape +and +.Ql C-c +will still close the popup. +This is useful for toast-style notifications that should not steal focus. .Pp .Fl x and diff --git a/tmux.h b/tmux.h index 60795e09..cbe75f8e 100644 --- a/tmux.h +++ b/tmux.h @@ -3607,6 +3607,7 @@ int menu_key_cb(struct client *, void *, struct key_event *); #define POPUP_CLOSEEXITZERO 0x2 #define POPUP_INTERNAL 0x4 #define POPUP_CLOSEANYKEY 0x8 +#define POPUP_PASSTHROUGH 0x10 typedef void (*popup_close_cb)(int, void *); typedef void (*popup_finish_edit_cb)(char *, size_t, void *); int popup_display(int, enum box_lines, struct cmdq_item *, u_int,