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