Use server_destroy_session() for kill-session.
[tmux-openbsd.git] / session.c
blob65f8ef412019a5d941e29855879f76b1ea6fa847
1 /* $OpenBSD$ */
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 <paths.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <time.h>
28 #include "tmux.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. */
39 struct session *
40 session_find(const char *name)
42 struct session *s;
43 u_int i;
45 for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
46 s = ARRAY_ITEM(&sessions, i);
47 if (s != NULL && strcmp(s->name, name) == 0)
48 return (s);
51 return (NULL);
54 /* Create a new session. */
55 struct 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,
58 char **cause)
60 struct session *s;
61 u_int i;
63 s = xmalloc(sizeof *s);
64 s->references = 0;
65 s->flags = 0;
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->curw = NULL;
72 TAILQ_INIT(&s->lastw);
73 RB_INIT(&s->windows);
75 paste_init_stack(&s->buffers);
77 options_init(&s->options, &global_s_options);
78 environ_init(&s->environ);
79 if (env != NULL)
80 environ_copy(env, &s->environ);
82 s->tio = NULL;
83 if (tio != NULL) {
84 s->tio = xmalloc(sizeof *s->tio);
85 memcpy(s->tio, tio, sizeof *s->tio);
88 s->sx = sx;
89 s->sy = sy;
91 for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
92 if (ARRAY_ITEM(&sessions, i) == NULL) {
93 ARRAY_SET(&sessions, i, s);
94 break;
97 if (i == ARRAY_LENGTH(&sessions))
98 ARRAY_ADD(&sessions, s);
100 if (name != NULL)
101 s->name = xstrdup(name);
102 else
103 xasprintf(&s->name, "%u", i);
105 if (cmd != NULL) {
106 if (session_new(s, NULL, cmd, cwd, idx, cause) == NULL) {
107 session_destroy(s);
108 return (NULL);
110 session_select(s, RB_ROOT(&s->windows)->idx);
113 log_debug("session %s created", s->name);
115 return (s);
118 /* Destroy a session. */
119 void
120 session_destroy(struct session *s)
122 u_int i;
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);
132 if (s->tio != NULL)
133 xfree(s->tio);
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));
145 xfree(s->name);
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);
150 break;
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))
164 return (0);
166 return (-1);
169 /* Create a new window on a session. */
170 struct winlink *
171 session_new(struct session *s,
172 const char *name, const char *cmd, const char *cwd, int idx, char **cause)
174 struct window *w;
175 struct environ env;
176 const char *shell;
177 u_int hlimit;
179 environ_init(&env);
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");
189 w = window_create(
190 name, cmd, shell, cwd, &env, s->tio, s->sx, s->sy, hlimit, cause);
191 if (w == NULL) {
192 environ_free(&env);
193 return (NULL);
195 environ_free(&env);
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. */
204 struct winlink *
205 session_attach(struct session *s, struct window *w, int idx, char **cause)
207 struct winlink *wl;
209 if ((wl = winlink_add(&s->windows, w, idx)) == NULL)
210 xasprintf(cause, "index in use: %d", idx);
211 session_group_synchronize_from(s);
212 return (wl);
215 /* Detach a window from a session. */
217 session_detach(struct session *s, struct winlink *wl)
219 if (s->curw == wl &&
220 session_last(s) != 0 && session_previous(s, 0) != 0)
221 session_next(s, 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)) {
228 session_destroy(s);
229 return (1);
231 return (0);
234 /* Return if session has window. */
235 struct winlink *
236 session_has(struct session *s, struct window *w)
238 struct winlink *wl;
240 RB_FOREACH(wl, winlinks, &s->windows) {
241 if (wl->window == w)
242 return (wl);
244 return (NULL);
247 struct winlink *
248 session_next_alert(struct winlink *wl)
250 while (wl != NULL) {
251 if (wl->flags & WINLINK_ALERTFLAGS)
252 break;
253 wl = winlink_next(wl);
255 return (wl);
258 /* Move session to next window. */
260 session_next(struct session *s, int alert)
262 struct winlink *wl;
264 if (s->curw == NULL)
265 return (-1);
267 wl = winlink_next(s->curw);
268 if (alert)
269 wl = session_next_alert(wl);
270 if (wl == NULL) {
271 wl = RB_MIN(winlinks, &s->windows);
272 if (alert && ((wl = session_next_alert(wl)) == NULL))
273 return (-1);
275 if (wl == s->curw)
276 return (1);
277 winlink_stack_remove(&s->lastw, wl);
278 winlink_stack_push(&s->lastw, s->curw);
279 s->curw = wl;
280 wl->flags &= ~WINLINK_ALERTFLAGS;
281 return (0);
284 struct winlink *
285 session_previous_alert(struct winlink *wl)
287 while (wl != NULL) {
288 if (wl->flags & WINLINK_ALERTFLAGS)
289 break;
290 wl = winlink_previous(wl);
292 return (wl);
295 /* Move session to previous window. */
297 session_previous(struct session *s, int alert)
299 struct winlink *wl;
301 if (s->curw == NULL)
302 return (-1);
304 wl = winlink_previous(s->curw);
305 if (alert)
306 wl = session_previous_alert(wl);
307 if (wl == NULL) {
308 wl = RB_MAX(winlinks, &s->windows);
309 if (alert && (wl = session_previous_alert(wl)) == NULL)
310 return (-1);
312 if (wl == s->curw)
313 return (1);
314 winlink_stack_remove(&s->lastw, wl);
315 winlink_stack_push(&s->lastw, s->curw);
316 s->curw = wl;
317 wl->flags &= ~WINLINK_ALERTFLAGS;
318 return (0);
321 /* Move session to specific window. */
323 session_select(struct session *s, int idx)
325 struct winlink *wl;
327 wl = winlink_find_by_index(&s->windows, idx);
328 if (wl == NULL)
329 return (-1);
330 if (wl == s->curw)
331 return (1);
332 winlink_stack_remove(&s->lastw, wl);
333 winlink_stack_push(&s->lastw, s->curw);
334 s->curw = wl;
335 wl->flags &= ~WINLINK_ALERTFLAGS;
336 return (0);
339 /* Move session to last used window. */
341 session_last(struct session *s)
343 struct winlink *wl;
345 wl = TAILQ_FIRST(&s->lastw);
346 if (wl == NULL)
347 return (-1);
348 if (wl == s->curw)
349 return (1);
351 winlink_stack_remove(&s->lastw, wl);
352 winlink_stack_push(&s->lastw, s->curw);
353 s->curw = wl;
354 wl->flags &= ~WINLINK_ALERTFLAGS;
355 return (0);
358 /* Find the session group containing a session. */
359 struct session_group *
360 session_group_find(struct session *target)
362 struct session_group *sg;
363 struct session *s;
365 TAILQ_FOREACH(sg, &session_groups, entry) {
366 TAILQ_FOREACH(s, &sg->sessions, gentry) {
367 if (s == target)
368 return (sg);
371 return (NULL);
374 /* Find session group index. */
375 u_int
376 session_group_index(struct session_group *sg)
378 struct session_group *sg2;
379 u_int i;
381 i = 0;
382 TAILQ_FOREACH(sg2, &session_groups, entry) {
383 if (sg == sg2)
384 return (i);
385 i++;
388 fatalx("session group not found");
392 * Add a session to the session group containing target, creating it if
393 * necessary.
395 void
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. */
410 void
411 session_group_remove(struct session *s)
413 struct session_group *sg;
415 if ((sg = session_group_find(s)) == NULL)
416 return;
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);
422 xfree(sg);
426 /* Synchronize a session to its session group. */
427 void
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)
434 return;
436 target = NULL;
437 TAILQ_FOREACH(target, &sg->sessions, gentry) {
438 if (target != s)
439 break;
441 session_group_synchronize1(target, s);
444 /* Synchronize a session group to a session. */
445 void
446 session_group_synchronize_from(struct session *target)
448 struct session_group *sg;
449 struct session *s;
451 if ((sg = session_group_find(target)) == NULL)
452 return;
454 TAILQ_FOREACH(s, &sg->sessions, gentry) {
455 if (s != target)
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
463 * stack and alerts.
465 void
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;
474 if (RB_EMPTY(ww))
475 return;
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)
481 session_next(s, 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. */
494 if (s->curw != NULL)
495 s->curw = winlink_find_by_index(&s->windows, s->curw->idx);
496 else
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);
504 if (wl2 != NULL)
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);