Coverage Report

Created: 2026-06-12 06:51

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