Coverage Report

Created: 2026-04-12 06:58

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