Home | History | Annotate | Line # | Download | only in dist
      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 
     61 Atom 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 
     80 static Image *ExtractIcon(ScreenInfo *scr, unsigned long *prop, int width,
     81                           int height);
     82 static void EwmhClientMessage_NET_WM_DESKTOP(XClientMessageEvent *msg);
     83 static void EwmhClientMessage_NET_WM_STATE(XClientMessageEvent *msg);
     84 static void EwmhClientMessage_NET_ACTIVE_WINDOW(XClientMessageEvent *msg);
     85 static void EwmhClientMessage_NET_WM_MOVERESIZE(XClientMessageEvent *msg);
     86 static void EwmhClientMessage_NET_CLOSE_WINDOW(XClientMessageEvent *msg);
     87 static XEvent synth_btnevent_for_moveresize(TwmWindow *twm_win);
     88 static unsigned long EwmhGetWindowProperty(Window w, Atom name, Atom type);
     89 static void EwmhGetStrut(TwmWindow *twm_win, bool update);
     90 static void EwmhRemoveStrut(TwmWindow *twm_win);
     91 static void EwmhSet_NET_WORKAREA(ScreenInfo *scr);
     92 static int EwmhGet_NET_WM_STATE(TwmWindow *twm_win);
     93 static void EwmhClientMessage_NET_WM_STATEchange(TwmWindow *twm_win, int change,
     94                 int newVal);
     95 
     96 #define ALL_WORKSPACES  0xFFFFFFFFU
     97 
     98 static 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 
    119 static void EwmhInitAtoms(void)
    120 {
    121 	XInternAtoms(dpy, XEWMHAtomNames, NUM_EWMH_XATOMS, False, XEWMHAtom);
    122 }
    123 
    124 static bool caughtError;
    125 
    126 static int CatchError(Display *display, XErrorEvent *event)
    127 {
    128 	caughtError = true;
    129 	return 0;
    130 }
    131 
    132 void 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 
    144 static 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  */
    194 static 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  */
    307 bool 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  */
    349 void 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  */
    475 void 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 
    512 static 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 
    528 void 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 
    546 void 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  */
    561 bool 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 
    637 Image *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 
    835 static uint16_t *buffer_16bpp;
    836 static uint32_t *buffer_32bpp;
    837 
    838 static 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 
    846 static void convert_for_32(int w, int x, int y, int argb)
    847 {
    848 	buffer_32bpp [y * w + x] = argb & 0x00FFFFFF;
    849 }
    850 
    851 static 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  */
    947 static 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  */
   1018 static 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  */
   1027 static 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  */
   1066 static 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  */
   1144 static 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  */
   1227 static 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  */
   1269 static 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 
   1342 static XEvent
   1343 synth_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  */
   1387 static 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  */
   1403 int 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  */
   1469 void 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  */
   1487 void 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 
   1541 static 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 
   1577 static 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 
   1602 int 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  */
   1648 static 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  */
   1689 void 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  */
   1701 void 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 
   1738 void 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 
   1782 void 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 
   1823 void 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  */
   1842 void 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 */
   1864 int 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 
   1876 bool 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 
   1887 bool 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 
   1898 bool 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  */
   1915 static 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  */
   1957 static 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  */
   2069 static 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  */
   2099 void 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 
   2116 void 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  */
   2137 void 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  */
   2237 static 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  */
   2260 static 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