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