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",
.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);

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)
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);

View File

@ -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 &&

12
tmux.1
View File

@ -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

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_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,