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