1 | /* $OpenBSD$ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2013 Nicholas Marriott <[email protected]> |
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 <ctype.h> |
22 | #include <stdlib.h> |
23 | #include <string.h> |
24 | #include <time.h> |
25 | |
26 | #include "tmux.h" |
27 | |
28 | /* Global command queue. */ |
29 | static struct cmdq_list global_queue = TAILQ_HEAD_INITIALIZER(global_queue); |
30 | |
31 | /* Get command queue name. */ |
32 | static const char * |
33 | cmdq_name(struct client *c) |
34 | { |
35 | static char s[256]; |
36 | |
37 | if (c == NULL) |
38 | return ("<global>" ); |
39 | if (c->name != NULL) |
40 | xsnprintf(s, sizeof s, "<%s>" , c->name); |
41 | else |
42 | xsnprintf(s, sizeof s, "<%p>" , c); |
43 | return (s); |
44 | } |
45 | |
46 | /* Get command queue from client. */ |
47 | static struct cmdq_list * |
48 | cmdq_get(struct client *c) |
49 | { |
50 | if (c == NULL) |
51 | return (&global_queue); |
52 | return (&c->queue); |
53 | } |
54 | |
55 | /* Append an item. */ |
56 | struct cmdq_item * |
57 | cmdq_append(struct client *c, struct cmdq_item *item) |
58 | { |
59 | struct cmdq_list *queue = cmdq_get(c); |
60 | struct cmdq_item *next; |
61 | |
62 | do { |
63 | next = item->next; |
64 | item->next = NULL; |
65 | |
66 | if (c != NULL) |
67 | c->references++; |
68 | item->client = c; |
69 | |
70 | item->queue = queue; |
71 | TAILQ_INSERT_TAIL(queue, item, entry); |
72 | log_debug("%s %s: %s" , __func__, cmdq_name(c), item->name); |
73 | |
74 | item = next; |
75 | } while (item != NULL); |
76 | return (TAILQ_LAST(queue, cmdq_list)); |
77 | } |
78 | |
79 | /* Insert an item. */ |
80 | struct cmdq_item * |
81 | cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) |
82 | { |
83 | struct client *c = after->client; |
84 | struct cmdq_list *queue = after->queue; |
85 | struct cmdq_item *next; |
86 | |
87 | do { |
88 | next = item->next; |
89 | item->next = after->next; |
90 | after->next = item; |
91 | |
92 | if (c != NULL) |
93 | c->references++; |
94 | item->client = c; |
95 | |
96 | item->queue = queue; |
97 | TAILQ_INSERT_AFTER(queue, after, item, entry); |
98 | log_debug("%s %s: %s after %s" , __func__, cmdq_name(c), |
99 | item->name, after->name); |
100 | |
101 | after = item; |
102 | item = next; |
103 | } while (item != NULL); |
104 | return (after); |
105 | } |
106 | |
107 | /* Insert a hook. */ |
108 | void |
109 | cmdq_insert_hook(struct session *s, struct cmdq_item *item, |
110 | struct cmd_find_state *fs, const char *fmt, ...) |
111 | { |
112 | struct options *oo; |
113 | va_list ap; |
114 | char *name; |
115 | struct cmdq_item *new_item; |
116 | struct options_entry *o; |
117 | struct options_array_item *a; |
118 | struct cmd_list *cmdlist; |
119 | |
120 | if (item->flags & CMDQ_NOHOOKS) |
121 | return; |
122 | if (s == NULL) |
123 | oo = global_s_options; |
124 | else |
125 | oo = s->options; |
126 | |
127 | va_start(ap, fmt); |
128 | xvasprintf(&name, fmt, ap); |
129 | va_end(ap); |
130 | |
131 | o = options_get(oo, name); |
132 | if (o == NULL) { |
133 | free(name); |
134 | return; |
135 | } |
136 | log_debug("running hook %s (parent %p)" , name, item); |
137 | |
138 | a = options_array_first(o); |
139 | while (a != NULL) { |
140 | cmdlist = options_array_item_value(a)->cmdlist; |
141 | if (cmdlist == NULL) { |
142 | a = options_array_next(a); |
143 | continue; |
144 | } |
145 | |
146 | new_item = cmdq_get_command(cmdlist, fs, NULL, CMDQ_NOHOOKS); |
147 | cmdq_format(new_item, "hook" , "%s" , name); |
148 | if (item != NULL) |
149 | item = cmdq_insert_after(item, new_item); |
150 | else |
151 | item = cmdq_append(NULL, new_item); |
152 | |
153 | a = options_array_next(a); |
154 | } |
155 | |
156 | free(name); |
157 | } |
158 | |
159 | /* Continue processing command queue. */ |
160 | void |
161 | cmdq_continue(struct cmdq_item *item) |
162 | { |
163 | item->flags &= ~CMDQ_WAITING; |
164 | } |
165 | |
166 | /* Remove an item. */ |
167 | static void |
168 | cmdq_remove(struct cmdq_item *item) |
169 | { |
170 | if (item->shared != NULL && --item->shared->references == 0) { |
171 | if (item->shared->formats != NULL) |
172 | format_free(item->shared->formats); |
173 | free(item->shared); |
174 | } |
175 | |
176 | if (item->client != NULL) |
177 | server_client_unref(item->client); |
178 | |
179 | if (item->cmdlist != NULL) |
180 | cmd_list_free(item->cmdlist); |
181 | |
182 | TAILQ_REMOVE(item->queue, item, entry); |
183 | |
184 | free(item->name); |
185 | free(item); |
186 | } |
187 | |
188 | /* Remove all subsequent items that match this item's group. */ |
189 | static void |
190 | cmdq_remove_group(struct cmdq_item *item) |
191 | { |
192 | struct cmdq_item *this, *next; |
193 | |
194 | if (item->group == 0) |
195 | return; |
196 | this = TAILQ_NEXT(item, entry); |
197 | while (this != NULL) { |
198 | next = TAILQ_NEXT(this, entry); |
199 | if (this->group == item->group) |
200 | cmdq_remove(this); |
201 | this = next; |
202 | } |
203 | } |
204 | |
205 | /* Get a command for the command queue. */ |
206 | struct cmdq_item * |
207 | cmdq_get_command(struct cmd_list *cmdlist, struct cmd_find_state *current, |
208 | struct mouse_event *m, int flags) |
209 | { |
210 | struct cmdq_item *item, *first = NULL, *last = NULL; |
211 | struct cmd *cmd; |
212 | struct cmdq_shared *shared = NULL; |
213 | u_int group = 0; |
214 | |
215 | TAILQ_FOREACH(cmd, &cmdlist->list, qentry) { |
216 | if (cmd->group != group) { |
217 | shared = xcalloc(1, sizeof *shared); |
218 | if (current != NULL) |
219 | cmd_find_copy_state(&shared->current, current); |
220 | else |
221 | cmd_find_clear_state(&shared->current, 0); |
222 | if (m != NULL) |
223 | memcpy(&shared->mouse, m, sizeof shared->mouse); |
224 | group = cmd->group; |
225 | } |
226 | |
227 | item = xcalloc(1, sizeof *item); |
228 | xasprintf(&item->name, "[%s/%p]" , cmd->entry->name, item); |
229 | item->type = CMDQ_COMMAND; |
230 | |
231 | item->group = cmd->group; |
232 | item->flags = flags; |
233 | |
234 | item->shared = shared; |
235 | item->cmdlist = cmdlist; |
236 | item->cmd = cmd; |
237 | |
238 | log_debug("%s: %s group %u" , __func__, item->name, item->group); |
239 | |
240 | shared->references++; |
241 | cmdlist->references++; |
242 | |
243 | if (first == NULL) |
244 | first = item; |
245 | if (last != NULL) |
246 | last->next = item; |
247 | last = item; |
248 | } |
249 | return (first); |
250 | } |
251 | |
252 | /* Fill in flag for a command. */ |
253 | static enum cmd_retval |
254 | cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs, |
255 | const struct cmd_entry_flag *flag) |
256 | { |
257 | const char *value; |
258 | |
259 | if (flag->flag == 0) { |
260 | cmd_find_clear_state(fs, 0); |
261 | return (CMD_RETURN_NORMAL); |
262 | } |
263 | |
264 | value = args_get(item->cmd->args, flag->flag); |
265 | if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) { |
266 | cmd_find_clear_state(fs, 0); |
267 | return (CMD_RETURN_ERROR); |
268 | } |
269 | return (CMD_RETURN_NORMAL); |
270 | } |
271 | |
272 | /* Fire command on command queue. */ |
273 | static enum cmd_retval |
274 | cmdq_fire_command(struct cmdq_item *item) |
275 | { |
276 | struct client *c = item->client; |
277 | const char *name = cmdq_name(c); |
278 | struct cmdq_shared *shared = item->shared; |
279 | struct cmd *cmd = item->cmd; |
280 | const struct cmd_entry *entry = cmd->entry; |
281 | enum cmd_retval retval; |
282 | struct cmd_find_state *fsp, fs; |
283 | int flags; |
284 | char *tmp; |
285 | |
286 | if (log_get_level() > 1) { |
287 | tmp = cmd_print(cmd); |
288 | log_debug("%s %s: (%u) %s" , __func__, name, item->group, tmp); |
289 | free(tmp); |
290 | } |
291 | |
292 | flags = !!(shared->flags & CMDQ_SHARED_CONTROL); |
293 | cmdq_guard(item, "begin" , flags); |
294 | |
295 | if (item->client == NULL) |
296 | item->client = cmd_find_client(item, NULL, 1); |
297 | retval = cmdq_find_flag(item, &item->source, &entry->source); |
298 | if (retval == CMD_RETURN_ERROR) |
299 | goto out; |
300 | retval = cmdq_find_flag(item, &item->target, &entry->target); |
301 | if (retval == CMD_RETURN_ERROR) |
302 | goto out; |
303 | |
304 | retval = entry->exec(cmd, item); |
305 | if (retval == CMD_RETURN_ERROR) |
306 | goto out; |
307 | |
308 | if (entry->flags & CMD_AFTERHOOK) { |
309 | if (cmd_find_valid_state(&item->target)) |
310 | fsp = &item->target; |
311 | else if (cmd_find_valid_state(&item->shared->current)) |
312 | fsp = &item->shared->current; |
313 | else if (cmd_find_from_client(&fs, item->client, 0) == 0) |
314 | fsp = &fs; |
315 | else |
316 | goto out; |
317 | cmdq_insert_hook(fsp->s, item, fsp, "after-%s" , entry->name); |
318 | } |
319 | |
320 | out: |
321 | item->client = c; |
322 | if (retval == CMD_RETURN_ERROR) |
323 | cmdq_guard(item, "error" , flags); |
324 | else |
325 | cmdq_guard(item, "end" , flags); |
326 | return (retval); |
327 | } |
328 | |
329 | /* Get a callback for the command queue. */ |
330 | struct cmdq_item * |
331 | cmdq_get_callback1(const char *name, cmdq_cb cb, void *data) |
332 | { |
333 | struct cmdq_item *item; |
334 | |
335 | item = xcalloc(1, sizeof *item); |
336 | xasprintf(&item->name, "[%s/%p]" , name, item); |
337 | item->type = CMDQ_CALLBACK; |
338 | |
339 | item->group = 0; |
340 | item->flags = 0; |
341 | |
342 | item->cb = cb; |
343 | item->data = data; |
344 | |
345 | return (item); |
346 | } |
347 | |
348 | /* Generic error callback. */ |
349 | static enum cmd_retval |
350 | cmdq_error_callback(struct cmdq_item *item, void *data) |
351 | { |
352 | char *error = data; |
353 | |
354 | cmdq_error(item, "%s" , error); |
355 | free(error); |
356 | |
357 | return (CMD_RETURN_NORMAL); |
358 | } |
359 | |
360 | /* Get an error callback for the command queue. */ |
361 | struct cmdq_item * |
362 | cmdq_get_error(const char *error) |
363 | { |
364 | return (cmdq_get_callback(cmdq_error_callback, xstrdup(error))); |
365 | } |
366 | |
367 | /* Fire callback on callback queue. */ |
368 | static enum cmd_retval |
369 | cmdq_fire_callback(struct cmdq_item *item) |
370 | { |
371 | return (item->cb(item, item->data)); |
372 | } |
373 | |
374 | /* Add a format to command queue. */ |
375 | void |
376 | cmdq_format(struct cmdq_item *item, const char *key, const char *fmt, ...) |
377 | { |
378 | struct cmdq_shared *shared = item->shared; |
379 | va_list ap; |
380 | char *value; |
381 | |
382 | va_start(ap, fmt); |
383 | xvasprintf(&value, fmt, ap); |
384 | va_end(ap); |
385 | |
386 | if (shared->formats == NULL) |
387 | shared->formats = format_create(NULL, NULL, FORMAT_NONE, 0); |
388 | format_add(shared->formats, key, "%s" , value); |
389 | |
390 | free(value); |
391 | } |
392 | |
393 | /* Process next item on command queue. */ |
394 | u_int |
395 | cmdq_next(struct client *c) |
396 | { |
397 | struct cmdq_list *queue = cmdq_get(c); |
398 | const char *name = cmdq_name(c); |
399 | struct cmdq_item *item; |
400 | enum cmd_retval retval; |
401 | u_int items = 0; |
402 | static u_int number; |
403 | |
404 | if (TAILQ_EMPTY(queue)) { |
405 | log_debug("%s %s: empty" , __func__, name); |
406 | return (0); |
407 | } |
408 | if (TAILQ_FIRST(queue)->flags & CMDQ_WAITING) { |
409 | log_debug("%s %s: waiting" , __func__, name); |
410 | return (0); |
411 | } |
412 | |
413 | log_debug("%s %s: enter" , __func__, name); |
414 | for (;;) { |
415 | item = TAILQ_FIRST(queue); |
416 | if (item == NULL) |
417 | break; |
418 | log_debug("%s %s: %s (%d), flags %x" , __func__, name, |
419 | item->name, item->type, item->flags); |
420 | |
421 | /* |
422 | * Any item with the waiting flag set waits until an external |
423 | * event clears the flag (for example, a job - look at |
424 | * run-shell). |
425 | */ |
426 | if (item->flags & CMDQ_WAITING) |
427 | goto waiting; |
428 | |
429 | /* |
430 | * Items are only fired once, once the fired flag is set, a |
431 | * waiting flag can only be cleared by an external event. |
432 | */ |
433 | if (~item->flags & CMDQ_FIRED) { |
434 | item->time = time(NULL); |
435 | item->number = ++number; |
436 | |
437 | switch (item->type) { |
438 | case CMDQ_COMMAND: |
439 | retval = cmdq_fire_command(item); |
440 | |
441 | /* |
442 | * If a command returns an error, remove any |
443 | * subsequent commands in the same group. |
444 | */ |
445 | if (retval == CMD_RETURN_ERROR) |
446 | cmdq_remove_group(item); |
447 | break; |
448 | case CMDQ_CALLBACK: |
449 | retval = cmdq_fire_callback(item); |
450 | break; |
451 | default: |
452 | retval = CMD_RETURN_ERROR; |
453 | break; |
454 | } |
455 | item->flags |= CMDQ_FIRED; |
456 | |
457 | if (retval == CMD_RETURN_WAIT) { |
458 | item->flags |= CMDQ_WAITING; |
459 | goto waiting; |
460 | } |
461 | items++; |
462 | } |
463 | cmdq_remove(item); |
464 | } |
465 | |
466 | log_debug("%s %s: exit (empty)" , __func__, name); |
467 | return (items); |
468 | |
469 | waiting: |
470 | log_debug("%s %s: exit (wait)" , __func__, name); |
471 | return (items); |
472 | } |
473 | |
474 | /* Print a guard line. */ |
475 | void |
476 | cmdq_guard(struct cmdq_item *item, const char *guard, int flags) |
477 | { |
478 | struct client *c = item->client; |
479 | long t = item->time; |
480 | u_int number = item->number; |
481 | |
482 | if (c != NULL && (c->flags & CLIENT_CONTROL)) |
483 | file_print(c, "%%%s %ld %u %d\n" , guard, t, number, flags); |
484 | } |
485 | |
486 | /* Show message from command. */ |
487 | void |
488 | cmdq_print(struct cmdq_item *item, const char *fmt, ...) |
489 | { |
490 | struct client *c = item->client; |
491 | struct window_pane *wp; |
492 | struct window_mode_entry *wme; |
493 | va_list ap; |
494 | char *tmp, *msg; |
495 | |
496 | va_start(ap, fmt); |
497 | xvasprintf(&msg, fmt, ap); |
498 | va_end(ap); |
499 | |
500 | log_debug("%s: %s" , __func__, msg); |
501 | |
502 | if (c == NULL) |
503 | /* nothing */; |
504 | else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { |
505 | if (~c->flags & CLIENT_UTF8) { |
506 | tmp = msg; |
507 | msg = utf8_sanitize(tmp); |
508 | free(tmp); |
509 | } |
510 | file_print(c, "%s\n" , msg); |
511 | } else { |
512 | wp = c->session->curw->window->active; |
513 | wme = TAILQ_FIRST(&wp->modes); |
514 | if (wme == NULL || wme->mode != &window_view_mode) |
515 | window_pane_set_mode(wp, &window_view_mode, NULL, NULL); |
516 | window_copy_add(wp, "%s" , msg); |
517 | } |
518 | |
519 | free(msg); |
520 | } |
521 | |
522 | /* Show error from command. */ |
523 | void |
524 | cmdq_error(struct cmdq_item *item, const char *fmt, ...) |
525 | { |
526 | struct client *c = item->client; |
527 | struct cmd *cmd = item->cmd; |
528 | va_list ap; |
529 | char *msg; |
530 | char *tmp; |
531 | |
532 | va_start(ap, fmt); |
533 | xvasprintf(&msg, fmt, ap); |
534 | va_end(ap); |
535 | |
536 | log_debug("%s: %s" , __func__, msg); |
537 | |
538 | if (c == NULL) |
539 | cfg_add_cause("%s:%u: %s" , cmd->file, cmd->line, msg); |
540 | else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { |
541 | if (~c->flags & CLIENT_UTF8) { |
542 | tmp = msg; |
543 | msg = utf8_sanitize(tmp); |
544 | free(tmp); |
545 | } |
546 | if (c->flags & CLIENT_CONTROL) |
547 | file_print(c, "%s\n" , msg); |
548 | else |
549 | file_error(c, "%s\n" , msg); |
550 | c->retval = 1; |
551 | } else { |
552 | *msg = toupper((u_char) *msg); |
553 | status_message_set(c, "%s" , msg); |
554 | } |
555 | |
556 | free(msg); |
557 | } |
558 | |