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