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