Coverage Report

Created: 2026-05-30 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/popup.c
Line
Count
Source
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
29
struct popup_data {
30
  struct client    *c;
31
  struct cmdq_item   *item;
32
  int       flags;
33
  char       *title;
34
35
  char       *style;
36
  char       *border_style;
37
  struct grid_cell    border_cell;
38
  enum box_lines      border_lines;
39
40
  struct screen     s;
41
  struct grid_cell    defaults;
42
  struct colour_palette   palette;
43
44
  struct visible_ranges   r;
45
  struct visible_ranges   or[2];
46
47
  struct job     *job;
48
  struct input_ctx   *ictx;
49
  int       status;
50
  popup_close_cb      cb;
51
  void       *arg;
52
53
  struct menu    *menu;
54
  struct menu_data   *md;
55
  int       close;
56
57
  /* Current position and size. */
58
  u_int       px;
59
  u_int       py;
60
  u_int       sx;
61
  u_int       sy;
62
63
  /* Preferred position and size. */
64
  u_int       ppx;
65
  u_int       ppy;
66
  u_int       psx;
67
  u_int       psy;
68
69
  enum { OFF, MOVE, SIZE }  dragging;
70
  u_int       dx;
71
  u_int       dy;
72
73
  u_int       lx;
74
  u_int       ly;
75
  u_int       lb;
76
};
77
78
struct popup_editor {
79
  char      *path;
80
  popup_finish_edit_cb   cb;
81
  void      *arg;
82
};
83
84
static const struct menu_item popup_menu_items[] = {
85
  { "Close", 'q', NULL },
86
  { "#{?buffer_name,Paste #[underscore]#{buffer_name},}", 'p', NULL },
87
  { "", KEYC_NONE, NULL },
88
  { "Fill Space", 'F', NULL },
89
  { "Centre", 'C', NULL },
90
  { "", KEYC_NONE, NULL },
91
  { "To Horizontal Pane", 'h', NULL },
92
  { "To Vertical Pane", 'v', NULL },
93
94
  { NULL, KEYC_NONE, NULL }
95
};
96
97
static const struct menu_item popup_internal_menu_items[] = {
98
  { "Close", 'q', NULL },
99
  { "", KEYC_NONE, NULL },
100
  { "Fill Space", 'F', NULL },
101
  { "Centre", 'C', NULL },
102
103
  { NULL, KEYC_NONE, NULL }
104
};
105
106
static void
107
popup_free(struct popup_data *pd)
108
0
{
109
0
  server_client_unref(pd->c);
110
111
0
  if (pd->job != NULL)
112
0
    job_free(pd->job);
113
0
  input_free(pd->ictx);
114
115
0
  free(pd->or[0].ranges);
116
0
  free(pd->or[1].ranges);
117
0
  free(pd->r.ranges);
118
0
  screen_free(&pd->s);
119
0
  colour_palette_free(&pd->palette);
120
121
0
  free(pd->title);
122
0
  free(pd->style);
123
0
  free(pd->border_style);
124
0
  free(pd);
125
0
}
126
127
static void
128
popup_reapply_styles(struct popup_data *pd)
129
0
{
130
0
  struct client   *c = pd->c;
131
0
  struct session    *s = c->session;
132
0
  struct options    *o;
133
0
  struct format_tree  *ft;
134
0
  struct style     sytmp;
135
136
0
  if (s == NULL)
137
0
    return;
138
0
  o = s->curw->window->options;
139
140
0
  ft = format_create_defaults(NULL, c, s, s->curw, NULL);
141
142
  /* Reapply popup style from options. */
143
0
  memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults);
144
0
  style_apply(&pd->defaults, o, "popup-style", ft);
145
0
  if (pd->style != NULL) {
146
0
    style_set(&sytmp, &grid_default_cell);
147
0
    if (style_parse(&sytmp, &pd->defaults, pd->style) == 0) {
148
0
      pd->defaults.fg = sytmp.gc.fg;
149
0
      pd->defaults.bg = sytmp.gc.bg;
150
0
    }
151
0
  }
152
0
  pd->defaults.attr = 0;
153
154
  /* Reapply border style from options. */
155
0
  memcpy(&pd->border_cell, &grid_default_cell, sizeof pd->border_cell);
156
0
  style_apply(&pd->border_cell, o, "popup-border-style", ft);
157
0
  if (pd->border_style != NULL) {
158
0
    style_set(&sytmp, &grid_default_cell);
159
0
    if (style_parse(&sytmp, &pd->border_cell,
160
0
        pd->border_style) == 0) {
161
0
      pd->border_cell.fg = sytmp.gc.fg;
162
0
      pd->border_cell.bg = sytmp.gc.bg;
163
0
    }
164
0
  }
165
0
  pd->border_cell.attr = 0;
166
167
0
  format_free(ft);
168
0
}
169
170
static void
171
popup_redraw_cb(const struct tty_ctx *ttyctx)
172
0
{
173
0
  struct popup_data *pd = ttyctx->arg;
174
175
0
  pd->c->flags |= CLIENT_REDRAWOVERLAY;
176
0
}
177
178
static int
179
popup_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
180
0
{
181
0
  struct popup_data *pd = ttyctx->arg;
182
183
0
  if (c != pd->c)
184
0
    return (0);
185
0
  if (pd->c->flags & CLIENT_REDRAWOVERLAY)
186
0
    return (0);
187
188
0
  ttyctx->wox = 0;
189
0
  ttyctx->woy = 0;
190
0
  ttyctx->wsx = c->tty.sx;
191
0
  ttyctx->wsy = c->tty.sy;
192
193
0
  if (pd->border_lines == BOX_LINES_NONE) {
194
0
    ttyctx->xoff = ttyctx->rxoff = pd->px;
195
0
    ttyctx->yoff = ttyctx->ryoff = pd->py;
196
0
  } else {
197
0
    ttyctx->xoff = ttyctx->rxoff = pd->px + 1;
198
0
    ttyctx->yoff = ttyctx->ryoff = pd->py + 1;
199
0
  }
200
201
0
  return (1);
202
0
}
203
204
static void
205
popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
206
0
{
207
0
  struct popup_data *pd = ctx->arg;
208
209
0
  memcpy(&ttyctx->defaults, &pd->defaults, sizeof ttyctx->defaults);
210
0
  ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER;
211
0
  ttyctx->palette = &pd->palette;
212
0
  ttyctx->redraw_cb = popup_redraw_cb;
213
0
  ttyctx->set_client_cb = popup_set_client_cb;
214
0
  ttyctx->arg = pd;
215
0
}
216
217
static struct screen *
218
popup_mode_cb(__unused struct client *c, void *data, u_int *cx, u_int *cy)
219
0
{
220
0
  struct popup_data *pd = data;
221
222
0
  if (pd->md != NULL)
223
0
    return (menu_mode_cb(c, pd->md, cx, cy));
224
225
0
  if (pd->border_lines == BOX_LINES_NONE) {
226
0
    *cx = pd->px + pd->s.cx;
227
0
    *cy = pd->py + pd->s.cy;
228
0
  } else {
229
0
    *cx = pd->px + 1 + pd->s.cx;
230
0
    *cy = pd->py + 1 + pd->s.cy;
231
0
  }
232
0
  return (&pd->s);
233
0
}
234
235
/* Return parts of the input range which are not obstructed by the popup. */
236
static struct visible_ranges *
237
popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx)
238
0
{
239
0
  struct popup_data *pd = data;
240
0
  struct visible_ranges *r = &pd->r;
241
0
  struct visible_ranges *mr;
242
0
  u_int      i, j, k = 0;
243
244
0
  if (pd->md != NULL) {
245
         /*
246
    * Work out the visible ranges for the menu (that is, the
247
    * ranges not covered by the menu). A menu should have at most
248
    * two ranges and we rely on this being the case.
249
    */
250
0
    mr = menu_check_cb(c, pd->md, px, py, nx);
251
0
    if (mr->used > 2)
252
0
      fatalx("too many menu ranges");
253
254
         /*
255
    * Walk the ranges still visible under the menu and check if
256
    * each is visible under the popup as well. At most there can be
257
    * three total ranges if popup and menu do not intersect.
258
    */
259
0
    for (i = 0; i < mr->used; i++) {
260
0
      server_client_overlay_range(pd->px, pd->py, pd->sx,
261
0
          pd->sy, r->ranges[i].px, py, r->ranges[i].nx,
262
0
          &pd->or[i]);
263
0
    }
264
265
    /*
266
     * We now have nonoverlapping ranges from left to right.
267
     * Combine them together into the output.
268
     */
269
0
    server_client_ensure_ranges(r, 3);
270
0
    for (i = 0; i < mr->used; i++) {
271
0
      for (j = 0; j < pd->or[i].used; j++) {
272
0
        if (pd->or[i].ranges[j].nx == 0)
273
0
          continue;
274
0
        if (k >= 3)
275
0
          fatalx("too many popup & menu ranges");
276
0
        r->ranges[k].px = pd->or[i].ranges[j].px;
277
0
        r->ranges[k].nx = pd->or[i].ranges[j].nx;
278
0
        k++;
279
0
      }
280
0
    }
281
0
    return (r);
282
0
  }
283
284
0
  server_client_overlay_range(pd->px, pd->py, pd->sx, pd->sy, px, py, nx,
285
0
      r);
286
0
  return (r);
287
0
}
288
289
static void
290
popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx)
291
0
{
292
0
  struct popup_data *pd = data;
293
0
  struct tty    *tty = &c->tty;
294
0
  struct screen    s;
295
0
  struct screen_write_ctx  ctx;
296
0
  u_int      i, px = pd->px, py = pd->py;
297
0
  struct colour_palette *palette = &pd->palette;
298
0
  struct grid_cell   defaults;
299
300
0
  popup_reapply_styles(pd);
301
302
0
  screen_init(&s, pd->sx, pd->sy, 0);
303
0
  if (pd->s.hyperlinks != NULL) {
304
0
    hyperlinks_free(s.hyperlinks);
305
0
    s.hyperlinks = hyperlinks_copy(pd->s.hyperlinks);
306
0
  }
307
0
  screen_write_start(&ctx, &s);
308
0
  screen_write_clearscreen(&ctx, 8);
309
310
0
  if (pd->border_lines == BOX_LINES_NONE) {
311
0
    screen_write_cursormove(&ctx, 0, 0, 0);
312
0
    screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx, pd->sy);
313
0
  } else if (pd->sx > 2 && pd->sy > 2) {
314
0
    screen_write_box(&ctx, pd->sx, pd->sy, pd->border_lines,
315
0
        &pd->border_cell, pd->title);
316
0
    screen_write_cursormove(&ctx, 1, 1, 0);
317
0
    screen_write_fast_copy(&ctx, &pd->s, 0, 0, pd->sx - 2,
318
0
        pd->sy - 2);
319
0
  }
320
0
  screen_write_stop(&ctx);
321
322
0
  memcpy(&defaults, &pd->defaults, sizeof defaults);
323
0
  if (defaults.fg == 8)
324
0
    defaults.fg = palette->fg;
325
0
  if (defaults.bg == 8)
326
0
    defaults.bg = palette->bg;
327
328
0
  if (pd->md != NULL) {
329
0
    c->overlay_check = menu_check_cb;
330
0
    c->overlay_data = pd->md;
331
0
  } else {
332
0
    c->overlay_check = NULL;
333
0
    c->overlay_data = NULL;
334
0
  }
335
0
  for (i = 0; i < pd->sy; i++) {
336
0
    tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &defaults,
337
0
        palette);
338
0
  }
339
0
  screen_free(&s);
340
0
  if (pd->md != NULL) {
341
0
    c->overlay_check = NULL;
342
0
    c->overlay_data = NULL;
343
0
    menu_draw_cb(c, pd->md, rctx);
344
0
  }
345
0
  c->overlay_check = popup_check_cb;
346
0
  c->overlay_data = pd;
347
0
}
348
349
static void
350
popup_free_cb(struct client *c, void *data)
351
0
{
352
0
  struct popup_data *pd = data;
353
0
  struct cmdq_item  *item = pd->item;
354
355
0
  if (pd->md != NULL)
356
0
    menu_free_cb(c, pd->md);
357
358
0
  if (pd->cb != NULL)
359
0
    pd->cb(pd->status, pd->arg);
360
361
0
  if (item != NULL) {
362
0
    if (cmdq_get_client(item) != NULL &&
363
0
        cmdq_get_client(item)->session == NULL)
364
0
      cmdq_get_client(item)->retval = pd->status;
365
0
    cmdq_continue(item);
366
0
  }
367
368
0
  popup_free(pd);
369
0
}
370
371
static void
372
popup_resize_cb(__unused struct client *c, void *data)
373
0
{
374
0
  struct popup_data *pd = data;
375
0
  struct tty    *tty = &c->tty;
376
377
0
  if (pd == NULL)
378
0
    return;
379
0
  if (pd->md != NULL)
380
0
    menu_free_cb(c, pd->md);
381
382
  /* Adjust position and size. */
383
0
  if (pd->psy > tty->sy)
384
0
    pd->sy = tty->sy;
385
0
  else
386
0
    pd->sy = pd->psy;
387
0
  if (pd->psx > tty->sx)
388
0
    pd->sx = tty->sx;
389
0
  else
390
0
    pd->sx = pd->psx;
391
0
  if (pd->ppy + pd->sy > tty->sy)
392
0
    pd->py = tty->sy - pd->sy;
393
0
  else
394
0
    pd->py = pd->ppy;
395
0
  if (pd->ppx + pd->sx > tty->sx)
396
0
    pd->px = tty->sx - pd->sx;
397
0
  else
398
0
    pd->px = pd->ppx;
399
400
  /* Avoid zero size screens. */
401
0
  if (pd->border_lines == BOX_LINES_NONE) {
402
0
    screen_resize(&pd->s, pd->sx, pd->sy, 0);
403
0
    if (pd->job != NULL)
404
0
      job_resize(pd->job, pd->sx, pd->sy );
405
0
  } else if (pd->sx > 2 && pd->sy > 2) {
406
0
    screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
407
0
    if (pd->job != NULL)
408
0
      job_resize(pd->job, pd->sx - 2, pd->sy - 2);
409
0
  }
410
0
}
411
412
static void
413
popup_make_pane(struct popup_data *pd, enum layout_type type)
414
0
{
415
0
  struct client   *c = pd->c;
416
0
  struct session    *s = c->session;
417
0
  struct window   *w = s->curw->window;
418
0
  struct layout_cell  *lc;
419
0
  struct window_pane  *wp = w->active, *new_wp;
420
0
  u_int      hlimit;
421
0
  const char    *shell;
422
423
0
  window_unzoom(w, 1);
424
425
0
  lc = layout_split_pane(wp, type, -1, 0);
426
0
  if (lc == NULL)
427
0
    return;
428
0
  hlimit = options_get_number(s->options, "history-limit");
429
0
  new_wp = window_add_pane(wp->window, NULL, hlimit, 0);
430
0
  layout_assign_pane(lc, new_wp, 0);
431
432
0
  if (pd->job != NULL) {
433
0
    new_wp->fd = job_transfer(pd->job, &new_wp->pid, new_wp->tty,
434
0
        sizeof new_wp->tty);
435
0
    pd->job = NULL;
436
0
  }
437
438
0
  screen_set_title(&pd->s, new_wp->base.title);
439
0
  screen_free(&new_wp->base);
440
0
  memcpy(&new_wp->base, &pd->s, sizeof wp->base);
441
0
  screen_resize(&new_wp->base, new_wp->sx, new_wp->sy, 1);
442
0
  screen_init(&pd->s, 1, 1, 0);
443
444
0
  shell = options_get_string(s->options, "default-shell");
445
0
  if (!checkshell(shell))
446
0
    shell = _PATH_BSHELL;
447
0
  new_wp->shell = xstrdup(shell);
448
449
0
  window_pane_set_event(new_wp);
450
0
  window_set_active_pane(w, new_wp, 1);
451
0
  new_wp->flags |= PANE_CHANGED;
452
453
0
  pd->close = 1;
454
0
}
455
456
static void
457
popup_menu_done(__unused struct menu *menu, __unused u_int choice,
458
    key_code key, void *data)
459
0
{
460
0
  struct popup_data *pd = data;
461
0
  struct client   *c = pd->c;
462
0
  struct paste_buffer *pb;
463
0
  const char    *buf;
464
0
  size_t       len;
465
466
0
  pd->md = NULL;
467
0
  pd->menu = NULL;
468
0
  server_redraw_client(pd->c);
469
470
0
  switch (key) {
471
0
  case 'p':
472
0
    pb = paste_get_top(NULL);
473
0
    if (pb != NULL) {
474
0
      buf = paste_buffer_data(pb, &len);
475
0
      bufferevent_write(job_get_event(pd->job), buf, len);
476
0
    }
477
0
    break;
478
0
  case 'F':
479
0
    pd->sx = c->tty.sx;
480
0
    pd->sy = c->tty.sy;
481
0
    pd->px = 0;
482
0
    pd->py = 0;
483
0
    server_redraw_client(c);
484
0
    break;
485
0
  case 'C':
486
0
    pd->px = c->tty.sx / 2 - pd->sx / 2;
487
0
    pd->py = c->tty.sy / 2 - pd->sy / 2;
488
0
    server_redraw_client(c);
489
0
    break;
490
0
  case 'h':
491
0
    popup_make_pane(pd, LAYOUT_LEFTRIGHT);
492
0
    break;
493
0
  case 'v':
494
0
    popup_make_pane(pd, LAYOUT_TOPBOTTOM);
495
0
    break;
496
0
  case 'q':
497
0
    pd->close = 1;
498
0
    break;
499
0
  }
500
0
}
501
502
static void
503
popup_handle_drag(struct client *c, struct popup_data *pd,
504
    struct mouse_event *m)
505
0
{
506
0
  u_int px, py;
507
508
0
  if (!MOUSE_DRAG(m->b))
509
0
    pd->dragging = OFF;
510
0
  else if (pd->dragging == MOVE) {
511
0
    if (m->x < pd->dx)
512
0
      px = 0;
513
0
    else if (m->x - pd->dx + pd->sx > c->tty.sx)
514
0
      px = c->tty.sx - pd->sx;
515
0
    else
516
0
      px = m->x - pd->dx;
517
0
    if (m->y < pd->dy)
518
0
      py = 0;
519
0
    else if (m->y - pd->dy + pd->sy > c->tty.sy)
520
0
      py = c->tty.sy - pd->sy;
521
0
    else
522
0
      py = m->y - pd->dy;
523
0
    pd->px = px;
524
0
    pd->py = py;
525
0
    pd->dx = m->x - pd->px;
526
0
    pd->dy = m->y - pd->py;
527
0
    pd->ppx = px;
528
0
    pd->ppy = py;
529
0
    server_redraw_client(c);
530
0
  } else if (pd->dragging == SIZE) {
531
0
    if (pd->border_lines == BOX_LINES_NONE) {
532
0
      if (m->x < pd->px + 1)
533
0
        return;
534
0
      if (m->y < pd->py + 1)
535
0
        return;
536
0
    } else {
537
0
      if (m->x < pd->px + 3)
538
0
        return;
539
0
      if (m->y < pd->py + 3)
540
0
        return;
541
0
    }
542
0
    pd->sx = m->x - pd->px;
543
0
    pd->sy = m->y - pd->py;
544
0
    pd->psx = pd->sx;
545
0
    pd->psy = pd->sy;
546
547
0
    if (pd->border_lines == BOX_LINES_NONE) {
548
0
      screen_resize(&pd->s, pd->sx, pd->sy, 0);
549
0
      if (pd->job != NULL)
550
0
        job_resize(pd->job, pd->sx, pd->sy);
551
0
    } else {
552
0
      screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 0);
553
0
      if (pd->job != NULL)
554
0
        job_resize(pd->job, pd->sx - 2, pd->sy - 2);
555
0
    }
556
0
    server_redraw_client(c);
557
0
  }
558
0
}
559
560
static int
561
popup_key_cb(struct client *c, void *data, struct key_event *event)
562
0
{
563
0
  struct popup_data *pd = data;
564
0
  struct mouse_event  *m = &event->m;
565
0
  const char    *buf;
566
0
  size_t       len;
567
0
  u_int      px, py, x;
568
0
  enum { NONE, LEFT, RIGHT, TOP, BOTTOM } border = NONE;
569
570
0
  if (pd->md != NULL) {
571
0
    if (menu_key_cb(c, pd->md, event) == 1) {
572
0
      pd->md = NULL;
573
0
      pd->menu = NULL;
574
0
      if (pd->close)
575
0
        server_client_clear_overlay(c);
576
0
      else
577
0
        server_redraw_client(c);
578
0
    }
579
0
    return (0);
580
0
  }
581
582
0
  if (KEYC_IS_MOUSE(event->key)) {
583
0
    if (pd->dragging != OFF) {
584
0
      popup_handle_drag(c, pd, m);
585
0
      goto out;
586
0
    }
587
0
    if (m->x < pd->px ||
588
0
        m->x > pd->px + pd->sx - 1 ||
589
0
        m->y < pd->py ||
590
0
        m->y > pd->py + pd->sy - 1) {
591
0
      if (MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3)
592
0
        goto menu;
593
0
      return (0);
594
0
    }
595
0
    if (pd->border_lines != BOX_LINES_NONE) {
596
0
      if (m->x == pd->px)
597
0
        border = LEFT;
598
0
      else if (m->x == pd->px + pd->sx - 1)
599
0
        border = RIGHT;
600
0
      else if (m->y == pd->py)
601
0
        border = TOP;
602
0
      else if (m->y == pd->py + pd->sy - 1)
603
0
        border = BOTTOM;
604
0
    }
605
0
    if ((m->b & MOUSE_MASK_MODIFIERS) == 0 &&
606
0
        MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_3 &&
607
0
        (border == LEFT || border == TOP))
608
0
        goto menu;
609
0
    if (((m->b & MOUSE_MASK_MODIFIERS) == MOUSE_MASK_META) ||
610
0
        (border != NONE && !MOUSE_DRAG(m->lb))) {
611
0
      if (!MOUSE_DRAG(m->b))
612
0
        goto out;
613
0
      if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_1)
614
0
        pd->dragging = MOVE;
615
0
      else if (MOUSE_BUTTONS(m->lb) == MOUSE_BUTTON_3)
616
0
        pd->dragging = SIZE;
617
0
      pd->dx = m->lx - pd->px;
618
0
      pd->dy = m->ly - pd->py;
619
0
      goto out;
620
0
    }
621
0
  }
622
0
  if ((((pd->flags & (POPUP_CLOSEEXIT|POPUP_CLOSEEXITZERO)) == 0) ||
623
0
      pd->job == NULL) &&
624
0
      (event->key == '\033' || event->key == ('c'|KEYC_CTRL)))
625
0
    return (1);
626
0
  if (pd->job == NULL && (pd->flags & POPUP_CLOSEANYKEY) &&
627
0
      !KEYC_IS_MOUSE(event->key) && !KEYC_IS_PASTE(event->key))
628
0
    return (1);
629
0
  if (pd->job != NULL) {
630
0
    if (KEYC_IS_MOUSE(event->key)) {
631
      /* Must be inside, checked already. */
632
0
      if (pd->border_lines == BOX_LINES_NONE) {
633
0
        px = m->x - pd->px;
634
0
        py = m->y - pd->py;
635
0
      } else {
636
0
        px = m->x - pd->px - 1;
637
0
        py = m->y - pd->py - 1;
638
0
      }
639
0
      if (!input_key_get_mouse(&pd->s, m, px, py, &buf, &len))
640
0
        return (0);
641
0
      bufferevent_write(job_get_event(pd->job), buf, len);
642
0
      return (0);
643
0
    }
644
0
    input_key(&pd->s, job_get_event(pd->job), event->key);
645
0
  }
646
0
  return (0);
647
648
0
menu:
649
0
  pd->menu = menu_create("");
650
0
  if (pd->flags & POPUP_INTERNAL) {
651
0
    menu_add_items(pd->menu, popup_internal_menu_items, NULL, c,
652
0
        NULL);
653
0
  } else
654
0
    menu_add_items(pd->menu, popup_menu_items, NULL, c, NULL);
655
0
  if (m->x >= (pd->menu->width + 4) / 2)
656
0
    x = m->x - (pd->menu->width + 4) / 2;
657
0
  else
658
0
    x = 0;
659
0
  pd->md = menu_prepare(pd->menu, 0, 0, NULL, x, m->y, c,
660
0
      BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, popup_menu_done, pd);
661
0
  c->flags |= CLIENT_REDRAWOVERLAY;
662
663
0
out:
664
0
  pd->lx = m->x;
665
0
  pd->ly = m->y;
666
0
  pd->lb = m->b;
667
0
  return (0);
668
0
}
669
670
static void
671
popup_job_update_cb(struct job *job)
672
0
{
673
0
  struct popup_data *pd = job_get_data(job);
674
0
  struct evbuffer   *evb = job_get_event(job)->input;
675
0
  struct client   *c = pd->c;
676
0
  struct screen   *s = &pd->s;
677
0
  void      *data = EVBUFFER_DATA(evb);
678
0
  size_t       size = EVBUFFER_LENGTH(evb);
679
680
0
  if (size == 0)
681
0
    return;
682
683
0
  if (pd->md != NULL) {
684
0
    c->overlay_check = menu_check_cb;
685
0
    c->overlay_data = pd->md;
686
0
  } else {
687
0
    c->overlay_check = NULL;
688
0
    c->overlay_data = NULL;
689
0
  }
690
0
  input_parse_screen(pd->ictx, s, popup_init_ctx_cb, pd, data, size);
691
0
  c->overlay_check = popup_check_cb;
692
0
  c->overlay_data = pd;
693
694
0
  evbuffer_drain(evb, size);
695
0
}
696
697
static void
698
popup_job_complete_cb(struct job *job)
699
0
{
700
0
  struct popup_data *pd = job_get_data(job);
701
0
  int      status;
702
703
0
  status = job_get_status(pd->job);
704
0
  if (WIFEXITED(status))
705
0
    pd->status = WEXITSTATUS(status);
706
0
  else if (WIFSIGNALED(status))
707
0
    pd->status = WTERMSIG(status);
708
0
  else
709
0
    pd->status = 0;
710
0
  pd->job = NULL;
711
712
0
  if ((pd->flags & POPUP_CLOSEEXIT) ||
713
0
      ((pd->flags & POPUP_CLOSEEXITZERO) && pd->status == 0))
714
0
    server_client_clear_overlay(pd->c);
715
0
}
716
717
int
718
popup_present(struct client *c)
719
0
{
720
0
  return (c->overlay_draw == popup_draw_cb);
721
0
}
722
723
int
724
popup_modify(struct client *c, const char *title, const char *style,
725
  const char *border_style, enum box_lines lines, int flags)
726
0
{
727
0
  struct popup_data   *pd = c->overlay_data;
728
0
  struct style    sytmp;
729
730
0
  if (title != NULL) {
731
0
    if (pd->title != NULL)
732
0
      free(pd->title);
733
0
    pd->title = xstrdup(title);
734
0
  }
735
0
  if (border_style != NULL) {
736
0
    free(pd->border_style);
737
0
    pd->border_style = xstrdup(border_style);
738
0
    style_set(&sytmp, &pd->border_cell);
739
0
    if (style_parse(&sytmp, &pd->border_cell, border_style) == 0) {
740
0
      pd->border_cell.fg = sytmp.gc.fg;
741
0
      pd->border_cell.bg = sytmp.gc.bg;
742
0
    }
743
0
  }
744
0
  if (style != NULL) {
745
0
    free(pd->style);
746
0
    pd->style = xstrdup(style);
747
0
    style_set(&sytmp, &pd->defaults);
748
0
    if (style_parse(&sytmp, &pd->defaults, style) == 0) {
749
0
      pd->defaults.fg = sytmp.gc.fg;
750
0
      pd->defaults.bg = sytmp.gc.bg;
751
0
    }
752
0
  }
753
0
  if (lines != BOX_LINES_DEFAULT) {
754
0
    if (lines == BOX_LINES_NONE && pd->border_lines != lines) {
755
0
      screen_resize(&pd->s, pd->sx, pd->sy, 1);
756
0
      job_resize(pd->job, pd->sx, pd->sy);
757
0
    } else if (pd->border_lines == BOX_LINES_NONE &&
758
0
        pd->border_lines != lines) {
759
0
      screen_resize(&pd->s, pd->sx - 2, pd->sy - 2, 1);
760
0
      job_resize(pd->job, pd->sx - 2, pd->sy - 2);
761
0
    }
762
0
    pd->border_lines = lines;
763
0
    tty_resize(&c->tty);
764
0
  }
765
0
  if (flags != -1)
766
0
    pd->flags = flags;
767
768
0
  server_redraw_client(c);
769
0
  return (0);
770
0
}
771
772
int
773
popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
774
    u_int py, u_int sx, u_int sy, struct environ *env, const char *shellcmd,
775
    int argc, char **argv, const char *cwd, const char *title, struct client *c,
776
    struct session *s, const char *style, const char *border_style,
777
    popup_close_cb cb, void *arg)
778
0
{
779
0
  struct popup_data *pd;
780
0
  u_int      jx, jy;
781
0
  struct options    *o;
782
0
  struct style     sytmp;
783
784
0
  if (s != NULL)
785
0
    o = s->curw->window->options;
786
0
  else
787
0
    o = c->session->curw->window->options;
788
789
0
  if (lines == BOX_LINES_DEFAULT)
790
0
    lines = options_get_number(o, "popup-border-lines");
791
0
  if (lines == BOX_LINES_NONE) {
792
0
    if (sx < 1 || sy < 1)
793
0
      return (-1);
794
0
    jx = sx;
795
0
    jy = sy;
796
0
  } else {
797
0
    if (sx < 3 || sy < 3)
798
0
      return (-1);
799
0
    jx = sx - 2;
800
0
    jy = sy - 2;
801
0
  }
802
0
  if (c->tty.sx < sx || c->tty.sy < sy)
803
0
    return (-1);
804
805
0
  pd = xcalloc(1, sizeof *pd);
806
0
  pd->item = item;
807
0
  pd->flags = flags;
808
809
0
  if (title != NULL)
810
0
    pd->title = xstrdup(title);
811
0
  if (style != NULL)
812
0
    pd->style = xstrdup(style);
813
0
  if (border_style != NULL)
814
0
    pd->border_style = xstrdup(border_style);
815
816
0
  pd->c = c;
817
0
  pd->c->references++;
818
819
0
  pd->cb = cb;
820
0
  pd->arg = arg;
821
0
  pd->status = 128 + SIGHUP;
822
823
0
  pd->border_lines = lines;
824
0
  memcpy(&pd->border_cell, &grid_default_cell, sizeof pd->border_cell);
825
0
  style_apply(&pd->border_cell, o, "popup-border-style", NULL);
826
0
  if (border_style != NULL) {
827
0
    style_set(&sytmp, &grid_default_cell);
828
0
    if (style_parse(&sytmp, &pd->border_cell, border_style) == 0) {
829
0
      pd->border_cell.fg = sytmp.gc.fg;
830
0
      pd->border_cell.bg = sytmp.gc.bg;
831
0
    }
832
0
  }
833
0
  pd->border_cell.attr = 0;
834
835
0
  screen_init(&pd->s, jx, jy, 0);
836
0
  screen_set_default_cursor(&pd->s, global_w_options);
837
0
  colour_palette_init(&pd->palette);
838
0
  colour_palette_from_option(&pd->palette, global_w_options);
839
840
0
  memcpy(&pd->defaults, &grid_default_cell, sizeof pd->defaults);
841
0
  style_apply(&pd->defaults, o, "popup-style", NULL);
842
0
  if (style != NULL) {
843
0
    style_set(&sytmp, &grid_default_cell);
844
0
    if (style_parse(&sytmp, &pd->defaults, style) == 0) {
845
0
      pd->defaults.fg = sytmp.gc.fg;
846
0
      pd->defaults.bg = sytmp.gc.bg;
847
0
    }
848
0
  }
849
0
  pd->defaults.attr = 0;
850
851
0
  pd->px = px;
852
0
  pd->py = py;
853
0
  pd->sx = sx;
854
0
  pd->sy = sy;
855
856
0
  pd->ppx = px;
857
0
  pd->ppy = py;
858
0
  pd->psx = sx;
859
0
  pd->psy = sy;
860
861
0
  if (flags & POPUP_NOJOB)
862
0
    pd->ictx = input_init(NULL, NULL, &pd->palette, NULL);
863
0
  else {
864
0
    pd->job = job_run(shellcmd, argc, argv, env, s, cwd,
865
0
        popup_job_update_cb, popup_job_complete_cb, NULL, pd,
866
0
        JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy);
867
0
    if (pd->job == NULL) {
868
0
      popup_free(pd);
869
0
      return (-1);
870
0
    }
871
0
    pd->ictx = input_init(NULL, job_get_event(pd->job),
872
0
        &pd->palette, c);
873
0
  }
874
875
0
  server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb,
876
0
      popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd);
877
0
  return (0);
878
0
}
879
880
void
881
popup_write(struct client *c, const char *data, size_t size)
882
0
{
883
0
  struct popup_data *pd = c->overlay_data;
884
885
0
  if (!popup_present(c))
886
0
    return;
887
0
  c->overlay_check = NULL;
888
0
  c->overlay_data = NULL;
889
0
  input_parse_screen(pd->ictx, &pd->s, popup_init_ctx_cb, pd, data, size);
890
0
  c->overlay_check = popup_check_cb;
891
0
  c->overlay_data = pd;
892
0
}
893
894
static void
895
popup_editor_free(struct popup_editor *pe)
896
0
{
897
0
  unlink(pe->path);
898
0
  free(pe->path);
899
0
  free(pe);
900
0
}
901
902
static void
903
popup_editor_close_cb(int status, void *arg)
904
0
{
905
0
  struct popup_editor *pe = arg;
906
0
  FILE      *f;
907
0
  char      *buf = NULL;
908
0
  off_t      len = 0;
909
910
0
  if (status != 0) {
911
0
    pe->cb(NULL, 0, pe->arg);
912
0
    popup_editor_free(pe);
913
0
    return;
914
0
  }
915
916
0
  f = fopen(pe->path, "r");
917
0
  if (f != NULL) {
918
0
    fseeko(f, 0, SEEK_END);
919
0
    len = ftello(f);
920
0
    fseeko(f, 0, SEEK_SET);
921
922
0
    if (len == 0 ||
923
0
        (uintmax_t)len > (uintmax_t)SIZE_MAX ||
924
0
        (buf = malloc(len)) == NULL ||
925
0
        fread(buf, len, 1, f) != 1) {
926
0
      free(buf);
927
0
      buf = NULL;
928
0
      len = 0;
929
0
    }
930
0
    fclose(f);
931
0
  }
932
0
  pe->cb(buf, len, pe->arg); /* callback now owns buffer */
933
0
  popup_editor_free(pe);
934
0
}
935
936
int
937
popup_editor(struct client *c, const char *buf, size_t len,
938
    popup_finish_edit_cb cb, void *arg)
939
0
{
940
0
  struct popup_editor *pe;
941
0
  int      fd;
942
0
  FILE      *f;
943
0
  char      *cmd;
944
0
  char       path[] = _PATH_TMP "tmux.XXXXXXXX";
945
0
  const char    *editor;
946
0
  u_int      px, py, sx, sy;
947
948
0
  editor = options_get_string(global_options, "editor");
949
0
  if (*editor == '\0')
950
0
    return (-1);
951
952
0
  fd = mkstemp(path);
953
0
  if (fd == -1)
954
0
    return (-1);
955
0
  f = fdopen(fd, "w");
956
0
  if (f == NULL)
957
0
    return (-1);
958
0
  if (fwrite(buf, len, 1, f) != 1) {
959
0
    fclose(f);
960
0
    return (-1);
961
0
  }
962
0
  fclose(f);
963
964
0
  pe = xcalloc(1, sizeof *pe);
965
0
  pe->path = xstrdup(path);
966
0
  pe->cb = cb;
967
0
  pe->arg = arg;
968
969
0
  sx = c->tty.sx * 9 / 10;
970
0
  sy = c->tty.sy * 9 / 10;
971
0
  px = (c->tty.sx / 2) - (sx / 2);
972
0
  py = (c->tty.sy / 2) - (sy / 2);
973
974
0
  xasprintf(&cmd, "%s %s", editor, path);
975
0
  if (popup_display(POPUP_INTERNAL|POPUP_CLOSEEXIT, BOX_LINES_DEFAULT,
976
0
      NULL, px, py, sx, sy, NULL, cmd, 0, NULL, _PATH_TMP, NULL, c, NULL,
977
0
      NULL, NULL, popup_editor_close_cb, pe) != 0) {
978
0
    popup_editor_free(pe);
979
0
    free(cmd);
980
0
    return (-1);
981
0
  }
982
0
  free(cmd);
983
0
  return (0);
984
0
}