A tiling window manager
1/*
2 * Copyright (c) 2019 joshua stein <jcs@jcs.org>
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the Free
6 * Software Foundation; either version 2 of the License, or (at your option)
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along with
15 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16 * Place, Suite 330, Boston, MA 02111-1307 USA.
17 */
18
19#include <err.h>
20#include "sdorfehs.h"
21
22rp_vscreen *vscreen_next(void);
23rp_vscreen *vscreen_prev(void);
24
25static int vscreen_access = 1;
26
27void
28init_vscreen(rp_vscreen *v, rp_screen *s)
29{
30 v->screen = s;
31 v->number = numset_request(s->vscreens_numset);
32 v->frames_numset = numset_new();
33 v->numset = numset_new();
34 v->last_access = 0;
35
36 if (v->number == 0)
37 v->name = xstrdup(DEFAULT_VSCREEN_NAME);
38 else
39 v->name = xsprintf("%d", v->number);
40
41 INIT_LIST_HEAD(&v->unmapped_windows);
42 INIT_LIST_HEAD(&v->mapped_windows);
43
44 init_frame_list(v);
45}
46
47void
48vscreen_del(rp_vscreen *v)
49{
50 rp_screen *s = v->screen;
51 rp_vscreen *target = NULL;
52 rp_window *cur;
53
54 if (v->number != 0)
55 target = screen_find_vscreen_by_number(s, 0);
56
57 list_for_each_entry(cur, &rp_mapped_window, node) {
58 if (cur->vscreen == v && target)
59 vscreen_move_window(target, cur);
60 }
61
62 if (s->current_vscreen == v && target)
63 set_current_vscreen(target);
64
65 numset_release(s->vscreens_numset, v->number);
66
67 vscreen_free(v);
68
69 list_del(&v->node);
70 free(v);
71}
72
73rp_vscreen *
74vscreen_next(void)
75{
76 return list_next_entry(rp_current_screen->current_vscreen,
77 &rp_current_screen->vscreens, node);
78}
79
80rp_vscreen *
81vscreen_prev(void)
82{
83 return list_prev_entry(rp_current_screen->current_vscreen,
84 &rp_current_screen->vscreens, node);
85}
86
87void
88vscreen_free(rp_vscreen *v)
89{
90 rp_frame *frame;
91 struct list_head *iter, *tmp;
92
93 list_for_each_safe_entry(frame, iter, tmp, &v->frames, node)
94 frame_free(v, frame);
95}
96
97int
98vscreens_resize(int n)
99{
100 struct list_head *tmp, *iter;
101 rp_vscreen *cur;
102 rp_screen *scr;
103 int x;
104
105 PRINT_DEBUG(("Resizing vscreens from %d to %d\n", defaults.vscreens,
106 n));
107
108 if (n < 1)
109 return 1;
110
111 if (n < defaults.vscreens) {
112 list_for_each_entry(scr, &rp_screens, node) {
113 list_for_each_safe_entry(cur, iter, tmp, &scr->vscreens,
114 node) {
115 if (cur->number < n)
116 continue;
117
118 if (scr->current_vscreen == cur)
119 set_current_vscreen(
120 screen_find_vscreen_by_number(scr,
121 0));
122
123 vscreen_del(cur);
124 }
125 }
126 } else if (n > defaults.vscreens) {
127 list_for_each_entry(scr, &rp_screens, node) {
128 for (x = defaults.vscreens; x < n; x++) {
129 cur = xmalloc(sizeof(rp_vscreen));
130 init_vscreen(cur, scr);
131 list_add_tail(&cur->node, &scr->vscreens);
132 }
133 }
134 }
135
136 defaults.vscreens = n;
137
138 set_atom(rp_glob_screen.root, _net_number_of_desktops, XA_CARDINAL,
139 (unsigned long *)&defaults.vscreens, 1);
140
141 return 0;
142}
143
144/* Returns a pointer to a list of frames. */
145struct list_head *
146vscreen_copy_frameset(rp_vscreen *v)
147{
148 struct list_head *head;
149 rp_frame *cur;
150
151 /* Init our new list. */
152 head = xmalloc(sizeof(struct list_head));
153 INIT_LIST_HEAD(head);
154
155 /* Copy each frame to our new list. */
156 list_for_each_entry(cur, &v->frames, node) {
157 list_add_tail(&(frame_copy(cur))->node, head);
158 }
159
160 return head;
161}
162
163/* Set head as the frameset, deleting the existing one. */
164void
165vscreen_restore_frameset(rp_vscreen *v, struct list_head *head)
166{
167 frameset_free(&v->frames);
168 INIT_LIST_HEAD(&v->frames);
169
170 /* Hook in our new frameset. */
171 list_splice(head, &v->frames);
172}
173
174/* Given a screen, free the frames' numbers from the numset. */
175void
176vscreen_free_nums(rp_vscreen *v)
177{
178 rp_frame *cur;
179
180 list_for_each_entry(cur, &v->frames, node) {
181 numset_release(v->frames_numset, cur->number);
182 }
183}
184
185/*
186 * Given a list of frames, free them, but don't remove their numbers from the
187 * numset.
188 */
189void
190frameset_free(struct list_head *head)
191{
192 rp_frame *frame;
193 struct list_head *iter, *tmp;
194
195 list_for_each_safe_entry(frame, iter, tmp, head, node) {
196 /*
197 * FIXME: what if frames has memory inside its struct that
198 * needs to be freed?
199 */
200 free(frame);
201 }
202}
203
204rp_frame *
205vscreen_get_frame(rp_vscreen *v, int frame_num)
206{
207 rp_frame *cur;
208
209 list_for_each_entry(cur, &v->frames, node) {
210 if (cur->number == frame_num)
211 return cur;
212 }
213
214 return NULL;
215}
216
217rp_frame *
218vscreen_find_frame_by_frame(rp_vscreen *v, rp_frame *f)
219{
220 rp_frame *cur;
221
222 list_for_each_entry(cur, &v->frames, node) {
223 PRINT_DEBUG(("cur=%p f=%p\n", cur, f));
224 if (cur == f)
225 return cur;
226 }
227
228 return NULL;
229}
230
231void
232set_current_vscreen(rp_vscreen *v)
233{
234 rp_window *win;
235 rp_frame *frame;
236
237 if (v == NULL || (v->screen == rp_current_screen &&
238 v->screen->current_vscreen == v))
239 return;
240
241 window_full_screen(NULL);
242
243 list_for_each_entry(frame, &rp_current_vscreen->frames, node)
244 frame->restore_win_number = frame->win_number;
245
246 hide_vscreen_windows(rp_current_vscreen);
247
248 rp_current_screen = v->screen;
249 v->screen->current_vscreen = v;
250 v->last_access = vscreen_access++;
251
252 list_for_each_entry(frame, &rp_current_vscreen->frames, node) {
253 if (frame->restore_win_number == EMPTY)
254 continue;
255
256 win = find_window_number(frame->restore_win_number);
257 if (win && win->vscreen != v) {
258 warnx("restore win for frame %d on incorrect vscreen\n",
259 frame->number);
260 win = NULL;
261 }
262
263 frame->restore_win_number = 0;
264
265 if (!win)
266 /* previously focused window disappeared */
267 win = find_window_for_frame(frame);
268
269 if (!win)
270 continue;
271
272 win->frame_number = frame->number;
273
274 maximize(win);
275 unhide_window(win);
276 }
277
278 set_window_focus(v->screen->key_window);
279
280 if ((frame = current_frame(v)))
281 set_active_frame(frame, 0);
282
283 update_bar(v->screen);
284 show_frame_indicator(0);
285
286 raise_utility_windows();
287
288 vscreen_announce_current(v);
289
290 hook_run(&rp_switch_vscreen_hook);
291}
292
293void
294vscreen_move_window(rp_vscreen *to, rp_window *w)
295{
296 rp_vscreen *from = w->vscreen;
297 rp_frame *f;
298 rp_window_elem *we;
299 struct rp_child_info *child;
300
301 we = vscreen_find_window(&from->mapped_windows, w);
302 if (we == NULL) {
303 PRINT_DEBUG(("Unable to find window in mapped window lists.\n"));
304 return;
305 }
306
307 f = find_windows_frame(w);
308 w->vscreen = to;
309 w->sticky_frame = EMPTY;
310
311 /* forget that this window was in the frame it was in */
312 if (f)
313 f->win_number = EMPTY;
314
315 numset_release(from->numset, we->number);
316 list_del(&we->node);
317
318 we->number = numset_request(to->numset);
319 vscreen_insert_window(&to->mapped_windows, we);
320
321 if (to == rp_current_vscreen)
322 set_active_window_force(w);
323 else
324 hide_window(w);
325
326 /*
327 * Update rp_children so that any new windows from this application
328 * will appear on the vscreen we just moved to
329 */
330 child = get_child_info(w->w, 0);
331 if (!child)
332 return;
333
334 child->frame = current_frame(to);
335 child->vscreen = to;
336 child->screen = to->screen;
337}
338
339struct numset *
340vscreen_get_numset(rp_vscreen *v)
341{
342 return v->numset;
343}
344
345/*
346 * Get the vscreen list and store it in buffer delimiting each window with
347 * delim. mark_start and mark_end will be filled with the text positions for
348 * the start and end of the current window.
349 */
350void
351get_vscreen_list(rp_screen *screen, char *delim, struct sbuf *buffer,
352 int *mark_start, int *mark_end)
353{
354 rp_vscreen *cur, *last;
355
356 if (buffer == NULL)
357 return;
358
359 sbuf_clear(buffer);
360
361 last = screen_last_vscreen(screen);
362 list_for_each_entry(cur, &screen->vscreens, node) {
363 char *fmt;
364 char separator;
365
366 if (cur == screen->current_vscreen) {
367 *mark_start = strlen(sbuf_get(buffer));
368 separator = '*';
369 } else if (cur == last)
370 separator = '+';
371 else
372 separator = '-';
373
374 /*
375 * A hack, pad the vscreen with a space at the beginning and end
376 * if there is no delimiter.
377 */
378 if (!delim)
379 sbuf_concat(buffer, " ");
380
381 fmt = xsprintf("%d%c%s", cur->number, separator, cur->name);
382 sbuf_concat(buffer, fmt);
383 free(fmt);
384
385 /*
386 * A hack, pad the vscreen with a space at the beginning and
387 * end if there is no delimiter.
388 */
389 if (!delim)
390 sbuf_concat(buffer, " ");
391
392 /*
393 * Only put the delimiter between the vscreen, and not after
394 * the the last vscreen.
395 */
396 if (delim && cur->node.next != &screen->vscreens)
397 sbuf_concat(buffer, delim);
398
399 if (cur == screen->current_vscreen)
400 *mark_end = strlen(sbuf_get(buffer));
401 }
402}
403
404void
405vscreen_rename(rp_vscreen *v, char *name)
406{
407 free(v->name);
408 v->name = xstrdup(name);
409}
410
411rp_vscreen *
412vscreen_next_vscreen(rp_vscreen *vscreen)
413{
414 return list_next_entry(vscreen->screen->current_vscreen,
415 &vscreen->screen->vscreens, node);
416}
417
418rp_vscreen *
419vscreen_prev_vscreen(rp_vscreen *vscreen)
420{
421 return list_prev_entry(vscreen->screen->current_vscreen,
422 &vscreen->screen->vscreens, node);
423}
424
425rp_vscreen *
426screen_last_vscreen(rp_screen *screen)
427{
428 int last_access = 0;
429 rp_vscreen *most_recent = NULL;
430 rp_vscreen *cur;
431
432 list_for_each_entry(cur, &screen->vscreens, node) {
433 if (cur != screen->current_vscreen &&
434 cur->last_access > last_access) {
435 most_recent = cur;
436 last_access = cur->last_access;
437 }
438 }
439 return most_recent;
440}
441
442rp_vscreen *
443screen_find_vscreen_by_number(rp_screen *s, int n)
444{
445 rp_vscreen *cur;
446
447 list_for_each_entry(cur, &s->vscreens, node) {
448 if (cur->number == n)
449 return cur;
450 }
451
452 return NULL;
453}
454
455rp_vscreen *
456screen_find_vscreen_by_name(rp_screen *s, char *name, int exact_match)
457{
458 rp_vscreen *cur;
459
460 if (exact_match) {
461 list_for_each_entry(cur, &s->vscreens, node) {
462 if (cur->name && !strcmp(cur->name, name))
463 return cur;
464 }
465 } else {
466 list_for_each_entry(cur, &s->vscreens, node) {
467 if (cur->name && str_comp(name, cur->name,
468 strlen(name)))
469 return cur;
470 }
471 }
472
473 return NULL;
474}
475
476rp_window_elem *
477vscreen_find_window(struct list_head *list, rp_window *win)
478{
479 rp_window_elem *cur;
480
481 list_for_each_entry(cur, list, node) {
482 if (cur->win == win)
483 return cur;
484 }
485
486 return NULL;
487}
488
489rp_window_elem *
490vscreen_find_window_by_number(rp_vscreen *v, int num)
491{
492 rp_window_elem *cur;
493
494 list_for_each_entry(cur, &v->mapped_windows, node) {
495 if (cur->number == num)
496 return cur;
497 }
498
499 return NULL;
500
501}
502
503/*
504 * Insert a window_elem into the correct spot in the vscreen's window list to
505 * preserve window number ordering.
506 */
507void
508vscreen_insert_window(struct list_head *h, rp_window_elem *w)
509{
510 rp_window_elem *cur;
511
512 list_for_each_entry(cur, h, node) {
513 if (cur->number > w->number) {
514 list_add_tail(&w->node, &cur->node);
515 return;
516 }
517 }
518
519 list_add_tail(&w->node, h);
520}
521
522static int
523vscreen_in_list(struct list_head *h, rp_window_elem *w)
524{
525 rp_window_elem *cur;
526
527 list_for_each_entry(cur, h, node) {
528 if (cur == w)
529 return 1;
530 }
531
532 return 0;
533}
534
535/*
536 * If a window_elem's number has changed then the list has to be resorted.
537 */
538void
539vscreen_resort_window(rp_vscreen *v, rp_window_elem *w)
540{
541 /* Only a mapped window can be resorted. */
542 if (!vscreen_in_list(&v->mapped_windows, w)) {
543 PRINT_DEBUG(("Attempting to resort an unmapped window!\n"));
544 return;
545 }
546 list_del(&w->node);
547 vscreen_insert_window(&v->mapped_windows, w);
548}
549
550void
551vscreen_add_window(rp_vscreen *v, rp_window *w)
552{
553 rp_window_elem *we;
554
555 /* Create our container structure for the window. */
556 we = xmalloc(sizeof(rp_window_elem));
557 we->win = w;
558 we->number = -1;
559
560 /* Finally, add it to our list. */
561 list_add_tail(&we->node, &v->unmapped_windows);
562}
563
564void
565vscreen_map_window(rp_vscreen *v, rp_window *win)
566{
567 rp_window_elem *we;
568
569 we = vscreen_find_window(&v->unmapped_windows, win);
570 if (we) {
571 we->number = numset_request(v->numset);
572 list_del(&we->node);
573 vscreen_insert_window(&v->mapped_windows, we);
574 }
575}
576
577void
578vscreen_unmap_window(rp_vscreen *v, rp_window *win)
579{
580 rp_window_elem *we;
581
582 we = vscreen_find_window(&v->mapped_windows, win);
583 if (we) {
584 numset_release(v->numset, we->number);
585 list_move_tail(&we->node, &v->unmapped_windows);
586 }
587}
588
589void
590vscreen_del_window(rp_vscreen *v, rp_window *win)
591{
592 rp_window_elem *cur;
593 struct list_head *iter, *tmp;
594
595 /* The assumption is that a window is unmapped before it's deleted. */
596 list_for_each_safe_entry(cur, iter, tmp, &v->unmapped_windows, node) {
597 if (cur->win == win) {
598 list_del(&cur->node);
599 free(cur);
600 }
601 }
602
603 /*
604 * Make sure the window isn't in the list of mapped windows. This would
605 * mean there is a bug.
606 */
607#ifdef DEBUG
608 list_for_each_entry(cur, &v->mapped_windows, node) {
609 if (cur->win == win)
610 PRINT_DEBUG(("This window wasn't removed from the "
611 "mapped window list.\n"));
612 }
613#endif
614}
615
616rp_window *
617vscreen_last_window(rp_vscreen *v)
618{
619 rp_frame *f;
620 rp_window_elem *most_recent = NULL;
621 rp_window_elem *cur;
622 int last_access = 0;
623
624 f = current_frame(v);
625 list_for_each_entry(cur, &v->mapped_windows, node) {
626 if (cur->win->sticky_frame != EMPTY &&
627 (!f || (cur->win->sticky_frame != f->number)))
628 continue;
629
630 if (cur->win->last_access >= last_access
631 && cur->win != current_window()
632 && !find_windows_frame(cur->win)
633 && (cur->win->vscreen == v || rp_have_xrandr)) {
634 most_recent = cur;
635 last_access = cur->win->last_access;
636 }
637 }
638
639 if (most_recent)
640 return most_recent->win;
641
642 return NULL;
643}
644
645rp_window *
646vscreen_next_window(rp_vscreen *v, rp_window *win)
647{
648 rp_window_elem *cur, *we;
649 rp_frame *f;
650
651 /* If there is no window, then get the last accessed one. */
652 if (win == NULL)
653 return vscreen_last_window(v);
654
655 /*
656 * If we can't find the window, then it's in a different vscreen, so
657 * get the last accessed one in this vscreen.
658 */
659 we = vscreen_find_window(&v->mapped_windows, win);
660 if (we == NULL)
661 return vscreen_last_window(v);
662
663 /*
664 * The window is in this vscreen, so find the next one in the list that
665 * isn't already displayed.
666 */
667 f = current_frame(v);
668 for (cur = list_next_entry(we, &v->mapped_windows, node);
669 cur != we;
670 cur = list_next_entry(cur, &v->mapped_windows, node)) {
671 if (cur->win->sticky_frame != EMPTY &&
672 (!f || (cur->win->sticky_frame != f->number)))
673 continue;
674
675 if (!find_windows_frame(cur->win) &&
676 (cur->win->vscreen == win->vscreen || rp_have_xrandr))
677 return cur->win;
678 }
679
680 return NULL;
681}
682
683rp_window *
684vscreen_prev_window(rp_vscreen *v, rp_window *win)
685{
686 rp_window_elem *cur, *we;
687 rp_frame *f;
688
689 /* If there is no window, then get the last accessed one. */
690 if (win == NULL)
691 return vscreen_last_window(v);
692
693 /*
694 * If we can't find the window, then it's in a different vscreen, so
695 * get the last accessed one in this vscreen.
696 */
697 we = vscreen_find_window(&v->mapped_windows, win);
698 if (we == NULL)
699 return vscreen_last_window(v);
700
701 /*
702 * The window is in this vscreen, so find the previous one in the list
703 * that isn't already displayed.
704 */
705 f = current_frame(v);
706 for (cur = list_prev_entry(we, &v->mapped_windows, node);
707 cur != we;
708 cur = list_prev_entry(cur, &v->mapped_windows, node)) {
709 if (cur->win->sticky_frame != EMPTY &&
710 (!f || (cur->win->sticky_frame != f->number)))
711 continue;
712
713 if (!find_windows_frame(cur->win) && rp_have_xrandr)
714 return cur->win;
715 }
716
717 return NULL;
718}
719
720void
721vscreens_merge(rp_vscreen *from, rp_vscreen *to)
722{
723 rp_window_elem *cur;
724 struct list_head *iter, *tmp;
725
726 /* Merging a vscreen with itself makes no sense. */
727 if (from == to)
728 return;
729
730 /* Move the unmapped windows. */
731 list_for_each_safe_entry(cur, iter, tmp, &from->unmapped_windows, node) {
732 list_del(&cur->node);
733 list_add_tail(&cur->node, &to->unmapped_windows);
734 }
735
736 /* Move the mapped windows. */
737 list_for_each_safe_entry(cur, iter, tmp, &from->mapped_windows, node) {
738 numset_release(from->numset, cur->number);
739 list_del(&cur->node);
740
741 cur->number = numset_request(to->numset);
742 vscreen_insert_window(&to->mapped_windows, cur);
743 }
744}
745
746/* Used by :cother / :iother */
747rp_window *
748vscreen_last_window_by_class(rp_vscreen *v, char *class)
749{
750 int last_access = 0;
751 rp_window_elem *most_recent = NULL;
752 rp_window_elem *cur;
753
754 list_for_each_entry(cur, &v->mapped_windows, node) {
755 if (cur->win->last_access >= last_access
756 && cur->win != current_window()
757 && !find_windows_frame(cur->win)
758 && strcmp(class, cur->win->res_class)) {
759 most_recent = cur;
760 last_access = cur->win->last_access;
761 }
762 }
763
764 if (most_recent)
765 return most_recent->win;
766
767 return NULL;
768}
769
770/* Used by :cother / :iother */
771rp_window *
772vscreen_last_window_by_class_complement(rp_vscreen *v, char *class)
773{
774 int last_access = 0;
775 rp_window_elem *most_recent = NULL;
776 rp_window_elem *cur;
777
778 list_for_each_entry(cur, &v->mapped_windows, node) {
779 if (cur->win->last_access >= last_access
780 && cur->win != current_window()
781 && !find_windows_frame(cur->win)
782 && !strcmp(class, cur->win->res_class)) {
783 most_recent = cur;
784 last_access = cur->win->last_access;
785 }
786 }
787
788 if (most_recent)
789 return most_recent->win;
790
791 return NULL;
792}
793
794void
795vscreen_announce_current(rp_vscreen *v)
796{
797 set_atom(v->screen->root, _net_current_desktop, XA_CARDINAL,
798 (unsigned long *)&v->number, 1);
799}