4 * Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
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.
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.
19 #include <sys/types.h>
31 struct cmdq_item
*item
;
35 struct grid_cell border_cell
;
36 enum box_lines border_lines
;
39 struct grid_cell defaults
;
40 struct colour_palette palette
;
43 struct input_ctx
*ictx
;
52 /* Current position and size. */
58 /* Preferred position and size. */
64 enum { OFF
, MOVE
, SIZE
} dragging
;
75 popup_finish_edit_cb cb
;
79 static const struct menu_item popup_menu_items
[] = {
80 { "Close", 'q', NULL
},
81 { "#{?buffer_name,Paste #[underscore]#{buffer_name},}", 'p', NULL
},
82 { "", KEYC_NONE
, NULL
},
83 { "Fill Space", 'F', NULL
},
84 { "Centre", 'C', NULL
},
85 { "", KEYC_NONE
, NULL
},
86 { "To Horizontal Pane", 'h', NULL
},
87 { "To Vertical Pane", 'v', NULL
},
89 { NULL
, KEYC_NONE
, NULL
}
92 static const struct menu_item popup_internal_menu_items
[] = {
93 { "Close", 'q', NULL
},
94 { "", KEYC_NONE
, NULL
},
95 { "Fill Space", 'F', NULL
},
96 { "Centre", 'C', NULL
},
98 { NULL
, KEYC_NONE
, NULL
}
102 popup_redraw_cb(const struct tty_ctx
*ttyctx
)
104 struct popup_data
*pd
= ttyctx
->arg
;
106 pd
->c
->flags
|= CLIENT_REDRAWOVERLAY
;
110 popup_set_client_cb(struct tty_ctx
*ttyctx
, struct client
*c
)
112 struct popup_data
*pd
= ttyctx
->arg
;
116 if (pd
->c
->flags
& CLIENT_REDRAWOVERLAY
)
122 ttyctx
->wsx
= c
->tty
.sx
;
123 ttyctx
->wsy
= c
->tty
.sy
;
125 if (pd
->border_lines
== BOX_LINES_NONE
) {
126 ttyctx
->xoff
= ttyctx
->rxoff
= pd
->px
;
127 ttyctx
->yoff
= ttyctx
->ryoff
= pd
->py
;
129 ttyctx
->xoff
= ttyctx
->rxoff
= pd
->px
+ 1;
130 ttyctx
->yoff
= ttyctx
->ryoff
= pd
->py
+ 1;
137 popup_init_ctx_cb(struct screen_write_ctx
*ctx
, struct tty_ctx
*ttyctx
)
139 struct popup_data
*pd
= ctx
->arg
;
141 memcpy(&ttyctx
->defaults
, &pd
->defaults
, sizeof ttyctx
->defaults
);
142 ttyctx
->palette
= &pd
->palette
;
143 ttyctx
->redraw_cb
= popup_redraw_cb
;
144 ttyctx
->set_client_cb
= popup_set_client_cb
;
148 static struct screen
*
149 popup_mode_cb(__unused
struct client
*c
, void *data
, u_int
*cx
, u_int
*cy
)
151 struct popup_data
*pd
= data
;
154 return (menu_mode_cb(c
, pd
->md
, cx
, cy
));
156 if (pd
->border_lines
== BOX_LINES_NONE
) {
157 *cx
= pd
->px
+ pd
->s
.cx
;
158 *cy
= pd
->py
+ pd
->s
.cy
;
160 *cx
= pd
->px
+ 1 + pd
->s
.cx
;
161 *cy
= pd
->py
+ 1 + pd
->s
.cy
;
166 /* Return parts of the input range which are not obstructed by the popup. */
168 popup_check_cb(struct client
* c
, void *data
, u_int px
, u_int py
, u_int nx
,
169 struct overlay_ranges
*r
)
171 struct popup_data
*pd
= data
;
172 struct overlay_ranges
or[2];
175 if (pd
->md
!= NULL
) {
176 /* Check each returned range for the menu against the popup. */
177 menu_check_cb(c
, pd
->md
, px
, py
, nx
, r
);
178 for (i
= 0; i
< 2; i
++) {
179 server_client_overlay_range(pd
->px
, pd
->py
, pd
->sx
,
180 pd
->sy
, r
->px
[i
], py
, r
->nx
[i
], &or[i
]);
184 * or has up to OVERLAY_MAX_RANGES non-overlapping ranges,
185 * ordered from left to right. Collect them in the output.
187 for (i
= 0; i
< 2; i
++) {
188 /* Each or[i] only has 2 ranges. */
189 for (j
= 0; j
< 2; j
++) {
190 if (or[i
].nx
[j
] > 0) {
191 r
->px
[k
] = or[i
].px
[j
];
192 r
->nx
[k
] = or[i
].nx
[j
];
198 /* Zero remaining ranges if any. */
199 for (i
= k
; i
< OVERLAY_MAX_RANGES
; i
++) {
207 server_client_overlay_range(pd
->px
, pd
->py
, pd
->sx
, pd
->sy
, px
, py
, nx
,
212 popup_draw_cb(struct client
*c
, void *data
, struct screen_redraw_ctx
*rctx
)
214 struct popup_data
*pd
= data
;
215 struct tty
*tty
= &c
->tty
;
217 struct screen_write_ctx ctx
;
218 u_int i
, px
= pd
->px
, py
= pd
->py
;
219 struct colour_palette
*palette
= &pd
->palette
;
220 struct grid_cell defaults
;
222 screen_init(&s
, pd
->sx
, pd
->sy
, 0);
223 screen_write_start(&ctx
, &s
);
224 screen_write_clearscreen(&ctx
, 8);
226 if (pd
->border_lines
== BOX_LINES_NONE
) {
227 screen_write_cursormove(&ctx
, 0, 0, 0);
228 screen_write_fast_copy(&ctx
, &pd
->s
, 0, 0, pd
->sx
, pd
->sy
);
229 } else if (pd
->sx
> 2 && pd
->sy
> 2) {
230 screen_write_box(&ctx
, pd
->sx
, pd
->sy
, pd
->border_lines
,
231 &pd
->border_cell
, pd
->title
);
232 screen_write_cursormove(&ctx
, 1, 1, 0);
233 screen_write_fast_copy(&ctx
, &pd
->s
, 0, 0, pd
->sx
- 2,
236 screen_write_stop(&ctx
);
238 memcpy(&defaults
, &pd
->defaults
, sizeof defaults
);
239 if (defaults
.fg
== 8)
240 defaults
.fg
= palette
->fg
;
241 if (defaults
.bg
== 8)
242 defaults
.bg
= palette
->bg
;
244 if (pd
->md
!= NULL
) {
245 c
->overlay_check
= menu_check_cb
;
246 c
->overlay_data
= pd
->md
;
248 c
->overlay_check
= NULL
;
249 c
->overlay_data
= NULL
;
251 for (i
= 0; i
< pd
->sy
; i
++) {
252 tty_draw_line(tty
, &s
, 0, i
, pd
->sx
, px
, py
+ i
, &defaults
,
256 if (pd
->md
!= NULL
) {
257 c
->overlay_check
= NULL
;
258 c
->overlay_data
= NULL
;
259 menu_draw_cb(c
, pd
->md
, rctx
);
261 c
->overlay_check
= popup_check_cb
;
262 c
->overlay_data
= pd
;
266 popup_free_cb(struct client
*c
, void *data
)
268 struct popup_data
*pd
= data
;
269 struct cmdq_item
*item
= pd
->item
;
272 menu_free_cb(c
, pd
->md
);
275 pd
->cb(pd
->status
, pd
->arg
);
278 if (cmdq_get_client(item
) != NULL
&&
279 cmdq_get_client(item
)->session
== NULL
)
280 cmdq_get_client(item
)->retval
= pd
->status
;
283 server_client_unref(pd
->c
);
287 input_free(pd
->ictx
);
290 colour_palette_free(&pd
->palette
);
297 popup_resize_cb(__unused
struct client
*c
, void *data
)
299 struct popup_data
*pd
= data
;
300 struct tty
*tty
= &c
->tty
;
305 menu_free_cb(c
, pd
->md
);
307 /* Adjust position and size. */
308 if (pd
->psy
> tty
->sy
)
312 if (pd
->psx
> tty
->sx
)
316 if (pd
->ppy
+ pd
->sy
> tty
->sy
)
317 pd
->py
= tty
->sy
- pd
->sy
;
320 if (pd
->ppx
+ pd
->sx
> tty
->sx
)
321 pd
->px
= tty
->sx
- pd
->sx
;
325 /* Avoid zero size screens. */
326 if (pd
->border_lines
== BOX_LINES_NONE
) {
327 screen_resize(&pd
->s
, pd
->sx
, pd
->sy
, 0);
329 job_resize(pd
->job
, pd
->sx
, pd
->sy
);
330 } else if (pd
->sx
> 2 && pd
->sy
> 2) {
331 screen_resize(&pd
->s
, pd
->sx
- 2, pd
->sy
- 2, 0);
333 job_resize(pd
->job
, pd
->sx
- 2, pd
->sy
- 2);
338 popup_make_pane(struct popup_data
*pd
, enum layout_type type
)
340 struct client
*c
= pd
->c
;
341 struct session
*s
= c
->session
;
342 struct window
*w
= s
->curw
->window
;
343 struct layout_cell
*lc
;
344 struct window_pane
*wp
= w
->active
, *new_wp
;
350 lc
= layout_split_pane(wp
, type
, -1, 0);
351 hlimit
= options_get_number(s
->options
, "history-limit");
352 new_wp
= window_add_pane(wp
->window
, NULL
, hlimit
, 0);
353 layout_assign_pane(lc
, new_wp
, 0);
355 new_wp
->fd
= job_transfer(pd
->job
, &new_wp
->pid
, new_wp
->tty
,
359 screen_set_title(&pd
->s
, new_wp
->base
.title
);
360 screen_free(&new_wp
->base
);
361 memcpy(&new_wp
->base
, &pd
->s
, sizeof wp
->base
);
362 screen_resize(&new_wp
->base
, new_wp
->sx
, new_wp
->sy
, 1);
363 screen_init(&pd
->s
, 1, 1, 0);
365 shell
= options_get_string(s
->options
, "default-shell");
366 if (!checkshell(shell
))
367 shell
= _PATH_BSHELL
;
368 new_wp
->shell
= xstrdup(shell
);
370 window_pane_set_event(new_wp
);
371 window_set_active_pane(w
, new_wp
, 1);
372 new_wp
->flags
|= PANE_CHANGED
;
378 popup_menu_done(__unused
struct menu
*menu
, __unused u_int choice
,
379 key_code key
, void *data
)
381 struct popup_data
*pd
= data
;
382 struct client
*c
= pd
->c
;
383 struct paste_buffer
*pb
;
389 server_redraw_client(pd
->c
);
393 pb
= paste_get_top(NULL
);
395 buf
= paste_buffer_data(pb
, &len
);
396 bufferevent_write(job_get_event(pd
->job
), buf
, len
);
404 server_redraw_client(c
);
407 pd
->px
= c
->tty
.sx
/ 2 - pd
->sx
/ 2;
408 pd
->py
= c
->tty
.sy
/ 2 - pd
->sy
/ 2;
409 server_redraw_client(c
);
412 popup_make_pane(pd
, LAYOUT_LEFTRIGHT
);
415 popup_make_pane(pd
, LAYOUT_TOPBOTTOM
);
424 popup_handle_drag(struct client
*c
, struct popup_data
*pd
,
425 struct mouse_event
*m
)
429 if (!MOUSE_DRAG(m
->b
))
431 else if (pd
->dragging
== MOVE
) {
434 else if (m
->x
- pd
->dx
+ pd
->sx
> c
->tty
.sx
)
435 px
= c
->tty
.sx
- pd
->sx
;
440 else if (m
->y
- pd
->dy
+ pd
->sy
> c
->tty
.sy
)
441 py
= c
->tty
.sy
- pd
->sy
;
446 pd
->dx
= m
->x
- pd
->px
;
447 pd
->dy
= m
->y
- pd
->py
;
450 server_redraw_client(c
);
451 } else if (pd
->dragging
== SIZE
) {
452 if (pd
->border_lines
== BOX_LINES_NONE
) {
453 if (m
->x
< pd
->px
+ 1)
455 if (m
->y
< pd
->py
+ 1)
458 if (m
->x
< pd
->px
+ 3)
460 if (m
->y
< pd
->py
+ 3)
463 pd
->sx
= m
->x
- pd
->px
;
464 pd
->sy
= m
->y
- pd
->py
;
468 if (pd
->border_lines
== BOX_LINES_NONE
) {
469 screen_resize(&pd
->s
, pd
->sx
, pd
->sy
, 0);
471 job_resize(pd
->job
, pd
->sx
, pd
->sy
);
473 screen_resize(&pd
->s
, pd
->sx
- 2, pd
->sy
- 2, 0);
475 job_resize(pd
->job
, pd
->sx
- 2, pd
->sy
- 2);
477 server_redraw_client(c
);
482 popup_key_cb(struct client
*c
, void *data
, struct key_event
*event
)
484 struct popup_data
*pd
= data
;
485 struct mouse_event
*m
= &event
->m
;
489 enum { NONE
, LEFT
, RIGHT
, TOP
, BOTTOM
} border
= NONE
;
491 if (pd
->md
!= NULL
) {
492 if (menu_key_cb(c
, pd
->md
, event
) == 1) {
496 server_client_clear_overlay(c
);
498 server_redraw_client(c
);
503 if (KEYC_IS_MOUSE(event
->key
)) {
504 if (pd
->dragging
!= OFF
) {
505 popup_handle_drag(c
, pd
, m
);
509 m
->x
> pd
->px
+ pd
->sx
- 1 ||
511 m
->y
> pd
->py
+ pd
->sy
- 1) {
512 if (MOUSE_BUTTONS(m
->b
) == MOUSE_BUTTON_3
)
516 if (pd
->border_lines
!= BOX_LINES_NONE
) {
519 else if (m
->x
== pd
->px
+ pd
->sx
- 1)
521 else if (m
->y
== pd
->py
)
523 else if (m
->y
== pd
->py
+ pd
->sy
- 1)
526 if ((m
->b
& MOUSE_MASK_MODIFIERS
) == 0 &&
527 MOUSE_BUTTONS(m
->b
) == MOUSE_BUTTON_3
&&
528 (border
== LEFT
|| border
== TOP
))
530 if (((m
->b
& MOUSE_MASK_MODIFIERS
) == MOUSE_MASK_META
) ||
532 if (!MOUSE_DRAG(m
->b
))
534 if (MOUSE_BUTTONS(m
->lb
) == MOUSE_BUTTON_1
)
536 else if (MOUSE_BUTTONS(m
->lb
) == MOUSE_BUTTON_3
)
538 pd
->dx
= m
->lx
- pd
->px
;
539 pd
->dy
= m
->ly
- pd
->py
;
543 if ((((pd
->flags
& (POPUP_CLOSEEXIT
|POPUP_CLOSEEXITZERO
)) == 0) ||
545 (event
->key
== '\033' || event
->key
== '\003'))
547 if (pd
->job
!= NULL
) {
548 if (KEYC_IS_MOUSE(event
->key
)) {
549 /* Must be inside, checked already. */
550 if (pd
->border_lines
== BOX_LINES_NONE
) {
554 px
= m
->x
- pd
->px
- 1;
555 py
= m
->y
- pd
->py
- 1;
557 if (!input_key_get_mouse(&pd
->s
, m
, px
, py
, &buf
, &len
))
559 bufferevent_write(job_get_event(pd
->job
), buf
, len
);
562 input_key(&pd
->s
, job_get_event(pd
->job
), event
->key
);
567 pd
->menu
= menu_create("");
568 if (pd
->flags
& POPUP_INTERNAL
) {
569 menu_add_items(pd
->menu
, popup_internal_menu_items
, NULL
, c
,
572 menu_add_items(pd
->menu
, popup_menu_items
, NULL
, c
, NULL
);
573 if (m
->x
>= (pd
->menu
->width
+ 4) / 2)
574 x
= m
->x
- (pd
->menu
->width
+ 4) / 2;
577 pd
->md
= menu_prepare(pd
->menu
, 0, 0, NULL
, x
, m
->y
, c
,
578 BOX_LINES_DEFAULT
, NULL
, NULL
, NULL
, NULL
, popup_menu_done
, pd
);
579 c
->flags
|= CLIENT_REDRAWOVERLAY
;
589 popup_job_update_cb(struct job
*job
)
591 struct popup_data
*pd
= job_get_data(job
);
592 struct evbuffer
*evb
= job_get_event(job
)->input
;
593 struct client
*c
= pd
->c
;
594 struct screen
*s
= &pd
->s
;
595 void *data
= EVBUFFER_DATA(evb
);
596 size_t size
= EVBUFFER_LENGTH(evb
);
601 if (pd
->md
!= NULL
) {
602 c
->overlay_check
= menu_check_cb
;
603 c
->overlay_data
= pd
->md
;
605 c
->overlay_check
= NULL
;
606 c
->overlay_data
= NULL
;
608 input_parse_screen(pd
->ictx
, s
, popup_init_ctx_cb
, pd
, data
, size
);
609 c
->overlay_check
= popup_check_cb
;
610 c
->overlay_data
= pd
;
612 evbuffer_drain(evb
, size
);
616 popup_job_complete_cb(struct job
*job
)
618 struct popup_data
*pd
= job_get_data(job
);
621 status
= job_get_status(pd
->job
);
622 if (WIFEXITED(status
))
623 pd
->status
= WEXITSTATUS(status
);
624 else if (WIFSIGNALED(status
))
625 pd
->status
= WTERMSIG(status
);
630 if ((pd
->flags
& POPUP_CLOSEEXIT
) ||
631 ((pd
->flags
& POPUP_CLOSEEXITZERO
) && pd
->status
== 0))
632 server_client_clear_overlay(pd
->c
);
636 popup_display(int flags
, enum box_lines lines
, struct cmdq_item
*item
, u_int px
,
637 u_int py
, u_int sx
, u_int sy
, struct environ
*env
, const char *shellcmd
,
638 int argc
, char **argv
, const char *cwd
, const char *title
, struct client
*c
,
639 struct session
*s
, const char *style
, const char *border_style
,
640 popup_close_cb cb
, void *arg
)
642 struct popup_data
*pd
;
648 o
= s
->curw
->window
->options
;
650 o
= c
->session
->curw
->window
->options
;
652 if (lines
== BOX_LINES_DEFAULT
)
653 lines
= options_get_number(o
, "popup-border-lines");
654 if (lines
== BOX_LINES_NONE
) {
655 if (sx
< 1 || sy
< 1)
660 if (sx
< 3 || sy
< 3)
665 if (c
->tty
.sx
< sx
|| c
->tty
.sy
< sy
)
668 pd
= xcalloc(1, sizeof *pd
);
672 pd
->title
= xstrdup(title
);
679 pd
->status
= 128 + SIGHUP
;
681 pd
->border_lines
= lines
;
682 memcpy(&pd
->border_cell
, &grid_default_cell
, sizeof pd
->border_cell
);
683 style_apply(&pd
->border_cell
, o
, "popup-border-style", NULL
);
684 if (border_style
!= NULL
) {
685 style_set(&sytmp
, &grid_default_cell
);
686 if (style_parse(&sytmp
, &pd
->border_cell
, border_style
) == 0) {
687 pd
->border_cell
.fg
= sytmp
.gc
.fg
;
688 pd
->border_cell
.bg
= sytmp
.gc
.bg
;
691 pd
->border_cell
.attr
= 0;
693 screen_init(&pd
->s
, jx
, jy
, 0);
694 colour_palette_init(&pd
->palette
);
695 colour_palette_from_option(&pd
->palette
, global_w_options
);
697 memcpy(&pd
->defaults
, &grid_default_cell
, sizeof pd
->defaults
);
698 style_apply(&pd
->defaults
, o
, "popup-style", NULL
);
700 style_set(&sytmp
, &grid_default_cell
);
701 if (style_parse(&sytmp
, &pd
->defaults
, style
) == 0) {
702 pd
->defaults
.fg
= sytmp
.gc
.fg
;
703 pd
->defaults
.bg
= sytmp
.gc
.bg
;
706 pd
->defaults
.attr
= 0;
718 pd
->job
= job_run(shellcmd
, argc
, argv
, env
, s
, cwd
,
719 popup_job_update_cb
, popup_job_complete_cb
, NULL
, pd
,
720 JOB_NOWAIT
|JOB_PTY
|JOB_KEEPWRITE
, jx
, jy
);
721 pd
->ictx
= input_init(NULL
, job_get_event(pd
->job
), &pd
->palette
);
723 server_client_set_overlay(c
, 0, popup_check_cb
, popup_mode_cb
,
724 popup_draw_cb
, popup_key_cb
, popup_free_cb
, popup_resize_cb
, pd
);
729 popup_editor_free(struct popup_editor
*pe
)
737 popup_editor_close_cb(int status
, void *arg
)
739 struct popup_editor
*pe
= arg
;
745 pe
->cb(NULL
, 0, pe
->arg
);
746 popup_editor_free(pe
);
750 f
= fopen(pe
->path
, "r");
752 fseeko(f
, 0, SEEK_END
);
754 fseeko(f
, 0, SEEK_SET
);
757 (uintmax_t)len
> (uintmax_t)SIZE_MAX
||
758 (buf
= malloc(len
)) == NULL
||
759 fread(buf
, len
, 1, f
) != 1) {
766 pe
->cb(buf
, len
, pe
->arg
); /* callback now owns buffer */
767 popup_editor_free(pe
);
771 popup_editor(struct client
*c
, const char *buf
, size_t len
,
772 popup_finish_edit_cb cb
, void *arg
)
774 struct popup_editor
*pe
;
778 char path
[] = _PATH_TMP
"tmux.XXXXXXXX";
780 u_int px
, py
, sx
, sy
;
782 editor
= options_get_string(global_options
, "editor");
792 if (fwrite(buf
, len
, 1, f
) != 1) {
798 pe
= xcalloc(1, sizeof *pe
);
799 pe
->path
= xstrdup(path
);
803 sx
= c
->tty
.sx
* 9 / 10;
804 sy
= c
->tty
.sy
* 9 / 10;
805 px
= (c
->tty
.sx
/ 2) - (sx
/ 2);
806 py
= (c
->tty
.sy
/ 2) - (sy
/ 2);
808 xasprintf(&cmd
, "%s %s", editor
, path
);
809 if (popup_display(POPUP_INTERNAL
|POPUP_CLOSEEXIT
, BOX_LINES_DEFAULT
,
810 NULL
, px
, py
, sx
, sy
, NULL
, cmd
, 0, NULL
, _PATH_TMP
, NULL
, c
, NULL
,
811 NULL
, NULL
, popup_editor_close_cb
, pe
) != 0) {
812 popup_editor_free(pe
);