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"
This commit is contained in:
Patrick Motard 2025-12-05 14:08:22 -06:00
parent ef0a7e328c
commit c46e2b2937
5 changed files with 41 additions and 6 deletions

View File

@ -54,8 +54,8 @@ const struct cmd_entry cmd_display_popup_entry = {
.name = "display-popup", .name = "display-popup",
.alias = "popup", .alias = "popup",
.args = { "Bb:Cc:d:e:Eh:kNs:S:t:T:w:x:y:", 0, -1, NULL }, .args = { "Bb:Cc:d:e:Eh:kNPs:S:t:T:w:x:y:", 0, -1, NULL },
.usage = "[-BCEkN] [-b border-lines] [-c target-client] " .usage = "[-BCEkNP] [-b border-lines] [-c target-client] "
"[-d start-directory] [-e environment] [-h height] " "[-d start-directory] [-e environment] [-h height] "
"[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE "[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE
" [-T title] [-w width] [-x position] [-y position] " " [-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 = 0;
flags |= POPUP_CLOSEANYKEY; flags |= POPUP_CLOSEANYKEY;
} }
if (args_has(args, 'P')) {
if (flags == -1)
flags = 0;
flags |= POPUP_PASSTHROUGH;
}
if (modify) { if (modify) {
popup_modify(tc, title, style, border_style, lines, flags); popup_modify(tc, title, style, border_style, lines, flags);

12
popup.c
View File

@ -153,6 +153,10 @@ popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy)
if (pd->md != NULL) if (pd->md != NULL)
return (menu_mode_cb(c, pd->md, cx, cy)); 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) { if (pd->border_lines == BOX_LINES_NONE) {
*cx = pd->px + pd->s.cx; *cx = pd->px + pd->s.cx;
*cy = pd->py + pd->s.cy; *cy = pd->py + pd->s.cy;
@ -504,6 +508,14 @@ popup_key_cb(struct client *c, void *data, struct key_event *event)
return (0); 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 (KEYC_IS_MOUSE(event->key)) {
if (pd->dragging != OFF) { if (pd->dragging != OFF) {
popup_handle_drag(c, pd, m); popup_handle_drag(c, pd, m);

View File

@ -2636,8 +2636,13 @@ server_client_handle_key(struct client *c, struct key_event *event)
case 1: case 1:
server_client_clear_overlay(c); server_client_clear_overlay(c);
return (0); return (0);
case -1:
break; /* passthrough to underlying pane */
default:
server_client_clear_overlay(c);
break;
} }
} } else
server_client_clear_overlay(c); server_client_clear_overlay(c);
if (c->prompt_string != NULL) { if (c->prompt_string != NULL) {
if (status_prompt_key(c, event->key) == 0) if (status_prompt_key(c, event->key) == 0)
@ -2920,6 +2925,8 @@ server_client_reset_state(struct client *c)
if (c->overlay_draw != NULL) { if (c->overlay_draw != NULL) {
if (c->overlay_mode != NULL) if (c->overlay_mode != NULL)
s = c->overlay_mode(c, c->overlay_data, &cx, &cy); 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) } else if (c->prompt_string == NULL)
s = wp->screen; s = wp->screen;
else else
@ -2948,7 +2955,7 @@ server_client_reset_state(struct client *c)
cy = tty->sy - 1; cy = tty->sy - 1;
} }
cx = c->prompt_cursor; cx = c->prompt_cursor;
} else if (c->overlay_draw == NULL) { } else if (c->overlay_draw == NULL || s == wp->screen) {
cursor = 0; cursor = 0;
tty_window_offset(tty, &ox, &oy, &sx, &sy); tty_window_offset(tty, &ox, &oy, &sx, &sy);
if (wp->xoff + s->cx >= ox && wp->xoff + s->cx <= ox + sx && if (wp->xoff + s->cx >= ox && wp->xoff + s->cx <= ox + sx &&

12
tmux.1
View File

@ -7020,7 +7020,7 @@ forwards any input read from stdin to the empty pane given by
.Ar target-pane . .Ar target-pane .
.Tg popup .Tg popup
.It Xo Ic display-popup .It Xo Ic display-popup
.Op Fl BCEkN .Op Fl BCEkNP
.Op Fl b Ar border-lines .Op Fl b Ar border-lines
.Op Fl c Ar target-client .Op Fl c Ar target-client
.Op Fl d Ar start-directory .Op Fl d Ar start-directory
@ -7053,6 +7053,7 @@ Only the
.Fl EE , .Fl EE ,
.Fl K , .Fl K ,
.Fl N , .Fl N ,
.Fl P ,
.Fl s , .Fl s ,
and and
.Fl S .Fl S
@ -7073,6 +7074,15 @@ allows any key to dismiss the popup instead of only
.Ql Escape .Ql Escape
or or
.Ql C-c . .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 .Pp
.Fl x .Fl x
and and

1
tmux.h
View File

@ -3607,6 +3607,7 @@ int menu_key_cb(struct client *, void *, struct key_event *);
#define POPUP_CLOSEEXITZERO 0x2 #define POPUP_CLOSEEXITZERO 0x2
#define POPUP_INTERNAL 0x4 #define POPUP_INTERNAL 0x4
#define POPUP_CLOSEANYKEY 0x8 #define POPUP_CLOSEANYKEY 0x8
#define POPUP_PASSTHROUGH 0x10
typedef void (*popup_close_cb)(int, void *); typedef void (*popup_close_cb)(int, void *);
typedef void (*popup_finish_edit_cb)(char *, size_t, void *); typedef void (*popup_finish_edit_cb)(char *, size_t, void *);
int popup_display(int, enum box_lines, struct cmdq_item *, u_int, int popup_display(int, enum box_lines, struct cmdq_item *, u_int,