xref: /openbsd-src/usr.bin/tmux/cmd.c (revision d59bb9942320b767f2a19aaa7690c8c6e30b724c)
1 /* $OpenBSD: cmd.c,v 1.136 2017/01/24 19:59:19 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2007 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 #include <sys/time.h>
21 
22 #include <fnmatch.h>
23 #include <paths.h>
24 #include <pwd.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 #include "tmux.h"
30 
31 extern const struct cmd_entry cmd_attach_session_entry;
32 extern const struct cmd_entry cmd_bind_key_entry;
33 extern const struct cmd_entry cmd_break_pane_entry;
34 extern const struct cmd_entry cmd_capture_pane_entry;
35 extern const struct cmd_entry cmd_choose_buffer_entry;
36 extern const struct cmd_entry cmd_choose_client_entry;
37 extern const struct cmd_entry cmd_choose_session_entry;
38 extern const struct cmd_entry cmd_choose_tree_entry;
39 extern const struct cmd_entry cmd_choose_window_entry;
40 extern const struct cmd_entry cmd_clear_history_entry;
41 extern const struct cmd_entry cmd_clock_mode_entry;
42 extern const struct cmd_entry cmd_command_prompt_entry;
43 extern const struct cmd_entry cmd_confirm_before_entry;
44 extern const struct cmd_entry cmd_copy_mode_entry;
45 extern const struct cmd_entry cmd_delete_buffer_entry;
46 extern const struct cmd_entry cmd_detach_client_entry;
47 extern const struct cmd_entry cmd_display_message_entry;
48 extern const struct cmd_entry cmd_display_panes_entry;
49 extern const struct cmd_entry cmd_down_pane_entry;
50 extern const struct cmd_entry cmd_find_window_entry;
51 extern const struct cmd_entry cmd_has_session_entry;
52 extern const struct cmd_entry cmd_if_shell_entry;
53 extern const struct cmd_entry cmd_join_pane_entry;
54 extern const struct cmd_entry cmd_kill_pane_entry;
55 extern const struct cmd_entry cmd_kill_server_entry;
56 extern const struct cmd_entry cmd_kill_session_entry;
57 extern const struct cmd_entry cmd_kill_window_entry;
58 extern const struct cmd_entry cmd_last_pane_entry;
59 extern const struct cmd_entry cmd_last_window_entry;
60 extern const struct cmd_entry cmd_link_window_entry;
61 extern const struct cmd_entry cmd_list_buffers_entry;
62 extern const struct cmd_entry cmd_list_clients_entry;
63 extern const struct cmd_entry cmd_list_commands_entry;
64 extern const struct cmd_entry cmd_list_keys_entry;
65 extern const struct cmd_entry cmd_list_panes_entry;
66 extern const struct cmd_entry cmd_list_sessions_entry;
67 extern const struct cmd_entry cmd_list_windows_entry;
68 extern const struct cmd_entry cmd_load_buffer_entry;
69 extern const struct cmd_entry cmd_lock_client_entry;
70 extern const struct cmd_entry cmd_lock_server_entry;
71 extern const struct cmd_entry cmd_lock_session_entry;
72 extern const struct cmd_entry cmd_move_pane_entry;
73 extern const struct cmd_entry cmd_move_window_entry;
74 extern const struct cmd_entry cmd_new_session_entry;
75 extern const struct cmd_entry cmd_new_window_entry;
76 extern const struct cmd_entry cmd_next_layout_entry;
77 extern const struct cmd_entry cmd_next_window_entry;
78 extern const struct cmd_entry cmd_paste_buffer_entry;
79 extern const struct cmd_entry cmd_pipe_pane_entry;
80 extern const struct cmd_entry cmd_previous_layout_entry;
81 extern const struct cmd_entry cmd_previous_window_entry;
82 extern const struct cmd_entry cmd_refresh_client_entry;
83 extern const struct cmd_entry cmd_rename_session_entry;
84 extern const struct cmd_entry cmd_rename_window_entry;
85 extern const struct cmd_entry cmd_resize_pane_entry;
86 extern const struct cmd_entry cmd_respawn_pane_entry;
87 extern const struct cmd_entry cmd_respawn_window_entry;
88 extern const struct cmd_entry cmd_rotate_window_entry;
89 extern const struct cmd_entry cmd_run_shell_entry;
90 extern const struct cmd_entry cmd_save_buffer_entry;
91 extern const struct cmd_entry cmd_select_layout_entry;
92 extern const struct cmd_entry cmd_select_pane_entry;
93 extern const struct cmd_entry cmd_select_window_entry;
94 extern const struct cmd_entry cmd_send_keys_entry;
95 extern const struct cmd_entry cmd_send_prefix_entry;
96 extern const struct cmd_entry cmd_set_buffer_entry;
97 extern const struct cmd_entry cmd_set_environment_entry;
98 extern const struct cmd_entry cmd_set_hook_entry;
99 extern const struct cmd_entry cmd_set_option_entry;
100 extern const struct cmd_entry cmd_set_window_option_entry;
101 extern const struct cmd_entry cmd_show_buffer_entry;
102 extern const struct cmd_entry cmd_show_environment_entry;
103 extern const struct cmd_entry cmd_show_hooks_entry;
104 extern const struct cmd_entry cmd_show_messages_entry;
105 extern const struct cmd_entry cmd_show_options_entry;
106 extern const struct cmd_entry cmd_show_window_options_entry;
107 extern const struct cmd_entry cmd_source_file_entry;
108 extern const struct cmd_entry cmd_split_window_entry;
109 extern const struct cmd_entry cmd_start_server_entry;
110 extern const struct cmd_entry cmd_suspend_client_entry;
111 extern const struct cmd_entry cmd_swap_pane_entry;
112 extern const struct cmd_entry cmd_swap_window_entry;
113 extern const struct cmd_entry cmd_switch_client_entry;
114 extern const struct cmd_entry cmd_unbind_key_entry;
115 extern const struct cmd_entry cmd_unlink_window_entry;
116 extern const struct cmd_entry cmd_up_pane_entry;
117 extern const struct cmd_entry cmd_wait_for_entry;
118 
119 const struct cmd_entry *cmd_table[] = {
120 	&cmd_attach_session_entry,
121 	&cmd_bind_key_entry,
122 	&cmd_break_pane_entry,
123 	&cmd_capture_pane_entry,
124 	&cmd_choose_buffer_entry,
125 	&cmd_choose_client_entry,
126 	&cmd_choose_session_entry,
127 	&cmd_choose_tree_entry,
128 	&cmd_choose_window_entry,
129 	&cmd_clear_history_entry,
130 	&cmd_clock_mode_entry,
131 	&cmd_command_prompt_entry,
132 	&cmd_confirm_before_entry,
133 	&cmd_copy_mode_entry,
134 	&cmd_delete_buffer_entry,
135 	&cmd_detach_client_entry,
136 	&cmd_display_message_entry,
137 	&cmd_display_panes_entry,
138 	&cmd_find_window_entry,
139 	&cmd_has_session_entry,
140 	&cmd_if_shell_entry,
141 	&cmd_join_pane_entry,
142 	&cmd_kill_pane_entry,
143 	&cmd_kill_server_entry,
144 	&cmd_kill_session_entry,
145 	&cmd_kill_window_entry,
146 	&cmd_last_pane_entry,
147 	&cmd_last_window_entry,
148 	&cmd_link_window_entry,
149 	&cmd_list_buffers_entry,
150 	&cmd_list_clients_entry,
151 	&cmd_list_commands_entry,
152 	&cmd_list_keys_entry,
153 	&cmd_list_panes_entry,
154 	&cmd_list_sessions_entry,
155 	&cmd_list_windows_entry,
156 	&cmd_load_buffer_entry,
157 	&cmd_lock_client_entry,
158 	&cmd_lock_server_entry,
159 	&cmd_lock_session_entry,
160 	&cmd_move_pane_entry,
161 	&cmd_move_window_entry,
162 	&cmd_new_session_entry,
163 	&cmd_new_window_entry,
164 	&cmd_next_layout_entry,
165 	&cmd_next_window_entry,
166 	&cmd_paste_buffer_entry,
167 	&cmd_pipe_pane_entry,
168 	&cmd_previous_layout_entry,
169 	&cmd_previous_window_entry,
170 	&cmd_refresh_client_entry,
171 	&cmd_rename_session_entry,
172 	&cmd_rename_window_entry,
173 	&cmd_resize_pane_entry,
174 	&cmd_respawn_pane_entry,
175 	&cmd_respawn_window_entry,
176 	&cmd_rotate_window_entry,
177 	&cmd_run_shell_entry,
178 	&cmd_save_buffer_entry,
179 	&cmd_select_layout_entry,
180 	&cmd_select_pane_entry,
181 	&cmd_select_window_entry,
182 	&cmd_send_keys_entry,
183 	&cmd_send_prefix_entry,
184 	&cmd_set_buffer_entry,
185 	&cmd_set_environment_entry,
186 	&cmd_set_hook_entry,
187 	&cmd_set_option_entry,
188 	&cmd_set_window_option_entry,
189 	&cmd_show_buffer_entry,
190 	&cmd_show_environment_entry,
191 	&cmd_show_hooks_entry,
192 	&cmd_show_messages_entry,
193 	&cmd_show_options_entry,
194 	&cmd_show_window_options_entry,
195 	&cmd_source_file_entry,
196 	&cmd_split_window_entry,
197 	&cmd_start_server_entry,
198 	&cmd_suspend_client_entry,
199 	&cmd_swap_pane_entry,
200 	&cmd_swap_window_entry,
201 	&cmd_switch_client_entry,
202 	&cmd_unbind_key_entry,
203 	&cmd_unlink_window_entry,
204 	&cmd_wait_for_entry,
205 	NULL
206 };
207 
208 int
209 cmd_pack_argv(int argc, char **argv, char *buf, size_t len)
210 {
211 	size_t	arglen;
212 	int	i;
213 
214 	if (argc == 0)
215 		return (0);
216 
217 	*buf = '\0';
218 	for (i = 0; i < argc; i++) {
219 		if (strlcpy(buf, argv[i], len) >= len)
220 			return (-1);
221 		arglen = strlen(argv[i]) + 1;
222 		buf += arglen;
223 		len -= arglen;
224 	}
225 
226 	return (0);
227 }
228 
229 int
230 cmd_unpack_argv(char *buf, size_t len, int argc, char ***argv)
231 {
232 	int	i;
233 	size_t	arglen;
234 
235 	if (argc == 0)
236 		return (0);
237 	*argv = xcalloc(argc, sizeof **argv);
238 
239 	buf[len - 1] = '\0';
240 	for (i = 0; i < argc; i++) {
241 		if (len == 0) {
242 			cmd_free_argv(argc, *argv);
243 			return (-1);
244 		}
245 
246 		arglen = strlen(buf) + 1;
247 		(*argv)[i] = xstrdup(buf);
248 		buf += arglen;
249 		len -= arglen;
250 	}
251 
252 	return (0);
253 }
254 
255 char **
256 cmd_copy_argv(int argc, char **argv)
257 {
258 	char	**new_argv;
259 	int	  i;
260 
261 	if (argc == 0)
262 		return (NULL);
263 	new_argv = xcalloc(argc + 1, sizeof *new_argv);
264 	for (i = 0; i < argc; i++) {
265 		if (argv[i] != NULL)
266 			new_argv[i] = xstrdup(argv[i]);
267 	}
268 	return (new_argv);
269 }
270 
271 void
272 cmd_free_argv(int argc, char **argv)
273 {
274 	int	i;
275 
276 	if (argc == 0)
277 		return;
278 	for (i = 0; i < argc; i++)
279 		free(argv[i]);
280 	free(argv);
281 }
282 
283 char *
284 cmd_stringify_argv(int argc, char **argv)
285 {
286 	char	*buf;
287 	int	 i;
288 	size_t	 len;
289 
290 	if (argc == 0)
291 		return (xstrdup(""));
292 
293 	len = 0;
294 	buf = NULL;
295 
296 	for (i = 0; i < argc; i++) {
297 		len += strlen(argv[i]) + 1;
298 		buf = xrealloc(buf, len);
299 
300 		if (i == 0)
301 			*buf = '\0';
302 		else
303 			strlcat(buf, " ", len);
304 		strlcat(buf, argv[i], len);
305 	}
306 	return (buf);
307 }
308 
309 static int
310 cmd_try_alias(int *argc, char ***argv)
311 {
312 	struct options_entry	 *o;
313 	int			  old_argc = *argc, new_argc;
314 	char			**old_argv = *argv, **new_argv;
315 	u_int			  size, idx;
316 	int			  i;
317 	size_t			  wanted;
318 	const char		 *s, *cp = NULL;
319 
320 	o = options_get_only(global_options, "command-alias");
321 	if (o == NULL || options_array_size(o, &size) == -1 || size == 0)
322 		return (-1);
323 
324 	wanted = strlen(old_argv[0]);
325 	for (idx = 0; idx < size; idx++) {
326 		s = options_array_get(o, idx);
327 		if (s == NULL)
328 			continue;
329 
330 		cp = strchr(s, '=');
331 		if (cp == NULL || (size_t)(cp - s) != wanted)
332 			continue;
333 		if (strncmp(old_argv[0], s, wanted) == 0)
334 			break;
335 	}
336 	if (idx == size)
337 		return (-1);
338 
339 	if (cmd_string_split(cp + 1, &new_argc, &new_argv) != 0)
340 		return (-1);
341 
342 	*argc = new_argc + old_argc - 1;
343 	*argv = xcalloc((*argc) + 1, sizeof **argv);
344 
345 	for (i = 0; i < new_argc; i++)
346 		(*argv)[i] = xstrdup(new_argv[i]);
347 	for (i = 1; i < old_argc; i++)
348 		(*argv)[new_argc + i - 1] = xstrdup(old_argv[i]);
349 
350 	log_debug("alias: %s=%s", old_argv[0], cp + 1);
351 	for (i = 0; i < *argc; i++)
352 		log_debug("alias: argv[%d] = %s", i, (*argv)[i]);
353 
354 	cmd_free_argv(new_argc, new_argv);
355 	return (0);
356 }
357 
358 struct cmd *
359 cmd_parse(int argc, char **argv, const char *file, u_int line, char **cause)
360 {
361 	const char		*name;
362 	const struct cmd_entry **entryp, *entry;
363 	struct cmd		*cmd;
364 	struct args		*args;
365 	char			 s[BUFSIZ];
366 	int			 ambiguous, allocated = 0;
367 
368 	*cause = NULL;
369 	if (argc == 0) {
370 		xasprintf(cause, "no command");
371 		return (NULL);
372 	}
373 	name = argv[0];
374 
375 retry:
376 	ambiguous = 0;
377 	entry = NULL;
378 	for (entryp = cmd_table; *entryp != NULL; entryp++) {
379 		if ((*entryp)->alias != NULL &&
380 		    strcmp((*entryp)->alias, argv[0]) == 0) {
381 			ambiguous = 0;
382 			entry = *entryp;
383 			break;
384 		}
385 
386 		if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0)
387 			continue;
388 		if (entry != NULL)
389 			ambiguous = 1;
390 		entry = *entryp;
391 
392 		/* Bail now if an exact match. */
393 		if (strcmp(entry->name, argv[0]) == 0)
394 			break;
395 	}
396 	if ((ambiguous || entry == NULL) &&
397 	    server_proc != NULL &&
398 	    !allocated &&
399 	    cmd_try_alias(&argc, &argv) == 0) {
400 		allocated = 1;
401 		goto retry;
402 	}
403 	if (ambiguous)
404 		goto ambiguous;
405 	if (entry == NULL) {
406 		xasprintf(cause, "unknown command: %s", name);
407 		return (NULL);
408 	}
409 
410 	args = args_parse(entry->args.template, argc, argv);
411 	if (args == NULL)
412 		goto usage;
413 	if (entry->args.lower != -1 && args->argc < entry->args.lower)
414 		goto usage;
415 	if (entry->args.upper != -1 && args->argc > entry->args.upper)
416 		goto usage;
417 
418 	cmd = xcalloc(1, sizeof *cmd);
419 	cmd->entry = entry;
420 	cmd->args = args;
421 
422 	if (file != NULL)
423 		cmd->file = xstrdup(file);
424 	cmd->line = line;
425 
426 	if (allocated)
427 		cmd_free_argv(argc, argv);
428 	return (cmd);
429 
430 ambiguous:
431 	*s = '\0';
432 	for (entryp = cmd_table; *entryp != NULL; entryp++) {
433 		if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0)
434 			continue;
435 		if (strlcat(s, (*entryp)->name, sizeof s) >= sizeof s)
436 			break;
437 		if (strlcat(s, ", ", sizeof s) >= sizeof s)
438 			break;
439 	}
440 	s[strlen(s) - 2] = '\0';
441 	xasprintf(cause, "ambiguous command: %s, could be: %s", name, s);
442 	return (NULL);
443 
444 usage:
445 	if (args != NULL)
446 		args_free(args);
447 	xasprintf(cause, "usage: %s %s", entry->name, entry->usage);
448 	return (NULL);
449 }
450 
451 static int
452 cmd_prepare_state_flag(char c, const char *target, enum cmd_entry_flag flag,
453     struct cmdq_item *item)
454 {
455 	int			 targetflags, error;
456 	struct cmd_find_state	*fs = NULL;
457 	struct cmd_find_state	 current;
458 
459 	if (flag == CMD_NONE ||
460 	    flag == CMD_CLIENT ||
461 	    flag == CMD_CLIENT_CANFAIL)
462 		return (0);
463 
464 	if (c == 't')
465 		fs = &item->state.tflag;
466 	else if (c == 's')
467 		fs = &item->state.sflag;
468 
469 	if (flag == CMD_SESSION_WITHPANE) {
470 		if (target != NULL && target[strcspn(target, ":.")] != '\0')
471 			flag = CMD_PANE;
472 		else
473 			flag = CMD_SESSION_PREFERUNATTACHED;
474 	}
475 
476 	targetflags = 0;
477 	switch (flag) {
478 	case CMD_SESSION:
479 	case CMD_SESSION_CANFAIL:
480 	case CMD_SESSION_PREFERUNATTACHED:
481 	case CMD_SESSION_WITHPANE:
482 		if (flag == CMD_SESSION_CANFAIL)
483 			targetflags |= CMD_FIND_QUIET;
484 		if (flag == CMD_SESSION_PREFERUNATTACHED)
485 			targetflags |= CMD_FIND_PREFER_UNATTACHED;
486 		break;
487 	case CMD_MOVEW_R:
488 		flag = CMD_WINDOW_INDEX;
489 		/* FALLTHROUGH */
490 	case CMD_WINDOW:
491 	case CMD_WINDOW_CANFAIL:
492 	case CMD_WINDOW_MARKED:
493 	case CMD_WINDOW_INDEX:
494 		if (flag == CMD_WINDOW_CANFAIL)
495 			targetflags |= CMD_FIND_QUIET;
496 		if (flag == CMD_WINDOW_MARKED)
497 			targetflags |= CMD_FIND_DEFAULT_MARKED;
498 		if (flag == CMD_WINDOW_INDEX)
499 			targetflags |= CMD_FIND_WINDOW_INDEX;
500 		break;
501 	case CMD_PANE:
502 	case CMD_PANE_CANFAIL:
503 	case CMD_PANE_MARKED:
504 		if (flag == CMD_PANE_CANFAIL)
505 			targetflags |= CMD_FIND_QUIET;
506 		if (flag == CMD_PANE_MARKED)
507 			targetflags |= CMD_FIND_DEFAULT_MARKED;
508 		break;
509 	default:
510 		fatalx("unknown %cflag %d", c, flag);
511 	}
512 	log_debug("%s: flag %c %d %#x", __func__, c, flag, targetflags);
513 
514 	error = cmd_find_current(&current, item, targetflags);
515 	if (error != 0) {
516 		if (~targetflags & CMD_FIND_QUIET)
517 			return (-1);
518 		cmd_find_clear_state(&current, NULL, 0);
519 	}
520 	if (!cmd_find_empty_state(&current) && !cmd_find_valid_state(&current))
521 		fatalx("invalid current state");
522 
523 	switch (flag) {
524 	case CMD_NONE:
525 	case CMD_CLIENT:
526 	case CMD_CLIENT_CANFAIL:
527 		return (0);
528 	case CMD_SESSION:
529 	case CMD_SESSION_CANFAIL:
530 	case CMD_SESSION_PREFERUNATTACHED:
531 	case CMD_SESSION_WITHPANE:
532 		error = cmd_find_target(fs, &current, item, target,
533 		    CMD_FIND_SESSION, targetflags);
534 		if (error != 0)
535 			goto error;
536 		break;
537 	case CMD_MOVEW_R:
538 		error = cmd_find_target(fs, &current, item, target,
539 		    CMD_FIND_SESSION, CMD_FIND_QUIET);
540 		if (error == 0)
541 			break;
542 		/* FALLTHROUGH */
543 	case CMD_WINDOW:
544 	case CMD_WINDOW_CANFAIL:
545 	case CMD_WINDOW_MARKED:
546 	case CMD_WINDOW_INDEX:
547 		error = cmd_find_target(fs, &current, item, target,
548 		    CMD_FIND_WINDOW, targetflags);
549 		if (error != 0)
550 			goto error;
551 		break;
552 	case CMD_PANE:
553 	case CMD_PANE_CANFAIL:
554 	case CMD_PANE_MARKED:
555 		error = cmd_find_target(fs, &current, item, target,
556 		    CMD_FIND_PANE, targetflags);
557 		if (error != 0)
558 			goto error;
559 		break;
560 	default:
561 		fatalx("unknown %cflag %d", c, flag);
562 	}
563 	return (0);
564 
565 error:
566 	if (~targetflags & CMD_FIND_QUIET)
567 		return (-1);
568 	cmd_find_clear_state(fs, NULL, 0);
569 	return (0);
570 }
571 
572 int
573 cmd_prepare_state(struct cmd *cmd, struct cmdq_item *item)
574 {
575 	const struct cmd_entry	*entry = cmd->entry;
576 	struct cmd_state	*state = &item->state;
577 	char			*tmp;
578 	enum cmd_entry_flag	 flag;
579 	const char		*s;
580 	int			 error;
581 
582 	tmp = cmd_print(cmd);
583 	log_debug("preparing state for %s (client %p)", tmp, item->client);
584 	free(tmp);
585 
586 	state->c = NULL;
587 	cmd_find_clear_state(&state->tflag, NULL, 0);
588 	cmd_find_clear_state(&state->sflag, NULL, 0);
589 
590 	flag = cmd->entry->cflag;
591 	if (flag == CMD_NONE) {
592 		flag = cmd->entry->tflag;
593 		if (flag == CMD_CLIENT || flag == CMD_CLIENT_CANFAIL)
594 			s = args_get(cmd->args, 't');
595 		else
596 			s = NULL;
597 	} else
598 		s = args_get(cmd->args, 'c');
599 	switch (flag) {
600 	case CMD_CLIENT:
601 		state->c = cmd_find_client(item, s, 0);
602 		if (state->c == NULL)
603 			return (-1);
604 		break;
605 	default:
606 		state->c = cmd_find_client(item, s, 1);
607 		break;
608 	}
609 	log_debug("using client %p", state->c);
610 
611 	s = args_get(cmd->args, 't');
612 	log_debug("preparing -t state: target %s", s == NULL ? "none" : s);
613 
614 	error = cmd_prepare_state_flag('t', s, entry->tflag, item);
615 	if (error != 0)
616 		return (error);
617 
618 	s = args_get(cmd->args, 's');
619 	log_debug("preparing -s state: target %s", s == NULL ? "none" : s);
620 
621 	error = cmd_prepare_state_flag('s', s, entry->sflag, item);
622 	if (error != 0)
623 		return (error);
624 
625 	if (!cmd_find_empty_state(&state->tflag) &&
626 	    !cmd_find_valid_state(&state->tflag))
627 		fatalx("invalid -t state");
628 	if (!cmd_find_empty_state(&state->sflag) &&
629 	    !cmd_find_valid_state(&state->sflag))
630 		fatalx("invalid -s state");
631 
632 	return (0);
633 }
634 
635 char *
636 cmd_print(struct cmd *cmd)
637 {
638 	char	*out, *s;
639 
640 	s = args_print(cmd->args);
641 	if (*s != '\0')
642 		xasprintf(&out, "%s %s", cmd->entry->name, s);
643 	else
644 		out = xstrdup(cmd->entry->name);
645 	free(s);
646 
647 	return (out);
648 }
649 
650 /* Adjust current mouse position for a pane. */
651 int
652 cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp,
653     u_int *yp, int last)
654 {
655 	u_int	x, y;
656 
657 	if (last) {
658 		x = m->lx;
659 		y = m->ly;
660 	} else {
661 		x = m->x;
662 		y = m->y;
663 	}
664 
665 	if (m->statusat == 0 && y > 0)
666 		y--;
667 	else if (m->statusat > 0 && y >= (u_int)m->statusat)
668 		y = m->statusat - 1;
669 
670 	if (x < wp->xoff || x >= wp->xoff + wp->sx)
671 		return (-1);
672 	if (y < wp->yoff || y >= wp->yoff + wp->sy)
673 		return (-1);
674 
675 	if (xp != NULL)
676 		*xp = x - wp->xoff;
677 	if (yp != NULL)
678 		*yp = y - wp->yoff;
679 	return (0);
680 }
681 
682 /* Get current mouse window if any. */
683 struct winlink *
684 cmd_mouse_window(struct mouse_event *m, struct session **sp)
685 {
686 	struct session	*s;
687 	struct window	*w;
688 
689 	if (!m->valid || m->s == -1 || m->w == -1)
690 		return (NULL);
691 	if ((s = session_find_by_id(m->s)) == NULL)
692 		return (NULL);
693 	if ((w = window_find_by_id(m->w)) == NULL)
694 		return (NULL);
695 
696 	if (sp != NULL)
697 		*sp = s;
698 	return (winlink_find_by_window(&s->windows, w));
699 }
700 
701 /* Get current mouse pane if any. */
702 struct window_pane *
703 cmd_mouse_pane(struct mouse_event *m, struct session **sp,
704     struct winlink **wlp)
705 {
706 	struct winlink		*wl;
707 	struct window_pane     	*wp;
708 
709 	if ((wl = cmd_mouse_window(m, sp)) == NULL)
710 		return (NULL);
711 	if ((wp = window_pane_find_by_id(m->wp)) == NULL)
712 		return (NULL);
713 	if (!window_has_pane(wl->window, wp))
714 		return (NULL);
715 
716 	if (wlp != NULL)
717 		*wlp = wl;
718 	return (wp);
719 }
720 
721 /* Replace the first %% or %idx in template by s. */
722 char *
723 cmd_template_replace(const char *template, const char *s, int idx)
724 {
725 	char		 ch, *buf;
726 	const char	*ptr, *cp, quote[] = "\"\\$";
727 	int		 replaced, quoted;
728 	size_t		 len;
729 
730 	if (strchr(template, '%') == NULL)
731 		return (xstrdup(template));
732 
733 	buf = xmalloc(1);
734 	*buf = '\0';
735 	len = 0;
736 	replaced = 0;
737 
738 	ptr = template;
739 	while (*ptr != '\0') {
740 		switch (ch = *ptr++) {
741 		case '%':
742 			if (*ptr < '1' || *ptr > '9' || *ptr - '0' != idx) {
743 				if (*ptr != '%' || replaced)
744 					break;
745 				replaced = 1;
746 			}
747 			ptr++;
748 
749 			quoted = (*ptr == '%');
750 			if (quoted)
751 				ptr++;
752 
753 			buf = xrealloc(buf, len + (strlen(s) * 3) + 1);
754 			for (cp = s; *cp != '\0'; cp++) {
755 				if (quoted && strchr(quote, *cp) != NULL)
756 					buf[len++] = '\\';
757 				if (quoted && *cp == ';') {
758 					buf[len++] = '\\';
759 					buf[len++] = '\\';
760 				}
761 				buf[len++] = *cp;
762 			}
763 			buf[len] = '\0';
764 			continue;
765 		}
766 		buf = xrealloc(buf, len + 2);
767 		buf[len++] = ch;
768 		buf[len] = '\0';
769 	}
770 
771 	return (buf);
772 }
773