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
);
123 s
->name
= xstrdup(name
);
124 s
->idx
= next_session
++;
128 s
->idx
= next_session
++;
131 xasprintf(&s
->name
, "%u", s
->idx
);
132 } while (RB_FIND(sessions
, &sessions
, s
) != NULL
);
134 RB_INSERT(sessions
, &sessions
, s
);
137 if (session_new(s
, NULL
, cmd
, cwd
, idx
, cause
) == NULL
) {
141 session_select(s
, RB_ROOT(&s
->windows
)->idx
);
144 log_debug("session %s created", s
->name
);
145 notify_session_created(s
);
150 /* Destroy a session. */
152 session_destroy(struct session
*s
)
155 log_debug("session %s destroyed", s
->name
);
157 RB_REMOVE(sessions
, &sessions
, s
);
158 notify_session_closed(s
);
163 session_group_remove(s
);
164 environ_free(&s
->environ
);
165 options_free(&s
->options
);
167 while (!TAILQ_EMPTY(&s
->lastw
))
168 winlink_stack_remove(&s
->lastw
, TAILQ_FIRST(&s
->lastw
));
169 while (!RB_EMPTY(&s
->windows
)) {
170 wl
= RB_ROOT(&s
->windows
);
171 notify_window_unlinked(s
, wl
->window
);
172 winlink_remove(&s
->windows
, wl
);
177 RB_INSERT(sessions
, &dead_sessions
, s
);
180 /* Check a session name is valid: not empty and no colons. */
182 session_check_name(const char *name
)
184 return (*name
!= '\0' && strchr(name
, ':') == NULL
);
187 /* Update session active time. */
189 session_update_activity(struct session
*s
)
191 if (gettimeofday(&s
->activity_time
, NULL
) != 0)
192 fatal("gettimeofday");
195 /* Find the next usable session. */
197 session_next_session(struct session
*s
)
201 if (RB_EMPTY(&sessions
) || !session_alive(s
))
204 s2
= RB_NEXT(sessions
, &sessions
, s
);
206 s2
= RB_MIN(sessions
, &sessions
);
212 /* Find the previous usable session. */
214 session_previous_session(struct session
*s
)
218 if (RB_EMPTY(&sessions
) || !session_alive(s
))
221 s2
= RB_PREV(sessions
, &sessions
, s
);
223 s2
= RB_MAX(sessions
, &sessions
);
229 /* Create a new window on a session. */
231 session_new(struct session
*s
,
232 const char *name
, const char *cmd
, const char *cwd
, int idx
, char **cause
)
240 if ((wl
= winlink_add(&s
->windows
, idx
)) == NULL
) {
241 xasprintf(cause
, "index in use: %d", idx
);
246 environ_copy(&global_environ
, &env
);
247 environ_copy(&s
->environ
, &env
);
248 server_fill_environ(s
, &env
);
250 shell
= options_get_string(&s
->options
, "default-shell");
251 if (*shell
== '\0' || areshell(shell
))
252 shell
= _PATH_BSHELL
;
254 hlimit
= options_get_number(&s
->options
, "history-limit");
256 name
, cmd
, shell
, cwd
, &env
, s
->tio
, s
->sx
, s
->sy
, hlimit
, cause
);
258 winlink_remove(&s
->windows
, wl
);
262 winlink_set_window(wl
, w
);
263 notify_window_linked(s
, w
);
266 if (options_get_number(&s
->options
, "set-remain-on-exit"))
267 options_set_number(&w
->options
, "remain-on-exit", 1);
269 session_group_synchronize_from(s
);
273 /* Attach a window to a session. */
275 session_attach(struct session
*s
, struct window
*w
, int idx
, char **cause
)
279 if ((wl
= winlink_add(&s
->windows
, idx
)) == NULL
) {
280 xasprintf(cause
, "index in use: %d", idx
);
283 winlink_set_window(wl
, w
);
284 notify_window_linked(s
, w
);
286 session_group_synchronize_from(s
);
290 /* Detach a window from a session. */
292 session_detach(struct session
*s
, struct winlink
*wl
)
295 session_last(s
) != 0 && session_previous(s
, 0) != 0)
298 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
299 notify_window_unlinked(s
, wl
->window
);
300 winlink_stack_remove(&s
->lastw
, wl
);
301 winlink_remove(&s
->windows
, wl
);
302 session_group_synchronize_from(s
);
303 if (RB_EMPTY(&s
->windows
)) {
310 /* Return if session has window. */
312 session_has(struct session
*s
, struct window
*w
)
316 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
324 session_next_alert(struct winlink
*wl
)
327 if (wl
->flags
& WINLINK_ALERTFLAGS
)
329 wl
= winlink_next(wl
);
334 /* Move session to next window. */
336 session_next(struct session
*s
, int alert
)
343 wl
= winlink_next(s
->curw
);
345 wl
= session_next_alert(wl
);
347 wl
= RB_MIN(winlinks
, &s
->windows
);
348 if (alert
&& ((wl
= session_next_alert(wl
)) == NULL
))
353 winlink_stack_remove(&s
->lastw
, wl
);
354 winlink_stack_push(&s
->lastw
, s
->curw
);
356 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
361 session_previous_alert(struct winlink
*wl
)
364 if (wl
->flags
& WINLINK_ALERTFLAGS
)
366 wl
= winlink_previous(wl
);
371 /* Move session to previous window. */
373 session_previous(struct session
*s
, int alert
)
380 wl
= winlink_previous(s
->curw
);
382 wl
= session_previous_alert(wl
);
384 wl
= RB_MAX(winlinks
, &s
->windows
);
385 if (alert
&& (wl
= session_previous_alert(wl
)) == NULL
)
390 winlink_stack_remove(&s
->lastw
, wl
);
391 winlink_stack_push(&s
->lastw
, s
->curw
);
393 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
397 /* Move session to specific window. */
399 session_select(struct session
*s
, int idx
)
403 wl
= winlink_find_by_index(&s
->windows
, idx
);
408 winlink_stack_remove(&s
->lastw
, wl
);
409 winlink_stack_push(&s
->lastw
, s
->curw
);
411 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
415 /* Move session to last used window. */
417 session_last(struct session
*s
)
421 wl
= TAILQ_FIRST(&s
->lastw
);
427 winlink_stack_remove(&s
->lastw
, wl
);
428 winlink_stack_push(&s
->lastw
, s
->curw
);
430 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
434 /* Find the session group containing a session. */
435 struct session_group
*
436 session_group_find(struct session
*target
)
438 struct session_group
*sg
;
441 TAILQ_FOREACH(sg
, &session_groups
, entry
) {
442 TAILQ_FOREACH(s
, &sg
->sessions
, gentry
) {
450 /* Find session group index. */
452 session_group_index(struct session_group
*sg
)
454 struct session_group
*sg2
;
458 TAILQ_FOREACH(sg2
, &session_groups
, entry
) {
464 fatalx("session group not found");
468 * Add a session to the session group containing target, creating it if
472 session_group_add(struct session
*target
, struct session
*s
)
474 struct session_group
*sg
;
476 if ((sg
= session_group_find(target
)) == NULL
) {
477 sg
= xmalloc(sizeof *sg
);
478 TAILQ_INSERT_TAIL(&session_groups
, sg
, entry
);
479 TAILQ_INIT(&sg
->sessions
);
480 TAILQ_INSERT_TAIL(&sg
->sessions
, target
, gentry
);
482 TAILQ_INSERT_TAIL(&sg
->sessions
, s
, gentry
);
485 /* Remove a session from its group and destroy the group if empty. */
487 session_group_remove(struct session
*s
)
489 struct session_group
*sg
;
491 if ((sg
= session_group_find(s
)) == NULL
)
493 TAILQ_REMOVE(&sg
->sessions
, s
, gentry
);
494 if (TAILQ_NEXT(TAILQ_FIRST(&sg
->sessions
), gentry
) == NULL
)
495 TAILQ_REMOVE(&sg
->sessions
, TAILQ_FIRST(&sg
->sessions
), gentry
);
496 if (TAILQ_EMPTY(&sg
->sessions
)) {
497 TAILQ_REMOVE(&session_groups
, sg
, entry
);
502 /* Synchronize a session to its session group. */
504 session_group_synchronize_to(struct session
*s
)
506 struct session_group
*sg
;
507 struct session
*target
;
509 if ((sg
= session_group_find(s
)) == NULL
)
513 TAILQ_FOREACH(target
, &sg
->sessions
, gentry
) {
517 session_group_synchronize1(target
, s
);
520 /* Synchronize a session group to a session. */
522 session_group_synchronize_from(struct session
*target
)
524 struct session_group
*sg
;
527 if ((sg
= session_group_find(target
)) == NULL
)
530 TAILQ_FOREACH(s
, &sg
->sessions
, gentry
) {
532 session_group_synchronize1(target
, s
);
537 * Synchronize a session with a target session. This means destroying all
538 * winlinks then recreating them, then updating the current window, last window
542 session_group_synchronize1(struct session
*target
, struct session
*s
)
544 struct winlinks old_windows
, *ww
;
545 struct winlink_stack old_lastw
;
546 struct winlink
*wl
, *wl2
;
548 /* Don't do anything if the session is empty (it'll be destroyed). */
549 ww
= &target
->windows
;
553 /* If the current window has vanished, move to the next now. */
554 if (s
->curw
!= NULL
&&
555 winlink_find_by_index(ww
, s
->curw
->idx
) == NULL
&&
556 session_last(s
) != 0 && session_previous(s
, 0) != 0)
559 /* Save the old pointer and reset it. */
560 memcpy(&old_windows
, &s
->windows
, sizeof old_windows
);
561 RB_INIT(&s
->windows
);
563 /* Link all the windows from the target. */
564 RB_FOREACH(wl
, winlinks
, ww
) {
565 wl2
= winlink_add(&s
->windows
, wl
->idx
);
566 winlink_set_window(wl2
, wl
->window
);
567 notify_window_linked(s
, wl2
->window
);
568 wl2
->flags
|= wl
->flags
& WINLINK_ALERTFLAGS
;
571 /* Fix up the current window. */
573 s
->curw
= winlink_find_by_index(&s
->windows
, s
->curw
->idx
);
575 s
->curw
= winlink_find_by_index(&s
->windows
, target
->curw
->idx
);
577 /* Fix up the last window stack. */
578 memcpy(&old_lastw
, &s
->lastw
, sizeof old_lastw
);
579 TAILQ_INIT(&s
->lastw
);
580 TAILQ_FOREACH(wl
, &old_lastw
, sentry
) {
581 wl2
= winlink_find_by_index(&s
->windows
, wl
->idx
);
583 TAILQ_INSERT_TAIL(&s
->lastw
, wl2
, sentry
);
586 /* Then free the old winlinks list. */
587 while (!RB_EMPTY(&old_windows
)) {
588 wl
= RB_ROOT(&old_windows
);
589 if (winlink_find_by_window_id(&s
->windows
, wl
->window
->id
) == NULL
)
590 notify_window_unlinked(s
, wl
->window
);
591 winlink_remove(&old_windows
, wl
);