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>
32 struct cmdq_item
*item
;
36 struct grid_cell border_cell
;
37 enum box_lines border_lines
;
40 struct grid_cell defaults
;
41 struct colour_palette palette
;
44 struct input_ctx
*ictx
;
53 /* Current position and size. */
59 /* Preferred position and size. */
65 enum { OFF
, MOVE
, SIZE
} dragging
;
76 popup_finish_edit_cb cb
;
80 static const struct menu_item popup_menu_items
[] = {
81 { "Close", 'q', NULL
},
82 { "#{?buffer_name,Paste #[underscore]#{buffer_name},}", 'p', NULL
},
83 { "", KEYC_NONE
, NULL
},
84 { "Fill Space", 'F', NULL
},
85 { "Centre", 'C', NULL
},
86 { "", KEYC_NONE
, NULL
},
87 { "To Horizontal Pane", 'h', NULL
},
88 { "To Vertical Pane", 'v', NULL
},
90 { NULL
, KEYC_NONE
, NULL
}
93 static const struct menu_item popup_internal_menu_items
[] = {
94 { "Close", 'q', NULL
},
95 { "", KEYC_NONE
, NULL
},
96 { "Fill Space", 'F', NULL
},
97 { "Centre", 'C', NULL
},
99 { NULL
, KEYC_NONE
, NULL
}
103 popup_redraw_cb(const struct tty_ctx
*ttyctx
)
105 struct popup_data
*pd
= ttyctx
->arg
;
107 pd
->c
->flags
|= CLIENT_REDRAWOVERLAY
;
111 popup_set_client_cb(struct tty_ctx
*ttyctx
, struct client
*c
)
113 struct popup_data
*pd
= ttyctx
->arg
;
117 if (pd
->c
->flags
& CLIENT_REDRAWOVERLAY
)
123 ttyctx
->wsx
= c
->tty
.sx
;
124 ttyctx
->wsy
= c
->tty
.sy
;
126 if (pd
->border_lines
== BOX_LINES_NONE
) {
127 ttyctx
->xoff
= ttyctx
->rxoff
= pd
->px
;
128 ttyctx
->yoff
= ttyctx
->ryoff
= pd
->py
;
130 ttyctx
->xoff
= ttyctx
->rxoff
= pd
->px
+ 1;
131 ttyctx
->yoff
= ttyctx
->ryoff
= pd
->py
+ 1;
138 popup_init_ctx_cb(struct screen_write_ctx
*ctx
, struct tty_ctx
*ttyctx
)
140 struct popup_data
*pd
= ctx
->arg
;
142 memcpy(&ttyctx
->defaults
, &pd
->defaults
, sizeof ttyctx
->defaults
);
143 ttyctx
->palette
= &pd
->palette
;
144 ttyctx
->redraw_cb
= popup_redraw_cb
;
145 ttyctx
->set_client_cb
= popup_set_client_cb
;
149 static struct screen
*
150 popup_mode_cb(__unused
struct client
*c
, void *data
, u_int
*cx
, u_int
*cy
)
152 struct popup_data
*pd
= data
;
155 return (menu_mode_cb(c
, pd
->md
, cx
, cy
));
157 if (pd
->border_lines
== BOX_LINES_NONE
) {
158 *cx
= pd
->px
+ pd
->s
.cx
;
159 *cy
= pd
->py
+ pd
->s
.cy
;
161 *cx
= pd
->px
+ 1 + pd
->s
.cx
;
162 *cy
= pd
->py
+ 1 + pd
->s
.cy
;
167 /* Return parts of the input range which are not obstructed by the popup. */
169 popup_check_cb(struct client
* c
, void *data
, u_int px
, u_int py
, u_int nx
,
170 struct overlay_ranges
*r
)
172 struct popup_data
*pd
= data
;
173 struct overlay_ranges
or[2];
176 if (pd
->md
!= NULL
) {
177 /* Check each returned range for the menu against the popup. */
178 menu_check_cb(c
, pd
->md
, px
, py
, nx
, r
);
179 for (i
= 0; i
< 2; i
++) {
180 server_client_overlay_range(pd
->px
, pd
->py
, pd
->sx
,
181 pd
->sy
, r
->px
[i
], py
, r
->nx
[i
], &or[i
]);
185 * or has up to OVERLAY_MAX_RANGES non-overlapping ranges,
186 * ordered from left to right. Collect them in the output.
188 for (i
= 0; i
< 2; i
++) {
189 /* Each or[i] only has 2 ranges. */
190 for (j
= 0; j
< 2; j
++) {
191 if (or[i
].nx
[j
] > 0) {
192 r
->px
[k
] = or[i
].px
[j
];
193 r
->nx
[k
] = or[i
].nx
[j
];
199 /* Zero remaining ranges if any. */
200 for (i
= k
; i
< OVERLAY_MAX_RANGES
; i
++) {
208 server_client_overlay_range(pd
->px
, pd
->py
, pd
->sx
, pd
->sy
, px
, py
, nx
,
213 popup_draw_cb(struct client
*c
, void *data
, struct screen_redraw_ctx
*rctx
)
215 struct popup_data
*pd
= data
;
216 struct tty
*tty
= &c
->tty
;
218 struct screen_write_ctx ctx
;
219 u_int i
, px
= pd
->px
, py
= pd
->py
;
220 struct colour_palette
*palette
= &pd
->palette
;
221 struct grid_cell defaults
;
223 screen_init(&s
, pd
->sx
, pd
->sy
, 0);
224 screen_write_start(&ctx
, &s
);
225 screen_write_clearscreen(&ctx
, 8);
227 if (pd
->border_lines
== BOX_LINES_NONE
) {
228 screen_write_cursormove(&ctx
, 0, 0, 0);
229 screen_write_fast_copy(&ctx
, &pd
->s
, 0, 0, pd
->sx
, pd
->sy
);
230 } else if (pd
->sx
> 2 && pd
->sy
> 2) {
231 screen_write_box(&ctx
, pd
->sx
, pd
->sy
, pd
->border_lines
,
232 &pd
->border_cell
, pd
->title
);
233 screen_write_cursormove(&ctx
, 1, 1, 0);
234 screen_write_fast_copy(&ctx
, &pd
->s
, 0, 0, pd
->sx
- 2,
237 screen_write_stop(&ctx
);
239 memcpy(&defaults
, &pd
->defaults
, sizeof defaults
);
240 if (defaults
.fg
== 8)
241 defaults
.fg
= palette
->fg
;
242 if (defaults
.bg
== 8)
243 defaults
.bg
= palette
->bg
;
245 if (pd
->md
!= NULL
) {
246 c
->overlay_check
= menu_check_cb
;
247 c
->overlay_data
= pd
->md
;
249 c
->overlay_check
= NULL
;
250 c
->overlay_data
= NULL
;
252 for (i
= 0; i
< pd
->sy
; i
++) {
253 tty_draw_line(tty
, &s
, 0, i
, pd
->sx
, px
, py
+ i
, &defaults
,
257 if (pd
->md
!= NULL
) {
258 c
->overlay_check
= NULL
;
259 c
->overlay_data
= NULL
;
260 menu_draw_cb(c
, pd
->md
, rctx
);
262 c
->overlay_check
= popup_check_cb
;
263 c
->overlay_data
= pd
;
267 popup_free_cb(struct client
*c
, void *data
)
269 struct popup_data
*pd
= data
;
270 struct cmdq_item
*item
= pd
->item
;
273 menu_free_cb(c
, pd
->md
);
276 pd
->cb(pd
->status
, pd
->arg
);
279 if (cmdq_get_client(item
) != NULL
&&
280 cmdq_get_client(item
)->session
== NULL
)
281 cmdq_get_client(item
)->retval
= pd
->status
;
284 server_client_unref(pd
->c
);
288 input_free(pd
->ictx
);
291 colour_palette_free(&pd
->palette
);
298 popup_resize_cb(__unused
struct client
*c
, void *data
)
300 struct popup_data
*pd
= data
;
301 struct tty
*tty
= &c
->tty
;
306 menu_free_cb(c
, pd
->md
);
308 /* Adjust position and size. */
309 if (pd
->psy
> tty
->sy
)
313 if (pd
->psx
> tty
->sx
)
317 if (pd
->ppy
+ pd
->sy
> tty
->sy
)
318 pd
->py
= tty
->sy
- pd
->sy
;
321 if (pd
->ppx
+ pd
->sx
> tty
->sx
)
322 pd
->px
= tty
->sx
- pd
->sx
;
326 /* Avoid zero size screens. */
327 if (pd
->border_lines
== BOX_LINES_NONE
) {
328 screen_resize(&pd
->s
, pd
->sx
, pd
->sy
, 0);
330 job_resize(pd
->job
, pd
->sx
, pd
->sy
);
331 } else if (pd
->sx
> 2 && pd
->sy
> 2) {
332 screen_resize(&pd
->s
, pd
->sx
- 2, pd
->sy
- 2, 0);
334 job_resize(pd
->job
, pd
->sx
- 2, pd
->sy
- 2);
339 popup_make_pane(struct popup_data
*pd
, enum layout_type type
)
341 struct client
*c
= pd
->c
;
342 struct session
*s
= c
->session
;
343 struct window
*w
= s
->curw
->window
;
344 struct layout_cell
*lc
;
345 struct window_pane
*wp
= w
->active
, *new_wp
;
351 lc
= layout_split_pane(wp
, type
, -1, 0);
352 hlimit
= options_get_number(s
->options
, "history-limit");
353 new_wp
= window_add_pane(wp
->window
, NULL
, hlimit
, 0);
354 layout_assign_pane(lc
, new_wp
, 0);
356 new_wp
->fd
= job_transfer(pd
->job
, &new_wp
->pid
, new_wp
->tty
,
360 screen_set_title(&pd
->s
, new_wp
->base
.title
);
361 screen_free(&new_wp
->base
);
362 memcpy(&new_wp
->base
, &pd
->s
, sizeof wp
->base
);
363 screen_resize(&new_wp
->base
, new_wp
->sx
, new_wp
->sy
, 1);
364 screen_init(&pd
->s
, 1, 1, 0);
366 shell
= options_get_string(s
->options
, "default-shell");
367 if (!checkshell(shell
))
368 shell
= _PATH_BSHELL
;
369 new_wp
->shell
= xstrdup(shell
);
371 window_pane_set_event(new_wp
);
372 window_set_active_pane(w
, new_wp
, 1);
373 new_wp
->flags
|= PANE_CHANGED
;
379 popup_menu_done(__unused
struct menu
*menu
, __unused u_int choice
,
380 key_code key
, void *data
)
382 struct popup_data
*pd
= data
;
383 struct client
*c
= pd
->c
;
384 struct paste_buffer
*pb
;
390 server_redraw_client(pd
->c
);
394 pb
= paste_get_top(NULL
);
396 buf
= paste_buffer_data(pb
, &len
);
397 bufferevent_write(job_get_event(pd
->job
), buf
, len
);
405 server_redraw_client(c
);
408 pd
->px
= c
->tty
.sx
/ 2 - pd
->sx
/ 2;
409 pd
->py
= c
->tty
.sy
/ 2 - pd
->sy
/ 2;
410 server_redraw_client(c
);
413 popup_make_pane(pd
, LAYOUT_LEFTRIGHT
);
416 popup_make_pane(pd
, LAYOUT_TOPBOTTOM
);
425 popup_handle_drag(struct client
*c
, struct popup_data
*pd
,
426 struct mouse_event
*m
)
430 if (!MOUSE_DRAG(m
->b
))
432 else if (pd
->dragging
== MOVE
) {
435 else if (m
->x
- pd
->dx
+ pd
->sx
> c
->tty
.sx
)
436 px
= c
->tty
.sx
- pd
->sx
;
441 else if (m
->y
- pd
->dy
+ pd
->sy
> c
->tty
.sy
)
442 py
= c
->tty
.sy
- pd
->sy
;
447 pd
->dx
= m
->x
- pd
->px
;
448 pd
->dy
= m
->y
- pd
->py
;
451 server_redraw_client(c
);
452 } else if (pd
->dragging
== SIZE
) {
453 if (pd
->border_lines
== BOX_LINES_NONE
) {
454 if (m
->x
< pd
->px
+ 1)
456 if (m
->y
< pd
->py
+ 1)
459 if (m
->x
< pd
->px
+ 3)
461 if (m
->y
< pd
->py
+ 3)
464 pd
->sx
= m
->x
- pd
->px
;
465 pd
->sy
= m
->y
- pd
->py
;
469 if (pd
->border_lines
== BOX_LINES_NONE
) {
470 screen_resize(&pd
->s
, pd
->sx
, pd
->sy
, 0);
472 job_resize(pd
->job
, pd
->sx
, pd
->sy
);
474 screen_resize(&pd
->s
, pd
->sx
- 2, pd
->sy
- 2, 0);
476 job_resize(pd
->job
, pd
->sx
- 2, pd
->sy
- 2);
478 server_redraw_client(c
);
483 popup_key_cb(struct client
*c
, void *data
, struct key_event
*event
)
485 struct popup_data
*pd
= data
;
486 struct mouse_event
*m
= &event
->m
;
490 enum { NONE
, LEFT
, RIGHT
, TOP
, BOTTOM
} border
= NONE
;
492 if (pd
->md
!= NULL
) {
493 if (menu_key_cb(c
, pd
->md
, event
) == 1) {
497 server_client_clear_overlay(c
);
499 server_redraw_client(c
);
504 if (KEYC_IS_MOUSE(event
->key
)) {
505 if (pd
->dragging
!= OFF
) {
506 popup_handle_drag(c
, pd
, m
);
510 m
->x
> pd
->px
+ pd
->sx
- 1 ||
512 m
->y
> pd
->py
+ pd
->sy
- 1) {
513 if (MOUSE_BUTTONS(m
->b
) == MOUSE_BUTTON_3
)
517 if (pd
->border_lines
!= BOX_LINES_NONE
) {
520 else if (m
->x
== pd
->px
+ pd
->sx
- 1)
522 else if (m
->y
== pd
->py
)
524 else if (m
->y
== pd
->py
+ pd
->sy
- 1)
527 if ((m
->b
& MOUSE_MASK_MODIFIERS
) == 0 &&
528 MOUSE_BUTTONS(m
->b
) == MOUSE_BUTTON_3
&&
529 (border
== LEFT
|| border
== TOP
))
531 if (((m
->b
& MOUSE_MASK_MODIFIERS
) == MOUSE_MASK_META
) ||
533 if (!MOUSE_DRAG(m
->b
))
535 if (MOUSE_BUTTONS(m
->lb
) == MOUSE_BUTTON_1
)
537 else if (MOUSE_BUTTONS(m
->lb
) == MOUSE_BUTTON_3
)
539 pd
->dx
= m
->lx
- pd
->px
;
540 pd
->dy
= m
->ly
- pd
->py
;
544 if ((((pd
->flags
& (POPUP_CLOSEEXIT
|POPUP_CLOSEEXITZERO
)) == 0) ||
546 (event
->key
== '\033' || event
->key
== ('c'|KEYC_CTRL
)))
548 if (pd
->job
!= NULL
) {
549 if (KEYC_IS_MOUSE(event
->key
)) {
550 /* Must be inside, checked already. */
551 if (pd
->border_lines
== BOX_LINES_NONE
) {
555 px
= m
->x
- pd
->px
- 1;
556 py
= m
->y
- pd
->py
- 1;
558 if (!input_key_get_mouse(&pd
->s
, m
, px
, py
, &buf
, &len
))
560 bufferevent_write(job_get_event(pd
->job
), buf
, len
);
563 input_key(&pd
->s
, job_get_event(pd
->job
), event
->key
);
568 pd
->menu
= menu_create("");
569 if (pd
->flags
& POPUP_INTERNAL
) {
570 menu_add_items(pd
->menu
, popup_internal_menu_items
, NULL
, c
,
573 menu_add_items(pd
->menu
, popup_menu_items
, NULL
, c
, NULL
);
574 if (m
->x
>= (pd
->menu
->width
+ 4) / 2)
575 x
= m
->x
- (pd
->menu
->width
+ 4) / 2;
578 pd
->md
= menu_prepare(pd
->menu
, 0, 0, NULL
, x
, m
->y
, c
,
579 BOX_LINES_DEFAULT
, NULL
, NULL
, NULL
, NULL
, popup_menu_done
, pd
);
580 c
->flags
|= CLIENT_REDRAWOVERLAY
;
590 popup_job_update_cb(struct job
*job
)
592 struct popup_data
*pd
= job_get_data(job
);
593 struct evbuffer
*evb
= job_get_event(job
)->input
;
594 struct client
*c
= pd
->c
;
595 struct screen
*s
= &pd
->s
;
596 void *data
= EVBUFFER_DATA(evb
);
597 size_t size
= EVBUFFER_LENGTH(evb
);
602 if (pd
->md
!= NULL
) {
603 c
->overlay_check
= menu_check_cb
;
604 c
->overlay_data
= pd
->md
;
606 c
->overlay_check
= NULL
;
607 c
->overlay_data
= NULL
;
609 input_parse_screen(pd
->ictx
, s
, popup_init_ctx_cb
, pd
, data
, size
);
610 c
->overlay_check
= popup_check_cb
;
611 c
->overlay_data
= pd
;
613 evbuffer_drain(evb
, size
);
617 popup_job_complete_cb(struct job
*job
)
619 struct popup_data
*pd
= job_get_data(job
);
622 status
= job_get_status(pd
->job
);
623 if (WIFEXITED(status
))
624 pd
->status
= WEXITSTATUS(status
);
625 else if (WIFSIGNALED(status
))
626 pd
->status
= WTERMSIG(status
);
631 if ((pd
->flags
& POPUP_CLOSEEXIT
) ||
632 ((pd
->flags
& POPUP_CLOSEEXITZERO
) && pd
->status
== 0))
633 server_client_clear_overlay(pd
->c
);
637 popup_display(int flags
, enum box_lines lines
, struct cmdq_item
*item
, u_int px
,
638 u_int py
, u_int sx
, u_int sy
, struct environ
*env
, const char *shellcmd
,
639 int argc
, char **argv
, const char *cwd
, const char *title
, struct client
*c
,
640 struct session
*s
, const char *style
, const char *border_style
,
641 popup_close_cb cb
, void *arg
)
643 struct popup_data
*pd
;
649 o
= s
->curw
->window
->options
;
651 o
= c
->session
->curw
->window
->options
;
653 if (lines
== BOX_LINES_DEFAULT
)
654 lines
= options_get_number(o
, "popup-border-lines");
655 if (lines
== BOX_LINES_NONE
) {
656 if (sx
< 1 || sy
< 1)
661 if (sx
< 3 || sy
< 3)
666 if (c
->tty
.sx
< sx
|| c
->tty
.sy
< sy
)
669 pd
= xcalloc(1, sizeof *pd
);
673 pd
->title
= xstrdup(title
);
680 pd
->status
= 128 + SIGHUP
;
682 pd
->border_lines
= lines
;
683 memcpy(&pd
->border_cell
, &grid_default_cell
, sizeof pd
->border_cell
);
684 style_apply(&pd
->border_cell
, o
, "popup-border-style", NULL
);
685 if (border_style
!= NULL
) {
686 style_set(&sytmp
, &grid_default_cell
);
687 if (style_parse(&sytmp
, &pd
->border_cell
, border_style
) == 0) {
688 pd
->border_cell
.fg
= sytmp
.gc
.fg
;
689 pd
->border_cell
.bg
= sytmp
.gc
.bg
;
692 pd
->border_cell
.attr
= 0;
694 screen_init(&pd
->s
, jx
, jy
, 0);
695 colour_palette_init(&pd
->palette
);
696 colour_palette_from_option(&pd
->palette
, global_w_options
);
698 memcpy(&pd
->defaults
, &grid_default_cell
, sizeof pd
->defaults
);
699 style_apply(&pd
->defaults
, o
, "popup-style", NULL
);
701 style_set(&sytmp
, &grid_default_cell
);
702 if (style_parse(&sytmp
, &pd
->defaults
, style
) == 0) {
703 pd
->defaults
.fg
= sytmp
.gc
.fg
;
704 pd
->defaults
.bg
= sytmp
.gc
.bg
;
707 pd
->defaults
.attr
= 0;
719 pd
->job
= job_run(shellcmd
, argc
, argv
, env
, s
, cwd
,
720 popup_job_update_cb
, popup_job_complete_cb
, NULL
, pd
,
721 JOB_NOWAIT
|JOB_PTY
|JOB_KEEPWRITE
, jx
, jy
);
722 pd
->ictx
= input_init(NULL
, job_get_event(pd
->job
), &pd
->palette
);
724 server_client_set_overlay(c
, 0, popup_check_cb
, popup_mode_cb
,
725 popup_draw_cb
, popup_key_cb
, popup_free_cb
, popup_resize_cb
, pd
);
730 popup_editor_free(struct popup_editor
*pe
)
738 popup_editor_close_cb(int status
, void *arg
)
740 struct popup_editor
*pe
= arg
;
746 pe
->cb(NULL
, 0, pe
->arg
);
747 popup_editor_free(pe
);
751 f
= fopen(pe
->path
, "r");
753 fseeko(f
, 0, SEEK_END
);
755 fseeko(f
, 0, SEEK_SET
);
758 (uintmax_t)len
> (uintmax_t)SIZE_MAX
||
759 (buf
= malloc(len
)) == NULL
||
760 fread(buf
, len
, 1, f
) != 1) {
767 pe
->cb(buf
, len
, pe
->arg
); /* callback now owns buffer */
768 popup_editor_free(pe
);
772 popup_editor(struct client
*c
, const char *buf
, size_t len
,
773 popup_finish_edit_cb cb
, void *arg
)
775 struct popup_editor
*pe
;
779 char path
[] = _PATH_TMP
"tmux.XXXXXXXX";
781 u_int px
, py
, sx
, sy
;
783 editor
= options_get_string(global_options
, "editor");
793 if (fwrite(buf
, len
, 1, f
) != 1) {
799 pe
= xcalloc(1, sizeof *pe
);
800 pe
->path
= xstrdup(path
);
804 sx
= c
->tty
.sx
* 9 / 10;
805 sy
= c
->tty
.sy
* 9 / 10;
806 px
= (c
->tty
.sx
/ 2) - (sx
/ 2);
807 py
= (c
->tty
.sy
/ 2) - (sy
/ 2);
809 xasprintf(&cmd
, "%s %s", editor
, path
);
810 if (popup_display(POPUP_INTERNAL
|POPUP_CLOSEEXIT
, BOX_LINES_DEFAULT
,
811 NULL
, px
, py
, sx
, sy
, NULL
, cmd
, 0, NULL
, _PATH_TMP
, NULL
, c
, NULL
,
812 NULL
, NULL
, popup_editor_close_cb
, pe
) != 0) {
813 popup_editor_free(pe
);