4 * Copyright (c) 2019 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>
28 * Display a menu on a client.
31 static enum args_parse_type
cmd_display_menu_args_parse(struct args
*,
33 static enum cmd_retval
cmd_display_menu_exec(struct cmd
*,
35 static enum cmd_retval
cmd_display_popup_exec(struct cmd
*,
38 const struct cmd_entry cmd_display_menu_entry
= {
39 .name
= "display-menu",
42 .args
= { "b:c:C:H:s:S:MOt:T:x:y:", 1, -1, cmd_display_menu_args_parse
},
43 .usage
= "[-MO] [-b border-lines] [-c target-client] "
44 "[-C starting-choice] [-H selected-style] [-s style] "
45 "[-S border-style] " CMD_TARGET_PANE_USAGE
"[-T title] "
46 "[-x position] [-y position] name key command ...",
48 .target
= { 't', CMD_FIND_PANE
, 0 },
50 .flags
= CMD_AFTERHOOK
|CMD_CLIENT_CFLAG
,
51 .exec
= cmd_display_menu_exec
54 const struct cmd_entry cmd_display_popup_entry
= {
55 .name
= "display-popup",
58 .args
= { "Bb:Cc:d:e:Eh:s:S:t:T:w:x:y:", 0, -1, NULL
},
59 .usage
= "[-BCE] [-b border-lines] [-c target-client] "
60 "[-d start-directory] [-e environment] [-h height] "
61 "[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE
62 "[-T title] [-w width] [-x position] [-y position] "
65 .target
= { 't', CMD_FIND_PANE
, 0 },
67 .flags
= CMD_AFTERHOOK
|CMD_CLIENT_CFLAG
,
68 .exec
= cmd_display_popup_exec
71 static enum args_parse_type
72 cmd_display_menu_args_parse(struct args
*args
, u_int idx
, __unused
char **cause
)
75 enum args_parse_type type
= ARGS_PARSE_STRING
;
78 type
= ARGS_PARSE_STRING
;
81 if (*args_string(args
, i
++) == '\0')
84 type
= ARGS_PARSE_STRING
;
88 type
= ARGS_PARSE_COMMANDS_OR_STRING
;
96 cmd_display_menu_get_position(struct client
*tc
, struct cmdq_item
*item
,
97 struct args
*args
, u_int
*px
, u_int
*py
, u_int w
, u_int h
)
99 struct tty
*tty
= &tc
->tty
;
100 struct cmd_find_state
*target
= cmdq_get_target(item
);
101 struct key_event
*event
= cmdq_get_event(item
);
102 struct session
*s
= tc
->session
;
103 struct winlink
*wl
= target
->wl
;
104 struct window_pane
*wp
= target
->wp
;
105 struct style_ranges
*ranges
= NULL
;
106 struct style_range
*sr
= NULL
;
110 u_int line
, ox
, oy
, sx
, sy
, lines
, position
;
112 struct format_tree
*ft
;
115 * Work out the position from the -x and -y arguments. This is the
116 * bottom-left position.
119 /* If the popup is too big, stop now. */
120 if (w
> tty
->sx
|| h
> tty
->sy
)
123 /* Create format with mouse position if any. */
124 ft
= format_create_from_target(item
);
125 if (event
->m
.valid
) {
126 format_add(ft
, "popup_mouse_x", "%u", event
->m
.x
);
127 format_add(ft
, "popup_mouse_y", "%u", event
->m
.y
);
131 * If there are any status lines, add this window position and the
132 * status line position.
134 top
= status_at_line(tc
);
136 lines
= status_line_size(tc
);
141 position
= options_get_number(s
->options
, "status-position");
143 for (line
= 0; line
< lines
; line
++) {
144 ranges
= &tc
->status
.entries
[line
].ranges
;
145 TAILQ_FOREACH(sr
, ranges
, entry
) {
146 if (sr
->type
!= STYLE_RANGE_WINDOW
)
148 if (sr
->argument
== (u_int
)wl
->idx
)
156 format_add(ft
, "popup_window_status_line_x", "%u",
159 format_add(ft
, "popup_window_status_line_y",
162 format_add(ft
, "popup_window_status_line_y",
163 "%u", tty
->sy
- lines
+ line
);
168 format_add(ft
, "popup_status_line_y", "%u", lines
+ h
);
170 format_add(ft
, "popup_status_line_y", "%u",
176 /* Popup width and height. */
177 format_add(ft
, "popup_width", "%u", w
);
178 format_add(ft
, "popup_height", "%u", h
);
180 /* Position so popup is in the centre. */
181 n
= (long)(tty
->sx
- 1) / 2 - w
/ 2;
183 format_add(ft
, "popup_centre_x", "%u", 0);
185 format_add(ft
, "popup_centre_x", "%ld", n
);
186 n
= (tty
->sy
- 1) / 2 + h
/ 2;
188 format_add(ft
, "popup_centre_y", "%u", tty
->sy
- h
);
190 format_add(ft
, "popup_centre_y", "%ld", n
);
192 /* Position of popup relative to mouse. */
193 if (event
->m
.valid
) {
194 n
= (long)event
->m
.x
- w
/ 2;
196 format_add(ft
, "popup_mouse_centre_x", "%u", 0);
198 format_add(ft
, "popup_mouse_centre_x", "%ld", n
);
199 n
= event
->m
.y
- h
/ 2;
200 if (n
+ h
>= tty
->sy
) {
201 format_add(ft
, "popup_mouse_centre_y", "%u",
204 format_add(ft
, "popup_mouse_centre_y", "%ld", n
);
205 n
= (long)event
->m
.y
+ h
;
207 format_add(ft
, "popup_mouse_top", "%u", tty
->sy
- 1);
209 format_add(ft
, "popup_mouse_top", "%ld", n
);
212 format_add(ft
, "popup_mouse_bottom", "%u", 0);
214 format_add(ft
, "popup_mouse_bottom", "%ld", n
);
217 /* Position in pane. */
218 tty_window_offset(&tc
->tty
, &ox
, &oy
, &sx
, &sy
);
219 n
= top
+ wp
->yoff
- oy
+ h
;
221 format_add(ft
, "popup_pane_top", "%u", tty
->sy
- h
);
223 format_add(ft
, "popup_pane_top", "%ld", n
);
224 format_add(ft
, "popup_pane_bottom", "%u", top
+ wp
->yoff
+ wp
->sy
- oy
);
225 format_add(ft
, "popup_pane_left", "%u", wp
->xoff
- ox
);
226 n
= (long)wp
->xoff
+ wp
->sx
- ox
- w
;
228 format_add(ft
, "popup_pane_right", "%u", 0);
230 format_add(ft
, "popup_pane_right", "%ld", n
);
232 /* Expand horizontal position. */
233 xp
= args_get(args
, 'x');
234 if (xp
== NULL
|| strcmp(xp
, "C") == 0)
235 xp
= "#{popup_centre_x}";
236 else if (strcmp(xp
, "R") == 0)
237 xp
= "#{popup_pane_right}";
238 else if (strcmp(xp
, "P") == 0)
239 xp
= "#{popup_pane_left}";
240 else if (strcmp(xp
, "M") == 0)
241 xp
= "#{popup_mouse_centre_x}";
242 else if (strcmp(xp
, "W") == 0)
243 xp
= "#{popup_window_status_line_x}";
244 p
= format_expand(ft
, xp
);
245 n
= strtol(p
, NULL
, 10);
246 if (n
+ w
>= tty
->sx
)
251 log_debug("%s: -x: %s = %s = %u (-w %u)", __func__
, xp
, p
, *px
, w
);
254 /* Expand vertical position */
255 yp
= args_get(args
, 'y');
256 if (yp
== NULL
|| strcmp(yp
, "C") == 0)
257 yp
= "#{popup_centre_y}";
258 else if (strcmp(yp
, "P") == 0)
259 yp
= "#{popup_pane_bottom}";
260 else if (strcmp(yp
, "M") == 0)
261 yp
= "#{popup_mouse_top}";
262 else if (strcmp(yp
, "S") == 0)
263 yp
= "#{popup_status_line_y}";
264 else if (strcmp(yp
, "W") == 0)
265 yp
= "#{popup_window_status_line_y}";
266 p
= format_expand(ft
, yp
);
267 n
= strtol(p
, NULL
, 10);
272 if (n
+ h
>= tty
->sy
)
277 log_debug("%s: -y: %s = %s = %u (-h %u)", __func__
, yp
, p
, *py
, h
);
284 static enum cmd_retval
285 cmd_display_menu_exec(struct cmd
*self
, struct cmdq_item
*item
)
287 struct args
*args
= cmd_get_args(self
);
288 struct cmd_find_state
*target
= cmdq_get_target(item
);
289 struct key_event
*event
= cmdq_get_event(item
);
290 struct client
*tc
= cmdq_get_target_client(item
);
291 struct menu
*menu
= NULL
;
292 struct menu_item menu_item
;
293 const char *key
, *name
, *value
;
294 const char *style
= args_get(args
, 's');
295 const char *border_style
= args_get(args
, 'S');
296 const char *selected_style
= args_get(args
, 'H');
297 enum box_lines lines
= BOX_LINES_DEFAULT
;
299 int flags
= 0, starting_choice
= 0;
300 u_int px
, py
, i
, count
= args_count(args
);
301 struct options
*o
= target
->s
->curw
->window
->options
;
302 struct options_entry
*oe
;
305 if (tc
->overlay_draw
!= NULL
)
306 return (CMD_RETURN_NORMAL
);
308 if (args_has(args
, 'C')) {
309 if (strcmp(args_get(args
, 'C'), "-") == 0)
310 starting_choice
= -1;
312 starting_choice
= args_strtonum(args
, 'C', 0, UINT_MAX
,
315 cmdq_error(item
, "starting choice %s", cause
);
317 return (CMD_RETURN_ERROR
);
322 if (args_has(args
, 'T'))
323 title
= format_single_from_target(item
, args_get(args
, 'T'));
326 menu
= menu_create(title
);
329 for (i
= 0; i
!= count
; /* nothing */) {
330 name
= args_string(args
, i
++);
332 menu_add_item(menu
, NULL
, item
, tc
, target
);
337 cmdq_error(item
, "not enough arguments");
339 return (CMD_RETURN_ERROR
);
341 key
= args_string(args
, i
++);
343 menu_item
.name
= name
;
344 menu_item
.key
= key_string_lookup_string(key
);
345 menu_item
.command
= args_string(args
, i
++);
347 menu_add_item(menu
, &menu_item
, item
, tc
, target
);
350 cmdq_error(item
, "invalid menu arguments");
351 return (CMD_RETURN_ERROR
);
353 if (menu
->count
== 0) {
355 return (CMD_RETURN_NORMAL
);
357 if (!cmd_display_menu_get_position(tc
, item
, args
, &px
, &py
,
358 menu
->width
+ 4, menu
->count
+ 2)) {
360 return (CMD_RETURN_NORMAL
);
363 value
= args_get(args
, 'b');
365 oe
= options_get(o
, "menu-border-lines");
366 lines
= options_find_choice(options_table_entry(oe
), value
,
369 cmdq_error(item
, "menu-border-lines %s", cause
);
371 return (CMD_RETURN_ERROR
);
375 if (args_has(args
, 'O'))
376 flags
|= MENU_STAYOPEN
;
377 if (!event
->m
.valid
&& !args_has(args
, 'M'))
378 flags
|= MENU_NOMOUSE
;
379 if (menu_display(menu
, flags
, starting_choice
, item
, px
, py
, tc
, lines
,
380 style
, selected_style
, border_style
, target
, NULL
, NULL
) != 0)
381 return (CMD_RETURN_NORMAL
);
382 return (CMD_RETURN_WAIT
);
385 static enum cmd_retval
386 cmd_display_popup_exec(struct cmd
*self
, struct cmdq_item
*item
)
388 struct args
*args
= cmd_get_args(self
);
389 struct cmd_find_state
*target
= cmdq_get_target(item
);
390 struct session
*s
= target
->s
;
391 struct client
*tc
= cmdq_get_target_client(item
);
392 struct tty
*tty
= &tc
->tty
;
393 const char *value
, *shell
, *shellcmd
= NULL
;
394 const char *style
= args_get(args
, 's');
395 const char *border_style
= args_get(args
, 'S');
396 char *cwd
, *cause
= NULL
, **argv
= NULL
, *title
;
397 int flags
= 0, argc
= 0;
398 enum box_lines lines
= BOX_LINES_DEFAULT
;
399 u_int px
, py
, w
, h
, count
= args_count(args
);
400 struct args_value
*av
;
401 struct environ
*env
= NULL
;
402 struct options
*o
= s
->curw
->window
->options
;
403 struct options_entry
*oe
;
405 if (args_has(args
, 'C')) {
406 server_client_clear_overlay(tc
);
407 return (CMD_RETURN_NORMAL
);
409 if (tc
->overlay_draw
!= NULL
)
410 return (CMD_RETURN_NORMAL
);
413 if (args_has(args
, 'h')) {
414 h
= args_percentage(args
, 'h', 1, tty
->sy
, tty
->sy
, &cause
);
416 cmdq_error(item
, "height %s", cause
);
418 return (CMD_RETURN_ERROR
);
423 if (args_has(args
, 'w')) {
424 w
= args_percentage(args
, 'w', 1, tty
->sx
, tty
->sx
, &cause
);
426 cmdq_error(item
, "width %s", cause
);
428 return (CMD_RETURN_ERROR
);
436 if (!cmd_display_menu_get_position(tc
, item
, args
, &px
, &py
, w
, h
))
437 return (CMD_RETURN_NORMAL
);
439 value
= args_get(args
, 'b');
440 if (args_has(args
, 'B'))
441 lines
= BOX_LINES_NONE
;
442 else if (value
!= NULL
) {
443 oe
= options_get(o
, "popup-border-lines");
444 lines
= options_find_choice(options_table_entry(oe
), value
,
447 cmdq_error(item
, "popup-border-lines %s", cause
);
449 return (CMD_RETURN_ERROR
);
453 value
= args_get(args
, 'd');
455 cwd
= format_single_from_target(item
, value
);
457 cwd
= xstrdup(server_client_get_cwd(tc
, s
));
459 shellcmd
= options_get_string(s
->options
, "default-command");
461 shellcmd
= args_string(args
, 0);
462 if (count
<= 1 && (shellcmd
== NULL
|| *shellcmd
== '\0')) {
464 shell
= options_get_string(s
->options
, "default-shell");
465 if (!checkshell(shell
))
466 shell
= _PATH_BSHELL
;
467 cmd_append_argv(&argc
, &argv
, shell
);
469 args_to_vector(args
, &argc
, &argv
);
471 if (args_has(args
, 'e') >= 1) {
472 env
= environ_create();
473 av
= args_first_value(args
, 'e');
475 environ_put(env
, av
->string
, 0);
476 av
= args_next_value(av
);
480 if (args_has(args
, 'T'))
481 title
= format_single_from_target(item
, args_get(args
, 'T'));
484 if (args_has(args
, 'E') > 1)
485 flags
|= POPUP_CLOSEEXITZERO
;
486 else if (args_has(args
, 'E'))
487 flags
|= POPUP_CLOSEEXIT
;
488 if (popup_display(flags
, lines
, item
, px
, py
, w
, h
, env
, shellcmd
, argc
,
489 argv
, cwd
, title
, tc
, s
, style
, border_style
, NULL
, NULL
) != 0) {
490 cmd_free_argv(argc
, argv
);
495 return (CMD_RETURN_NORMAL
);
501 cmd_free_argv(argc
, argv
);
502 return (CMD_RETURN_WAIT
);