Home | History | Annotate | Line # | Download | only in dist
      1 /* $OpenBSD$ */
      2 
      3 /*
      4  * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott (at) gmail.com>
      5  *
      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.
      9  *
     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.
     17  */
     18 
     19 #include <sys/types.h>
     20 
     21 #include <stdlib.h>
     22 #include <string.h>
     23 
     24 #include "tmux.h"
     25 
     26 /*
     27  * Display a menu on a client.
     28  */
     29 
     30 static enum args_parse_type	cmd_display_menu_args_parse(struct args *,
     31 				    u_int, char **);
     32 static enum cmd_retval		cmd_display_menu_exec(struct cmd *,
     33 				    struct cmdq_item *);
     34 static enum cmd_retval		cmd_display_popup_exec(struct cmd *,
     35 				    struct cmdq_item *);
     36 
     37 const struct cmd_entry cmd_display_menu_entry = {
     38 	.name = "display-menu",
     39 	.alias = "menu",
     40 
     41 	.args = { "b:c:C:H:s:S:MOt:T:x:y:", 1, -1, cmd_display_menu_args_parse },
     42 	.usage = "[-MO] [-b border-lines] [-c target-client] "
     43 		 "[-C starting-choice] [-H selected-style] [-s style] "
     44 		 "[-S border-style] " CMD_TARGET_PANE_USAGE " [-T title] "
     45 		 "[-x position] [-y position] name [key] [command] ...",
     46 
     47 	.target = { 't', CMD_FIND_PANE, 0 },
     48 
     49 	.flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG,
     50 	.exec = cmd_display_menu_exec
     51 };
     52 
     53 const struct cmd_entry cmd_display_popup_entry = {
     54 	.name = "display-popup",
     55 	.alias = "popup",
     56 
     57 	.args = { "Bb:Cc:d:e:Eh:kNs:S:t:T:w:x:y:", 0, -1, NULL },
     58 	.usage = "[-BCEkN] [-b border-lines] [-c target-client] "
     59 		 "[-d start-directory] [-e environment] [-h height] "
     60 		 "[-s style] [-S border-style] " CMD_TARGET_PANE_USAGE
     61 		 " [-T title] [-w width] [-x position] [-y position] "
     62 		 "[shell-command [argument ...]]",
     63 
     64 	.target = { 't', CMD_FIND_PANE, 0 },
     65 
     66 	.flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG,
     67 	.exec = cmd_display_popup_exec
     68 };
     69 
     70 static enum args_parse_type
     71 cmd_display_menu_args_parse(struct args *args, u_int idx, __unused char **cause)
     72 {
     73 	u_int			 i = 0;
     74 	enum args_parse_type	 type = ARGS_PARSE_STRING;
     75 
     76 	for (;;) {
     77 		type = ARGS_PARSE_STRING;
     78 		if (i == idx)
     79 			break;
     80 		if (*args_string(args, i++) == '\0')
     81 			continue;
     82 
     83 		type = ARGS_PARSE_STRING;
     84 		if (i++ == idx)
     85 			break;
     86 
     87 		type = ARGS_PARSE_COMMANDS_OR_STRING;
     88 		if (i++ == idx)
     89 			break;
     90 	}
     91 	return (type);
     92 }
     93 
     94 static int
     95 cmd_display_menu_get_pos(struct client *tc, struct cmdq_item *item,
     96     struct args *args, u_int *px, u_int *py, u_int w, u_int h)
     97 {
     98 	struct tty		*tty = &tc->tty;
     99 	struct cmd_find_state	*target = cmdq_get_target(item);
    100 	struct key_event	*event = cmdq_get_event(item);
    101 	struct session		*s = tc->session;
    102 	struct winlink		*wl = target->wl;
    103 	struct window_pane	*wp = target->wp;
    104 	struct style_ranges	*ranges = NULL;
    105 	struct style_range	*sr = NULL;
    106 	const char		*xp, *yp;
    107 	char			*p;
    108 	int			 top;
    109 	u_int			 line, ox, oy, sx, sy, lines, position;
    110 	long			 n;
    111 	struct format_tree	*ft;
    112 
    113 	/*
    114 	 * Work out the position from the -x and -y arguments. This is the
    115 	 * bottom-left position.
    116 	 */
    117 
    118 	/* If the popup is too big, stop now. */
    119 	if (w > tty->sx || h > tty->sy)
    120 		return (0);
    121 
    122 	/* Create format with mouse position if any. */
    123 	ft = format_create_from_target(item);
    124 	if (event->m.valid) {
    125 		format_add(ft, "popup_mouse_x", "%u", event->m.x);
    126 		format_add(ft, "popup_mouse_y", "%u", event->m.y);
    127 	}
    128 
    129 	/*
    130 	 * If there are any status lines, add this window position and the
    131 	 * status line position.
    132 	 */
    133 	top = status_at_line(tc);
    134 	if (top != -1) {
    135 		lines = status_line_size(tc);
    136 		if (top == 0)
    137 			top = lines;
    138 		else
    139 			top = 0;
    140 		position = options_get_number(s->options, "status-position");
    141 
    142 		for (line = 0; line < lines; line++) {
    143 			ranges = &tc->status.entries[line].ranges;
    144 			TAILQ_FOREACH(sr, ranges, entry) {
    145 				if (sr->type != STYLE_RANGE_WINDOW)
    146 					continue;
    147 				if (sr->argument == (u_int)wl->idx)
    148 					break;
    149 			}
    150 			if (sr != NULL)
    151 				break;
    152 		}
    153 
    154 		if (sr != NULL) {
    155 			format_add(ft, "popup_window_status_line_x", "%u",
    156 			    sr->start);
    157 			if (position == 0) {
    158 				format_add(ft, "popup_window_status_line_y",
    159 				    "%u", line + 1 + h);
    160 			} else {
    161 				format_add(ft, "popup_window_status_line_y",
    162 				    "%u", tty->sy - lines + line);
    163 			}
    164 		}
    165 
    166 		if (position == 0)
    167 			format_add(ft, "popup_status_line_y", "%u", lines + h);
    168 		else {
    169 			format_add(ft, "popup_status_line_y", "%u",
    170 			    tty->sy - lines);
    171 		}
    172 	} else
    173 		top = 0;
    174 
    175 	/* Popup width and height. */
    176 	format_add(ft, "popup_width", "%u", w);
    177 	format_add(ft, "popup_height", "%u", h);
    178 
    179 	/* Position so popup is in the centre. */
    180 	n = (long)(tty->sx - 1) / 2 - w / 2;
    181 	if (n < 0)
    182 		format_add(ft, "popup_centre_x", "%u", 0);
    183 	else
    184 		format_add(ft, "popup_centre_x", "%ld", n);
    185 	n = (tty->sy - 1) / 2 + h / 2;
    186 	if ((unsigned long)n >= tty->sy)
    187 		format_add(ft, "popup_centre_y", "%u", tty->sy - h);
    188 	else
    189 		format_add(ft, "popup_centre_y", "%ld", n);
    190 
    191 	/* Position of popup relative to mouse. */
    192 	if (event->m.valid) {
    193 		n = (long)event->m.x - w / 2;
    194 		if (n < 0)
    195 			format_add(ft, "popup_mouse_centre_x", "%u", 0);
    196 		else
    197 			format_add(ft, "popup_mouse_centre_x", "%ld", n);
    198 		n = event->m.y - h / 2;
    199 		if (n + h >= tty->sy) {
    200 			format_add(ft, "popup_mouse_centre_y", "%u",
    201 			    tty->sy - h);
    202 		} else
    203 			format_add(ft, "popup_mouse_centre_y", "%ld", n);
    204 		n = (long)event->m.y + h;
    205 		if (n >= (long)tty->sy)
    206 			format_add(ft, "popup_mouse_top", "%u", tty->sy - 1);
    207 		else
    208 			format_add(ft, "popup_mouse_top", "%ld", n);
    209 		n = event->m.y - h;
    210 		if (n < 0)
    211 			format_add(ft, "popup_mouse_bottom", "%u", 0);
    212 		else
    213 			format_add(ft, "popup_mouse_bottom", "%ld", n);
    214 	}
    215 
    216 	/* Position in pane. */
    217 	tty_window_offset(&tc->tty, &ox, &oy, &sx, &sy);
    218 	n = top + wp->yoff - oy + h;
    219 	if ((unsigned long)n >= tty->sy)
    220 		format_add(ft, "popup_pane_top", "%u", tty->sy - h);
    221 	else
    222 		format_add(ft, "popup_pane_top", "%ld", n);
    223 	format_add(ft, "popup_pane_bottom", "%u", top + wp->yoff + wp->sy - oy);
    224 	format_add(ft, "popup_pane_left", "%u", wp->xoff - ox);
    225 	n = (long)wp->xoff + wp->sx - ox - w;
    226 	if (n < 0)
    227 		format_add(ft, "popup_pane_right", "%u", 0);
    228 	else
    229 		format_add(ft, "popup_pane_right", "%ld", n);
    230 
    231 	/* Expand horizontal position. */
    232 	xp = args_get(args, 'x');
    233 	if (xp == NULL || strcmp(xp, "C") == 0)
    234 		xp = "#{popup_centre_x}";
    235 	else if (strcmp(xp, "R") == 0)
    236 		xp = "#{popup_pane_right}";
    237 	else if (strcmp(xp, "P") == 0)
    238 		xp = "#{popup_pane_left}";
    239 	else if (strcmp(xp, "M") == 0)
    240 		xp = "#{popup_mouse_centre_x}";
    241 	else if (strcmp(xp, "W") == 0)
    242 		xp = "#{popup_window_status_line_x}";
    243 	p = format_expand(ft, xp);
    244 	n = strtol(p, NULL, 10);
    245 	if (n + w >= tty->sx)
    246 		n = tty->sx - w;
    247 	else if (n < 0)
    248 		n = 0;
    249 	*px = n;
    250 	log_debug("%s: -x: %s = %s = %u (-w %u)", __func__, xp, p, *px, w);
    251 	free(p);
    252 
    253 	/* Expand vertical position  */
    254 	yp = args_get(args, 'y');
    255 	if (yp == NULL || strcmp(yp, "C") == 0)
    256 		yp = "#{popup_centre_y}";
    257 	else if (strcmp(yp, "P") == 0)
    258 		yp = "#{popup_pane_bottom}";
    259 	else if (strcmp(yp, "M") == 0)
    260 		yp = "#{popup_mouse_top}";
    261 	else if (strcmp(yp, "S") == 0)
    262 		yp = "#{popup_status_line_y}";
    263 	else if (strcmp(yp, "W") == 0)
    264 		yp = "#{popup_window_status_line_y}";
    265 	p = format_expand(ft, yp);
    266 	n = strtol(p, NULL, 10);
    267 	if ((unsigned long)n < h)
    268 		n = 0;
    269 	else
    270 		n -= h;
    271 	if (n + h >= tty->sy)
    272 		n = tty->sy - h;
    273 	else if (n < 0)
    274 		n = 0;
    275 	*py = n;
    276 	log_debug("%s: -y: %s = %s = %u (-h %u)", __func__, yp, p, *py, h);
    277 	free(p);
    278 
    279 	format_free(ft);
    280 	return (1);
    281 }
    282 
    283 static enum cmd_retval
    284 cmd_display_menu_exec(struct cmd *self, struct cmdq_item *item)
    285 {
    286 	struct args		*args = cmd_get_args(self);
    287 	struct cmd_find_state	*target = cmdq_get_target(item);
    288 	struct key_event	*event = cmdq_get_event(item);
    289 	struct client		*tc = cmdq_get_target_client(item);
    290 	struct menu		*menu = NULL;
    291 	struct menu_item	 menu_item;
    292 	const char		*key, *name, *value;
    293 	const char		*style = args_get(args, 's');
    294 	const char		*border_style = args_get(args, 'S');
    295 	const char		*selected_style = args_get(args, 'H');
    296 	enum box_lines		 lines = BOX_LINES_DEFAULT;
    297 	char			*title, *cause;
    298 	int			 flags = 0, starting_choice = 0;
    299 	u_int			 px, py, i, count = args_count(args);
    300 	struct options		*o = target->s->curw->window->options;
    301 	struct options_entry	*oe;
    302 
    303 
    304 	if (tc->overlay_draw != NULL)
    305 		return (CMD_RETURN_NORMAL);
    306 
    307 	if (args_has(args, 'C')) {
    308 		if (strcmp(args_get(args, 'C'), "-") == 0)
    309 			starting_choice = -1;
    310 		else {
    311 			starting_choice = args_strtonum(args, 'C', 0, UINT_MAX,
    312 			    &cause);
    313 			if (cause != NULL) {
    314 				cmdq_error(item, "starting choice %s", cause);
    315 				free(cause);
    316 				return (CMD_RETURN_ERROR);
    317 			}
    318 		}
    319 	}
    320 
    321 	if (args_has(args, 'T'))
    322 		title = format_single_from_target(item, args_get(args, 'T'));
    323 	else
    324 		title = xstrdup("");
    325 	menu = menu_create(title);
    326 	free(title);
    327 
    328 	for (i = 0; i != count; /* nothing */) {
    329 		name = args_string(args, i++);
    330 		if (*name == '\0') {
    331 			menu_add_item(menu, NULL, item, tc, target);
    332 			continue;
    333 		}
    334 
    335 		if (count - i < 2) {
    336 			cmdq_error(item, "not enough arguments");
    337 			menu_free(menu);
    338 			return (CMD_RETURN_ERROR);
    339 		}
    340 		key = args_string(args, i++);
    341 
    342 		menu_item.name = name;
    343 		menu_item.key = key_string_lookup_string(key);
    344 		menu_item.command = args_string(args, i++);
    345 
    346 		menu_add_item(menu, &menu_item, item, tc, target);
    347 	}
    348 	if (menu == NULL) {
    349 		cmdq_error(item, "invalid menu arguments");
    350 		return (CMD_RETURN_ERROR);
    351 	}
    352 	if (menu->count == 0) {
    353 		menu_free(menu);
    354 		return (CMD_RETURN_NORMAL);
    355 	}
    356 	if (!cmd_display_menu_get_pos(tc, item, args, &px, &py, menu->width + 4,
    357 	    menu->count + 2)) {
    358 		menu_free(menu);
    359 		return (CMD_RETURN_NORMAL);
    360 	}
    361 
    362 	value = args_get(args, 'b');
    363 	if (value != NULL) {
    364 		oe = options_get(o, "menu-border-lines");
    365 		lines = options_find_choice(options_table_entry(oe), value,
    366 		    &cause);
    367 		if (lines == -1) {
    368 			cmdq_error(item, "menu-border-lines %s", cause);
    369 			free(cause);
    370 			return (CMD_RETURN_ERROR);
    371 		}
    372 	}
    373 
    374 	if (args_has(args, 'O'))
    375 		flags |= MENU_STAYOPEN;
    376 	if (!event->m.valid && !args_has(args, 'M'))
    377 		flags |= MENU_NOMOUSE;
    378 	if (menu_display(menu, flags, starting_choice, item, px, py, tc, lines,
    379 	    style, selected_style, border_style, target, NULL, NULL) != 0)
    380 		return (CMD_RETURN_NORMAL);
    381 	return (CMD_RETURN_WAIT);
    382 }
    383 
    384 static enum cmd_retval
    385 cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
    386 {
    387 	struct args		*args = cmd_get_args(self);
    388 	struct cmd_find_state	*target = cmdq_get_target(item);
    389 	struct session		*s = target->s;
    390 	struct client		*tc = cmdq_get_target_client(item);
    391 	struct tty		*tty = &tc->tty;
    392 	const char		*value, *shell, *shellcmd = NULL;
    393 	const char		*style = args_get(args, 's');
    394 	const char		*border_style = args_get(args, 'S');
    395 	char			*cwd = NULL, *cause = NULL, **argv = NULL;
    396 	char			*title;
    397 	int			 modify = popup_present(tc);
    398 	int			 flags = -1, argc = 0;
    399 	enum box_lines		 lines = BOX_LINES_DEFAULT;
    400 	u_int			 px, py, w = 0, h = 0, count = args_count(args);
    401 	struct args_value	*av;
    402 	struct environ		*env = NULL;
    403 	struct options		*o = s->curw->window->options;
    404 	struct options_entry	*oe;
    405 
    406 	if (args_has(args, 'C')) {
    407 		server_client_clear_overlay(tc);
    408 		return (CMD_RETURN_NORMAL);
    409 	}
    410 	if (!modify && tc->overlay_draw != NULL)
    411 		return (CMD_RETURN_NORMAL);
    412 
    413 	if (!modify) {
    414 		h = tty->sy / 2;
    415 		if (args_has(args, 'h')) {
    416 			h = args_percentage(args, 'h', 1, tty->sy, tty->sy,
    417 			    &cause);
    418 			if (cause != NULL) {
    419 				cmdq_error(item, "height %s", cause);
    420 				free(cause);
    421 				return (CMD_RETURN_ERROR);
    422 			}
    423 		}
    424 
    425 		w = tty->sx / 2;
    426 		if (args_has(args, 'w')) {
    427 			w = args_percentage(args, 'w', 1, tty->sx, tty->sx,
    428 			    &cause);
    429 			if (cause != NULL) {
    430 				cmdq_error(item, "width %s", cause);
    431 				free(cause);
    432 				return (CMD_RETURN_ERROR);
    433 			}
    434 		}
    435 
    436 		if (w > tty->sx)
    437 			w = tty->sx;
    438 		if (h > tty->sy)
    439 			h = tty->sy;
    440 		if (!cmd_display_menu_get_pos(tc, item, args, &px, &py, w, h))
    441 			return (CMD_RETURN_NORMAL);
    442 
    443 		value = args_get(args, 'd');
    444 		if (value != NULL)
    445 			cwd = format_single_from_target(item, value);
    446 		else
    447 			cwd = xstrdup(server_client_get_cwd(tc, s));
    448 		if (count == 0) {
    449 			shellcmd = options_get_string(s->options,
    450 			    "default-command");
    451 		} else if (count == 1)
    452 			shellcmd = args_string(args, 0);
    453 		if (count <= 1 && (shellcmd == NULL || *shellcmd == '\0')) {
    454 			shellcmd = NULL;
    455 			shell = options_get_string(s->options, "default-shell");
    456 			if (!checkshell(shell))
    457 				shell = _PATH_BSHELL;
    458 			cmd_append_argv(&argc, &argv, shell);
    459 		} else
    460 			args_to_vector(args, &argc, &argv);
    461 
    462 		if (args_has(args, 'e') >= 1) {
    463 			env = environ_create();
    464 			av = args_first_value(args, 'e');
    465 			while (av != NULL) {
    466 				environ_put(env, av->string, 0);
    467 				av = args_next_value(av);
    468 			}
    469 		}
    470 	}
    471 
    472 	value = args_get(args, 'b');
    473 	if (args_has(args, 'B'))
    474 		lines = BOX_LINES_NONE;
    475 	else if (value != NULL) {
    476 		oe = options_get(o, "popup-border-lines");
    477 		lines = options_find_choice(options_table_entry(oe), value,
    478 		    &cause);
    479 		if (cause != NULL) {
    480 			cmdq_error(item, "popup-border-lines %s", cause);
    481 			free(cause);
    482 			return (CMD_RETURN_ERROR);
    483 		}
    484 	}
    485 
    486 	if (args_has(args, 'T'))
    487 		title = format_single_from_target(item, args_get(args, 'T'));
    488 	else
    489 		title = xstrdup("");
    490 
    491 	if (args_has(args, 'N') || !modify)
    492 		flags = 0;
    493 	if (args_has(args, 'E') > 1) {
    494 		if (flags == -1)
    495 			flags = 0;
    496 		flags |= POPUP_CLOSEEXITZERO;
    497 	} else if (args_has(args, 'E')) {
    498 		if (flags == -1)
    499 			flags = 0;
    500 		flags |= POPUP_CLOSEEXIT;
    501 	}
    502 	if (args_has(args, 'k')) {
    503 		if (flags == -1)
    504 			flags = 0;
    505 		flags |= POPUP_CLOSEANYKEY;
    506 	}
    507 
    508 	if (modify) {
    509 		popup_modify(tc, title, style, border_style, lines, flags);
    510 		free(title);
    511 		return (CMD_RETURN_NORMAL);
    512 	}
    513 	if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc,
    514 	    argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0) {
    515 		cmd_free_argv(argc, argv);
    516 		if (env != NULL)
    517 			environ_free(env);
    518 		free(cwd);
    519 		free(title);
    520 		return (CMD_RETURN_NORMAL);
    521 	}
    522 	if (env != NULL)
    523 		environ_free(env);
    524 	free(cwd);
    525 	free(title);
    526 	cmd_free_argv(argc, argv);
    527 	return (CMD_RETURN_WAIT);
    528 }
    529