1*83bde373Snicm /* $OpenBSD: session.c,v 1.98 2024/11/25 08:34:01 nicm Exp $ */ 2311827fbSnicm 3311827fbSnicm /* 498ca8272Snicm * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> 5311827fbSnicm * 6311827fbSnicm * Permission to use, copy, modify, and distribute this software for any 7311827fbSnicm * purpose with or without fee is hereby granted, provided that the above 8311827fbSnicm * copyright notice and this permission notice appear in all copies. 9311827fbSnicm * 10311827fbSnicm * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11311827fbSnicm * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12311827fbSnicm * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13311827fbSnicm * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14311827fbSnicm * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15311827fbSnicm * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16311827fbSnicm * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17311827fbSnicm */ 18311827fbSnicm 19311827fbSnicm #include <sys/types.h> 20311827fbSnicm #include <sys/time.h> 21311827fbSnicm 223a5ec08bSnicm #include <paths.h> 23311827fbSnicm #include <string.h> 24311827fbSnicm #include <stdlib.h> 25311827fbSnicm #include <unistd.h> 26c27246d4Snicm #include <vis.h> 27be5c3babSnicm #include <time.h> 28311827fbSnicm 29311827fbSnicm #include "tmux.h" 30311827fbSnicm 31311827fbSnicm struct sessions sessions; 32a8c3b024Snicm u_int next_session_id; 335a3b3ebaSnicm struct session_groups session_groups = RB_INITIALIZER(&session_groups); 34311827fbSnicm 359883b791Snicm static void session_free(int, short, void *); 369883b791Snicm static void session_lock_timer(int, short, void *); 379883b791Snicm static struct winlink *session_next_alert(struct winlink *); 389883b791Snicm static struct winlink *session_previous_alert(struct winlink *); 39ced21769Snicm static void session_group_remove(struct session *); 4006440b28Snicm static void session_group_synchronize1(struct session *, struct session *); 4106440b28Snicm 4259996dc3Snicm int 4359996dc3Snicm session_cmp(struct session *s1, struct session *s2) 4459996dc3Snicm { 4559996dc3Snicm return (strcmp(s1->name, s2->name)); 4659996dc3Snicm } 475a3b3ebaSnicm RB_GENERATE(sessions, session, entry, session_cmp); 4859996dc3Snicm 49*83bde373Snicm int 5006440b28Snicm session_group_cmp(struct session_group *s1, struct session_group *s2) 5106440b28Snicm { 5206440b28Snicm return (strcmp(s1->name, s2->name)); 5306440b28Snicm } 54*83bde373Snicm RB_GENERATE(session_groups, session_group, entry, session_group_cmp); 5506440b28Snicm 56b0024163Snicm /* 57b0024163Snicm * Find if session is still alive. This is true if it is still on the global 58b0024163Snicm * sessions list. 59b0024163Snicm */ 60b0024163Snicm int 61b0024163Snicm session_alive(struct session *s) 62b0024163Snicm { 6359996dc3Snicm struct session *s_loop; 64b0024163Snicm 6559996dc3Snicm RB_FOREACH(s_loop, sessions, &sessions) { 6659996dc3Snicm if (s_loop == s) 6759996dc3Snicm return (1); 6859996dc3Snicm } 6959996dc3Snicm return (0); 70b0024163Snicm } 71b0024163Snicm 72311827fbSnicm /* Find session by name. */ 73311827fbSnicm struct session * 74311827fbSnicm session_find(const char *name) 75311827fbSnicm { 7659996dc3Snicm struct session s; 77311827fbSnicm 7859996dc3Snicm s.name = (char *) name; 7959996dc3Snicm return (RB_FIND(sessions, &sessions, &s)); 80311827fbSnicm } 81311827fbSnicm 823b2ac4f9Snicm /* Find session by id parsed from a string. */ 833b2ac4f9Snicm struct session * 843b2ac4f9Snicm session_find_by_id_str(const char *s) 853b2ac4f9Snicm { 863b2ac4f9Snicm const char *errstr; 873b2ac4f9Snicm u_int id; 883b2ac4f9Snicm 893b2ac4f9Snicm if (*s != '$') 903b2ac4f9Snicm return (NULL); 913b2ac4f9Snicm 923b2ac4f9Snicm id = strtonum(s + 1, 0, UINT_MAX, &errstr); 933b2ac4f9Snicm if (errstr != NULL) 943b2ac4f9Snicm return (NULL); 953b2ac4f9Snicm return (session_find_by_id(id)); 963b2ac4f9Snicm } 973b2ac4f9Snicm 980a539d5dSnicm /* Find session by id. */ 9959996dc3Snicm struct session * 1000a539d5dSnicm session_find_by_id(u_int id) 10159996dc3Snicm { 10259996dc3Snicm struct session *s; 10359996dc3Snicm 10459996dc3Snicm RB_FOREACH(s, sessions, &sessions) { 1050a539d5dSnicm if (s->id == id) 10659996dc3Snicm return (s); 10759996dc3Snicm } 108311827fbSnicm return (NULL); 109311827fbSnicm } 110311827fbSnicm 111311827fbSnicm /* Create a new session. */ 112311827fbSnicm struct session * 113c26c4f79Snicm session_create(const char *prefix, const char *name, const char *cwd, 114c26c4f79Snicm struct environ *env, struct options *oo, struct termios *tio) 115311827fbSnicm { 116311827fbSnicm struct session *s; 117311827fbSnicm 11842b95bd7Snicm s = xcalloc(1, sizeof *s); 119f8fcc5dbSnicm s->references = 1; 120311827fbSnicm s->flags = 0; 1217255ff90Snicm 1223baa4a0cSnicm s->cwd = xstrdup(cwd); 123b511d510Snicm 12401b2421eSnicm TAILQ_INIT(&s->lastw); 125311827fbSnicm RB_INIT(&s->windows); 1267255ff90Snicm 127c26c4f79Snicm s->environ = env; 1287b470e93Snicm s->options = oo; 129f58d7262Snicm 130b2140406Snicm status_update_cache(s); 131e62b76d6Snicm 132f58d7262Snicm s->tio = NULL; 133f58d7262Snicm if (tio != NULL) { 134f58d7262Snicm s->tio = xmalloc(sizeof *s->tio); 135f58d7262Snicm memcpy(s->tio, tio, sizeof *s->tio); 136f58d7262Snicm } 137311827fbSnicm 1388a5e6023Snicm if (name != NULL) { 139311827fbSnicm s->name = xstrdup(name); 1400a539d5dSnicm s->id = next_session_id++; 1418a5e6023Snicm } else { 1428a5e6023Snicm do { 1430a539d5dSnicm s->id = next_session_id++; 1447d053cf9Snicm free(s->name); 14506440b28Snicm if (prefix != NULL) 14606440b28Snicm xasprintf(&s->name, "%s-%u", prefix, s->id); 14706440b28Snicm else 1480a539d5dSnicm xasprintf(&s->name, "%u", s->id); 1498a5e6023Snicm } while (RB_FIND(sessions, &sessions, s) != NULL); 1508a5e6023Snicm } 15159996dc3Snicm RB_INSERT(sessions, &sessions, s); 15201b2421eSnicm 153e215b69eSnicm log_debug("new session %s $%u", s->name, s->id); 154e215b69eSnicm 15542b95bd7Snicm if (gettimeofday(&s->creation_time, NULL) != 0) 15642b95bd7Snicm fatal("gettimeofday failed"); 15742b95bd7Snicm session_update_activity(s, &s->creation_time); 15842b95bd7Snicm 159311827fbSnicm return (s); 160311827fbSnicm } 161311827fbSnicm 16254279ec3Snicm /* Add a reference to a session. */ 16354279ec3Snicm void 16454279ec3Snicm session_add_ref(struct session *s, const char *from) 16554279ec3Snicm { 16654279ec3Snicm s->references++; 16754279ec3Snicm log_debug("%s: %s %s, now %d", __func__, s->name, from, s->references); 16854279ec3Snicm } 16954279ec3Snicm 170f8fcc5dbSnicm /* Remove a reference from a session. */ 171f8fcc5dbSnicm void 17254279ec3Snicm session_remove_ref(struct session *s, const char *from) 173f8fcc5dbSnicm { 174f8fcc5dbSnicm s->references--; 17554279ec3Snicm log_debug("%s: %s %s, now %d", __func__, s->name, from, s->references); 17654279ec3Snicm 177f8fcc5dbSnicm if (s->references == 0) 178f8fcc5dbSnicm event_once(-1, EV_TIMEOUT, session_free, s, NULL); 179f8fcc5dbSnicm } 180f8fcc5dbSnicm 181f8fcc5dbSnicm /* Free session. */ 1829883b791Snicm static void 183d0e2e7f1Snicm session_free(__unused int fd, __unused short events, void *arg) 184f8fcc5dbSnicm { 185f8fcc5dbSnicm struct session *s = arg; 186f8fcc5dbSnicm 1873e2ccc08Snicm log_debug("session %s freed (%d references)", s->name, s->references); 188f8fcc5dbSnicm 18918804715Snicm if (s->references == 0) { 190fb46cb3dSnicm environ_free(s->environ); 191d89252e5Snicm options_free(s->options); 192d89252e5Snicm 19318804715Snicm free(s->name); 194f8fcc5dbSnicm free(s); 195f8fcc5dbSnicm } 19618804715Snicm } 197f8fcc5dbSnicm 198311827fbSnicm /* Destroy a session. */ 199311827fbSnicm void 200c26c4f79Snicm session_destroy(struct session *s, int notify, const char *from) 201311827fbSnicm { 20205babb28Snicm struct winlink *wl; 203a8dbd3acSnicm 204bc4816c6Snicm log_debug("session %s destroyed (%s)", s->name, from); 205b2f27a60Snicm 206b2f27a60Snicm if (s->curw == NULL) 207b2f27a60Snicm return; 20883f26c8fSnicm s->curw = NULL; 209311827fbSnicm 21059996dc3Snicm RB_REMOVE(sessions, &sessions, s); 211c26c4f79Snicm if (notify) 2122ae124feSnicm notify_session("session-closed", s); 213311827fbSnicm 2147d053cf9Snicm free(s->tio); 215f58d7262Snicm 21642b95bd7Snicm if (event_initialized(&s->lock_timer)) 21742b95bd7Snicm event_del(&s->lock_timer); 21842b95bd7Snicm 21901b2421eSnicm session_group_remove(s); 220311827fbSnicm 22101b2421eSnicm while (!TAILQ_EMPTY(&s->lastw)) 22201b2421eSnicm winlink_stack_remove(&s->lastw, TAILQ_FIRST(&s->lastw)); 22305babb28Snicm while (!RB_EMPTY(&s->windows)) { 22405babb28Snicm wl = RB_ROOT(&s->windows); 2252ae124feSnicm notify_session_window("window-unlinked", s, wl->window); 22605babb28Snicm winlink_remove(&s->windows, wl); 22705babb28Snicm } 228311827fbSnicm 2293baa4a0cSnicm free((void *)s->cwd); 2307255ff90Snicm 23154279ec3Snicm session_remove_ref(s, __func__); 232311827fbSnicm } 233311827fbSnicm 234c27246d4Snicm /* Sanitize session name. */ 235c27246d4Snicm char * 236e8932751Snicm session_check_name(const char *name) 237e8932751Snicm { 238c27246d4Snicm char *copy, *cp, *new_name; 239c27246d4Snicm 240998b9ee1Snicm if (*name == '\0') 241998b9ee1Snicm return (NULL); 242c27246d4Snicm copy = xstrdup(name); 243c27246d4Snicm for (cp = copy; *cp != '\0'; cp++) { 244c27246d4Snicm if (*cp == ':' || *cp == '.') 245c27246d4Snicm *cp = '_'; 246c27246d4Snicm } 247c27246d4Snicm utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); 248c27246d4Snicm free(copy); 249c27246d4Snicm return (new_name); 250e8932751Snicm } 251e8932751Snicm 25242b95bd7Snicm /* Lock session if it has timed out. */ 2539883b791Snicm static void 254d0e2e7f1Snicm session_lock_timer(__unused int fd, __unused short events, void *arg) 25542b95bd7Snicm { 25642b95bd7Snicm struct session *s = arg; 25742b95bd7Snicm 258647c5c18Snicm if (s->attached == 0) 25942b95bd7Snicm return; 26042b95bd7Snicm 26142b95bd7Snicm log_debug("session %s locked, activity time %lld", s->name, 26242b95bd7Snicm (long long)s->activity_time.tv_sec); 26342b95bd7Snicm 26442b95bd7Snicm server_lock_session(s); 26542b95bd7Snicm recalculate_sizes(); 26642b95bd7Snicm } 26742b95bd7Snicm 26809082c68Snicm /* Update activity time. */ 269d43b0d5fSnicm void 27009082c68Snicm session_update_activity(struct session *s, struct timeval *from) 271d43b0d5fSnicm { 27242b95bd7Snicm struct timeval tv; 27309082c68Snicm 27409082c68Snicm if (from == NULL) 27509082c68Snicm gettimeofday(&s->activity_time, NULL); 27609082c68Snicm else 27709082c68Snicm memcpy(&s->activity_time, from, sizeof s->activity_time); 27842b95bd7Snicm 279482624f4Snicm log_debug("session $%u %s activity %lld.%06d", s->id, 2802fe539a5Snicm s->name, (long long)s->activity_time.tv_sec, 281482624f4Snicm (int)s->activity_time.tv_usec); 282e215b69eSnicm 28342b95bd7Snicm if (evtimer_initialized(&s->lock_timer)) 28442b95bd7Snicm evtimer_del(&s->lock_timer); 28542b95bd7Snicm else 28642b95bd7Snicm evtimer_set(&s->lock_timer, session_lock_timer, s); 28742b95bd7Snicm 288647c5c18Snicm if (s->attached != 0) { 28942b95bd7Snicm timerclear(&tv); 290d89252e5Snicm tv.tv_sec = options_get_number(s->options, "lock-after-time"); 29142b95bd7Snicm if (tv.tv_sec != 0) 29242b95bd7Snicm evtimer_add(&s->lock_timer, &tv); 29342b95bd7Snicm } 294d43b0d5fSnicm } 295d43b0d5fSnicm 296e0e6fb95Snicm /* Find the next usable session. */ 297e0e6fb95Snicm struct session * 298e0e6fb95Snicm session_next_session(struct session *s) 299e0e6fb95Snicm { 300e0e6fb95Snicm struct session *s2; 301e0e6fb95Snicm 30259996dc3Snicm if (RB_EMPTY(&sessions) || !session_alive(s)) 303e0e6fb95Snicm return (NULL); 304e0e6fb95Snicm 3055fa6c6a5Snicm s2 = RB_NEXT(sessions, &sessions, s); 30659996dc3Snicm if (s2 == NULL) 30759996dc3Snicm s2 = RB_MIN(sessions, &sessions); 30859996dc3Snicm if (s2 == s) 30959996dc3Snicm return (NULL); 310e0e6fb95Snicm return (s2); 311e0e6fb95Snicm } 312e0e6fb95Snicm 313e0e6fb95Snicm /* Find the previous usable session. */ 314e0e6fb95Snicm struct session * 315e0e6fb95Snicm session_previous_session(struct session *s) 316e0e6fb95Snicm { 317e0e6fb95Snicm struct session *s2; 318e0e6fb95Snicm 31959996dc3Snicm if (RB_EMPTY(&sessions) || !session_alive(s)) 320e0e6fb95Snicm return (NULL); 321e0e6fb95Snicm 3225fa6c6a5Snicm s2 = RB_PREV(sessions, &sessions, s); 32359996dc3Snicm if (s2 == NULL) 32459996dc3Snicm s2 = RB_MAX(sessions, &sessions); 32559996dc3Snicm if (s2 == s) 32659996dc3Snicm return (NULL); 327e0e6fb95Snicm return (s2); 328e0e6fb95Snicm } 329e0e6fb95Snicm 330311827fbSnicm /* Attach a window to a session. */ 331311827fbSnicm struct winlink * 332311827fbSnicm session_attach(struct session *s, struct window *w, int idx, char **cause) 333311827fbSnicm { 334311827fbSnicm struct winlink *wl; 335311827fbSnicm 33613f42f0aSnicm if ((wl = winlink_add(&s->windows, idx)) == NULL) { 337311827fbSnicm xasprintf(cause, "index in use: %d", idx); 33813f42f0aSnicm return (NULL); 33913f42f0aSnicm } 340de5a0fddSnicm wl->session = s; 34113f42f0aSnicm winlink_set_window(wl, w); 3422ae124feSnicm notify_session_window("window-linked", s, w); 34313f42f0aSnicm 34401b2421eSnicm session_group_synchronize_from(s); 345311827fbSnicm return (wl); 346311827fbSnicm } 347311827fbSnicm 348311827fbSnicm /* Detach a window from a session. */ 349311827fbSnicm int 350311827fbSnicm session_detach(struct session *s, struct winlink *wl) 351311827fbSnicm { 352311827fbSnicm if (s->curw == wl && 35383f26c8fSnicm session_last(s) != 0 && 35483f26c8fSnicm session_previous(s, 0) != 0) 355311827fbSnicm session_next(s, 0); 356311827fbSnicm 3574336fb18Snicm wl->flags &= ~WINLINK_ALERTFLAGS; 3582ae124feSnicm notify_session_window("window-unlinked", s, wl->window); 359311827fbSnicm winlink_stack_remove(&s->lastw, wl); 360311827fbSnicm winlink_remove(&s->windows, wl); 36183f26c8fSnicm 36201b2421eSnicm session_group_synchronize_from(s); 36383f26c8fSnicm 3646a041561Snicm if (RB_EMPTY(&s->windows)) 365311827fbSnicm return (1); 366311827fbSnicm return (0); 367311827fbSnicm } 368311827fbSnicm 369311827fbSnicm /* Return if session has window. */ 37052e9404eSnicm int 371311827fbSnicm session_has(struct session *s, struct window *w) 372311827fbSnicm { 373311827fbSnicm struct winlink *wl; 374311827fbSnicm 375de5a0fddSnicm TAILQ_FOREACH(wl, &w->winlinks, wentry) { 376de5a0fddSnicm if (wl->session == s) 37752e9404eSnicm return (1); 378311827fbSnicm } 37952e9404eSnicm return (0); 380311827fbSnicm } 381311827fbSnicm 382c8f8381cSnicm /* 383c8f8381cSnicm * Return 1 if a window is linked outside this session (not including session 384c8f8381cSnicm * groups). The window must be in this session! 385c8f8381cSnicm */ 386c8f8381cSnicm int 387c8f8381cSnicm session_is_linked(struct session *s, struct window *w) 388c8f8381cSnicm { 389c8f8381cSnicm struct session_group *sg; 390c8f8381cSnicm 39106440b28Snicm if ((sg = session_group_contains(s)) != NULL) 392c8f8381cSnicm return (w->references != session_group_count(sg)); 393c8f8381cSnicm return (w->references != 1); 394c8f8381cSnicm } 395c8f8381cSnicm 3969883b791Snicm static struct winlink * 3974336fb18Snicm session_next_alert(struct winlink *wl) 398311827fbSnicm { 399311827fbSnicm while (wl != NULL) { 4004336fb18Snicm if (wl->flags & WINLINK_ALERTFLAGS) 401311827fbSnicm break; 4025c8958bdSnicm wl = winlink_next(wl); 403311827fbSnicm } 404311827fbSnicm return (wl); 405311827fbSnicm } 406311827fbSnicm 407311827fbSnicm /* Move session to next window. */ 408311827fbSnicm int 40968c9d207Snicm session_next(struct session *s, int alert) 410311827fbSnicm { 411311827fbSnicm struct winlink *wl; 412311827fbSnicm 413311827fbSnicm if (s->curw == NULL) 414311827fbSnicm return (-1); 415311827fbSnicm 4165c8958bdSnicm wl = winlink_next(s->curw); 41768c9d207Snicm if (alert) 4184336fb18Snicm wl = session_next_alert(wl); 419311827fbSnicm if (wl == NULL) { 420311827fbSnicm wl = RB_MIN(winlinks, &s->windows); 4214336fb18Snicm if (alert && ((wl = session_next_alert(wl)) == NULL)) 422311827fbSnicm return (-1); 423311827fbSnicm } 4249627e48cSnicm return (session_set_current(s, wl)); 425311827fbSnicm } 426311827fbSnicm 4279883b791Snicm static struct winlink * 4284336fb18Snicm session_previous_alert(struct winlink *wl) 429311827fbSnicm { 430311827fbSnicm while (wl != NULL) { 4314336fb18Snicm if (wl->flags & WINLINK_ALERTFLAGS) 432311827fbSnicm break; 4335c8958bdSnicm wl = winlink_previous(wl); 434311827fbSnicm } 435311827fbSnicm return (wl); 436311827fbSnicm } 437311827fbSnicm 438311827fbSnicm /* Move session to previous window. */ 439311827fbSnicm int 44068c9d207Snicm session_previous(struct session *s, int alert) 441311827fbSnicm { 442311827fbSnicm struct winlink *wl; 443311827fbSnicm 444311827fbSnicm if (s->curw == NULL) 445311827fbSnicm return (-1); 446311827fbSnicm 4475c8958bdSnicm wl = winlink_previous(s->curw); 44868c9d207Snicm if (alert) 4494336fb18Snicm wl = session_previous_alert(wl); 450311827fbSnicm if (wl == NULL) { 451311827fbSnicm wl = RB_MAX(winlinks, &s->windows); 4524336fb18Snicm if (alert && (wl = session_previous_alert(wl)) == NULL) 453311827fbSnicm return (-1); 454311827fbSnicm } 4559627e48cSnicm return (session_set_current(s, wl)); 456311827fbSnicm } 457311827fbSnicm 458311827fbSnicm /* Move session to specific window. */ 459311827fbSnicm int 460311827fbSnicm session_select(struct session *s, int idx) 461311827fbSnicm { 462311827fbSnicm struct winlink *wl; 463311827fbSnicm 464311827fbSnicm wl = winlink_find_by_index(&s->windows, idx); 4659627e48cSnicm return (session_set_current(s, wl)); 466311827fbSnicm } 467311827fbSnicm 468311827fbSnicm /* Move session to last used window. */ 469311827fbSnicm int 470311827fbSnicm session_last(struct session *s) 471311827fbSnicm { 472311827fbSnicm struct winlink *wl; 473311827fbSnicm 47401b2421eSnicm wl = TAILQ_FIRST(&s->lastw); 475311827fbSnicm if (wl == NULL) 476311827fbSnicm return (-1); 477311827fbSnicm if (wl == s->curw) 478311827fbSnicm return (1); 479311827fbSnicm 4809627e48cSnicm return (session_set_current(s, wl)); 4819627e48cSnicm } 4829627e48cSnicm 4839627e48cSnicm /* Set current winlink to wl .*/ 4849627e48cSnicm int 4859627e48cSnicm session_set_current(struct session *s, struct winlink *wl) 4869627e48cSnicm { 4871a773291Snicm struct winlink *old = s->curw; 4881a773291Snicm 4899627e48cSnicm if (wl == NULL) 4909627e48cSnicm return (-1); 4919627e48cSnicm if (wl == s->curw) 4929627e48cSnicm return (1); 4939627e48cSnicm 494311827fbSnicm winlink_stack_remove(&s->lastw, wl); 495311827fbSnicm winlink_stack_push(&s->lastw, s->curw); 496311827fbSnicm s->curw = wl; 4971a773291Snicm if (options_get_number(global_options, "focus-events")) { 498728c8ef2Snicm if (old != NULL) 4991a773291Snicm window_update_focus(old->window); 5001a773291Snicm window_update_focus(wl->window); 5011a773291Snicm } 50220ef07adSnicm winlink_clear_flags(wl); 50381fe4598Snicm window_update_activity(wl->window); 5047b470e93Snicm tty_update_window_offset(wl->window); 5053c14ce20Snicm notify_session("session-window-changed", s); 506311827fbSnicm return (0); 507311827fbSnicm } 50801b2421eSnicm 50901b2421eSnicm /* Find the session group containing a session. */ 51001b2421eSnicm struct session_group * 51106440b28Snicm session_group_contains(struct session *target) 51201b2421eSnicm { 51301b2421eSnicm struct session_group *sg; 51401b2421eSnicm struct session *s; 51501b2421eSnicm 51606440b28Snicm RB_FOREACH(sg, session_groups, &session_groups) { 51701b2421eSnicm TAILQ_FOREACH(s, &sg->sessions, gentry) { 51801b2421eSnicm if (s == target) 51901b2421eSnicm return (sg); 52001b2421eSnicm } 52101b2421eSnicm } 52201b2421eSnicm return (NULL); 52301b2421eSnicm } 52401b2421eSnicm 52506440b28Snicm /* Find session group by name. */ 52606440b28Snicm struct session_group * 52706440b28Snicm session_group_find(const char *name) 52801b2421eSnicm { 52906440b28Snicm struct session_group sg; 53001b2421eSnicm 53106440b28Snicm sg.name = name; 53206440b28Snicm return (RB_FIND(session_groups, &session_groups, &sg)); 53301b2421eSnicm } 53401b2421eSnicm 53506440b28Snicm /* Create a new session group. */ 53606440b28Snicm struct session_group * 53706440b28Snicm session_group_new(const char *name) 53801b2421eSnicm { 53901b2421eSnicm struct session_group *sg; 54001b2421eSnicm 54106440b28Snicm if ((sg = session_group_find(name)) != NULL) 54206440b28Snicm return (sg); 54306440b28Snicm 54406440b28Snicm sg = xcalloc(1, sizeof *sg); 54506440b28Snicm sg->name = xstrdup(name); 54601b2421eSnicm TAILQ_INIT(&sg->sessions); 54706440b28Snicm 54806440b28Snicm RB_INSERT(session_groups, &session_groups, sg); 54906440b28Snicm return (sg); 55001b2421eSnicm } 55106440b28Snicm 55206440b28Snicm /* Add a session to a session group. */ 55306440b28Snicm void 55406440b28Snicm session_group_add(struct session_group *sg, struct session *s) 55506440b28Snicm { 55606440b28Snicm if (session_group_contains(s) == NULL) 55701b2421eSnicm TAILQ_INSERT_TAIL(&sg->sessions, s, gentry); 55801b2421eSnicm } 55901b2421eSnicm 56001b2421eSnicm /* Remove a session from its group and destroy the group if empty. */ 561ced21769Snicm static void 56201b2421eSnicm session_group_remove(struct session *s) 56301b2421eSnicm { 56401b2421eSnicm struct session_group *sg; 56501b2421eSnicm 56606440b28Snicm if ((sg = session_group_contains(s)) == NULL) 56701b2421eSnicm return; 56801b2421eSnicm TAILQ_REMOVE(&sg->sessions, s, gentry); 56901b2421eSnicm if (TAILQ_EMPTY(&sg->sessions)) { 57006440b28Snicm RB_REMOVE(session_groups, &session_groups, sg); 571c27246d4Snicm free((void *)sg->name); 5727d053cf9Snicm free(sg); 57301b2421eSnicm } 57401b2421eSnicm } 57501b2421eSnicm 57656ecd093Snicm /* Count number of sessions in session group. */ 577988c4c87Snicm u_int 57856ecd093Snicm session_group_count(struct session_group *sg) 57956ecd093Snicm { 58056ecd093Snicm struct session *s; 58156ecd093Snicm u_int n; 58256ecd093Snicm 58356ecd093Snicm n = 0; 58456ecd093Snicm TAILQ_FOREACH(s, &sg->sessions, gentry) 58556ecd093Snicm n++; 58656ecd093Snicm return (n); 58756ecd093Snicm } 58856ecd093Snicm 589e0f10a33Snicm /* Count number of clients attached to sessions in session group. */ 590e0f10a33Snicm u_int 591e0f10a33Snicm session_group_attached_count(struct session_group *sg) 592e0f10a33Snicm { 593e0f10a33Snicm struct session *s; 594e0f10a33Snicm u_int n; 595e0f10a33Snicm 596e0f10a33Snicm n = 0; 597e0f10a33Snicm TAILQ_FOREACH(s, &sg->sessions, gentry) 598e0f10a33Snicm n += s->attached; 599e0f10a33Snicm return (n); 600e0f10a33Snicm } 601e0f10a33Snicm 60201b2421eSnicm /* Synchronize a session to its session group. */ 60301b2421eSnicm void 60401b2421eSnicm session_group_synchronize_to(struct session *s) 60501b2421eSnicm { 60601b2421eSnicm struct session_group *sg; 60701b2421eSnicm struct session *target; 60801b2421eSnicm 60906440b28Snicm if ((sg = session_group_contains(s)) == NULL) 61001b2421eSnicm return; 61101b2421eSnicm 61201b2421eSnicm target = NULL; 61301b2421eSnicm TAILQ_FOREACH(target, &sg->sessions, gentry) { 61401b2421eSnicm if (target != s) 61501b2421eSnicm break; 61601b2421eSnicm } 61706440b28Snicm if (target != NULL) 61801b2421eSnicm session_group_synchronize1(target, s); 61901b2421eSnicm } 62001b2421eSnicm 62101b2421eSnicm /* Synchronize a session group to a session. */ 62201b2421eSnicm void 62301b2421eSnicm session_group_synchronize_from(struct session *target) 62401b2421eSnicm { 62501b2421eSnicm struct session_group *sg; 62601b2421eSnicm struct session *s; 62701b2421eSnicm 62806440b28Snicm if ((sg = session_group_contains(target)) == NULL) 62901b2421eSnicm return; 63001b2421eSnicm 63101b2421eSnicm TAILQ_FOREACH(s, &sg->sessions, gentry) { 63201b2421eSnicm if (s != target) 63301b2421eSnicm session_group_synchronize1(target, s); 63401b2421eSnicm } 63501b2421eSnicm } 63601b2421eSnicm 63701b2421eSnicm /* 63801b2421eSnicm * Synchronize a session with a target session. This means destroying all 63901b2421eSnicm * winlinks then recreating them, then updating the current window, last window 64001b2421eSnicm * stack and alerts. 64101b2421eSnicm */ 642ced21769Snicm static void 64301b2421eSnicm session_group_synchronize1(struct session *target, struct session *s) 64401b2421eSnicm { 64501b2421eSnicm struct winlinks old_windows, *ww; 64601b2421eSnicm struct winlink_stack old_lastw; 64701b2421eSnicm struct winlink *wl, *wl2; 64801b2421eSnicm 64901b2421eSnicm /* Don't do anything if the session is empty (it'll be destroyed). */ 65001b2421eSnicm ww = &target->windows; 65101b2421eSnicm if (RB_EMPTY(ww)) 65201b2421eSnicm return; 65301b2421eSnicm 65401b2421eSnicm /* If the current window has vanished, move to the next now. */ 655d2c17161Snicm if (s->curw != NULL && 656d2c17161Snicm winlink_find_by_index(ww, s->curw->idx) == NULL && 657d2c17161Snicm session_last(s) != 0 && session_previous(s, 0) != 0) 65801b2421eSnicm session_next(s, 0); 65901b2421eSnicm 66001b2421eSnicm /* Save the old pointer and reset it. */ 66101b2421eSnicm memcpy(&old_windows, &s->windows, sizeof old_windows); 66201b2421eSnicm RB_INIT(&s->windows); 66301b2421eSnicm 66401b2421eSnicm /* Link all the windows from the target. */ 6654336fb18Snicm RB_FOREACH(wl, winlinks, ww) { 66613f42f0aSnicm wl2 = winlink_add(&s->windows, wl->idx); 667de5a0fddSnicm wl2->session = s; 66813f42f0aSnicm winlink_set_window(wl2, wl->window); 6692ae124feSnicm notify_session_window("window-linked", s, wl2->window); 6704336fb18Snicm wl2->flags |= wl->flags & WINLINK_ALERTFLAGS; 6714336fb18Snicm } 67201b2421eSnicm 67301b2421eSnicm /* Fix up the current window. */ 67401b2421eSnicm if (s->curw != NULL) 67501b2421eSnicm s->curw = winlink_find_by_index(&s->windows, s->curw->idx); 67601b2421eSnicm else 67701b2421eSnicm s->curw = winlink_find_by_index(&s->windows, target->curw->idx); 67801b2421eSnicm 67901b2421eSnicm /* Fix up the last window stack. */ 68001b2421eSnicm memcpy(&old_lastw, &s->lastw, sizeof old_lastw); 68101b2421eSnicm TAILQ_INIT(&s->lastw); 68201b2421eSnicm TAILQ_FOREACH(wl, &old_lastw, sentry) { 68301b2421eSnicm wl2 = winlink_find_by_index(&s->windows, wl->idx); 6848f273ebaSnicm if (wl2 != NULL) { 68501b2421eSnicm TAILQ_INSERT_TAIL(&s->lastw, wl2, sentry); 6868f273ebaSnicm wl2->flags |= WINLINK_VISITED; 6878f273ebaSnicm } 68801b2421eSnicm } 68901b2421eSnicm 69001b2421eSnicm /* Then free the old winlinks list. */ 69101b2421eSnicm while (!RB_EMPTY(&old_windows)) { 69201b2421eSnicm wl = RB_ROOT(&old_windows); 69356ecd093Snicm wl2 = winlink_find_by_window_id(&s->windows, wl->window->id); 69456ecd093Snicm if (wl2 == NULL) 6952ae124feSnicm notify_session_window("window-unlinked", s, wl->window); 6962f59442cSnicm winlink_remove(&old_windows, wl); 69701b2421eSnicm } 69801b2421eSnicm } 69953c15224Snicm 70053c15224Snicm /* Renumber the windows across winlinks attached to a specific session. */ 70153c15224Snicm void 70253c15224Snicm session_renumber_windows(struct session *s) 70353c15224Snicm { 70453c15224Snicm struct winlink *wl, *wl1, *wl_new; 70553c15224Snicm struct winlinks old_wins; 70653c15224Snicm struct winlink_stack old_lastw; 70765b39cd1Snicm int new_idx, new_curw_idx, marked_idx = -1; 70853c15224Snicm 70953c15224Snicm /* Save and replace old window list. */ 71053c15224Snicm memcpy(&old_wins, &s->windows, sizeof old_wins); 71153c15224Snicm RB_INIT(&s->windows); 71253c15224Snicm 71353c15224Snicm /* Start renumbering from the base-index if it's set. */ 714d89252e5Snicm new_idx = options_get_number(s->options, "base-index"); 71553c15224Snicm new_curw_idx = 0; 71653c15224Snicm 71753c15224Snicm /* Go through the winlinks and assign new indexes. */ 71853c15224Snicm RB_FOREACH(wl, winlinks, &old_wins) { 71953c15224Snicm wl_new = winlink_add(&s->windows, new_idx); 720de5a0fddSnicm wl_new->session = s; 72153c15224Snicm winlink_set_window(wl_new, wl->window); 72253c15224Snicm wl_new->flags |= wl->flags & WINLINK_ALERTFLAGS; 72353c15224Snicm 72465b39cd1Snicm if (wl == marked_pane.wl) 72565b39cd1Snicm marked_idx = wl_new->idx; 72653c15224Snicm if (wl == s->curw) 72753c15224Snicm new_curw_idx = wl_new->idx; 72853c15224Snicm 72953c15224Snicm new_idx++; 73053c15224Snicm } 73153c15224Snicm 73253c15224Snicm /* Fix the stack of last windows now. */ 73353c15224Snicm memcpy(&old_lastw, &s->lastw, sizeof old_lastw); 73453c15224Snicm TAILQ_INIT(&s->lastw); 73553c15224Snicm TAILQ_FOREACH(wl, &old_lastw, sentry) { 736bdc25a05Snicm wl->flags &= ~WINLINK_VISITED; 73718d0989cSnicm wl_new = winlink_find_by_window(&s->windows, wl->window); 738bdc25a05Snicm if (wl_new != NULL) { 73953c15224Snicm TAILQ_INSERT_TAIL(&s->lastw, wl_new, sentry); 740bdc25a05Snicm wl_new->flags |= WINLINK_VISITED; 741bdc25a05Snicm } 74253c15224Snicm } 74353c15224Snicm 74453c15224Snicm /* Set the current window. */ 74565b39cd1Snicm if (marked_idx != -1) { 74665b39cd1Snicm marked_pane.wl = winlink_find_by_index(&s->windows, marked_idx); 74765b39cd1Snicm if (marked_pane.wl == NULL) 74865b39cd1Snicm server_clear_marked(); 74965b39cd1Snicm } 75053c15224Snicm s->curw = winlink_find_by_index(&s->windows, new_curw_idx); 75153c15224Snicm 75253c15224Snicm /* Free the old winlinks (reducing window references too). */ 75353c15224Snicm RB_FOREACH_SAFE(wl, winlinks, &old_wins, wl1) 75453c15224Snicm winlink_remove(&old_wins, wl); 75553c15224Snicm } 756