Drop main() prototype. Syncs with NetBSD-8
[minix.git] / external / bsd / tmux / dist / session.c
blobaef4f271326d97f84581b8e60c004424a8bf6bf6
1 /* Id */
3 /*
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>
20 #include <sys/time.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <time.h>
27 #include "tmux.h"
29 /* Global session list. */
30 struct sessions sessions;
31 struct sessions dead_sessions;
32 u_int next_session_id;
33 struct session_groups session_groups;
35 struct winlink *session_next_alert(struct winlink *);
36 struct winlink *session_previous_alert(struct winlink *);
38 RB_GENERATE(sessions, session, entry, session_cmp);
40 int
41 session_cmp(struct session *s1, struct session *s2)
43 return (strcmp(s1->name, s2->name));
47 * Find if session is still alive. This is true if it is still on the global
48 * sessions list.
50 int
51 session_alive(struct session *s)
53 struct session *s_loop;
55 RB_FOREACH(s_loop, sessions, &sessions) {
56 if (s_loop == s)
57 return (1);
59 return (0);
62 /* Find session by name. */
63 struct session *
64 session_find(const char *name)
66 struct session s;
68 s.name = __UNCONST(name);
69 return (RB_FIND(sessions, &sessions, &s));
72 /* Find session by id. */
73 struct session *
74 session_find_by_id(u_int id)
76 struct session *s;
78 RB_FOREACH(s, sessions, &sessions) {
79 if (s->id == id)
80 return (s);
82 return (NULL);
85 /* Create a new session. */
86 struct session *
87 session_create(const char *name, const char *cmd, int cwd, struct environ *env,
88 struct termios *tio, int idx, u_int sx, u_int sy, char **cause)
90 struct session *s;
92 s = xmalloc(sizeof *s);
93 s->references = 0;
94 s->flags = 0;
96 if (gettimeofday(&s->creation_time, NULL) != 0)
97 fatal("gettimeofday failed");
98 session_update_activity(s);
100 s->cwd = dup(cwd);
102 s->curw = NULL;
103 TAILQ_INIT(&s->lastw);
104 RB_INIT(&s->windows);
106 options_init(&s->options, &global_s_options);
107 environ_init(&s->environ);
108 if (env != NULL)
109 environ_copy(env, &s->environ);
111 s->tio = NULL;
112 if (tio != NULL) {
113 s->tio = xmalloc(sizeof *s->tio);
114 memcpy(s->tio, tio, sizeof *s->tio);
117 s->sx = sx;
118 s->sy = sy;
120 if (name != NULL) {
121 s->name = xstrdup(name);
122 s->id = next_session_id++;
123 } else {
124 s->name = NULL;
125 do {
126 s->id = next_session_id++;
127 free (s->name);
128 xasprintf(&s->name, "%u", s->id);
129 } while (RB_FIND(sessions, &sessions, s) != NULL);
131 RB_INSERT(sessions, &sessions, s);
133 if (cmd != NULL) {
134 if (session_new(s, NULL, cmd, cwd, idx, cause) == NULL) {
135 session_destroy(s);
136 return (NULL);
138 session_select(s, RB_ROOT(&s->windows)->idx);
141 log_debug("session %s created", s->name);
142 notify_session_created(s);
144 return (s);
147 /* Destroy a session. */
148 void
149 session_destroy(struct session *s)
151 struct winlink *wl;
153 log_debug("session %s destroyed", s->name);
155 RB_REMOVE(sessions, &sessions, s);
156 notify_session_closed(s);
158 free(s->tio);
160 session_group_remove(s);
161 environ_free(&s->environ);
162 options_free(&s->options);
164 while (!TAILQ_EMPTY(&s->lastw))
165 winlink_stack_remove(&s->lastw, TAILQ_FIRST(&s->lastw));
166 while (!RB_EMPTY(&s->windows)) {
167 wl = RB_ROOT(&s->windows);
168 notify_window_unlinked(s, wl->window);
169 winlink_remove(&s->windows, wl);
172 close(s->cwd);
174 RB_INSERT(sessions, &dead_sessions, s);
177 /* Check a session name is valid: not empty and no colons or periods. */
179 session_check_name(const char *name)
181 return (*name != '\0' && name[strcspn(name, ":.")] == '\0');
184 /* Update session active time. */
185 void
186 session_update_activity(struct session *s)
188 if (gettimeofday(&s->activity_time, NULL) != 0)
189 fatal("gettimeofday");
192 /* Find the next usable session. */
193 struct session *
194 session_next_session(struct session *s)
196 struct session *s2;
198 if (RB_EMPTY(&sessions) || !session_alive(s))
199 return (NULL);
201 s2 = RB_NEXT(sessions, &sessions, s);
202 if (s2 == NULL)
203 s2 = RB_MIN(sessions, &sessions);
204 if (s2 == s)
205 return (NULL);
206 return (s2);
209 /* Find the previous usable session. */
210 struct session *
211 session_previous_session(struct session *s)
213 struct session *s2;
215 if (RB_EMPTY(&sessions) || !session_alive(s))
216 return (NULL);
218 s2 = RB_PREV(sessions, &sessions, s);
219 if (s2 == NULL)
220 s2 = RB_MAX(sessions, &sessions);
221 if (s2 == s)
222 return (NULL);
223 return (s2);
226 /* Create a new window on a session. */
227 struct winlink *
228 session_new(struct session *s, const char *name, const char *cmd, int cwd,
229 int idx, char **cause)
231 struct window *w;
232 struct winlink *wl;
233 struct environ env;
234 const char *shell;
235 u_int hlimit;
237 if ((wl = winlink_add(&s->windows, idx)) == NULL) {
238 xasprintf(cause, "index in use: %d", idx);
239 return (NULL);
242 environ_init(&env);
243 environ_copy(&global_environ, &env);
244 environ_copy(&s->environ, &env);
245 server_fill_environ(s, &env);
247 shell = options_get_string(&s->options, "default-shell");
248 if (*shell == '\0' || areshell(shell))
249 shell = _PATH_BSHELL;
251 hlimit = options_get_number(&s->options, "history-limit");
252 w = window_create(name, cmd, shell, cwd, &env, s->tio, s->sx, s->sy,
253 hlimit, cause);
254 if (w == NULL) {
255 winlink_remove(&s->windows, wl);
256 environ_free(&env);
257 return (NULL);
259 winlink_set_window(wl, w);
260 notify_window_linked(s, w);
261 environ_free(&env);
263 if (options_get_number(&s->options, "set-remain-on-exit"))
264 options_set_number(&w->options, "remain-on-exit", 1);
266 session_group_synchronize_from(s);
267 return (wl);
270 /* Attach a window to a session. */
271 struct winlink *
272 session_attach(struct session *s, struct window *w, int idx, char **cause)
274 struct winlink *wl;
276 if ((wl = winlink_add(&s->windows, idx)) == NULL) {
277 xasprintf(cause, "index in use: %d", idx);
278 return (NULL);
280 winlink_set_window(wl, w);
281 notify_window_linked(s, w);
283 session_group_synchronize_from(s);
284 return (wl);
287 /* Detach a window from a session. */
289 session_detach(struct session *s, struct winlink *wl)
291 if (s->curw == wl &&
292 session_last(s) != 0 && session_previous(s, 0) != 0)
293 session_next(s, 0);
295 wl->flags &= ~WINLINK_ALERTFLAGS;
296 notify_window_unlinked(s, wl->window);
297 winlink_stack_remove(&s->lastw, wl);
298 winlink_remove(&s->windows, wl);
299 session_group_synchronize_from(s);
300 if (RB_EMPTY(&s->windows)) {
301 session_destroy(s);
302 return (1);
304 return (0);
307 /* Return if session has window. */
308 struct winlink *
309 session_has(struct session *s, struct window *w)
311 struct winlink *wl;
313 RB_FOREACH(wl, winlinks, &s->windows) {
314 if (wl->window == w)
315 return (wl);
317 return (NULL);
320 struct winlink *
321 session_next_alert(struct winlink *wl)
323 while (wl != NULL) {
324 if (wl->flags & WINLINK_ALERTFLAGS)
325 break;
326 wl = winlink_next(wl);
328 return (wl);
331 /* Move session to next window. */
333 session_next(struct session *s, int alert)
335 struct winlink *wl;
337 if (s->curw == NULL)
338 return (-1);
340 wl = winlink_next(s->curw);
341 if (alert)
342 wl = session_next_alert(wl);
343 if (wl == NULL) {
344 wl = RB_MIN(winlinks, &s->windows);
345 if (alert && ((wl = session_next_alert(wl)) == NULL))
346 return (-1);
348 return (session_set_current(s, wl));
351 struct winlink *
352 session_previous_alert(struct winlink *wl)
354 while (wl != NULL) {
355 if (wl->flags & WINLINK_ALERTFLAGS)
356 break;
357 wl = winlink_previous(wl);
359 return (wl);
362 /* Move session to previous window. */
364 session_previous(struct session *s, int alert)
366 struct winlink *wl;
368 if (s->curw == NULL)
369 return (-1);
371 wl = winlink_previous(s->curw);
372 if (alert)
373 wl = session_previous_alert(wl);
374 if (wl == NULL) {
375 wl = RB_MAX(winlinks, &s->windows);
376 if (alert && (wl = session_previous_alert(wl)) == NULL)
377 return (-1);
379 return (session_set_current(s, wl));
382 /* Move session to specific window. */
384 session_select(struct session *s, int idx)
386 struct winlink *wl;
388 wl = winlink_find_by_index(&s->windows, idx);
389 return (session_set_current(s, wl));
392 /* Move session to last used window. */
394 session_last(struct session *s)
396 struct winlink *wl;
398 wl = TAILQ_FIRST(&s->lastw);
399 if (wl == NULL)
400 return (-1);
401 if (wl == s->curw)
402 return (1);
404 return (session_set_current(s, wl));
407 /* Set current winlink to wl .*/
409 session_set_current(struct session *s, struct winlink *wl)
411 if (wl == NULL)
412 return (-1);
413 if (wl == s->curw)
414 return (1);
416 winlink_stack_remove(&s->lastw, wl);
417 winlink_stack_push(&s->lastw, s->curw);
418 s->curw = wl;
419 winlink_clear_flags(wl);
420 return (0);
423 /* Find the session group containing a session. */
424 struct session_group *
425 session_group_find(struct session *target)
427 struct session_group *sg;
428 struct session *s;
430 TAILQ_FOREACH(sg, &session_groups, entry) {
431 TAILQ_FOREACH(s, &sg->sessions, gentry) {
432 if (s == target)
433 return (sg);
436 return (NULL);
439 /* Find session group index. */
440 u_int
441 session_group_index(struct session_group *sg)
443 struct session_group *sg2;
444 u_int i;
446 i = 0;
447 TAILQ_FOREACH(sg2, &session_groups, entry) {
448 if (sg == sg2)
449 return (i);
450 i++;
453 fatalx("session group not found");
457 * Add a session to the session group containing target, creating it if
458 * necessary.
460 void
461 session_group_add(struct session *target, struct session *s)
463 struct session_group *sg;
465 if ((sg = session_group_find(target)) == NULL) {
466 sg = xmalloc(sizeof *sg);
467 TAILQ_INSERT_TAIL(&session_groups, sg, entry);
468 TAILQ_INIT(&sg->sessions);
469 TAILQ_INSERT_TAIL(&sg->sessions, target, gentry);
471 TAILQ_INSERT_TAIL(&sg->sessions, s, gentry);
474 /* Remove a session from its group and destroy the group if empty. */
475 void
476 session_group_remove(struct session *s)
478 struct session_group *sg;
480 if ((sg = session_group_find(s)) == NULL)
481 return;
482 TAILQ_REMOVE(&sg->sessions, s, gentry);
483 if (TAILQ_NEXT(TAILQ_FIRST(&sg->sessions), gentry) == NULL)
484 TAILQ_REMOVE(&sg->sessions, TAILQ_FIRST(&sg->sessions), gentry);
485 if (TAILQ_EMPTY(&sg->sessions)) {
486 TAILQ_REMOVE(&session_groups, sg, entry);
487 free(sg);
491 /* Synchronize a session to its session group. */
492 void
493 session_group_synchronize_to(struct session *s)
495 struct session_group *sg;
496 struct session *target;
498 if ((sg = session_group_find(s)) == NULL)
499 return;
501 target = NULL;
502 TAILQ_FOREACH(target, &sg->sessions, gentry) {
503 if (target != s)
504 break;
506 session_group_synchronize1(target, s);
509 /* Synchronize a session group to a session. */
510 void
511 session_group_synchronize_from(struct session *target)
513 struct session_group *sg;
514 struct session *s;
516 if ((sg = session_group_find(target)) == NULL)
517 return;
519 TAILQ_FOREACH(s, &sg->sessions, gentry) {
520 if (s != target)
521 session_group_synchronize1(target, s);
526 * Synchronize a session with a target session. This means destroying all
527 * winlinks then recreating them, then updating the current window, last window
528 * stack and alerts.
530 void
531 session_group_synchronize1(struct session *target, struct session *s)
533 struct winlinks old_windows, *ww;
534 struct winlink_stack old_lastw;
535 struct winlink *wl, *wl2;
537 /* Don't do anything if the session is empty (it'll be destroyed). */
538 ww = &target->windows;
539 if (RB_EMPTY(ww))
540 return;
542 /* If the current window has vanished, move to the next now. */
543 if (s->curw != NULL &&
544 winlink_find_by_index(ww, s->curw->idx) == NULL &&
545 session_last(s) != 0 && session_previous(s, 0) != 0)
546 session_next(s, 0);
548 /* Save the old pointer and reset it. */
549 memcpy(&old_windows, &s->windows, sizeof old_windows);
550 RB_INIT(&s->windows);
552 /* Link all the windows from the target. */
553 RB_FOREACH(wl, winlinks, ww) {
554 wl2 = winlink_add(&s->windows, wl->idx);
555 winlink_set_window(wl2, wl->window);
556 notify_window_linked(s, wl2->window);
557 wl2->flags |= wl->flags & WINLINK_ALERTFLAGS;
560 /* Fix up the current window. */
561 if (s->curw != NULL)
562 s->curw = winlink_find_by_index(&s->windows, s->curw->idx);
563 else
564 s->curw = winlink_find_by_index(&s->windows, target->curw->idx);
566 /* Fix up the last window stack. */
567 memcpy(&old_lastw, &s->lastw, sizeof old_lastw);
568 TAILQ_INIT(&s->lastw);
569 TAILQ_FOREACH(wl, &old_lastw, sentry) {
570 wl2 = winlink_find_by_index(&s->windows, wl->idx);
571 if (wl2 != NULL)
572 TAILQ_INSERT_TAIL(&s->lastw, wl2, sentry);
575 /* Then free the old winlinks list. */
576 while (!RB_EMPTY(&old_windows)) {
577 wl = RB_ROOT(&old_windows);
578 if (winlink_find_by_window_id(&s->windows, wl->window->id) == NULL)
579 notify_window_unlinked(s, wl->window);
580 winlink_remove(&old_windows, wl);
584 /* Renumber the windows across winlinks attached to a specific session. */
585 void
586 session_renumber_windows(struct session *s)
588 struct winlink *wl, *wl1, *wl_new;
589 struct winlinks old_wins;
590 struct winlink_stack old_lastw;
591 int new_idx, new_curw_idx;
593 /* Save and replace old window list. */
594 memcpy(&old_wins, &s->windows, sizeof old_wins);
595 RB_INIT(&s->windows);
597 /* Start renumbering from the base-index if it's set. */
598 new_idx = options_get_number(&s->options, "base-index");
599 new_curw_idx = 0;
601 /* Go through the winlinks and assign new indexes. */
602 RB_FOREACH(wl, winlinks, &old_wins) {
603 wl_new = winlink_add(&s->windows, new_idx);
604 winlink_set_window(wl_new, wl->window);
605 wl_new->flags |= wl->flags & WINLINK_ALERTFLAGS;
607 if (wl == s->curw)
608 new_curw_idx = wl_new->idx;
610 new_idx++;
613 /* Fix the stack of last windows now. */
614 memcpy(&old_lastw, &s->lastw, sizeof old_lastw);
615 TAILQ_INIT(&s->lastw);
616 TAILQ_FOREACH(wl, &old_lastw, sentry) {
617 wl_new = winlink_find_by_window(&s->windows, wl->window);
618 if (wl_new != NULL)
619 TAILQ_INSERT_TAIL(&s->lastw, wl_new, sentry);
622 /* Set the current window. */
623 s->curw = winlink_find_by_index(&s->windows, new_curw_idx);
625 /* Free the old winlinks (reducing window references too). */
626 RB_FOREACH_SAFE(wl, winlinks, &old_wins, wl1)
627 winlink_remove(&old_wins, wl);