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
);
71 s
->cwd
= xstrdup(cwd
);
74 TAILQ_INIT(&s
->lastw
);
77 paste_init_stack(&s
->buffers
);
79 options_init(&s
->options
, &global_s_options
);
80 environ_init(&s
->environ
);
82 environ_copy(env
, &s
->environ
);
86 s
->tio
= xmalloc(sizeof *s
->tio
);
87 memcpy(s
->tio
, tio
, sizeof *s
->tio
);
93 for (i
= 0; i
< ARRAY_LENGTH(&sessions
); i
++) {
94 if (ARRAY_ITEM(&sessions
, i
) == NULL
) {
95 ARRAY_SET(&sessions
, i
, s
);
99 if (i
== ARRAY_LENGTH(&sessions
))
100 ARRAY_ADD(&sessions
, s
);
103 s
->name
= xstrdup(name
);
105 xasprintf(&s
->name
, "%u", i
);
108 if (session_new(s
, NULL
, cmd
, cwd
, idx
, cause
) == NULL
) {
112 session_select(s
, RB_ROOT(&s
->windows
)->idx
);
115 log_debug("session %s created", s
->name
);
120 /* Destroy a session. */
122 session_destroy(struct session
*s
)
126 log_debug("session %s destroyed", s
->name
);
128 if (session_index(s
, &i
) != 0)
129 fatalx("session not found");
130 ARRAY_SET(&sessions
, i
, NULL
);
131 while (!ARRAY_EMPTY(&sessions
) && ARRAY_LAST(&sessions
) == NULL
)
132 ARRAY_TRUNC(&sessions
, 1);
137 session_group_remove(s
);
138 environ_free(&s
->environ
);
139 options_free(&s
->options
);
140 paste_free_stack(&s
->buffers
);
142 while (!TAILQ_EMPTY(&s
->lastw
))
143 winlink_stack_remove(&s
->lastw
, TAILQ_FIRST(&s
->lastw
));
144 while (!RB_EMPTY(&s
->windows
))
145 winlink_remove(&s
->windows
, RB_ROOT(&s
->windows
));
150 for (i
= 0; i
< ARRAY_LENGTH(&dead_sessions
); i
++) {
151 if (ARRAY_ITEM(&dead_sessions
, i
) == NULL
) {
152 ARRAY_SET(&dead_sessions
, i
, s
);
156 if (i
== ARRAY_LENGTH(&dead_sessions
))
157 ARRAY_ADD(&dead_sessions
, s
);
158 s
->flags
|= SESSION_DEAD
;
161 /* Find session index. */
163 session_index(struct session
*s
, u_int
*i
)
165 for (*i
= 0; *i
< ARRAY_LENGTH(&sessions
); (*i
)++) {
166 if (s
== ARRAY_ITEM(&sessions
, *i
))
172 /* Find the next usable session. */
174 session_next_session(struct session
*s
)
179 if (ARRAY_LENGTH(&sessions
) == 0 || session_index(s
, &i
) != 0)
183 if (i
== ARRAY_LENGTH(&sessions
) - 1)
187 s2
= ARRAY_ITEM(&sessions
, i
);
188 } while (s2
== NULL
|| s2
->flags
& SESSION_DEAD
);
193 /* Find the previous usable session. */
195 session_previous_session(struct session
*s
)
200 if (ARRAY_LENGTH(&sessions
) == 0 || session_index(s
, &i
) != 0)
205 i
= ARRAY_LENGTH(&sessions
) - 1;
208 s2
= ARRAY_ITEM(&sessions
, i
);
209 } while (s2
== NULL
|| s2
->flags
& SESSION_DEAD
);
214 /* Create a new window on a session. */
216 session_new(struct session
*s
,
217 const char *name
, const char *cmd
, const char *cwd
, int idx
, char **cause
)
225 environ_copy(&global_environ
, &env
);
226 environ_copy(&s
->environ
, &env
);
227 server_fill_environ(s
, &env
);
229 shell
= options_get_string(&s
->options
, "default-shell");
230 if (*shell
== '\0' || areshell(shell
))
231 shell
= _PATH_BSHELL
;
233 hlimit
= options_get_number(&s
->options
, "history-limit");
235 name
, cmd
, shell
, cwd
, &env
, s
->tio
, s
->sx
, s
->sy
, hlimit
, cause
);
242 if (options_get_number(&s
->options
, "set-remain-on-exit"))
243 options_set_number(&w
->options
, "remain-on-exit", 1);
245 return (session_attach(s
, w
, idx
, cause
));
248 /* Attach a window to a session. */
250 session_attach(struct session
*s
, struct window
*w
, int idx
, char **cause
)
254 if ((wl
= winlink_add(&s
->windows
, w
, idx
)) == NULL
)
255 xasprintf(cause
, "index in use: %d", idx
);
256 session_group_synchronize_from(s
);
260 /* Detach a window from a session. */
262 session_detach(struct session
*s
, struct winlink
*wl
)
265 session_last(s
) != 0 && session_previous(s
, 0) != 0)
268 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
269 winlink_stack_remove(&s
->lastw
, wl
);
270 winlink_remove(&s
->windows
, wl
);
271 session_group_synchronize_from(s
);
272 if (RB_EMPTY(&s
->windows
)) {
279 /* Return if session has window. */
281 session_has(struct session
*s
, struct window
*w
)
285 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
293 session_next_alert(struct winlink
*wl
)
296 if (wl
->flags
& WINLINK_ALERTFLAGS
)
298 wl
= winlink_next(wl
);
303 /* Move session to next window. */
305 session_next(struct session
*s
, int alert
)
312 wl
= winlink_next(s
->curw
);
314 wl
= session_next_alert(wl
);
316 wl
= RB_MIN(winlinks
, &s
->windows
);
317 if (alert
&& ((wl
= session_next_alert(wl
)) == NULL
))
322 winlink_stack_remove(&s
->lastw
, wl
);
323 winlink_stack_push(&s
->lastw
, s
->curw
);
325 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
330 session_previous_alert(struct winlink
*wl
)
333 if (wl
->flags
& WINLINK_ALERTFLAGS
)
335 wl
= winlink_previous(wl
);
340 /* Move session to previous window. */
342 session_previous(struct session
*s
, int alert
)
349 wl
= winlink_previous(s
->curw
);
351 wl
= session_previous_alert(wl
);
353 wl
= RB_MAX(winlinks
, &s
->windows
);
354 if (alert
&& (wl
= session_previous_alert(wl
)) == NULL
)
359 winlink_stack_remove(&s
->lastw
, wl
);
360 winlink_stack_push(&s
->lastw
, s
->curw
);
362 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
366 /* Move session to specific window. */
368 session_select(struct session
*s
, int idx
)
372 wl
= winlink_find_by_index(&s
->windows
, idx
);
377 winlink_stack_remove(&s
->lastw
, wl
);
378 winlink_stack_push(&s
->lastw
, s
->curw
);
380 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
384 /* Move session to last used window. */
386 session_last(struct session
*s
)
390 wl
= TAILQ_FIRST(&s
->lastw
);
396 winlink_stack_remove(&s
->lastw
, wl
);
397 winlink_stack_push(&s
->lastw
, s
->curw
);
399 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
403 /* Find the session group containing a session. */
404 struct session_group
*
405 session_group_find(struct session
*target
)
407 struct session_group
*sg
;
410 TAILQ_FOREACH(sg
, &session_groups
, entry
) {
411 TAILQ_FOREACH(s
, &sg
->sessions
, gentry
) {
419 /* Find session group index. */
421 session_group_index(struct session_group
*sg
)
423 struct session_group
*sg2
;
427 TAILQ_FOREACH(sg2
, &session_groups
, entry
) {
433 fatalx("session group not found");
437 * Add a session to the session group containing target, creating it if
441 session_group_add(struct session
*target
, struct session
*s
)
443 struct session_group
*sg
;
445 if ((sg
= session_group_find(target
)) == NULL
) {
446 sg
= xmalloc(sizeof *sg
);
447 TAILQ_INSERT_TAIL(&session_groups
, sg
, entry
);
448 TAILQ_INIT(&sg
->sessions
);
449 TAILQ_INSERT_TAIL(&sg
->sessions
, target
, gentry
);
451 TAILQ_INSERT_TAIL(&sg
->sessions
, s
, gentry
);
454 /* Remove a session from its group and destroy the group if empty. */
456 session_group_remove(struct session
*s
)
458 struct session_group
*sg
;
460 if ((sg
= session_group_find(s
)) == NULL
)
462 TAILQ_REMOVE(&sg
->sessions
, s
, gentry
);
463 if (TAILQ_NEXT(TAILQ_FIRST(&sg
->sessions
), gentry
) == NULL
)
464 TAILQ_REMOVE(&sg
->sessions
, TAILQ_FIRST(&sg
->sessions
), gentry
);
465 if (TAILQ_EMPTY(&sg
->sessions
)) {
466 TAILQ_REMOVE(&session_groups
, sg
, entry
);
471 /* Synchronize a session to its session group. */
473 session_group_synchronize_to(struct session
*s
)
475 struct session_group
*sg
;
476 struct session
*target
;
478 if ((sg
= session_group_find(s
)) == NULL
)
482 TAILQ_FOREACH(target
, &sg
->sessions
, gentry
) {
486 session_group_synchronize1(target
, s
);
489 /* Synchronize a session group to a session. */
491 session_group_synchronize_from(struct session
*target
)
493 struct session_group
*sg
;
496 if ((sg
= session_group_find(target
)) == NULL
)
499 TAILQ_FOREACH(s
, &sg
->sessions
, gentry
) {
501 session_group_synchronize1(target
, s
);
506 * Synchronize a session with a target session. This means destroying all
507 * winlinks then recreating them, then updating the current window, last window
511 session_group_synchronize1(struct session
*target
, struct session
*s
)
513 struct winlinks old_windows
, *ww
;
514 struct winlink_stack old_lastw
;
515 struct winlink
*wl
, *wl2
;
517 /* Don't do anything if the session is empty (it'll be destroyed). */
518 ww
= &target
->windows
;
522 /* If the current window has vanished, move to the next now. */
523 if (s
->curw
!= NULL
&&
524 winlink_find_by_index(ww
, s
->curw
->idx
) == NULL
&&
525 session_last(s
) != 0 && session_previous(s
, 0) != 0)
528 /* Save the old pointer and reset it. */
529 memcpy(&old_windows
, &s
->windows
, sizeof old_windows
);
530 RB_INIT(&s
->windows
);
532 /* Link all the windows from the target. */
533 RB_FOREACH(wl
, winlinks
, ww
) {
534 wl2
= winlink_add(&s
->windows
, wl
->window
, wl
->idx
);
535 wl2
->flags
|= wl
->flags
& WINLINK_ALERTFLAGS
;
538 /* Fix up the current window. */
540 s
->curw
= winlink_find_by_index(&s
->windows
, s
->curw
->idx
);
542 s
->curw
= winlink_find_by_index(&s
->windows
, target
->curw
->idx
);
544 /* Fix up the last window stack. */
545 memcpy(&old_lastw
, &s
->lastw
, sizeof old_lastw
);
546 TAILQ_INIT(&s
->lastw
);
547 TAILQ_FOREACH(wl
, &old_lastw
, sentry
) {
548 wl2
= winlink_find_by_index(&s
->windows
, wl
->idx
);
550 TAILQ_INSERT_TAIL(&s
->lastw
, wl2
, sentry
);
553 /* Then free the old winlinks list. */
554 while (!RB_EMPTY(&old_windows
)) {
555 wl
= RB_ROOT(&old_windows
);
556 winlink_remove(&old_windows
, wl
);