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
,
255 if (pd
->md
!= NULL
) {
256 c
->overlay_check
= NULL
;
257 c
->overlay_data
= NULL
;
258 menu_draw_cb(c
, pd
->md
, rctx
);
260 c
->overlay_check
= popup_check_cb
;
261 c
->overlay_data
= pd
;
265 popup_free_cb(struct client
*c
, void *data
)
267 struct popup_data
*pd
= data
;
268 struct cmdq_item
*item
= pd
->item
;
271 menu_free_cb(c
, pd
->md
);
274 pd
->cb(pd
->status
, pd
->arg
);
277 if (cmdq_get_client(item
) != NULL
&&
278 cmdq_get_client(item
)->session
== NULL
)
279 cmdq_get_client(item
)->retval
= pd
->status
;
282 server_client_unref(pd
->c
);
286 input_free(pd
->ictx
);
289 colour_palette_free(&pd
->palette
);
296 popup_resize_cb(__unused
struct client
*c
, void *data
)
298 struct popup_data
*pd
= data
;
299 struct tty
*tty
= &c
->tty
;
304 menu_free_cb(c
, pd
->md
);
306 /* Adjust position and size. */
307 if (pd
->psy
> tty
->sy
)
311 if (pd
->psx
> tty
->sx
)
315 if (pd
->ppy
+ pd
->sy
> tty
->sy
)
316 pd
->py
= tty
->sy
- pd
->sy
;
319 if (pd
->ppx
+ pd
->sx
> tty
->sx
)
320 pd
->px
= tty
->sx
- pd
->sx
;
324 /* Avoid zero size screens. */
325 if (pd
->border_lines
== BOX_LINES_NONE
) {
326 screen_resize(&pd
->s
, pd
->sx
, pd
->sy
, 0);
328 job_resize(pd
->job
, pd
->sx
, pd
->sy
);
329 } else if (pd
->sx
> 2 && pd
->sy
> 2) {
330 screen_resize(&pd
->s
, pd
->sx
- 2, pd
->sy
- 2, 0);
332 job_resize(pd
->job
, pd
->sx
- 2, pd
->sy
- 2);
337 popup_make_pane(struct popup_data
*pd
, enum layout_type type
)
339 struct client
*c
= pd
->c
;
340 struct session
*s
= c
->session
;
341 struct window
*w
= s
->curw
->window
;
342 struct layout_cell
*lc
;
343 struct window_pane
*wp
= w
->active
, *new_wp
;
349 lc
= layout_split_pane(wp
, type
, -1, 0);
350 hlimit
= options_get_number(s
->options
, "history-limit");
351 new_wp
= window_add_pane(wp
->window
, NULL
, hlimit
, 0);
352 layout_assign_pane(lc
, new_wp
, 0);
354 new_wp
->fd
= job_transfer(pd
->job
, &new_wp
->pid
, new_wp
->tty
,
358 screen_set_title(&pd
->s
, new_wp
->base
.title
);
359 screen_free(&new_wp
->base
);
360 memcpy(&new_wp
->base
, &pd
->s
, sizeof wp
->base
);
361 screen_resize(&new_wp
->base
, new_wp
->sx
, new_wp
->sy
, 1);
362 screen_init(&pd
->s
, 1, 1, 0);
364 shell
= options_get_string(s
->options
, "default-shell");
365 if (!checkshell(shell
))
366 shell
= _PATH_BSHELL
;
367 new_wp
->shell
= xstrdup(shell
);
369 window_pane_set_event(new_wp
);
370 window_set_active_pane(w
, new_wp
, 1);
371 new_wp
->flags
|= PANE_CHANGED
;
377 popup_menu_done(__unused
struct menu
*menu
, __unused u_int choice
,
378 key_code key
, void *data
)
380 struct popup_data
*pd
= data
;
381 struct client
*c
= pd
->c
;
382 struct paste_buffer
*pb
;
388 server_redraw_client(pd
->c
);
392 pb
= paste_get_top(NULL
);
394 buf
= paste_buffer_data(pb
, &len
);
395 bufferevent_write(job_get_event(pd
->job
), buf
, len
);
403 server_redraw_client(c
);
406 pd
->px
= c
->tty
.sx
/ 2 - pd
->sx
/ 2;
407 pd
->py
= c
->tty
.sy
/ 2 - pd
->sy
/ 2;
408 server_redraw_client(c
);
411 popup_make_pane(pd
, LAYOUT_LEFTRIGHT
);
414 popup_make_pane(pd
, LAYOUT_TOPBOTTOM
);
423 popup_handle_drag(struct client
*c
, struct popup_data
*pd
,
424 struct mouse_event
*m
)
428 if (!MOUSE_DRAG(m
->b
))
430 else if (pd
->dragging
== MOVE
) {
433 else if (m
->x
- pd
->dx
+ pd
->sx
> c
->tty
.sx
)
434 px
= c
->tty
.sx
- pd
->sx
;
439 else if (m
->y
- pd
->dy
+ pd
->sy
> c
->tty
.sy
)
440 py
= c
->tty
.sy
- pd
->sy
;
445 pd
->dx
= m
->x
- pd
->px
;
446 pd
->dy
= m
->y
- pd
->py
;
449 server_redraw_client(c
);
450 } else if (pd
->dragging
== SIZE
) {
451 if (pd
->border_lines
== BOX_LINES_NONE
) {
452 if (m
->x
< pd
->px
+ 1)
454 if (m
->y
< pd
->py
+ 1)
457 if (m
->x
< pd
->px
+ 3)
459 if (m
->y
< pd
->py
+ 3)
462 pd
->sx
= m
->x
- pd
->px
;
463 pd
->sy
= m
->y
- pd
->py
;
467 if (pd
->border_lines
== BOX_LINES_NONE
) {
468 screen_resize(&pd
->s
, pd
->sx
, pd
->sy
, 0);
470 job_resize(pd
->job
, pd
->sx
, pd
->sy
);
472 screen_resize(&pd
->s
, pd
->sx
- 2, pd
->sy
- 2, 0);
474 job_resize(pd
->job
, pd
->sx
- 2, pd
->sy
- 2);
476 server_redraw_client(c
);
481 popup_key_cb(struct client
*c
, void *data
, struct key_event
*event
)
483 struct popup_data
*pd
= data
;
484 struct mouse_event
*m
= &event
->m
;
488 enum { NONE
, LEFT
, RIGHT
, TOP
, BOTTOM
} border
= NONE
;
490 if (pd
->md
!= NULL
) {
491 if (menu_key_cb(c
, pd
->md
, event
) == 1) {
495 server_client_clear_overlay(c
);
497 server_redraw_client(c
);
502 if (KEYC_IS_MOUSE(event
->key
)) {
503 if (pd
->dragging
!= OFF
) {
504 popup_handle_drag(c
, pd
, m
);
508 m
->x
> pd
->px
+ pd
->sx
- 1 ||
510 m
->y
> pd
->py
+ pd
->sy
- 1) {
511 if (MOUSE_BUTTONS(m
->b
) == MOUSE_BUTTON_3
)
515 if (pd
->border_lines
!= BOX_LINES_NONE
) {
518 else if (m
->x
== pd
->px
+ pd
->sx
- 1)
520 else if (m
->y
== pd
->py
)
522 else if (m
->y
== pd
->py
+ pd
->sy
- 1)
525 if ((m
->b
& MOUSE_MASK_MODIFIERS
) == 0 &&
526 MOUSE_BUTTONS(m
->b
) == MOUSE_BUTTON_3
&&
527 (border
== LEFT
|| border
== TOP
))
529 if (((m
->b
& MOUSE_MASK_MODIFIERS
) == MOUSE_MASK_META
) ||
531 if (!MOUSE_DRAG(m
->b
))
533 if (MOUSE_BUTTONS(m
->lb
) == MOUSE_BUTTON_1
)
535 else if (MOUSE_BUTTONS(m
->lb
) == MOUSE_BUTTON_3
)
537 pd
->dx
= m
->lx
- pd
->px
;
538 pd
->dy
= m
->ly
- pd
->py
;
542 if ((((pd
->flags
& (POPUP_CLOSEEXIT
|POPUP_CLOSEEXITZERO
)) == 0) ||
544 (event
->key
== '\033' || event
->key
== '\003'))
546 if (pd
->job
!= NULL
) {
547 if (KEYC_IS_MOUSE(event
->key
)) {
548 /* Must be inside, checked already. */
549 if (pd
->border_lines
== BOX_LINES_NONE
) {
553 px
= m
->x
- pd
->px
- 1;
554 py
= m
->y
- pd
->py
- 1;
556 if (!input_key_get_mouse(&pd
->s
, m
, px
, py
, &buf
, &len
))
558 bufferevent_write(job_get_event(pd
->job
), buf
, len
);
561 input_key(&pd
->s
, job_get_event(pd
->job
), event
->key
);
566 pd
->menu
= menu_create("");
567 if (pd
->flags
& POPUP_INTERNAL
) {
568 menu_add_items(pd
->menu
, popup_internal_menu_items
, NULL
, c
,
571 menu_add_items(pd
->menu
, popup_menu_items
, NULL
, c
, NULL
);
572 if (m
->x
>= (pd
->menu
->width
+ 4) / 2)
573 x
= m
->x
- (pd
->menu
->width
+ 4) / 2;
576 pd
->md
= menu_prepare(pd
->menu
, 0, NULL
, x
, m
->y
, c
, NULL
,
577 popup_menu_done
, pd
);
578 c
->flags
|= CLIENT_REDRAWOVERLAY
;
588 popup_job_update_cb(struct job
*job
)
590 struct popup_data
*pd
= job_get_data(job
);
591 struct evbuffer
*evb
= job_get_event(job
)->input
;
592 struct client
*c
= pd
->c
;
593 struct screen
*s
= &pd
->s
;
594 void *data
= EVBUFFER_DATA(evb
);
595 size_t size
= EVBUFFER_LENGTH(evb
);
600 if (pd
->md
!= NULL
) {
601 c
->overlay_check
= menu_check_cb
;
602 c
->overlay_data
= pd
->md
;
604 c
->overlay_check
= NULL
;
605 c
->overlay_data
= NULL
;
607 input_parse_screen(pd
->ictx
, s
, popup_init_ctx_cb
, pd
, data
, size
);
608 c
->overlay_check
= popup_check_cb
;
609 c
->overlay_data
= pd
;
611 evbuffer_drain(evb
, size
);
615 popup_job_complete_cb(struct job
*job
)
617 struct popup_data
*pd
= job_get_data(job
);
620 status
= job_get_status(pd
->job
);
621 if (WIFEXITED(status
))
622 pd
->status
= WEXITSTATUS(status
);
623 else if (WIFSIGNALED(status
))
624 pd
->status
= WTERMSIG(status
);
629 if ((pd
->flags
& POPUP_CLOSEEXIT
) ||
630 ((pd
->flags
& POPUP_CLOSEEXITZERO
) && pd
->status
== 0))
631 server_client_clear_overlay(pd
->c
);
635 popup_display(int flags
, enum box_lines lines
, struct cmdq_item
*item
, u_int px
,
636 u_int py
, u_int sx
, u_int sy
, struct environ
*env
, const char *shellcmd
,
637 int argc
, char **argv
, const char *cwd
, const char *title
, struct client
*c
,
638 struct session
*s
, const char* style
, const char* border_style
,
639 popup_close_cb cb
, void *arg
)
641 struct popup_data
*pd
;
647 o
= s
->curw
->window
->options
;
649 o
= c
->session
->curw
->window
->options
;
651 if (lines
== BOX_LINES_DEFAULT
)
652 lines
= options_get_number(o
, "popup-border-lines");
653 if (lines
== BOX_LINES_NONE
) {
654 if (sx
< 1 || sy
< 1)
659 if (sx
< 3 || sy
< 3)
664 if (c
->tty
.sx
< sx
|| c
->tty
.sy
< sy
)
667 pd
= xcalloc(1, sizeof *pd
);
671 pd
->title
= xstrdup(title
);
678 pd
->status
= 128 + SIGHUP
;
680 pd
->border_lines
= lines
;
681 memcpy(&pd
->border_cell
, &grid_default_cell
, sizeof pd
->border_cell
);
682 style_apply(&pd
->border_cell
, o
, "popup-border-style", NULL
);
683 if (border_style
!= NULL
) {
684 style_set(&sytmp
, &grid_default_cell
);
685 if (style_parse(&sytmp
, &pd
->border_cell
, border_style
) == 0) {
686 pd
->border_cell
.fg
= sytmp
.gc
.fg
;
687 pd
->border_cell
.bg
= sytmp
.gc
.bg
;
690 pd
->border_cell
.attr
= 0;
692 screen_init(&pd
->s
, jx
, jy
, 0);
693 colour_palette_init(&pd
->palette
);
694 colour_palette_from_option(&pd
->palette
, global_w_options
);
696 memcpy(&pd
->defaults
, &grid_default_cell
, sizeof pd
->defaults
);
697 style_apply(&pd
->defaults
, o
, "popup-style", NULL
);
699 style_set(&sytmp
, &grid_default_cell
);
700 if (style_parse(&sytmp
, &pd
->defaults
, style
) == 0) {
701 pd
->defaults
.fg
= sytmp
.gc
.fg
;
702 pd
->defaults
.bg
= sytmp
.gc
.bg
;
705 pd
->defaults
.attr
= 0;
717 pd
->job
= job_run(shellcmd
, argc
, argv
, env
, s
, cwd
,
718 popup_job_update_cb
, popup_job_complete_cb
, NULL
, pd
,
719 JOB_NOWAIT
|JOB_PTY
|JOB_KEEPWRITE
, jx
, jy
);
720 pd
->ictx
= input_init(NULL
, job_get_event(pd
->job
), &pd
->palette
);
722 server_client_set_overlay(c
, 0, popup_check_cb
, popup_mode_cb
,
723 popup_draw_cb
, popup_key_cb
, popup_free_cb
, popup_resize_cb
, pd
);
728 popup_editor_free(struct popup_editor
*pe
)
736 popup_editor_close_cb(int status
, void *arg
)
738 struct popup_editor
*pe
= arg
;
744 pe
->cb(NULL
, 0, pe
->arg
);
745 popup_editor_free(pe
);
749 f
= fopen(pe
->path
, "r");
751 fseeko(f
, 0, SEEK_END
);
753 fseeko(f
, 0, SEEK_SET
);
756 (uintmax_t)len
> (uintmax_t)SIZE_MAX
||
757 (buf
= malloc(len
)) == NULL
||
758 fread(buf
, len
, 1, f
) != 1) {
765 pe
->cb(buf
, len
, pe
->arg
); /* callback now owns buffer */
766 popup_editor_free(pe
);
770 popup_editor(struct client
*c
, const char *buf
, size_t len
,
771 popup_finish_edit_cb cb
, void *arg
)
773 struct popup_editor
*pe
;
777 char path
[] = _PATH_TMP
"tmux.XXXXXXXX";
779 u_int px
, py
, sx
, sy
;
781 editor
= options_get_string(global_options
, "editor");
789 if (fwrite(buf
, len
, 1, f
) != 1) {
795 pe
= xcalloc(1, sizeof *pe
);
796 pe
->path
= xstrdup(path
);
800 sx
= c
->tty
.sx
* 9 / 10;
801 sy
= c
->tty
.sy
* 9 / 10;
802 px
= (c
->tty
.sx
/ 2) - (sx
/ 2);
803 py
= (c
->tty
.sy
/ 2) - (sy
/ 2);
805 xasprintf(&cmd
, "%s %s", editor
, path
);
806 if (popup_display(POPUP_INTERNAL
|POPUP_CLOSEEXIT
, BOX_LINES_DEFAULT
,
807 NULL
, px
, py
, sx
, sy
, NULL
, cmd
, 0, NULL
, _PATH_TMP
, NULL
, c
, NULL
,
808 NULL
, NULL
, popup_editor_close_cb
, pe
) != 0) {
809 popup_editor_free(pe
);