1 /* $OpenBSD: cmd-find.c,v 1.32 2016/03/03 14:14:46 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2015 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 21 #include <fnmatch.h> 22 #include <limits.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <paths.h> 26 #include <unistd.h> 27 28 #include "tmux.h" 29 30 struct session *cmd_find_try_TMUX(struct client *, struct window *); 31 int cmd_find_client_better(struct client *, struct client *); 32 struct client *cmd_find_best_client(struct client **, u_int); 33 int cmd_find_session_better(struct session *, struct session *, 34 int); 35 struct session *cmd_find_best_session(struct session **, u_int, int); 36 int cmd_find_best_session_with_window(struct cmd_find_state *); 37 int cmd_find_best_winlink_with_window(struct cmd_find_state *); 38 39 int cmd_find_current_session_with_client(struct cmd_find_state *); 40 int cmd_find_current_session(struct cmd_find_state *); 41 struct client *cmd_find_current_client(struct cmd_q *); 42 43 const char *cmd_find_map_table(const char *[][2], const char *); 44 45 int cmd_find_get_session(struct cmd_find_state *, const char *); 46 int cmd_find_get_window(struct cmd_find_state *, const char *); 47 int cmd_find_get_window_with_session(struct cmd_find_state *, const char *); 48 int cmd_find_get_window_with_pane(struct cmd_find_state *); 49 int cmd_find_get_pane(struct cmd_find_state *, const char *); 50 int cmd_find_get_pane_with_session(struct cmd_find_state *, const char *); 51 int cmd_find_get_pane_with_window(struct cmd_find_state *, const char *); 52 53 const char *cmd_find_session_table[][2] = { 54 { NULL, NULL } 55 }; 56 const char *cmd_find_window_table[][2] = { 57 { "{start}", "^" }, 58 { "{last}", "!" }, 59 { "{end}", "$" }, 60 { "{next}", "+" }, 61 { "{previous}", "-" }, 62 { NULL, NULL } 63 }; 64 const char *cmd_find_pane_table[][2] = { 65 { "{last}", "!" }, 66 { "{next}", "+" }, 67 { "{previous}", "-" }, 68 { "{top}", "top" }, 69 { "{bottom}", "bottom" }, 70 { "{left}", "left" }, 71 { "{right}", "right" }, 72 { "{top-left}", "top-left" }, 73 { "{top-right}", "top-right" }, 74 { "{bottom-left}", "bottom-left" }, 75 { "{bottom-right}", "bottom-right" }, 76 { "{up-of}", "{up-of}" }, 77 { "{down-of}", "{down-of}" }, 78 { "{left-of}", "{left-of}" }, 79 { "{right-of}", "{right-of}" }, 80 { NULL, NULL } 81 }; 82 83 /* Get session from TMUX if present. */ 84 struct session * 85 cmd_find_try_TMUX(struct client *c, struct window *w) 86 { 87 struct environ_entry *envent; 88 char tmp[256]; 89 long long pid; 90 u_int session; 91 struct session *s; 92 93 envent = environ_find(c->environ, "TMUX"); 94 if (envent == NULL) 95 return (NULL); 96 97 if (sscanf(envent->value, "%255[^,],%lld,%d", tmp, &pid, &session) != 3) 98 return (NULL); 99 if (pid != getpid()) 100 return (NULL); 101 log_debug("client %p TMUX is %s (session @%u)", c, envent->value, 102 session); 103 104 s = session_find_by_id(session); 105 if (s == NULL || (w != NULL && !session_has(s, w))) 106 return (NULL); 107 return (s); 108 } 109 110 /* Is this client better? */ 111 int 112 cmd_find_client_better(struct client *c, struct client *than) 113 { 114 if (than == NULL) 115 return (1); 116 return (timercmp(&c->activity_time, &than->activity_time, >)); 117 } 118 119 /* Find best client from a list, or all if list is NULL. */ 120 struct client * 121 cmd_find_best_client(struct client **clist, u_int csize) 122 { 123 struct client *c_loop, *c; 124 u_int i; 125 126 c = NULL; 127 if (clist != NULL) { 128 for (i = 0; i < csize; i++) { 129 if (clist[i]->session == NULL) 130 continue; 131 if (cmd_find_client_better(clist[i], c)) 132 c = clist[i]; 133 } 134 } else { 135 TAILQ_FOREACH(c_loop, &clients, entry) { 136 if (c_loop->session == NULL) 137 continue; 138 if (cmd_find_client_better(c_loop, c)) 139 c = c_loop; 140 } 141 } 142 return (c); 143 } 144 145 /* Is this session better? */ 146 int 147 cmd_find_session_better(struct session *s, struct session *than, int flags) 148 { 149 int attached; 150 151 if (than == NULL) 152 return (1); 153 if (flags & CMD_FIND_PREFER_UNATTACHED) { 154 attached = (~than->flags & SESSION_UNATTACHED); 155 if (attached && (s->flags & SESSION_UNATTACHED)) 156 return (1); 157 else if (!attached && (~s->flags & SESSION_UNATTACHED)) 158 return (0); 159 } 160 return (timercmp(&s->activity_time, &than->activity_time, >)); 161 } 162 163 /* Find best session from a list, or all if list is NULL. */ 164 struct session * 165 cmd_find_best_session(struct session **slist, u_int ssize, int flags) 166 { 167 struct session *s_loop, *s; 168 u_int i; 169 170 s = NULL; 171 if (slist != NULL) { 172 for (i = 0; i < ssize; i++) { 173 if (cmd_find_session_better(slist[i], s, flags)) 174 s = slist[i]; 175 } 176 } else { 177 RB_FOREACH(s_loop, sessions, &sessions) { 178 if (cmd_find_session_better(s_loop, s, flags)) 179 s = s_loop; 180 } 181 } 182 return (s); 183 } 184 185 /* Find best session and winlink for window. */ 186 int 187 cmd_find_best_session_with_window(struct cmd_find_state *fs) 188 { 189 struct session **slist = NULL; 190 u_int ssize; 191 struct session *s; 192 193 if (fs->cmdq != NULL && fs->cmdq->client != NULL) { 194 fs->s = cmd_find_try_TMUX(fs->cmdq->client, fs->w); 195 if (fs->s != NULL) 196 return (cmd_find_best_winlink_with_window(fs)); 197 } 198 199 ssize = 0; 200 RB_FOREACH(s, sessions, &sessions) { 201 if (!session_has(s, fs->w)) 202 continue; 203 slist = xreallocarray(slist, ssize + 1, sizeof *slist); 204 slist[ssize++] = s; 205 } 206 if (ssize == 0) 207 goto fail; 208 fs->s = cmd_find_best_session(slist, ssize, fs->flags); 209 if (fs->s == NULL) 210 goto fail; 211 free(slist); 212 return (cmd_find_best_winlink_with_window(fs)); 213 214 fail: 215 free(slist); 216 return (-1); 217 } 218 219 /* 220 * Find the best winlink for a window (the current if it contains the pane, 221 * otherwise the first). 222 */ 223 int 224 cmd_find_best_winlink_with_window(struct cmd_find_state *fs) 225 { 226 struct winlink *wl, *wl_loop; 227 228 wl = NULL; 229 if (fs->s->curw->window == fs->w) 230 wl = fs->s->curw; 231 else { 232 RB_FOREACH(wl_loop, winlinks, &fs->s->windows) { 233 if (wl_loop->window == fs->w) { 234 wl = wl_loop; 235 break; 236 } 237 } 238 } 239 if (wl == NULL) 240 return (-1); 241 fs->wl = wl; 242 fs->idx = fs->wl->idx; 243 return (0); 244 } 245 246 /* Find current session when we have an unattached client. */ 247 int 248 cmd_find_current_session_with_client(struct cmd_find_state *fs) 249 { 250 struct window_pane *wp; 251 252 /* 253 * If this is running in a pane, we can use that to limit the list of 254 * sessions to those containing that pane (we still use the current 255 * window in the best session). 256 */ 257 if (fs->cmdq != NULL && fs->cmdq->client->tty.path != NULL) { 258 RB_FOREACH(wp, window_pane_tree, &all_window_panes) { 259 if (strcmp(wp->tty, fs->cmdq->client->tty.path) == 0) 260 break; 261 } 262 } else 263 wp = NULL; 264 265 /* Not running in a pane. We know nothing. Find the best session. */ 266 if (wp == NULL) 267 goto unknown_pane; 268 269 /* Find the best session and winlink containing this pane. */ 270 fs->w = wp->window; 271 if (cmd_find_best_session_with_window(fs) != 0) { 272 if (wp != NULL) { 273 /* 274 * The window may have been destroyed but the pane 275 * still on all_window_panes due to something else 276 * holding a reference. 277 */ 278 goto unknown_pane; 279 } 280 return (-1); 281 } 282 283 /* Use the current window and pane from this session. */ 284 fs->wl = fs->s->curw; 285 fs->idx = fs->wl->idx; 286 fs->w = fs->wl->window; 287 fs->wp = fs->w->active; 288 289 return (0); 290 291 unknown_pane: 292 fs->s = NULL; 293 if (fs->cmdq != NULL) 294 fs->s = cmd_find_try_TMUX(fs->cmdq->client, NULL); 295 if (fs->s == NULL) 296 fs->s = cmd_find_best_session(NULL, 0, fs->flags); 297 if (fs->s == NULL) 298 return (-1); 299 fs->wl = fs->s->curw; 300 fs->idx = fs->wl->idx; 301 fs->w = fs->wl->window; 302 fs->wp = fs->w->active; 303 304 return (0); 305 } 306 307 /* 308 * Work out the best current state. If this function succeeds, the state is 309 * guaranteed to be completely filled in. 310 */ 311 int 312 cmd_find_current_session(struct cmd_find_state *fs) 313 { 314 /* If we know the current client, use it. */ 315 if (fs->cmdq != NULL && fs->cmdq->client != NULL) { 316 log_debug("%s: have client %p%s", __func__, fs->cmdq->client, 317 fs->cmdq->client->session == NULL ? "" : " (with session)"); 318 if (fs->cmdq->client->session == NULL) 319 return (cmd_find_current_session_with_client(fs)); 320 fs->s = fs->cmdq->client->session; 321 fs->wl = fs->s->curw; 322 fs->idx = fs->wl->idx; 323 fs->w = fs->wl->window; 324 fs->wp = fs->w->active; 325 return (0); 326 } 327 328 /* We know nothing, find the best session and client. */ 329 fs->s = cmd_find_best_session(NULL, 0, fs->flags); 330 if (fs->s == NULL) 331 return (-1); 332 fs->wl = fs->s->curw; 333 fs->idx = fs->wl->idx; 334 fs->w = fs->wl->window; 335 fs->wp = fs->w->active; 336 337 return (0); 338 } 339 340 /* Work out the best current client. */ 341 struct client * 342 cmd_find_current_client(struct cmd_q *cmdq) 343 { 344 struct cmd_find_state current; 345 struct session *s; 346 struct client *c, **clist = NULL; 347 u_int csize; 348 349 /* If the queue client has a session, use it. */ 350 if (cmdq->client != NULL && cmdq->client->session != NULL) { 351 log_debug("%s: using cmdq %p client %p", __func__, cmdq, 352 cmdq->client); 353 return (cmdq->client); 354 } 355 356 /* Otherwise find the current session. */ 357 cmd_find_clear_state(¤t, cmdq, 0); 358 if (cmd_find_current_session(¤t) != 0) 359 return (NULL); 360 361 /* If it is attached, find the best of it's clients. */ 362 s = current.s; 363 log_debug("%s: current session $%u %s", __func__, s->id, s->name); 364 if (~s->flags & SESSION_UNATTACHED) { 365 csize = 0; 366 TAILQ_FOREACH(c, &clients, entry) { 367 if (c->session != s) 368 continue; 369 clist = xreallocarray(clist, csize + 1, sizeof *clist); 370 clist[csize++] = c; 371 } 372 if (csize != 0) { 373 c = cmd_find_best_client(clist, csize); 374 if (c != NULL) { 375 free(clist); 376 return (c); 377 } 378 } 379 free(clist); 380 } 381 382 /* Otherwise pick best of all clients. */ 383 return (cmd_find_best_client(NULL, 0)); 384 } 385 386 /* Maps string in table. */ 387 const char * 388 cmd_find_map_table(const char *table[][2], const char *s) 389 { 390 u_int i; 391 392 for (i = 0; table[i][0] != NULL; i++) { 393 if (strcmp(s, table[i][0]) == 0) 394 return (table[i][1]); 395 } 396 return (s); 397 } 398 399 /* Find session from string. Fills in s. */ 400 int 401 cmd_find_get_session(struct cmd_find_state *fs, const char *session) 402 { 403 struct session *s, *s_loop; 404 struct client *c; 405 406 log_debug("%s: %s", __func__, session); 407 408 /* Check for session ids starting with $. */ 409 if (*session == '$') { 410 fs->s = session_find_by_id_str(session); 411 if (fs->s == NULL) 412 return (-1); 413 return (0); 414 } 415 416 /* Look for exactly this session. */ 417 fs->s = session_find(session); 418 if (fs->s != NULL) 419 return (0); 420 421 /* Look for as a client. */ 422 c = cmd_find_client(NULL, session, 1); 423 if (c != NULL && c->session != NULL) { 424 fs->s = c->session; 425 return (0); 426 } 427 428 /* Stop now if exact only. */ 429 if (fs->flags & CMD_FIND_EXACT_SESSION) 430 return (-1); 431 432 /* Otherwise look for prefix. */ 433 s = NULL; 434 RB_FOREACH(s_loop, sessions, &sessions) { 435 if (strncmp(session, s_loop->name, strlen(session)) == 0) { 436 if (s != NULL) 437 return (-1); 438 s = s_loop; 439 } 440 } 441 if (s != NULL) { 442 fs->s = s; 443 return (0); 444 } 445 446 /* Then as a pattern. */ 447 s = NULL; 448 RB_FOREACH(s_loop, sessions, &sessions) { 449 if (fnmatch(session, s_loop->name, 0) == 0) { 450 if (s != NULL) 451 return (-1); 452 s = s_loop; 453 } 454 } 455 if (s != NULL) { 456 fs->s = s; 457 return (0); 458 } 459 460 return (-1); 461 } 462 463 /* Find window from string. Fills in s, wl, w. */ 464 int 465 cmd_find_get_window(struct cmd_find_state *fs, const char *window) 466 { 467 log_debug("%s: %s", __func__, window); 468 469 /* Check for window ids starting with @. */ 470 if (*window == '@') { 471 fs->w = window_find_by_id_str(window); 472 if (fs->w == NULL) 473 return (-1); 474 return (cmd_find_best_session_with_window(fs)); 475 } 476 477 /* Not a window id, so use the current session. */ 478 fs->s = fs->current->s; 479 480 /* We now only need to find the winlink in this session. */ 481 if (cmd_find_get_window_with_session(fs, window) == 0) 482 return (0); 483 484 /* Otherwise try as a session itself. */ 485 if (cmd_find_get_session(fs, window) == 0) { 486 fs->wl = fs->s->curw; 487 fs->w = fs->wl->window; 488 if (~fs->flags & CMD_FIND_WINDOW_INDEX) 489 fs->idx = fs->wl->idx; 490 return (0); 491 } 492 493 return (-1); 494 } 495 496 /* 497 * Find window from string, assuming it is in given session. Needs s, fills in 498 * wl and w. 499 */ 500 int 501 cmd_find_get_window_with_session(struct cmd_find_state *fs, const char *window) 502 { 503 struct winlink *wl; 504 const char *errstr; 505 int idx, n, exact; 506 struct session *s; 507 508 log_debug("%s: %s", __func__, window); 509 exact = (fs->flags & CMD_FIND_EXACT_WINDOW); 510 511 /* 512 * Start with the current window as the default. So if only an index is 513 * found, the window will be the current. 514 */ 515 fs->wl = fs->s->curw; 516 fs->w = fs->wl->window; 517 518 /* Check for window ids starting with @. */ 519 if (*window == '@') { 520 fs->w = window_find_by_id_str(window); 521 if (fs->w == NULL || !session_has(fs->s, fs->w)) 522 return (-1); 523 return (cmd_find_best_winlink_with_window(fs)); 524 } 525 526 /* Try as an offset. */ 527 if (!exact && (window[0] == '+' || window[0] == '-')) { 528 if (window[1] != '\0') 529 n = strtonum(window + 1, 1, INT_MAX, NULL); 530 else 531 n = 1; 532 s = fs->s; 533 if (fs->flags & CMD_FIND_WINDOW_INDEX) { 534 if (window[0] == '+') { 535 if (INT_MAX - s->curw->idx < n) 536 return (-1); 537 fs->idx = s->curw->idx + n; 538 } else { 539 if (n < s->curw->idx) 540 return (-1); 541 fs->idx = s->curw->idx - n; 542 } 543 return (0); 544 } 545 if (window[0] == '+') 546 fs->wl = winlink_next_by_number(s->curw, s, n); 547 else 548 fs->wl = winlink_previous_by_number(s->curw, s, n); 549 if (fs->wl != NULL) { 550 fs->idx = fs->wl->idx; 551 fs->w = fs->wl->window; 552 return (0); 553 } 554 } 555 556 /* Try special characters. */ 557 if (!exact) { 558 if (strcmp(window, "!") == 0) { 559 fs->wl = TAILQ_FIRST(&fs->s->lastw); 560 if (fs->wl == NULL) 561 return (-1); 562 fs->idx = fs->wl->idx; 563 fs->w = fs->wl->window; 564 return (0); 565 } else if (strcmp(window, "^") == 0) { 566 fs->wl = RB_MIN(winlinks, &fs->s->windows); 567 if (fs->wl == NULL) 568 return (-1); 569 fs->idx = fs->wl->idx; 570 fs->w = fs->wl->window; 571 return (0); 572 } else if (strcmp(window, "$") == 0) { 573 fs->wl = RB_MAX(winlinks, &fs->s->windows); 574 if (fs->wl == NULL) 575 return (-1); 576 fs->idx = fs->wl->idx; 577 fs->w = fs->wl->window; 578 return (0); 579 } 580 } 581 582 /* First see if this is a valid window index in this session. */ 583 if (window[0] != '+' && window[0] != '-') { 584 idx = strtonum(window, 0, INT_MAX, &errstr); 585 if (errstr == NULL) { 586 if (fs->flags & CMD_FIND_WINDOW_INDEX) { 587 fs->idx = idx; 588 return (0); 589 } 590 fs->wl = winlink_find_by_index(&fs->s->windows, idx); 591 if (fs->wl != NULL) { 592 fs->w = fs->wl->window; 593 return (0); 594 } 595 } 596 } 597 598 /* Look for exact matches, error if more than one. */ 599 fs->wl = NULL; 600 RB_FOREACH(wl, winlinks, &fs->s->windows) { 601 if (strcmp(window, wl->window->name) == 0) { 602 if (fs->wl != NULL) 603 return (-1); 604 fs->wl = wl; 605 } 606 } 607 if (fs->wl != NULL) { 608 fs->idx = fs->wl->idx; 609 fs->w = fs->wl->window; 610 return (0); 611 } 612 613 /* Stop now if exact only. */ 614 if (exact) 615 return (-1); 616 617 /* Try as the start of a window name, error if multiple. */ 618 fs->wl = NULL; 619 RB_FOREACH(wl, winlinks, &fs->s->windows) { 620 if (strncmp(window, wl->window->name, strlen(window)) == 0) { 621 if (fs->wl != NULL) 622 return (-1); 623 fs->wl = wl; 624 } 625 } 626 if (fs->wl != NULL) { 627 fs->idx = fs->wl->idx; 628 fs->w = fs->wl->window; 629 return (0); 630 } 631 632 /* Now look for pattern matches, again error if multiple. */ 633 fs->wl = NULL; 634 RB_FOREACH(wl, winlinks, &fs->s->windows) { 635 if (fnmatch(window, wl->window->name, 0) == 0) { 636 if (fs->wl != NULL) 637 return (-1); 638 fs->wl = wl; 639 } 640 } 641 if (fs->wl != NULL) { 642 fs->idx = fs->wl->idx; 643 fs->w = fs->wl->window; 644 return (0); 645 } 646 647 return (-1); 648 } 649 650 /* Find window from given pane. Needs wp, fills in s and wl and w. */ 651 int 652 cmd_find_get_window_with_pane(struct cmd_find_state *fs) 653 { 654 log_debug("%s", __func__); 655 656 fs->w = fs->wp->window; 657 return (cmd_find_best_session_with_window(fs)); 658 } 659 660 /* Find pane from string. Fills in s, wl, w, wp. */ 661 int 662 cmd_find_get_pane(struct cmd_find_state *fs, const char *pane) 663 { 664 log_debug("%s: %s", __func__, pane); 665 666 /* Check for pane ids starting with %. */ 667 if (*pane == '%') { 668 fs->wp = window_pane_find_by_id_str(pane); 669 if (fs->wp == NULL) 670 return (-1); 671 fs->w = fs->wp->window; 672 return (cmd_find_best_session_with_window(fs)); 673 } 674 675 /* Not a pane id, so try the current session and window. */ 676 fs->s = fs->current->s; 677 fs->wl = fs->current->wl; 678 fs->idx = fs->current->idx; 679 fs->w = fs->current->w; 680 681 /* We now only need to find the pane in this window. */ 682 if (cmd_find_get_pane_with_window(fs, pane) == 0) 683 return (0); 684 685 /* Otherwise try as a window itself (this will also try as session). */ 686 if (cmd_find_get_window(fs, pane) == 0) { 687 fs->wp = fs->w->active; 688 return (0); 689 } 690 691 return (-1); 692 } 693 694 /* 695 * Find pane from string, assuming it is in given session. Needs s, fills in wl 696 * and w and wp. 697 */ 698 int 699 cmd_find_get_pane_with_session(struct cmd_find_state *fs, const char *pane) 700 { 701 log_debug("%s: %s", __func__, pane); 702 703 /* Check for pane ids starting with %. */ 704 if (*pane == '%') { 705 fs->wp = window_pane_find_by_id_str(pane); 706 if (fs->wp == NULL) 707 return (-1); 708 fs->w = fs->wp->window; 709 return (cmd_find_best_winlink_with_window(fs)); 710 } 711 712 /* Otherwise use the current window. */ 713 fs->wl = fs->s->curw; 714 fs->idx = fs->wl->idx; 715 fs->w = fs->wl->window; 716 717 /* Now we just need to look up the pane. */ 718 return (cmd_find_get_pane_with_window(fs, pane)); 719 } 720 721 /* 722 * Find pane from string, assuming it is in the given window. Needs w, fills in 723 * wp. 724 */ 725 int 726 cmd_find_get_pane_with_window(struct cmd_find_state *fs, const char *pane) 727 { 728 const char *errstr; 729 int idx; 730 struct window_pane *wp; 731 u_int n; 732 733 log_debug("%s: %s", __func__, pane); 734 735 /* Check for pane ids starting with %. */ 736 if (*pane == '%') { 737 fs->wp = window_pane_find_by_id_str(pane); 738 if (fs->wp == NULL || fs->wp->window != fs->w) 739 return (-1); 740 return (0); 741 } 742 743 /* Try special characters. */ 744 if (strcmp(pane, "!") == 0) { 745 if (fs->w->last == NULL) 746 return (-1); 747 fs->wp = fs->w->last; 748 return (0); 749 } else if (strcmp(pane, "{up-of}") == 0) { 750 fs->wp = window_pane_find_up(fs->w->active); 751 if (fs->wp == NULL) 752 return (-1); 753 return (0); 754 } else if (strcmp(pane, "{down-of}") == 0) { 755 fs->wp = window_pane_find_down(fs->w->active); 756 if (fs->wp == NULL) 757 return (-1); 758 return (0); 759 } else if (strcmp(pane, "{left-of}") == 0) { 760 fs->wp = window_pane_find_left(fs->w->active); 761 if (fs->wp == NULL) 762 return (-1); 763 return (0); 764 } else if (strcmp(pane, "{right-of}") == 0) { 765 fs->wp = window_pane_find_right(fs->w->active); 766 if (fs->wp == NULL) 767 return (-1); 768 return (0); 769 } 770 771 /* Try as an offset. */ 772 if (pane[0] == '+' || pane[0] == '-') { 773 if (pane[1] != '\0') 774 n = strtonum(pane + 1, 1, INT_MAX, NULL); 775 else 776 n = 1; 777 wp = fs->w->active; 778 if (pane[0] == '+') 779 fs->wp = window_pane_next_by_number(fs->w, wp, n); 780 else 781 fs->wp = window_pane_previous_by_number(fs->w, wp, n); 782 if (fs->wp != NULL) 783 return (0); 784 } 785 786 /* Get pane by index. */ 787 idx = strtonum(pane, 0, INT_MAX, &errstr); 788 if (errstr == NULL) { 789 fs->wp = window_pane_at_index(fs->w, idx); 790 if (fs->wp != NULL) 791 return (0); 792 } 793 794 /* Try as a description. */ 795 fs->wp = window_find_string(fs->w, pane); 796 if (fs->wp != NULL) 797 return (0); 798 799 return (-1); 800 } 801 802 /* Clear state. */ 803 void 804 cmd_find_clear_state(struct cmd_find_state *fs, struct cmd_q *cmdq, int flags) 805 { 806 memset(fs, 0, sizeof *fs); 807 808 fs->cmdq = cmdq; 809 fs->flags = flags; 810 811 fs->idx = -1; 812 } 813 814 /* Check if a state if valid. */ 815 int 816 cmd_find_valid_state(struct cmd_find_state *fs) 817 { 818 struct winlink *wl; 819 820 if (fs->s == NULL || fs->wl == NULL || fs->w == NULL || fs->wp == NULL) 821 return (0); 822 823 if (!session_alive(fs->s)) 824 return (0); 825 826 RB_FOREACH(wl, winlinks, &fs->s->windows) { 827 if (wl->window == fs->w && wl == fs->wl) 828 break; 829 } 830 if (wl == NULL) 831 return (0); 832 833 if (fs->w != fs->wl->window) 834 return (0); 835 836 if (!window_has_pane(fs->w, fs->wp)) 837 return (0); 838 return (window_pane_visible(fs->wp)); 839 } 840 841 /* Copy a state. */ 842 void 843 cmd_find_copy_state(struct cmd_find_state *dst, struct cmd_find_state *src) 844 { 845 dst->s = src->s; 846 dst->wl = src->wl; 847 dst->idx = src->idx; 848 dst->w = src->w; 849 dst->wp = src->wp; 850 } 851 852 /* Log the result. */ 853 void 854 cmd_find_log_state(const char *prefix, struct cmd_find_state *fs) 855 { 856 if (fs->s != NULL) 857 log_debug("%s: s=$%u", prefix, fs->s->id); 858 else 859 log_debug("%s: s=none", prefix); 860 if (fs->wl != NULL) { 861 log_debug("%s: wl=%u %d w=@%u %s", prefix, fs->wl->idx, 862 fs->wl->window == fs->w, fs->w->id, fs->w->name); 863 } else 864 log_debug("%s: wl=none", prefix); 865 if (fs->wp != NULL) 866 log_debug("%s: wp=%%%u", prefix, fs->wp->id); 867 else 868 log_debug("%s: wp=none", prefix); 869 if (fs->idx != -1) 870 log_debug("%s: idx=%d", prefix, fs->idx); 871 else 872 log_debug("%s: idx=none", prefix); 873 } 874 875 /* Find state from a session. */ 876 int 877 cmd_find_from_session(struct cmd_find_state *fs, struct session *s) 878 { 879 cmd_find_clear_state(fs, NULL, 0); 880 881 fs->s = s; 882 fs->wl = fs->s->curw; 883 fs->w = fs->wl->window; 884 fs->wp = fs->w->active; 885 886 cmd_find_log_state(__func__, fs); 887 return (0); 888 } 889 890 /* Find state from a winlink. */ 891 int 892 cmd_find_from_winlink(struct cmd_find_state *fs, struct session *s, 893 struct winlink *wl) 894 { 895 cmd_find_clear_state(fs, NULL, 0); 896 897 fs->s = s; 898 fs->wl = wl; 899 fs->w = wl->window; 900 fs->wp = wl->window->active; 901 902 cmd_find_log_state(__func__, fs); 903 return (0); 904 } 905 906 /* Find state from a window. */ 907 int 908 cmd_find_from_window(struct cmd_find_state *fs, struct window *w) 909 { 910 cmd_find_clear_state(fs, NULL, 0); 911 912 fs->w = w; 913 if (cmd_find_best_session_with_window(fs) != 0) 914 return (-1); 915 if (cmd_find_best_winlink_with_window(fs) != 0) 916 return (-1); 917 918 cmd_find_log_state(__func__, fs); 919 return (0); 920 } 921 922 /* Find state from a pane. */ 923 int 924 cmd_find_from_pane(struct cmd_find_state *fs, struct window_pane *wp) 925 { 926 if (cmd_find_from_window(fs, wp->window) != 0) 927 return (-1); 928 fs->wp = wp; 929 930 cmd_find_log_state(__func__, fs); 931 return (0); 932 } 933 934 /* Find current state. */ 935 int 936 cmd_find_current(struct cmd_find_state *fs, struct cmd_q *cmdq, int flags) 937 { 938 cmd_find_clear_state(fs, cmdq, flags); 939 if (cmd_find_current_session(fs) != 0) { 940 if (~flags & CMD_FIND_QUIET) 941 cmdq_error(cmdq, "no current session"); 942 return (-1); 943 } 944 return (0); 945 } 946 947 /* 948 * Split target into pieces and resolve for the given type. Fills in the given 949 * state. Returns 0 on success or -1 on error. 950 */ 951 int 952 cmd_find_target(struct cmd_find_state *fs, struct cmd_find_state *current, 953 struct cmd_q *cmdq, const char *target, enum cmd_find_type type, int flags) 954 { 955 struct mouse_event *m; 956 char *colon, *period, *copy = NULL; 957 const char *session, *window, *pane; 958 959 /* Log the arguments. */ 960 if (target == NULL) 961 log_debug("%s: target none, type %d", __func__, type); 962 else 963 log_debug("%s: target %s, type %d", __func__, target, type); 964 log_debug("%s: cmdq %p, flags %#x", __func__, cmdq, flags); 965 966 /* Clear new state. */ 967 cmd_find_clear_state(fs, cmdq, flags); 968 969 /* Find current state. */ 970 if (server_check_marked() && (flags & CMD_FIND_DEFAULT_MARKED)) 971 fs->current = &marked_pane; 972 else if (cmd_find_valid_state(&cmdq->current)) 973 fs->current = &cmdq->current; 974 else 975 fs->current = current; 976 977 /* An empty or NULL target is the current. */ 978 if (target == NULL || *target == '\0') 979 goto current; 980 981 /* Mouse target is a plain = or {mouse}. */ 982 if (strcmp(target, "=") == 0 || strcmp(target, "{mouse}") == 0) { 983 m = &cmdq->item->mouse; 984 switch (type) { 985 case CMD_FIND_PANE: 986 fs->wp = cmd_mouse_pane(m, &fs->s, &fs->wl); 987 if (fs->wp != NULL) 988 fs->w = fs->wl->window; 989 break; 990 case CMD_FIND_WINDOW: 991 case CMD_FIND_SESSION: 992 fs->wl = cmd_mouse_window(m, &fs->s); 993 if (fs->wl != NULL) { 994 fs->w = fs->wl->window; 995 fs->wp = fs->w->active; 996 } 997 break; 998 } 999 if (fs->wp == NULL) { 1000 if (~flags & CMD_FIND_QUIET) 1001 cmdq_error(cmdq, "no mouse target"); 1002 goto error; 1003 } 1004 goto found; 1005 } 1006 1007 /* Marked target is a plain ~ or {marked}. */ 1008 if (strcmp(target, "~") == 0 || strcmp(target, "{marked}") == 0) { 1009 if (!server_check_marked()) { 1010 if (~flags & CMD_FIND_QUIET) 1011 cmdq_error(cmdq, "no marked target"); 1012 goto error; 1013 } 1014 cmd_find_copy_state(fs, &marked_pane); 1015 goto found; 1016 } 1017 1018 /* Find separators if they exist. */ 1019 copy = xstrdup(target); 1020 colon = strchr(copy, ':'); 1021 if (colon != NULL) 1022 *colon++ = '\0'; 1023 if (colon == NULL) 1024 period = strchr(copy, '.'); 1025 else 1026 period = strchr(colon, '.'); 1027 if (period != NULL) 1028 *period++ = '\0'; 1029 1030 /* Set session, window and pane parts. */ 1031 session = window = pane = NULL; 1032 if (colon != NULL && period != NULL) { 1033 session = copy; 1034 window = colon; 1035 pane = period; 1036 } else if (colon != NULL && period == NULL) { 1037 session = copy; 1038 window = colon; 1039 } else if (colon == NULL && period != NULL) { 1040 window = copy; 1041 pane = period; 1042 } else { 1043 if (*copy == '$') 1044 session = copy; 1045 else if (*copy == '@') 1046 window = copy; 1047 else if (*copy == '%') 1048 pane = copy; 1049 else { 1050 switch (type) { 1051 case CMD_FIND_SESSION: 1052 session = copy; 1053 break; 1054 case CMD_FIND_WINDOW: 1055 window = copy; 1056 break; 1057 case CMD_FIND_PANE: 1058 pane = copy; 1059 break; 1060 } 1061 } 1062 } 1063 1064 /* Set exact match flags. */ 1065 if (session != NULL && *session == '=') { 1066 session++; 1067 fs->flags |= CMD_FIND_EXACT_SESSION; 1068 } 1069 if (window != NULL && *window == '=') { 1070 window++; 1071 fs->flags |= CMD_FIND_EXACT_WINDOW; 1072 } 1073 1074 /* Empty is the same as NULL. */ 1075 if (session != NULL && *session == '\0') 1076 session = NULL; 1077 if (window != NULL && *window == '\0') 1078 window = NULL; 1079 if (pane != NULL && *pane == '\0') 1080 pane = NULL; 1081 1082 /* Map though conversion table. */ 1083 if (session != NULL) 1084 session = cmd_find_map_table(cmd_find_session_table, session); 1085 if (window != NULL) 1086 window = cmd_find_map_table(cmd_find_window_table, window); 1087 if (pane != NULL) 1088 pane = cmd_find_map_table(cmd_find_pane_table, pane); 1089 1090 log_debug("target %s (flags %#x): session=%s, window=%s, pane=%s", 1091 target, flags, session == NULL ? "none" : session, 1092 window == NULL ? "none" : window, pane == NULL ? "none" : pane); 1093 1094 /* No pane is allowed if want an index. */ 1095 if (pane != NULL && (flags & CMD_FIND_WINDOW_INDEX)) { 1096 if (~flags & CMD_FIND_QUIET) 1097 cmdq_error(cmdq, "can't specify pane here"); 1098 goto error; 1099 } 1100 1101 /* If the session isn't NULL, look it up. */ 1102 if (session != NULL) { 1103 /* This will fill in session. */ 1104 if (cmd_find_get_session(fs, session) != 0) 1105 goto no_session; 1106 1107 /* If window and pane are NULL, use that session's current. */ 1108 if (window == NULL && pane == NULL) { 1109 fs->wl = fs->s->curw; 1110 fs->idx = -1; 1111 fs->w = fs->wl->window; 1112 fs->wp = fs->w->active; 1113 goto found; 1114 } 1115 1116 /* If window is present but pane not, find window in session. */ 1117 if (window != NULL && pane == NULL) { 1118 /* This will fill in winlink and window. */ 1119 if (cmd_find_get_window_with_session(fs, window) != 0) 1120 goto no_window; 1121 fs->wp = fs->wl->window->active; 1122 goto found; 1123 } 1124 1125 /* If pane is present but window not, find pane. */ 1126 if (window == NULL && pane != NULL) { 1127 /* This will fill in winlink and window and pane. */ 1128 if (cmd_find_get_pane_with_session(fs, pane) != 0) 1129 goto no_pane; 1130 goto found; 1131 } 1132 1133 /* 1134 * If window and pane are present, find both in session. This 1135 * will fill in winlink and window. 1136 */ 1137 if (cmd_find_get_window_with_session(fs, window) != 0) 1138 goto no_window; 1139 /* This will fill in pane. */ 1140 if (cmd_find_get_pane_with_window(fs, pane) != 0) 1141 goto no_pane; 1142 goto found; 1143 } 1144 1145 /* No session. If window and pane, try them. */ 1146 if (window != NULL && pane != NULL) { 1147 /* This will fill in session, winlink and window. */ 1148 if (cmd_find_get_window(fs, window) != 0) 1149 goto no_window; 1150 /* This will fill in pane. */ 1151 if (cmd_find_get_pane_with_window(fs, pane) != 0) 1152 goto no_pane; 1153 goto found; 1154 } 1155 1156 /* If just window is present, try it. */ 1157 if (window != NULL && pane == NULL) { 1158 /* This will fill in session, winlink and window. */ 1159 if (cmd_find_get_window(fs, window) != 0) 1160 goto no_window; 1161 fs->wp = fs->wl->window->active; 1162 goto found; 1163 } 1164 1165 /* If just pane is present, try it. */ 1166 if (window == NULL && pane != NULL) { 1167 /* This will fill in session, winlink, window and pane. */ 1168 if (cmd_find_get_pane(fs, pane) != 0) 1169 goto no_pane; 1170 goto found; 1171 } 1172 1173 current: 1174 /* Use the current session. */ 1175 cmd_find_copy_state(fs, fs->current); 1176 if (flags & CMD_FIND_WINDOW_INDEX) 1177 fs->idx = -1; 1178 goto found; 1179 1180 error: 1181 fs->current = NULL; 1182 log_debug(" error"); 1183 1184 free(copy); 1185 return (-1); 1186 1187 found: 1188 fs->current = NULL; 1189 cmd_find_log_state(__func__, fs); 1190 1191 free(copy); 1192 return (0); 1193 1194 no_session: 1195 if (~flags & CMD_FIND_QUIET) 1196 cmdq_error(cmdq, "can't find session %s", session); 1197 goto error; 1198 1199 no_window: 1200 if (~flags & CMD_FIND_QUIET) 1201 cmdq_error(cmdq, "can't find window %s", window); 1202 goto error; 1203 1204 no_pane: 1205 if (~flags & CMD_FIND_QUIET) 1206 cmdq_error(cmdq, "can't find pane %s", pane); 1207 goto error; 1208 } 1209 1210 /* Find the target client or report an error and return NULL. */ 1211 struct client * 1212 cmd_find_client(struct cmd_q *cmdq, const char *target, int quiet) 1213 { 1214 struct client *c; 1215 char *copy; 1216 size_t size; 1217 const char *path; 1218 1219 /* A NULL argument means the current client. */ 1220 if (cmdq != NULL && target == NULL) { 1221 c = cmd_find_current_client(cmdq); 1222 if (c == NULL && !quiet) 1223 cmdq_error(cmdq, "no current client"); 1224 log_debug("%s: no target, return %p", __func__, c); 1225 return (c); 1226 } 1227 copy = xstrdup(target); 1228 1229 /* Trim a single trailing colon if any. */ 1230 size = strlen(copy); 1231 if (size != 0 && copy[size - 1] == ':') 1232 copy[size - 1] = '\0'; 1233 1234 /* Check path of each client. */ 1235 TAILQ_FOREACH(c, &clients, entry) { 1236 if (c->session == NULL || c->tty.path == NULL) 1237 continue; 1238 path = c->tty.path; 1239 1240 /* Try for exact match. */ 1241 if (strcmp(copy, path) == 0) 1242 break; 1243 1244 /* Try without leading /dev. */ 1245 if (strncmp(path, _PATH_DEV, (sizeof _PATH_DEV) - 1) != 0) 1246 continue; 1247 if (strcmp(copy, path + (sizeof _PATH_DEV) - 1) == 0) 1248 break; 1249 } 1250 1251 /* If no client found, report an error. */ 1252 if (c == NULL && !quiet) 1253 cmdq_error(cmdq, "can't find client %s", copy); 1254 1255 free(copy); 1256 log_debug("%s: target %s, return %p", __func__, target, c); 1257 return (c); 1258 } 1259