1/*
2 * Copyright 2014 Olaf Seibert
3 */
4
5/*
6 * Implements some of the Extended Window Manager Hints, as (extremely
7 * poorly) documented at
8 * http://standards.freedesktop.org/wm-spec/wm-spec-1.3.html .
9 * In fact, the wiki page that refers to that as being the current version
10 * (http://www.freedesktop.org/wiki/Specifications/wm-spec/)
11 * neglects to tell us there are newer versions 1.4 and 1.5 at
12 * http://standards.freedesktop.org/wm-spec/wm-spec-1.5.html
13 * (which has a listable directory that I discovered by accident).
14 * The same wiki page also has lots of dead links to a CVS repository.
15 * Nevertheless, it is where one ends up if one starts at
16 * http://www.freedesktop.org/wiki/Specifications/ .
17 *
18 * EWMH is an extension to the ICCCM (Inter-Client Communication
19 * Conventions Manual).
20 * http://tronche.com/gui/x/icccm/
21 *
22 * To fill in lots of details, the source code of other window managers
23 * has been consulted.
24 */
25
26#include "ctwm.h"
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <time.h>
31#include <stdint.h>
32#include <assert.h>
33
34#include <X11/Xatom.h>
35#include <X11/extensions/shape.h>
36
37#include "ctwm_atoms.h"
38#include "ctwm_shutdown.h"
39#include "ewmh_atoms.h"
40#include "screen.h"
41#include "events.h"
42#include "event_handlers.h"
43#include "functions_defs.h"
44#include "icons.h"
45#include "otp.h"
46#include "image.h"
47#include "list.h"
48#include "functions.h"
49#include "occupation.h"
50#include "r_layout.h"
51#include "util.h"
52#include "vscreen.h"
53#include "win_iconify.h"
54#include "win_ops.h"
55#include "win_resize.h"
56#include "win_utils.h"
57#include "workspace_utils.h"
58
59/* #define DEBUG_EWMH */
60
61Atom XEWMHAtom[NUM_EWMH_XATOMS];
62
63#define NET_WM_STATE_REMOVE        0    /* remove/unset property */
64#define NET_WM_STATE_ADD           1    /* add/set property */
65#define NET_WM_STATE_TOGGLE        2    /* toggle property  */
66
67#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT      0
68#define _NET_WM_MOVERESIZE_SIZE_TOP          1
69#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT     2
70#define _NET_WM_MOVERESIZE_SIZE_RIGHT        3
71#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT  4
72#define _NET_WM_MOVERESIZE_SIZE_BOTTOM       5
73#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT   6
74#define _NET_WM_MOVERESIZE_SIZE_LEFT         7
75#define _NET_WM_MOVERESIZE_MOVE              8   /* movement only */
76#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD     9   /* size via keyboard */
77#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD    10   /* move via keyboard */
78#define _NET_WM_MOVERESIZE_CANCEL           11   /* cancel operation */
79
80static Image *ExtractIcon(ScreenInfo *scr, unsigned long *prop, int width,
81                          int height);
82static void EwmhClientMessage_NET_WM_DESKTOP(XClientMessageEvent *msg);
83static void EwmhClientMessage_NET_WM_STATE(XClientMessageEvent *msg);
84static void EwmhClientMessage_NET_ACTIVE_WINDOW(XClientMessageEvent *msg);
85static void EwmhClientMessage_NET_WM_MOVERESIZE(XClientMessageEvent *msg);
86static void EwmhClientMessage_NET_CLOSE_WINDOW(XClientMessageEvent *msg);
87static XEvent synth_btnevent_for_moveresize(TwmWindow *twm_win);
88static unsigned long EwmhGetWindowProperty(Window w, Atom name, Atom type);
89static void EwmhGetStrut(TwmWindow *twm_win, bool update);
90static void EwmhRemoveStrut(TwmWindow *twm_win);
91static void EwmhSet_NET_WORKAREA(ScreenInfo *scr);
92static int EwmhGet_NET_WM_STATE(TwmWindow *twm_win);
93static void EwmhClientMessage_NET_WM_STATEchange(TwmWindow *twm_win, int change,
94                int newVal);
95
96#define ALL_WORKSPACES  0xFFFFFFFFU
97
98static void SendPropertyMessage(Window to, Window about,
99                                Atom messagetype,
100                                long l0, long l1, long l2, long l3, long l4,
101                                long mask)
102{
103	XEvent e;
104
105	e.xclient.type = ClientMessage;
106	e.xclient.message_type = messagetype;
107	e.xclient.display = dpy;
108	e.xclient.window = about;
109	e.xclient.format = 32;
110	e.xclient.data.l[0] = l0;
111	e.xclient.data.l[1] = l1;
112	e.xclient.data.l[2] = l2;
113	e.xclient.data.l[3] = l3;
114	e.xclient.data.l[4] = l4;
115
116	XSendEvent(dpy, to, False, mask, &e);
117}
118
119static void EwmhInitAtoms(void)
120{
121	XInternAtoms(dpy, XEWMHAtomNames, NUM_EWMH_XATOMS, False, XEWMHAtom);
122}
123
124static bool caughtError;
125
126static int CatchError(Display *display, XErrorEvent *event)
127{
128	caughtError = true;
129	return 0;
130}
131
132void EwmhInit(void)
133{
134	EwmhInitAtoms();
135}
136
137/*
138 * Force-generate some event, so that we know the current time.
139 *
140 * Suggested in the ICCCM:
141 * http://tronche.com/gui/x/icccm/sec-2.html#s-2.1
142 */
143
144static void GenerateTimestamp(ScreenInfo *scr)
145{
146	XEvent event;
147	struct timespec tosleep;
148	int timeout = 200;          /* 0.2 seconds in ms */
149	int found;
150
151	/* Sleep in 10ms chunks */
152	tosleep.tv_sec  = 0;
153	tosleep.tv_nsec = (10 * 1000 * 1000);
154
155	if(EventTime > 0) {
156		return;
157	}
158
159	XChangeProperty(dpy, scr->icccm_Window,
160	                XA_WM_CLASS, XA_STRING,
161	                8, PropModeAppend, NULL, 0);
162
163	while(timeout > 0) {
164		found = XCheckTypedWindowEvent(dpy, scr->icccm_Window, PropertyNotify, &event);
165		if(found) {
166			break;
167		}
168		nanosleep(&tosleep, NULL);
169		timeout -= 10;
170	}
171
172	if(found) {
173#ifdef DEBUG_EWMH
174		fprintf(stderr, "GenerateTimestamp: time = %ld, timeout left = %d\n",
175		        event.xproperty.time, timeout);
176#endif /* DEBUG_EWMH */
177		if(EventTime < event.xproperty.time) {
178			EventTime = event.xproperty.time;
179		}
180	}
181}
182
183/*
184 * Perform the "replace the window manager" protocol, as vaguely hinted
185 * at by the ICCCM section 4.3.
186 * http://tronche.com/gui/x/icccm/sec-4.html#s-4.3
187 *
188 * We do want to run through this even if we're not running with
189 * --replace ourselves, because it also sets up the bits for other
190 * invocations to --replace us.
191 *
192 * TODO: convert the selection to atom VERSION.
193 */
194static bool EwmhReplaceWM(ScreenInfo *scr)
195{
196	char atomname[32];
197	Atom wmAtom;
198	Window selectionOwner;
199
200	snprintf(atomname, sizeof(atomname), "WM_S%d", scr->screen);
201	wmAtom = XInternAtom(dpy, atomname, False);
202
203	selectionOwner = XGetSelectionOwner(dpy, wmAtom);
204	if(selectionOwner == scr->icccm_Window) {
205		selectionOwner = None;
206	}
207
208	if(selectionOwner != None) {
209		XErrorHandler oldHandler;
210
211		/*
212		 * Check if that owner still exists, and if it does, we want
213		 * StructureNotify-kind events from it.
214		 */
215		caughtError = false;
216		oldHandler = XSetErrorHandler(CatchError);
217
218		XSelectInput(dpy, selectionOwner, StructureNotifyMask);
219		XSync(dpy, False);
220
221		XSetErrorHandler(oldHandler);
222
223		if(caughtError) {
224			selectionOwner = None;
225		}
226	}
227
228	/*
229	 * If something else is owning things and we're not running
230	 * --replace, give up now and return failure.
231	 */
232	if(selectionOwner != None && !CLarg.ewmh_replace) {
233#ifdef DEBUG_EWMH
234		fprintf(stderr, "A window manager is already running on screen %d\n",
235		        scr->screen);
236#endif
237		return false;
238	}
239
240	/* We're asked to --replace, so try to do so */
241	XSetSelectionOwner(dpy, wmAtom, scr->icccm_Window, CurrentTime);
242
243	if(XGetSelectionOwner(dpy, wmAtom) != scr->icccm_Window) {
244		fprintf(stderr, "Did not get window manager selection on screen %d\n",
245		        scr->screen);
246		return false;
247	}
248
249	/*
250	 * If there was a previous selection owner, wait for it
251	 * to go away.
252	 */
253
254	if(selectionOwner != None) {
255		int timeout = 10 * 1000;        /* 10 seconds in ms */
256		XEvent event;
257		struct timespec tosleep;
258
259		/* Sleep in 100ms chunks */
260		tosleep.tv_sec  = 0;
261		tosleep.tv_nsec = (100 * 1000 * 1000);
262
263		while(timeout > 0) {
264
265			int found = XCheckTypedWindowEvent(dpy, selectionOwner, DestroyNotify, &event);
266			if(found) {
267				break;
268			}
269			nanosleep(&tosleep, NULL);
270			timeout -= 100;
271		}
272
273		if(timeout <= 0) {
274			fprintf(stderr, "Timed out waiting for other window manager "
275			        "on screen %d to quit\n",
276			        scr->screen);
277			return false;
278		}
279	}
280
281	/*
282	 * Send a message to confirm we're now managing the screen.
283	 * ICCCM, end of chapter 2, section "Manager Selections".
284	 * http://tronche.com/gui/x/icccm/sec-2.html#s-2.8
285	 *
286	 * ICCCM says StructureNotifyMask,
287	 * OpenBox says SubstructureNotifyMask.
288	 */
289
290	GenerateTimestamp(scr);
291
292	SendPropertyMessage(scr->XineramaRoot, scr->XineramaRoot,
293	                    XA_MANAGER, EventTime, wmAtom, scr->icccm_Window, 0, 0,
294	                    StructureNotifyMask);
295
296	return true;
297}
298
299/*
300 * This function is called very early in initialisation.
301 *
302 * Only scr->screen and scr->XineramaRoot are valid: we want to know if
303 * it makes sense to continue with the full initialisation.
304 *
305 * Create the ICCCM window that owns the WM_Sn selection.
306 */
307bool EwmhInitScreenEarly(ScreenInfo *scr)
308{
309	XSetWindowAttributes attrib;
310
311#ifdef DEBUG_EWMH
312	fprintf(stderr, "EwmhInitScreenEarly: XCreateWindow\n");
313#endif
314	attrib.event_mask = PropertyChangeMask;
315	attrib.override_redirect = True;
316	scr->icccm_Window = XCreateWindow(dpy, scr->XineramaRoot,
317	                                  -100, -100, 1, 1, 0,
318	                                  CopyFromParent, InputOutput,
319	                                  CopyFromParent,
320	                                  CWEventMask | CWOverrideRedirect,
321	                                  &attrib);
322
323	XMapWindow(dpy, scr->icccm_Window);
324	XLowerWindow(dpy, scr->icccm_Window);
325
326#ifdef DEBUG_EWMH
327	fprintf(stderr, "EwmhInitScreenEarly: call EwmhReplaceWM\n");
328#endif
329	if(!EwmhReplaceWM(scr)) {
330		XDestroyWindow(dpy, scr->icccm_Window);
331		scr->icccm_Window = None;
332
333#ifdef DEBUG_EWMH
334		fprintf(stderr, "EwmhInitScreenEarly: return false\n");
335#endif
336		return false;
337	}
338
339#ifdef DEBUG_EWMH
340	fprintf(stderr, "EwmhInitScreenEarly: return true\n");
341#endif
342	return true;
343}
344
345/*
346 * This initialisation is called late, when scr has been set up
347 * completely.
348 */
349void EwmhInitScreenLate(ScreenInfo *scr)
350{
351	long data[2];
352
353	/* Set _NET_SUPPORTING_WM_CHECK on root window */
354	data[0] = scr->icccm_Window;
355	XChangeProperty(dpy, scr->XineramaRoot,
356	                XA__NET_SUPPORTING_WM_CHECK, XA_WINDOW,
357	                32, PropModeReplace,
358	                (unsigned char *)data, 1);
359
360	/*
361	 * Set properties on the window;
362	 * this also belongs with _NET_SUPPORTING_WM_CHECK
363	 */
364	XChangeProperty(dpy, scr->icccm_Window,
365	                XA__NET_WM_NAME, XA_UTF8_STRING,
366	                8, PropModeReplace,
367	                (unsigned char *)"ctwm", 4);
368
369	data[0] = scr->icccm_Window;
370	XChangeProperty(dpy, scr->icccm_Window,
371	                XA__NET_SUPPORTING_WM_CHECK, XA_WINDOW,
372	                32, PropModeReplace,
373	                (unsigned char *)data, 1);
374
375	/*
376	 * Add supported properties to the root window.
377	 */
378	data[0] = 0;
379	data[1] = 0;
380	XChangeProperty(dpy, scr->XineramaRoot,
381	                XA__NET_DESKTOP_VIEWPORT, XA_CARDINAL,
382	                32, PropModeReplace,
383	                (unsigned char *)data, 2);
384
385	data[0] = scr->rootw;
386	data[1] = scr->rooth;
387	XChangeProperty(dpy, scr->XineramaRoot,
388	                XA__NET_DESKTOP_GEOMETRY, XA_CARDINAL,
389	                32, PropModeReplace,
390	                (unsigned char *)data, 2);
391
392	EwmhSet_NET_WORKAREA(scr);
393
394	if(scr->workSpaceManagerActive) {
395		data[0] = scr->workSpaceMgr.count;
396	}
397	else {
398		data[0] = 1;
399	}
400
401	XChangeProperty(dpy, scr->XineramaRoot,
402	                XA__NET_NUMBER_OF_DESKTOPS, XA_CARDINAL,
403	                32, PropModeReplace,
404	                (unsigned char *)data, 1);
405
406	if(scr->workSpaceManagerActive) {
407		/* TODO: this is for the first Virtual Screen only... */
408		/*data[0] = scr->workSpaceMgr.workSpaceWindowList->currentwspc->number; */
409		data[0] = 0;
410	}
411	else {
412		data[0] = 0;
413	}
414	XChangeProperty(dpy, scr->XineramaRoot,
415	                XA__NET_CURRENT_DESKTOP, XA_CARDINAL,
416	                32, PropModeReplace,
417	                (unsigned char *)data, 1);
418
419	EwmhSet_NET_SHOWING_DESKTOP(0);
420
421	long supported[30];
422	int i = 0;
423
424	supported[i++] = XA__NET_SUPPORTING_WM_CHECK;
425	supported[i++] = XA__NET_DESKTOP_VIEWPORT;
426	supported[i++] = XA__NET_NUMBER_OF_DESKTOPS;
427	supported[i++] = XA__NET_CURRENT_DESKTOP;
428	supported[i++] = XA__NET_DESKTOP_GEOMETRY;
429	supported[i++] = XA__NET_WM_ICON;
430	supported[i++] = XA__NET_WM_DESKTOP;
431	supported[i++] = XA__NET_CLIENT_LIST;
432	supported[i++] = XA__NET_CLIENT_LIST_STACKING;
433	supported[i++] = XA__NET_WM_WINDOW_TYPE;
434	supported[i++] = XA__NET_WM_WINDOW_TYPE_NORMAL;
435	supported[i++] = XA__NET_WM_WINDOW_TYPE_DESKTOP;
436	supported[i++] = XA__NET_WM_WINDOW_TYPE_DOCK;
437	supported[i++] = XA__NET_WM_STRUT;
438	supported[i++] = XA__NET_WM_STRUT_PARTIAL;
439	supported[i++] = XA__NET_SHOWING_DESKTOP;
440	supported[i++] = XA__NET_WM_STATE;
441	supported[i++] = XA__NET_WM_STATE_MAXIMIZED_VERT;
442	supported[i++] = XA__NET_WM_STATE_MAXIMIZED_HORZ;
443	supported[i++] = XA__NET_WM_STATE_FULLSCREEN;
444	supported[i++] = XA__NET_ACTIVE_WINDOW;
445	supported[i++] = XA__NET_WORKAREA;
446	supported[i++] = XA__NET_WM_MOVERESIZE;
447	supported[i++] = XA__NET_WM_STATE_SHADED;
448	supported[i++] = XA__NET_WM_STATE_ABOVE;
449	supported[i++] = XA__NET_WM_STATE_BELOW;
450	supported[i++] = XA__NET_CLOSE_WINDOW;
451
452	XChangeProperty(dpy, scr->XineramaRoot,
453	                XA__NET_SUPPORTED, XA_ATOM,
454	                32, PropModeReplace,
455	                (unsigned char *)supported, i);
456}
457
458
459#ifdef VSCREEN
460/*
461 * Set up the _NET_VIRTUAL_ROOTS property, which indicates that we're
462 * using virtual root windows.
463 * This applies only if we have multiple virtual screens.
464 *
465 * Also record this as a supported atom in _NET_SUPPORTED.
466 *
467 * Really, our virtual screens (with their virtual root windows) don't quite
468 * fit in the EWMH idiom. Several root window properties (such as
469 * _NET_CURRENT_DESKTOP) are more appropriate on the virtual root windows. But
470 * that is not where other clients would look for them.
471 *
472 * The idea seems to be that the virtual roots as used for workspaces (desktops
473 * in EWMH terminology) are only mapped one at a time.
474 */
475void EwmhInitVirtualRoots(ScreenInfo *scr)
476{
477	int numVscreens = scr->numVscreens;
478
479	if(numVscreens > 1) {
480		long *data;
481		long d0;
482		VirtualScreen *vs;
483		int i;
484
485		data = calloc(numVscreens, sizeof(long));
486
487		for(vs = scr->vScreenList, i = 0;
488		                vs != NULL && i < numVscreens;
489		                vs = vs->next, i++) {
490			data[i] = vs->window;
491		}
492
493		XChangeProperty(dpy, scr->XineramaRoot,
494		                XA__NET_VIRTUAL_ROOTS, XA_WINDOW,
495		                32, PropModeReplace,
496		                (unsigned char *)data, numVscreens);
497
498		/* This might fail, but what can we do about it? */
499
500		free(data);
501
502		d0 = XA__NET_VIRTUAL_ROOTS;
503		XChangeProperty(dpy, scr->XineramaRoot,
504		                XA__NET_SUPPORTED, XA_ATOM,
505		                32, PropModeAppend,
506		                (unsigned char *)&d0, 1);
507	}
508}
509#endif
510
511
512static void EwmhTerminateScreen(ScreenInfo *scr)
513{
514	XDeleteProperty(dpy, scr->XineramaRoot, XA__NET_SUPPORTED);
515
516	/*
517	 * Don't delete scr->icccm_Window; let it be deleted automatically
518	 * when we terminate the X server connection. A replacement window
519	 * manager may want to start working immediately after it has
520	 * disappeared.
521	 */
522}
523
524/*
525 * Clear everything that needs to be cleared before we exit.
526 */
527
528void EwmhTerminate(void)
529{
530	int scrnum;
531	ScreenInfo *scr;
532
533	for(scrnum = 0; scrnum < NumScreens; scrnum++) {
534		if((scr = ScreenList[scrnum]) == NULL) {
535			continue;
536		}
537		EwmhTerminateScreen(scr);
538	}
539}
540
541/*
542 * Event handler: lost the WM_Sn selection
543 * (that's the only selection we have).
544 */
545
546void EwmhSelectionClear(XSelectionClearEvent *sev)
547{
548#ifdef DEBUG_EWMH
549	fprintf(stderr, "sev->window = %x\n", (unsigned)sev->window);
550#endif
551	DoShutdown();
552}
553
554/*
555 * When accepting client messages to the root window,
556 * be accepting and accept both the real root window and the
557 * current virtual screen.
558 *
559 * Should perhaps also accept any other virtual screen.
560 */
561bool EwmhClientMessage(XClientMessageEvent *msg)
562{
563	if(msg->format != 32) {
564		return false;
565	}
566
567	/* Messages regarding any window */
568	if(msg->message_type == XA__NET_WM_DESKTOP) {
569		EwmhClientMessage_NET_WM_DESKTOP(msg);
570		return true;
571	}
572	else if(msg->message_type == XA__NET_WM_STATE) {
573		EwmhClientMessage_NET_WM_STATE(msg);
574		return true;
575	}
576	else if(msg->message_type == XA__NET_ACTIVE_WINDOW) {
577		EwmhClientMessage_NET_ACTIVE_WINDOW(msg);
578		return true;
579	}
580	else if(msg->message_type == XA__NET_WM_MOVERESIZE) {
581		EwmhClientMessage_NET_WM_MOVERESIZE(msg);
582		return true;
583	}
584	else if(msg->message_type == XA__NET_CLOSE_WINDOW) {
585		EwmhClientMessage_NET_CLOSE_WINDOW(msg);
586		return true;
587	}
588
589	/* Messages regarding the root window */
590	if(msg->window != Scr->XineramaRoot &&
591	                msg->window != Scr->Root) {
592#ifdef DEBUG_EWMH
593		fprintf(stderr, "Received unrecognized client message: %s\n",
594		        XGetAtomName(dpy, msg->message_type));
595#endif
596		return false;
597	}
598
599	if(msg->message_type == XA__NET_CURRENT_DESKTOP) {
600		GotoWorkSpaceByNumber(Scr->currentvs, msg->data.l[0]);
601		return true;
602	}
603	else if(msg->message_type == XA__NET_SHOWING_DESKTOP) {
604		ShowBackground(Scr->currentvs, msg->data.l[0] ? 1 : 0);
605		return true;
606	}
607	else {
608#ifdef DEBUG_EWMH
609		fprintf(stderr, "Received unrecognized client message about root window: %s\n",
610		        XGetAtomName(dpy, msg->message_type));
611#endif
612	}
613
614	return false;
615}
616
617/*
618 * The format of the _NET_WM_ICON property is
619 *
620 * [0] width
621 * [1] height
622 *     height repetitions of
623 *         row, which is
624 *              width repetitions of
625 *                      pixel: ARGB
626 * repeat for next size.
627 *
628 * Some icons can be 256x256 CARDINALs which is 65536 CARDINALS!
629 * Therefore we fetch in pieces and skip the pixels of large icons
630 * until needed.
631 *
632 * First scan all sizes. Keep a record of the closest smaller and larger
633 * size. At the end, choose from one of those.
634 * Finally, go and fetch the pixel data.
635 */
636
637Image *EwmhGetIcon(ScreenInfo *scr, TwmWindow *twm_win)
638{
639	int fetch_offset;
640	Atom actual_type;
641	int actual_format;
642	unsigned long nitems, bytes_after;
643	unsigned long *prop;
644
645	int wanted_area;
646	int smaller, larger;
647	int offset;
648	int smaller_offset, larger_offset;
649	int i;
650
651	int width, height;
652
653	fetch_offset = 0;
654	if(XGetWindowProperty(dpy, twm_win->w, XA__NET_WM_ICON,
655	                      fetch_offset, 8 * 1024, False, XA_CARDINAL,
656	                      &actual_type, &actual_format, &nitems,
657	                      &bytes_after, (unsigned char **)&prop) != Success || nitems == 0) {
658		return NULL;
659	}
660
661	if(actual_format != 32) {
662		XFree(prop);
663		return NULL;
664	}
665
666#ifdef DEBUG_EWMH
667	fprintf(stderr, "_NET_WM_ICON data fetched\n");
668#endif
669	/*
670	 * Usually the icons are square, but that is not a rule.
671	 * So we measure the area instead.
672	 *
673	 * Approach wanted size from both directions and at the end,
674	 * choose the "nearest".
675	 */
676	wanted_area = Scr->PreferredIconWidth * Scr->PreferredIconHeight;
677	smaller = 0;
678	larger = 999999;
679	smaller_offset = -1;
680	larger_offset = -1;
681	i = 0;
682
683	for(;;) {
684		offset = i;
685
686		int w = prop[i++];
687		int h = prop[i++];
688		int size = w * h;
689
690		const int area = w * h;
691
692#ifdef DEBUG_EWMH
693		fprintf(stderr, "[%d+%d] w=%d h=%d\n", fetch_offset, offset, w, h);
694#endif
695
696
697		if(area == wanted_area) {
698#ifdef DEBUG_EWMH
699			fprintf(stderr, "exact match [%d+%d=%d] w=%d h=%d\n", fetch_offset, offset,
700			        fetch_offset + offset, w, h);
701#endif /* DEBUG_EWMH */
702			smaller_offset = fetch_offset + offset;
703			smaller = area;
704			larger_offset = -1;
705			break;
706		}
707		else if(area < wanted_area) {
708			if(area > smaller) {
709#ifdef DEBUG_EWMH
710				fprintf(stderr, "increase smaller, was [%d]\n", smaller_offset);
711#endif /* DEBUG_EWMH */
712				smaller = area;
713				smaller_offset = fetch_offset + offset;
714			}
715		}
716		else {   /* area > wanted_area */
717			if(area < larger) {
718#ifdef DEBUG_EWMH
719				fprintf(stderr, "decrease larger, was [%d]\n", larger_offset);
720#endif /* DEBUG_EWMH */
721				larger = area;
722				larger_offset = fetch_offset + offset;
723			}
724		}
725
726		if(i + size + 2 > nitems) {
727#ifdef DEBUG_EWMH
728			fprintf(stderr, "not enough data: %d + %d > %ld \n", i, size, nitems);
729#endif /* DEBUG_EWMH */
730
731			if(i + size + 2 <= nitems + bytes_after / 4) {
732				/* we can fetch some more... */
733				XFree(prop);
734				fetch_offset += i + size;
735				if(XGetWindowProperty(dpy, twm_win->w, XA__NET_WM_ICON,
736				                      fetch_offset, 8 * 1024, False, XA_CARDINAL,
737				                      &actual_type, &actual_format, &nitems,
738				                      &bytes_after, (unsigned char **)&prop) != Success) {
739					continue;
740				}
741				i = 0;
742				continue;
743			}
744			break;
745		}
746		i += size;
747	}
748
749	/*
750	 * Choose which icon approximates our desired size best.
751	 */
752	int area = 0;
753	ALLOW_DEAD_STORE(area); // all branches below init it
754
755	if(smaller_offset >= 0) {
756		if(larger_offset >= 0) {
757			/* choose the nearest */
758#ifdef DEBUG_EWMH
759			fprintf(stderr, "choose nearest %d %d\n", smaller, larger);
760#endif /* DEBUG_EWMH */
761			if((double)larger / wanted_area > (double)wanted_area / smaller) {
762				offset = smaller_offset;
763				area = smaller;
764			}
765			else {
766				offset = larger_offset;
767				area = larger;
768			}
769		}
770		else {
771			/* choose smaller */
772#ifdef DEBUG_EWMH
773			fprintf(stderr, "choose smaller (only) %d\n", smaller);
774#endif /* DEBUG_EWMH */
775			offset = smaller_offset;
776			area = smaller;
777		}
778	}
779	else if(larger_offset >= 0) {
780		/* choose larger */
781#ifdef DEBUG_EWMH
782		fprintf(stderr, "choose larger (only) %d\n", larger);
783#endif /* DEBUG_EWMH */
784		offset = larger_offset;
785		area = larger;
786	}
787	else {
788		/* no icons found at all? */
789#ifdef DEBUG_EWMH
790		fprintf(stderr, "nothing to choose from\n");
791#endif /* DEBUG_EWMH */
792		XFree(prop);
793		return NULL;
794	}
795
796	/*
797	 * Now fetch the pixels.
798	 */
799
800#ifdef DEBUG_EWMH
801	fprintf(stderr, "offset = %d fetch_offset = %d\n", offset, fetch_offset);
802	fprintf(stderr, "offset + 2 + area = %d fetch_offset + nitems = %ld\n",
803	        offset + 2 + area, fetch_offset + nitems);
804#endif /* DEBUG_EWMH */
805	if(offset < fetch_offset ||
806	                offset + 2 + area > fetch_offset + nitems) {
807		XFree(prop);
808		fetch_offset = offset;
809#ifdef DEBUG_EWMH
810		fprintf(stderr, "refetching from %d\n", fetch_offset);
811#endif /* DEBUG_EWMH */
812		if(XGetWindowProperty(dpy, twm_win->w, XA__NET_WM_ICON,
813		                      fetch_offset, 2 + area, False, XA_CARDINAL,
814		                      &actual_type, &actual_format, &nitems,
815		                      &bytes_after, (unsigned char **)&prop) != Success) {
816			return NULL;
817		}
818	}
819
820	i = offset - fetch_offset;
821	width = prop[i++];
822	height = prop[i++];
823#ifdef DEBUG_EWMH
824	fprintf(stderr, "Chosen [%d] w=%d h=%d area=%d\n", offset, width, height, area);
825#endif /* DEBUG_EWMH */
826	assert(width * height == area);
827
828	Image *image = ExtractIcon(scr, &prop[i], width, height);
829
830	XFree(prop);
831
832	return image;
833}
834
835static uint16_t *buffer_16bpp;
836static uint32_t *buffer_32bpp;
837
838static void convert_for_16(int w, int x, int y, int argb)
839{
840	int r = (argb >> 16) & 0xFF;
841	int g = (argb >>  8) & 0xFF;
842	int b = (argb >>  0) & 0xFF;
843	buffer_16bpp [y * w + x] = ((r >> 3) << 11) + ((g >> 2) << 5) + (b >> 3);
844}
845
846static void convert_for_32(int w, int x, int y, int argb)
847{
848	buffer_32bpp [y * w + x] = argb & 0x00FFFFFF;
849}
850
851static Image *ExtractIcon(ScreenInfo *scr, unsigned long *prop, int width,
852                          int height)
853{
854	XImage *ximage;
855	void (*store_data)(int w, int x, int y, int argb);
856	int x, y, transparency;
857	int rowbytes;
858	unsigned char *maskbits;
859
860	GC gc;
861	Pixmap pixret;
862	Pixmap mask;
863	Image *image;
864	int i;
865
866	ximage = NULL;
867
868	/** XXX sort of duplicated from util.c:LoadJpegImage() */
869	if(scr->d_depth == 16) {
870		store_data = convert_for_16;
871		buffer_16bpp = malloc(width * height * sizeof(buffer_16bpp[0]));
872		buffer_32bpp = NULL;
873		ximage = XCreateImage(dpy, CopyFromParent, scr->d_depth, ZPixmap, 0,
874		                      (char *) buffer_16bpp, width, height, 16, width * 2);
875	}
876	else if(scr->d_depth == 24 || scr->d_depth == 32) {
877		store_data = convert_for_32;
878		buffer_32bpp = malloc(width * height * sizeof(buffer_32bpp[0]));
879		buffer_16bpp = NULL;
880		ximage = XCreateImage(dpy, CopyFromParent, scr->d_depth, ZPixmap, 0,
881		                      (char *) buffer_32bpp, width, height, 32, width * 4);
882	}
883	else {
884#ifdef DEBUG_EWMH
885		fprintf(stderr, "Screen unsupported depth for 32-bit icon: %d\n", scr->d_depth);
886#endif /* DEBUG_EWMH */
887		XFree(prop);
888		return NULL;
889	}
890	if(ximage == NULL) {
891#ifdef DEBUG_EWMH
892		fprintf(stderr, "cannot create image for icon\n");
893#endif /* DEBUG_EWMH */
894		XFree(prop);
895		return NULL;
896	}
897
898	transparency = 0;
899	rowbytes = (width + 7) / 8;
900	maskbits = calloc(height, rowbytes);
901
902	/*
903	 * Copy all ARGB pixels to the pixmap (the RGB part), and the bitmap (the
904	 * Alpha, or opaqueness part). If any pixels are transparent, we're going
905	 * to need a shape.
906	 */
907	i = 0;
908	for(y = 0; y < height; y++) {
909		for(x = 0; x < width; x++) {
910			unsigned long argb = prop[i++];
911			store_data(width, x, y, argb);
912			int opaque = ((argb >> 24) & 0xFF) >= 0x80; /* arbitrary cutoff */
913			if(opaque) {
914				maskbits [rowbytes * y + (x / 8)] |= 0x01 << (x % 8);
915			}
916			else {
917				transparency = 1;
918			}
919		}
920	}
921
922	gc = DefaultGC(dpy, scr->screen);
923	pixret = XCreatePixmap(dpy, scr->Root, width, height, scr->d_depth);
924	XPutImage(dpy, pixret, gc, ximage, 0, 0, 0, 0, width, height);
925	XDestroyImage(ximage);  /* also frees buffer_{16,32}bpp */
926	ximage = NULL;
927
928	mask = None;
929	if(transparency) {
930		mask = XCreatePixmapFromBitmapData(dpy, scr->Root, (char *)maskbits,
931		                                   width, height, 1, 0, 1);
932	}
933	free(maskbits);
934
935	image = AllocImage();
936	image->width  = width;
937	image->height = height;
938	image->pixmap = pixret;
939	image->mask   = mask;
940
941	return image;
942}
943
944/*
945 * Handle a PropertyNotify on _NET_WM_ICON.
946 */
947static void EwmhHandle_NET_WM_ICONNotify(XPropertyEvent *event,
948                TwmWindow *twm_win)
949{
950	unsigned long valuemask;                /* mask for create windows */
951	XSetWindowAttributes attributes;        /* attributes for create windows */
952	Icon *icon = twm_win->icon;
953	int x;
954
955#ifdef DEBUG_EWMH
956	fprintf(stderr, "EwmhHandlePropertyNotify: NET_WM_ICON\n");
957#endif /* DEBUG_EWMH */
958	/*
959	 * If there is no icon yet, we'll look at this property
960	 * later, if and when we do create an icon.
961	 */
962	if(!icon || icon->match != match_net_wm_icon) {
963#ifdef DEBUG_EWMH
964		fprintf(stderr, "no icon, or not match_net_wm_icon\n");
965#endif /* DEBUG_EWMH */
966		return;
967	}
968
969	Image *image = EwmhGetIcon(Scr, twm_win);
970
971	/* TODO: de-duplicate with handling of XA_WM_HINTS */
972	{
973		Image *old_image = icon->image;
974		icon->image = image;
975		FreeImage(old_image);
976	}
977
978
979	if(twm_win->icon->bm_w) {
980		XDestroyWindow(dpy, twm_win->icon->bm_w);
981	}
982
983	valuemask = CWBackPixmap;
984	attributes.background_pixmap = image->pixmap;
985
986	x = GetIconOffset(twm_win->icon);
987	twm_win->icon->bm_w =
988	        XCreateWindow(dpy, twm_win->icon->w, x, 0,
989	                      twm_win->icon->width,
990	                      twm_win->icon->height,
991	                      0, Scr->d_depth,
992	                      CopyFromParent, Scr->d_visual,
993	                      valuemask, &attributes);
994
995	if(image->mask) {
996		XShapeCombineMask(dpy, twm_win->icon->bm_w, ShapeBounding, 0, 0, image->mask,
997		                  ShapeSet);
998		XShapeCombineMask(dpy, twm_win->icon->w,    ShapeBounding, x, 0, image->mask,
999		                  ShapeSet);
1000	}
1001	else {
1002		XRectangle rect;
1003
1004		rect.x      = x;
1005		rect.y      = 0;
1006		rect.width  = twm_win->icon->width;
1007		rect.height = twm_win->icon->height;
1008		XShapeCombineRectangles(dpy, twm_win->icon->w, ShapeBounding, 0,
1009		                        0, &rect, 1, ShapeUnion, 0);
1010	}
1011	XMapSubwindows(dpy, twm_win->icon->w);
1012	RedoIconName(twm_win);
1013}
1014
1015/*
1016 * Handle a PropertyNotify on _NET_WM_STRUT(PARTIAL).
1017 */
1018static void EwmhHandle_NET_WM_STRUTNotify(XPropertyEvent *event,
1019                TwmWindow *twm_win)
1020{
1021	EwmhGetStrut(twm_win, true);
1022}
1023
1024/*
1025 * Handle a _NET_WM_STATE ClientMessage.
1026 */
1027static int atomToFlag(Atom a)
1028{
1029#ifdef DEBUG_EWMH
1030# define CRWARN(x) fprintf(stderr, "atomToFlag: ignoring " #x "\n")
1031#else
1032# define CRWARN(x) (void)0
1033#endif
1034#define CHKNRET(st) \
1035        if(a == XA__NET_WM_##st) { \
1036                if(LookInNameList(Scr->EWMHIgnore, #st)) { \
1037                        CRWARN(st); \
1038                        return 0; \
1039                } \
1040                return EWMH_##st; \
1041        }
1042
1043	/* Check (potentially ignoring) various flags we know */
1044	CHKNRET(STATE_MAXIMIZED_VERT);
1045	CHKNRET(STATE_MAXIMIZED_HORZ);
1046	CHKNRET(STATE_FULLSCREEN);
1047	CHKNRET(STATE_SHADED);
1048	CHKNRET(STATE_ABOVE);
1049	CHKNRET(STATE_BELOW);
1050
1051#undef CHKNRET
1052#undef CRWARN
1053
1054	/* Else we don't recognize it */
1055	return 0;
1056}
1057
1058
1059/*
1060 * Handle the NET_WM_STATE client message.
1061 * It can change 1 or 2 values represented in NET_WM_STATE.
1062 * The second change is allowed
1063 * specifically for (re)setting horizontal and vertical maximalisation in
1064 * one go. Treat that as a special case.
1065 */
1066static void EwmhClientMessage_NET_WM_STATE(XClientMessageEvent *msg)
1067{
1068	Window w = msg->window;
1069	TwmWindow *twm_win;
1070	int change, change1, change2, newValue;
1071
1072	twm_win = GetTwmWindow(w);
1073
1074	if(twm_win == NULL) {
1075		return;
1076	}
1077
1078	/*
1079	 * Due to EWMHIgnore, it's possible to wind up with change1=0 and
1080	 * change2=something, so swap 'em if that happens.
1081	 */
1082	change1 = atomToFlag(msg->data.l[1]);
1083	change2 = atomToFlag(msg->data.l[2]);
1084	if(change1 == 0 && change2 != 0) {
1085		change1 = change2;
1086		change2 = 0;
1087	}
1088	change = change1 | change2;
1089
1090	switch(msg->data.l[0]) {
1091		case NET_WM_STATE_REMOVE:
1092#ifdef DEBUG_EWMH
1093			printf("NET_WM_STATE_REMOVE: ");
1094#endif
1095			newValue = 0;
1096			break;
1097		case NET_WM_STATE_ADD:
1098#ifdef DEBUG_EWMH
1099			printf("NET_WM_STATE_ADD: ");
1100#endif
1101			newValue = change;
1102			break;
1103		case NET_WM_STATE_TOGGLE:
1104#ifdef DEBUG_EWMH
1105			printf("NET_WM_STATE_TOGGLE: ");
1106#endif
1107			newValue = ~twm_win->ewmhFlags & change;
1108			break;
1109		default:
1110#ifdef DEBUG_EWMH
1111			printf("invalid operation in NET_WM_STATE: %ld\n", msg->data.l[0]);
1112#endif
1113			return;
1114	}
1115#ifdef DEBUG_EWMH
1116	printf("%s and %s\n", XGetAtomName(dpy, msg->data.l[1]),
1117	       XGetAtomName(dpy, msg->data.l[2]));
1118#endif
1119
1120	/*
1121	 * Special-case the horizontal and vertical zoom.
1122	 * You can turn them both on or both off, but no other combinations
1123	 * are done as a unit.
1124	 */
1125	if(change == (EWMH_STATE_MAXIMIZED_VERT | EWMH_STATE_MAXIMIZED_HORZ) &&
1126	                (newValue == 0 || newValue == change)) {
1127		EwmhClientMessage_NET_WM_STATEchange(twm_win, change, newValue);
1128	}
1129	else {
1130		EwmhClientMessage_NET_WM_STATEchange(twm_win, change1, newValue & change1);
1131		if(change2 != 0) {
1132			EwmhClientMessage_NET_WM_STATEchange(twm_win, change2, newValue & change2);
1133		}
1134	}
1135}
1136
1137/*
1138 * change - bitmask of settings that possibly change. Only one bit is
1139 *                      set in this, with the possible exception of
1140 *                      *MAXIMIZED_{VERT,HORIZ} which can be set together.
1141 * newValue - bits with the new values; only valid bits are the ones
1142 *                      in change.
1143 */
1144static void EwmhClientMessage_NET_WM_STATEchange(TwmWindow *twm_win, int change,
1145                int newValue)
1146{
1147	/* Now check what we need to change */
1148
1149	if(change & (EWMH_STATE_MAXIMIZED_VERT | EWMH_STATE_MAXIMIZED_HORZ |
1150	                EWMH_STATE_FULLSCREEN)) {
1151		int newZoom = ZOOM_NONE;
1152
1153		switch(newValue) {
1154			case 0:
1155				newZoom = twm_win->zoomed;      /* turn off whatever zoom */
1156				break;
1157			case EWMH_STATE_MAXIMIZED_VERT:
1158				newZoom = F_ZOOM;
1159				break;
1160			case EWMH_STATE_MAXIMIZED_HORZ:
1161				newZoom = F_HORIZOOM;
1162				break;
1163			case EWMH_STATE_MAXIMIZED_HORZ | EWMH_STATE_MAXIMIZED_VERT:
1164				newZoom = F_FULLZOOM;
1165				break;
1166			case EWMH_STATE_FULLSCREEN:
1167				newZoom = F_FULLSCREENZOOM;
1168				break;
1169		}
1170		fullzoom(twm_win, newZoom);
1171	}
1172	else if(change & EWMH_STATE_SHADED) {
1173#ifdef DEBUG_EWMH
1174		printf("EWMH_STATE_SHADED: newValue: %d old: %d\n", newValue,
1175		       twm_win->ewmhFlags & EWMH_STATE_SHADED);
1176#endif
1177		if((twm_win->ewmhFlags & EWMH_STATE_SHADED) ^ newValue) {
1178			/* Toggle the shade/squeeze state */
1179#ifdef DEBUG_EWMH
1180			printf("EWMH_STATE_SHADED: change it\n");
1181#endif
1182			Squeeze(twm_win);
1183		}
1184	}
1185	else if(change & (EWMH_STATE_ABOVE | EWMH_STATE_BELOW)) {
1186		/*
1187		 * Other changes call into ctwm code, which in turn calls back to
1188		 * EWMH code to update the ewmhFlags and the property.  This one
1189		 * we handle completely internally.
1190		 */
1191		unsigned omask = 0, oval = 0;
1192		const int prepri = OtpEffectivePriority(twm_win);
1193
1194		/* Which bits are we changing and what to? */
1195#define DOBIT(fld) do { \
1196          if(change   & EWMH_STATE_##fld) { omask |= OTP_AFLAG_##fld; } \
1197          if(newValue & EWMH_STATE_##fld) { oval  |= OTP_AFLAG_##fld; } \
1198        } while(0)
1199
1200		DOBIT(ABOVE);
1201		DOBIT(BELOW);
1202
1203#undef DOBIT
1204
1205		/* Update OTP as necessary */
1206		OtpSetAflagMask(twm_win, omask, oval);
1207		if(OtpEffectivePriority(twm_win) != prepri) {
1208			OtpRestackWindow(twm_win);
1209		}
1210
1211		/* Set the EWMH property back on the window */
1212		EwmhSet_NET_WM_STATE(twm_win, change);
1213	}
1214}
1215
1216/*
1217 * Handle the _NET_ACTIVE_WINDOW client message.
1218 * Pagers would send such a message to "activate" a window.
1219 *
1220 * What does "activate" really mean? It isn't properly described.
1221 *
1222 * Let's presume that it means that the window is de-iconified and gets
1223 * focus.  The mouse may be moved to it (but not all window button apps
1224 * do that).  But is it always raised or should that depend on the
1225 * RaiseOnWarp option?
1226 */
1227static void EwmhClientMessage_NET_ACTIVE_WINDOW(XClientMessageEvent *msg)
1228{
1229	Window w = msg->window;
1230	TwmWindow *twm_win;
1231
1232	twm_win = GetTwmWindow(w);
1233
1234	if(twm_win == NULL) {
1235		return;
1236	}
1237
1238	if(!twm_win->mapped) {
1239		DeIconify(twm_win);
1240	}
1241#if 0
1242	WarpToWindow(twm_win, Scr->RaiseOnWarp /* True ? */);
1243#else
1244	/*
1245	 * Keep the mouse pointer where it is (typically the dock).
1246	 * WarpToWindow() would change the current workspace if needed to go
1247	 * to the window. But pagers would only send this message for
1248	 * windows in the current workspace, I expect.
1249	 */
1250	if(Scr->RaiseOnWarp) {
1251		AutoRaiseWindow(twm_win);
1252	}
1253	SetFocus(twm_win, msg->data.l[1]);
1254#endif
1255}
1256
1257/*
1258 * Ugly implementation of _NET_WM_MOVERESIZE.
1259 *
1260 * window = window to be moved or resized
1261 * message_type = _NET_WM_MOVERESIZE
1262 * format = 32
1263 * data.l[0] = x_root
1264 * data.l[1] = y_root
1265 * data.l[2] = direction
1266 * data.l[3] = button
1267 * data.l[4] = source indication
1268 */
1269static void EwmhClientMessage_NET_WM_MOVERESIZE(XClientMessageEvent *msg)
1270{
1271	TwmWindow *twm_win;
1272
1273	twm_win = GetTwmWindow(msg->window);
1274
1275	if(twm_win == NULL) {
1276		return;
1277	}
1278
1279	if(!twm_win->mapped) {
1280		DeIconify(twm_win);
1281	}
1282
1283	switch(msg->data.l[2]) {
1284		case _NET_WM_MOVERESIZE_SIZE_TOPLEFT:
1285		case _NET_WM_MOVERESIZE_SIZE_TOP:
1286		case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT:
1287		case _NET_WM_MOVERESIZE_SIZE_RIGHT:
1288		case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT:
1289		case _NET_WM_MOVERESIZE_SIZE_BOTTOM:
1290		case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT:
1291		case _NET_WM_MOVERESIZE_SIZE_LEFT:
1292		case _NET_WM_MOVERESIZE_SIZE_KEYBOARD: {
1293			/* Fake up as if we triggered f.resize on it */
1294			XEvent xevent = synth_btnevent_for_moveresize(twm_win);
1295
1296			/*
1297			 * The resize won't actually start until we cross the cursor
1298			 * over a border.  Perhaps we should find the nearest corner,
1299			 * and pre-warp the cursor there?  That may be less friendly
1300			 * for the user, since it might not be as predictable, and
1301			 * having the cursor zoom off without warning is probably a
1302			 * little surprising...
1303			 */
1304			ExecuteFunction(F_RESIZE, "", twm_win->frame, twm_win,
1305			                &xevent, C_WINDOW, false);
1306
1307			/*
1308			 * It's not guaranteed that this actually began as a button
1309			 * press, but our implementation is expecting the button to
1310			 * be held down all through the move, so that
1311			 * ExecuteFunction() won't actually end until there's a
1312			 * button release of some kind, which will trigger
1313			 * HandleButtonRelease() properly.  So we don't need to call
1314			 * it ourselves.
1315			 */
1316
1317			break;
1318		}
1319		case _NET_WM_MOVERESIZE_MOVE:
1320		case _NET_WM_MOVERESIZE_MOVE_KEYBOARD: {
1321			/* Fake up as if we triggered f.move on it */
1322			XEvent xevent = synth_btnevent_for_moveresize(twm_win);
1323			ExecuteFunction(F_MOVE, "", twm_win->frame, twm_win,
1324			                &xevent, C_WINDOW, false);
1325
1326			/* X-ref HandleButtonRelease() discussion above */
1327			break;
1328		}
1329		case _NET_WM_MOVERESIZE_CANCEL:
1330			/*
1331			 * TODO: check if the twm_win is the same.
1332			 * TODO: check how to make this actually work.
1333			 *
1334			 * As currently implemented, I don't believe we ever need to
1335			 * do anything here.  All the needed cleanup should happen in
1336			 * our ButtonRelease handler.
1337			 */
1338			break;
1339	}
1340}
1341
1342static XEvent
1343synth_btnevent_for_moveresize(TwmWindow *twm_win)
1344{
1345	XEvent xevent;
1346	Window root = twm_win->parent_vs->window;
1347	Window child = twm_win->w;
1348	int x_root = twm_win->frame_x;
1349	int y_root = twm_win->frame_y;
1350	int x_win  = 0;
1351	int y_win  = 0;
1352	unsigned int dummy_mask;
1353
1354	/* Find the pointer */
1355	XQueryPointer(dpy, twm_win->frame, &root, &child, &x_root, &y_root,
1356	              &x_win, &y_win, &dummy_mask);
1357
1358	/* Synthesize a button event */
1359	xevent.type = ButtonPress;
1360	xevent.xbutton.root = root;
1361	xevent.xbutton.window = child;
1362	xevent.xbutton.x_root = x_root;
1363	xevent.xbutton.y_root = y_root;
1364	xevent.xbutton.x = x_win;
1365	xevent.xbutton.y = y_win;
1366	xevent.xbutton.time = EventTime;
1367
1368	return xevent;
1369}
1370
1371
1372/*
1373 * Implementation of _NET_CLOSE_WINDOW
1374 *
1375 * window = window to be closed
1376 * message_type = _NET_CLOSE_WINDOW
1377 * format = 32
1378 * data.l[0] = timestamp
1379 * data.l[1] = source indication:
1380 *             0 Clients that support only older version of this spec
1381 *             1 for normal applications, and
1382 *             2 for pagers and other Clients that represent direct user actions
1383 * data.l[2] = 0
1384 * data.l[3] = 0
1385 * data.l[4] = 0
1386 */
1387static void EwmhClientMessage_NET_CLOSE_WINDOW(XClientMessageEvent *msg)
1388{
1389	TwmWindow *tmp_win;
1390
1391	tmp_win = GetTwmWindow(msg->window);
1392
1393	if(tmp_win != NULL) {
1394		ButtonPressed = -1;
1395		ExecuteFunction(F_DELETE, NULL, msg->window, tmp_win,
1396		                (XEvent *)msg, C_NO_CONTEXT, 0);
1397	}
1398}
1399
1400/*
1401 * Handle any PropertyNotify.
1402 */
1403int EwmhHandlePropertyNotify(XPropertyEvent *event, TwmWindow *twm_win)
1404{
1405	if(event->atom == XA__NET_WM_ICON) {
1406		EwmhHandle_NET_WM_ICONNotify(event, twm_win);
1407		return 1;
1408	}
1409	else if(event->atom == XA__NET_WM_STRUT_PARTIAL ||
1410	                event->atom == XA__NET_WM_STRUT) {
1411		EwmhHandle_NET_WM_STRUTNotify(event, twm_win);
1412		return 1;
1413	}
1414	else if(event->atom == XA__NET_WM_NAME) {
1415		char *prop = GetWMPropertyString(twm_win->w, XA__NET_WM_NAME);
1416		if(prop == NULL) {
1417			FreeWMPropertyString(twm_win->names.net_wm_name);
1418			twm_win->names.net_wm_name = NULL;
1419			apply_window_name(twm_win);
1420			return 1;
1421		}
1422
1423		if(twm_win->names.net_wm_name != NULL
1424		                && strcmp(twm_win->names.net_wm_name, prop) == 0) {
1425			/* No change, just free and skip out */
1426			free(prop);
1427			return 1;
1428		}
1429
1430		/* It's changing, free the old and bring in the new */
1431		FreeWMPropertyString(twm_win->names.net_wm_name);
1432		twm_win->names.net_wm_name = prop;
1433
1434		/* Kick the reset process */
1435		apply_window_name(twm_win);
1436		return 1;
1437	}
1438	else if(event->atom == XA__NET_WM_ICON_NAME) {
1439		char *prop = GetWMPropertyString(twm_win->w, XA__NET_WM_ICON_NAME);
1440		if(prop == NULL) {
1441			FreeWMPropertyString(twm_win->names.net_wm_icon_name);
1442			twm_win->names.net_wm_icon_name = NULL;
1443			apply_window_icon_name(twm_win);
1444			return 1;
1445		}
1446
1447		if(twm_win->names.net_wm_icon_name != NULL
1448		                && strcmp(twm_win->names.net_wm_icon_name, prop) == 0) {
1449			/* No change, just free and skip out */
1450			free(prop);
1451			return 1;
1452		}
1453
1454		/* It's changing, free the old and bring in the new */
1455		FreeWMPropertyString(twm_win->names.net_wm_icon_name);
1456		twm_win->names.net_wm_icon_name = prop;
1457
1458		/* Kick the reset process */
1459		apply_window_icon_name(twm_win);
1460		return 1;
1461	}
1462
1463	return 0;
1464}
1465
1466/*
1467 * Set the _NET_WM_DESKTOP property for the current workspace.
1468 */
1469void EwmhSet_NET_WM_DESKTOP(TwmWindow *twm_win)
1470{
1471	WorkSpace *ws;
1472
1473	VirtualScreen *vs = twm_win->vs;
1474	if(vs != NULL) {
1475		ws = vs->wsw->currentwspc;
1476	}
1477	else {
1478		ws = NULL;
1479	}
1480
1481	EwmhSet_NET_WM_DESKTOP_ws(twm_win, ws);
1482}
1483
1484/*
1485 * Set the _NET_WM_DESKTOP property for the given workspace.
1486 */
1487void EwmhSet_NET_WM_DESKTOP_ws(TwmWindow *twm_win, WorkSpace *ws)
1488{
1489	unsigned long workspaces[MAXWORKSPACE];
1490	int n = 0;
1491
1492	if(!Scr->workSpaceManagerActive) {
1493		workspaces[n++] = 0;
1494	}
1495	else if(twm_win->occupation == fullOccupation) {
1496		workspaces[n++] = ALL_WORKSPACES;
1497	}
1498	else {
1499		/*
1500		 * Our windows can occupy multiple workspaces ("virtual desktops" in
1501		 * EWMH terminology) at once. Extend the _NET_WM_DESKTOP property
1502		 * by setting it to multiple CARDINALs if this occurs.
1503		 * Put the currently visible workspace (if any) first, since typical
1504		 * pager apps don't know about this.
1505		 */
1506		int occupation = twm_win->occupation;
1507
1508		/*
1509		 * Set visible workspace number.
1510		 */
1511		if(ws != NULL) {
1512			int wsn = ws->number;
1513
1514			workspaces[n++] = wsn;
1515			occupation &= ~(1 << wsn);
1516		}
1517
1518		/*
1519		 * Set any other workspace numbers.
1520		 */
1521		if(occupation != 0) {
1522			int i;
1523			int mask = 1;
1524
1525			for(i = 0; i < MAXWORKSPACE; i++) {
1526				if(occupation & mask) {
1527					workspaces[n++] = i;
1528				}
1529				mask <<= 1;
1530			}
1531		}
1532	}
1533
1534	XChangeProperty(dpy, twm_win->w,
1535	                XA__NET_WM_DESKTOP, XA_CARDINAL,
1536	                32, PropModeReplace,
1537	                (unsigned char *)workspaces, n);
1538}
1539
1540
1541static unsigned long EwmhGetWindowProperty(Window w, Atom name, Atom type)
1542{
1543	Atom actual_type;
1544	int actual_format;
1545	unsigned long nitems, bytes_after;
1546	unsigned long *prop;
1547	unsigned long value;
1548
1549	if(XGetWindowProperty(dpy, w, name,
1550	                      0, 1, False, type,
1551	                      &actual_type, &actual_format, &nitems,
1552	                      &bytes_after, (unsigned char **)&prop) != Success) {
1553		return 0;
1554	}
1555
1556	if(actual_format == 32) {
1557		if(nitems >= 1) {
1558			value = prop[0];
1559		}
1560		else {
1561			value = 0;
1562		}
1563	}
1564	else {
1565		value = 0;
1566	}
1567
1568	XFree(prop);
1569	return value;
1570}
1571
1572/*
1573 * Simple function to get multiple properties of format 32.
1574 * If it fails, returns NULL, and *nitems_return == 0.
1575 */
1576
1577static unsigned long *EwmhGetWindowProperties(Window w, Atom name, Atom type,
1578                unsigned long *nitems_return)
1579{
1580	Atom actual_type;
1581	int actual_format;
1582	unsigned long bytes_after;
1583	unsigned long *prop;
1584
1585	if(XGetWindowProperty(dpy, w, name,
1586	                      0, 8192, False, type,
1587	                      &actual_type, &actual_format, nitems_return,
1588	                      &bytes_after, (unsigned char **)&prop) != Success) {
1589		*nitems_return = 0;
1590		return NULL;
1591	}
1592
1593	if(actual_format != 32) {
1594		XFree(prop);
1595		prop = NULL;
1596		*nitems_return = 0;
1597	}
1598
1599	return prop;
1600}
1601
1602int EwmhGetOccupation(TwmWindow *twm_win)
1603{
1604	unsigned long nitems;
1605	unsigned long *prop;
1606	int occupation;
1607
1608	occupation = 0;
1609
1610	prop = EwmhGetWindowProperties(twm_win->w,
1611	                               XA__NET_WM_DESKTOP, XA_CARDINAL, &nitems);
1612
1613	if(prop) {
1614		int i;
1615		for(i = 0; i < nitems; i++) {
1616			unsigned int val = prop[i];
1617			if(val == ALL_WORKSPACES) {
1618				occupation = fullOccupation;
1619			}
1620			else if(val < Scr->workSpaceMgr.count) {
1621				occupation |= 1 << val;
1622			}
1623			else {
1624				occupation |= 1 << (Scr->workSpaceMgr.count - 1);
1625			}
1626		}
1627
1628		occupation &= fullOccupation;
1629
1630		XFree(prop);
1631	}
1632
1633	return occupation;
1634}
1635
1636/*
1637 * The message to change the desktop of a window doesn't recognize
1638 * that it may be in more than one.
1639 *
1640 * Therefore the following heuristic is applied:
1641 * - the window is removed from the workspace where it is visible (if any);
1642 * - it is added to the given workspace;
1643 * - other occupation bits are left unchanged.
1644 *
1645 * If asked to put it on a too high numbered workspace, put it on
1646 * the highest possible.
1647 */
1648static void EwmhClientMessage_NET_WM_DESKTOP(XClientMessageEvent *msg)
1649{
1650	Window w = msg->window;
1651	TwmWindow *twm_win;
1652	int occupation;
1653	VirtualScreen *vs;
1654	unsigned int val;
1655
1656	twm_win = GetTwmWindow(w);
1657
1658	if(twm_win == NULL) {
1659		return;
1660	}
1661
1662	occupation = twm_win->occupation;
1663
1664	/* Remove from visible workspace */
1665	if((vs = twm_win->vs) != NULL) {
1666		occupation &= ~(1 << vs->wsw->currentwspc->number);
1667	}
1668
1669	val = (unsigned int)msg->data.l[0];
1670
1671	/* Add to requested workspace (or to all) */
1672	if(val == ALL_WORKSPACES) {
1673		occupation = fullOccupation;
1674	}
1675	else if(val < Scr->workSpaceMgr.count) {
1676		occupation |= 1 << val;
1677	}
1678	else {
1679		occupation |= 1 << (Scr->workSpaceMgr.count - 1);
1680	}
1681
1682	ChangeOccupation(twm_win, occupation);
1683}
1684
1685/*
1686 * Delete all properties that should be removed from a withdrawn
1687 * window.
1688 */
1689void EwmhUnmapNotify(TwmWindow *twm_win)
1690{
1691	XDeleteProperty(dpy, twm_win->w, XA__NET_WM_DESKTOP);
1692}
1693
1694/*
1695 * Add a new window to _NET_CLIENT_LIST.
1696 * Newer windows are always added at the end.
1697 *
1698 * Look at new_win->iconmanagerlist as an optimization for
1699 * !LookInList(Scr->IconMgrNoShow, new_win->name, &new_win->class)).
1700 */
1701void EwmhAddClientWindow(TwmWindow *new_win)
1702{
1703	if(Scr->ewmh_CLIENT_LIST_size == 0) {
1704		return;
1705	}
1706	if(new_win->iconmanagerlist != NULL &&
1707	                !new_win->iswspmgr &&
1708	                !new_win->isiconmgr) {
1709		Scr->ewmh_CLIENT_LIST_used++;
1710		if(Scr->ewmh_CLIENT_LIST_used > Scr->ewmh_CLIENT_LIST_size) {
1711			long *tp;
1712			int tsz = Scr->ewmh_CLIENT_LIST_size;
1713
1714			Scr->ewmh_CLIENT_LIST_size *= 2;
1715			tp = realloc(Scr->ewmh_CLIENT_LIST,
1716			             sizeof(long) * Scr->ewmh_CLIENT_LIST_size);
1717			if(tp == NULL) {
1718				Scr->ewmh_CLIENT_LIST_size = tsz;
1719				fprintf(stderr, "Unable to allocate memory for EWMH client list.\n");
1720				return;
1721			}
1722			Scr->ewmh_CLIENT_LIST = tp;
1723		}
1724		if(Scr->ewmh_CLIENT_LIST) {
1725			Scr->ewmh_CLIENT_LIST[Scr->ewmh_CLIENT_LIST_used - 1] = new_win->w;
1726		}
1727		else {
1728			Scr->ewmh_CLIENT_LIST_size = 0;
1729			fprintf(stderr, "Unable to allocate memory for EWMH client list.\n");
1730			return;
1731		}
1732		XChangeProperty(dpy, Scr->Root, XA__NET_CLIENT_LIST, XA_WINDOW, 32,
1733		                PropModeReplace, (unsigned char *)Scr->ewmh_CLIENT_LIST,
1734		                Scr->ewmh_CLIENT_LIST_used);
1735	}
1736}
1737
1738void EwmhDeleteClientWindow(TwmWindow *old_win)
1739{
1740	int i;
1741
1742	if(old_win->ewmhFlags & EWMH_HAS_STRUT) {
1743		EwmhRemoveStrut(old_win);
1744	}
1745
1746	/*
1747	 * Remove the window from _NET_CLIENT_LIST.
1748	 */
1749	if(Scr->ewmh_CLIENT_LIST_size == 0) {
1750		return;
1751	}
1752	for(i = Scr->ewmh_CLIENT_LIST_used - 1; i >= 0; i--) {
1753		if(Scr->ewmh_CLIENT_LIST[i] == old_win->w) {
1754			memmove(&Scr->ewmh_CLIENT_LIST[i],
1755			        &Scr->ewmh_CLIENT_LIST[i + 1],
1756			        (Scr->ewmh_CLIENT_LIST_used - 1 - i) * sizeof(Scr->ewmh_CLIENT_LIST[0]));
1757			Scr->ewmh_CLIENT_LIST_used--;
1758			if(Scr->ewmh_CLIENT_LIST_used &&
1759			                (Scr->ewmh_CLIENT_LIST_used * 3) < Scr->ewmh_CLIENT_LIST_size) {
1760				Scr->ewmh_CLIENT_LIST_size /= 2;
1761				Scr->ewmh_CLIENT_LIST = realloc(Scr->ewmh_CLIENT_LIST,
1762				                                sizeof((Scr->ewmh_CLIENT_LIST[0])) * Scr->ewmh_CLIENT_LIST_size);
1763				/* memory shrinking, shouldn't have problems */
1764			}
1765			break;
1766		}
1767	}
1768	/* If window was not found, there is no need to update the property. */
1769	if(i >= 0) {
1770		XChangeProperty(dpy, Scr->Root, XA__NET_CLIENT_LIST, XA_WINDOW, 32,
1771		                PropModeReplace, (unsigned char *)Scr->ewmh_CLIENT_LIST,
1772		                Scr->ewmh_CLIENT_LIST_used);
1773	}
1774}
1775
1776/*
1777 * Similar to EwmhAddClientWindow() and EwmhDeleteClientWindow(),
1778 * but the windows are in stacking order.
1779 * Therefore we look at the OTPs, which are by definition in the correct order.
1780 */
1781
1782void EwmhSet_NET_CLIENT_LIST_STACKING(void)
1783{
1784	int size;
1785	unsigned long *prop;
1786	TwmWindow *twm_win;
1787	int i;
1788
1789	/* Expect the same number of windows as in the _NET_CLIENT_LIST */
1790	size = Scr->ewmh_CLIENT_LIST_used + 10;
1791	prop = calloc(size, sizeof(unsigned long));
1792	if(prop == NULL) {
1793		return;
1794	}
1795
1796	i = 0;
1797	for(twm_win = OtpBottomWin();
1798	                twm_win != NULL;
1799	                twm_win = OtpNextWinUp(twm_win)) {
1800		if(twm_win->iconmanagerlist != NULL &&
1801		                !twm_win->iswspmgr &&
1802		                !twm_win->isiconmgr) {
1803			prop[i] = twm_win->w;
1804			i++;
1805			if(i > size) {
1806				fprintf(stderr, "Too many stacked windows\n");
1807				break;
1808			}
1809		}
1810	}
1811
1812	if(i != Scr->ewmh_CLIENT_LIST_used) {
1813		fprintf(stderr, "Incorrect number of stacked windows: %d (expected %d)\n",
1814		        i, Scr->ewmh_CLIENT_LIST_used);
1815	}
1816
1817	XChangeProperty(dpy, Scr->Root, XA__NET_CLIENT_LIST_STACKING, XA_WINDOW, 32,
1818	                PropModeReplace, (unsigned char *)prop, i);
1819
1820	free(prop);
1821}
1822
1823void EwmhSet_NET_ACTIVE_WINDOW(Window w)
1824{
1825	unsigned long prop[1];
1826
1827	prop[0] = w;
1828
1829	XChangeProperty(dpy, Scr->Root, XA__NET_ACTIVE_WINDOW, XA_WINDOW, 32,
1830	                PropModeReplace, (unsigned char *)prop, 1);
1831}
1832
1833/*
1834 * Get window properties as relevant when the window is initially mapped.
1835 *
1836 * So far, only NET_WM_WINDOW_TYPE and _NET_WM_STRUT_PARTIAL.
1837 * In particular, most of the initial value of _NET_WM_STATE is ignored. TODO.
1838 *
1839 * Also do any generic initialisation needed to EWMH-specific fields
1840 * in a TwmWindow.
1841 */
1842void EwmhGetProperties(TwmWindow *twm_win)
1843{
1844	twm_win->ewmhFlags = 0;
1845
1846	Atom type = EwmhGetWindowProperty(twm_win->w, XA__NET_WM_WINDOW_TYPE, XA_ATOM);
1847
1848	if(type == XA__NET_WM_WINDOW_TYPE_DESKTOP) {
1849		twm_win->ewmhWindowType = wt_Desktop;
1850	}
1851	else if(type == XA__NET_WM_WINDOW_TYPE_DOCK) {
1852		twm_win->ewmhWindowType = wt_Dock;
1853	}
1854	else {
1855		twm_win->ewmhWindowType = wt_Normal;
1856	}
1857	EwmhGetStrut(twm_win, false);
1858	/* Only the 3 listed states are supported for now */
1859	twm_win->ewmhFlags |= EwmhGet_NET_WM_STATE(twm_win) &
1860	                      (EWMH_STATE_ABOVE | EWMH_STATE_BELOW | EWMH_STATE_SHADED);
1861}
1862
1863/* Only used in initially mapping a window */
1864int EwmhGetInitPriority(TwmWindow *twm_win)
1865{
1866	switch(twm_win->ewmhWindowType) {
1867		case wt_Desktop:
1868			return EWMH_PRI_DESKTOP;
1869		case wt_Dock:
1870			return EWMH_PRI_DOCK;
1871		default:
1872			return 0;
1873	}
1874}
1875
1876bool EwmhHasBorder(TwmWindow *twm_win)
1877{
1878	switch(twm_win->ewmhWindowType) {
1879		case wt_Desktop:
1880		case wt_Dock:
1881			return false;
1882		default:
1883			return true;
1884	}
1885}
1886
1887bool EwmhHasTitle(TwmWindow *twm_win)
1888{
1889	switch(twm_win->ewmhWindowType) {
1890		case wt_Desktop:
1891		case wt_Dock:
1892			return false;
1893		default:
1894			return true;
1895	}
1896}
1897
1898bool EwmhOnWindowRing(TwmWindow *twm_win)
1899{
1900	switch(twm_win->ewmhWindowType) {
1901		case wt_Desktop:
1902		case wt_Dock:
1903			return false;
1904		default:
1905			return true;
1906	}
1907}
1908
1909/*
1910 * Recalculate the effective border values from the remembered struts.
1911 * Interestingly it is not documented how to do that.
1912 * Usually only one dock is present on each side, so it shouldn't matter
1913 * too much, but I presume that maximizing the values is the thing to do.
1914 */
1915static void EwmhRecalculateStrut(void)
1916{
1917	int left   = 0;
1918	int right  = 0;
1919	int top    = 0;
1920	int bottom = 0;
1921	EwmhStrut *strut = Scr->ewmhStruts;
1922
1923	while(strut != NULL) {
1924		left   = max(left,   strut->left);
1925		right  = max(right,  strut->right);
1926		top    = max(top,    strut->top);
1927		bottom = max(bottom, strut->bottom);
1928
1929		strut  = strut->next;
1930	}
1931
1932	Scr->BorderLeft   = left;
1933	Scr->BorderRight  = right;
1934	Scr->BorderTop    = top;
1935	Scr->BorderBottom = bottom;
1936
1937	// Bordered layout may have changed
1938	Scr->BorderedLayout = RLayoutCopyCropped(Scr->Layout,
1939	                      left, right, top, bottom);
1940	if(Scr->BorderedLayout == NULL) {
1941		Scr->BorderedLayout = Scr->Layout;        // nothing to crop
1942	}
1943
1944	EwmhSet_NET_WORKAREA(Scr);
1945}
1946/*
1947 * Check _NET_WM_STRUT_PARTIAL or _NET_WM_STRUT.
1948 * These are basically automatic settings for Border{Left,Right,Top,Bottom}.
1949 *
1950 * If any values are found, collect them in a list of strut values
1951 * belonging to Scr.  When a window is added or removed that has struts,
1952 * the new effective value must be calculated.  The expectation is that
1953 * at most a handful of windows will have struts.
1954 *
1955 * If update is true, this is called as an update for an existing window.
1956 */
1957static void EwmhGetStrut(TwmWindow *twm_win, bool update)
1958{
1959	unsigned long nitems;
1960	unsigned long *prop;
1961	EwmhStrut *strut;
1962
1963	prop = EwmhGetWindowProperties(twm_win->w,
1964	                               XA__NET_WM_STRUT_PARTIAL, XA_CARDINAL,
1965	                               &nitems);
1966	if(prop == NULL) {
1967		prop = EwmhGetWindowProperties(twm_win->w,
1968		                               XA__NET_WM_STRUT, XA_CARDINAL,  &nitems);
1969		if(prop == NULL) {
1970			return;
1971		}
1972	}
1973
1974
1975	if(nitems < 4) {
1976#ifdef DEBUG_EWMH
1977		/* This happens a lot, despite returning Success ??? */
1978		printf("struts: prop = %p, nitems = %ld\n", prop, nitems);
1979#endif
1980		XFree(prop);
1981		return;
1982	}
1983#ifdef DEBUG_EWMH
1984	printf("struts: left %ld, right %ld, top %ld, bottom %ld\n",
1985	       prop[0], prop[1], prop[2], prop[3]);
1986#endif
1987
1988	/*
1989	 * If there were no struts before, the user configured margins are set
1990	 * in Border{Left,Right,Top,Bottom}. In order not to lose those values
1991	 * when recalculating them, convert them to struts for a dummy window.
1992	 */
1993
1994	if(Scr->ewmhStruts == NULL &&
1995	                (Scr->BorderLeft |
1996	                 Scr->BorderRight |
1997	                 Scr->BorderTop |
1998	                 Scr->BorderBottom) != 0) {
1999		strut = calloc(1, sizeof(EwmhStrut));
2000		if(strut == NULL) {
2001			XFree(prop);
2002			return;
2003		}
2004
2005		strut->next   = NULL;
2006		strut->win    = NULL;
2007		strut->left   = Scr->BorderLeft;
2008		strut->right  = Scr->BorderRight;
2009		strut->top    = Scr->BorderTop;
2010		strut->bottom = Scr->BorderBottom;
2011
2012		Scr->ewmhStruts = strut;
2013	}
2014
2015	strut = NULL;
2016
2017	/*
2018	 * Find the struts of the window that we're supposed to be updating.
2019	 * If not found, there is no problem: we'll just allocate a new
2020	 * record.
2021	 */
2022
2023	if(update) {
2024		strut = Scr->ewmhStruts;
2025
2026		while(strut != NULL) {
2027			if(strut->win == twm_win) {
2028				break;
2029			}
2030			strut = strut->next;
2031		}
2032	}
2033
2034	/*
2035	 * If needed, allocate a new struts record and link it in.
2036	 */
2037	if(strut == NULL) {
2038		strut = calloc(1, sizeof(EwmhStrut));
2039		if(strut == NULL) {
2040			XFree(prop);
2041			return;
2042		}
2043
2044		strut->next = Scr->ewmhStruts;
2045		Scr->ewmhStruts = strut;
2046	}
2047
2048	strut->win    = twm_win;
2049	strut->left   = prop[0];
2050	strut->right  = prop[1];
2051	strut->top    = prop[2];
2052	strut->bottom = prop[3];
2053
2054	XFree(prop);
2055
2056	/*
2057	 * Mark this window as having contributed some struts.
2058	 * This can be checked and undone when the window is deleted.
2059	 */
2060	twm_win->ewmhFlags |= EWMH_HAS_STRUT;
2061
2062	EwmhRecalculateStrut();
2063}
2064
2065/*
2066 * Remove the struts associated with the given window from the
2067 * remembered list. If found, recalculate the effective borders.
2068 */
2069static void EwmhRemoveStrut(TwmWindow *twm_win)
2070{
2071	EwmhStrut **prev = &Scr->ewmhStruts;
2072	EwmhStrut *strut = Scr->ewmhStruts;
2073
2074	while(strut != NULL) {
2075		if(strut->win == twm_win) {
2076			twm_win->ewmhFlags &= ~EWMH_HAS_STRUT;
2077
2078			*prev = strut->next;
2079			free(strut);
2080
2081			EwmhRecalculateStrut();
2082
2083			break;
2084		}
2085		prev = &strut->next;
2086		strut = strut->next;
2087	}
2088}
2089
2090
2091/**
2092 * Set _NET_FRAME_EXTENTS property.
2093 * This tells the client how much space is being taken up by the window
2094 * decorations.  Some clients may need this information to position other
2095 * windows on top of themselves.  e.g., Firefox's form autofill and
2096 * context menu will be positioned a bit wrong (high, by the height of
2097 * the titlebar) without this.
2098 */
2099void EwmhSet_NET_FRAME_EXTENTS(TwmWindow *twm_win)
2100{
2101	long data[4];
2102	const long w = twm_win->frame_bw3D + twm_win->frame_bw;
2103
2104	data[0] = w; // left
2105	data[1] = w; // right
2106	data[2] = twm_win->title_height + w; // top
2107	data[3] = w; // bottom
2108
2109	XChangeProperty(dpy, twm_win->w,
2110	                XA__NET_FRAME_EXTENTS, XA_CARDINAL,
2111	                32, PropModeReplace,
2112	                (unsigned char *)data, 4);
2113}
2114
2115
2116void EwmhSet_NET_SHOWING_DESKTOP(int state)
2117{
2118	unsigned long prop[1];
2119
2120	prop[0] = state;
2121
2122	XChangeProperty(dpy, Scr->XineramaRoot, XA__NET_SHOWING_DESKTOP, XA_CARDINAL,
2123	                32,
2124	                PropModeReplace, (unsigned char *)prop, 1);
2125}
2126
2127/*
2128 * Set _NET_WM_STATE.
2129 *
2130 * TwmWindow.ewmhFlags keeps track of the atoms that should be in
2131 * the list, so that we don't have to fetch or recalculate them all.
2132 *
2133 * XXX It's not clear that the theoretical performance gain and edge-case
2134 * bug avoidance of the 'changes' arg is worth the complexity and
2135 * edge-case bug creation it brings.  Consider removing it.
2136 */
2137void EwmhSet_NET_WM_STATE(TwmWindow *twm_win, int changes)
2138{
2139	unsigned long prop[10];
2140	int flags;
2141	int i;
2142
2143	if(changes & EWMH_STATE_MAXIMIZED_VERT) {
2144		int newFlags = 0;
2145
2146		switch(twm_win->zoomed) {
2147			case F_FULLZOOM:
2148				newFlags = EWMH_STATE_MAXIMIZED_VERT |
2149				           EWMH_STATE_MAXIMIZED_HORZ;
2150				break;
2151			case F_ZOOM:
2152			case F_LEFTZOOM:
2153			case F_RIGHTZOOM:
2154				newFlags = EWMH_STATE_MAXIMIZED_VERT;
2155				break;
2156			case F_HORIZOOM:
2157			case F_TOPZOOM:
2158			case F_BOTTOMZOOM:
2159				newFlags = EWMH_STATE_MAXIMIZED_HORZ;
2160				break;
2161			case F_FULLSCREENZOOM:
2162				newFlags = EWMH_STATE_FULLSCREEN;
2163				break;
2164		}
2165
2166		twm_win->ewmhFlags &= ~(EWMH_STATE_MAXIMIZED_VERT |
2167		                        EWMH_STATE_MAXIMIZED_HORZ |
2168		                        EWMH_STATE_FULLSCREEN);
2169		twm_win->ewmhFlags |= newFlags;
2170	}
2171
2172	if(changes & EWMH_STATE_SHADED) {
2173		if(twm_win->squeezed) {
2174			twm_win->ewmhFlags |= EWMH_STATE_SHADED;
2175		}
2176		else {
2177			twm_win->ewmhFlags &= ~EWMH_STATE_SHADED;
2178		}
2179	}
2180
2181	if(changes & (EWMH_STATE_ABOVE | EWMH_STATE_BELOW)) {
2182		int pri = OtpEffectiveDisplayPriority(twm_win);
2183
2184		twm_win->ewmhFlags &= ~(EWMH_STATE_ABOVE | EWMH_STATE_BELOW);
2185
2186		/*
2187		 * If it's a DOCK or DESKTOP, and where we expect those to be, we
2188		 * consider that there's nothing to tell it.  Otherwise, we tell
2189		 * it ABOVE/BELOW based on where it effectively is.
2190		 */
2191		if(twm_win->ewmhWindowType == wt_Dock && pri == EWMH_PRI_DOCK) {
2192			pri = 0;
2193		}
2194		if(twm_win->ewmhWindowType == wt_Desktop && pri == EWMH_PRI_DESKTOP) {
2195			pri = 0;
2196		}
2197
2198		if(pri > 0) {
2199			twm_win->ewmhFlags |= EWMH_STATE_ABOVE;
2200		}
2201		else if(pri < 0) {
2202			twm_win->ewmhFlags |= EWMH_STATE_BELOW;
2203		}
2204	}
2205
2206	flags = twm_win->ewmhFlags;
2207	i = 0;
2208
2209	if(flags & EWMH_STATE_MAXIMIZED_VERT) {
2210		prop[i++] = XA__NET_WM_STATE_MAXIMIZED_VERT;
2211	}
2212	if(flags & EWMH_STATE_MAXIMIZED_HORZ) {
2213		prop[i++] = XA__NET_WM_STATE_MAXIMIZED_HORZ;
2214	}
2215	if(flags & EWMH_STATE_FULLSCREEN) {
2216		prop[i++] = XA__NET_WM_STATE_FULLSCREEN;
2217	}
2218	if(flags & EWMH_STATE_SHADED) {
2219		prop[i++] = XA__NET_WM_STATE_SHADED;
2220	}
2221	if(flags & EWMH_STATE_ABOVE) {
2222		prop[i++] = XA__NET_WM_STATE_ABOVE;
2223	}
2224	if(flags & EWMH_STATE_BELOW) {
2225		prop[i++] = XA__NET_WM_STATE_BELOW;
2226	}
2227
2228	XChangeProperty(dpy, twm_win->w, XA__NET_WM_STATE, XA_ATOM, 32,
2229	                PropModeReplace, (unsigned char *)prop, i);
2230}
2231
2232/*
2233 * Get the initial state of _NET_WM_STATE.
2234 *
2235 * Only some of the flags are supported when initially creating a window.
2236 */
2237static int EwmhGet_NET_WM_STATE(TwmWindow *twm_win)
2238{
2239	int flags = 0;
2240	unsigned long *prop;
2241	unsigned long nitems;
2242	int i;
2243
2244	prop = EwmhGetWindowProperties(twm_win->w, XA__NET_WM_STATE, XA_ATOM, &nitems);
2245
2246	if(prop) {
2247		for(i = 0; i < nitems; i++) {
2248			flags |= atomToFlag(prop[i]);
2249		}
2250
2251		XFree(prop);
2252	}
2253
2254	return flags;
2255}
2256
2257/*
2258 * Set _NET_WORKAREA.
2259 */
2260static void EwmhSet_NET_WORKAREA(ScreenInfo *scr)
2261{
2262	unsigned long prop[4];
2263
2264	/* x */ prop[0] = scr->BorderLeft;
2265	/* y */ prop[1] = scr->BorderTop;
2266	/* w */ prop[2] = scr->rootw - scr->BorderLeft - scr->BorderRight;
2267	/* h */ prop[3] = scr->rooth - scr->BorderTop - scr->BorderBottom;
2268
2269	XChangeProperty(dpy, Scr->XineramaRoot, XA__NET_WORKAREA, XA_CARDINAL, 32,
2270	                PropModeReplace, (unsigned char *)prop, 4);
2271}
2272