xref: /netbsd-src/external/bsd/tmux/dist/popup.c (revision f8cf1a9151c7af1cb0bd8b09c13c66bca599c027)
19fb66d81Schristos /* $OpenBSD$ */
29fb66d81Schristos 
39fb66d81Schristos /*
49fb66d81Schristos  * Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
59fb66d81Schristos  *
69fb66d81Schristos  * Permission to use, copy, modify, and distribute this software for any
79fb66d81Schristos  * purpose with or without fee is hereby granted, provided that the above
89fb66d81Schristos  * copyright notice and this permission notice appear in all copies.
99fb66d81Schristos  *
109fb66d81Schristos  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
119fb66d81Schristos  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
129fb66d81Schristos  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
139fb66d81Schristos  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
149fb66d81Schristos  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
159fb66d81Schristos  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
169fb66d81Schristos  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
179fb66d81Schristos  */
189fb66d81Schristos 
199fb66d81Schristos #include <sys/types.h>
209fb66d81Schristos #include <sys/wait.h>
219fb66d81Schristos 
229fb66d81Schristos #include <signal.h>
239fb66d81Schristos #include <stdlib.h>
249fb66d81Schristos #include <string.h>
259fb66d81Schristos #include <unistd.h>
269fb66d81Schristos 
279fb66d81Schristos #include "tmux.h"
289fb66d81Schristos 
299fb66d81Schristos struct popup_data {
309fb66d81Schristos 	struct client		 *c;
319fb66d81Schristos 	struct cmdq_item	 *item;
329fb66d81Schristos 	int			  flags;
336db26757Swiz 	char			 *title;
346db26757Swiz 
356db26757Swiz 	struct grid_cell	  border_cell;
366db26757Swiz 	enum box_lines		  border_lines;
379fb66d81Schristos 
389fb66d81Schristos 	struct screen		  s;
396db26757Swiz 	struct grid_cell	  defaults;
406db26757Swiz 	struct colour_palette	  palette;
416db26757Swiz 
429fb66d81Schristos 	struct job		 *job;
439fb66d81Schristos 	struct input_ctx	 *ictx;
449fb66d81Schristos 	int			  status;
459fb66d81Schristos 	popup_close_cb		  cb;
469fb66d81Schristos 	void			 *arg;
479fb66d81Schristos 
486db26757Swiz 	struct menu		 *menu;
496db26757Swiz 	struct menu_data	 *md;
506db26757Swiz 	int			  close;
516db26757Swiz 
526db26757Swiz 	/* Current position and size. */
539fb66d81Schristos 	u_int			  px;
549fb66d81Schristos 	u_int			  py;
559fb66d81Schristos 	u_int			  sx;
569fb66d81Schristos 	u_int			  sy;
579fb66d81Schristos 
586db26757Swiz 	/* Preferred position and size. */
596db26757Swiz 	u_int			  ppx;
606db26757Swiz 	u_int			  ppy;
616db26757Swiz 	u_int			  psx;
626db26757Swiz 	u_int			  psy;
636db26757Swiz 
649fb66d81Schristos 	enum { OFF, MOVE, SIZE }  dragging;
659fb66d81Schristos 	u_int			  dx;
669fb66d81Schristos 	u_int			  dy;
679fb66d81Schristos 
689fb66d81Schristos 	u_int			  lx;
699fb66d81Schristos 	u_int			  ly;
709fb66d81Schristos 	u_int			  lb;
719fb66d81Schristos };
729fb66d81Schristos 
739fb66d81Schristos struct popup_editor {
749fb66d81Schristos 	char			*path;
759fb66d81Schristos 	popup_finish_edit_cb	 cb;
769fb66d81Schristos 	void			*arg;
779fb66d81Schristos };
789fb66d81Schristos 
796db26757Swiz static const struct menu_item popup_menu_items[] = {
806db26757Swiz 	{ "Close", 'q', NULL },
816db26757Swiz 	{ "#{?buffer_name,Paste #[underscore]#{buffer_name},}", 'p', NULL },
826db26757Swiz 	{ "", KEYC_NONE, NULL },
836db26757Swiz 	{ "Fill Space", 'F', NULL },
846db26757Swiz 	{ "Centre", 'C', NULL },
856db26757Swiz 	{ "", KEYC_NONE, NULL },
866db26757Swiz 	{ "To Horizontal Pane", 'h', NULL },
876db26757Swiz 	{ "To Vertical Pane", 'v', NULL },
886db26757Swiz 
896db26757Swiz 	{ NULL, KEYC_NONE, NULL }
906db26757Swiz };
916db26757Swiz 
926db26757Swiz static const struct menu_item popup_internal_menu_items[] = {
936db26757Swiz 	{ "Close", 'q', NULL },
946db26757Swiz 	{ "", KEYC_NONE, NULL },
956db26757Swiz 	{ "Fill Space", 'F', NULL },
966db26757Swiz 	{ "Centre", 'C', NULL },
976db26757Swiz 
986db26757Swiz 	{ NULL, KEYC_NONE, NULL }
996db26757Swiz };
1006db26757Swiz 
1019fb66d81Schristos static void
1029fb66d81Schristos popup_redraw_cb(const struct tty_ctx *ttyctx)
1039fb66d81Schristos {
1049fb66d81Schristos 	struct popup_data	*pd = ttyctx->arg;
1059fb66d81Schristos 
1069fb66d81Schristos 	pd->c->flags |= CLIENT_REDRAWOVERLAY;
1079fb66d81Schristos }
1089fb66d81Schristos 
1099fb66d81Schristos static int
1109fb66d81Schristos popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
1119fb66d81Schristos {
1129fb66d81Schristos 	struct popup_data	*pd = ttyctx->arg;
1139fb66d81Schristos 
1149fb66d81Schristos 	if (c != pd->c)
1159fb66d81Schristos 		return (0);
1169fb66d81Schristos 	if (pd->c->flags & CLIENT_REDRAWOVERLAY)
1179fb66d81Schristos 		return (0);
1189fb66d81Schristos 
1199fb66d81Schristos 	ttyctx->bigger = 0;
1209fb66d81Schristos 	ttyctx->wox = 0;
1219fb66d81Schristos 	ttyctx->woy = 0;
1229fb66d81Schristos 	ttyctx->wsx = c->tty.sx;
1239fb66d81Schristos 	ttyctx->wsy = c->tty.sy;
1249fb66d81Schristos 
1256db26757Swiz 	if (pd->border_lines == BOX_LINES_NONE) {
1266db26757Swiz 		ttyctx->xoff = ttyctx->rxoff = pd->px;
1276db26757Swiz 		ttyctx->yoff = ttyctx->ryoff = pd->py;
1286db26757Swiz 	} else {
1299fb66d81Schristos 		ttyctx->xoff = ttyctx->rxoff = pd->px + 1;
1309fb66d81Schristos 		ttyctx->yoff = ttyctx->ryoff = pd->py + 1;
1316db26757Swiz 	}
1329fb66d81Schristos 
1339fb66d81Schristos 	return (1);
1349fb66d81Schristos }
1359fb66d81Schristos 
1369fb66d81Schristos static void
1379fb66d81Schristos popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
1389fb66d81Schristos {
1399fb66d81Schristos 	struct popup_data	*pd = ctx->arg;
1409fb66d81Schristos 
1416db26757Swiz 	memcpy(&ttyctx->defaults, &pd->defaults, sizeof ttyctx->defaults);
1426db26757Swiz 	ttyctx->palette = &pd->palette;
1439fb66d81Schristos 	ttyctx->redraw_cb = popup_redraw_cb;
1449fb66d81Schristos 	ttyctx->set_client_cb = popup_set_client_cb;
1459fb66d81Schristos 	ttyctx->arg = pd;
1469fb66d81Schristos }
1479fb66d81Schristos 
1489fb66d81Schristos static struct screen *
1496db26757Swiz popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy)
1509fb66d81Schristos {
1516db26757Swiz 	struct popup_data	*pd = data;
1529fb66d81Schristos 
1536db26757Swiz 	if (pd->md != NULL)
1546db26757Swiz 		return (menu_mode_cb(c, pd->md, cx, cy));
1556db26757Swiz 
1566db26757Swiz 	if (pd->border_lines == BOX_LINES_NONE) {
1576db26757Swiz 		*cx = pd->px + pd->s.cx;
1586db26757Swiz 		*cy = pd->py + pd->s.cy;
1596db26757Swiz 	} else {
1609fb66d81Schristos 		*cx = pd->px + 1 + pd->s.cx;
1619fb66d81Schristos 		*cy = pd->py + 1 + pd->s.cy;
1626db26757Swiz 	}
1639fb66d81Schristos 	return (&pd->s);
1649fb66d81Schristos }
1659fb66d81Schristos 
1666db26757Swiz /* Return parts of the input range which are not obstructed by the popup. */
1676db26757Swiz static void
1686db26757Swiz popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx,
1696db26757Swiz     struct overlay_ranges *r)
1709fb66d81Schristos {
1716db26757Swiz 	struct popup_data	*pd = data;
1726db26757Swiz 	struct overlay_ranges	 or[2];
1736db26757Swiz 	u_int			 i, j, k = 0;
1749fb66d81Schristos 
1756db26757Swiz 	if (pd->md != NULL) {
1766db26757Swiz 		/* Check each returned range for the menu against the popup. */
1776db26757Swiz 		menu_check_cb(c, pd->md, px, py, nx, r);
1786db26757Swiz 		for (i = 0; i < 2; i++) {
1796db26757Swiz 			server_client_overlay_range(pd->px, pd->py, pd->sx,
1806db26757Swiz 			    pd->sy, r->px[i], py, r->nx[i], &or[i]);
1816db26757Swiz 		}
1826db26757Swiz 
1836db26757Swiz 		/*
1846db26757Swiz 		 * or has up to OVERLAY_MAX_RANGES non-overlapping ranges,
1856db26757Swiz 		 * ordered from left to right. Collect them in the output.
1866db26757Swiz 		 */
1876db26757Swiz 		for (i = 0; i < 2; i++) {
1886db26757Swiz 			/* Each or[i] only has 2 ranges. */
1896db26757Swiz 			for (j = 0; j < 2; j++) {
1906db26757Swiz 				if (or[i].nx[j] > 0) {
1916db26757Swiz 					r->px[k] = or[i].px[j];
1926db26757Swiz 					r->nx[k] = or[i].nx[j];
1936db26757Swiz 					k++;
1946db26757Swiz 				}
1956db26757Swiz 			}
1966db26757Swiz 		}
1976db26757Swiz 
1986db26757Swiz 		/* Zero remaining ranges if any. */
1996db26757Swiz 		for (i = k; i < OVERLAY_MAX_RANGES; i++) {
2006db26757Swiz 			r->px[i] = 0;
2016db26757Swiz 			r->nx[i] = 0;
2026db26757Swiz 		}
2036db26757Swiz 
2046db26757Swiz 		return;
2056db26757Swiz 	}
2066db26757Swiz 
2076db26757Swiz 	server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, nx,
2086db26757Swiz 	    r);
2099fb66d81Schristos }
2109fb66d81Schristos 
2119fb66d81Schristos static void
2126db26757Swiz popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx)
2139fb66d81Schristos {
2146db26757Swiz 	struct popup_data	*pd = data;
2159fb66d81Schristos 	struct tty		*tty = &c->tty;
2169fb66d81Schristos 	struct screen		 s;
2179fb66d81Schristos 	struct screen_write_ctx	 ctx;
2189fb66d81Schristos 	u_int			 i, px = pd->px, py = pd->py;
2196db26757Swiz 	struct colour_palette	*palette = &pd->palette;
2206db26757Swiz 	struct grid_cell	 defaults;
2219fb66d81Schristos 
2229fb66d81Schristos 	screen_init(&s, pd->sx, pd->sy, 0);
2239fb66d81Schristos 	screen_write_start(&ctx, &s);
2249fb66d81Schristos 	screen_write_clearscreen(&ctx, 8);
2256db26757Swiz 
2266db26757Swiz 	if (pd->border_lines == BOX_LINES_NONE) {
2276db26757Swiz 		screen_write_cursormove(&ctx, 0, 0, 0);
2286db26757Swiz 		screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx, pd->sy);
2296db26757Swiz 	} else if (pd->sx > 2 && pd->sy > 2) {
2306db26757Swiz 		screen_write_box(&ctx, pd->sx, pd->sy, pd->border_lines,
2316db26757Swiz 		    &pd->border_cell, pd->title);
2329fb66d81Schristos 		screen_write_cursormove(&ctx, 1, 1, 0);
2336db26757Swiz 		screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx - 2,
2346db26757Swiz 		    pd->sy - 2);
2356db26757Swiz 	}
2369fb66d81Schristos 	screen_write_stop(&ctx);
2379fb66d81Schristos 
2386db26757Swiz 	memcpy(&defaults, &pd->defaults, sizeof defaults);
2396db26757Swiz 	if (defaults.fg == 8)
2406db26757Swiz 		defaults.fg = palette->fg;
2416db26757Swiz 	if (defaults.bg == 8)
2426db26757Swiz 		defaults.bg = palette->bg;
2436db26757Swiz 
2446db26757Swiz 	if (pd->md != NULL) {
2456db26757Swiz 		c->overlay_check = menu_check_cb;
2466db26757Swiz 		c->overlay_data = pd->md;
2476db26757Swiz 	} else {
2489fb66d81Schristos 		c->overlay_check = NULL;
2496db26757Swiz 		c->overlay_data = NULL;
2506db26757Swiz 	}
2519fb66d81Schristos 	for (i = 0; i < pd->sy; i++) {
2526db26757Swiz 		tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &defaults,
2536db26757Swiz 		    palette);
2546db26757Swiz 	}
255c23f9150Swiz 	screen_free(&s);
2566db26757Swiz 	if (pd->md != NULL) {
2576db26757Swiz 		c->overlay_check = NULL;
2586db26757Swiz 		c->overlay_data = NULL;
2596db26757Swiz 		menu_draw_cb(c, pd->md, rctx);
2609fb66d81Schristos 	}
2619fb66d81Schristos 	c->overlay_check = popup_check_cb;
2626db26757Swiz 	c->overlay_data = pd;
2639fb66d81Schristos }
2649fb66d81Schristos 
2659fb66d81Schristos static void
2666db26757Swiz popup_free_cb(struct client *c, void *data)
2679fb66d81Schristos {
2686db26757Swiz 	struct popup_data	*pd = data;
2699fb66d81Schristos 	struct cmdq_item	*item = pd->item;
2709fb66d81Schristos 
2716db26757Swiz 	if (pd->md != NULL)
2726db26757Swiz 		menu_free_cb(c, pd->md);
2736db26757Swiz 
2749fb66d81Schristos 	if (pd->cb != NULL)
2759fb66d81Schristos 		pd->cb(pd->status, pd->arg);
2769fb66d81Schristos 
2779fb66d81Schristos 	if (item != NULL) {
2789fb66d81Schristos 		if (cmdq_get_client(item) != NULL &&
2799fb66d81Schristos 		    cmdq_get_client(item)->session == NULL)
2809fb66d81Schristos 			cmdq_get_client(item)->retval = pd->status;
2819fb66d81Schristos 		cmdq_continue(item);
2829fb66d81Schristos 	}
2839fb66d81Schristos 	server_client_unref(pd->c);
2849fb66d81Schristos 
2859fb66d81Schristos 	if (pd->job != NULL)
2869fb66d81Schristos 		job_free(pd->job);
2879fb66d81Schristos 	input_free(pd->ictx);
2889fb66d81Schristos 
2899fb66d81Schristos 	screen_free(&pd->s);
2906db26757Swiz 	colour_palette_free(&pd->palette);
2916db26757Swiz 
2926db26757Swiz 	free(pd->title);
2939fb66d81Schristos 	free(pd);
2949fb66d81Schristos }
2959fb66d81Schristos 
2969fb66d81Schristos static void
2976db26757Swiz popup_resize_cb(__unused struct client *c, void *data)
2986db26757Swiz {
2996db26757Swiz 	struct popup_data	*pd = data;
3006db26757Swiz 	struct tty		*tty = &c->tty;
3016db26757Swiz 
3026db26757Swiz 	if (pd == NULL)
3036db26757Swiz 		return;
3046db26757Swiz 	if (pd->md != NULL)
3056db26757Swiz 		menu_free_cb(c, pd->md);
3066db26757Swiz 
3076db26757Swiz 	/* Adjust position and size. */
3086db26757Swiz 	if (pd->psy > tty->sy)
3096db26757Swiz 		pd->sy = tty->sy;
3106db26757Swiz 	else
3116db26757Swiz 		pd->sy = pd->psy;
3126db26757Swiz 	if (pd->psx > tty->sx)
3136db26757Swiz 		pd->sx = tty->sx;
3146db26757Swiz 	else
3156db26757Swiz 		pd->sx = pd->psx;
3166db26757Swiz 	if (pd->ppy + pd->sy > tty->sy)
3176db26757Swiz 		pd->py = tty->sy - pd->sy;
3186db26757Swiz 	else
3196db26757Swiz 		pd->py = pd->ppy;
3206db26757Swiz 	if (pd->ppx + pd->sx > tty->sx)
3216db26757Swiz 		pd->px = tty->sx - pd->sx;
3226db26757Swiz 	else
3236db26757Swiz 		pd->px = pd->ppx;
3246db26757Swiz 
3256db26757Swiz 	/* Avoid zero size screens. */
3266db26757Swiz 	if (pd->border_lines == BOX_LINES_NONE) {
3276db26757Swiz 		screen_resize(&pd->s, pd->sx, pd->sy, 0);
3286db26757Swiz 		if (pd->job != NULL)
3296db26757Swiz 			job_resize(pd->job, pd->sx, pd->sy );
3306db26757Swiz 	} else if (pd->sx > 2 && pd->sy > 2) {
3316db26757Swiz 		screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
3326db26757Swiz 		if (pd->job != NULL)
3336db26757Swiz 			job_resize(pd->job, pd->sx - 2, pd->sy - 2);
3346db26757Swiz 	}
3356db26757Swiz }
3366db26757Swiz 
3376db26757Swiz static void
3386db26757Swiz popup_make_pane(struct popup_data *pd, enum layout_type type)
3396db26757Swiz {
3406db26757Swiz 	struct client		*c = pd->c;
3416db26757Swiz 	struct session		*s = c->session;
3426db26757Swiz 	struct window		*w = s->curw->window;
3436db26757Swiz 	struct layout_cell	*lc;
3446db26757Swiz 	struct window_pane	*wp = w->active, *new_wp;
3456db26757Swiz 	u_int			 hlimit;
3466db26757Swiz 	const char		*shell;
3476db26757Swiz 
348*f8cf1a91Swiz 	window_unzoom(w, 1);
3496db26757Swiz 
3506db26757Swiz 	lc = layout_split_pane(wp, type, -1, 0);
3516db26757Swiz 	hlimit = options_get_number(s->options, "history-limit");
3526db26757Swiz 	new_wp = window_add_pane(wp->window, NULL, hlimit, 0);
3536db26757Swiz 	layout_assign_pane(lc, new_wp, 0);
3546db26757Swiz 
3556db26757Swiz 	new_wp->fd = job_transfer(pd->job, &new_wp->pid, new_wp->tty,
3566db26757Swiz 	    sizeof new_wp->tty);
3576db26757Swiz 	pd->job = NULL;
3586db26757Swiz 
3596db26757Swiz 	screen_set_title(&pd->s, new_wp->base.title);
3606db26757Swiz 	screen_free(&new_wp->base);
3616db26757Swiz 	memcpy(&new_wp->base, &pd->s, sizeof wp->base);
3626db26757Swiz 	screen_resize(&new_wp->base, new_wp->sx, new_wp->sy, 1);
3636db26757Swiz 	screen_init(&pd->s, 1, 1, 0);
3646db26757Swiz 
3656db26757Swiz 	shell = options_get_string(s->options, "default-shell");
3666db26757Swiz 	if (!checkshell(shell))
3676db26757Swiz 		shell = _PATH_BSHELL;
3686db26757Swiz 	new_wp->shell = xstrdup(shell);
3696db26757Swiz 
3706db26757Swiz 	window_pane_set_event(new_wp);
3716db26757Swiz 	window_set_active_pane(w, new_wp, 1);
3726db26757Swiz 	new_wp->flags |= PANE_CHANGED;
3736db26757Swiz 
3746db26757Swiz 	pd->close = 1;
3756db26757Swiz }
3766db26757Swiz 
3776db26757Swiz static void
3786db26757Swiz popup_menu_done(__unused struct menu *menu, __unused u_int choice,
3796db26757Swiz     key_code key, void *data)
3806db26757Swiz {
3816db26757Swiz 	struct popup_data	*pd = data;
3826db26757Swiz 	struct client		*c = pd->c;
3836db26757Swiz 	struct paste_buffer	*pb;
3846db26757Swiz 	const char		*buf;
3856db26757Swiz 	size_t			 len;
3866db26757Swiz 
3876db26757Swiz 	pd->md = NULL;
3886db26757Swiz 	pd->menu = NULL;
3896db26757Swiz 	server_redraw_client(pd->c);
3906db26757Swiz 
3916db26757Swiz 	switch (key) {
3926db26757Swiz 	case 'p':
3936db26757Swiz 		pb = paste_get_top(NULL);
3946db26757Swiz 		if (pb != NULL) {
3956db26757Swiz 			buf = paste_buffer_data(pb, &len);
3966db26757Swiz 			bufferevent_write(job_get_event(pd->job), buf, len);
3976db26757Swiz 		}
3986db26757Swiz 		break;
3996db26757Swiz 	case 'F':
4006db26757Swiz 		pd->sx = c->tty.sx;
4016db26757Swiz 		pd->sy = c->tty.sy;
4026db26757Swiz 		pd->px = 0;
4036db26757Swiz 		pd->py = 0;
4046db26757Swiz 		server_redraw_client(c);
4056db26757Swiz 		break;
4066db26757Swiz 	case 'C':
4076db26757Swiz 		pd->px = c->tty.sx / 2 - pd->sx / 2;
4086db26757Swiz 		pd->py = c->tty.sy / 2 - pd->sy / 2;
4096db26757Swiz 		server_redraw_client(c);
4106db26757Swiz 		break;
4116db26757Swiz 	case 'h':
4126db26757Swiz 		popup_make_pane(pd, LAYOUT_LEFTRIGHT);
4136db26757Swiz 		break;
4146db26757Swiz 	case 'v':
4156db26757Swiz 		popup_make_pane(pd, LAYOUT_TOPBOTTOM);
4166db26757Swiz 		break;
4176db26757Swiz 	case 'q':
4186db26757Swiz 		pd->close = 1;
4196db26757Swiz 		break;
4206db26757Swiz 	}
4216db26757Swiz }
4226db26757Swiz 
4236db26757Swiz static void
4249fb66d81Schristos popup_handle_drag(struct client *c, struct popup_data *pd,
4259fb66d81Schristos     struct mouse_event *m)
4269fb66d81Schristos {
4279fb66d81Schristos 	u_int	px, py;
4289fb66d81Schristos 
4299fb66d81Schristos 	if (!MOUSE_DRAG(m->b))
4309fb66d81Schristos 		pd->dragging = OFF;
4319fb66d81Schristos 	else if (pd->dragging == MOVE) {
4329fb66d81Schristos 		if (m->x < pd->dx)
4339fb66d81Schristos 			px = 0;
4349fb66d81Schristos 		else if (m->x - pd->dx + pd->sx > c->tty.sx)
4359fb66d81Schristos 			px = c->tty.sx - pd->sx;
4369fb66d81Schristos 		else
4379fb66d81Schristos 			px = m->x - pd->dx;
4389fb66d81Schristos 		if (m->y < pd->dy)
4399fb66d81Schristos 			py = 0;
4409fb66d81Schristos 		else if (m->y - pd->dy + pd->sy > c->tty.sy)
4419fb66d81Schristos 			py = c->tty.sy - pd->sy;
4429fb66d81Schristos 		else
4439fb66d81Schristos 			py = m->y - pd->dy;
4449fb66d81Schristos 		pd->px = px;
4459fb66d81Schristos 		pd->py = py;
4469fb66d81Schristos 		pd->dx = m->x - pd->px;
4479fb66d81Schristos 		pd->dy = m->y - pd->py;
4486db26757Swiz 		pd->ppx = px;
4496db26757Swiz 		pd->ppy = py;
4509fb66d81Schristos 		server_redraw_client(c);
4519fb66d81Schristos 	} else if (pd->dragging == SIZE) {
4526db26757Swiz 		if (pd->border_lines == BOX_LINES_NONE) {
4536db26757Swiz 			if (m->x < pd->px + 1)
4546db26757Swiz 				return;
4556db26757Swiz 			if (m->y < pd->py + 1)
4566db26757Swiz 				return;
4576db26757Swiz 		} else {
4589fb66d81Schristos 			if (m->x < pd->px + 3)
4599fb66d81Schristos 				return;
4609fb66d81Schristos 			if (m->y < pd->py + 3)
4619fb66d81Schristos 				return;
4626db26757Swiz 		}
4639fb66d81Schristos 		pd->sx = m->x - pd->px;
4649fb66d81Schristos 		pd->sy = m->y - pd->py;
4656db26757Swiz 		pd->psx = pd->sx;
4666db26757Swiz 		pd->psy = pd->sy;
4679fb66d81Schristos 
4686db26757Swiz 		if (pd->border_lines == BOX_LINES_NONE) {
4696db26757Swiz 			screen_resize(&pd->s, pd->sx, pd->sy, 0);
4706db26757Swiz 			if (pd->job != NULL)
4716db26757Swiz 				job_resize(pd->job, pd->sx, pd->sy);
4726db26757Swiz 		} else {
4739fb66d81Schristos 			screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
4749fb66d81Schristos 			if (pd->job != NULL)
4759fb66d81Schristos 				job_resize(pd->job, pd->sx - 2, pd->sy - 2);
4766db26757Swiz 		}
4779fb66d81Schristos 		server_redraw_client(c);
4789fb66d81Schristos 	}
4799fb66d81Schristos }
4809fb66d81Schristos 
4819fb66d81Schristos static int
4826db26757Swiz popup_key_cb(struct client *c, void *data, struct key_event *event)
4839fb66d81Schristos {
4846db26757Swiz 	struct popup_data	*pd = data;
4859fb66d81Schristos 	struct mouse_event	*m = &event->m;
4869fb66d81Schristos 	const char		*buf;
4879fb66d81Schristos 	size_t			 len;
4886db26757Swiz 	u_int			 px, py, x;
4896db26757Swiz 	enum { NONE, LEFT, RIGHT, TOP, BOTTOM } border = NONE;
4906db26757Swiz 
4916db26757Swiz 	if (pd->md != NULL) {
4926db26757Swiz 		if (menu_key_cb(c, pd->md, event) == 1) {
4936db26757Swiz 			pd->md = NULL;
4946db26757Swiz 			pd->menu = NULL;
4956db26757Swiz 			if (pd->close)
4966db26757Swiz 				server_client_clear_overlay(c);
4976db26757Swiz 			else
4986db26757Swiz 				server_redraw_client(c);
4996db26757Swiz 		}
5006db26757Swiz 		return (0);
5016db26757Swiz 	}
5029fb66d81Schristos 
5039fb66d81Schristos 	if (KEYC_IS_MOUSE(event->key)) {
5049fb66d81Schristos 		if (pd->dragging != OFF) {
5059fb66d81Schristos 			popup_handle_drag(c, pd, m);
5069fb66d81Schristos 			goto out;
5079fb66d81Schristos 		}
5089fb66d81Schristos 		if (m->x < pd->px ||
5099fb66d81Schristos 		    m->x > pd->px + pd->sx - 1 ||
5109fb66d81Schristos 		    m->y < pd->py ||
5119fb66d81Schristos 		    m->y > pd->py + pd->sy - 1) {
5126db26757Swiz 			if (MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3)
5136db26757Swiz 				goto menu;
5149fb66d81Schristos 			return (0);
5159fb66d81Schristos 		}
5166db26757Swiz 		if (pd->border_lines != BOX_LINES_NONE) {
5176db26757Swiz 			if (m->x == pd->px)
5186db26757Swiz 				border = LEFT;
5196db26757Swiz 			else if (m->x == pd->px + pd->sx - 1)
5206db26757Swiz 				border = RIGHT;
5216db26757Swiz 			else if (m->y == pd->py)
5226db26757Swiz 				border = TOP;
5236db26757Swiz 			else if (m->y == pd->py + pd->sy - 1)
5246db26757Swiz 				border = BOTTOM;
5256db26757Swiz 		}
5266db26757Swiz 		if ((m->b & MOUSE_MASK_MODIFIERS) == 0 &&
5276db26757Swiz 		    MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3 &&
5286db26757Swiz 		    (border == LEFT || border == TOP))
5296db26757Swiz 		    goto menu;
5306db26757Swiz 		if (((m->b & MOUSE_MASK_MODIFIERS) == MOUSE_MASK_META) ||
5316db26757Swiz 		    border != NONE) {
5329fb66d81Schristos 			if (!MOUSE_DRAG(m->b))
5339fb66d81Schristos 				goto out;
5346db26757Swiz 			if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_1)
5359fb66d81Schristos 				pd->dragging = MOVE;
5366db26757Swiz 			else if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_3)
5379fb66d81Schristos 				pd->dragging = SIZE;
5389fb66d81Schristos 			pd->dx = m->lx - pd->px;
5399fb66d81Schristos 			pd->dy = m->ly - pd->py;
5409fb66d81Schristos 			goto out;
5419fb66d81Schristos 		}
5429fb66d81Schristos 	}
5439fb66d81Schristos 	if ((((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0) ||
5449fb66d81Schristos 	    pd->job == NULL) &&
545*f8cf1a91Swiz 	    (event->key == '\033' || event->key == ('c'|KEYC_CTRL)))
5469fb66d81Schristos 		return (1);
5479fb66d81Schristos 	if (pd->job != NULL) {
5489fb66d81Schristos 		if (KEYC_IS_MOUSE(event->key)) {
5499fb66d81Schristos 			/* Must be inside, checked already. */
5506db26757Swiz 			if (pd->border_lines == BOX_LINES_NONE) {
5516db26757Swiz 				px = m->x - pd->px;
5526db26757Swiz 				py = m->y - pd->py;
5536db26757Swiz 			} else {
5546db26757Swiz 				px = m->x - pd->px - 1;
5556db26757Swiz 				py = m->y - pd->py - 1;
5566db26757Swiz 			}
5576db26757Swiz 			if (!input_key_get_mouse(&pd->s, m, px, py, &buf, &len))
5589fb66d81Schristos 				return (0);
5599fb66d81Schristos 			bufferevent_write(job_get_event(pd->job), buf, len);
5609fb66d81Schristos 			return (0);
5619fb66d81Schristos 		}
5629fb66d81Schristos 		input_key(&pd->s, job_get_event(pd->job), event->key);
5639fb66d81Schristos 	}
5649fb66d81Schristos 	return (0);
5659fb66d81Schristos 
5666db26757Swiz menu:
5676db26757Swiz 	pd->menu = menu_create("");
5686db26757Swiz 	if (pd->flags & POPUP_INTERNAL) {
5696db26757Swiz 		menu_add_items(pd->menu, popup_internal_menu_items, NULL, c,
5706db26757Swiz 		    NULL);
5716db26757Swiz 	} else
5726db26757Swiz 		menu_add_items(pd->menu, popup_menu_items, NULL, c, NULL);
5736db26757Swiz 	if (m->x >= (pd->menu->width + 4) / 2)
5746db26757Swiz 		x = m->x - (pd->menu->width + 4) / 2;
5756db26757Swiz 	else
5766db26757Swiz 		x = 0;
577c23f9150Swiz 	pd->md = menu_prepare(pd->menu, 0, 0, NULL, x, m->y, c,
578c23f9150Swiz 	    BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, popup_menu_done, pd);
5796db26757Swiz 	c->flags |= CLIENT_REDRAWOVERLAY;
5806db26757Swiz 
5819fb66d81Schristos out:
5829fb66d81Schristos 	pd->lx = m->x;
5839fb66d81Schristos 	pd->ly = m->y;
5849fb66d81Schristos 	pd->lb = m->b;
5859fb66d81Schristos 	return (0);
5869fb66d81Schristos }
5879fb66d81Schristos 
5889fb66d81Schristos static void
5899fb66d81Schristos popup_job_update_cb(struct job *job)
5909fb66d81Schristos {
5919fb66d81Schristos 	struct popup_data	*pd = job_get_data(job);
5929fb66d81Schristos 	struct evbuffer		*evb = job_get_event(job)->input;
5939fb66d81Schristos 	struct client		*c = pd->c;
5949fb66d81Schristos 	struct screen		*s = &pd->s;
5959fb66d81Schristos 	void			*data = EVBUFFER_DATA(evb);
5969fb66d81Schristos 	size_t			 size = EVBUFFER_LENGTH(evb);
5979fb66d81Schristos 
5989fb66d81Schristos 	if (size == 0)
5999fb66d81Schristos 		return;
6009fb66d81Schristos 
6016db26757Swiz 	if (pd->md != NULL) {
6026db26757Swiz 		c->overlay_check = menu_check_cb;
6036db26757Swiz 		c->overlay_data = pd->md;
6046db26757Swiz 	} else {
6059fb66d81Schristos 		c->overlay_check = NULL;
6066db26757Swiz 		c->overlay_data = NULL;
6076db26757Swiz 	}
6089fb66d81Schristos 	input_parse_screen(pd->ictx, s, popup_init_ctx_cb, pd, data, size);
6099fb66d81Schristos 	c->overlay_check = popup_check_cb;
6106db26757Swiz 	c->overlay_data = pd;
6119fb66d81Schristos 
6129fb66d81Schristos 	evbuffer_drain(evb, size);
6139fb66d81Schristos }
6149fb66d81Schristos 
6159fb66d81Schristos static void
6169fb66d81Schristos popup_job_complete_cb(struct job *job)
6179fb66d81Schristos {
6189fb66d81Schristos 	struct popup_data	*pd = job_get_data(job);
6199fb66d81Schristos 	int			 status;
6209fb66d81Schristos 
6219fb66d81Schristos 	status = job_get_status(pd->job);
6229fb66d81Schristos 	if (WIFEXITED(status))
6239fb66d81Schristos 		pd->status = WEXITSTATUS(status);
6249fb66d81Schristos 	else if (WIFSIGNALED(status))
6259fb66d81Schristos 		pd->status = WTERMSIG(status);
6269fb66d81Schristos 	else
6279fb66d81Schristos 		pd->status = 0;
6289fb66d81Schristos 	pd->job = NULL;
6299fb66d81Schristos 
6309fb66d81Schristos 	if ((pd->flags & POPUP_CLOSEEXIT) ||
6319fb66d81Schristos 	    ((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0))
6329fb66d81Schristos 		server_client_clear_overlay(pd->c);
6339fb66d81Schristos }
6349fb66d81Schristos 
6359fb66d81Schristos int
6366db26757Swiz popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
6376db26757Swiz     u_int py, u_int sx, u_int sy, struct environ *env, const char *shellcmd,
6386db26757Swiz     int argc, char **argv, const char *cwd, const char *title, struct client *c,
6396db26757Swiz     struct session *s, const char *style, const char *border_style,
6406db26757Swiz     popup_close_cb cb, void *arg)
6419fb66d81Schristos {
6429fb66d81Schristos 	struct popup_data	*pd;
6436db26757Swiz 	u_int			 jx, jy;
6446db26757Swiz 	struct options		*o;
6456db26757Swiz 	struct style		 sytmp;
6469fb66d81Schristos 
6476db26757Swiz 	if (s != NULL)
6486db26757Swiz 		o = s->curw->window->options;
6496db26757Swiz 	else
6506db26757Swiz 		o = c->session->curw->window->options;
6516db26757Swiz 
6526db26757Swiz 	if (lines == BOX_LINES_DEFAULT)
6536db26757Swiz 		lines = options_get_number(o, "popup-border-lines");
6546db26757Swiz 	if (lines == BOX_LINES_NONE) {
6556db26757Swiz 		if (sx < 1 || sy < 1)
6566db26757Swiz 			return (-1);
6576db26757Swiz 		jx = sx;
6586db26757Swiz 		jy = sy;
6596db26757Swiz 	} else {
6609fb66d81Schristos 		if (sx < 3 || sy < 3)
6619fb66d81Schristos 			return (-1);
6626db26757Swiz 		jx = sx - 2;
6636db26757Swiz 		jy = sy - 2;
6646db26757Swiz 	}
6659fb66d81Schristos 	if (c->tty.sx < sx || c->tty.sy < sy)
6669fb66d81Schristos 		return (-1);
6679fb66d81Schristos 
6689fb66d81Schristos 	pd = xcalloc(1, sizeof *pd);
6699fb66d81Schristos 	pd->item = item;
6709fb66d81Schristos 	pd->flags = flags;
6716db26757Swiz 	if (title != NULL)
6726db26757Swiz 		pd->title = xstrdup(title);
6739fb66d81Schristos 
6749fb66d81Schristos 	pd->c = c;
6759fb66d81Schristos 	pd->c->references++;
6769fb66d81Schristos 
6779fb66d81Schristos 	pd->cb = cb;
6789fb66d81Schristos 	pd->arg = arg;
6799fb66d81Schristos 	pd->status = 128 + SIGHUP;
6809fb66d81Schristos 
6816db26757Swiz 	pd->border_lines = lines;
6826db26757Swiz 	memcpy(&pd->border_cell, &grid_default_cell, sizeof pd->border_cell);
6836db26757Swiz 	style_apply(&pd->border_cell, o, "popup-border-style", NULL);
6846db26757Swiz 	if (border_style != NULL) {
6856db26757Swiz 		style_set(&sytmp, &grid_default_cell);
6866db26757Swiz 		if (style_parse(&sytmp, &pd->border_cell, border_style) == 0) {
6876db26757Swiz 			pd->border_cell.fg = sytmp.gc.fg;
6886db26757Swiz 			pd->border_cell.bg = sytmp.gc.bg;
6896db26757Swiz 		}
6906db26757Swiz 	}
6916db26757Swiz 	pd->border_cell.attr = 0;
6926db26757Swiz 
6936db26757Swiz 	screen_init(&pd->s, jx, jy, 0);
6946db26757Swiz 	colour_palette_init(&pd->palette);
6956db26757Swiz 	colour_palette_from_option(&pd->palette, global_w_options);
6966db26757Swiz 
6976db26757Swiz 	memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults);
6986db26757Swiz 	style_apply(&pd->defaults, o, "popup-style", NULL);
6996db26757Swiz 	if (style != NULL) {
7006db26757Swiz 		style_set(&sytmp, &grid_default_cell);
7016db26757Swiz 		if (style_parse(&sytmp, &pd->defaults, style) == 0) {
7026db26757Swiz 			pd->defaults.fg = sytmp.gc.fg;
7036db26757Swiz 			pd->defaults.bg = sytmp.gc.bg;
7046db26757Swiz 		}
7056db26757Swiz 	}
7066db26757Swiz 	pd->defaults.attr = 0;
7079fb66d81Schristos 
7089fb66d81Schristos 	pd->px = px;
7099fb66d81Schristos 	pd->py = py;
7109fb66d81Schristos 	pd->sx = sx;
7119fb66d81Schristos 	pd->sy = sy;
7129fb66d81Schristos 
7136db26757Swiz 	pd->ppx = px;
7146db26757Swiz 	pd->ppy = py;
7156db26757Swiz 	pd->psx = sx;
7166db26757Swiz 	pd->psy = sy;
7176db26757Swiz 
7186db26757Swiz 	pd->job = job_run(shellcmd, argc, argv, env, s, cwd,
7199fb66d81Schristos 	    popup_job_update_cb, popup_job_complete_cb, NULL, pd,
720*f8cf1a91Swiz 	    JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy);
7216db26757Swiz 	pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette);
7229fb66d81Schristos 
7239fb66d81Schristos 	server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb,
7246db26757Swiz 	    popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd);
7259fb66d81Schristos 	return (0);
7269fb66d81Schristos }
7279fb66d81Schristos 
7289fb66d81Schristos static void
7299fb66d81Schristos popup_editor_free(struct popup_editor *pe)
7309fb66d81Schristos {
7319fb66d81Schristos 	unlink(pe->path);
7329fb66d81Schristos 	free(pe->path);
7339fb66d81Schristos 	free(pe);
7349fb66d81Schristos }
7359fb66d81Schristos 
7369fb66d81Schristos static void
7379fb66d81Schristos popup_editor_close_cb(int status, void *arg)
7389fb66d81Schristos {
7399fb66d81Schristos 	struct popup_editor	*pe = arg;
7409fb66d81Schristos 	FILE			*f;
7419fb66d81Schristos 	char			*buf = NULL;
7429fb66d81Schristos 	off_t			 len = 0;
7439fb66d81Schristos 
7449fb66d81Schristos 	if (status != 0) {
7459fb66d81Schristos 		pe->cb(NULL, 0, pe->arg);
7469fb66d81Schristos 		popup_editor_free(pe);
7479fb66d81Schristos 		return;
7489fb66d81Schristos 	}
7499fb66d81Schristos 
7509fb66d81Schristos 	f = fopen(pe->path, "r");
7519fb66d81Schristos 	if (f != NULL) {
7529fb66d81Schristos 		fseeko(f, 0, SEEK_END);
7539fb66d81Schristos 		len = ftello(f);
7549fb66d81Schristos 		fseeko(f, 0, SEEK_SET);
7559fb66d81Schristos 
7569fb66d81Schristos 		if (len == 0 ||
7579fb66d81Schristos 		    (uintmax_t)len > (uintmax_t)SIZE_MAX ||
7589fb66d81Schristos 		    (buf = malloc(len)) == NULL ||
7599fb66d81Schristos 		    fread(buf, len, 1, f) != 1) {
7609fb66d81Schristos 			free(buf);
7619fb66d81Schristos 			buf = NULL;
7629fb66d81Schristos 			len = 0;
7639fb66d81Schristos 		}
7649fb66d81Schristos 		fclose(f);
7659fb66d81Schristos 	}
7669fb66d81Schristos 	pe->cb(buf, len, pe->arg); /* callback now owns buffer */
7679fb66d81Schristos 	popup_editor_free(pe);
7689fb66d81Schristos }
7699fb66d81Schristos 
7709fb66d81Schristos int
7719fb66d81Schristos popup_editor(struct client *c, const char *buf, size_t len,
7729fb66d81Schristos     popup_finish_edit_cb cb, void *arg)
7739fb66d81Schristos {
7749fb66d81Schristos 	struct popup_editor	*pe;
7759fb66d81Schristos 	int			 fd;
7769fb66d81Schristos 	FILE			*f;
7779fb66d81Schristos 	char			*cmd;
7789fb66d81Schristos 	char			 path[] = _PATH_TMP "tmux.XXXXXXXX";
7799fb66d81Schristos 	const char		*editor;
7809fb66d81Schristos 	u_int			 px, py, sx, sy;
7819fb66d81Schristos 
7829fb66d81Schristos 	editor = options_get_string(global_options, "editor");
7839fb66d81Schristos 	if (*editor == '\0')
7849fb66d81Schristos 		return (-1);
7859fb66d81Schristos 
7869fb66d81Schristos 	fd = mkstemp(path);
7879fb66d81Schristos 	if (fd == -1)
7889fb66d81Schristos 		return (-1);
7899fb66d81Schristos 	f = fdopen(fd, "w");
790c23f9150Swiz 	if (f == NULL)
791c23f9150Swiz 		return (-1);
7929fb66d81Schristos 	if (fwrite(buf, len, 1, f) != 1) {
7939fb66d81Schristos 		fclose(f);
7949fb66d81Schristos 		return (-1);
7959fb66d81Schristos 	}
7969fb66d81Schristos 	fclose(f);
7979fb66d81Schristos 
7989fb66d81Schristos 	pe = xcalloc(1, sizeof *pe);
7999fb66d81Schristos 	pe->path = xstrdup(path);
8009fb66d81Schristos 	pe->cb = cb;
8019fb66d81Schristos 	pe->arg = arg;
8029fb66d81Schristos 
8039fb66d81Schristos 	sx = c->tty.sx * 9 / 10;
8049fb66d81Schristos 	sy = c->tty.sy * 9 / 10;
8059fb66d81Schristos 	px = (c->tty.sx / 2) - (sx / 2);
8069fb66d81Schristos 	py = (c->tty.sy / 2) - (sy / 2);
8079fb66d81Schristos 
8089fb66d81Schristos 	xasprintf(&cmd, "%s %s", editor, path);
8096db26757Swiz 	if (popup_display(POPUP_INTERNAL|POPUP_CLOSEEXIT, BOX_LINES_DEFAULT,
8106db26757Swiz 	    NULL, px, py, sx, sy, NULL, cmd, 0, NULL, _PATH_TMP, NULL, c, NULL,
8116db26757Swiz 	    NULL, NULL, popup_editor_close_cb, pe) != 0) {
8129fb66d81Schristos 		popup_editor_free(pe);
8139fb66d81Schristos 		free(cmd);
8149fb66d81Schristos 		return (-1);
8159fb66d81Schristos 	}
8169fb66d81Schristos 	free(cmd);
8179fb66d81Schristos 	return (0);
8189fb66d81Schristos }
819