Annotation of src/usr.bin/tmux/popup.c, Revision 1.21
1.21 ! nicm 1: /* $OpenBSD: popup.c,v 1.20 2020/09/22 06:44:52 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: char **lines;
36: u_int nlines;
37:
38: char *cmd;
39: struct cmd_find_state fs;
40: struct screen s;
41:
42: struct job *job;
43: struct input_ctx *ictx;
44: int status;
1.14 nicm 45: popup_close_cb cb;
46: void *arg;
1.1 nicm 47:
48: u_int px;
49: u_int py;
50: u_int sx;
51: u_int sy;
52:
53: enum { OFF, MOVE, SIZE } dragging;
54: u_int dx;
55: u_int dy;
56:
57: u_int lx;
58: u_int ly;
59: u_int lb;
60: };
61:
1.17 nicm 62: struct popup_editor {
63: char *path;
64: popup_finish_edit_cb cb;
65: void *arg;
66: };
67:
1.1 nicm 68: static void
1.15 nicm 69: popup_redraw_cb(const struct tty_ctx *ttyctx)
70: {
71: struct popup_data *pd = ttyctx->arg;
72:
73: pd->c->flags |= CLIENT_REDRAWOVERLAY;
74: }
75:
76: static int
77: popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
78: {
79: struct popup_data *pd = ttyctx->arg;
80:
1.16 nicm 81: if (c != pd->c)
82: return (0);
1.15 nicm 83: if (pd->c->flags & CLIENT_REDRAWOVERLAY)
1.16 nicm 84: return (0);
1.15 nicm 85:
86: ttyctx->bigger = 0;
87: ttyctx->wox = 0;
88: ttyctx->woy = 0;
89: ttyctx->wsx = c->tty.sx;
90: ttyctx->wsy = c->tty.sy;
91:
92: ttyctx->xoff = ttyctx->rxoff = pd->px + 1;
93: ttyctx->yoff = ttyctx->ryoff = pd->py + 1;
94:
95: return (1);
96: }
97:
98: static void
99: popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
100: {
101: struct popup_data *pd = ctx->arg;
102:
103: ttyctx->redraw_cb = popup_redraw_cb;
104: ttyctx->set_client_cb = popup_set_client_cb;
105: ttyctx->arg = pd;
106: }
107:
108: static void
1.1 nicm 109: popup_write_screen(struct client *c, struct popup_data *pd)
110: {
111: struct cmdq_item *item = pd->item;
112: struct screen_write_ctx ctx;
113: char *copy, *next, *loop, *tmp;
114: struct format_tree *ft;
115: u_int i, y;
116:
1.8 nicm 117: ft = format_create(c, item, FORMAT_NONE, 0);
1.1 nicm 118: if (cmd_find_valid_state(&pd->fs))
119: format_defaults(ft, c, pd->fs.s, pd->fs.wl, pd->fs.wp);
120: else
121: format_defaults(ft, c, NULL, NULL, NULL);
122:
1.15 nicm 123: screen_write_start(&ctx, &pd->s);
1.1 nicm 124: screen_write_clearscreen(&ctx, 8);
125:
126: y = 0;
127: for (i = 0; i < pd->nlines; i++) {
128: if (y == pd->sy - 2)
129: break;
130: copy = next = xstrdup(pd->lines[i]);
131: while ((loop = strsep(&next, "\n")) != NULL) {
132: if (y == pd->sy - 2)
133: break;
134: tmp = format_expand(ft, loop);
135: screen_write_cursormove(&ctx, 0, y, 0);
136: format_draw(&ctx, &grid_default_cell, pd->sx - 2, tmp,
137: NULL);
138: free(tmp);
139: y++;
140: }
141: free(copy);
142: }
143:
144: format_free(ft);
145: screen_write_cursormove(&ctx, 0, y, 0);
146: screen_write_stop(&ctx);
147: }
148:
1.15 nicm 149: static struct screen *
1.1 nicm 150: popup_mode_cb(struct client *c, u_int *cx, u_int *cy)
151: {
152: struct popup_data *pd = c->overlay_data;
153:
154: if (pd->ictx == NULL)
155: return (0);
156: *cx = pd->px + 1 + pd->s.cx;
157: *cy = pd->py + 1 + pd->s.cy;
1.15 nicm 158: return (&pd->s);
1.1 nicm 159: }
160:
161: static int
162: popup_check_cb(struct client *c, u_int px, u_int py)
163: {
164: struct popup_data *pd = c->overlay_data;
165:
166: if (px < pd->px || px > pd->px + pd->sx - 1)
167: return (1);
168: if (py < pd->py || py > pd->py + pd->sy - 1)
169: return (1);
170: return (0);
171: }
172:
173: static void
174: popup_draw_cb(struct client *c, __unused struct screen_redraw_ctx *ctx0)
175: {
176: struct popup_data *pd = c->overlay_data;
177: struct tty *tty = &c->tty;
178: struct screen s;
179: struct screen_write_ctx ctx;
180: u_int i, px = pd->px, py = pd->py;
181:
182: screen_init(&s, pd->sx, pd->sy, 0);
1.15 nicm 183: screen_write_start(&ctx, &s);
1.1 nicm 184: screen_write_clearscreen(&ctx, 8);
185: screen_write_box(&ctx, pd->sx, pd->sy);
186: screen_write_cursormove(&ctx, 1, 1, 0);
187: screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx - 2, pd->sy - 2);
188: screen_write_stop(&ctx);
189:
190: c->overlay_check = NULL;
1.15 nicm 191: for (i = 0; i < pd->sy; i++){
192: tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i,
193: &grid_default_cell, NULL);
194: }
1.1 nicm 195: c->overlay_check = popup_check_cb;
196: }
197:
198: static void
199: popup_free_cb(struct client *c)
200: {
201: struct popup_data *pd = c->overlay_data;
202: struct cmdq_item *item = pd->item;
203: u_int i;
204:
1.14 nicm 205: if (pd->cb != NULL)
206: pd->cb(pd->status, pd->arg);
207:
1.1 nicm 208: if (item != NULL) {
209: if (pd->ictx != NULL &&
1.8 nicm 210: cmdq_get_client(item) != NULL &&
211: cmdq_get_client(item)->session == NULL)
212: cmdq_get_client(item)->retval = pd->status;
1.1 nicm 213: cmdq_continue(item);
214: }
215: server_client_unref(pd->c);
216:
217: if (pd->job != NULL)
218: job_free(pd->job);
219: if (pd->ictx != NULL)
220: input_free(pd->ictx);
221:
222: for (i = 0; i < pd->nlines; i++)
223: free(pd->lines[i]);
224: free(pd->lines);
225:
226: screen_free(&pd->s);
227: free(pd->cmd);
228: free(pd);
229: }
230:
231: static void
232: popup_handle_drag(struct client *c, struct popup_data *pd,
233: struct mouse_event *m)
234: {
235: u_int px, py;
236:
237: if (!MOUSE_DRAG(m->b))
238: pd->dragging = OFF;
239: else if (pd->dragging == MOVE) {
240: if (m->x < pd->dx)
241: px = 0;
242: else if (m->x - pd->dx + pd->sx > c->tty.sx)
243: px = c->tty.sx - pd->sx;
244: else
245: px = m->x - pd->dx;
246: if (m->y < pd->dy)
247: py = 0;
248: else if (m->y - pd->dy + pd->sy > c->tty.sy)
249: py = c->tty.sy - pd->sy;
250: else
251: py = m->y - pd->dy;
252: pd->px = px;
253: pd->py = py;
254: pd->dx = m->x - pd->px;
255: pd->dy = m->y - pd->py;
256: server_redraw_client(c);
257: } else if (pd->dragging == SIZE) {
1.7 nicm 258: if (m->x < pd->px + 3)
1.1 nicm 259: return;
1.7 nicm 260: if (m->y < pd->py + 3)
1.1 nicm 261: return;
262: pd->sx = m->x - pd->px;
263: pd->sy = m->y - pd->py;
264:
1.20 nicm 265: screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
1.1 nicm 266: if (pd->ictx == NULL)
267: popup_write_screen(c, pd);
268: else if (pd->job != NULL)
269: job_resize(pd->job, pd->sx - 2, pd->sy - 2);
270: server_redraw_client(c);
271: }
272: }
273:
274: static int
275: popup_key_cb(struct client *c, struct key_event *event)
276: {
277: struct popup_data *pd = c->overlay_data;
278: struct mouse_event *m = &event->m;
279: struct cmd_find_state *fs = &pd->fs;
280: struct format_tree *ft;
1.6 nicm 281: const char *cmd, *buf;
282: size_t len;
1.13 nicm 283: struct cmdq_state *state;
284: enum cmd_parse_status status;
285: char *error;
1.1 nicm 286:
287: if (KEYC_IS_MOUSE(event->key)) {
288: if (pd->dragging != OFF) {
289: popup_handle_drag(c, pd, m);
290: goto out;
291: }
292: if (m->x < pd->px ||
293: m->x > pd->px + pd->sx - 1 ||
294: m->y < pd->py ||
295: m->y > pd->py + pd->sy - 1) {
296: if (MOUSE_BUTTONS (m->b) == 1)
297: return (1);
298: return (0);
299: }
300: if ((m->b & MOUSE_MASK_META) ||
301: m->x == pd->px ||
302: m->x == pd->px + pd->sx - 1 ||
303: m->y == pd->py ||
304: m->y == pd->py + pd->sy - 1) {
305: if (!MOUSE_DRAG(m->b))
306: goto out;
307: if (MOUSE_BUTTONS(m->lb) == 0)
308: pd->dragging = MOVE;
309: else if (MOUSE_BUTTONS(m->lb) == 2)
310: pd->dragging = SIZE;
311: pd->dx = m->lx - pd->px;
312: pd->dy = m->ly - pd->py;
313: goto out;
314: }
315: }
316:
317: if (pd->ictx != NULL && (pd->flags & POPUP_WRITEKEYS)) {
1.3 nicm 318: if (((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0 ||
319: pd->job == NULL) &&
1.1 nicm 320: (event->key == '\033' || event->key == '\003'))
321: return (1);
322: if (pd->job == NULL)
323: return (0);
1.6 nicm 324: if (KEYC_IS_MOUSE(event->key)) {
325: /* Must be inside, checked already. */
1.21 ! nicm 326: if (!input_key_get_mouse(&pd->s, m, m->x - pd->px - 1,
! 327: m->y - pd->py - 1, &buf, &len))
1.6 nicm 328: return (0);
329: bufferevent_write(job_get_event(pd->job), buf, len);
330: return (0);
331: }
1.18 nicm 332: input_key(&pd->s, job_get_event(pd->job), event->key);
1.1 nicm 333: return (0);
334: }
335:
336: if (pd->cmd == NULL)
337: return (1);
338:
339: ft = format_create(NULL, pd->item, FORMAT_NONE, 0);
340: if (cmd_find_valid_state(fs))
341: format_defaults(ft, c, fs->s, fs->wl, fs->wp);
342: else
343: format_defaults(ft, c, NULL, NULL, NULL);
1.19 nicm 344: format_add(ft, "popup_key", "%s", key_string_lookup_key(event->key, 0));
1.1 nicm 345: if (KEYC_IS_MOUSE(event->key)) {
346: format_add(ft, "popup_mouse", "1");
347: format_add(ft, "popup_mouse_x", "%u", m->x - pd->px);
348: format_add(ft, "popup_mouse_y", "%u", m->y - pd->py);
349: }
350: cmd = format_expand(ft, pd->cmd);
351: format_free(ft);
352:
1.13 nicm 353: if (pd->item != NULL)
354: event = cmdq_get_event(pd->item);
355: else
356: event = NULL;
357: state = cmdq_new_state(&pd->fs, event, 0);
358:
359: status = cmd_parse_and_append(cmd, NULL, c, state, &error);
360: if (status == CMD_PARSE_ERROR) {
361: cmdq_append(c, cmdq_get_error(error));
362: free(error);
1.1 nicm 363: }
1.13 nicm 364: cmdq_free_state(state);
365:
1.1 nicm 366: return (1);
367:
368: out:
369: pd->lx = m->x;
370: pd->ly = m->y;
371: pd->lb = m->b;
372: return (0);
373: }
374:
375: static void
376: popup_job_update_cb(struct job *job)
377: {
378: struct popup_data *pd = job_get_data(job);
379: struct evbuffer *evb = job_get_event(job)->input;
1.15 nicm 380: struct client *c = pd->c;
1.1 nicm 381: struct screen *s = &pd->s;
382: void *data = EVBUFFER_DATA(evb);
383: size_t size = EVBUFFER_LENGTH(evb);
384:
1.15 nicm 385: if (size == 0)
386: return;
387:
388: c->overlay_check = NULL;
389: c->tty.flags &= ~TTY_FREEZE;
390:
391: input_parse_screen(pd->ictx, s, popup_init_ctx_cb, pd, data, size);
392:
393: c->tty.flags |= TTY_FREEZE;
394: c->overlay_check = popup_check_cb;
395:
396: evbuffer_drain(evb, size);
1.1 nicm 397: }
398:
399: static void
400: popup_job_complete_cb(struct job *job)
401: {
402: struct popup_data *pd = job_get_data(job);
403: int status;
404:
405: status = job_get_status(pd->job);
406: if (WIFEXITED(status))
407: pd->status = WEXITSTATUS(status);
408: else if (WIFSIGNALED(status))
409: pd->status = WTERMSIG(status);
410: else
411: pd->status = 0;
412: pd->job = NULL;
413:
1.4 nicm 414: if ((pd->flags & POPUP_CLOSEEXIT) ||
415: ((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0))
1.1 nicm 416: server_client_clear_overlay(pd->c);
417: }
418:
419: u_int
1.2 nicm 420: popup_height(u_int nlines, const char **lines)
421: {
422: char *copy, *next, *loop;
423: u_int i, height = 0;
424:
425: for (i = 0; i < nlines; i++) {
426: copy = next = xstrdup(lines[i]);
427: while ((loop = strsep(&next, "\n")) != NULL)
428: height++;
429: free(copy);
430: }
431:
432: return (height);
433: }
434:
435: u_int
1.1 nicm 436: popup_width(struct cmdq_item *item, u_int nlines, const char **lines,
437: struct client *c, struct cmd_find_state *fs)
438: {
439: char *copy, *next, *loop, *tmp;
440: struct format_tree *ft;
441: u_int i, width = 0, tmpwidth;
442:
1.8 nicm 443: ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
1.1 nicm 444: if (fs != NULL && cmd_find_valid_state(fs))
445: format_defaults(ft, c, fs->s, fs->wl, fs->wp);
446: else
447: format_defaults(ft, c, NULL, NULL, NULL);
448:
449: for (i = 0; i < nlines; i++) {
450: copy = next = xstrdup(lines[i]);
451: while ((loop = strsep(&next, "\n")) != NULL) {
452: tmp = format_expand(ft, loop);
453: tmpwidth = format_width(tmp);
454: if (tmpwidth > width)
455: width = tmpwidth;
456: free(tmp);
457: }
1.2 nicm 458: free(copy);
1.1 nicm 459: }
460:
461: format_free(ft);
462: return (width);
463: }
464:
465: int
466: popup_display(int flags, struct cmdq_item *item, u_int px, u_int py, u_int sx,
467: u_int sy, u_int nlines, const char **lines, const char *shellcmd,
468: const char *cmd, const char *cwd, struct client *c,
1.14 nicm 469: struct cmd_find_state *fs, popup_close_cb cb, void *arg)
1.1 nicm 470: {
471: struct popup_data *pd;
472: u_int i;
473: struct session *s;
474: int jobflags;
475:
476: if (sx < 3 || sy < 3)
477: return (-1);
478: if (c->tty.sx < sx || c->tty.sy < sy)
479: return (-1);
480:
481: pd = xcalloc(1, sizeof *pd);
482: pd->item = item;
483: pd->flags = flags;
484:
485: pd->c = c;
486: pd->c->references++;
487:
1.14 nicm 488: pd->cb = cb;
489: pd->arg = arg;
1.1 nicm 490: pd->status = 128 + SIGHUP;
491:
492: if (fs != NULL)
493: cmd_find_copy_state(&pd->fs, fs);
494: screen_init(&pd->s, sx - 2, sy - 2, 0);
495:
496: if (cmd != NULL)
497: pd->cmd = xstrdup(cmd);
498:
499: pd->px = px;
500: pd->py = py;
501: pd->sx = sx;
502: pd->sy = sy;
503:
504: pd->nlines = nlines;
505: if (pd->nlines != 0)
506: pd->lines = xreallocarray(NULL, pd->nlines, sizeof *pd->lines);
507:
508: for (i = 0; i < pd->nlines; i++)
509: pd->lines[i] = xstrdup(lines[i]);
510: popup_write_screen(c, pd);
511:
512: if (shellcmd != NULL) {
513: if (fs != NULL)
514: s = fs->s;
515: else
516: s = NULL;
517: jobflags = JOB_NOWAIT|JOB_PTY;
518: if (flags & POPUP_WRITEKEYS)
519: jobflags |= JOB_KEEPWRITE;
520: pd->job = job_run(shellcmd, s, cwd, popup_job_update_cb,
521: popup_job_complete_cb, NULL, pd, jobflags, pd->sx - 2,
522: pd->sy - 2);
1.5 nicm 523: pd->ictx = input_init(NULL, job_get_event(pd->job));
1.1 nicm 524: }
525:
526: server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb,
527: popup_draw_cb, popup_key_cb, popup_free_cb, pd);
1.17 nicm 528: return (0);
529: }
530:
531: static void
532: popup_editor_free(struct popup_editor *pe)
533: {
534: unlink(pe->path);
535: free(pe->path);
536: free(pe);
537: }
538:
539: static void
540: popup_editor_close_cb(int status, void *arg)
541: {
542: struct popup_editor *pe = arg;
543: FILE *f;
544: char *buf = NULL;
545: off_t len = 0;
546:
547: if (status != 0) {
548: pe->cb(NULL, 0, pe->arg);
549: popup_editor_free(pe);
550: return;
551: }
552:
553: f = fopen(pe->path, "r");
554: if (f != NULL) {
555: fseeko(f, 0, SEEK_END);
556: len = ftello(f);
557: fseeko(f, 0, SEEK_SET);
558:
559: if (len == 0 ||
560: (uintmax_t)len > (uintmax_t)SIZE_MAX ||
561: (buf = malloc(len)) == NULL ||
562: fread(buf, len, 1, f) != 1) {
563: free(buf);
564: buf = NULL;
565: len = 0;
566: }
567: fclose(f);
568: }
569: pe->cb(buf, len, pe->arg); /* callback now owns buffer */
570: popup_editor_free(pe);
571: }
572:
573: int
574: popup_editor(struct client *c, const char *buf, size_t len,
575: popup_finish_edit_cb cb, void *arg)
576: {
577: struct popup_editor *pe;
578: int fd;
579: FILE *f;
580: char *cmd;
581: char path[] = _PATH_TMP "tmux.XXXXXXXX";
582: const char *editor;
583: u_int px, py, sx, sy;
584:
585: editor = options_get_string(global_options, "editor");
586: if (*editor == '\0')
587: return (-1);
588:
589: fd = mkstemp(path);
590: if (fd == -1)
591: return (-1);
592: f = fdopen(fd, "w");
593: if (fwrite(buf, len, 1, f) != 1) {
594: fclose(f);
595: return (-1);
596: }
597: fclose(f);
598:
599: pe = xcalloc(1, sizeof *pe);
600: pe->path = xstrdup(path);
601: pe->cb = cb;
602: pe->arg = arg;
603:
604: sx = c->tty.sx * 9 / 10;
605: sy = c->tty.sy * 9 / 10;
606: px = (c->tty.sx / 2) - (sx / 2);
607: py = (c->tty.sy / 2) - (sy / 2);
608:
609: xasprintf(&cmd, "%s %s", editor, path);
610: if (popup_display(POPUP_WRITEKEYS|POPUP_CLOSEEXIT, NULL, px, py, sx, sy,
611: 0, NULL, cmd, NULL, _PATH_TMP, c, NULL, popup_editor_close_cb,
612: pe) != 0) {
613: popup_editor_free(pe);
614: free(cmd);
615: return (-1);
616: }
617: free(cmd);
1.1 nicm 618: return (0);
619: }