Annotation of src/usr.bin/tmux/cmd-display-menu.c, Revision 1.41
1.41 ! nicm 1: /* $OpenBSD: cmd-display-menu.c,v 1.40 2023/08/07 10:52:00 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.41 ! nicm 42: .args = { "b:c:C:t:s:S:OT:x:y:", 1, -1, cmd_display_menu_args_parse },
! 43: .usage = "[-O] [-b border-lines] [-c target-client] "
! 44: "[-C starting-choice] [-s style] [-S border-style] "
! 45: CMD_TARGET_PANE_USAGE "[-T title] [-x position] [-y position] "
! 46: "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');
! 296: enum box_lines lines = BOX_LINES_DEFAULT;
1.38 nicm 297: char *title, *cause;
298: int flags = 0, starting_choice = 0;
1.28 nicm 299: u_int px, py, i, count = args_count(args);
1.41 ! nicm 300: struct options *o = target->s->curw->window->options;
! 301: struct options_entry *oe;
! 302:
1.1 nicm 303:
1.18 nicm 304: if (tc->overlay_draw != NULL)
1.1 nicm 305: return (CMD_RETURN_NORMAL);
306:
1.41 ! nicm 307: if (args_has(args, 'C')) {
! 308: if (strcmp(args_get(args, 'C'), "-") == 0)
1.38 nicm 309: starting_choice = -1;
310: else {
1.41 ! nicm 311: starting_choice = args_strtonum(args, 'C', 0, UINT_MAX,
1.38 nicm 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:
1.1 nicm 321: if (args_has(args, 'T'))
1.18 nicm 322: title = format_single_from_target(item, args_get(args, 'T'));
1.1 nicm 323: else
324: title = xstrdup("");
1.4 nicm 325: menu = menu_create(title);
1.40 nicm 326: free(title);
1.4 nicm 327:
1.28 nicm 328: for (i = 0; i != count; /* nothing */) {
329: name = args_string(args, i++);
1.4 nicm 330: if (*name == '\0') {
1.18 nicm 331: menu_add_item(menu, NULL, item, tc, target);
1.4 nicm 332: continue;
333: }
334:
1.28 nicm 335: if (count - i < 2) {
1.4 nicm 336: cmdq_error(item, "not enough arguments");
337: menu_free(menu);
338: return (CMD_RETURN_ERROR);
339: }
1.28 nicm 340: key = args_string(args, i++);
1.4 nicm 341:
342: menu_item.name = name;
343: menu_item.key = key_string_lookup_string(key);
1.28 nicm 344: menu_item.command = args_string(args, i++);
1.4 nicm 345:
1.18 nicm 346: menu_add_item(menu, &menu_item, item, tc, target);
1.4 nicm 347: }
1.1 nicm 348: if (menu == NULL) {
1.4 nicm 349: cmdq_error(item, "invalid menu arguments");
1.1 nicm 350: return (CMD_RETURN_ERROR);
351: }
352: if (menu->count == 0) {
353: menu_free(menu);
354: return (CMD_RETURN_NORMAL);
355: }
1.21 nicm 356: if (!cmd_display_menu_get_position(tc, item, args, &px, &py,
357: menu->width + 4, menu->count + 2)) {
358: menu_free(menu);
359: return (CMD_RETURN_NORMAL);
360: }
1.1 nicm 361:
1.41 ! nicm 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:
1.20 nicm 374: if (args_has(args, 'O'))
375: flags |= MENU_STAYOPEN;
1.17 nicm 376: if (!event->m.valid)
1.1 nicm 377: flags |= MENU_NOMOUSE;
1.41 ! nicm 378: if (menu_display(menu, flags, starting_choice, item, px, py, tc, lines,
! 379: style, border_style, target, NULL, NULL) != 0)
1.7 nicm 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: {
1.13 nicm 387: struct args *args = cmd_get_args(self);
1.14 nicm 388: struct cmd_find_state *target = cmdq_get_target(item);
1.23 nicm 389: struct session *s = target->s;
1.18 nicm 390: struct client *tc = cmdq_get_target_client(item);
391: struct tty *tty = &tc->tty;
1.28 nicm 392: const char *value, *shell, *shellcmd = NULL;
1.37 nicm 393: const char *style = args_get(args, 's');
394: const char *border_style = args_get(args, 'S');
1.36 nicm 395: char *cwd, *cause = NULL, **argv = NULL, *title;
1.28 nicm 396: int flags = 0, argc = 0;
1.35 nicm 397: enum box_lines lines = BOX_LINES_DEFAULT;
1.28 nicm 398: u_int px, py, w, h, count = args_count(args);
1.34 nicm 399: struct args_value *av;
400: struct environ *env = NULL;
1.35 nicm 401: struct options *o = s->curw->window->options;
402: struct options_entry *oe;
1.7 nicm 403:
404: if (args_has(args, 'C')) {
1.18 nicm 405: server_client_clear_overlay(tc);
1.7 nicm 406: return (CMD_RETURN_NORMAL);
407: }
1.18 nicm 408: if (tc->overlay_draw != NULL)
1.7 nicm 409: return (CMD_RETURN_NORMAL);
410:
1.23 nicm 411: h = tty->sy / 2;
1.7 nicm 412: if (args_has(args, 'h')) {
1.18 nicm 413: h = args_percentage(args, 'h', 1, tty->sy, tty->sy, &cause);
1.7 nicm 414: if (cause != NULL) {
415: cmdq_error(item, "height %s", cause);
416: free(cause);
417: return (CMD_RETURN_ERROR);
418: }
419: }
420:
1.23 nicm 421: w = tty->sx / 2;
1.7 nicm 422: if (args_has(args, 'w')) {
1.18 nicm 423: w = args_percentage(args, 'w', 1, tty->sx, tty->sx, &cause);
1.7 nicm 424: if (cause != NULL) {
425: cmdq_error(item, "width %s", cause);
426: free(cause);
427: return (CMD_RETURN_ERROR);
428: }
429: }
430:
1.31 nicm 431: if (w > tty->sx)
432: w = tty->sx;
433: if (h > tty->sy)
434: h = tty->sy;
1.21 nicm 435: if (!cmd_display_menu_get_position(tc, item, args, &px, &py, w, h))
436: return (CMD_RETURN_NORMAL);
1.7 nicm 437:
1.35 nicm 438: value = args_get(args, 'b');
439: if (args_has(args, 'B'))
440: lines = BOX_LINES_NONE;
441: else if (value != NULL) {
442: oe = options_get(o, "popup-border-lines");
443: lines = options_find_choice(options_table_entry(oe), value,
444: &cause);
445: if (cause != NULL) {
446: cmdq_error(item, "popup-border-lines %s", cause);
447: free(cause);
448: return (CMD_RETURN_ERROR);
449: }
450: }
451:
1.7 nicm 452: value = args_get(args, 'd');
453: if (value != NULL)
1.18 nicm 454: cwd = format_single_from_target(item, value);
1.7 nicm 455: else
1.23 nicm 456: cwd = xstrdup(server_client_get_cwd(tc, s));
1.28 nicm 457: if (count == 0)
1.23 nicm 458: shellcmd = options_get_string(s->options, "default-command");
1.28 nicm 459: else if (count == 1)
460: shellcmd = args_string(args, 0);
461: if (count <= 1 && (shellcmd == NULL || *shellcmd == '\0')) {
1.23 nicm 462: shellcmd = NULL;
1.28 nicm 463: shell = options_get_string(s->options, "default-shell");
464: if (!checkshell(shell))
465: shell = _PATH_BSHELL;
466: cmd_append_argv(&argc, &argv, shell);
467: } else
1.33 nicm 468: args_to_vector(args, &argc, &argv);
1.7 nicm 469:
1.34 nicm 470: if (args_has(args, 'e') >= 1) {
471: env = environ_create();
472: av = args_first_value(args, 'e');
473: while (av != NULL) {
474: environ_put(env, av->string, 0);
475: av = args_next_value(av);
476: }
477: }
478:
1.36 nicm 479: if (args_has(args, 'T'))
480: title = format_single_from_target(item, args_get(args, 'T'));
481: else
482: title = xstrdup("");
1.9 nicm 483: if (args_has(args, 'E') > 1)
484: flags |= POPUP_CLOSEEXITZERO;
485: else if (args_has(args, 'E'))
1.7 nicm 486: flags |= POPUP_CLOSEEXIT;
1.35 nicm 487: if (popup_display(flags, lines, item, px, py, w, h, env, shellcmd, argc,
1.37 nicm 488: argv, cwd, title, tc, s, style, border_style, NULL, NULL) != 0) {
1.28 nicm 489: cmd_free_argv(argc, argv);
1.34 nicm 490: if (env != NULL)
491: environ_free(env);
1.39 nicm 492: free(cwd);
1.36 nicm 493: free(title);
1.1 nicm 494: return (CMD_RETURN_NORMAL);
1.28 nicm 495: }
1.34 nicm 496: if (env != NULL)
497: environ_free(env);
1.39 nicm 498: free(cwd);
1.36 nicm 499: free(title);
1.28 nicm 500: cmd_free_argv(argc, argv);
1.1 nicm 501: return (CMD_RETURN_WAIT);
502: }