4 * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
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.
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.
19 #include <sys/types.h>
29 /* Global session list. */
30 struct sessions sessions
;
31 struct sessions dead_sessions
;
32 u_int next_session_id
;
33 struct session_groups session_groups
;
35 struct winlink
*session_next_alert(struct winlink
*);
36 struct winlink
*session_previous_alert(struct winlink
*);
38 RB_GENERATE(sessions
, session
, entry
, session_cmp
);
41 session_cmp(struct session
*s1
, struct session
*s2
)
43 return (strcmp(s1
->name
, s2
->name
));
47 * Find if session is still alive. This is true if it is still on the global
51 session_alive(struct session
*s
)
53 struct session
*s_loop
;
55 RB_FOREACH(s_loop
, sessions
, &sessions
) {
62 /* Find session by name. */
64 session_find(const char *name
)
68 s
.name
= __UNCONST(name
);
69 return (RB_FIND(sessions
, &sessions
, &s
));
72 /* Find session by id. */
74 session_find_by_id(u_int id
)
78 RB_FOREACH(s
, sessions
, &sessions
) {
85 /* Create a new session. */
87 session_create(const char *name
, const char *cmd
, int cwd
, struct environ
*env
,
88 struct termios
*tio
, int idx
, u_int sx
, u_int sy
, char **cause
)
92 s
= xmalloc(sizeof *s
);
96 if (gettimeofday(&s
->creation_time
, NULL
) != 0)
97 fatal("gettimeofday failed");
98 session_update_activity(s
);
103 TAILQ_INIT(&s
->lastw
);
104 RB_INIT(&s
->windows
);
106 options_init(&s
->options
, &global_s_options
);
107 environ_init(&s
->environ
);
109 environ_copy(env
, &s
->environ
);
113 s
->tio
= xmalloc(sizeof *s
->tio
);
114 memcpy(s
->tio
, tio
, sizeof *s
->tio
);
121 s
->name
= xstrdup(name
);
122 s
->id
= next_session_id
++;
126 s
->id
= next_session_id
++;
128 xasprintf(&s
->name
, "%u", s
->id
);
129 } while (RB_FIND(sessions
, &sessions
, s
) != NULL
);
131 RB_INSERT(sessions
, &sessions
, s
);
134 if (session_new(s
, NULL
, cmd
, cwd
, idx
, cause
) == NULL
) {
138 session_select(s
, RB_ROOT(&s
->windows
)->idx
);
141 log_debug("session %s created", s
->name
);
142 notify_session_created(s
);
147 /* Destroy a session. */
149 session_destroy(struct session
*s
)
153 log_debug("session %s destroyed", s
->name
);
155 RB_REMOVE(sessions
, &sessions
, s
);
156 notify_session_closed(s
);
160 session_group_remove(s
);
161 environ_free(&s
->environ
);
162 options_free(&s
->options
);
164 while (!TAILQ_EMPTY(&s
->lastw
))
165 winlink_stack_remove(&s
->lastw
, TAILQ_FIRST(&s
->lastw
));
166 while (!RB_EMPTY(&s
->windows
)) {
167 wl
= RB_ROOT(&s
->windows
);
168 notify_window_unlinked(s
, wl
->window
);
169 winlink_remove(&s
->windows
, wl
);
174 RB_INSERT(sessions
, &dead_sessions
, s
);
177 /* Check a session name is valid: not empty and no colons or periods. */
179 session_check_name(const char *name
)
181 return (*name
!= '\0' && name
[strcspn(name
, ":.")] == '\0');
184 /* Update session active time. */
186 session_update_activity(struct session
*s
)
188 if (gettimeofday(&s
->activity_time
, NULL
) != 0)
189 fatal("gettimeofday");
192 /* Find the next usable session. */
194 session_next_session(struct session
*s
)
198 if (RB_EMPTY(&sessions
) || !session_alive(s
))
201 s2
= RB_NEXT(sessions
, &sessions
, s
);
203 s2
= RB_MIN(sessions
, &sessions
);
209 /* Find the previous usable session. */
211 session_previous_session(struct session
*s
)
215 if (RB_EMPTY(&sessions
) || !session_alive(s
))
218 s2
= RB_PREV(sessions
, &sessions
, s
);
220 s2
= RB_MAX(sessions
, &sessions
);
226 /* Create a new window on a session. */
228 session_new(struct session
*s
, const char *name
, const char *cmd
, int cwd
,
229 int idx
, char **cause
)
237 if ((wl
= winlink_add(&s
->windows
, idx
)) == NULL
) {
238 xasprintf(cause
, "index in use: %d", idx
);
243 environ_copy(&global_environ
, &env
);
244 environ_copy(&s
->environ
, &env
);
245 server_fill_environ(s
, &env
);
247 shell
= options_get_string(&s
->options
, "default-shell");
248 if (*shell
== '\0' || areshell(shell
))
249 shell
= _PATH_BSHELL
;
251 hlimit
= options_get_number(&s
->options
, "history-limit");
252 w
= window_create(name
, cmd
, shell
, cwd
, &env
, s
->tio
, s
->sx
, s
->sy
,
255 winlink_remove(&s
->windows
, wl
);
259 winlink_set_window(wl
, w
);
260 notify_window_linked(s
, w
);
263 if (options_get_number(&s
->options
, "set-remain-on-exit"))
264 options_set_number(&w
->options
, "remain-on-exit", 1);
266 session_group_synchronize_from(s
);
270 /* Attach a window to a session. */
272 session_attach(struct session
*s
, struct window
*w
, int idx
, char **cause
)
276 if ((wl
= winlink_add(&s
->windows
, idx
)) == NULL
) {
277 xasprintf(cause
, "index in use: %d", idx
);
280 winlink_set_window(wl
, w
);
281 notify_window_linked(s
, w
);
283 session_group_synchronize_from(s
);
287 /* Detach a window from a session. */
289 session_detach(struct session
*s
, struct winlink
*wl
)
292 session_last(s
) != 0 && session_previous(s
, 0) != 0)
295 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
296 notify_window_unlinked(s
, wl
->window
);
297 winlink_stack_remove(&s
->lastw
, wl
);
298 winlink_remove(&s
->windows
, wl
);
299 session_group_synchronize_from(s
);
300 if (RB_EMPTY(&s
->windows
)) {
307 /* Return if session has window. */
309 session_has(struct session
*s
, struct window
*w
)
313 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
321 session_next_alert(struct winlink
*wl
)
324 if (wl
->flags
& WINLINK_ALERTFLAGS
)
326 wl
= winlink_next(wl
);
331 /* Move session to next window. */
333 session_next(struct session
*s
, int alert
)
340 wl
= winlink_next(s
->curw
);
342 wl
= session_next_alert(wl
);
344 wl
= RB_MIN(winlinks
, &s
->windows
);
345 if (alert
&& ((wl
= session_next_alert(wl
)) == NULL
))
348 return (session_set_current(s
, wl
));
352 session_previous_alert(struct winlink
*wl
)
355 if (wl
->flags
& WINLINK_ALERTFLAGS
)
357 wl
= winlink_previous(wl
);
362 /* Move session to previous window. */
364 session_previous(struct session
*s
, int alert
)
371 wl
= winlink_previous(s
->curw
);
373 wl
= session_previous_alert(wl
);
375 wl
= RB_MAX(winlinks
, &s
->windows
);
376 if (alert
&& (wl
= session_previous_alert(wl
)) == NULL
)
379 return (session_set_current(s
, wl
));
382 /* Move session to specific window. */
384 session_select(struct session
*s
, int idx
)
388 wl
= winlink_find_by_index(&s
->windows
, idx
);
389 return (session_set_current(s
, wl
));
392 /* Move session to last used window. */
394 session_last(struct session
*s
)
398 wl
= TAILQ_FIRST(&s
->lastw
);
404 return (session_set_current(s
, wl
));
407 /* Set current winlink to wl .*/
409 session_set_current(struct session
*s
, struct winlink
*wl
)
416 winlink_stack_remove(&s
->lastw
, wl
);
417 winlink_stack_push(&s
->lastw
, s
->curw
);
419 winlink_clear_flags(wl
);
423 /* Find the session group containing a session. */
424 struct session_group
*
425 session_group_find(struct session
*target
)
427 struct session_group
*sg
;
430 TAILQ_FOREACH(sg
, &session_groups
, entry
) {
431 TAILQ_FOREACH(s
, &sg
->sessions
, gentry
) {
439 /* Find session group index. */
441 session_group_index(struct session_group
*sg
)
443 struct session_group
*sg2
;
447 TAILQ_FOREACH(sg2
, &session_groups
, entry
) {
453 fatalx("session group not found");
457 * Add a session to the session group containing target, creating it if
461 session_group_add(struct session
*target
, struct session
*s
)
463 struct session_group
*sg
;
465 if ((sg
= session_group_find(target
)) == NULL
) {
466 sg
= xmalloc(sizeof *sg
);
467 TAILQ_INSERT_TAIL(&session_groups
, sg
, entry
);
468 TAILQ_INIT(&sg
->sessions
);
469 TAILQ_INSERT_TAIL(&sg
->sessions
, target
, gentry
);
471 TAILQ_INSERT_TAIL(&sg
->sessions
, s
, gentry
);
474 /* Remove a session from its group and destroy the group if empty. */
476 session_group_remove(struct session
*s
)
478 struct session_group
*sg
;
480 if ((sg
= session_group_find(s
)) == NULL
)
482 TAILQ_REMOVE(&sg
->sessions
, s
, gentry
);
483 if (TAILQ_NEXT(TAILQ_FIRST(&sg
->sessions
), gentry
) == NULL
)
484 TAILQ_REMOVE(&sg
->sessions
, TAILQ_FIRST(&sg
->sessions
), gentry
);
485 if (TAILQ_EMPTY(&sg
->sessions
)) {
486 TAILQ_REMOVE(&session_groups
, sg
, entry
);
491 /* Synchronize a session to its session group. */
493 session_group_synchronize_to(struct session
*s
)
495 struct session_group
*sg
;
496 struct session
*target
;
498 if ((sg
= session_group_find(s
)) == NULL
)
502 TAILQ_FOREACH(target
, &sg
->sessions
, gentry
) {
506 session_group_synchronize1(target
, s
);
509 /* Synchronize a session group to a session. */
511 session_group_synchronize_from(struct session
*target
)
513 struct session_group
*sg
;
516 if ((sg
= session_group_find(target
)) == NULL
)
519 TAILQ_FOREACH(s
, &sg
->sessions
, gentry
) {
521 session_group_synchronize1(target
, s
);
526 * Synchronize a session with a target session. This means destroying all
527 * winlinks then recreating them, then updating the current window, last window
531 session_group_synchronize1(struct session
*target
, struct session
*s
)
533 struct winlinks old_windows
, *ww
;
534 struct winlink_stack old_lastw
;
535 struct winlink
*wl
, *wl2
;
537 /* Don't do anything if the session is empty (it'll be destroyed). */
538 ww
= &target
->windows
;
542 /* If the current window has vanished, move to the next now. */
543 if (s
->curw
!= NULL
&&
544 winlink_find_by_index(ww
, s
->curw
->idx
) == NULL
&&
545 session_last(s
) != 0 && session_previous(s
, 0) != 0)
548 /* Save the old pointer and reset it. */
549 memcpy(&old_windows
, &s
->windows
, sizeof old_windows
);
550 RB_INIT(&s
->windows
);
552 /* Link all the windows from the target. */
553 RB_FOREACH(wl
, winlinks
, ww
) {
554 wl2
= winlink_add(&s
->windows
, wl
->idx
);
555 winlink_set_window(wl2
, wl
->window
);
556 notify_window_linked(s
, wl2
->window
);
557 wl2
->flags
|= wl
->flags
& WINLINK_ALERTFLAGS
;
560 /* Fix up the current window. */
562 s
->curw
= winlink_find_by_index(&s
->windows
, s
->curw
->idx
);
564 s
->curw
= winlink_find_by_index(&s
->windows
, target
->curw
->idx
);
566 /* Fix up the last window stack. */
567 memcpy(&old_lastw
, &s
->lastw
, sizeof old_lastw
);
568 TAILQ_INIT(&s
->lastw
);
569 TAILQ_FOREACH(wl
, &old_lastw
, sentry
) {
570 wl2
= winlink_find_by_index(&s
->windows
, wl
->idx
);
572 TAILQ_INSERT_TAIL(&s
->lastw
, wl2
, sentry
);
575 /* Then free the old winlinks list. */
576 while (!RB_EMPTY(&old_windows
)) {
577 wl
= RB_ROOT(&old_windows
);
578 if (winlink_find_by_window_id(&s
->windows
, wl
->window
->id
) == NULL
)
579 notify_window_unlinked(s
, wl
->window
);
580 winlink_remove(&old_windows
, wl
);
584 /* Renumber the windows across winlinks attached to a specific session. */
586 session_renumber_windows(struct session
*s
)
588 struct winlink
*wl
, *wl1
, *wl_new
;
589 struct winlinks old_wins
;
590 struct winlink_stack old_lastw
;
591 int new_idx
, new_curw_idx
;
593 /* Save and replace old window list. */
594 memcpy(&old_wins
, &s
->windows
, sizeof old_wins
);
595 RB_INIT(&s
->windows
);
597 /* Start renumbering from the base-index if it's set. */
598 new_idx
= options_get_number(&s
->options
, "base-index");
601 /* Go through the winlinks and assign new indexes. */
602 RB_FOREACH(wl
, winlinks
, &old_wins
) {
603 wl_new
= winlink_add(&s
->windows
, new_idx
);
604 winlink_set_window(wl_new
, wl
->window
);
605 wl_new
->flags
|= wl
->flags
& WINLINK_ALERTFLAGS
;
608 new_curw_idx
= wl_new
->idx
;
613 /* Fix the stack of last windows now. */
614 memcpy(&old_lastw
, &s
->lastw
, sizeof old_lastw
);
615 TAILQ_INIT(&s
->lastw
);
616 TAILQ_FOREACH(wl
, &old_lastw
, sentry
) {
617 wl_new
= winlink_find_by_window(&s
->windows
, wl
->window
);
619 TAILQ_INSERT_TAIL(&s
->lastw
, wl_new
, sentry
);
622 /* Set the current window. */
623 s
->curw
= winlink_find_by_index(&s
->windows
, new_curw_idx
);
625 /* Free the old winlinks (reducing window references too). */
626 RB_FOREACH_SAFE(wl
, winlinks
, &old_wins
, wl1
)
627 winlink_remove(&old_wins
, wl
);