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 colour_palette palette
;
38 struct input_ctx
*ictx
;
47 /* Current position and size. */
53 /* Preferred position and size. */
59 enum { OFF
, MOVE
, SIZE
} dragging
;
70 popup_finish_edit_cb cb
;
74 static const struct menu_item popup_menu_items
[] = {
75 { "Close", 'q', NULL
},
76 { "#{?buffer_name,Paste #[underscore]#{buffer_name},}", 'p', NULL
},
77 { "", KEYC_NONE
, NULL
},
78 { "Fill Space", 'F', NULL
},
79 { "Centre", 'C', NULL
},
80 { "", KEYC_NONE
, NULL
},
81 { "To Horizontal Pane", 'h', NULL
},
82 { "To Vertical Pane", 'v', NULL
},
84 { NULL
, KEYC_NONE
, NULL
}
87 static const struct menu_item popup_internal_menu_items
[] = {
88 { "Close", 'q', NULL
},
89 { "", KEYC_NONE
, NULL
},
90 { "Fill Space", 'F', NULL
},
91 { "Centre", 'C', NULL
},
93 { NULL
, KEYC_NONE
, NULL
}
97 popup_redraw_cb(const struct tty_ctx
*ttyctx
)
99 struct popup_data
*pd
= ttyctx
->arg
;
101 pd
->c
->flags
|= CLIENT_REDRAWOVERLAY
;
105 popup_set_client_cb(struct tty_ctx
*ttyctx
, struct client
*c
)
107 struct popup_data
*pd
= ttyctx
->arg
;
111 if (pd
->c
->flags
& CLIENT_REDRAWOVERLAY
)
117 ttyctx
->wsx
= c
->tty
.sx
;
118 ttyctx
->wsy
= c
->tty
.sy
;
120 if (pd
->flags
& POPUP_NOBORDER
) {
121 ttyctx
->xoff
= ttyctx
->rxoff
= pd
->px
;
122 ttyctx
->yoff
= ttyctx
->ryoff
= pd
->py
;
124 ttyctx
->xoff
= ttyctx
->rxoff
= pd
->px
+ 1;
125 ttyctx
->yoff
= ttyctx
->ryoff
= pd
->py
+ 1;
132 popup_init_ctx_cb(struct screen_write_ctx
*ctx
, struct tty_ctx
*ttyctx
)
134 struct popup_data
*pd
= ctx
->arg
;
136 ttyctx
->palette
= &pd
->palette
;
137 ttyctx
->redraw_cb
= popup_redraw_cb
;
138 ttyctx
->set_client_cb
= popup_set_client_cb
;
142 static struct screen
*
143 popup_mode_cb(__unused
struct client
*c
, void *data
, u_int
*cx
, u_int
*cy
)
145 struct popup_data
*pd
= data
;
148 return (menu_mode_cb(c
, pd
->md
, cx
, cy
));
150 if (pd
->flags
& POPUP_NOBORDER
) {
151 *cx
= pd
->px
+ pd
->s
.cx
;
152 *cy
= pd
->py
+ pd
->s
.cy
;
154 *cx
= pd
->px
+ 1 + pd
->s
.cx
;
155 *cy
= pd
->py
+ 1 + pd
->s
.cy
;
161 popup_check_cb(struct client
*c
, void *data
, u_int px
, u_int py
)
163 struct popup_data
*pd
= data
;
165 if (pd
->md
!= NULL
&& menu_check_cb(c
, pd
->md
, px
, py
) == 0)
167 if (px
< pd
->px
|| px
> pd
->px
+ pd
->sx
- 1)
169 if (py
< pd
->py
|| py
> pd
->py
+ pd
->sy
- 1)
175 popup_draw_cb(struct client
*c
, void *data
, struct screen_redraw_ctx
*rctx
)
177 struct popup_data
*pd
= data
;
178 struct tty
*tty
= &c
->tty
;
180 struct screen_write_ctx ctx
;
181 u_int i
, px
= pd
->px
, py
= pd
->py
;
182 struct colour_palette
*palette
= &pd
->palette
;
185 screen_init(&s
, pd
->sx
, pd
->sy
, 0);
186 screen_write_start(&ctx
, &s
);
187 screen_write_clearscreen(&ctx
, 8);
189 if (pd
->flags
& POPUP_NOBORDER
) {
190 screen_write_cursormove(&ctx
, 0, 0, 0);
191 screen_write_fast_copy(&ctx
, &pd
->s
, 0, 0, pd
->sx
, pd
->sy
);
192 } else if (pd
->sx
> 2 && pd
->sy
> 2) {
193 screen_write_box(&ctx
, pd
->sx
, pd
->sy
);
194 screen_write_cursormove(&ctx
, 1, 1, 0);
195 screen_write_fast_copy(&ctx
, &pd
->s
, 0, 0, pd
->sx
- 2,
198 screen_write_stop(&ctx
);
200 memcpy(&gc
, &grid_default_cell
, sizeof gc
);
201 gc
.fg
= pd
->palette
.fg
;
202 gc
.bg
= pd
->palette
.bg
;
204 if (pd
->md
!= NULL
) {
205 c
->overlay_check
= menu_check_cb
;
206 c
->overlay_data
= pd
->md
;
208 c
->overlay_check
= NULL
;
209 c
->overlay_data
= NULL
;
211 for (i
= 0; i
< pd
->sy
; i
++)
212 tty_draw_line(tty
, &s
, 0, i
, pd
->sx
, px
, py
+ i
, &gc
, palette
);
213 if (pd
->md
!= NULL
) {
214 c
->overlay_check
= NULL
;
215 c
->overlay_data
= NULL
;
216 menu_draw_cb(c
, pd
->md
, rctx
);
218 c
->overlay_check
= popup_check_cb
;
219 c
->overlay_data
= pd
;
223 popup_free_cb(struct client
*c
, void *data
)
225 struct popup_data
*pd
= data
;
226 struct cmdq_item
*item
= pd
->item
;
229 menu_free_cb(c
, pd
->md
);
232 pd
->cb(pd
->status
, pd
->arg
);
235 if (cmdq_get_client(item
) != NULL
&&
236 cmdq_get_client(item
)->session
== NULL
)
237 cmdq_get_client(item
)->retval
= pd
->status
;
240 server_client_unref(pd
->c
);
244 input_free(pd
->ictx
);
247 colour_palette_free(&pd
->palette
);
253 popup_resize_cb(__unused
struct client
*c
, void *data
)
255 struct popup_data
*pd
= data
;
256 struct tty
*tty
= &c
->tty
;
261 menu_free_cb(c
, pd
->md
);
263 /* Adjust position and size. */
264 if (pd
->psy
> tty
->sy
)
268 if (pd
->psx
> tty
->sx
)
272 if (pd
->ppy
+ pd
->sy
> tty
->sy
)
273 pd
->py
= tty
->sy
- pd
->sy
;
276 if (pd
->ppx
+ pd
->sx
> tty
->sx
)
277 pd
->px
= tty
->sx
- pd
->sx
;
281 /* Avoid zero size screens. */
282 if (pd
->flags
& POPUP_NOBORDER
) {
283 screen_resize(&pd
->s
, pd
->sx
, pd
->sy
, 0);
285 job_resize(pd
->job
, pd
->sx
, pd
->sy
);
286 } else if (pd
->sx
> 2 && pd
->sy
> 2) {
287 screen_resize(&pd
->s
, pd
->sx
- 2, pd
->sy
- 2, 0);
289 job_resize(pd
->job
, pd
->sx
- 2, pd
->sy
- 2);
294 popup_make_pane(struct popup_data
*pd
, enum layout_type type
)
296 struct client
*c
= pd
->c
;
297 struct session
*s
= c
->session
;
298 struct window
*w
= s
->curw
->window
;
299 struct layout_cell
*lc
;
300 struct window_pane
*wp
= w
->active
, *new_wp
;
306 lc
= layout_split_pane(wp
, type
, -1, 0);
307 hlimit
= options_get_number(s
->options
, "history-limit");
308 new_wp
= window_add_pane(wp
->window
, NULL
, hlimit
, 0);
309 layout_assign_pane(lc
, new_wp
, 0);
311 new_wp
->fd
= job_transfer(pd
->job
, &new_wp
->pid
, new_wp
->tty
,
315 screen_set_title(&pd
->s
, new_wp
->base
.title
);
316 screen_free(&new_wp
->base
);
317 memcpy(&new_wp
->base
, &pd
->s
, sizeof wp
->base
);
318 screen_resize(&new_wp
->base
, new_wp
->sx
, new_wp
->sy
, 1);
319 screen_init(&pd
->s
, 1, 1, 0);
321 shell
= options_get_string(s
->options
, "default-shell");
322 if (!checkshell(shell
))
323 shell
= _PATH_BSHELL
;
324 new_wp
->shell
= xstrdup(shell
);
326 window_pane_set_event(new_wp
);
327 window_set_active_pane(w
, new_wp
, 1);
328 new_wp
->flags
|= PANE_CHANGED
;
334 popup_menu_done(__unused
struct menu
*menu
, __unused u_int choice
,
335 key_code key
, void *data
)
337 struct popup_data
*pd
= data
;
338 struct client
*c
= pd
->c
;
339 struct paste_buffer
*pb
;
345 server_redraw_client(pd
->c
);
349 pb
= paste_get_top(NULL
);
351 buf
= paste_buffer_data(pb
, &len
);
352 bufferevent_write(job_get_event(pd
->job
), buf
, len
);
360 server_redraw_client(c
);
363 pd
->px
= c
->tty
.sx
/ 2 - pd
->sx
/ 2;
364 pd
->py
= c
->tty
.sy
/ 2 - pd
->sy
/ 2;
365 server_redraw_client(c
);
368 popup_make_pane(pd
, LAYOUT_LEFTRIGHT
);
371 popup_make_pane(pd
, LAYOUT_TOPBOTTOM
);
380 popup_handle_drag(struct client
*c
, struct popup_data
*pd
,
381 struct mouse_event
*m
)
385 if (!MOUSE_DRAG(m
->b
))
387 else if (pd
->dragging
== MOVE
) {
390 else if (m
->x
- pd
->dx
+ pd
->sx
> c
->tty
.sx
)
391 px
= c
->tty
.sx
- pd
->sx
;
396 else if (m
->y
- pd
->dy
+ pd
->sy
> c
->tty
.sy
)
397 py
= c
->tty
.sy
- pd
->sy
;
402 pd
->dx
= m
->x
- pd
->px
;
403 pd
->dy
= m
->y
- pd
->py
;
406 server_redraw_client(c
);
407 } else if (pd
->dragging
== SIZE
) {
408 if (pd
->flags
& POPUP_NOBORDER
) {
409 if (m
->x
< pd
->px
+ 1)
411 if (m
->y
< pd
->py
+ 1)
414 if (m
->x
< pd
->px
+ 3)
416 if (m
->y
< pd
->py
+ 3)
419 pd
->sx
= m
->x
- pd
->px
;
420 pd
->sy
= m
->y
- pd
->py
;
424 if (pd
->flags
& POPUP_NOBORDER
) {
425 screen_resize(&pd
->s
, pd
->sx
, pd
->sy
, 0);
427 job_resize(pd
->job
, pd
->sx
, pd
->sy
);
429 screen_resize(&pd
->s
, pd
->sx
- 2, pd
->sy
- 2, 0);
431 job_resize(pd
->job
, pd
->sx
- 2, pd
->sy
- 2);
433 server_redraw_client(c
);
438 popup_key_cb(struct client
*c
, void *data
, struct key_event
*event
)
440 struct popup_data
*pd
= data
;
441 struct mouse_event
*m
= &event
->m
;
445 enum { NONE
, LEFT
, RIGHT
, TOP
, BOTTOM
} border
= NONE
;
447 if (pd
->md
!= NULL
) {
448 if (menu_key_cb(c
, pd
->md
, event
) == 1) {
452 server_client_clear_overlay(c
);
454 server_redraw_client(c
);
459 if (KEYC_IS_MOUSE(event
->key
)) {
460 if (pd
->dragging
!= OFF
) {
461 popup_handle_drag(c
, pd
, m
);
465 m
->x
> pd
->px
+ pd
->sx
- 1 ||
467 m
->y
> pd
->py
+ pd
->sy
- 1) {
468 if (MOUSE_BUTTONS(m
->b
) == 2)
472 if (~pd
->flags
& POPUP_NOBORDER
) {
475 else if (m
->x
== pd
->px
+ pd
->sx
- 1)
477 else if (m
->y
== pd
->py
)
479 else if (m
->y
== pd
->py
+ pd
->sy
- 1)
482 if ((m
->b
& MOUSE_MASK_MODIFIERS
) == 0 &&
483 MOUSE_BUTTONS(m
->b
) == 2 &&
484 (border
== LEFT
|| border
== TOP
))
486 if (((m
->b
& MOUSE_MASK_MODIFIERS
) == MOUSE_MASK_META
) ||
488 if (!MOUSE_DRAG(m
->b
))
490 if (MOUSE_BUTTONS(m
->lb
) == 0)
492 else if (MOUSE_BUTTONS(m
->lb
) == 2)
494 pd
->dx
= m
->lx
- pd
->px
;
495 pd
->dy
= m
->ly
- pd
->py
;
499 if ((((pd
->flags
& (POPUP_CLOSEEXIT
|POPUP_CLOSEEXITZERO
)) == 0) ||
501 (event
->key
== '\033' || event
->key
== '\003'))
503 if (pd
->job
!= NULL
) {
504 if (KEYC_IS_MOUSE(event
->key
)) {
505 /* Must be inside, checked already. */
506 if (pd
->flags
& POPUP_NOBORDER
) {
510 px
= m
->x
- pd
->px
- 1;
511 py
= m
->y
- pd
->py
- 1;
513 if (!input_key_get_mouse(&pd
->s
, m
, px
, py
, &buf
, &len
))
515 bufferevent_write(job_get_event(pd
->job
), buf
, len
);
518 input_key(&pd
->s
, job_get_event(pd
->job
), event
->key
);
523 pd
->menu
= menu_create("");
524 if (pd
->flags
& POPUP_INTERNAL
) {
525 menu_add_items(pd
->menu
, popup_internal_menu_items
, NULL
, NULL
,
528 menu_add_items(pd
->menu
, popup_menu_items
, NULL
, NULL
, NULL
);
529 if (m
->x
>= (pd
->menu
->width
+ 4) / 2)
530 x
= m
->x
- (pd
->menu
->width
+ 4) / 2;
533 pd
->md
= menu_prepare(pd
->menu
, 0, NULL
, x
, m
->y
, c
, NULL
,
534 popup_menu_done
, pd
);
535 c
->flags
|= CLIENT_REDRAWOVERLAY
;
545 popup_job_update_cb(struct job
*job
)
547 struct popup_data
*pd
= job_get_data(job
);
548 struct evbuffer
*evb
= job_get_event(job
)->input
;
549 struct client
*c
= pd
->c
;
550 struct screen
*s
= &pd
->s
;
551 void *data
= EVBUFFER_DATA(evb
);
552 size_t size
= EVBUFFER_LENGTH(evb
);
557 if (pd
->md
!= NULL
) {
558 c
->overlay_check
= menu_check_cb
;
559 c
->overlay_data
= pd
->md
;
561 c
->overlay_check
= NULL
;
562 c
->overlay_data
= NULL
;
564 input_parse_screen(pd
->ictx
, s
, popup_init_ctx_cb
, pd
, data
, size
);
565 c
->overlay_check
= popup_check_cb
;
566 c
->overlay_data
= pd
;
568 evbuffer_drain(evb
, size
);
572 popup_job_complete_cb(struct job
*job
)
574 struct popup_data
*pd
= job_get_data(job
);
577 status
= job_get_status(pd
->job
);
578 if (WIFEXITED(status
))
579 pd
->status
= WEXITSTATUS(status
);
580 else if (WIFSIGNALED(status
))
581 pd
->status
= WTERMSIG(status
);
586 if ((pd
->flags
& POPUP_CLOSEEXIT
) ||
587 ((pd
->flags
& POPUP_CLOSEEXITZERO
) && pd
->status
== 0))
588 server_client_clear_overlay(pd
->c
);
592 popup_display(int flags
, struct cmdq_item
*item
, u_int px
, u_int py
, u_int sx
,
593 u_int sy
, const char *shellcmd
, int argc
, char **argv
, const char *cwd
,
594 struct client
*c
, struct session
*s
, popup_close_cb cb
, void *arg
)
596 struct popup_data
*pd
;
599 if (flags
& POPUP_NOBORDER
) {
600 if (sx
< 1 || sy
< 1)
605 if (sx
< 3 || sy
< 3)
610 if (c
->tty
.sx
< sx
|| c
->tty
.sy
< sy
)
613 pd
= xcalloc(1, sizeof *pd
);
622 pd
->status
= 128 + SIGHUP
;
624 screen_init(&pd
->s
, sx
- 2, sy
- 2, 0);
625 colour_palette_init(&pd
->palette
);
626 colour_palette_from_option(&pd
->palette
, global_w_options
);
638 pd
->job
= job_run(shellcmd
, argc
, argv
, s
, cwd
,
639 popup_job_update_cb
, popup_job_complete_cb
, NULL
, pd
,
640 JOB_NOWAIT
|JOB_PTY
|JOB_KEEPWRITE
, jx
, jy
);
641 pd
->ictx
= input_init(NULL
, job_get_event(pd
->job
), &pd
->palette
);
643 server_client_set_overlay(c
, 0, popup_check_cb
, popup_mode_cb
,
644 popup_draw_cb
, popup_key_cb
, popup_free_cb
, popup_resize_cb
, pd
);
649 popup_editor_free(struct popup_editor
*pe
)
657 popup_editor_close_cb(int status
, void *arg
)
659 struct popup_editor
*pe
= arg
;
665 pe
->cb(NULL
, 0, pe
->arg
);
666 popup_editor_free(pe
);
670 f
= fopen(pe
->path
, "r");
672 fseeko(f
, 0, SEEK_END
);
674 fseeko(f
, 0, SEEK_SET
);
677 (uintmax_t)len
> (uintmax_t)SIZE_MAX
||
678 (buf
= malloc(len
)) == NULL
||
679 fread(buf
, len
, 1, f
) != 1) {
686 pe
->cb(buf
, len
, pe
->arg
); /* callback now owns buffer */
687 popup_editor_free(pe
);
691 popup_editor(struct client
*c
, const char *buf
, size_t len
,
692 popup_finish_edit_cb cb
, void *arg
)
694 struct popup_editor
*pe
;
698 char path
[] = _PATH_TMP
"tmux.XXXXXXXX";
700 u_int px
, py
, sx
, sy
;
702 editor
= options_get_string(global_options
, "editor");
710 if (fwrite(buf
, len
, 1, f
) != 1) {
716 pe
= xcalloc(1, sizeof *pe
);
717 pe
->path
= xstrdup(path
);
721 sx
= c
->tty
.sx
* 9 / 10;
722 sy
= c
->tty
.sy
* 9 / 10;
723 px
= (c
->tty
.sx
/ 2) - (sx
/ 2);
724 py
= (c
->tty
.sy
/ 2) - (sy
/ 2);
726 xasprintf(&cmd
, "%s %s", editor
, path
);
727 if (popup_display(POPUP_INTERNAL
|POPUP_CLOSEEXIT
, NULL
, px
, py
, sx
, sy
,
728 cmd
, 0, NULL
, _PATH_TMP
, c
, NULL
, popup_editor_close_cb
, pe
) != 0) {
729 popup_editor_free(pe
);