1
0
mirror of https://github.com/tmux/tmux.git synced 2025-03-31 20:58:47 +00:00

Add support for popups to control mode

This commit is contained in:
George Nachman 2025-02-07 13:24:57 -08:00
parent ef68debc8d
commit 5fcda70d0c
5 changed files with 160 additions and 26 deletions

View File

@ -432,7 +432,15 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
w = tty->sx; w = tty->sx;
if (h > tty->sy) if (h > tty->sy)
h = tty->sy; h = tty->sy;
if (!cmd_display_menu_get_position(tc, item, args, &px, &py, w, h)) if (tc->flags & CLIENT_CONTROL) {
/* Control clients may not have a window size, so provide a reasonable default so popups can still work. */
if (w == 0)
w = 80;
if (h == 0)
h = 25;
px = 0;
py = 0;
} else if (!cmd_display_menu_get_position(tc, item, args, &px, &py, w, h))
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);
value = args_get(args, 'b'); value = args_get(args, 'b');
@ -485,7 +493,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
else if (args_has(args, 'E')) else if (args_has(args, 'E'))
flags |= POPUP_CLOSEEXIT; flags |= POPUP_CLOSEEXIT;
if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc, if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc,
argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0) { argv, cwd, title, tc, s, style, border_style, NULL, NULL, target->wp) != 0) {
cmd_free_argv(argc, argv); cmd_free_argv(argc, argv);
if (env != NULL) if (env != NULL)
environ_free(env); environ_free(env);
@ -498,5 +506,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
free(cwd); free(cwd);
free(title); free(title);
cmd_free_argv(argc, argv); cmd_free_argv(argc, argv);
if (tc->flags & CLIENT_CONTROL)
return (CMD_RETURN_NORMAL);
return (CMD_RETURN_WAIT); return (CMD_RETURN_WAIT);
} }

View File

@ -260,3 +260,19 @@ control_notify_paste_buffer_deleted(const char *name)
control_write(c, "%%paste-buffer-deleted %s", name); control_write(c, "%%paste-buffer-deleted %s", name);
} }
} }
void
control_notify_popup(struct client *c, int status, char *buf, size_t len, int wp)
{
struct evbuffer *message = evbuffer_new();
if (message == NULL)
fatalx("out of memory");
evbuffer_add_printf(message, "%%popup %d", status);
if (wp != -1)
evbuffer_add_printf(message, " %u", wp);
evbuffer_add_printf(message, " : ");
control_escape(message, buf, len);
control_write_buffer(c, message);
evbuffer_free(message);
}

View File

@ -43,7 +43,9 @@
*/ */
struct control_block { struct control_block {
size_t size; size_t size;
/* exactly one of `line` and `buffer` will be nonnull */
char *line; char *line;
struct evbuffer *buffer;
uint64_t t; uint64_t t;
TAILQ_ENTRY(control_block) entry; TAILQ_ENTRY(control_block) entry;
@ -225,7 +227,10 @@ control_free_sub(struct control_state *cs, struct control_sub *csub)
static void static void
control_free_block(struct control_state *cs, struct control_block *cb) control_free_block(struct control_state *cs, struct control_block *cb)
{ {
if (cb->line != NULL)
free(cb->line); free(cb->line);
if (cb->buffer != NULL)
evbuffer_free(cb->buffer);
TAILQ_REMOVE(&cs->all_blocks, cb, all_entry); TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
free(cb); free(cb);
} }
@ -401,13 +406,63 @@ control_vwrite(struct client *c, const char *fmt, va_list ap)
free(s); free(s);
} }
static void
control_vwrite_buffer(struct client *c, struct evbuffer *buffer)
{
struct control_state *cs = c->control_state;
log_debug("%s: %s: writing buffer", __func__, c->name);
bufferevent_write_buffer(cs->write_event, buffer);
bufferevent_write(cs->write_event, "\n", 1);
bufferevent_enable(cs->write_event, EV_WRITE);
}
/* Frees line and buffer after using them asynchronously. */
static void
control_enqueue(struct client *c, struct control_state *cs, char *line, struct evbuffer *buffer)
{
struct control_block *cb = xcalloc(1, sizeof *cb);
if (line != NULL) {
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
cb->line = line;
} else {
log_debug("%s: %s: storing buffer", __func__, c->name);
cb->buffer = buffer;
}
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
cb->t = get_timer();
bufferevent_enable(cs->write_event, EV_WRITE);
}
void
control_write_buffer(struct client *c, struct evbuffer *buffer)
{
struct control_state *cs = c->control_state;
struct control_block *cb;
va_list ap;
if (TAILQ_EMPTY(&cs->all_blocks)) {
control_vwrite_buffer(c, buffer);
return;
}
control_enqueue(c, cs, NULL, buffer);
va_end(ap);
}
/* Write a line. */ /* Write a line. */
void void
control_write(struct client *c, const char *fmt, ...) control_write(struct client *c, const char *fmt, ...)
{ {
struct control_state *cs = c->control_state; struct control_state *cs = c->control_state;
struct control_block *cb;
va_list ap; va_list ap;
char *line;
va_start(ap, fmt); va_start(ap, fmt);
@ -417,13 +472,8 @@ control_write(struct client *c, const char *fmt, ...)
return; return;
} }
cb = xcalloc(1, sizeof *cb); xvasprintf(&line, fmt, ap);
xvasprintf(&cb->line, fmt, ap); control_enqueue(c, cs, line, NULL);
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
cb->t = get_timer();
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
bufferevent_enable(cs->write_event, EV_WRITE);
va_end(ap); va_end(ap);
} }
@ -604,6 +654,17 @@ control_flush_all_blocks(struct client *c)
} }
} }
void
control_escape(struct evbuffer *message, char *s, size_t size)
{
for (size_t i = 0; i < size; i++) {
if (s[i] < ' ' || s[i] == '\\')
evbuffer_add_printf(message, "\\%03o", s[i]);
else
evbuffer_add_printf(message, "%c", s[i]);
}
}
/* Append data to buffer. */ /* Append data to buffer. */
static struct evbuffer * static struct evbuffer *
control_append_data(struct client *c, struct control_pane *cp, uint64_t age, control_append_data(struct client *c, struct control_pane *cp, uint64_t age,
@ -611,7 +672,6 @@ control_append_data(struct client *c, struct control_pane *cp, uint64_t age,
{ {
u_char *new_data; u_char *new_data;
size_t new_size; size_t new_size;
u_int i;
if (message == NULL) { if (message == NULL) {
message = evbuffer_new(); message = evbuffer_new();
@ -628,12 +688,7 @@ control_append_data(struct client *c, struct control_pane *cp, uint64_t age,
new_data = window_pane_get_new_data(wp, &cp->offset, &new_size); new_data = window_pane_get_new_data(wp, &cp->offset, &new_size);
if (new_size < size) if (new_size < size)
fatalx("not enough data: %zu < %zu", new_size, size); fatalx("not enough data: %zu < %zu", new_size, size);
for (i = 0; i < size; i++) { control_escape(message, new_data, size);
if (new_data[i] < ' ' || new_data[i] == '\\')
evbuffer_add_printf(message, "\\%03o", new_data[i]);
else
evbuffer_add_printf(message, "%c", new_data[i]);
}
window_pane_update_used_data(wp, &cp->offset, size); window_pane_update_used_data(wp, &cp->offset, size);
return (message); return (message);
} }

58
popup.c
View File

@ -28,6 +28,7 @@
struct popup_data { struct popup_data {
struct client *c; struct client *c;
int wp;
struct cmdq_item *item; struct cmdq_item *item;
int flags; int flags;
char *title; char *title;
@ -614,6 +615,47 @@ popup_job_update_cb(struct job *job)
evbuffer_drain(evb, size); evbuffer_drain(evb, size);
} }
// NOTE TO REVIEWER: This is a copy of cmd_capture_pane_append. I think we'd want a shared implementation but I don't know where it should go.
static char *
popup_append(char *buf, size_t *len, char *line, size_t linelen)
{
buf = xrealloc(buf, *len + linelen + 1);
memcpy(buf + *len, line, linelen);
*len += linelen;
return (buf);
}
static void
popup_notify_control(struct client *c, int status, struct screen *s, int wp)
{
char *buf = NULL;
struct grid_cell *gc = NULL;
int sx = screen_size_x(s);
int sy = screen_size_y(s);
char *line;
size_t linelen;
size_t len = 0;
int i;
struct grid *gd = s->grid;
const struct grid_line *gl;
for (i = 0; i < sy; i++) {
line = grid_string_cells(gd, 0, i, sx, &gc, GRID_STRING_WITH_SEQUENCES, s);
linelen = strlen(line);
buf = popup_append(buf, &len, line, linelen);
gl = grid_peek_line(gd, i);
if (!(gl->flags & GRID_LINE_WRAPPED))
buf[len++] = '\n';
free(line);
}
control_notify_popup(c, status, buf, len, wp);
free(buf);
}
static void static void
popup_job_complete_cb(struct job *job) popup_job_complete_cb(struct job *job)
{ {
@ -629,7 +671,9 @@ popup_job_complete_cb(struct job *job)
pd->status = 0; pd->status = 0;
pd->job = NULL; pd->job = NULL;
if ((pd->flags & POPUP_CLOSEEXIT) || if (pd->c->flags & CLIENT_CONTROL)
popup_notify_control(pd->c, pd->status, &pd->s, pd->wp);
else if ((pd->flags & POPUP_CLOSEEXIT) ||
((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0)) ((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0))
server_client_clear_overlay(pd->c); server_client_clear_overlay(pd->c);
} }
@ -639,7 +683,7 @@ popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
u_int py, u_int sx, u_int sy, struct environ *env, const char *shellcmd, u_int py, u_int sx, u_int sy, struct environ *env, const char *shellcmd,
int argc, char **argv, const char *cwd, const char *title, struct client *c, int argc, char **argv, const char *cwd, const char *title, struct client *c,
struct session *s, const char *style, const char *border_style, struct session *s, const char *style, const char *border_style,
popup_close_cb cb, void *arg) popup_close_cb cb, void *arg, struct window_pane *wp)
{ {
struct popup_data *pd; struct popup_data *pd;
u_int jx, jy; u_int jx, jy;
@ -664,10 +708,14 @@ popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
jx = sx - 2; jx = sx - 2;
jy = sy - 2; jy = sy - 2;
} }
if (c->tty.sx < sx || c->tty.sy < sy) if (!(c->flags & CLIENT_CONTROL) && (c->tty.sx < sx || c->tty.sy < sy))
return (-1); return (-1);
pd = xcalloc(1, sizeof *pd); pd = xcalloc(1, sizeof *pd);
if (wp != NULL)
pd->wp = wp->id;
else
pd->wp = -1;
pd->item = item; pd->item = item;
pd->flags = flags; pd->flags = flags;
if (title != NULL) if (title != NULL)
@ -723,8 +771,10 @@ popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy); JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy);
pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette); pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette);
if (!(c->flags & CLIENT_CONTROL)) {
server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb, server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb,
popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd); popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd);
}
return (0); return (0);
} }
@ -811,7 +861,7 @@ popup_editor(struct client *c, const char *buf, size_t len,
xasprintf(&cmd, "%s %s", editor, path); xasprintf(&cmd, "%s %s", editor, path);
if (popup_display(POPUP_INTERNAL|POPUP_CLOSEEXIT, BOX_LINES_DEFAULT, if (popup_display(POPUP_INTERNAL|POPUP_CLOSEEXIT, BOX_LINES_DEFAULT,
NULL, px, py, sx, sy, NULL, cmd, 0, NULL, _PATH_TMP, NULL, c, NULL, NULL, px, py, sx, sy, NULL, cmd, 0, NULL, _PATH_TMP, NULL, c, NULL,
NULL, NULL, popup_editor_close_cb, pe) != 0) { NULL, NULL, popup_editor_close_cb, pe, NULL) != 0) {
popup_editor_free(pe); popup_editor_free(pe);
free(cmd); free(cmd);
return (-1); return (-1);

5
tmux.h
View File

@ -3379,11 +3379,13 @@ struct window_pane_offset *control_pane_offset(struct client *,
struct window_pane *, int *); struct window_pane *, int *);
void control_reset_offsets(struct client *); void control_reset_offsets(struct client *);
void printflike(2, 3) control_write(struct client *, const char *, ...); void printflike(2, 3) control_write(struct client *, const char *, ...);
void control_write_buffer(struct client *c, struct evbuffer *buffer);
void control_write_output(struct client *, struct window_pane *); void control_write_output(struct client *, struct window_pane *);
int control_all_done(struct client *); int control_all_done(struct client *);
void control_add_sub(struct client *, const char *, enum control_sub_type, void control_add_sub(struct client *, const char *, enum control_sub_type,
int, const char *); int, const char *);
void control_remove_sub(struct client *, const char *); void control_remove_sub(struct client *, const char *);
void control_escape(struct evbuffer *, char *, size_t);
/* control-notify.c */ /* control-notify.c */
void control_notify_pane_mode_changed(int); void control_notify_pane_mode_changed(int);
@ -3400,6 +3402,7 @@ void control_notify_session_closed(struct session *);
void control_notify_session_window_changed(struct session *); void control_notify_session_window_changed(struct session *);
void control_notify_paste_buffer_changed(const char *); void control_notify_paste_buffer_changed(const char *);
void control_notify_paste_buffer_deleted(const char *); void control_notify_paste_buffer_deleted(const char *);
void control_notify_popup(struct client *c, int status, char *buf, size_t len, int wp);
/* session.c */ /* session.c */
extern struct sessions sessions; extern struct sessions sessions;
@ -3531,7 +3534,7 @@ int popup_display(int, enum box_lines, struct cmdq_item *, u_int,
u_int, u_int, u_int, struct environ *, const char *, int, u_int, u_int, u_int, struct environ *, const char *, int,
char **, const char *, const char *, struct client *, char **, const char *, const char *, struct client *,
struct session *, const char *, const char *, struct session *, const char *, const char *,
popup_close_cb, void *); popup_close_cb, void *, struct window_pane *);
int popup_editor(struct client *, const char *, size_t, int popup_editor(struct client *, const char *, size_t,
popup_finish_edit_cb, void *); popup_finish_edit_cb, void *);