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_activity(struct session
*, struct winlink
*);
36 struct winlink
*session_previous_activity(struct session
*, struct winlink
*);
39 session_alert_cancel(struct session
*s
, struct winlink
*wl
)
41 struct session_alert
*sa
, *sb
;
43 sa
= SLIST_FIRST(&s
->alerts
);
46 sa
= SLIST_NEXT(sa
, entry
);
48 if (wl
== NULL
|| sb
->wl
== wl
) {
49 SLIST_REMOVE(&s
->alerts
, sb
, session_alert
, entry
);
56 session_alert_add(struct session
*s
, struct window
*w
, int type
)
58 struct session_alert
*sa
;
61 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
65 if (wl
->window
== w
&&
66 !session_alert_has(s
, wl
, type
)) {
67 sa
= xmalloc(sizeof *sa
);
70 SLIST_INSERT_HEAD(&s
->alerts
, sa
, entry
);
76 session_alert_has(struct session
*s
, struct winlink
*wl
, int type
)
78 struct session_alert
*sa
;
80 SLIST_FOREACH(sa
, &s
->alerts
, entry
) {
81 if (sa
->wl
== wl
&& sa
->type
== type
)
89 session_alert_has_window(struct session
*s
, struct window
*w
, int type
)
91 struct session_alert
*sa
;
93 SLIST_FOREACH(sa
, &s
->alerts
, entry
) {
94 if (sa
->wl
->window
== w
&& sa
->type
== type
)
101 /* Find session by name. */
103 session_find(const char *name
)
108 for (i
= 0; i
< ARRAY_LENGTH(&sessions
); i
++) {
109 s
= ARRAY_ITEM(&sessions
, i
);
110 if (s
!= NULL
&& strcmp(s
->name
, name
) == 0)
117 /* Create a new session. */
119 session_create(const char *name
, const char *cmd
, const char *cwd
,
120 struct environ
*env
, struct termios
*tio
, int idx
, u_int sx
, u_int sy
,
126 s
= xmalloc(sizeof *s
);
130 if (gettimeofday(&s
->creation_time
, NULL
) != 0)
131 fatal("gettimeofday failed");
132 memcpy(&s
->activity_time
, &s
->creation_time
, sizeof s
->activity_time
);
135 TAILQ_INIT(&s
->lastw
);
136 RB_INIT(&s
->windows
);
137 SLIST_INIT(&s
->alerts
);
139 paste_init_stack(&s
->buffers
);
141 options_init(&s
->options
, &global_s_options
);
142 environ_init(&s
->environ
);
144 environ_copy(env
, &s
->environ
);
148 s
->tio
= xmalloc(sizeof *s
->tio
);
149 memcpy(s
->tio
, tio
, sizeof *s
->tio
);
155 for (i
= 0; i
< ARRAY_LENGTH(&sessions
); i
++) {
156 if (ARRAY_ITEM(&sessions
, i
) == NULL
) {
157 ARRAY_SET(&sessions
, i
, s
);
161 if (i
== ARRAY_LENGTH(&sessions
))
162 ARRAY_ADD(&sessions
, s
);
165 s
->name
= xstrdup(name
);
167 xasprintf(&s
->name
, "%u", i
);
170 if (session_new(s
, NULL
, cmd
, cwd
, idx
, cause
) == NULL
) {
174 session_select(s
, RB_ROOT(&s
->windows
)->idx
);
177 log_debug("session %s created", s
->name
);
182 /* Destroy a session. */
184 session_destroy(struct session
*s
)
188 log_debug("session %s destroyed", s
->name
);
190 if (session_index(s
, &i
) != 0)
191 fatalx("session not found");
192 ARRAY_SET(&sessions
, i
, NULL
);
193 while (!ARRAY_EMPTY(&sessions
) && ARRAY_LAST(&sessions
) == NULL
)
194 ARRAY_TRUNC(&sessions
, 1);
199 session_group_remove(s
);
200 session_alert_cancel(s
, NULL
);
201 environ_free(&s
->environ
);
202 options_free(&s
->options
);
203 paste_free_stack(&s
->buffers
);
205 while (!TAILQ_EMPTY(&s
->lastw
))
206 winlink_stack_remove(&s
->lastw
, TAILQ_FIRST(&s
->lastw
));
207 while (!RB_EMPTY(&s
->windows
))
208 winlink_remove(&s
->windows
, RB_ROOT(&s
->windows
));
212 for (i
= 0; i
< ARRAY_LENGTH(&dead_sessions
); i
++) {
213 if (ARRAY_ITEM(&dead_sessions
, i
) == NULL
) {
214 ARRAY_SET(&dead_sessions
, i
, s
);
218 if (i
== ARRAY_LENGTH(&dead_sessions
))
219 ARRAY_ADD(&dead_sessions
, s
);
220 s
->flags
|= SESSION_DEAD
;
223 /* Find session index. */
225 session_index(struct session
*s
, u_int
*i
)
227 for (*i
= 0; *i
< ARRAY_LENGTH(&sessions
); (*i
)++) {
228 if (s
== ARRAY_ITEM(&sessions
, *i
))
234 /* Create a new window on a session. */
236 session_new(struct session
*s
,
237 const char *name
, const char *cmd
, const char *cwd
, int idx
, char **cause
)
245 environ_copy(&global_environ
, &env
);
246 environ_copy(&s
->environ
, &env
);
247 server_fill_environ(s
, &env
);
249 shell
= options_get_string(&s
->options
, "default-shell");
250 if (*shell
== '\0' || areshell(shell
))
251 shell
= _PATH_BSHELL
;
253 hlimit
= options_get_number(&s
->options
, "history-limit");
255 name
, cmd
, shell
, cwd
, &env
, s
->tio
, s
->sx
, s
->sy
, hlimit
, cause
);
262 if (options_get_number(&s
->options
, "set-remain-on-exit"))
263 options_set_number(&w
->options
, "remain-on-exit", 1);
265 return (session_attach(s
, w
, idx
, cause
));
268 /* Attach a window to a session. */
270 session_attach(struct session
*s
, struct window
*w
, int idx
, char **cause
)
274 if ((wl
= winlink_add(&s
->windows
, w
, idx
)) == NULL
)
275 xasprintf(cause
, "index in use: %d", idx
);
276 session_group_synchronize_from(s
);
280 /* Detach a window from a session. */
282 session_detach(struct session
*s
, struct winlink
*wl
)
285 session_last(s
) != 0 && session_previous(s
, 0) != 0)
288 session_alert_cancel(s
, wl
);
289 winlink_stack_remove(&s
->lastw
, wl
);
290 winlink_remove(&s
->windows
, wl
);
291 session_group_synchronize_from(s
);
292 if (RB_EMPTY(&s
->windows
)) {
299 /* Return if session has window. */
301 session_has(struct session
*s
, struct window
*w
)
305 RB_FOREACH(wl
, winlinks
, &s
->windows
) {
313 session_next_activity(struct session
*s
, struct winlink
*wl
)
316 if (session_alert_has(s
, wl
, WINDOW_BELL
))
318 if (session_alert_has(s
, wl
, WINDOW_ACTIVITY
))
320 if (session_alert_has(s
, wl
, WINDOW_CONTENT
))
322 wl
= winlink_next(wl
);
327 /* Move session to next window. */
329 session_next(struct session
*s
, int activity
)
336 wl
= winlink_next(s
->curw
);
338 wl
= session_next_activity(s
, wl
);
340 wl
= RB_MIN(winlinks
, &s
->windows
);
341 if (activity
&& ((wl
= session_next_activity(s
, wl
)) == NULL
))
346 winlink_stack_remove(&s
->lastw
, wl
);
347 winlink_stack_push(&s
->lastw
, s
->curw
);
349 session_alert_cancel(s
, wl
);
354 session_previous_activity(struct session
*s
, struct winlink
*wl
)
357 if (session_alert_has(s
, wl
, WINDOW_BELL
))
359 if (session_alert_has(s
, wl
, WINDOW_ACTIVITY
))
361 if (session_alert_has(s
, wl
, WINDOW_CONTENT
))
363 wl
= winlink_previous(wl
);
368 /* Move session to previous window. */
370 session_previous(struct session
*s
, int activity
)
377 wl
= winlink_previous(s
->curw
);
379 wl
= session_previous_activity(s
, wl
);
381 wl
= RB_MAX(winlinks
, &s
->windows
);
382 if (activity
&& (wl
= session_previous_activity(s
, wl
)) == NULL
)
387 winlink_stack_remove(&s
->lastw
, wl
);
388 winlink_stack_push(&s
->lastw
, s
->curw
);
390 session_alert_cancel(s
, wl
);
394 /* Move session to specific window. */
396 session_select(struct session
*s
, int idx
)
400 wl
= winlink_find_by_index(&s
->windows
, idx
);
405 winlink_stack_remove(&s
->lastw
, wl
);
406 winlink_stack_push(&s
->lastw
, s
->curw
);
408 session_alert_cancel(s
, wl
);
412 /* Move session to last used window. */
414 session_last(struct session
*s
)
418 wl
= TAILQ_FIRST(&s
->lastw
);
424 winlink_stack_remove(&s
->lastw
, wl
);
425 winlink_stack_push(&s
->lastw
, s
->curw
);
427 session_alert_cancel(s
, wl
);
431 /* Find the session group containing a session. */
432 struct session_group
*
433 session_group_find(struct session
*target
)
435 struct session_group
*sg
;
438 TAILQ_FOREACH(sg
, &session_groups
, entry
) {
439 TAILQ_FOREACH(s
, &sg
->sessions
, gentry
) {
447 /* Find session group index. */
449 session_group_index(struct session_group
*sg
)
451 struct session_group
*sg2
;
455 TAILQ_FOREACH(sg2
, &session_groups
, entry
) {
461 fatalx("session group not found");
465 * Add a session to the session group containing target, creating it if
469 session_group_add(struct session
*target
, struct session
*s
)
471 struct session_group
*sg
;
473 if ((sg
= session_group_find(target
)) == NULL
) {
474 sg
= xmalloc(sizeof *sg
);
475 TAILQ_INSERT_TAIL(&session_groups
, sg
, entry
);
476 TAILQ_INIT(&sg
->sessions
);
477 TAILQ_INSERT_TAIL(&sg
->sessions
, target
, gentry
);
479 TAILQ_INSERT_TAIL(&sg
->sessions
, s
, gentry
);
482 /* Remove a session from its group and destroy the group if empty. */
484 session_group_remove(struct session
*s
)
486 struct session_group
*sg
;
488 if ((sg
= session_group_find(s
)) == NULL
)
490 TAILQ_REMOVE(&sg
->sessions
, s
, gentry
);
491 if (TAILQ_NEXT(TAILQ_FIRST(&sg
->sessions
), gentry
) == NULL
)
492 TAILQ_REMOVE(&sg
->sessions
, TAILQ_FIRST(&sg
->sessions
), gentry
);
493 if (TAILQ_EMPTY(&sg
->sessions
)) {
494 TAILQ_REMOVE(&session_groups
, sg
, entry
);
499 /* Synchronize a session to its session group. */
501 session_group_synchronize_to(struct session
*s
)
503 struct session_group
*sg
;
504 struct session
*target
;
506 if ((sg
= session_group_find(s
)) == NULL
)
510 TAILQ_FOREACH(target
, &sg
->sessions
, gentry
) {
514 session_group_synchronize1(target
, s
);
517 /* Synchronize a session group to a session. */
519 session_group_synchronize_from(struct session
*target
)
521 struct session_group
*sg
;
524 if ((sg
= session_group_find(target
)) == NULL
)
527 TAILQ_FOREACH(s
, &sg
->sessions
, gentry
) {
529 session_group_synchronize1(target
, s
);
534 * Synchronize a session with a target session. This means destroying all
535 * winlinks then recreating them, then updating the current window, last window
539 session_group_synchronize1(struct session
*target
, struct session
*s
)
541 struct winlinks old_windows
, *ww
;
542 struct winlink_stack old_lastw
;
543 struct winlink
*wl
, *wl2
;
544 struct session_alert
*sa
;
546 /* Don't do anything if the session is empty (it'll be destroyed). */
547 ww
= &target
->windows
;
551 /* If the current window has vanished, move to the next now. */
552 if (s
->curw
!= NULL
&&
553 winlink_find_by_index(ww
, s
->curw
->idx
) == NULL
&&
554 session_last(s
) != 0 && session_previous(s
, 0) != 0)
557 /* Save the old pointer and reset it. */
558 memcpy(&old_windows
, &s
->windows
, sizeof old_windows
);
559 RB_INIT(&s
->windows
);
561 /* Link all the windows from the target. */
562 RB_FOREACH(wl
, winlinks
, ww
)
563 winlink_add(&s
->windows
, wl
->window
, wl
->idx
);
565 /* Fix up the current window. */
567 s
->curw
= winlink_find_by_index(&s
->windows
, s
->curw
->idx
);
569 s
->curw
= winlink_find_by_index(&s
->windows
, target
->curw
->idx
);
571 /* Fix up the last window stack. */
572 memcpy(&old_lastw
, &s
->lastw
, sizeof old_lastw
);
573 TAILQ_INIT(&s
->lastw
);
574 TAILQ_FOREACH(wl
, &old_lastw
, sentry
) {
575 wl2
= winlink_find_by_index(&s
->windows
, wl
->idx
);
577 TAILQ_INSERT_TAIL(&s
->lastw
, wl2
, sentry
);
580 /* And update the alerts list. */
581 SLIST_FOREACH(sa
, &s
->alerts
, entry
) {
582 wl
= winlink_find_by_index(&s
->windows
, sa
->wl
->idx
);
584 session_alert_cancel(s
, sa
->wl
);
589 /* Then free the old winlinks list. */
590 while (!RB_EMPTY(&old_windows
)) {
591 wl
= RB_ROOT(&old_windows
);
592 winlink_remove(&old_windows
, wl
);