1/* $OpenBSD$ */
2
3/*
4 * Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/types.h>
20#include <sys/wait.h>
21
22#include <signal.h>
23#include <stdlib.h>
24#include <string.h>
25#include <unistd.h>
26
27#include "tmux.h"
28
29struct popup_data {
30	struct client		 *c;
31	struct cmdq_item	 *item;
32	int			  flags;
33	char			 *title;
34
35	struct grid_cell	  border_cell;
36	enum box_lines		  border_lines;
37
38	struct screen		  s;
39	struct grid_cell	  defaults;
40	struct colour_palette	  palette;
41
42	struct job		 *job;
43	struct input_ctx	 *ictx;
44	int			  status;
45	popup_close_cb		  cb;
46	void			 *arg;
47
48	struct menu		 *menu;
49	struct menu_data	 *md;
50	int			  close;
51
52	/* Current position and size. */
53	u_int			  px;
54	u_int			  py;
55	u_int			  sx;
56	u_int			  sy;
57
58	/* Preferred position and size. */
59	u_int			  ppx;
60	u_int			  ppy;
61	u_int			  psx;
62	u_int			  psy;
63
64	enum { OFF, MOVE, SIZE }  dragging;
65	u_int			  dx;
66	u_int			  dy;
67
68	u_int			  lx;
69	u_int			  ly;
70	u_int			  lb;
71};
72
73struct popup_editor {
74	char			*path;
75	popup_finish_edit_cb	 cb;
76	void			*arg;
77};
78
79static const struct menu_item popup_menu_items[] = {
80	{ "Close", 'q', NULL },
81	{ "#{?buffer_name,Paste #[underscore]#{buffer_name},}", 'p', NULL },
82	{ "", KEYC_NONE, NULL },
83	{ "Fill Space", 'F', NULL },
84	{ "Centre", 'C', NULL },
85	{ "", KEYC_NONE, NULL },
86	{ "To Horizontal Pane", 'h', NULL },
87	{ "To Vertical Pane", 'v', NULL },
88
89	{ NULL, KEYC_NONE, NULL }
90};
91
92static const struct menu_item popup_internal_menu_items[] = {
93	{ "Close", 'q', NULL },
94	{ "", KEYC_NONE, NULL },
95	{ "Fill Space", 'F', NULL },
96	{ "Centre", 'C', NULL },
97
98	{ NULL, KEYC_NONE, NULL }
99};
100
101static void
102popup_redraw_cb(const struct tty_ctx *ttyctx)
103{
104	struct popup_data	*pd = ttyctx->arg;
105
106	pd->c->flags |= CLIENT_REDRAWOVERLAY;
107}
108
109static int
110popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
111{
112	struct popup_data	*pd = ttyctx->arg;
113
114	if (c != pd->c)
115		return (0);
116	if (pd->c->flags & CLIENT_REDRAWOVERLAY)
117		return (0);
118
119	ttyctx->bigger = 0;
120	ttyctx->wox = 0;
121	ttyctx->woy = 0;
122	ttyctx->wsx = c->tty.sx;
123	ttyctx->wsy = c->tty.sy;
124
125	if (pd->border_lines == BOX_LINES_NONE) {
126		ttyctx->xoff = ttyctx->rxoff = pd->px;
127		ttyctx->yoff = ttyctx->ryoff = pd->py;
128	} else {
129		ttyctx->xoff = ttyctx->rxoff = pd->px + 1;
130		ttyctx->yoff = ttyctx->ryoff = pd->py + 1;
131	}
132
133	return (1);
134}
135
136static void
137popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
138{
139	struct popup_data	*pd = ctx->arg;
140
141	memcpy(&ttyctx->defaults, &pd->defaults, sizeof ttyctx->defaults);
142	ttyctx->palette = &pd->palette;
143	ttyctx->redraw_cb = popup_redraw_cb;
144	ttyctx->set_client_cb = popup_set_client_cb;
145	ttyctx->arg = pd;
146}
147
148static struct screen *
149popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy)
150{
151	struct popup_data	*pd = data;
152
153	if (pd->md != NULL)
154		return (menu_mode_cb(c, pd->md, cx, cy));
155
156	if (pd->border_lines == BOX_LINES_NONE) {
157		*cx = pd->px + pd->s.cx;
158		*cy = pd->py + pd->s.cy;
159	} else {
160		*cx = pd->px + 1 + pd->s.cx;
161		*cy = pd->py + 1 + pd->s.cy;
162	}
163	return (&pd->s);
164}
165
166/* Return parts of the input range which are not obstructed by the popup. */
167static void
168popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx,
169    struct overlay_ranges *r)
170{
171	struct popup_data	*pd = data;
172	struct overlay_ranges	 or[2];
173	u_int			 i, j, k = 0;
174
175	if (pd->md != NULL) {
176		/* Check each returned range for the menu against the popup. */
177		menu_check_cb(c, pd->md, px, py, nx, r);
178		for (i = 0; i < 2; i++) {
179			server_client_overlay_range(pd->px, pd->py, pd->sx,
180			    pd->sy, r->px[i], py, r->nx[i], &or[i]);
181		}
182
183		/*
184		 * or has up to OVERLAY_MAX_RANGES non-overlapping ranges,
185		 * ordered from left to right. Collect them in the output.
186		 */
187		for (i = 0; i < 2; i++) {
188			/* Each or[i] only has 2 ranges. */
189			for (j = 0; j < 2; j++) {
190				if (or[i].nx[j] > 0) {
191					r->px[k] = or[i].px[j];
192					r->nx[k] = or[i].nx[j];
193					k++;
194				}
195			}
196		}
197
198		/* Zero remaining ranges if any. */
199		for (i = k; i < OVERLAY_MAX_RANGES; i++) {
200			r->px[i] = 0;
201			r->nx[i] = 0;
202		}
203
204		return;
205	}
206
207	server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, nx,
208	    r);
209}
210
211static void
212popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx)
213{
214	struct popup_data	*pd = data;
215	struct tty		*tty = &c->tty;
216	struct screen		 s;
217	struct screen_write_ctx	 ctx;
218	u_int			 i, px = pd->px, py = pd->py;
219	struct colour_palette	*palette = &pd->palette;
220	struct grid_cell	 defaults;
221
222	screen_init(&s, pd->sx, pd->sy, 0);
223	screen_write_start(&ctx, &s);
224	screen_write_clearscreen(&ctx, 8);
225
226	if (pd->border_lines == BOX_LINES_NONE) {
227		screen_write_cursormove(&ctx, 0, 0, 0);
228		screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx, pd->sy);
229	} else if (pd->sx > 2 && pd->sy > 2) {
230		screen_write_box(&ctx, pd->sx, pd->sy, pd->border_lines,
231		    &pd->border_cell, pd->title);
232		screen_write_cursormove(&ctx, 1, 1, 0);
233		screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx - 2,
234		    pd->sy - 2);
235	}
236	screen_write_stop(&ctx);
237
238	memcpy(&defaults, &pd->defaults, sizeof defaults);
239	if (defaults.fg == 8)
240		defaults.fg = palette->fg;
241	if (defaults.bg == 8)
242		defaults.bg = palette->bg;
243
244	if (pd->md != NULL) {
245		c->overlay_check = menu_check_cb;
246		c->overlay_data = pd->md;
247	} else {
248		c->overlay_check = NULL;
249		c->overlay_data = NULL;
250	}
251	for (i = 0; i < pd->sy; i++) {
252		tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &defaults,
253		    palette);
254	}
255	if (pd->md != NULL) {
256		c->overlay_check = NULL;
257		c->overlay_data = NULL;
258		menu_draw_cb(c, pd->md, rctx);
259	}
260	c->overlay_check = popup_check_cb;
261	c->overlay_data = pd;
262}
263
264static void
265popup_free_cb(struct client *c, void *data)
266{
267	struct popup_data	*pd = data;
268	struct cmdq_item	*item = pd->item;
269
270	if (pd->md != NULL)
271		menu_free_cb(c, pd->md);
272
273	if (pd->cb != NULL)
274		pd->cb(pd->status, pd->arg);
275
276	if (item != NULL) {
277		if (cmdq_get_client(item) != NULL &&
278		    cmdq_get_client(item)->session == NULL)
279			cmdq_get_client(item)->retval = pd->status;
280		cmdq_continue(item);
281	}
282	server_client_unref(pd->c);
283
284	if (pd->job != NULL)
285		job_free(pd->job);
286	input_free(pd->ictx);
287
288	screen_free(&pd->s);
289	colour_palette_free(&pd->palette);
290
291	free(pd->title);
292	free(pd);
293}
294
295static void
296popup_resize_cb(__unused struct client *c, void *data)
297{
298	struct popup_data	*pd = data;
299	struct tty		*tty = &c->tty;
300
301	if (pd == NULL)
302		return;
303	if (pd->md != NULL)
304		menu_free_cb(c, pd->md);
305
306	/* Adjust position and size. */
307	if (pd->psy > tty->sy)
308		pd->sy = tty->sy;
309	else
310		pd->sy = pd->psy;
311	if (pd->psx > tty->sx)
312		pd->sx = tty->sx;
313	else
314		pd->sx = pd->psx;
315	if (pd->ppy + pd->sy > tty->sy)
316		pd->py = tty->sy - pd->sy;
317	else
318		pd->py = pd->ppy;
319	if (pd->ppx + pd->sx > tty->sx)
320		pd->px = tty->sx - pd->sx;
321	else
322		pd->px = pd->ppx;
323
324	/* Avoid zero size screens. */
325	if (pd->border_lines == BOX_LINES_NONE) {
326		screen_resize(&pd->s, pd->sx, pd->sy, 0);
327		if (pd->job != NULL)
328			job_resize(pd->job, pd->sx, pd->sy );
329	} else if (pd->sx > 2 && pd->sy > 2) {
330		screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
331		if (pd->job != NULL)
332			job_resize(pd->job, pd->sx - 2, pd->sy - 2);
333	}
334}
335
336static void
337popup_make_pane(struct popup_data *pd, enum layout_type type)
338{
339	struct client		*c = pd->c;
340	struct session		*s = c->session;
341	struct window		*w = s->curw->window;
342	struct layout_cell	*lc;
343	struct window_pane	*wp = w->active, *new_wp;
344	u_int			 hlimit;
345	const char		*shell;
346
347	window_unzoom(w);
348
349	lc = layout_split_pane(wp, type, -1, 0);
350	hlimit = options_get_number(s->options, "history-limit");
351	new_wp = window_add_pane(wp->window, NULL, hlimit, 0);
352	layout_assign_pane(lc, new_wp, 0);
353
354	new_wp->fd = job_transfer(pd->job, &new_wp->pid, new_wp->tty,
355	    sizeof new_wp->tty);
356	pd->job = NULL;
357
358	screen_set_title(&pd->s, new_wp->base.title);
359	screen_free(&new_wp->base);
360	memcpy(&new_wp->base, &pd->s, sizeof wp->base);
361	screen_resize(&new_wp->base, new_wp->sx, new_wp->sy, 1);
362	screen_init(&pd->s, 1, 1, 0);
363
364	shell = options_get_string(s->options, "default-shell");
365	if (!checkshell(shell))
366		shell = _PATH_BSHELL;
367	new_wp->shell = xstrdup(shell);
368
369	window_pane_set_event(new_wp);
370	window_set_active_pane(w, new_wp, 1);
371	new_wp->flags |= PANE_CHANGED;
372
373	pd->close = 1;
374}
375
376static void
377popup_menu_done(__unused struct menu *menu, __unused u_int choice,
378    key_code key, void *data)
379{
380	struct popup_data	*pd = data;
381	struct client		*c = pd->c;
382	struct paste_buffer	*pb;
383	const char		*buf;
384	size_t			 len;
385
386	pd->md = NULL;
387	pd->menu = NULL;
388	server_redraw_client(pd->c);
389
390	switch (key) {
391	case 'p':
392		pb = paste_get_top(NULL);
393		if (pb != NULL) {
394			buf = paste_buffer_data(pb, &len);
395			bufferevent_write(job_get_event(pd->job), buf, len);
396		}
397		break;
398	case 'F':
399		pd->sx = c->tty.sx;
400		pd->sy = c->tty.sy;
401		pd->px = 0;
402		pd->py = 0;
403		server_redraw_client(c);
404		break;
405	case 'C':
406		pd->px = c->tty.sx / 2 - pd->sx / 2;
407		pd->py = c->tty.sy / 2 - pd->sy / 2;
408		server_redraw_client(c);
409		break;
410	case 'h':
411		popup_make_pane(pd, LAYOUT_LEFTRIGHT);
412		break;
413	case 'v':
414		popup_make_pane(pd, LAYOUT_TOPBOTTOM);
415		break;
416	case 'q':
417		pd->close = 1;
418		break;
419	}
420}
421
422static void
423popup_handle_drag(struct client *c, struct popup_data *pd,
424    struct mouse_event *m)
425{
426	u_int	px, py;
427
428	if (!MOUSE_DRAG(m->b))
429		pd->dragging = OFF;
430	else if (pd->dragging == MOVE) {
431		if (m->x < pd->dx)
432			px = 0;
433		else if (m->x - pd->dx + pd->sx > c->tty.sx)
434			px = c->tty.sx - pd->sx;
435		else
436			px = m->x - pd->dx;
437		if (m->y < pd->dy)
438			py = 0;
439		else if (m->y - pd->dy + pd->sy > c->tty.sy)
440			py = c->tty.sy - pd->sy;
441		else
442			py = m->y - pd->dy;
443		pd->px = px;
444		pd->py = py;
445		pd->dx = m->x - pd->px;
446		pd->dy = m->y - pd->py;
447		pd->ppx = px;
448		pd->ppy = py;
449		server_redraw_client(c);
450	} else if (pd->dragging == SIZE) {
451		if (pd->border_lines == BOX_LINES_NONE) {
452			if (m->x < pd->px + 1)
453				return;
454			if (m->y < pd->py + 1)
455				return;
456		} else {
457			if (m->x < pd->px + 3)
458				return;
459			if (m->y < pd->py + 3)
460				return;
461		}
462		pd->sx = m->x - pd->px;
463		pd->sy = m->y - pd->py;
464		pd->psx = pd->sx;
465		pd->psy = pd->sy;
466
467		if (pd->border_lines == BOX_LINES_NONE) {
468			screen_resize(&pd->s, pd->sx, pd->sy, 0);
469			if (pd->job != NULL)
470				job_resize(pd->job, pd->sx, pd->sy);
471		} else {
472			screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
473			if (pd->job != NULL)
474				job_resize(pd->job, pd->sx - 2, pd->sy - 2);
475		}
476		server_redraw_client(c);
477	}
478}
479
480static int
481popup_key_cb(struct client *c, void *data, struct key_event *event)
482{
483	struct popup_data	*pd = data;
484	struct mouse_event	*m = &event->m;
485	const char		*buf;
486	size_t			 len;
487	u_int			 px, py, x;
488	enum { NONE, LEFT, RIGHT, TOP, BOTTOM } border = NONE;
489
490	if (pd->md != NULL) {
491		if (menu_key_cb(c, pd->md, event) == 1) {
492			pd->md = NULL;
493			pd->menu = NULL;
494			if (pd->close)
495				server_client_clear_overlay(c);
496			else
497				server_redraw_client(c);
498		}
499		return (0);
500	}
501
502	if (KEYC_IS_MOUSE(event->key)) {
503		if (pd->dragging != OFF) {
504			popup_handle_drag(c, pd, m);
505			goto out;
506		}
507		if (m->x < pd->px ||
508		    m->x > pd->px + pd->sx - 1 ||
509		    m->y < pd->py ||
510		    m->y > pd->py + pd->sy - 1) {
511			if (MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3)
512				goto menu;
513			return (0);
514		}
515		if (pd->border_lines != BOX_LINES_NONE) {
516			if (m->x == pd->px)
517				border = LEFT;
518			else if (m->x == pd->px + pd->sx - 1)
519				border = RIGHT;
520			else if (m->y == pd->py)
521				border = TOP;
522			else if (m->y == pd->py + pd->sy - 1)
523				border = BOTTOM;
524		}
525		if ((m->b & MOUSE_MASK_MODIFIERS) == 0 &&
526		    MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3 &&
527		    (border == LEFT || border == TOP))
528		    goto menu;
529		if (((m->b & MOUSE_MASK_MODIFIERS) == MOUSE_MASK_META) ||
530		    border != NONE) {
531			if (!MOUSE_DRAG(m->b))
532				goto out;
533			if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_1)
534				pd->dragging = MOVE;
535			else if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_3)
536				pd->dragging = SIZE;
537			pd->dx = m->lx - pd->px;
538			pd->dy = m->ly - pd->py;
539			goto out;
540		}
541	}
542	if ((((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0) ||
543	    pd->job == NULL) &&
544	    (event->key == '\033' || event->key == '\003'))
545		return (1);
546	if (pd->job != NULL) {
547		if (KEYC_IS_MOUSE(event->key)) {
548			/* Must be inside, checked already. */
549			if (pd->border_lines == BOX_LINES_NONE) {
550				px = m->x - pd->px;
551				py = m->y - pd->py;
552			} else {
553				px = m->x - pd->px - 1;
554				py = m->y - pd->py - 1;
555			}
556			if (!input_key_get_mouse(&pd->s, m, px, py, &buf, &len))
557				return (0);
558			bufferevent_write(job_get_event(pd->job), buf, len);
559			return (0);
560		}
561		input_key(&pd->s, job_get_event(pd->job), event->key);
562	}
563	return (0);
564
565menu:
566	pd->menu = menu_create("");
567	if (pd->flags & POPUP_INTERNAL) {
568		menu_add_items(pd->menu, popup_internal_menu_items, NULL, c,
569		    NULL);
570	} else
571		menu_add_items(pd->menu, popup_menu_items, NULL, c, NULL);
572	if (m->x >= (pd->menu->width + 4) / 2)
573		x = m->x - (pd->menu->width + 4) / 2;
574	else
575		x = 0;
576	pd->md = menu_prepare(pd->menu, 0, NULL, x, m->y, c, NULL,
577	    popup_menu_done, pd);
578	c->flags |= CLIENT_REDRAWOVERLAY;
579
580out:
581	pd->lx = m->x;
582	pd->ly = m->y;
583	pd->lb = m->b;
584	return (0);
585}
586
587static void
588popup_job_update_cb(struct job *job)
589{
590	struct popup_data	*pd = job_get_data(job);
591	struct evbuffer		*evb = job_get_event(job)->input;
592	struct client		*c = pd->c;
593	struct screen		*s = &pd->s;
594	void			*data = EVBUFFER_DATA(evb);
595	size_t			 size = EVBUFFER_LENGTH(evb);
596
597	if (size == 0)
598		return;
599
600	if (pd->md != NULL) {
601		c->overlay_check = menu_check_cb;
602		c->overlay_data = pd->md;
603	} else {
604		c->overlay_check = NULL;
605		c->overlay_data = NULL;
606	}
607	input_parse_screen(pd->ictx, s, popup_init_ctx_cb, pd, data, size);
608	c->overlay_check = popup_check_cb;
609	c->overlay_data = pd;
610
611	evbuffer_drain(evb, size);
612}
613
614static void
615popup_job_complete_cb(struct job *job)
616{
617	struct popup_data	*pd = job_get_data(job);
618	int			 status;
619
620	status = job_get_status(pd->job);
621	if (WIFEXITED(status))
622		pd->status = WEXITSTATUS(status);
623	else if (WIFSIGNALED(status))
624		pd->status = WTERMSIG(status);
625	else
626		pd->status = 0;
627	pd->job = NULL;
628
629	if ((pd->flags & POPUP_CLOSEEXIT) ||
630	    ((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0))
631		server_client_clear_overlay(pd->c);
632}
633
634int
635popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
636    u_int py, u_int sx, u_int sy, struct environ *env, const char *shellcmd,
637    int argc, char **argv, const char *cwd, const char *title, struct client *c,
638    struct session *s, const char* style, const char* border_style,
639    popup_close_cb cb, void *arg)
640{
641	struct popup_data	*pd;
642	u_int			 jx, jy;
643	struct options		*o;
644	struct style		 sytmp;
645
646	if (s != NULL)
647		o = s->curw->window->options;
648	else
649		o = c->session->curw->window->options;
650
651	if (lines == BOX_LINES_DEFAULT)
652		lines = options_get_number(o, "popup-border-lines");
653	if (lines == BOX_LINES_NONE) {
654		if (sx < 1 || sy < 1)
655			return (-1);
656		jx = sx;
657		jy = sy;
658	} else {
659		if (sx < 3 || sy < 3)
660			return (-1);
661		jx = sx - 2;
662		jy = sy - 2;
663	}
664	if (c->tty.sx < sx || c->tty.sy < sy)
665		return (-1);
666
667	pd = xcalloc(1, sizeof *pd);
668	pd->item = item;
669	pd->flags = flags;
670	if (title != NULL)
671		pd->title = xstrdup(title);
672
673	pd->c = c;
674	pd->c->references++;
675
676	pd->cb = cb;
677	pd->arg = arg;
678	pd->status = 128 + SIGHUP;
679
680	pd->border_lines = lines;
681	memcpy(&pd->border_cell, &grid_default_cell, sizeof pd->border_cell);
682	style_apply(&pd->border_cell, o, "popup-border-style", NULL);
683	if (border_style != NULL) {
684		style_set(&sytmp, &grid_default_cell);
685		if (style_parse(&sytmp, &pd->border_cell, border_style) == 0) {
686			pd->border_cell.fg = sytmp.gc.fg;
687			pd->border_cell.bg = sytmp.gc.bg;
688		}
689	}
690	pd->border_cell.attr = 0;
691
692	screen_init(&pd->s, jx, jy, 0);
693	colour_palette_init(&pd->palette);
694	colour_palette_from_option(&pd->palette, global_w_options);
695
696	memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults);
697	style_apply(&pd->defaults, o, "popup-style", NULL);
698	if (style != NULL) {
699		style_set(&sytmp, &grid_default_cell);
700		if (style_parse(&sytmp, &pd->defaults, style) == 0) {
701			pd->defaults.fg = sytmp.gc.fg;
702			pd->defaults.bg = sytmp.gc.bg;
703		}
704	}
705	pd->defaults.attr = 0;
706
707	pd->px = px;
708	pd->py = py;
709	pd->sx = sx;
710	pd->sy = sy;
711
712	pd->ppx = px;
713	pd->ppy = py;
714	pd->psx = sx;
715	pd->psy = sy;
716
717	pd->job = job_run(shellcmd, argc, argv, env, s, cwd,
718	    popup_job_update_cb, popup_job_complete_cb, NULL, pd,
719	    JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE, jx, jy);
720	pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette);
721
722	server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb,
723	    popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd);
724	return (0);
725}
726
727static void
728popup_editor_free(struct popup_editor *pe)
729{
730	unlink(pe->path);
731	free(pe->path);
732	free(pe);
733}
734
735static void
736popup_editor_close_cb(int status, void *arg)
737{
738	struct popup_editor	*pe = arg;
739	FILE			*f;
740	char			*buf = NULL;
741	off_t			 len = 0;
742
743	if (status != 0) {
744		pe->cb(NULL, 0, pe->arg);
745		popup_editor_free(pe);
746		return;
747	}
748
749	f = fopen(pe->path, "r");
750	if (f != NULL) {
751		fseeko(f, 0, SEEK_END);
752		len = ftello(f);
753		fseeko(f, 0, SEEK_SET);
754
755		if (len == 0 ||
756		    (uintmax_t)len > (uintmax_t)SIZE_MAX ||
757		    (buf = malloc(len)) == NULL ||
758		    fread(buf, len, 1, f) != 1) {
759			free(buf);
760			buf = NULL;
761			len = 0;
762		}
763		fclose(f);
764	}
765	pe->cb(buf, len, pe->arg); /* callback now owns buffer */
766	popup_editor_free(pe);
767}
768
769int
770popup_editor(struct client *c, const char *buf, size_t len,
771    popup_finish_edit_cb cb, void *arg)
772{
773	struct popup_editor	*pe;
774	int			 fd;
775	FILE			*f;
776	char			*cmd;
777	char			 path[] = _PATH_TMP "tmux.XXXXXXXX";
778	const char		*editor;
779	u_int			 px, py, sx, sy;
780
781	editor = options_get_string(global_options, "editor");
782	if (*editor == '\0')
783		return (-1);
784
785	fd = mkstemp(path);
786	if (fd == -1)
787		return (-1);
788	f = fdopen(fd, "w");
789	if (fwrite(buf, len, 1, f) != 1) {
790		fclose(f);
791		return (-1);
792	}
793	fclose(f);
794
795	pe = xcalloc(1, sizeof *pe);
796	pe->path = xstrdup(path);
797	pe->cb = cb;
798	pe->arg = arg;
799
800	sx = c->tty.sx * 9 / 10;
801	sy = c->tty.sy * 9 / 10;
802	px = (c->tty.sx / 2) - (sx / 2);
803	py = (c->tty.sy / 2) - (sy / 2);
804
805	xasprintf(&cmd, "%s %s", editor, path);
806	if (popup_display(POPUP_INTERNAL|POPUP_CLOSEEXIT, BOX_LINES_DEFAULT,
807	    NULL, px, py, sx, sy, NULL, cmd, 0, NULL, _PATH_TMP, NULL, c, NULL,
808	    NULL, NULL, popup_editor_close_cb, pe) != 0) {
809		popup_editor_free(pe);
810		free(cmd);
811		return (-1);
812	}
813	free(cmd);
814	return (0);
815}
816