xref: /openbsd-src/usr.bin/tmux/session.c (revision ae3cb403620ab940fbaabb3055fac045a63d56b7)
1 /* $OpenBSD: session.c,v 1.78 2017/11/02 18:27:35 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
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.
9  *
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.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/time.h>
21 
22 #include <paths.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <time.h>
27 
28 #include "tmux.h"
29 
30 struct sessions		sessions;
31 static u_int		next_session_id;
32 struct session_groups	session_groups;
33 
34 static void	session_free(int, short, void *);
35 
36 static void	session_lock_timer(int, short, void *);
37 
38 static struct winlink *session_next_alert(struct winlink *);
39 static struct winlink *session_previous_alert(struct winlink *);
40 
41 static void	session_group_remove(struct session *);
42 static void	session_group_synchronize1(struct session *, struct session *);
43 
44 RB_GENERATE(sessions, session, entry, session_cmp);
45 
46 int
47 session_cmp(struct session *s1, struct session *s2)
48 {
49 	return (strcmp(s1->name, s2->name));
50 }
51 
52 RB_GENERATE(session_groups, session_group, entry, session_group_cmp);
53 
54 int
55 session_group_cmp(struct session_group *s1, struct session_group *s2)
56 {
57 	return (strcmp(s1->name, s2->name));
58 }
59 
60 /*
61  * Find if session is still alive. This is true if it is still on the global
62  * sessions list.
63  */
64 int
65 session_alive(struct session *s)
66 {
67 	struct session *s_loop;
68 
69 	RB_FOREACH(s_loop, sessions, &sessions) {
70 		if (s_loop == s)
71 			return (1);
72 	}
73 	return (0);
74 }
75 
76 /* Find session by name. */
77 struct session *
78 session_find(const char *name)
79 {
80 	struct session	s;
81 
82 	s.name = (char *) name;
83 	return (RB_FIND(sessions, &sessions, &s));
84 }
85 
86 /* Find session by id parsed from a string. */
87 struct session *
88 session_find_by_id_str(const char *s)
89 {
90 	const char	*errstr;
91 	u_int		 id;
92 
93 	if (*s != '$')
94 		return (NULL);
95 
96 	id = strtonum(s + 1, 0, UINT_MAX, &errstr);
97 	if (errstr != NULL)
98 		return (NULL);
99 	return (session_find_by_id(id));
100 }
101 
102 /* Find session by id. */
103 struct session *
104 session_find_by_id(u_int id)
105 {
106 	struct session	*s;
107 
108 	RB_FOREACH(s, sessions, &sessions) {
109 		if (s->id == id)
110 			return (s);
111 	}
112 	return (NULL);
113 }
114 
115 /* Create a new session. */
116 struct session *
117 session_create(const char *prefix, const char *name, int argc, char **argv,
118     const char *path, const char *cwd, struct environ *env, struct termios *tio,
119     int idx, u_int sx, u_int sy, char **cause)
120 {
121 	struct session	*s;
122 	struct winlink	*wl;
123 
124 	s = xcalloc(1, sizeof *s);
125 	s->references = 1;
126 	s->flags = 0;
127 
128 	s->cwd = xstrdup(cwd);
129 
130 	s->curw = NULL;
131 	TAILQ_INIT(&s->lastw);
132 	RB_INIT(&s->windows);
133 
134 	s->environ = environ_create();
135 	if (env != NULL)
136 		environ_copy(env, s->environ);
137 
138 	s->options = options_create(global_s_options);
139 	s->hooks = hooks_create(global_hooks);
140 
141 	status_update_saved(s);
142 
143 	s->tio = NULL;
144 	if (tio != NULL) {
145 		s->tio = xmalloc(sizeof *s->tio);
146 		memcpy(s->tio, tio, sizeof *s->tio);
147 	}
148 
149 	s->sx = sx;
150 	s->sy = sy;
151 
152 	if (name != NULL) {
153 		s->name = xstrdup(name);
154 		s->id = next_session_id++;
155 	} else {
156 		s->name = NULL;
157 		do {
158 			s->id = next_session_id++;
159 			free(s->name);
160 			if (prefix != NULL)
161 				xasprintf(&s->name, "%s-%u", prefix, s->id);
162 			else
163 				xasprintf(&s->name, "%u", s->id);
164 		} while (RB_FIND(sessions, &sessions, s) != NULL);
165 	}
166 	RB_INSERT(sessions, &sessions, s);
167 
168 	log_debug("new session %s $%u", s->name, s->id);
169 
170 	if (gettimeofday(&s->creation_time, NULL) != 0)
171 		fatal("gettimeofday failed");
172 	session_update_activity(s, &s->creation_time);
173 
174 	if (argc >= 0) {
175 		wl = session_new(s, NULL, argc, argv, path, cwd, idx, cause);
176 		if (wl == NULL) {
177 			session_destroy(s, __func__);
178 			return (NULL);
179 		}
180 		session_select(s, RB_ROOT(&s->windows)->idx);
181 	}
182 
183 	log_debug("session %s created", s->name);
184 
185 	return (s);
186 }
187 
188 /* Add a reference to a session. */
189 void
190 session_add_ref(struct session *s, const char *from)
191 {
192 	s->references++;
193 	log_debug("%s: %s %s, now %d", __func__, s->name, from, s->references);
194 }
195 
196 /* Remove a reference from a session. */
197 void
198 session_remove_ref(struct session *s, const char *from)
199 {
200 	s->references--;
201 	log_debug("%s: %s %s, now %d", __func__, s->name, from, s->references);
202 
203 	if (s->references == 0)
204 		event_once(-1, EV_TIMEOUT, session_free, s, NULL);
205 }
206 
207 /* Free session. */
208 static void
209 session_free(__unused int fd, __unused short events, void *arg)
210 {
211 	struct session	*s = arg;
212 
213 	log_debug("session %s freed (%d references)", s->name, s->references);
214 
215 	if (s->references == 0) {
216 		environ_free(s->environ);
217 
218 		options_free(s->options);
219 		hooks_free(s->hooks);
220 
221 		free(s->name);
222 		free(s);
223 	}
224 }
225 
226 /* Destroy a session. */
227 void
228 session_destroy(struct session *s, const char *from)
229 {
230 	struct winlink	*wl;
231 
232 	log_debug("session %s destroyed (%s)", s->name, from);
233 	s->curw = NULL;
234 
235 	RB_REMOVE(sessions, &sessions, s);
236 	notify_session("session-closed", s);
237 
238 	free(s->tio);
239 
240 	if (event_initialized(&s->lock_timer))
241 		event_del(&s->lock_timer);
242 
243 	session_group_remove(s);
244 
245 	while (!TAILQ_EMPTY(&s->lastw))
246 		winlink_stack_remove(&s->lastw, TAILQ_FIRST(&s->lastw));
247 	while (!RB_EMPTY(&s->windows)) {
248 		wl = RB_ROOT(&s->windows);
249 		notify_session_window("window-unlinked", s, wl->window);
250 		winlink_remove(&s->windows, wl);
251 	}
252 
253 	free((void *)s->cwd);
254 
255 	session_remove_ref(s, __func__);
256 }
257 
258 /* Check a session name is valid: not empty and no colons or periods. */
259 int
260 session_check_name(const char *name)
261 {
262 	return (*name != '\0' && name[strcspn(name, ":.")] == '\0');
263 }
264 
265 /* Lock session if it has timed out. */
266 static void
267 session_lock_timer(__unused int fd, __unused short events, void *arg)
268 {
269 	struct session	*s = arg;
270 
271 	if (s->flags & SESSION_UNATTACHED)
272 		return;
273 
274 	log_debug("session %s locked, activity time %lld", s->name,
275 	    (long long)s->activity_time.tv_sec);
276 
277 	server_lock_session(s);
278 	recalculate_sizes();
279 }
280 
281 /* Update activity time. */
282 void
283 session_update_activity(struct session *s, struct timeval *from)
284 {
285 	struct timeval	*last = &s->last_activity_time;
286 	struct timeval	 tv;
287 
288 	memcpy(last, &s->activity_time, sizeof *last);
289 	if (from == NULL)
290 		gettimeofday(&s->activity_time, NULL);
291 	else
292 		memcpy(&s->activity_time, from, sizeof s->activity_time);
293 
294 	log_debug("session %s activity %lld.%06d (last %lld.%06d)", s->name,
295 	    (long long)s->activity_time.tv_sec, (int)s->activity_time.tv_usec,
296 	    (long long)last->tv_sec, (int)last->tv_usec);
297 
298 	if (evtimer_initialized(&s->lock_timer))
299 		evtimer_del(&s->lock_timer);
300 	else
301 		evtimer_set(&s->lock_timer, session_lock_timer, s);
302 
303 	if (~s->flags & SESSION_UNATTACHED) {
304 		timerclear(&tv);
305 		tv.tv_sec = options_get_number(s->options, "lock-after-time");
306 		if (tv.tv_sec != 0)
307 			evtimer_add(&s->lock_timer, &tv);
308 	}
309 }
310 
311 /* Find the next usable session. */
312 struct session *
313 session_next_session(struct session *s)
314 {
315 	struct session *s2;
316 
317 	if (RB_EMPTY(&sessions) || !session_alive(s))
318 		return (NULL);
319 
320 	s2 = RB_NEXT(sessions, &sessions, s);
321 	if (s2 == NULL)
322 		s2 = RB_MIN(sessions, &sessions);
323 	if (s2 == s)
324 		return (NULL);
325 	return (s2);
326 }
327 
328 /* Find the previous usable session. */
329 struct session *
330 session_previous_session(struct session *s)
331 {
332 	struct session *s2;
333 
334 	if (RB_EMPTY(&sessions) || !session_alive(s))
335 		return (NULL);
336 
337 	s2 = RB_PREV(sessions, &sessions, s);
338 	if (s2 == NULL)
339 		s2 = RB_MAX(sessions, &sessions);
340 	if (s2 == s)
341 		return (NULL);
342 	return (s2);
343 }
344 
345 /* Create a new window on a session. */
346 struct winlink *
347 session_new(struct session *s, const char *name, int argc, char **argv,
348     const char *path, const char *cwd, int idx, char **cause)
349 {
350 	struct window	*w;
351 	struct winlink	*wl;
352 	struct environ	*env;
353 	const char	*shell;
354 	u_int		 hlimit;
355 
356 	if ((wl = winlink_add(&s->windows, idx)) == NULL) {
357 		xasprintf(cause, "index in use: %d", idx);
358 		return (NULL);
359 	}
360 	wl->session = s;
361 
362 	shell = options_get_string(s->options, "default-shell");
363 	if (*shell == '\0' || areshell(shell))
364 		shell = _PATH_BSHELL;
365 
366 	hlimit = options_get_number(s->options, "history-limit");
367 	env = environ_for_session(s, 0);
368 	w = window_create_spawn(name, argc, argv, path, shell, cwd, env, s->tio,
369 	    s->sx, s->sy, hlimit, cause);
370 	if (w == NULL) {
371 		winlink_remove(&s->windows, wl);
372 		environ_free(env);
373 		return (NULL);
374 	}
375 	winlink_set_window(wl, w);
376 	environ_free(env);
377 	notify_session_window("window-linked", s, w);
378 
379 	session_group_synchronize_from(s);
380 	return (wl);
381 }
382 
383 /* Attach a window to a session. */
384 struct winlink *
385 session_attach(struct session *s, struct window *w, int idx, char **cause)
386 {
387 	struct winlink	*wl;
388 
389 	if ((wl = winlink_add(&s->windows, idx)) == NULL) {
390 		xasprintf(cause, "index in use: %d", idx);
391 		return (NULL);
392 	}
393 	wl->session = s;
394 	winlink_set_window(wl, w);
395 	notify_session_window("window-linked", s, w);
396 
397 	session_group_synchronize_from(s);
398 	return (wl);
399 }
400 
401 /* Detach a window from a session. */
402 int
403 session_detach(struct session *s, struct winlink *wl)
404 {
405 	if (s->curw == wl &&
406 	    session_last(s) != 0 &&
407 	    session_previous(s, 0) != 0)
408 		session_next(s, 0);
409 
410 	wl->flags &= ~WINLINK_ALERTFLAGS;
411 	notify_session_window("window-unlinked", s, wl->window);
412 	winlink_stack_remove(&s->lastw, wl);
413 	winlink_remove(&s->windows, wl);
414 
415 	session_group_synchronize_from(s);
416 
417 	if (RB_EMPTY(&s->windows)) {
418 		session_destroy(s, __func__);
419 		return (1);
420 	}
421 	return (0);
422 }
423 
424 /* Return if session has window. */
425 int
426 session_has(struct session *s, struct window *w)
427 {
428 	struct winlink	*wl;
429 
430 	TAILQ_FOREACH(wl, &w->winlinks, wentry) {
431 		if (wl->session == s)
432 			return (1);
433 	}
434 	return (0);
435 }
436 
437 /*
438  * Return 1 if a window is linked outside this session (not including session
439  * groups). The window must be in this session!
440  */
441 int
442 session_is_linked(struct session *s, struct window *w)
443 {
444 	struct session_group	*sg;
445 
446 	if ((sg = session_group_contains(s)) != NULL)
447 		return (w->references != session_group_count(sg));
448 	return (w->references != 1);
449 }
450 
451 static struct winlink *
452 session_next_alert(struct winlink *wl)
453 {
454 	while (wl != NULL) {
455 		if (wl->flags & WINLINK_ALERTFLAGS)
456 			break;
457 		wl = winlink_next(wl);
458 	}
459 	return (wl);
460 }
461 
462 /* Move session to next window. */
463 int
464 session_next(struct session *s, int alert)
465 {
466 	struct winlink	*wl;
467 
468 	if (s->curw == NULL)
469 		return (-1);
470 
471 	wl = winlink_next(s->curw);
472 	if (alert)
473 		wl = session_next_alert(wl);
474 	if (wl == NULL) {
475 		wl = RB_MIN(winlinks, &s->windows);
476 		if (alert && ((wl = session_next_alert(wl)) == NULL))
477 			return (-1);
478 	}
479 	return (session_set_current(s, wl));
480 }
481 
482 static struct winlink *
483 session_previous_alert(struct winlink *wl)
484 {
485 	while (wl != NULL) {
486 		if (wl->flags & WINLINK_ALERTFLAGS)
487 			break;
488 		wl = winlink_previous(wl);
489 	}
490 	return (wl);
491 }
492 
493 /* Move session to previous window. */
494 int
495 session_previous(struct session *s, int alert)
496 {
497 	struct winlink	*wl;
498 
499 	if (s->curw == NULL)
500 		return (-1);
501 
502 	wl = winlink_previous(s->curw);
503 	if (alert)
504 		wl = session_previous_alert(wl);
505 	if (wl == NULL) {
506 		wl = RB_MAX(winlinks, &s->windows);
507 		if (alert && (wl = session_previous_alert(wl)) == NULL)
508 			return (-1);
509 	}
510 	return (session_set_current(s, wl));
511 }
512 
513 /* Move session to specific window. */
514 int
515 session_select(struct session *s, int idx)
516 {
517 	struct winlink	*wl;
518 
519 	wl = winlink_find_by_index(&s->windows, idx);
520 	return (session_set_current(s, wl));
521 }
522 
523 /* Move session to last used window. */
524 int
525 session_last(struct session *s)
526 {
527 	struct winlink	*wl;
528 
529 	wl = TAILQ_FIRST(&s->lastw);
530 	if (wl == NULL)
531 		return (-1);
532 	if (wl == s->curw)
533 		return (1);
534 
535 	return (session_set_current(s, wl));
536 }
537 
538 /* Set current winlink to wl .*/
539 int
540 session_set_current(struct session *s, struct winlink *wl)
541 {
542 	if (wl == NULL)
543 		return (-1);
544 	if (wl == s->curw)
545 		return (1);
546 
547 	winlink_stack_remove(&s->lastw, wl);
548 	winlink_stack_push(&s->lastw, s->curw);
549 	s->curw = wl;
550 	winlink_clear_flags(wl);
551 	window_update_activity(wl->window);
552 	notify_session("session-window-changed", s);
553 	return (0);
554 }
555 
556 /* Find the session group containing a session. */
557 struct session_group *
558 session_group_contains(struct session *target)
559 {
560 	struct session_group	*sg;
561 	struct session		*s;
562 
563 	RB_FOREACH(sg, session_groups, &session_groups) {
564 		TAILQ_FOREACH(s, &sg->sessions, gentry) {
565 			if (s == target)
566 				return (sg);
567 		}
568 	}
569 	return (NULL);
570 }
571 
572 /* Find session group by name. */
573 struct session_group *
574 session_group_find(const char *name)
575 {
576 	struct session_group	sg;
577 
578 	sg.name = name;
579 	return (RB_FIND(session_groups, &session_groups, &sg));
580 }
581 
582 /* Create a new session group. */
583 struct session_group *
584 session_group_new(const char *name)
585 {
586 	struct session_group	*sg;
587 
588 	if ((sg = session_group_find(name)) != NULL)
589 		return (sg);
590 
591 	sg = xcalloc(1, sizeof *sg);
592 	sg->name = xstrdup(name);
593 	TAILQ_INIT(&sg->sessions);
594 
595 	RB_INSERT(session_groups, &session_groups, sg);
596 	return (sg);
597 }
598 
599 /* Add a session to a session group. */
600 void
601 session_group_add(struct session_group *sg, struct session *s)
602 {
603 	if (session_group_contains(s) == NULL)
604 		TAILQ_INSERT_TAIL(&sg->sessions, s, gentry);
605 }
606 
607 /* Remove a session from its group and destroy the group if empty. */
608 static void
609 session_group_remove(struct session *s)
610 {
611 	struct session_group	*sg;
612 
613 	if ((sg = session_group_contains(s)) == NULL)
614 		return;
615 	TAILQ_REMOVE(&sg->sessions, s, gentry);
616 	if (TAILQ_EMPTY(&sg->sessions)) {
617 		RB_REMOVE(session_groups, &session_groups, sg);
618 		free(sg);
619 	}
620 }
621 
622 /* Count number of sessions in session group. */
623 u_int
624 session_group_count(struct session_group *sg)
625 {
626 	struct session	*s;
627 	u_int		 n;
628 
629 	n = 0;
630 	TAILQ_FOREACH(s, &sg->sessions, gentry)
631 	    n++;
632 	return (n);
633 }
634 
635 /* Synchronize a session to its session group. */
636 void
637 session_group_synchronize_to(struct session *s)
638 {
639 	struct session_group	*sg;
640 	struct session		*target;
641 
642 	if ((sg = session_group_contains(s)) == NULL)
643 		return;
644 
645 	target = NULL;
646 	TAILQ_FOREACH(target, &sg->sessions, gentry) {
647 		if (target != s)
648 			break;
649 	}
650 	if (target != NULL)
651 		session_group_synchronize1(target, s);
652 }
653 
654 /* Synchronize a session group to a session. */
655 void
656 session_group_synchronize_from(struct session *target)
657 {
658 	struct session_group	*sg;
659 	struct session		*s;
660 
661 	if ((sg = session_group_contains(target)) == NULL)
662 		return;
663 
664 	TAILQ_FOREACH(s, &sg->sessions, gentry) {
665 		if (s != target)
666 			session_group_synchronize1(target, s);
667 	}
668 }
669 
670 /*
671  * Synchronize a session with a target session. This means destroying all
672  * winlinks then recreating them, then updating the current window, last window
673  * stack and alerts.
674  */
675 static void
676 session_group_synchronize1(struct session *target, struct session *s)
677 {
678 	struct winlinks		 old_windows, *ww;
679 	struct winlink_stack	 old_lastw;
680 	struct winlink		*wl, *wl2;
681 
682 	/* Don't do anything if the session is empty (it'll be destroyed). */
683 	ww = &target->windows;
684 	if (RB_EMPTY(ww))
685 		return;
686 
687 	/* If the current window has vanished, move to the next now. */
688 	if (s->curw != NULL &&
689 	    winlink_find_by_index(ww, s->curw->idx) == NULL &&
690 	    session_last(s) != 0 && session_previous(s, 0) != 0)
691 		session_next(s, 0);
692 
693 	/* Save the old pointer and reset it. */
694 	memcpy(&old_windows, &s->windows, sizeof old_windows);
695 	RB_INIT(&s->windows);
696 
697 	/* Link all the windows from the target. */
698 	RB_FOREACH(wl, winlinks, ww) {
699 		wl2 = winlink_add(&s->windows, wl->idx);
700 		wl2->session = s;
701 		winlink_set_window(wl2, wl->window);
702 		notify_session_window("window-linked", s, wl2->window);
703 		wl2->flags |= wl->flags & WINLINK_ALERTFLAGS;
704 	}
705 
706 	/* Fix up the current window. */
707 	if (s->curw != NULL)
708 		s->curw = winlink_find_by_index(&s->windows, s->curw->idx);
709 	else
710 		s->curw = winlink_find_by_index(&s->windows, target->curw->idx);
711 
712 	/* Fix up the last window stack. */
713 	memcpy(&old_lastw, &s->lastw, sizeof old_lastw);
714 	TAILQ_INIT(&s->lastw);
715 	TAILQ_FOREACH(wl, &old_lastw, sentry) {
716 		wl2 = winlink_find_by_index(&s->windows, wl->idx);
717 		if (wl2 != NULL)
718 			TAILQ_INSERT_TAIL(&s->lastw, wl2, sentry);
719 	}
720 
721 	/* Then free the old winlinks list. */
722 	while (!RB_EMPTY(&old_windows)) {
723 		wl = RB_ROOT(&old_windows);
724 		wl2 = winlink_find_by_window_id(&s->windows, wl->window->id);
725 		if (wl2 == NULL)
726 			notify_session_window("window-unlinked", s, wl->window);
727 		winlink_remove(&old_windows, wl);
728 	}
729 }
730 
731 /* Renumber the windows across winlinks attached to a specific session. */
732 void
733 session_renumber_windows(struct session *s)
734 {
735 	struct winlink		*wl, *wl1, *wl_new;
736 	struct winlinks		 old_wins;
737 	struct winlink_stack	 old_lastw;
738 	int			 new_idx, new_curw_idx;
739 
740 	/* Save and replace old window list. */
741 	memcpy(&old_wins, &s->windows, sizeof old_wins);
742 	RB_INIT(&s->windows);
743 
744 	/* Start renumbering from the base-index if it's set. */
745 	new_idx = options_get_number(s->options, "base-index");
746 	new_curw_idx = 0;
747 
748 	/* Go through the winlinks and assign new indexes. */
749 	RB_FOREACH(wl, winlinks, &old_wins) {
750 		wl_new = winlink_add(&s->windows, new_idx);
751 		wl_new->session = s;
752 		winlink_set_window(wl_new, wl->window);
753 		wl_new->flags |= wl->flags & WINLINK_ALERTFLAGS;
754 
755 		if (wl == s->curw)
756 			new_curw_idx = wl_new->idx;
757 
758 		new_idx++;
759 	}
760 
761 	/* Fix the stack of last windows now. */
762 	memcpy(&old_lastw, &s->lastw, sizeof old_lastw);
763 	TAILQ_INIT(&s->lastw);
764 	TAILQ_FOREACH(wl, &old_lastw, sentry) {
765 		wl_new = winlink_find_by_window(&s->windows, wl->window);
766 		if (wl_new != NULL)
767 			TAILQ_INSERT_TAIL(&s->lastw, wl_new, sentry);
768 	}
769 
770 	/* Set the current window. */
771 	s->curw = winlink_find_by_index(&s->windows, new_curw_idx);
772 
773 	/* Free the old winlinks (reducing window references too). */
774 	RB_FOREACH_SAFE(wl, winlinks, &old_wins, wl1)
775 		winlink_remove(&old_wins, wl);
776 }
777