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
;
33 struct session_groups session_groups
;
35 struct winlink
*session_next_alert(struct winlink
*);
36 struct winlink
*session_previous_alert(struct winlink
*);
38 /* Find session by name. */
40 session_find(const char *name
)
45 for (i
= 0; i
< ARRAY_LENGTH(&sessions
); i
++) {
46 s
= ARRAY_ITEM(&sessions
, i
);
47 if (s
!= NULL
&& strcmp(s
->name
, name
) == 0)
54 /* Create a new session. */
56 session_create(const char *name
, const char *cmd
, const char *cwd
,
57 struct environ
*env
, struct termios
*tio
, int idx
, u_int sx
, u_int sy
,
63 s
= xmalloc(sizeof *s
);
67 if (gettimeofday(&s
->creation_time
, NULL
) != 0)
68 fatal("gettimeofday failed");
69 memcpy(&s
->activity_time
, &s
->creation_time
, sizeof s
->activity_time
);
72 TAILQ_INIT(&s
->lastw
);
75 paste_init_stack(&s
->buffers
);
77 options_init(&s
->options
, &global_s_options
);
78 environ_init(&s
->environ
);
80 environ_copy(env
, &s
->environ
);
84 s
->tio
= xmalloc(sizeof *s
->tio
);
85 memcpy(s
->tio
, tio
, sizeof *s
->tio
);
91 for (i
= 0; i
< ARRAY_LENGTH(&sessions
); i
++) {
92 if (ARRAY_ITEM(&sessions
, i
) == NULL
) {
93 ARRAY_SET(&sessions
, i
, s
);
97 if (i
== ARRAY_LENGTH(&sessions
))
98 ARRAY_ADD(&sessions
, s
);
101 s
->name
= xstrdup(name
);
103 xasprintf(&s
->name
, "%u", i
);
106 if (session_new(s
, NULL
, cmd
, cwd
, idx
, cause
) == NULL
) {
110 session_select(s
, RB_ROOT(&s
->windows
)->idx
);
113 log_debug("session %s created", s
->name
);
118 /* Destroy a session. */
120 session_destroy(struct session
*s
)
124 log_debug("session %s destroyed", s
->name
);
126 if (session_index(s
, &i
) != 0)
127 fatalx("session not found");
128 ARRAY_SET(&sessions
, i
, NULL
);
129 while (!ARRAY_EMPTY(&sessions
) && ARRAY_LAST(&sessions
) == NULL
)
130 ARRAY_TRUNC(&sessions
, 1);
135 session_group_remove(s
);
136 environ_free(&s
->environ
);
137 options_free(&s
->options
);
138 paste_free_stack(&s
->buffers
);
140 while (!TAILQ_EMPTY(&s
->lastw
))
141 winlink_stack_remove(&s
->lastw
, TAILQ_FIRST(&s
->lastw
));
142 while (!RB_EMPTY(&s
->windows
))
143 winlink_remove(&s
->windows
, RB_ROOT(&s
->windows
));
147 for (i
= 0; i
< ARRAY_LENGTH(&dead_sessions
); i
++) {
148 if (ARRAY_ITEM(&dead_sessions
, i
) == NULL
) {
149 ARRAY_SET(&dead_sessions
, i
, s
);
153 if (i
== ARRAY_LENGTH(&dead_sessions
))
154 ARRAY_ADD(&dead_sessions
, s
);
155 s
->flags
|= SESSION_DEAD
;
158 /* Find session index. */
160 session_index(struct session
*s
, u_int
*i
)
162 for (*i
= 0; *i
< ARRAY_LENGTH(&sessions
); (*i
)++) {
163 if (s
== ARRAY_ITEM(&sessions
, *i
))
169 /* Create a new window on a session. */
171 session_new(struct session
*s
,
172 const char *name
, const char *cmd
, const char *cwd
, int idx
, char **cause
)
180 environ_copy(&global_environ
, &env
);
181 environ_copy(&s
->environ
, &env
);
182 server_fill_environ(s
, &env
);
184 shell
= options_get_string(&s
->options
, "default-shell");
185 if (*shell
== '\0' || areshell(shell
))
186 shell
= _PATH_BSHELL
;
188 hlimit
= options_get_number(&s
->options
, "history-limit");
190 name
, cmd
, shell
, cwd
, &env
, s
->tio
, s
->sx
, s
->sy
, hlimit
, cause
);
197 if (options_get_number(&s
->options
, "set-remain-on-exit"))
198 options_set_number(&w
->options
, "remain-on-exit", 1);
200 return (session_attach(s
, w
, idx
, cause
));
203 /* Attach a window to a session. */
205 session_attach(struct session
*s
, struct window
*w
, int idx
, char **cause
)
209 if ((wl
= winlink_add(&s
->windows
, w
, idx
)) == NULL
)
210 xasprintf(cause
, "index in use: %d", idx
);
211 session_group_synchronize_from(s
);
215 /* Detach a window from a session. */
217 session_detach(struct session
*s
, struct winlink
*wl
)
220 session_last(s
) != 0 && session_previous(s
, 0) != 0)
223 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
224 winlink_stack_remove(&s
->lastw
, wl
);
225 winlink_remove(&s
->windows
, wl
);
226 session_group_synchronize_from(s
);
227 if (RB_EMPTY(&s
->windows
)) {
234 /* Return if session has window. */
236 session_has(struct session
*s
, struct window
*w
)
240 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
248 session_next_alert(struct winlink
*wl
)
251 if (wl
->flags
& WINLINK_ALERTFLAGS
)
253 wl
= winlink_next(wl
);
258 /* Move session to next window. */
260 session_next(struct session
*s
, int alert
)
267 wl
= winlink_next(s
->curw
);
269 wl
= session_next_alert(wl
);
271 wl
= RB_MIN(winlinks
, &s
->windows
);
272 if (alert
&& ((wl
= session_next_alert(wl
)) == NULL
))
277 winlink_stack_remove(&s
->lastw
, wl
);
278 winlink_stack_push(&s
->lastw
, s
->curw
);
280 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
285 session_previous_alert(struct winlink
*wl
)
288 if (wl
->flags
& WINLINK_ALERTFLAGS
)
290 wl
= winlink_previous(wl
);
295 /* Move session to previous window. */
297 session_previous(struct session
*s
, int alert
)
304 wl
= winlink_previous(s
->curw
);
306 wl
= session_previous_alert(wl
);
308 wl
= RB_MAX(winlinks
, &s
->windows
);
309 if (alert
&& (wl
= session_previous_alert(wl
)) == NULL
)
314 winlink_stack_remove(&s
->lastw
, wl
);
315 winlink_stack_push(&s
->lastw
, s
->curw
);
317 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
321 /* Move session to specific window. */
323 session_select(struct session
*s
, int idx
)
327 wl
= winlink_find_by_index(&s
->windows
, idx
);
332 winlink_stack_remove(&s
->lastw
, wl
);
333 winlink_stack_push(&s
->lastw
, s
->curw
);
335 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
339 /* Move session to last used window. */
341 session_last(struct session
*s
)
345 wl
= TAILQ_FIRST(&s
->lastw
);
351 winlink_stack_remove(&s
->lastw
, wl
);
352 winlink_stack_push(&s
->lastw
, s
->curw
);
354 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
358 /* Find the session group containing a session. */
359 struct session_group
*
360 session_group_find(struct session
*target
)
362 struct session_group
*sg
;
365 TAILQ_FOREACH(sg
, &session_groups
, entry
) {
366 TAILQ_FOREACH(s
, &sg
->sessions
, gentry
) {
374 /* Find session group index. */
376 session_group_index(struct session_group
*sg
)
378 struct session_group
*sg2
;
382 TAILQ_FOREACH(sg2
, &session_groups
, entry
) {
388 fatalx("session group not found");
392 * Add a session to the session group containing target, creating it if
396 session_group_add(struct session
*target
, struct session
*s
)
398 struct session_group
*sg
;
400 if ((sg
= session_group_find(target
)) == NULL
) {
401 sg
= xmalloc(sizeof *sg
);
402 TAILQ_INSERT_TAIL(&session_groups
, sg
, entry
);
403 TAILQ_INIT(&sg
->sessions
);
404 TAILQ_INSERT_TAIL(&sg
->sessions
, target
, gentry
);
406 TAILQ_INSERT_TAIL(&sg
->sessions
, s
, gentry
);
409 /* Remove a session from its group and destroy the group if empty. */
411 session_group_remove(struct session
*s
)
413 struct session_group
*sg
;
415 if ((sg
= session_group_find(s
)) == NULL
)
417 TAILQ_REMOVE(&sg
->sessions
, s
, gentry
);
418 if (TAILQ_NEXT(TAILQ_FIRST(&sg
->sessions
), gentry
) == NULL
)
419 TAILQ_REMOVE(&sg
->sessions
, TAILQ_FIRST(&sg
->sessions
), gentry
);
420 if (TAILQ_EMPTY(&sg
->sessions
)) {
421 TAILQ_REMOVE(&session_groups
, sg
, entry
);
426 /* Synchronize a session to its session group. */
428 session_group_synchronize_to(struct session
*s
)
430 struct session_group
*sg
;
431 struct session
*target
;
433 if ((sg
= session_group_find(s
)) == NULL
)
437 TAILQ_FOREACH(target
, &sg
->sessions
, gentry
) {
441 session_group_synchronize1(target
, s
);
444 /* Synchronize a session group to a session. */
446 session_group_synchronize_from(struct session
*target
)
448 struct session_group
*sg
;
451 if ((sg
= session_group_find(target
)) == NULL
)
454 TAILQ_FOREACH(s
, &sg
->sessions
, gentry
) {
456 session_group_synchronize1(target
, s
);
461 * Synchronize a session with a target session. This means destroying all
462 * winlinks then recreating them, then updating the current window, last window
466 session_group_synchronize1(struct session
*target
, struct session
*s
)
468 struct winlinks old_windows
, *ww
;
469 struct winlink_stack old_lastw
;
470 struct winlink
*wl
, *wl2
;
472 /* Don't do anything if the session is empty (it'll be destroyed). */
473 ww
= &target
->windows
;
477 /* If the current window has vanished, move to the next now. */
478 if (s
->curw
!= NULL
&&
479 winlink_find_by_index(ww
, s
->curw
->idx
) == NULL
&&
480 session_last(s
) != 0 && session_previous(s
, 0) != 0)
483 /* Save the old pointer and reset it. */
484 memcpy(&old_windows
, &s
->windows
, sizeof old_windows
);
485 RB_INIT(&s
->windows
);
487 /* Link all the windows from the target. */
488 RB_FOREACH(wl
, winlinks
, ww
) {
489 wl2
= winlink_add(&s
->windows
, wl
->window
, wl
->idx
);
490 wl2
->flags
|= wl
->flags
& WINLINK_ALERTFLAGS
;
493 /* Fix up the current window. */
495 s
->curw
= winlink_find_by_index(&s
->windows
, s
->curw
->idx
);
497 s
->curw
= winlink_find_by_index(&s
->windows
, target
->curw
->idx
);
499 /* Fix up the last window stack. */
500 memcpy(&old_lastw
, &s
->lastw
, sizeof old_lastw
);
501 TAILQ_INIT(&s
->lastw
);
502 TAILQ_FOREACH(wl
, &old_lastw
, sentry
) {
503 wl2
= winlink_find_by_index(&s
->windows
, wl
->idx
);
505 TAILQ_INSERT_TAIL(&s
->lastw
, wl2
, sentry
);
508 /* Then free the old winlinks list. */
509 while (!RB_EMPTY(&old_windows
)) {
510 wl
= RB_ROOT(&old_windows
);
511 winlink_remove(&old_windows
, wl
);