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