otp.c revision 0bbfda8a
1/*
2 * Copyright 1992, 2005 Stefan Monnier.
3 *
4 * Author:  Stefan Monnier [ monnier@lia.di.epfl.ch ]
5 * Adapted for use with more than one virtual screen by
6 * Olaf "Rhialto" Seibert <rhialto@falu.nl>.
7 *
8 * $Id: otp.c,v 1.1.1.1 2021/04/11 08:36:52 nia Exp $
9 *
10 * handles all the OnTopPriority-related issues.
11 *
12 */
13
14#include "ctwm.h"
15
16#include <stdio.h>
17#include <stdlib.h>
18#include <assert.h>
19#include <X11/Xatom.h>
20
21#include "otp.h"
22#include "ctwm_atoms.h"
23#include "screen.h"
24#include "util.h"
25#include "icons.h"
26#include "list.h"
27#include "events.h"
28#include "event_handlers.h"
29#include "vscreen.h"
30#include "win_utils.h"
31
32#define DEBUG_OTP       0
33#if DEBUG_OTP
34#define DPRINTF(x)      fprintf x
35#else
36#define DPRINTF(x)
37#endif
38
39#if defined(NDEBUG)
40# define CHECK_OTP      0
41#else
42# define CHECK_OTP      1
43#endif
44
45/* number of priorities known to ctwm: [0..ONTOP_MAX] */
46#define OTP_ZERO 8
47#define OTP_MAX (OTP_ZERO * 2)
48
49/* Shorten code a little */
50#define PRI(owl) OwlEffectivePriority(owl)
51#define PRI_CP(from, to) do {                  \
52            to->pri_base = from->pri_base;     \
53            to->pri_aflags = from->pri_aflags; \
54        } while(0)
55
56struct OtpWinList {
57	OtpWinList *above;
58	OtpWinList *below;
59	TwmWindow  *twm_win;
60	WinType     type;
61	bool        switching;
62	int         pri_base;   // Base priority
63	unsigned    pri_aflags; // Flags that might alter it; OTP_AFLAG_*
64	bool        stashed_aflags;
65};
66
67struct OtpPreferences {
68	name_list  *priorityL[OTP_MAX + 1];
69	int         priority;
70	name_list  *switchingL;
71	bool        switching;
72};
73
74typedef struct Box {
75	int x;
76	int y;
77	int width;
78	int height;
79} Box;
80
81
82static bool OtpCheckConsistencyVS(VirtualScreen *currentvs, Window vroot);
83static void OwlSetAflagMask(OtpWinList *owl, unsigned mask, unsigned setto);
84static void OwlSetAflag(OtpWinList *owl, unsigned flag);
85static void OwlClearAflag(OtpWinList *owl, unsigned flag);
86static void OwlStashAflags(OtpWinList *owl);
87static unsigned OwlGetStashedAflags(OtpWinList *owl, bool *gotit);
88static int OwlEffectivePriority(OtpWinList *owl);
89
90static Box BoxOfOwl(OtpWinList *owl)
91{
92	Box b;
93
94	switch(owl->type) {
95		case IconWin: {
96			Icon *icon = owl->twm_win->icon;
97
98			b.x = icon->w_x;
99			b.y = icon->w_y;
100			b.width = icon->w_width;
101			b.height = icon->w_height;
102			break;
103		}
104		case WinWin: {
105			TwmWindow *twm_win = owl->twm_win;
106
107			b.x = twm_win->frame_x;
108			b.y = twm_win->frame_y;
109			b.width = twm_win->frame_width;
110			b.height = twm_win->frame_height;
111			break;
112		}
113		default:
114			assert(false);
115	}
116	return b;
117}
118
119
120static bool BoxesIntersect(Box *b1, Box *b2)
121{
122	bool interX = (b1->x + b1->width > b2->x) && (b2->x + b2->width > b1->x);
123	bool interY = (b1->y + b1->height > b2->y) && (b2->y + b2->height > b1->y);
124
125	return (interX && interY);
126}
127
128
129static bool isIntersectingWith(OtpWinList *owl1, OtpWinList *owl2)
130{
131	Box b1 = BoxOfOwl(owl1);
132	Box b2 = BoxOfOwl(owl2);
133
134	return BoxesIntersect(&b1, &b2);
135}
136
137
138static bool isOnScreen(OtpWinList *owl)
139{
140	TwmWindow *twm_win = owl->twm_win;
141
142	return (((owl->type == IconWin) ? twm_win->iconified : twm_win->mapped)
143	        && OCCUPY(twm_win, Scr->currentvs->wsw->currentwspc));
144}
145
146
147bool isTransientOf(TwmWindow *trans, TwmWindow *main)
148{
149	return (trans->istransient && trans->transientfor == main->w);
150}
151
152bool isGroupLeader(TwmWindow *twm_win)
153{
154	return ((twm_win->group == 0)
155	        || (twm_win->group == twm_win->w));
156}
157
158bool isGroupLeaderOf(TwmWindow *leader, TwmWindow *twm_win)
159{
160	return (isGroupLeader(leader)
161	        && !isGroupLeader(twm_win)
162	        && (leader->group == twm_win->group));
163}
164
165bool isSmallTransientOf(TwmWindow *trans, TwmWindow *main)
166{
167	int trans_area, main_area;
168
169	if(isTransientOf(trans, main)) {
170		assert(trans->frame);
171		trans_area = trans->frame_width * trans->frame_height;
172		main_area = main->frame_width * main->frame_height;
173
174		return (trans_area < ((main_area * Scr->TransientOnTop) / 100));
175	}
176	else {
177		return false;
178	}
179}
180
181static Window WindowOfOwl(OtpWinList *owl)
182{
183	return (owl->type == IconWin)
184	       ? owl->twm_win->icon->w : owl->twm_win->frame;
185}
186
187bool OtpCheckConsistency(void)
188{
189#if DEBUG_OTP
190	VirtualScreen *tvs;
191	bool result = true;
192
193	for(tvs = Scr->vScreenList; tvs != NULL; tvs = tvs->next) {
194		fprintf(stderr, "OtpCheckConsistencyVS: vs:(x,y)=(%d,%d)\n",
195		        tvs->x, tvs->y);
196		result = result && OtpCheckConsistencyVS(tvs, tvs->window);
197	}
198	return result;
199#else
200	return OtpCheckConsistencyVS(Scr->currentvs, Scr->Root);
201#endif
202}
203
204static bool OtpCheckConsistencyVS(VirtualScreen *currentvs, Window vroot)
205{
206#if CHECK_OTP
207	OtpWinList *owl;
208	TwmWindow *twm_win;
209	Window root, parent, *children;
210	unsigned int nchildren;
211	int priority = 0;
212	int stack = -1;
213	int nwins = 0;
214
215	XQueryTree(dpy, vroot, &root, &parent, &children, &nchildren);
216
217#if DEBUG_OTP
218	{
219		int i;
220		fprintf(stderr, "XQueryTree: %d children:\n", nchildren);
221
222		for(i = 0; i < nchildren; i++) {
223			fprintf(stderr, "[%d]=%x ", i, (unsigned int)children[i]);
224		}
225		fprintf(stderr, "\n");
226	}
227#endif
228
229	for(owl = Scr->bottomOwl; owl != NULL; owl = owl->above) {
230		twm_win = owl->twm_win;
231
232		/* check the back arrows are correct */
233		assert(((owl->type == IconWin) && (owl == twm_win->icon->otp))
234		       || ((owl->type == WinWin) && (owl == twm_win->otp)));
235
236		/* check the doubly linked list's consistency */
237		if(owl->below == NULL) {
238			assert(owl == Scr->bottomOwl);
239		}
240		else {
241			assert(owl->below->above == owl);
242		}
243
244		/* Code already ensures this */
245		assert(owl->pri_base <= OTP_MAX);
246
247		/* List should be bottom->top, so effective pri better ascend */
248		assert(PRI(owl) >= priority);
249		priority = PRI(owl);
250
251#if DEBUG_OTP
252
253		fprintf(stderr, "checking owl: pri %d w=%x stack=%d",
254		        priority, (unsigned int)WindowOfOwl(owl), stack);
255		if(twm_win) {
256			fprintf(stderr, " title=%s occupation=%x ",
257			        twm_win->name,
258			        (unsigned int)twm_win->occupation);
259			if(owl->twm_win->vs) {
260				fprintf(stderr, " vs:(x,y)=(%d,%d)",
261				        twm_win->vs->x,
262				        twm_win->vs->y);
263			}
264			else {
265				fprintf(stderr, " vs:NULL");
266			}
267			if(owl->twm_win->parent_vs) {
268				fprintf(stderr, " parent_vs:(x,y)=(%d,%d)",
269				        twm_win->parent_vs->x,
270				        twm_win->parent_vs->y);
271			}
272			else {
273				fprintf(stderr, " parent_vs:NULL");
274			}
275		}
276		fprintf(stderr, " %s\n", (owl->type == WinWin ? "Window" : "Icon"));
277#endif
278
279		/* count the number of twm windows */
280		if(owl->type == WinWin) {
281			nwins++;
282		}
283
284		if(twm_win->winbox) {
285			/*
286			 * We can't check windows in a WindowBox, since they are
287			 * not direct children of the Root window.
288			 */
289			DPRINTF((stderr, "Can't check this window, it is in a WinBox\n"));
290			continue;
291		}
292
293		/*
294		 * Check only windows from the current vitual screen; the others
295		 * won't show up in the tree from XQueryTree().
296		 */
297		if(currentvs == twm_win->parent_vs) {
298			/* check the window's existence. */
299			Window windowOfOwl = WindowOfOwl(owl);
300
301#if DEBUG_OTP
302			int i;
303			for(i = 0; i < nchildren && windowOfOwl != children[i];) {
304				i++;
305			}
306			fprintf(stderr, "search for owl in stack -> i=%d\n", i);
307			assert(i > stack && "Window not in good place in stack");
308			assert(i < nchildren && "Window was not found in stack");
309			if(0) {
310				char buf[128];
311				snprintf(buf, 128, "xwininfo -all -id %d", (int)windowOfOwl);
312				system(buf);
313			}
314
315			/* we know that this always increases stack (assert i>stack) */
316			stack = i;
317#else /* DEBUG_OTP */
318			/* check against the Xserver's stack */
319			do {
320				stack++;
321				DPRINTF((stderr, "stack++: children[%d] = %x\n", stack,
322				         (unsigned int)children[stack]));
323				assert(stack < nchildren);
324			}
325			while(windowOfOwl != children[stack]);
326#endif /* DEBUG_OTP */
327		}
328	}
329
330	XFree(children);
331
332	/* by decrementing nwins, check that all the wins are in our list */
333	for(twm_win = Scr->FirstWindow; twm_win != NULL; twm_win = twm_win->next) {
334		nwins--;
335	}
336	/* if we just removed a win, it might still be somewhere, hence the -1 */
337	assert((nwins <= 0) && (nwins >= -1));
338#endif
339	return true;
340}
341
342
343static void RemoveOwl(OtpWinList *owl)
344{
345	if(owl->above != NULL) {
346		owl->above->below = owl->below;
347	}
348	if(owl->below != NULL) {
349		owl->below->above = owl->above;
350	}
351	else {
352		Scr->bottomOwl = owl->above;
353	}
354	owl->below = NULL;
355	owl->above = NULL;
356}
357
358
359/**
360 * For the purpose of putting a window above another,
361 * they need to have the same parent, i.e. be in the same
362 * VirtualScreen.
363 */
364static OtpWinList *GetOwlAtOrBelowInVS(OtpWinList *owl, VirtualScreen *vs)
365{
366	while(owl != NULL && owl->twm_win->parent_vs != vs) {
367		owl = owl->below;
368	}
369
370	return owl;
371}
372
373/*
374 * Windows in a box don't really occur in the stacking order of the
375 * root window.
376 * In the OWL list, keep them just on top of their box, in their
377 * respective order of course.
378 * Therefore we may need to update the owl we're going to be above.
379 */
380static OtpWinList *GetOwlAtOrBelowInWinbox(OtpWinList **owlp, WindowBox *wb)
381{
382	OtpWinList *owl = *owlp;
383
384	while(owl != NULL && owl->twm_win->winbox != wb) {
385		owl = owl->below;
386	}
387
388	if(owl == NULL) {
389		/* we have gone below the box: put it just on top of it */
390		*owlp = wb->twmwin->otp;
391	}
392	else {
393		*owlp = owl;
394	}
395	return owl;
396}
397
398
399static void InsertOwlAbove(OtpWinList *owl, OtpWinList *other_owl)
400{
401#if DEBUG_OTP
402	fprintf(stderr, "InsertOwlAbove owl->pri=%d w=0x%x parent_vs:(x,y)=(%d,%d)",
403	        PRI(owl),
404	        (unsigned int)WindowOfOwl(owl),
405	        owl->twm_win->parent_vs->x,
406	        owl->twm_win->parent_vs->y);
407	if(other_owl != NULL) {
408		fprintf(stderr, "\n  other_owl->pri=%d w=0x%x parent_vs:(x,y)=(%d,%d)",
409		        PRI(other_owl),
410		        (unsigned int)WindowOfOwl(other_owl),
411		        owl->twm_win->parent_vs->x,
412		        owl->twm_win->parent_vs->y);
413	}
414	fprintf(stderr, "\n");
415#endif
416
417	assert(owl->above == NULL);
418	assert(owl->below == NULL);
419
420
421	if(other_owl == NULL) {
422		DPRINTF((stderr, "Bottom-most window overall\n"));
423		/* special case for the lowest window overall */
424		assert(PRI(owl) <= PRI(Scr->bottomOwl));
425
426		/* pass the action to the Xserver */
427		XLowerWindow(dpy, WindowOfOwl(owl));
428
429		/* update the list */
430		owl->above = Scr->bottomOwl;
431		owl->above->below = owl;
432		Scr->bottomOwl = owl;
433	}
434	else {
435		WindowBox *winbox = owl->twm_win->winbox;
436		OtpWinList *vs_owl;
437
438		if(winbox != NULL) {
439			vs_owl = GetOwlAtOrBelowInWinbox(&other_owl, winbox);
440		}
441		else {
442
443			vs_owl = GetOwlAtOrBelowInVS(other_owl, owl->twm_win->parent_vs);
444		}
445
446		assert(PRI(owl) >= PRI(other_owl));
447		if(other_owl->above != NULL) {
448			assert(PRI(owl) <= PRI(other_owl->above));
449		}
450
451		if(vs_owl == NULL) {
452			DPRINTF((stderr, "Bottom-most window in VirtualScreen or window box\n"));
453			/* special case for the lowest window in this virtual screen or window box */
454
455			/* pass the action to the Xserver */
456			XLowerWindow(dpy, WindowOfOwl(owl));
457		}
458		else {
459			XWindowChanges xwc;
460			int xwcm;
461
462			DPRINTF((stderr, "General case\n"));
463			/* general case */
464			assert(PRI(vs_owl) <= PRI(other_owl));
465			assert(owl->twm_win->parent_vs == vs_owl->twm_win->parent_vs);
466
467			/* pass the action to the Xserver */
468			xwcm = CWStackMode | CWSibling;
469			xwc.sibling = WindowOfOwl(vs_owl);
470			xwc.stack_mode = Above;
471			XConfigureWindow(dpy, WindowOfOwl(owl), xwcm, &xwc);
472		}
473
474		/* update the list */
475		owl->below = other_owl;
476		owl->above = other_owl->above;
477		owl->below->above = owl;
478		if(owl->above != NULL) {
479			owl->above->below = owl;
480		}
481	}
482}
483
484
485/* should owl stay above other_owl if other_owl was raised ? */
486static bool shouldStayAbove(OtpWinList *owl, OtpWinList *other_owl)
487{
488	return ((owl->type == WinWin)
489	        && (other_owl->type == WinWin)
490	        && isSmallTransientOf(owl->twm_win, other_owl->twm_win));
491}
492
493
494static void RaiseSmallTransientsOfAbove(OtpWinList *owl, OtpWinList *other_owl)
495{
496	OtpWinList *trans_owl, *tmp_owl;
497
498	/* the icons have no transients and we can't have windows below NULL */
499	if((owl->type != WinWin) || other_owl == NULL) {
500		return;
501	}
502
503	/* beware: we modify the list as we scan it. This is the reason for tmp */
504	for(trans_owl = other_owl->below; trans_owl != NULL; trans_owl = tmp_owl) {
505		tmp_owl = trans_owl->below;
506		if(shouldStayAbove(trans_owl, owl)) {
507			RemoveOwl(trans_owl);
508			PRI_CP(owl, trans_owl);
509			InsertOwlAbove(trans_owl, other_owl);
510		}
511	}
512}
513
514
515static OtpWinList *OwlRightBelow(int priority)
516{
517	OtpWinList *owl1, *owl2;
518
519	/* in case there isn't anything below */
520	if(priority <= PRI(Scr->bottomOwl)) {
521		return NULL;
522	}
523
524	for(owl1 = Scr->bottomOwl, owl2 = owl1->above;
525	                (owl2 != NULL) && (PRI(owl2) < priority);
526	                owl1 = owl2, owl2 = owl2->above) {
527		/* nada */;
528	}
529
530	assert(owl2 == owl1->above);
531	assert(PRI(owl1) < priority);
532	assert((owl2 == NULL) || (PRI(owl2) >= priority));
533
534
535	return owl1;
536}
537
538static void InsertOwl(OtpWinList *owl, int where)
539{
540	OtpWinList *other_owl;
541	int priority;
542
543	DPRINTF((stderr, "InsertOwl %s\n",
544	         (where == Above) ? "Above" :
545	         (where == Below) ? "Below" :
546	         "???"));
547	assert(owl->above == NULL);
548	assert(owl->below == NULL);
549	assert((where == Above) || (where == Below));
550
551	priority = PRI(owl) - (where == Above ? 0 : 1);
552
553	if(Scr->bottomOwl == NULL) {
554		/* for the first window: just insert it in the list */
555		Scr->bottomOwl = owl;
556	}
557	else {
558		other_owl = OwlRightBelow(priority + 1);
559
560		/* make sure other_owl is not one of the transients */
561		while((other_owl != NULL)
562		                && shouldStayAbove(other_owl, owl)) {
563			PRI_CP(owl, other_owl);
564
565			other_owl = other_owl->below;
566		}
567
568		/* raise the transient windows that should stay on top */
569		RaiseSmallTransientsOfAbove(owl, other_owl);
570
571		/* now go ahead and put the window where it should go */
572		InsertOwlAbove(owl, other_owl);
573	}
574}
575
576
577static void SetOwlPriority(OtpWinList *owl, int new_pri, int where)
578{
579	DPRINTF((stderr, "SetOwlPriority(%d)\n", new_pri));
580
581	/* make sure the values are within bounds */
582	if(new_pri < 0) {
583		new_pri = 0;
584	}
585	if(new_pri > OTP_MAX) {
586		new_pri = OTP_MAX;
587	}
588
589	RemoveOwl(owl);
590	owl->pri_base = new_pri;
591	InsertOwl(owl, where);
592
593	assert(owl->pri_base == new_pri);
594}
595
596
597/*
598 * Shift transients of a window to a new [base] priority, preparatory to
599 * moving that window itself there.
600 */
601static void TryToMoveTransientsOfTo(OtpWinList *owl, int priority, int where)
602{
603	OtpWinList *other_owl;
604
605	/* the icons have no transients */
606	if(owl->type != WinWin) {
607		return;
608	}
609
610	/*
611	 * We start looking for transients of owl at the bottom of its OTP
612	 * layer.
613	 */
614	other_owl = OwlRightBelow(PRI(owl));
615	other_owl = (other_owl == NULL) ? Scr->bottomOwl : other_owl->above;
616	assert(PRI(other_owl) >= PRI(owl));
617
618	/* !beware! we're changing the list as we scan it, hence the tmp_owl */
619	while((other_owl != NULL) && (PRI(other_owl) == PRI(owl))) {
620		OtpWinList *tmp_owl = other_owl->above;
621		if((other_owl->type == WinWin)
622		                && isTransientOf(other_owl->twm_win, owl->twm_win)) {
623			/* Copy in our flags so it winds up in the right place */
624			other_owl->pri_aflags = owl->pri_aflags;
625			SetOwlPriority(other_owl, priority, where);
626		}
627		other_owl = tmp_owl;
628	}
629}
630
631static void TryToSwitch(OtpWinList *owl, int where)
632{
633	int priority;
634
635	if(!owl->switching) {
636		return;
637	}
638
639	/*
640	 * Switching is purely an adjustment to the base priority, so we
641	 * don't need to figure stuff based on the effective.
642	 */
643	priority = OTP_MAX - owl->pri_base;
644	if(((where == Above) && (priority > owl->pri_base)) ||
645	                ((where == Below) && (priority < owl->pri_base))) {
646		/*
647		 * TTMTOT() before changing pri_base since it uses the current
648		 * state to find the transients.
649		 */
650		TryToMoveTransientsOfTo(owl, priority, where);
651		owl->pri_base = priority;
652	}
653}
654
655static void RaiseOwl(OtpWinList *owl)
656{
657	TryToSwitch(owl, Above);
658	RemoveOwl(owl);
659	InsertOwl(owl, Above);
660}
661
662
663static void LowerOwl(OtpWinList *owl)
664{
665	TryToSwitch(owl, Below);
666	RemoveOwl(owl);
667	InsertOwl(owl, Below);
668}
669
670static bool isHiddenBy(OtpWinList *owl, OtpWinList *other_owl)
671{
672	/* doesn't check that owl is on screen */
673	return (isOnScreen(other_owl)
674	        && isIntersectingWith(owl, other_owl));
675}
676
677static void TinyRaiseOwl(OtpWinList *owl)
678{
679	OtpWinList *other_owl = owl->above;
680
681	while((other_owl != NULL) && (PRI(other_owl) == PRI(owl))) {
682		if(isHiddenBy(owl, other_owl)
683		                && !shouldStayAbove(other_owl, owl)) {
684			RemoveOwl(owl);
685			RaiseSmallTransientsOfAbove(owl, other_owl);
686			InsertOwlAbove(owl, other_owl);
687			return;
688		}
689		else {
690			other_owl = other_owl->above;
691		}
692	}
693}
694
695static void TinyLowerOwl(OtpWinList *owl)
696{
697	OtpWinList *other_owl = owl->below;
698
699	while((other_owl != NULL) && (PRI(other_owl) == PRI(owl))) {
700		if(isHiddenBy(owl, other_owl)) {
701			RemoveOwl(owl);
702			InsertOwlAbove(owl, other_owl->below);
703			return;
704		}
705		else {
706			other_owl = other_owl->below;
707		}
708	}
709}
710
711static void RaiseLowerOwl(OtpWinList *owl)
712{
713	OtpWinList *other_owl;
714	int priority;
715
716	/*
717	 * abs(effective pri)
718	 *
719	 * XXX Why?  This seems like it's encoding the assumption
720	 * "f.raiselower should assume any negative [user-level] priorities
721	 * are a result of a window that should be positive being switched,
722	 * and we should switch it positive before raising if we need to", or
723	 * some such.
724	 */
725	priority = MAX(PRI(owl), OTP_MAX - PRI(owl));
726
727	for(other_owl = owl->above;
728	                (other_owl != NULL) && (PRI(other_owl) <= priority);
729	                other_owl = other_owl->above) {
730		if(isHiddenBy(owl, other_owl)
731		                && !shouldStayAbove(other_owl, owl)) {
732			RaiseOwl(owl);
733			return;
734		}
735	}
736	LowerOwl(owl);
737}
738
739
740void OtpRaise(TwmWindow *twm_win, WinType wintype)
741{
742	OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp;
743	assert(owl != NULL);
744
745	RaiseOwl(owl);
746
747	OtpCheckConsistency();
748#ifdef EWMH
749	EwmhSet_NET_CLIENT_LIST_STACKING();
750#endif /* EWMH */
751}
752
753
754void OtpLower(TwmWindow *twm_win, WinType wintype)
755{
756	OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp;
757	assert(owl != NULL);
758
759	LowerOwl(owl);
760
761	OtpCheckConsistency();
762#ifdef EWMH
763	EwmhSet_NET_CLIENT_LIST_STACKING();
764#endif /* EWMH */
765}
766
767
768void OtpRaiseLower(TwmWindow *twm_win, WinType wintype)
769{
770	OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp;
771	assert(owl != NULL);
772
773	RaiseLowerOwl(owl);
774
775	OtpCheckConsistency();
776#ifdef EWMH
777	EwmhSet_NET_CLIENT_LIST_STACKING();
778#endif /* EWMH */
779}
780
781
782void OtpTinyRaise(TwmWindow *twm_win, WinType wintype)
783{
784	OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp;
785	assert(owl != NULL);
786
787	TinyRaiseOwl(owl);
788
789	OtpCheckConsistency();
790#ifdef EWMH
791	EwmhSet_NET_CLIENT_LIST_STACKING();
792#endif /* EWMH */
793}
794
795
796void OtpTinyLower(TwmWindow *twm_win, WinType wintype)
797{
798	OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp;
799	assert(owl != NULL);
800
801	TinyLowerOwl(owl);
802
803	OtpCheckConsistency();
804#ifdef EWMH
805	EwmhSet_NET_CLIENT_LIST_STACKING();
806#endif /* EWMH */
807}
808
809
810/*
811 * XCirculateSubwindows() is complicated by the fact that it restacks only
812 * in case of overlapping windows. Therefore it seems easier to not
813 * try to emulate that but to leave it to the X server.
814 *
815 * If XCirculateSubwindows() actually does something, it sends a
816 * CirculateNotify event, but you only receive it if
817 * SubstructureNotifyMask is selected on the root window.
818 * However... if that is done from the beginning, for some reason all
819 * windows disappear when ctwm starts or exits.
820 * Maybe SubstructureNotifyMask interferes with SubstructureRedirectMask?
821 *
822 * To get around that, the SubstructureNotifyMask is selected only
823 * temporarily here when wanted.
824 */
825
826void OtpCirculateSubwindows(VirtualScreen *vs, int direction)
827{
828	Window w = vs->window;
829	XWindowAttributes winattrs;
830	Bool circulated;
831
832	DPRINTF((stderr, "OtpCirculateSubwindows %d\n", direction));
833
834	XGetWindowAttributes(dpy, w, &winattrs);
835	XSelectInput(dpy, w, winattrs.your_event_mask | SubstructureNotifyMask);
836	XCirculateSubwindows(dpy, w, direction);
837	XSelectInput(dpy, w, winattrs.your_event_mask);
838	/*
839	 * Now we should get the CirculateNotify event.
840	 * It usually seems to arrive soon enough, but just to be sure, look
841	 * ahead in the message queue to see if it can be expedited.
842	 */
843	circulated = XCheckTypedWindowEvent(dpy, w, CirculateNotify, &Event);
844	if(circulated) {
845		HandleCirculateNotify();
846	}
847}
848
849/*
850 * Update our list of Owls after the Circulate action, and also
851 * enforce the priority by possibly restacking the window again.
852 */
853
854void OtpHandleCirculateNotify(VirtualScreen *vs, TwmWindow *twm_win,
855                              WinType wintype, int place)
856{
857	switch(place) {
858		case PlaceOnTop:
859			OtpRaise(twm_win, wintype);
860			break;
861		case PlaceOnBottom:
862			OtpLower(twm_win, wintype);
863			break;
864		default:
865			DPRINTF((stderr, "OtpHandleCirculateNotify: place=%d\n", place));
866			assert(0 &&
867			       "OtpHandleCirculateNotify: place equals PlaceOnTop nor PlaceOnBottom");
868	}
869}
870
871void OtpSetPriority(TwmWindow *twm_win, WinType wintype, int new_pri, int where)
872{
873	OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp;
874	int priority = OTP_ZERO + new_pri;
875
876	DPRINTF((stderr, "OtpSetPriority: new_pri=%d\n", new_pri));
877	assert(owl != NULL);
878
879	if(twm_win->winbox != NULL || twm_win->iswinbox) {
880		return;
881	}
882
883	if(ABS(new_pri) > OTP_ZERO) {
884		DPRINTF((stderr, "invalid OnTopPriority value: %d\n", new_pri));
885	}
886	else {
887		TryToMoveTransientsOfTo(owl, priority, where);
888		SetOwlPriority(owl, priority, where);
889	}
890
891	OtpCheckConsistency();
892}
893
894
895void OtpChangePriority(TwmWindow *twm_win, WinType wintype, int relpriority)
896{
897	OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp;
898	int priority = owl->pri_base + relpriority;
899	int where;
900
901	if(twm_win->winbox != NULL || twm_win->iswinbox) {
902		return;
903	}
904
905	where = relpriority < 0 ? Below : Above;
906
907	TryToMoveTransientsOfTo(owl, priority, where);
908	SetOwlPriority(owl, priority, where);
909
910	OtpCheckConsistency();
911}
912
913
914void OtpSwitchPriority(TwmWindow *twm_win, WinType wintype)
915{
916	OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp;
917	int priority = OTP_MAX - owl->pri_base;
918	int where;
919
920	assert(owl != NULL);
921
922	if(twm_win->winbox != NULL || twm_win->iswinbox) {
923		return;
924	}
925
926	where = priority < OTP_ZERO ? Below : Above;
927	TryToMoveTransientsOfTo(owl, priority, where);
928	SetOwlPriority(owl, priority, where);
929
930	OtpCheckConsistency();
931}
932
933
934void OtpToggleSwitching(TwmWindow *twm_win, WinType wintype)
935{
936	OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp;
937	assert(owl != NULL);
938
939	if(twm_win->winbox != NULL || twm_win->iswinbox) {
940		return;
941	}
942
943	owl->switching = !owl->switching;
944
945	OtpCheckConsistency();
946}
947
948
949/*
950 * This is triggered as a result of a StackMode ConfigureRequest.  We
951 * choose to interpret this as restacking relative to the base
952 * priorities, since all the alterations are EWMH-related, and those
953 * should probably override.
954 *
955 * XXX Or should they?  Maybe we should alter until our effective is
956 * positioned as desired relative to their effective?  This may also need
957 * revisiting if we grow alterations that aren't a result of EWMH stuff.
958 */
959void OtpForcePlacement(TwmWindow *twm_win, int where, TwmWindow *other_win)
960{
961	OtpWinList *owl = twm_win->otp;
962	OtpWinList *other_owl = other_win->otp;
963
964	assert(twm_win->otp != NULL);
965	assert(other_win->otp != NULL);
966
967	if(where == BottomIf) {
968		where = Below;
969	}
970	if(where != Below) {
971		where = Above;
972	}
973
974	/* remove the owl to change it */
975	RemoveOwl(owl);
976
977	/*
978	 * Base our priority base off that other win.  Don't use PRI_CP since
979	 * we shouldn't suddenly get its flags as well.
980	 */
981	owl->pri_base = other_owl->pri_base;
982
983	/* put the owl back into the list */
984	if(where == Below) {
985		other_owl = other_owl->below;
986	}
987	InsertOwlAbove(owl, other_owl);
988
989	OtpCheckConsistency();
990}
991
992
993static void ApplyPreferences(OtpPreferences *prefs, OtpWinList *owl)
994{
995	int i;
996	TwmWindow *twm_win = owl->twm_win;
997
998	/* check PrioritySwitch */
999	if(LookInList(prefs->switchingL, twm_win->name, &twm_win->class)) {
1000		owl->switching = !prefs->switching;
1001	}
1002
1003	/* check OnTopPriority */
1004	for(i = 0; i <= OTP_MAX; i++) {
1005		if(LookInList(prefs->priorityL[i],
1006		                twm_win->name, &twm_win->class)) {
1007			owl->pri_base = i;
1008		}
1009	}
1010}
1011
1012
1013/*
1014 * Reset stuff based on preferences; called during property changes if
1015 * AutoPriority set.
1016 */
1017static void RecomputeOwlPrefs(OtpPreferences *prefs, OtpWinList *owl)
1018{
1019	int old_pri;
1020
1021	old_pri = owl->pri_base;
1022	ApplyPreferences(prefs, owl);
1023	if(old_pri != owl->pri_base) {
1024		RemoveOwl(owl);
1025		InsertOwl(owl, Above);
1026
1027		/*
1028		 * Stash flags if we don't have any yet, since we just changed
1029		 * the priority.
1030		 */
1031		if(!owl->stashed_aflags) {
1032			OwlStashAflags(owl);
1033		}
1034
1035#ifdef EWMH
1036		/* Let other things know we did something with stacking */
1037		EwmhSet_NET_WM_STATE(owl->twm_win, EWMH_STATE_ABOVE);
1038#endif
1039	}
1040}
1041
1042void OtpRecomputePrefs(TwmWindow *twm_win)
1043{
1044	assert(twm_win->otp != NULL);
1045
1046	RecomputeOwlPrefs(Scr->OTP, twm_win->otp);
1047	if(twm_win->icon != NULL) {
1048		RecomputeOwlPrefs(Scr->IconOTP, twm_win->icon->otp);
1049	}
1050
1051	OtpCheckConsistency();
1052}
1053
1054
1055static void free_OtpWinList(OtpWinList *owl)
1056{
1057	assert(owl->above == NULL);
1058	assert(owl->below == NULL);
1059	free(owl);
1060}
1061
1062
1063void OtpRemove(TwmWindow *twm_win, WinType wintype)
1064{
1065	OtpWinList **owlp;
1066	owlp = (wintype == IconWin) ? &twm_win->icon->otp : &twm_win->otp;
1067
1068	assert(*owlp != NULL);
1069
1070	RemoveOwl(*owlp);
1071	free_OtpWinList(*owlp);
1072	*owlp = NULL;
1073
1074	OtpCheckConsistency();
1075}
1076
1077
1078static OtpWinList *new_OtpWinList(TwmWindow *twm_win,
1079                                  WinType wintype,
1080                                  bool switching,
1081                                  int priority)
1082{
1083	OtpWinList *owl = malloc(sizeof(OtpWinList));
1084
1085	owl->above = NULL;
1086	owl->below = NULL;
1087	owl->twm_win = twm_win;
1088	owl->type = wintype;
1089	owl->switching = switching;
1090	owl->pri_base = priority;
1091	owl->pri_aflags = 0;
1092
1093	/*
1094	 * We never need to stash anything for icons, they don't persist
1095	 * across restart anyway.  So pretend we did stash already to
1096	 * discourage other code from trying to stash.
1097	 */
1098	owl->stashed_aflags = (wintype == WinWin ? false : true);
1099
1100	return owl;
1101}
1102
1103static OtpWinList *AddNewOwl(TwmWindow *twm_win, WinType wintype,
1104                             OtpWinList *parent)
1105{
1106	OtpWinList *owl;
1107	OtpPreferences *prefs = (wintype == IconWin) ? Scr->IconOTP : Scr->OTP;
1108
1109	/* make the new owl */
1110	owl = new_OtpWinList(twm_win, wintype,
1111	                     prefs->switching, prefs->priority);
1112
1113	/* inherit the default attributes from the parent window if appropriate */
1114	if(parent != NULL) {
1115		PRI_CP(parent, owl);
1116		owl->switching = parent->switching;
1117	}
1118
1119	/* now see if the preferences have something to say */
1120	if(!(parent != NULL && twm_win->istransient)) {
1121		ApplyPreferences(prefs, owl);
1122	}
1123
1124#ifdef EWMH
1125	/* If nothing came in, EWMH might have something to say */
1126	if(owl->pri_base == 0) {
1127		owl->pri_base = EwmhGetInitPriority(twm_win) + OTP_ZERO;
1128	}
1129#endif
1130
1131	/*
1132	 * Initialize flags.  Right now, the only stashed flags are related
1133	 * to EWMH requests, so in a sense this whole thing could be dropped
1134	 * under #ifdef.  But I'll assume that might not always be the case,
1135	 * so for now the !(EWMH) case is just a twisty noop.
1136	 */
1137	{
1138		bool gotflags = false;
1139		unsigned aflags = 0, fromstash = 0;
1140
1141		aflags = OwlGetStashedAflags(owl, &gotflags);
1142
1143#ifdef EWMH
1144		fromstash = (OTP_AFLAG_ABOVE | OTP_AFLAG_BELOW);
1145#endif
1146
1147		if(gotflags) {
1148			/*
1149			 * Got stashed OTP flags; use 'em.  Explicitly mask in only
1150			 * the flags we're caring about; the others aren't telling us
1151			 * info we need to persist.
1152			 */
1153			aflags &= fromstash;
1154		}
1155
1156#ifdef EWMH
1157		/* FULLSCREEN we get from the normal EWMH prop no matter what */
1158		if(twm_win->ewmhFlags & EWMH_STATE_FULLSCREEN) {
1159			aflags |= OTP_AFLAG_FULLSCREEN;
1160		}
1161
1162		if(!gotflags) {
1163			/* Nothing from OTP about above/below; check EWMH */
1164			aflags = 0;
1165			if(twm_win->ewmhFlags & EWMH_STATE_ABOVE) {
1166				aflags |= OTP_AFLAG_ABOVE;
1167			}
1168			if(twm_win->ewmhFlags & EWMH_STATE_BELOW) {
1169				aflags |= OTP_AFLAG_BELOW;
1170			}
1171		}
1172#endif // EWMH
1173
1174		/* Set whatever we figured */
1175		owl->pri_aflags |= aflags;
1176		owl->stashed_aflags = gotflags;
1177
1178		/* If we set a priority or flags, we should stash away flags */
1179		if((PRI(owl) != OTP_ZERO || owl->pri_aflags != 0)
1180		                && !owl->stashed_aflags) {
1181			OwlStashAflags(owl);
1182		}
1183	}
1184
1185	/* finally put the window where it should go */
1186	InsertOwl(owl, Above);
1187
1188	return owl;
1189}
1190
1191void OtpAdd(TwmWindow *twm_win, WinType wintype)
1192{
1193	TwmWindow *other_win;
1194	OtpWinList *parent = NULL;
1195	OtpWinList **owlp;
1196	owlp = (wintype == IconWin) ? &twm_win->icon->otp : &twm_win->otp;
1197
1198	assert(*owlp == NULL);
1199
1200	/* windows in boxes *must* inherit priority from the box */
1201	if(twm_win->winbox) {
1202		parent = twm_win->winbox->twmwin->otp;
1203		parent->switching = false;
1204	}
1205	/* in case it's a transient, find the parent */
1206	else if(wintype == WinWin && (twm_win->istransient
1207	                              || !isGroupLeader(twm_win))) {
1208		other_win = Scr->FirstWindow;
1209		while(other_win != NULL
1210		                && !isTransientOf(twm_win, other_win)
1211		                && !isGroupLeaderOf(other_win, twm_win)) {
1212			other_win = other_win->next;
1213		}
1214		if(other_win != NULL) {
1215			parent = other_win->otp;
1216		}
1217	}
1218
1219	/* make the new owl */
1220	*owlp = AddNewOwl(twm_win, wintype, parent);
1221
1222	assert(*owlp != NULL);
1223	OtpCheckConsistency();
1224}
1225
1226void OtpReassignIcon(TwmWindow *twm_win, Icon *old_icon)
1227{
1228	if(old_icon != NULL) {
1229		/* Transfer OWL to new Icon */
1230		Icon *new_icon = twm_win->icon;
1231		if(new_icon != NULL) {
1232			new_icon->otp = old_icon->otp;
1233			old_icon->otp = NULL;
1234		}
1235	}
1236	else {
1237		/* Create a new OWL for this Icon */
1238		OtpAdd(twm_win, IconWin);
1239	}
1240}
1241
1242void OtpFreeIcon(TwmWindow *twm_win)
1243{
1244	/* Remove the icon's OWL, if any */
1245	Icon *cur_icon = twm_win->icon;
1246	if(cur_icon != NULL) {
1247		OtpRemove(twm_win, IconWin);
1248	}
1249}
1250
1251name_list **OtpScrSwitchingL(ScreenInfo *scr, WinType wintype)
1252{
1253	OtpPreferences *prefs = (wintype == IconWin) ? scr->IconOTP : scr->OTP;
1254
1255	assert(prefs != NULL);
1256
1257	return &(prefs->switchingL);
1258}
1259
1260
1261void OtpScrSetSwitching(ScreenInfo *scr, WinType wintype, bool switching)
1262{
1263#ifndef NDEBUG
1264	OtpPreferences *prefs = (wintype == IconWin) ? scr->IconOTP : scr->OTP;
1265
1266	assert(prefs != NULL);
1267#endif
1268
1269	scr->OTP->switching = switching;
1270}
1271
1272
1273void OtpScrSetZero(ScreenInfo *scr, WinType wintype, int priority)
1274{
1275	OtpPreferences *prefs = (wintype == IconWin) ? scr->IconOTP : scr->OTP;
1276
1277	assert(prefs != NULL);
1278
1279	if(ABS(priority) > OTP_ZERO) {
1280		fprintf(stderr, "invalid OnTopPriority value: %d\n", priority);
1281		return;
1282	}
1283
1284	prefs->priority = priority + OTP_ZERO;
1285}
1286
1287
1288name_list **OtpScrPriorityL(ScreenInfo *scr, WinType wintype, int priority)
1289{
1290	OtpPreferences *prefs = (wintype == IconWin) ? scr->IconOTP : scr->OTP;
1291
1292	assert(prefs != NULL);
1293
1294	if(ABS(priority) > OTP_ZERO) {
1295		fprintf(stderr, "invalid OnTopPriority value: %d\n", priority);
1296		priority = 0;
1297	}
1298	return &(prefs->priorityL[priority + OTP_ZERO]);
1299}
1300
1301
1302static OtpPreferences *new_OtpPreferences(void)
1303{
1304	OtpPreferences *pref = malloc(sizeof(OtpPreferences));
1305	int i;
1306
1307	/* initialize default values */
1308	for(i = 0; i <= OTP_MAX; i++) {
1309		pref->priorityL[i] = NULL;
1310	}
1311	pref->priority = OTP_ZERO;
1312	pref->switchingL = NULL;
1313	pref->switching = false;
1314
1315	return pref;
1316}
1317
1318static void free_OtpPreferences(OtpPreferences *pref)
1319{
1320	int i;
1321
1322	FreeList(&pref->switchingL);
1323	for(i = 0; i <= OTP_MAX; i++) {
1324		FreeList(&pref->priorityL[i]);
1325	}
1326
1327	free(pref);
1328}
1329
1330void OtpScrInitData(ScreenInfo *scr)
1331{
1332	if(scr->OTP != NULL) {
1333		free_OtpPreferences(scr->OTP);
1334	}
1335	if(scr->IconOTP != NULL) {
1336		free_OtpPreferences(scr->IconOTP);
1337	}
1338	scr->OTP = new_OtpPreferences();
1339	scr->IconOTP = new_OtpPreferences();
1340}
1341
1342int ReparentWindow(Display *display, TwmWindow *twm_win, WinType wintype,
1343                   Window parent, int x, int y)
1344{
1345	int result;
1346	OtpWinList *owl = (wintype == IconWin) ? twm_win->icon->otp : twm_win->otp;
1347	OtpWinList *other = owl->below;
1348	assert(owl != NULL);
1349
1350	DPRINTF((stderr, "ReparentWindow: w=%x type=%d\n",
1351	         (unsigned int)WindowOfOwl(owl), wintype));
1352	result = XReparentWindow(display, WindowOfOwl(owl), parent, x, y);
1353	/* The raise was already done by XReparentWindow, so this call
1354	   just re-places the window at the right spot in the list
1355	   and enforces priority settings. */
1356	RemoveOwl(owl);
1357	InsertOwlAbove(owl, other);
1358	OtpCheckConsistency();
1359	return result;
1360}
1361
1362void
1363ReparentWindowAndIcon(Display *display, TwmWindow *twm_win,
1364                      Window parent, int win_x, int win_y,
1365                      int icon_x, int icon_y)
1366{
1367	OtpWinList *win_owl = twm_win->otp;
1368	assert(twm_win->icon != NULL);
1369	OtpWinList *icon_owl = twm_win->icon->otp;
1370	assert(win_owl != NULL);
1371	assert(icon_owl != NULL);
1372	OtpWinList *below_win = win_owl->below;
1373	OtpWinList *below_icon = icon_owl->below;
1374
1375	DPRINTF((stderr, "ReparentWindowAndIcon %x\n", (unsigned int)twm_win->frame));
1376	XReparentWindow(display, twm_win->frame, parent, win_x, win_y);
1377	XReparentWindow(display, twm_win->icon->w, parent, icon_x, icon_y);
1378	/* The raise was already done by XReparentWindow, so this call
1379	   just re-places the window at the right spot in the list
1380	   and enforces priority settings. */
1381	RemoveOwl(win_owl);
1382	RemoveOwl(icon_owl);
1383	if(below_win != icon_owl) {
1384		/*
1385		 * Only insert the window above something if it isn't the icon,
1386		 * because that isn't back yet.
1387		 */
1388		InsertOwlAbove(win_owl, below_win);
1389		InsertOwlAbove(icon_owl, below_icon);
1390	}
1391	else {
1392		/* In such a case, do it in the opposite order. */
1393		InsertOwlAbove(icon_owl, below_icon);
1394		InsertOwlAbove(win_owl, below_win);
1395	}
1396	OtpCheckConsistency();
1397	return;
1398}
1399
1400/* Iterators.  */
1401TwmWindow *OtpBottomWin()
1402{
1403	OtpWinList *owl = Scr->bottomOwl;
1404	while(owl && owl->type != WinWin) {
1405		owl = owl->above;
1406	}
1407	return owl ? owl->twm_win : NULL;
1408}
1409
1410TwmWindow *OtpTopWin()
1411{
1412	OtpWinList *owl = Scr->bottomOwl, *top = NULL;
1413	while(owl) {
1414		if(owl->type == WinWin) {
1415			top = owl;
1416		}
1417		owl = owl->above;
1418	}
1419	return top ? top->twm_win : NULL;
1420}
1421
1422TwmWindow *OtpNextWinUp(TwmWindow *twm_win)
1423{
1424	OtpWinList *owl = twm_win->otp->above;
1425	while(owl && owl->type != WinWin) {
1426		owl = owl->above;
1427	}
1428	return owl ? owl->twm_win : NULL;
1429}
1430
1431TwmWindow *OtpNextWinDown(TwmWindow *twm_win)
1432{
1433	OtpWinList *owl = twm_win->otp->below;
1434	while(owl && owl->type != WinWin) {
1435		owl = owl->below;
1436	}
1437	return owl ? owl->twm_win : NULL;
1438}
1439
1440
1441/*
1442 * Stuff for messing with pri_aflags
1443 */
1444/* Set the masked bits to exactly what's given */
1445void
1446OtpSetAflagMask(TwmWindow *twm_win, unsigned mask, unsigned setto)
1447{
1448	assert(twm_win != NULL);
1449	assert(twm_win->otp != NULL);
1450	OwlSetAflagMask(twm_win->otp, mask, setto);
1451}
1452
1453static void
1454OwlSetAflagMask(OtpWinList *owl, unsigned mask, unsigned setto)
1455{
1456	assert(owl != NULL);
1457
1458	owl->pri_aflags &= ~mask;
1459	owl->pri_aflags |= (setto & mask);
1460	OwlStashAflags(owl);
1461}
1462
1463/* Set/clear individual ones */
1464void
1465OtpSetAflag(TwmWindow *twm_win, unsigned flag)
1466{
1467	assert(twm_win != NULL);
1468	assert(twm_win->otp != NULL);
1469
1470	OwlSetAflag(twm_win->otp, flag);
1471}
1472
1473static void
1474OwlSetAflag(OtpWinList *owl, unsigned flag)
1475{
1476	assert(owl != NULL);
1477
1478	owl->pri_aflags |= flag;
1479	OwlStashAflags(owl);
1480}
1481
1482void
1483OtpClearAflag(TwmWindow *twm_win, unsigned flag)
1484{
1485	assert(twm_win != NULL);
1486	assert(twm_win->otp != NULL);
1487
1488	OwlClearAflag(twm_win->otp, flag);
1489}
1490
1491static void
1492OwlClearAflag(OtpWinList *owl, unsigned flag)
1493{
1494	assert(owl != NULL);
1495
1496	owl->pri_aflags &= ~flag;
1497	OwlStashAflags(owl);
1498}
1499
1500/*
1501 * Stash up flags in a property.  We use this to keep track of whether we
1502 * have above/below flags set in the OTP info, so we can know what to set
1503 * when we restart.  Otherwise we can't tell whether stuff like EWMH
1504 * _NET_WM_STATE flags are saying 'above' because the above flag got set
1505 * at some point, or whether other OTP config happens to have already
1506 * raised it.
1507 */
1508void
1509OtpStashAflagsFirstTime(TwmWindow *twm_win)
1510{
1511	if(!twm_win->otp->stashed_aflags) {
1512		OwlStashAflags(twm_win->otp);
1513	}
1514}
1515
1516static void
1517OwlStashAflags(OtpWinList *owl)
1518{
1519	unsigned long of_prop = owl->pri_aflags;
1520
1521	/* Only "real" windows need stashed flags */
1522	if(owl->type != WinWin) {
1523		return;
1524	}
1525
1526	XChangeProperty(dpy, owl->twm_win->w, XA_CTWM_OTP_AFLAGS, XA_INTEGER,
1527	                32, PropModeReplace, (unsigned char *)&of_prop, 1);
1528
1529	owl->stashed_aflags = true;
1530}
1531
1532static unsigned
1533OwlGetStashedAflags(OtpWinList *owl, bool *gotit)
1534{
1535	/* Lotta dummy args */
1536	int ret;
1537	Atom act_type;
1538	int d_fmt;
1539	unsigned long nitems, d_after;
1540	unsigned long aflags, *aflags_p;
1541
1542	/* Only on real windows */
1543	if(owl->type != WinWin) {
1544		*gotit = false;
1545		return 0;
1546	}
1547
1548	ret = XGetWindowProperty(dpy, owl->twm_win->w, XA_CTWM_OTP_AFLAGS, 0, 1,
1549	                         False, XA_INTEGER, &act_type, &d_fmt, &nitems,
1550	                         &d_after, (unsigned char **)&aflags_p);
1551	if(ret == Success && act_type == XA_INTEGER && aflags_p != NULL) {
1552		aflags = *aflags_p;
1553		XFree(aflags_p);
1554		*gotit = true;
1555	}
1556	else {
1557		*gotit = false;
1558		aflags = 0;
1559	}
1560
1561	return aflags;
1562}
1563
1564
1565/*
1566 * Figure where a window should be stacked based on the current world,
1567 * and move it there.  This function pretty much assumes it's not already
1568 * there; callers should generally be figuring that out before calling
1569 * this.
1570 */
1571void
1572OtpRestackWindow(TwmWindow *twm_win)
1573{
1574	OtpWinList *owl = twm_win->otp;
1575
1576	RemoveOwl(owl);
1577	InsertOwl(owl, Above);
1578	OtpCheckConsistency();
1579}
1580
1581
1582
1583/**
1584 * Focus/unfocus backend.  This is used on windows whose stacking is
1585 * focus-dependent (e.g., EWMH fullscreen), to move them and their
1586 * transients around.  For these windows, getting/losing focus is
1587 * practically the same as a f.setpriority, except it's on the calculated
1588 * rather than the base parts.  And it's hard to re-use our existing
1589 * functions to do it because we have to move Scr->Focus before the main
1590 * window changes, but then it's too late to see where all the transients
1591 * were.
1592 *
1593 * There are a number of unpleasant assumptions in here relating to where
1594 * the transients are, and IWBNI we could be smarter and quicker about
1595 * dealing with them.  But this gets us past the simple to cause
1596 * assertion failures, anyway...
1597 */
1598static void
1599OtpFocusWindowBE(TwmWindow *twm_win, int oldprio)
1600{
1601	OtpWinList *owl = twm_win->otp;
1602
1603	// This one comes off the list, and goes back in its new place.
1604	RemoveOwl(owl);
1605	InsertOwl(owl, Above);
1606
1607	// Now root around for any transients of it, and
1608	// nudge them into the new location.  The whole Above/Below thing is
1609	// kinda a heavy-handed guess, but...
1610	//
1611	// This is nearly a reimplementation of TryToMoveTransientsOfTo(),
1612	// but the assumption that we can find the transients by starting
1613	// from where the old priority was in the list turns out to be deeply
1614	// broken.  So just walk the whole thing.  Which isn't ideal, but...
1615	//
1616	// We also need to do loop detection, since otherwise we'll get stuck
1617	// when a window has multiple transients to move around.  Since we
1618	// read from the bottom up, if a window is moving up the stack, then
1619	// its transients move up, and we run into them again and again.
1620	//
1621	// XXX It should not be this freakin' hard to find a window's
1622	// transients.  We should fix that more globally.
1623
1624	// XXX Let's just get a friggin' vector implementation already...
1625	size_t tlsz = 32;  // Should hardly ever be too small
1626	size_t tlused = 0;
1627	OtpWinList **tlst = calloc(tlsz, sizeof(OtpWinList *));
1628	if(tlst == NULL) {
1629		fprintf(stderr, "%s(): realloc() failed\n", __func__);
1630		abort();
1631	}
1632
1633	// Loop through and find them all
1634	OtpWinList *trans = Scr->bottomOwl;
1635	while((trans != NULL)) {
1636		// Gotta pre-stash, since we're sometimes about to move trans.
1637		OtpWinList *next = trans->above;
1638
1639		if((trans->type == WinWin)
1640				&& isTransientOf(trans->twm_win, twm_win)) {
1641			// Got one, stash it
1642			tlst[tlused++] = trans;
1643
1644			// Grow?
1645			if(tlused == tlsz) {
1646				tlsz *= 2;
1647				OtpWinList **tln = realloc(tlst, (tlsz * sizeof(OtpWinList *)));
1648				if(tln == NULL) {
1649					fprintf(stderr, "%s(): realloc() failed\n", __func__);
1650					abort();
1651				}
1652				tlst = tln;
1653			}
1654		}
1655
1656		// And onward
1657		trans = next;
1658	}
1659
1660
1661	// Now loop over them and shuffle them up
1662	for(int i = 0 ; i < tlused ; i++) {
1663		RemoveOwl(tlst[i]);
1664		InsertOwl(tlst[i], Above);
1665	}
1666
1667	free(tlst);
1668
1669	OtpCheckConsistency();
1670}
1671
1672/**
1673 * Unfocus a window.  This needs to know internals of OTP because of
1674 * focus-dependent stacking of it and its transients.
1675 */
1676void
1677OtpUnfocusWindow(TwmWindow *twm_win)
1678{
1679	// Stash where it currently appears to be.  We assume all its
1680	// transients currently have the same effective priority.  See also
1681	// TryToMoveTransientsOfTo() which makes the same assumption.  I'm
1682	// not sure that's entirely warranted...
1683	int oldprio = PRI(twm_win->otp);
1684
1685	// Now tell ourselves it's unfocused
1686	assert(Scr->Focus == twm_win);
1687	Scr->Focus = NULL;
1688
1689	// And do the work
1690	OtpFocusWindowBE(twm_win, oldprio);
1691}
1692
1693/**
1694 * Focus a window.  This needs to know internals of OTP because of
1695 * focus-dependent stacking of it and its transients.
1696 */
1697void
1698OtpFocusWindow(TwmWindow *twm_win)
1699{
1700	// X-ref OtoUnfocusWindow() comments.
1701	int oldprio = PRI(twm_win->otp);
1702
1703	assert(Scr->Focus != twm_win);
1704	Scr->Focus = twm_win;
1705
1706	OtpFocusWindowBE(twm_win, oldprio);
1707}
1708
1709
1710
1711/*
1712 * Calculating effective priority.  Take the base priority (what gets
1713 * set/altered by various OTP config and functions), and then tack on
1714 * whatever alterations more ephemeral things might apply.  This
1715 * currently pretty much means EWMH bits.
1716 */
1717int
1718OtpEffectiveDisplayPriority(TwmWindow *twm_win)
1719{
1720	assert(twm_win != NULL);
1721	assert(twm_win->otp != NULL);
1722
1723	return(OwlEffectivePriority(twm_win->otp) - OTP_ZERO);
1724}
1725
1726int
1727OtpEffectivePriority(TwmWindow *twm_win)
1728{
1729	assert(twm_win != NULL);
1730	assert(twm_win->otp != NULL);
1731
1732	return OwlEffectivePriority(twm_win->otp);
1733}
1734
1735static int
1736OwlEffectivePriority(OtpWinList *owl)
1737{
1738	int pri;
1739
1740	assert(owl != NULL);
1741
1742	pri = owl->pri_base;
1743
1744#ifdef EWMH
1745	/* ABOVE/BELOW states shift a bit relative to the base */
1746	if(owl->pri_aflags & OTP_AFLAG_ABOVE) {
1747		pri += EWMH_PRI_ABOVE;
1748	}
1749	if(owl->pri_aflags & OTP_AFLAG_BELOW) {
1750		pri -= EWMH_PRI_ABOVE;
1751	}
1752
1753	/*
1754	 * Special magic: EWMH says that _BELOW + _DOCK = (just _BELOW).
1755	 * So if both are set, and its base is where we'd expect just a _DOCK
1756	 * to be, try cancelling that out.
1757	 */
1758	{
1759		EwmhWindowType ewt = owl->twm_win->ewmhWindowType;
1760		if((owl->pri_aflags & OTP_AFLAG_BELOW) && (ewt == wt_Dock) &&
1761		                (owl->pri_base == EWMH_PRI_DOCK + OTP_ZERO)) {
1762			pri -= EWMH_PRI_DOCK;
1763		}
1764	}
1765
1766	/*
1767	 * If FULLSCREEN and focused, jam to (nearly; let the user still win
1768	 * if they try) the top.  We also need to handle transients; they
1769	 * might not have focus, but still need to be on top of the window
1770	 * they're coming up transient for, or else they'll be hidden
1771	 * forever.
1772	 */
1773	if(owl->pri_aflags & OTP_AFLAG_FULLSCREEN) {
1774		if(Scr->Focus == owl->twm_win) {
1775			// It's focused, shift it up
1776			pri = EWMH_PRI_FULLSCREEN + OTP_ZERO;
1777		}
1778		else if(owl->twm_win->istransient) {
1779			// It's a transient of something else; if that something else
1780			// has the fullscreen/focus combo, we should pop this up top
1781			// too.  Technically, we should perhaps test whether its
1782			// parent is also OTP_AFLAG_FULLSCREEN, but if the transient
1783			// has it, the parent probably does too.  Worry about that
1784			// detail if it ever becomes a problem.
1785			TwmWindow *parent = GetTwmWindow(owl->twm_win->transientfor);
1786			if(Scr->Focus == parent) {
1787				// Shift this up so we stay on top
1788				pri = EWMH_PRI_FULLSCREEN + OTP_ZERO;
1789			}
1790		}
1791	}
1792#endif
1793
1794	/* Constrain */
1795	pri = MAX(pri, 0);
1796	pri = MIN(pri, OTP_MAX);
1797
1798	return pri;
1799}
1800
1801
1802/*
1803 * Does the priority of a window depend on its focus state?  External
1804 * code needs to know, to know when it might need restacking.
1805 */
1806bool
1807OtpIsFocusDependent(TwmWindow *twm_win)
1808{
1809	assert(twm_win != NULL);
1810	assert(twm_win->otp != NULL);
1811
1812#ifdef EWMH
1813	/*
1814	 * EWMH says _FULLSCREEN and focused windows get shoved to the top;
1815	 * this implies that _FULLSCREEN and _not_ focused don't.  So if the
1816	 * focus is changing, that means we may need to restack.
1817	 */
1818	if(twm_win->otp->pri_aflags & OTP_AFLAG_FULLSCREEN) {
1819		return true;
1820	}
1821#endif
1822
1823	return false;
1824}
1825