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