Annotation of src/usr.bin/tmux/popup.c, Revision 1.25
1.25 ! nicm 1: /* $OpenBSD: popup.c,v 1.24 2021/08/05 09:43:51 nicm Exp $ */
1.1 nicm 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:
1.17 nicm 22: #include <paths.h>
1.1 nicm 23: #include <signal.h>
24: #include <stdlib.h>
25: #include <string.h>
1.17 nicm 26: #include <unistd.h>
1.1 nicm 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;
1.25 ! nicm 36: struct colour_palette palette;
1.1 nicm 37: struct job *job;
38: struct input_ctx *ictx;
39: int status;
1.14 nicm 40: popup_close_cb cb;
41: void *arg;
1.1 nicm 42:
1.23 nicm 43: /* Current position and size. */
1.1 nicm 44: u_int px;
45: u_int py;
46: u_int sx;
47: u_int sy;
48:
1.23 nicm 49: /* Preferred position and size. */
50: u_int ppx;
51: u_int ppy;
52: u_int psx;
53: u_int psy;
54:
1.1 nicm 55: enum { OFF, MOVE, SIZE } dragging;
56: u_int dx;
57: u_int dy;
58:
59: u_int lx;
60: u_int ly;
61: u_int lb;
62: };
63:
1.17 nicm 64: struct popup_editor {
65: char *path;
66: popup_finish_edit_cb cb;
67: void *arg;
68: };
69:
1.1 nicm 70: static void
1.15 nicm 71: popup_redraw_cb(const struct tty_ctx *ttyctx)
72: {
73: struct popup_data *pd = ttyctx->arg;
74:
75: pd->c->flags |= CLIENT_REDRAWOVERLAY;
76: }
77:
78: static int
79: popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
80: {
81: struct popup_data *pd = ttyctx->arg;
82:
1.16 nicm 83: if (c != pd->c)
84: return (0);
1.15 nicm 85: if (pd->c->flags & CLIENT_REDRAWOVERLAY)
1.16 nicm 86: return (0);
1.15 nicm 87:
88: ttyctx->bigger = 0;
89: ttyctx->wox = 0;
90: ttyctx->woy = 0;
91: ttyctx->wsx = c->tty.sx;
92: ttyctx->wsy = c->tty.sy;
93:
94: ttyctx->xoff = ttyctx->rxoff = pd->px + 1;
95: ttyctx->yoff = ttyctx->ryoff = pd->py + 1;
96:
97: return (1);
98: }
99:
100: static void
101: popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
102: {
103: struct popup_data *pd = ctx->arg;
104:
1.25 ! nicm 105: ttyctx->palette = &pd->palette;
1.15 nicm 106: ttyctx->redraw_cb = popup_redraw_cb;
107: ttyctx->set_client_cb = popup_set_client_cb;
108: ttyctx->arg = pd;
109: }
110:
111: static struct screen *
1.1 nicm 112: popup_mode_cb(struct client *c, u_int *cx, u_int *cy)
113: {
114: struct popup_data *pd = c->overlay_data;
115:
116: *cx = pd->px + 1 + pd->s.cx;
117: *cy = pd->py + 1 + pd->s.cy;
1.15 nicm 118: return (&pd->s);
1.1 nicm 119: }
120:
121: static int
122: popup_check_cb(struct client *c, u_int px, u_int py)
123: {
124: struct popup_data *pd = c->overlay_data;
125:
126: if (px < pd->px || px > pd->px + pd->sx - 1)
127: return (1);
128: if (py < pd->py || py > pd->py + pd->sy - 1)
129: return (1);
130: return (0);
131: }
132:
133: static void
134: popup_draw_cb(struct client *c, __unused struct screen_redraw_ctx *ctx0)
135: {
136: struct popup_data *pd = c->overlay_data;
137: struct tty *tty = &c->tty;
138: struct screen s;
139: struct screen_write_ctx ctx;
140: u_int i, px = pd->px, py = pd->py;
1.25 ! nicm 141: struct colour_palette *palette = &pd->palette;
! 142: struct grid_cell gc;
1.1 nicm 143:
144: screen_init(&s, pd->sx, pd->sy, 0);
1.15 nicm 145: screen_write_start(&ctx, &s);
1.1 nicm 146: screen_write_clearscreen(&ctx, 8);
1.23 nicm 147:
148: /* Skip drawing popup if the terminal is too small. */
149: if (pd->sx > 2 && pd->sy > 2) {
150: screen_write_box(&ctx, pd->sx, pd->sy);
151: screen_write_cursormove(&ctx, 1, 1, 0);
152: screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx - 2,
153: pd->sy - 2);
154: }
1.1 nicm 155: screen_write_stop(&ctx);
156:
1.25 ! nicm 157: memcpy(&gc, &grid_default_cell, sizeof gc);
! 158: gc.fg = pd->palette.fg;
! 159: gc.bg = pd->palette.bg;
! 160:
1.1 nicm 161: c->overlay_check = NULL;
1.25 ! nicm 162: for (i = 0; i < pd->sy; i++)
! 163: tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &gc, palette);
1.1 nicm 164: c->overlay_check = popup_check_cb;
165: }
166:
167: static void
168: popup_free_cb(struct client *c)
169: {
170: struct popup_data *pd = c->overlay_data;
171: struct cmdq_item *item = pd->item;
172:
1.14 nicm 173: if (pd->cb != NULL)
174: pd->cb(pd->status, pd->arg);
175:
1.1 nicm 176: if (item != NULL) {
1.22 nicm 177: if (cmdq_get_client(item) != NULL &&
1.8 nicm 178: cmdq_get_client(item)->session == NULL)
179: cmdq_get_client(item)->retval = pd->status;
1.1 nicm 180: cmdq_continue(item);
181: }
182: server_client_unref(pd->c);
183:
184: if (pd->job != NULL)
185: job_free(pd->job);
1.22 nicm 186: input_free(pd->ictx);
1.1 nicm 187:
188: screen_free(&pd->s);
1.25 ! nicm 189: colour_palette_free(&pd->palette);
1.1 nicm 190: free(pd);
191: }
192:
193: static void
1.23 nicm 194: popup_resize_cb(struct client *c)
195: {
196: struct popup_data *pd = c->overlay_data;
197: struct tty *tty = &c->tty;
198:
199: if (pd == NULL)
200: return;
201:
202: /* Adjust position and size. */
203: if (pd->psy > tty->sy)
204: pd->sy = tty->sy;
205: else
206: pd->sy = pd->psy;
207: if (pd->psx > tty->sx)
208: pd->sx = tty->sx;
209: else
210: pd->sx = pd->psx;
211: if (pd->ppy + pd->sy > tty->sy)
212: pd->py = tty->sy - pd->sy;
213: else
214: pd->py = pd->ppy;
215: if (pd->ppx + pd->sx > tty->sx)
216: pd->px = tty->sx - pd->sx;
217: else
218: pd->px = pd->ppx;
219:
220: /* Avoid zero size screens. */
221: if (pd->sx > 2 && pd->sy > 2) {
222: screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
223: if (pd->job != NULL)
224: job_resize(pd->job, pd->sx - 2, pd->sy - 2);
225: }
226: }
227:
228: static void
1.1 nicm 229: popup_handle_drag(struct client *c, struct popup_data *pd,
230: struct mouse_event *m)
231: {
232: u_int px, py;
233:
234: if (!MOUSE_DRAG(m->b))
235: pd->dragging = OFF;
236: else if (pd->dragging == MOVE) {
237: if (m->x < pd->dx)
238: px = 0;
239: else if (m->x - pd->dx + pd->sx > c->tty.sx)
240: px = c->tty.sx - pd->sx;
241: else
242: px = m->x - pd->dx;
243: if (m->y < pd->dy)
244: py = 0;
245: else if (m->y - pd->dy + pd->sy > c->tty.sy)
246: py = c->tty.sy - pd->sy;
247: else
248: py = m->y - pd->dy;
249: pd->px = px;
250: pd->py = py;
251: pd->dx = m->x - pd->px;
252: pd->dy = m->y - pd->py;
1.23 nicm 253: pd->ppx = px;
254: pd->ppy = py;
1.1 nicm 255: server_redraw_client(c);
256: } else if (pd->dragging == SIZE) {
1.7 nicm 257: if (m->x < pd->px + 3)
1.1 nicm 258: return;
1.7 nicm 259: if (m->y < pd->py + 3)
1.1 nicm 260: return;
261: pd->sx = m->x - pd->px;
262: pd->sy = m->y - pd->py;
1.23 nicm 263: pd->psx = pd->sx;
264: pd->psy = pd->sy;
1.1 nicm 265:
1.20 nicm 266: screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
1.22 nicm 267: if (pd->job != NULL)
1.1 nicm 268: job_resize(pd->job, pd->sx - 2, pd->sy - 2);
269: server_redraw_client(c);
270: }
271: }
272:
273: static int
274: popup_key_cb(struct client *c, struct key_event *event)
275: {
276: struct popup_data *pd = c->overlay_data;
277: struct mouse_event *m = &event->m;
1.22 nicm 278: const char *buf;
1.6 nicm 279: size_t len;
1.1 nicm 280:
281: if (KEYC_IS_MOUSE(event->key)) {
282: if (pd->dragging != OFF) {
283: popup_handle_drag(c, pd, m);
284: goto out;
285: }
286: if (m->x < pd->px ||
287: m->x > pd->px + pd->sx - 1 ||
288: m->y < pd->py ||
289: m->y > pd->py + pd->sy - 1) {
290: if (MOUSE_BUTTONS (m->b) == 1)
291: return (1);
292: return (0);
293: }
294: if ((m->b & MOUSE_MASK_META) ||
295: m->x == pd->px ||
296: m->x == pd->px + pd->sx - 1 ||
297: m->y == pd->py ||
298: m->y == pd->py + pd->sy - 1) {
299: if (!MOUSE_DRAG(m->b))
300: goto out;
301: if (MOUSE_BUTTONS(m->lb) == 0)
302: pd->dragging = MOVE;
303: else if (MOUSE_BUTTONS(m->lb) == 2)
304: pd->dragging = SIZE;
305: pd->dx = m->lx - pd->px;
306: pd->dy = m->ly - pd->py;
307: goto out;
308: }
309: }
310:
1.22 nicm 311: if ((((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0) ||
312: pd->job == NULL) &&
313: (event->key == '\033' || event->key == '\003'))
314: return (1);
315: if (pd->job != NULL) {
1.6 nicm 316: if (KEYC_IS_MOUSE(event->key)) {
317: /* Must be inside, checked already. */
1.21 nicm 318: if (!input_key_get_mouse(&pd->s, m, m->x - pd->px - 1,
319: m->y - pd->py - 1, &buf, &len))
1.6 nicm 320: return (0);
321: bufferevent_write(job_get_event(pd->job), buf, len);
322: return (0);
323: }
1.18 nicm 324: input_key(&pd->s, job_get_event(pd->job), event->key);
1.1 nicm 325: }
1.22 nicm 326: return (0);
1.1 nicm 327:
328: out:
329: pd->lx = m->x;
330: pd->ly = m->y;
331: pd->lb = m->b;
332: return (0);
333: }
334:
335: static void
336: popup_job_update_cb(struct job *job)
337: {
338: struct popup_data *pd = job_get_data(job);
339: struct evbuffer *evb = job_get_event(job)->input;
1.15 nicm 340: struct client *c = pd->c;
1.1 nicm 341: struct screen *s = &pd->s;
342: void *data = EVBUFFER_DATA(evb);
343: size_t size = EVBUFFER_LENGTH(evb);
344:
1.15 nicm 345: if (size == 0)
346: return;
347:
348: c->overlay_check = NULL;
349: input_parse_screen(pd->ictx, s, popup_init_ctx_cb, pd, data, size);
350: c->overlay_check = popup_check_cb;
351:
352: evbuffer_drain(evb, size);
1.1 nicm 353: }
354:
355: static void
356: popup_job_complete_cb(struct job *job)
357: {
358: struct popup_data *pd = job_get_data(job);
359: int status;
360:
361: status = job_get_status(pd->job);
362: if (WIFEXITED(status))
363: pd->status = WEXITSTATUS(status);
364: else if (WIFSIGNALED(status))
365: pd->status = WTERMSIG(status);
366: else
367: pd->status = 0;
368: pd->job = NULL;
369:
1.4 nicm 370: if ((pd->flags & POPUP_CLOSEEXIT) ||
371: ((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0))
1.1 nicm 372: server_client_clear_overlay(pd->c);
373: }
374:
375: int
376: popup_display(int flags, struct cmdq_item *item, u_int px, u_int py, u_int sx,
1.22 nicm 377: u_int sy, const char *shellcmd, int argc, char **argv, const char *cwd,
378: struct client *c, struct session *s, popup_close_cb cb, void *arg)
1.1 nicm 379: {
380: struct popup_data *pd;
381:
382: if (sx < 3 || sy < 3)
383: return (-1);
384: if (c->tty.sx < sx || c->tty.sy < sy)
385: return (-1);
386:
387: pd = xcalloc(1, sizeof *pd);
388: pd->item = item;
389: pd->flags = flags;
390:
391: pd->c = c;
392: pd->c->references++;
393:
1.14 nicm 394: pd->cb = cb;
395: pd->arg = arg;
1.1 nicm 396: pd->status = 128 + SIGHUP;
397:
398: screen_init(&pd->s, sx - 2, sy - 2, 0);
1.25 ! nicm 399: colour_palette_init(&pd->palette);
! 400: colour_palette_from_option(&pd->palette, global_w_options);
1.1 nicm 401:
402: pd->px = px;
403: pd->py = py;
404: pd->sx = sx;
405: pd->sy = sy;
406:
1.23 nicm 407: pd->ppx = px;
408: pd->ppy = py;
409: pd->psx = sx;
410: pd->psy = sy;
411:
1.22 nicm 412: pd->job = job_run(shellcmd, argc, argv, s, cwd,
413: popup_job_update_cb, popup_job_complete_cb, NULL, pd,
414: JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE, pd->sx - 2, pd->sy - 2);
1.25 ! nicm 415: pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette);
1.1 nicm 416:
417: server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb,
1.23 nicm 418: popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd);
1.17 nicm 419: return (0);
420: }
421:
422: static void
423: popup_editor_free(struct popup_editor *pe)
424: {
425: unlink(pe->path);
426: free(pe->path);
427: free(pe);
428: }
429:
430: static void
431: popup_editor_close_cb(int status, void *arg)
432: {
433: struct popup_editor *pe = arg;
434: FILE *f;
435: char *buf = NULL;
436: off_t len = 0;
437:
438: if (status != 0) {
439: pe->cb(NULL, 0, pe->arg);
440: popup_editor_free(pe);
441: return;
442: }
443:
444: f = fopen(pe->path, "r");
445: if (f != NULL) {
446: fseeko(f, 0, SEEK_END);
447: len = ftello(f);
448: fseeko(f, 0, SEEK_SET);
449:
450: if (len == 0 ||
451: (uintmax_t)len > (uintmax_t)SIZE_MAX ||
452: (buf = malloc(len)) == NULL ||
453: fread(buf, len, 1, f) != 1) {
454: free(buf);
455: buf = NULL;
456: len = 0;
457: }
458: fclose(f);
459: }
460: pe->cb(buf, len, pe->arg); /* callback now owns buffer */
461: popup_editor_free(pe);
462: }
463:
464: int
465: popup_editor(struct client *c, const char *buf, size_t len,
466: popup_finish_edit_cb cb, void *arg)
467: {
468: struct popup_editor *pe;
469: int fd;
470: FILE *f;
471: char *cmd;
472: char path[] = _PATH_TMP "tmux.XXXXXXXX";
473: const char *editor;
474: u_int px, py, sx, sy;
475:
476: editor = options_get_string(global_options, "editor");
477: if (*editor == '\0')
478: return (-1);
479:
480: fd = mkstemp(path);
481: if (fd == -1)
482: return (-1);
483: f = fdopen(fd, "w");
484: if (fwrite(buf, len, 1, f) != 1) {
485: fclose(f);
486: return (-1);
487: }
488: fclose(f);
489:
490: pe = xcalloc(1, sizeof *pe);
491: pe->path = xstrdup(path);
492: pe->cb = cb;
493: pe->arg = arg;
494:
495: sx = c->tty.sx * 9 / 10;
496: sy = c->tty.sy * 9 / 10;
497: px = (c->tty.sx / 2) - (sx / 2);
498: py = (c->tty.sy / 2) - (sy / 2);
499:
500: xasprintf(&cmd, "%s %s", editor, path);
1.22 nicm 501: if (popup_display(POPUP_CLOSEEXIT, NULL, px, py, sx, sy, cmd, 0, NULL,
502: _PATH_TMP, c, NULL, popup_editor_close_cb, pe) != 0) {
1.17 nicm 503: popup_editor_free(pe);
504: free(cmd);
505: return (-1);
506: }
507: free(cmd);
1.1 nicm 508: return (0);
509: }