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>
30 /* Global session list. */
31 struct sessions sessions
;
32 struct sessions dead_sessions
;
34 struct session_groups session_groups
;
36 struct winlink
*session_next_alert(struct winlink
*);
37 struct winlink
*session_previous_alert(struct winlink
*);
39 RB_GENERATE(sessions
, session
, entry
, session_cmp
);
42 session_cmp(struct session
*s1
, struct session
*s2
)
44 return (strcmp(s1
->name
, s2
->name
));
48 * Find if session is still alive. This is true if it is still on the global
52 session_alive(struct session
*s
)
54 struct session
*s_loop
;
56 RB_FOREACH(s_loop
, sessions
, &sessions
) {
63 /* Find session by name. */
65 session_find(const char *name
)
69 s
.name
= (char *) name
;
70 return (RB_FIND(sessions
, &sessions
, &s
));
73 /* Find session by index. */
75 session_find_by_index(u_int idx
)
79 RB_FOREACH(s
, sessions
, &sessions
) {
86 /* Create a new session. */
88 session_create(const char *name
, const char *cmd
, const char *cwd
,
89 struct environ
*env
, struct termios
*tio
, int idx
, u_int sx
, u_int sy
,
94 s
= xmalloc(sizeof *s
);
98 if (gettimeofday(&s
->creation_time
, NULL
) != 0)
99 fatal("gettimeofday failed");
100 session_update_activity(s
);
102 s
->cwd
= xstrdup(cwd
);
105 TAILQ_INIT(&s
->lastw
);
106 RB_INIT(&s
->windows
);
108 options_init(&s
->options
, &global_s_options
);
109 environ_init(&s
->environ
);
111 environ_copy(env
, &s
->environ
);
115 s
->tio
= xmalloc(sizeof *s
->tio
);
116 memcpy(s
->tio
, tio
, sizeof *s
->tio
);
122 s
->idx
= next_session
++;
124 s
->name
= xstrdup(name
);
126 xasprintf(&s
->name
, "%u", s
->idx
);
127 RB_INSERT(sessions
, &sessions
, s
);
130 if (session_new(s
, NULL
, cmd
, cwd
, idx
, cause
) == NULL
) {
134 session_select(s
, RB_ROOT(&s
->windows
)->idx
);
137 log_debug("session %s created", s
->name
);
142 /* Destroy a session. */
144 session_destroy(struct session
*s
)
146 log_debug("session %s destroyed", s
->name
);
148 RB_REMOVE(sessions
, &sessions
, s
);
153 session_group_remove(s
);
154 environ_free(&s
->environ
);
155 options_free(&s
->options
);
157 while (!TAILQ_EMPTY(&s
->lastw
))
158 winlink_stack_remove(&s
->lastw
, TAILQ_FIRST(&s
->lastw
));
159 while (!RB_EMPTY(&s
->windows
))
160 winlink_remove(&s
->windows
, RB_ROOT(&s
->windows
));
164 RB_INSERT(sessions
, &dead_sessions
, s
);
167 /* Check a session name is valid: not empty and no colons. */
169 session_check_name(const char *name
)
171 return (*name
!= '\0' && strchr(name
, ':') == NULL
);
174 /* Update session active time. */
176 session_update_activity(struct session
*s
)
178 if (gettimeofday(&s
->activity_time
, NULL
) != 0)
179 fatal("gettimeofday");
182 /* Find the next usable session. */
184 session_next_session(struct session
*s
)
188 if (RB_EMPTY(&sessions
) || !session_alive(s
))
191 s2
= RB_NEXT(sessions
, &sessions
, s
);
193 s2
= RB_MIN(sessions
, &sessions
);
199 /* Find the previous usable session. */
201 session_previous_session(struct session
*s
)
205 if (RB_EMPTY(&sessions
) || !session_alive(s
))
208 s2
= RB_PREV(sessions
, &sessions
, s
);
210 s2
= RB_MAX(sessions
, &sessions
);
216 /* Create a new window on a session. */
218 session_new(struct session
*s
,
219 const char *name
, const char *cmd
, const char *cwd
, int idx
, char **cause
)
227 if ((wl
= winlink_add(&s
->windows
, idx
)) == NULL
) {
228 xasprintf(cause
, "index in use: %d", idx
);
233 environ_copy(&global_environ
, &env
);
234 environ_copy(&s
->environ
, &env
);
235 server_fill_environ(s
, &env
);
237 shell
= options_get_string(&s
->options
, "default-shell");
238 if (*shell
== '\0' || areshell(shell
))
239 shell
= _PATH_BSHELL
;
241 hlimit
= options_get_number(&s
->options
, "history-limit");
243 name
, cmd
, shell
, cwd
, &env
, s
->tio
, s
->sx
, s
->sy
, hlimit
, cause
);
245 winlink_remove(&s
->windows
, wl
);
249 winlink_set_window(wl
, w
);
252 if (options_get_number(&s
->options
, "set-remain-on-exit"))
253 options_set_number(&w
->options
, "remain-on-exit", 1);
255 session_group_synchronize_from(s
);
259 /* Attach a window to a session. */
261 session_attach(struct session
*s
, struct window
*w
, int idx
, char **cause
)
265 if ((wl
= winlink_add(&s
->windows
, idx
)) == NULL
) {
266 xasprintf(cause
, "index in use: %d", idx
);
269 winlink_set_window(wl
, w
);
271 session_group_synchronize_from(s
);
275 /* Detach a window from a session. */
277 session_detach(struct session
*s
, struct winlink
*wl
)
280 session_last(s
) != 0 && session_previous(s
, 0) != 0)
283 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
284 winlink_stack_remove(&s
->lastw
, wl
);
285 winlink_remove(&s
->windows
, wl
);
286 session_group_synchronize_from(s
);
287 if (RB_EMPTY(&s
->windows
)) {
294 /* Return if session has window. */
296 session_has(struct session
*s
, struct window
*w
)
300 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
308 session_next_alert(struct winlink
*wl
)
311 if (wl
->flags
& WINLINK_ALERTFLAGS
)
313 wl
= winlink_next(wl
);
318 /* Move session to next window. */
320 session_next(struct session
*s
, int alert
)
327 wl
= winlink_next(s
->curw
);
329 wl
= session_next_alert(wl
);
331 wl
= RB_MIN(winlinks
, &s
->windows
);
332 if (alert
&& ((wl
= session_next_alert(wl
)) == NULL
))
337 winlink_stack_remove(&s
->lastw
, wl
);
338 winlink_stack_push(&s
->lastw
, s
->curw
);
340 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
345 session_previous_alert(struct winlink
*wl
)
348 if (wl
->flags
& WINLINK_ALERTFLAGS
)
350 wl
= winlink_previous(wl
);
355 /* Move session to previous window. */
357 session_previous(struct session
*s
, int alert
)
364 wl
= winlink_previous(s
->curw
);
366 wl
= session_previous_alert(wl
);
368 wl
= RB_MAX(winlinks
, &s
->windows
);
369 if (alert
&& (wl
= session_previous_alert(wl
)) == NULL
)
374 winlink_stack_remove(&s
->lastw
, wl
);
375 winlink_stack_push(&s
->lastw
, s
->curw
);
377 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
381 /* Move session to specific window. */
383 session_select(struct session
*s
, int idx
)
387 wl
= winlink_find_by_index(&s
->windows
, idx
);
392 winlink_stack_remove(&s
->lastw
, wl
);
393 winlink_stack_push(&s
->lastw
, s
->curw
);
395 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
399 /* Move session to last used window. */
401 session_last(struct session
*s
)
405 wl
= TAILQ_FIRST(&s
->lastw
);
411 winlink_stack_remove(&s
->lastw
, wl
);
412 winlink_stack_push(&s
->lastw
, s
->curw
);
414 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
418 /* Find the session group containing a session. */
419 struct session_group
*
420 session_group_find(struct session
*target
)
422 struct session_group
*sg
;
425 TAILQ_FOREACH(sg
, &session_groups
, entry
) {
426 TAILQ_FOREACH(s
, &sg
->sessions
, gentry
) {
434 /* Find session group index. */
436 session_group_index(struct session_group
*sg
)
438 struct session_group
*sg2
;
442 TAILQ_FOREACH(sg2
, &session_groups
, entry
) {
448 fatalx("session group not found");
452 * Add a session to the session group containing target, creating it if
456 session_group_add(struct session
*target
, struct session
*s
)
458 struct session_group
*sg
;
460 if ((sg
= session_group_find(target
)) == NULL
) {
461 sg
= xmalloc(sizeof *sg
);
462 TAILQ_INSERT_TAIL(&session_groups
, sg
, entry
);
463 TAILQ_INIT(&sg
->sessions
);
464 TAILQ_INSERT_TAIL(&sg
->sessions
, target
, gentry
);
466 TAILQ_INSERT_TAIL(&sg
->sessions
, s
, gentry
);
469 /* Remove a session from its group and destroy the group if empty. */
471 session_group_remove(struct session
*s
)
473 struct session_group
*sg
;
475 if ((sg
= session_group_find(s
)) == NULL
)
477 TAILQ_REMOVE(&sg
->sessions
, s
, gentry
);
478 if (TAILQ_NEXT(TAILQ_FIRST(&sg
->sessions
), gentry
) == NULL
)
479 TAILQ_REMOVE(&sg
->sessions
, TAILQ_FIRST(&sg
->sessions
), gentry
);
480 if (TAILQ_EMPTY(&sg
->sessions
)) {
481 TAILQ_REMOVE(&session_groups
, sg
, entry
);
486 /* Synchronize a session to its session group. */
488 session_group_synchronize_to(struct session
*s
)
490 struct session_group
*sg
;
491 struct session
*target
;
493 if ((sg
= session_group_find(s
)) == NULL
)
497 TAILQ_FOREACH(target
, &sg
->sessions
, gentry
) {
501 session_group_synchronize1(target
, s
);
504 /* Synchronize a session group to a session. */
506 session_group_synchronize_from(struct session
*target
)
508 struct session_group
*sg
;
511 if ((sg
= session_group_find(target
)) == NULL
)
514 TAILQ_FOREACH(s
, &sg
->sessions
, gentry
) {
516 session_group_synchronize1(target
, s
);
521 * Synchronize a session with a target session. This means destroying all
522 * winlinks then recreating them, then updating the current window, last window
526 session_group_synchronize1(struct session
*target
, struct session
*s
)
528 struct winlinks old_windows
, *ww
;
529 struct winlink_stack old_lastw
;
530 struct winlink
*wl
, *wl2
;
532 /* Don't do anything if the session is empty (it'll be destroyed). */
533 ww
= &target
->windows
;
537 /* If the current window has vanished, move to the next now. */
538 if (s
->curw
!= NULL
&&
539 winlink_find_by_index(ww
, s
->curw
->idx
) == NULL
&&
540 session_last(s
) != 0 && session_previous(s
, 0) != 0)
543 /* Save the old pointer and reset it. */
544 memcpy(&old_windows
, &s
->windows
, sizeof old_windows
);
545 RB_INIT(&s
->windows
);
547 /* Link all the windows from the target. */
548 RB_FOREACH(wl
, winlinks
, ww
) {
549 wl2
= winlink_add(&s
->windows
, wl
->idx
);
550 winlink_set_window(wl2
, wl
->window
);
551 wl2
->flags
|= wl
->flags
& WINLINK_ALERTFLAGS
;
554 /* Fix up the current window. */
556 s
->curw
= winlink_find_by_index(&s
->windows
, s
->curw
->idx
);
558 s
->curw
= winlink_find_by_index(&s
->windows
, target
->curw
->idx
);
560 /* Fix up the last window stack. */
561 memcpy(&old_lastw
, &s
->lastw
, sizeof old_lastw
);
562 TAILQ_INIT(&s
->lastw
);
563 TAILQ_FOREACH(wl
, &old_lastw
, sentry
) {
564 wl2
= winlink_find_by_index(&s
->windows
, wl
->idx
);
566 TAILQ_INSERT_TAIL(&s
->lastw
, wl2
, sentry
);
569 /* Then free the old winlinks list. */
570 while (!RB_EMPTY(&old_windows
)) {
571 wl
= RB_ROOT(&old_windows
);
572 winlink_remove(&old_windows
, wl
);