Home | History | Annotate | Line # | Download | only in dist
      1 /*
      2  * Copyright 1992 Claude Lecommandeur.
      3  */
      4 
      5 #include "ctwm.h"
      6 
      7 #include <stdio.h>
      8 #include <string.h>
      9 #include <stdlib.h>
     10 
     11 #include <X11/Xatom.h>
     12 
     13 #include "ctwm_atoms.h"
     14 #include "util.h"
     15 #include "animate.h"
     16 #include "screen.h"
     17 #include "add_window.h"
     18 #include "events.h"
     19 #include "otp.h"
     20 #include "cursor.h"
     21 #include "image.h"
     22 #include "drawing.h"
     23 #include "list.h"
     24 #include "occupation.h"
     25 #include "vscreen.h"
     26 #include "win_decorations.h"
     27 #include "win_iconify.h"
     28 #include "win_ops.h"
     29 #include "win_utils.h"
     30 #include "workspace_manager.h"
     31 #include "workspace_utils.h"
     32 #include "xparsegeometry.h"
     33 
     34 
     35 #include "gram.tab.h"
     36 
     37 
     38 // Temp; x-ref desc in workspace_utils
     39 extern bool useBackgroundInfo;
     40 
     41 
     42 static void CreateWorkSpaceManagerWindow(VirtualScreen *vs);
     43 static void ResizeWorkSpaceManager(VirtualScreen *vs, TwmWindow *win);
     44 static void PaintWorkSpaceManagerBorder(VirtualScreen *vs);
     45 
     46 static void wmap_mapwin_backend(TwmWindow *win, bool handleraise);
     47 
     48 static void WMapRedrawWindow(Window window, int width, int height,
     49                              ColorPair cp, const char *label);
     50 
     51 static void InvertColorPair(ColorPair *cp);
     52 
     53 
     54 static XContext MapWListContext = None;
     55 static Cursor handCursor = None;
     56 
     57 
     58 
     59 /*
     60  ****************************************************************
     61  *
     62  * First, functions related to general creation and drawing of the WSM
     63  * window and its backing structs
     64  *
     65  ****************************************************************
     66  */
     67 
     68 /*
     69  * Allocate an X Context for WSM stuff.
     70  */
     71 void
     72 InitWorkSpaceManagerContext(void)
     73 {
     74 	if(MapWListContext == None) {
     75 		MapWListContext = XUniqueContext();
     76 	}
     77 }
     78 
     79 
     80 /**
     81  * Prep up structures for WSM windows in each VS.  Called (for each
     82  * Screen) in startup after InitVirtualScreens() has setup the VS stuff
     83  * (and after config file processing).  This also retrieves info for each
     84  * vs about which workspace is active, if available (from restarting
     85  * ourself, etc).
     86  *
     87  * XXX Passed param isn't quite complete, as we call some funcs that use
     88  * global Scr...
     89  */
     90 void
     91 ConfigureWorkSpaceManager(ScreenInfo *scr)
     92 {
     93 	WorkSpace *ws = Scr->workSpaceMgr.workSpaceList;
     94 	char *vsmapbuf, *vsmap;
     95 
     96 	// Get the workspace name that's up on this vscreen.  This is
     97 	// where the startup process actually sets what workspace we're
     98 	// [re]starting in.
     99 	vsmapbuf = CtwmGetVScreenMap(dpy, Scr->Root);
    100 	if(vsmapbuf != NULL) {
    101 		// Property is a comma-separate list of the workspaces for
    102 		// each vscreen, in magic order.  So we start by chopping off
    103 		// the first and then re-chop in the loop below.
    104 		vsmap = strtok(vsmapbuf, ",");
    105 	}
    106 	else {
    107 		vsmap = NULL;
    108 	}
    109 
    110 
    111 	// Setup each vs
    112 	for(VirtualScreen *vs = scr->vScreenList; vs != NULL; vs = vs->next) {
    113 		WorkSpace *fws;
    114 
    115 		/*
    116 		 * Make sure this is all properly initialized to nothing.  Otherwise
    117 		 * bad and undefined behavior can show up in certain cases (e.g.,
    118 		 * with no Workspaces {} defined in .ctwmrc, the only defined
    119 		 * workspace will be random memory bytes, which can causes crashes on
    120 		 * e.g.  f.menu "TwmWindows".)
    121 		 */
    122 		WorkSpaceWindow *wsw = calloc(1, sizeof(WorkSpaceWindow));
    123 		wsw->state = scr->workSpaceMgr.initialstate;
    124 
    125 		// If we have a current ws for this vs, assign it in, and
    126 		// loop onward to the ws for the next vs.  For any we don't
    127 		// have a default for, the default ws is the first one we haven't
    128 		// used yet.
    129 		if((fws = GetWorkspace(vsmap)) != NULL) {
    130 			wsw->currentwspc = fws;
    131 			vsmap = strtok(NULL, ",");
    132 		}
    133 		else {
    134 			wsw->currentwspc = ws;
    135 			if(ws) {
    136 				ws = ws->next;
    137 			}
    138 		}
    139 
    140 		vs->wsw = wsw;
    141 	}
    142 
    143 	free(vsmapbuf);
    144 }
    145 
    146 
    147 
    148 
    149 /*
    150  * Create workspace manager windows for each vscreen.  Called (for each
    151  * screen) late in startup, after the preceeding funcs have run their
    152  * course.
    153  */
    154 void
    155 CreateWorkSpaceManager(void)
    156 {
    157 	if(! Scr->workSpaceManagerActive) {
    158 		return;
    159 	}
    160 
    161 	/* Setup basic fonts/colors/cursors */
    162 	Scr->workSpaceMgr.windowFont.basename =
    163 	        "-adobe-courier-medium-r-normal--10-100-75-75-m-60-iso8859-1";
    164 	Scr->workSpaceMgr.buttonFont = Scr->IconManagerFont;
    165 	Scr->workSpaceMgr.cp         = Scr->IconManagerC;
    166 	if(!Scr->BeNiceToColormap) {
    167 		GetShadeColors(&Scr->workSpaceMgr.cp);
    168 	}
    169 	if(handCursor == None) {
    170 		NewFontCursor(&handCursor, "top_left_arrow");
    171 	}
    172 
    173 
    174 	/*
    175 	 * Create a WSM window for each vscreen.  We don't need one for each
    176 	 * workspace (we just reuse the same one), but we do need one for
    177 	 * each vscreen (since they have to be displayed simultaneously).
    178 	 */
    179 	for(VirtualScreen *vs = Scr->vScreenList; vs != NULL; vs = vs->next) {
    180 		CreateWorkSpaceManagerWindow(vs);
    181 	}
    182 
    183 
    184 	/*
    185 	 * Init background in the WSM workspace subwindow and potentially the
    186 	 * root window to the settings for the active workspace
    187 	 *
    188 	 * XXX CTAG_BGDRAW This process is also done in similar fashion
    189 	 * during CreateWorkSpaceManagerWindow(), and the two parts are done
    190 	 * split well apart during GotoWorkSpace().  The details of the
    191 	 * process should be factored out into helper functions instead of
    192 	 * being reimplemented in each place.  That will require a little
    193 	 * shuffling of code, and careful thinking on the apparent
    194 	 * differences (which seem like they may be cosmetic).  Todo.
    195 	 */
    196 	for(VirtualScreen *vs = Scr->vScreenList; vs != NULL; vs = vs->next) {
    197 		WorkSpaceWindow *wsw = vs->wsw;    // Our WSW
    198 		WorkSpace *ws2 = wsw->currentwspc; // Active WS
    199 		MapSubwindow *msw = wsw->mswl[ws2->number]; // Active WS's subwin
    200 
    201 		/* Setup the background/border on the active workspace */
    202 		if(Scr->workSpaceMgr.curImage == NULL) {
    203 			if(Scr->workSpaceMgr.curPaint) {
    204 				XSetWindowBackground(dpy, msw->w, Scr->workSpaceMgr.curColors.back);
    205 			}
    206 		}
    207 		else {
    208 			XSetWindowBackgroundPixmap(dpy, msw->w, Scr->workSpaceMgr.curImage->pixmap);
    209 		}
    210 		XSetWindowBorder(dpy, msw->w, Scr->workSpaceMgr.curBorderColor);
    211 		XClearWindow(dpy, msw->w);
    212 
    213 		/* Set the root window to the color/image of that WS if we should */
    214 		if(useBackgroundInfo && ! Scr->DontPaintRootWindow) {
    215 			if(ws2->image == NULL) {
    216 				XSetWindowBackground(dpy, vs->window, ws2->backcp.back);
    217 			}
    218 			else {
    219 				XSetWindowBackgroundPixmap(dpy, vs->window, ws2->image->pixmap);
    220 			}
    221 			XClearWindow(dpy, vs->window);
    222 		}
    223 	}
    224 
    225 
    226 	/*
    227 	 * Set the property we use to store the full list of workspaces.
    228 	 *
    229 	 * XXX This isn't really part of creating the WSM windows, so doesn't
    230 	 * strictly belong here.  It does need to happen after the config
    231 	 * file parsing setup the workspaces, so couldn't go into
    232 	 * InitWorkSpaceManager().  It could probably move into
    233 	 * ConfigureWorkSpaceManager() though, or could move into a separate
    234 	 * hypotehtical ConfigureWorkSpaces() sort of thing...
    235 	 */
    236 	{
    237 		char *wrkSpcList;
    238 		int  len;
    239 
    240 		len = GetPropertyFromMask(0xFFFFFFFFu, &wrkSpcList);
    241 		XChangeProperty(dpy, Scr->Root, XA_WM_WORKSPACESLIST, XA_STRING, 8,
    242 		                PropModeReplace, (unsigned char *) wrkSpcList, len);
    243 		free(wrkSpcList);
    244 	}
    245 }
    246 
    247 
    248 /*
    249  * Put together the actual window for the workspace manager.  Called as
    250  * part of CreateWorkSpaceManager() during startup, once per vscreen
    251  * (since there's a separate window for each).
    252  */
    253 static void
    254 CreateWorkSpaceManagerWindow(VirtualScreen *vs)
    255 {
    256 	unsigned int width, height;
    257 	TwmWindow *tmp_win;
    258 	int x, y, gravity;
    259 	/* Shortcuts */
    260 	const int vspace = Scr->workSpaceMgr.vspace;
    261 	const int hspace = Scr->workSpaceMgr.hspace;
    262 	const long count = Scr->workSpaceMgr.count;
    263 
    264 	/* No workspaces?  Nothing to do. */
    265 	if(count == 0) {
    266 		return;
    267 	}
    268 
    269 	/*
    270 	 * Work out grid.  wSM.columns will be filled if specified in
    271 	 * WorkSpaceManageGeometry, or uninitialized (0) if not.
    272 	 */
    273 	{
    274 		int lines, columns;
    275 		columns = Scr->workSpaceMgr.columns;
    276 		if(columns == 0) {
    277 			lines = 2;
    278 			columns = ((count - 1) / lines) + 1;
    279 		}
    280 		else {
    281 			lines = ((count - 1) / columns) + 1;
    282 		}
    283 		Scr->workSpaceMgr.lines   = lines;
    284 		Scr->workSpaceMgr.columns = columns;
    285 	}
    286 
    287 
    288 	/* Work out dimensions of stuff */
    289 	{
    290 		unsigned int bwidth, bheight;
    291 		unsigned short strWid;
    292 		WorkSpace *ws;
    293 		const char *geometry = Scr->workSpaceMgr.geometry;
    294 		const int lines      = Scr->workSpaceMgr.lines;
    295 		const int columns    = Scr->workSpaceMgr.columns;
    296 
    297 		/* Figure longest label */
    298 		strWid = 0;
    299 		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
    300 			XRectangle inc_rect;
    301 			XRectangle logical_rect;
    302 			unsigned short wid;
    303 			const MyFont font = Scr->workSpaceMgr.buttonFont;
    304 
    305 			XmbTextExtents(font.font_set, ws->label, strlen(ws->label),
    306 			               &inc_rect, &logical_rect);
    307 			wid = logical_rect.width;
    308 			if(wid > strWid) {
    309 				strWid = wid;
    310 			}
    311 		}
    312 
    313 		/*
    314 		 * If WorkSpaceManagerGeometry is given, work from that.  Else,
    315 		 * create a workable minimum ourselves.
    316 		 * */
    317 		if(geometry != NULL) {
    318 			int mask;
    319 
    320 			/* Base button/subwindow sizes */
    321 			bwidth = strWid + 10;
    322 			bheight = 22;
    323 
    324 			/* Adjust to WSMGeometry if specified */
    325 			mask = RLayoutXParseGeometry(Scr->Layout, geometry, &x, &y, &width, &height);
    326 			if(mask & WidthValue) {
    327 				bwidth = (width - (columns * hspace)) / columns;
    328 			}
    329 			if(mask & HeightValue) {
    330 				bheight = (height - (lines * vspace)) / lines;
    331 			}
    332 
    333 			/* Size of the whole thing is based off those */
    334 			width  = columns * (bwidth  + hspace);
    335 			height = lines   * (bheight + vspace);
    336 
    337 			/*
    338 			 * If no Y given, put it at the bottom of the screen.  If one
    339 			 * is, just accept it.  If it's a negative, we have to figure
    340 			 * out where that actually is on this vscreen.
    341 			 */
    342 			if(!(mask & YValue)) {
    343 				y = 0;
    344 				mask |= YNegative;
    345 			}
    346 			if(mask & YNegative) {
    347 				y += vs->h - height;
    348 			}
    349 
    350 			/*
    351 			 * If X is given, tweak as necessary for the vscreen
    352 			 * location.  Otherwise, put it in in something like the
    353 			 * middle.
    354 			 */
    355 			if(mask & XValue) {
    356 				if(mask & XNegative) {
    357 					x += vs->w - width;
    358 					gravity = (mask & YNegative) ? SouthEastGravity : NorthEastGravity;
    359 				}
    360 				else {
    361 					gravity = (mask & YNegative) ? SouthWestGravity : NorthWestGravity;
    362 				}
    363 			}
    364 			else {
    365 				x = (vs->w - width) / 2;
    366 				gravity = (mask & YValue) ? ((mask & YNegative) ?
    367 				                             SouthGravity : NorthGravity) : SouthGravity;
    368 			}
    369 		}
    370 		else {
    371 			/* No geom specified, come up with one */
    372 			bwidth  = strWid + 2 * Scr->WMgrButtonShadowDepth + 6;
    373 			bheight = 22;
    374 			width   = columns * (bwidth  + hspace);
    375 			height  = lines   * (bheight + vspace);
    376 			x       = (vs->w - width) / 2;
    377 			y       = vs->h - height;
    378 			gravity = NorthWestGravity;
    379 		}
    380 	}
    381 
    382 	/* Set w/h to dummy values; ResizeWorkSpaceManager() writes real ones */
    383 	vs->wsw->width  = 1;
    384 	vs->wsw->height = 1;
    385 
    386 	/* Allocate structs for map/button subwindows */
    387 	vs->wsw->bswl = calloc(count, sizeof(ButtonSubwindow *));
    388 	vs->wsw->mswl = calloc(count, sizeof(MapSubwindow *));
    389 
    390 	/* Create main window */
    391 	vs->wsw->w = XCreateSimpleWindow(dpy, Scr->Root, x, y, width, height, 0,
    392 	                                 Scr->Black, Scr->workSpaceMgr.cp.back);
    393 
    394 
    395 	/*
    396 	 * Create the map and button subwindows for each workspace
    397 	 */
    398 	{
    399 		WorkSpace *ws;
    400 
    401 		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
    402 			MapSubwindow *msw;
    403 			ButtonSubwindow *bsw;
    404 			const int Dummy = 1;
    405 			const unsigned long border = Scr->workSpaceMgr.defBorderColor;
    406 
    407 			/* Alloc structs */
    408 			vs->wsw->bswl[ws->number] = bsw
    409 			                            = calloc(1, sizeof(ButtonSubwindow));
    410 			vs->wsw->mswl[ws->number] = msw = calloc(1, sizeof(MapSubwindow));
    411 
    412 			/*
    413 			 * Create windows for button/map.  ResizeWorkSpaceManager()
    414 			 * sets the real sizes and positions, so we dummy 'em.
    415 			 */
    416 			bsw->w = XCreateSimpleWindow(dpy, vs->wsw->w,
    417 			                             Dummy, Dummy, Dummy, Dummy,
    418 			                             0, Scr->Black, ws->cp.back);
    419 
    420 			msw->w = XCreateSimpleWindow(dpy, vs->wsw->w,
    421 			                             Dummy, Dummy, Dummy, Dummy,
    422 			                             1, border, ws->cp.back);
    423 
    424 			/* Map whichever is up by default */
    425 			if(vs->wsw->state == WMS_buttons) {
    426 				XMapWindow(dpy, bsw->w);
    427 			}
    428 			else {
    429 				XMapWindow(dpy, msw->w);
    430 			}
    431 
    432 			/* Setup background on map-state window */
    433 			/* XXX X-ref CTAG_BGDRAW in CreateWorkSpaceManager() */
    434 			if(useBackgroundInfo) {
    435 				if(ws->image == NULL || Scr->NoImagesInWorkSpaceManager) {
    436 					XSetWindowBackground(dpy, msw->w, ws->backcp.back);
    437 				}
    438 				else {
    439 					XSetWindowBackgroundPixmap(dpy, msw->w, ws->image->pixmap);
    440 				}
    441 			}
    442 			else {
    443 				if(Scr->workSpaceMgr.defImage == NULL || Scr->NoImagesInWorkSpaceManager) {
    444 					XSetWindowBackground(dpy, msw->w, Scr->workSpaceMgr.defColors.back);
    445 				}
    446 				else {
    447 					XSetWindowBackgroundPixmap(dpy, msw->w, Scr->workSpaceMgr.defImage->pixmap);
    448 				}
    449 			}
    450 
    451 			/*
    452 			 * Clear out button subwin; PaintWorkSpaceManager() fills it
    453 			 * in.  Is this really necessary?
    454 			 */
    455 			XClearWindow(dpy, bsw->w);
    456 		}
    457 	}
    458 
    459 
    460 	/* Set WM properties */
    461 	{
    462 		XSizeHints sizehints;
    463 		XWMHints   wmhints;
    464 		const int lines   = Scr->workSpaceMgr.lines;
    465 		const int columns = Scr->workSpaceMgr.columns;
    466 		const char *name      = Scr->workSpaceMgr.name;
    467 		const char *icon_name = Scr->workSpaceMgr.icon_name;
    468 
    469 		sizehints.flags       = USPosition | PBaseSize | PMinSize | PResizeInc
    470 		                        | PWinGravity;
    471 		sizehints.x           = x;
    472 		sizehints.y           = y;
    473 		sizehints.base_width  = columns * hspace;
    474 		sizehints.base_height = lines   * vspace;
    475 		sizehints.width_inc   = columns;
    476 		sizehints.height_inc  = lines;
    477 		sizehints.min_width   = columns  * (hspace + 2);
    478 		sizehints.min_height  = lines    * (vspace + 2);
    479 		sizehints.win_gravity = gravity;
    480 
    481 		wmhints.flags         = InputHint | StateHint;
    482 		wmhints.input         = True;
    483 		wmhints.initial_state = NormalState;
    484 
    485 		XmbSetWMProperties(dpy, vs->wsw->w, name, icon_name, NULL, 0,
    486 		                   &sizehints, &wmhints, NULL);
    487 	}
    488 
    489 
    490 	/* Create our TwmWindow wrapping around it */
    491 	tmp_win = AddWindow(vs->wsw->w, AWT_WORKSPACE_MANAGER,
    492 	                    Scr->iconmgr, vs);
    493 	if(! tmp_win) {
    494 		fprintf(stderr, "cannot create workspace manager window, exiting...\n");
    495 		exit(1);
    496 	}
    497 	tmp_win->occupation = fullOccupation;
    498 	tmp_win->attr.width = width;
    499 	tmp_win->attr.height = height;
    500 	vs->wsw->twm_win = tmp_win;
    501 
    502 
    503 	/* Do the figuring to size and internal-layout it */
    504 	ResizeWorkSpaceManager(vs, tmp_win);
    505 
    506 
    507 	/* Setup cursor/gravity and listen for events */
    508 	{
    509 		XWindowAttributes wattr;
    510 		XSetWindowAttributes attr;
    511 		unsigned long attrmask;
    512 
    513 		attr.cursor = Scr->ButtonCursor;
    514 		attr.win_gravity = gravity;
    515 		attrmask = CWCursor | CWWinGravity;
    516 		XChangeWindowAttributes(dpy, vs->wsw->w, attrmask, &attr);
    517 
    518 		XGetWindowAttributes(dpy, vs->wsw->w, &wattr);
    519 		attrmask = wattr.your_event_mask | KeyPressMask | KeyReleaseMask
    520 		           | ExposureMask;
    521 		XSelectInput(dpy, vs->wsw->w, attrmask);
    522 	}
    523 
    524 
    525 	/*
    526 	 * Mark the buttons as listening to click and exposure events, and
    527 	 * stash away some pointers in contexts.  We stash the overall WSM
    528 	 * window in TwmContext, which means that when an event looks up the
    529 	 * window, it finds the WSM rather than the subwindow, and then falls
    530 	 * into the WMgrHandle*Event()'s, which then dig down into the event
    531 	 * to find where it happened in there.
    532 	 *
    533 	 * The map window doesn't listen to expose events; it's just empty
    534 	 * and background colored.  The individual subwindows in the map
    535 	 * listen for exposes for drawing themselves.
    536 	 *
    537 	 * Dragging windows around to move or re-occupy in the map window
    538 	 * does rely on motion events, but we don't listen for them here.
    539 	 * That happens in WMgrHandleButtonEvent() after getting the initial
    540 	 * click.  It changes the listen and runs through the action
    541 	 * internally; those motions never run through our main event loop.
    542 	 */
    543 	for(WorkSpace *ws = Scr->workSpaceMgr.workSpaceList; ws != NULL;
    544 	                ws = ws->next) {
    545 		Window buttonw = vs->wsw->bswl[ws->number]->w;
    546 		Window mapsubw = vs->wsw->mswl[ws->number]->w;
    547 
    548 		XSelectInput(dpy, buttonw, ButtonPressMask | ButtonReleaseMask
    549 		             | ExposureMask);
    550 		XSaveContext(dpy, buttonw, TwmContext, (XPointer) tmp_win);
    551 		XSaveContext(dpy, buttonw, ScreenContext, (XPointer) Scr);
    552 
    553 		XSelectInput(dpy, mapsubw, ButtonPressMask | ButtonReleaseMask);
    554 		XSaveContext(dpy, mapsubw, TwmContext, (XPointer) tmp_win);
    555 		XSaveContext(dpy, mapsubw, ScreenContext, (XPointer) Scr);
    556 	}
    557 
    558 
    559 	/* Set WM_STATE prop */
    560 	SetMapStateProp(tmp_win, WithdrawnState);
    561 
    562 
    563 	/* Setup root window if necessary */
    564 	/* XXX X-ref CTAG_BGDRAW in CreateWorkSpaceManager() */
    565 	if(useBackgroundInfo && ! Scr->DontPaintRootWindow) {
    566 		WorkSpace *ws = Scr->workSpaceMgr.workSpaceList;
    567 		if(ws->image == NULL) {
    568 			XSetWindowBackground(dpy, Scr->Root, ws->backcp.back);
    569 		}
    570 		else {
    571 			XSetWindowBackgroundPixmap(dpy, Scr->Root, ws->image->pixmap);
    572 		}
    573 		XClearWindow(dpy, Scr->Root);
    574 	}
    575 
    576 
    577 	/*
    578 	 * Don't have to PaintWorkSpaceManager(vs) here, because
    579 	 * ResizeWorkSpaceManager() already called it for us.
    580 	 */
    581 }
    582 
    583 
    584 /*
    585  * Size and layout a WSM.  Mostly an internal bit in the process of
    586  * setting it up.
    587  */
    588 static void
    589 ResizeWorkSpaceManager(VirtualScreen *vs, TwmWindow *win)
    590 {
    591 	WorkSpace *ws;
    592 	int       i, j;
    593 	/* Lots of shortcuts to ease reading */
    594 	const int neww    = win->attr.width;
    595 	const int newh    = win->attr.height;
    596 	const int hspace  = Scr->workSpaceMgr.hspace;
    597 	const int vspace  = Scr->workSpaceMgr.vspace;
    598 	const int lines   = Scr->workSpaceMgr.lines;
    599 	const int columns = Scr->workSpaceMgr.columns;
    600 	const int bwidth  = (neww - (columns * hspace)) / columns;
    601 	const int bheight = (newh - (lines   * vspace)) / lines;
    602 	const int wwidth  = neww / columns;
    603 	const int wheight = newh / lines;
    604 	const float wf    = (float)(wwidth  - 2) / (float) vs->w;
    605 	const float hf    = (float)(wheight - 2) / (float) vs->h;
    606 
    607 	/* If nothing's changed since our last run, there's nothing to change */
    608 	if(neww == vs->wsw->width && newh == vs->wsw->height) {
    609 		return;
    610 	}
    611 
    612 	/* Set the new overall vals */
    613 	vs->wsw->bwidth  = bwidth;
    614 	vs->wsw->bheight = bheight;
    615 	vs->wsw->width   = neww;
    616 	vs->wsw->height  = newh;
    617 	vs->wsw->wwidth  = wwidth;
    618 	vs->wsw->wheight = wheight;
    619 
    620 	/* Iterate over the WS's */
    621 	i = 0;
    622 	j = 0;
    623 	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
    624 		MapSubwindow    *msw = vs->wsw->mswl[ws->number];
    625 		ButtonSubwindow *bsw = vs->wsw->bswl[ws->number];
    626 
    627 		/* Move button window to its place in the grid and size appropriately */
    628 		XMoveResizeWindow(dpy, bsw->w,
    629 		                  i * (bwidth  + hspace) + (hspace / 2),
    630 		                  j * (bheight + vspace) + (vspace / 2),
    631 		                  bwidth, bheight);
    632 
    633 		/* Move the map window as well */
    634 		msw->x = i * wwidth;
    635 		msw->y = j * wheight;
    636 		XMoveResizeWindow(dpy, msw->w, msw->x, msw->y, wwidth - 2, wheight - 2);
    637 
    638 		/*
    639 		 * Redo interior sizing and placement of all the windows in the
    640 		 * WS in the map window
    641 		 */
    642 		for(WinList *wl = msw->wl; wl != NULL; wl = wl->next) {
    643 			TwmWindow *tmp_win = wl->twm_win;
    644 			wl->x      = (int)(tmp_win->frame_x * wf);
    645 			wl->y      = (int)(tmp_win->frame_y * hf);
    646 			wl->width  = (unsigned int)((tmp_win->frame_width  * wf) + 0.5);
    647 			wl->height = (unsigned int)((tmp_win->frame_height * hf) + 0.5);
    648 			XMoveResizeWindow(dpy, wl->w, wl->x, wl->y, wl->width, wl->height);
    649 		}
    650 
    651 		/* And around to the next WS */
    652 		i++;
    653 		if(i == columns) {
    654 			i = 0;
    655 			j++;
    656 		};
    657 	}
    658 
    659 
    660 	/* Draw it */
    661 	PaintWorkSpaceManager(vs);
    662 }
    663 
    664 
    665 /*
    666  * Draw up the button-state pieces of a WSM window.
    667  *
    668  * Note: this is currently stubbed out and does nothing.  Historically
    669  * it's been called during startup when the WSM window is put together,
    670  * and when the screen is unmasked.  However, the only apparent result is
    671  * that the border and buttons get drawn a little earlier; they already
    672  * get expose events that get picked up when we start the event loop.
    673  *
    674  * If we don't find any reason to reinstate it, we should remove this in
    675  * the future.
    676  */
    677 void
    678 PaintWorkSpaceManager(VirtualScreen *vs)
    679 {
    680 	WorkSpace *ws;
    681 
    682 	/* x-ref header comment */
    683 	return;
    684 
    685 	PaintWorkSpaceManagerBorder(vs);
    686 
    687 	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
    688 		Window buttonw = vs->wsw->bswl[ws->number]->w;
    689 		ButtonState bs = (ws == vs->wsw->currentwspc) ? on : off;
    690 
    691 		PaintWsButton(WSPCWINDOW, vs, buttonw, ws->label, ws->cp, bs);
    692 	}
    693 }
    694 
    695 
    696 /*
    697  * Border around the WSM
    698  */
    699 static void
    700 PaintWorkSpaceManagerBorder(VirtualScreen *vs)
    701 {
    702 	int width, height;
    703 
    704 	width  = vs->wsw->width;
    705 	height = vs->wsw->height;
    706 	Draw3DBorder(vs->wsw->w, 0, 0, width, height, 2, Scr->workSpaceMgr.cp, off,
    707 	             true, false);
    708 }
    709 
    710 
    711 /*
    712  * Draw a workspace manager window on expose.  X-ref comment on
    713  * PaintWorkSpaceManager().
    714  */
    715 void
    716 WMgrHandleExposeEvent(VirtualScreen *vs, XEvent *event)
    717 {
    718 	if(vs->wsw->state == WMS_buttons) {
    719 		Window buttonw;
    720 		WorkSpace *ws;
    721 
    722 		/* Find the button we're exposing */
    723 		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
    724 			buttonw = vs->wsw->bswl[ws->number]->w;
    725 			if(event->xexpose.window == buttonw) {
    726 				break;
    727 			}
    728 		}
    729 
    730 		/* If none, just paint the border.  Else paint the button. */
    731 		if(ws == NULL) {
    732 			PaintWorkSpaceManagerBorder(vs);
    733 		}
    734 		else {
    735 			ButtonState bs = (ws == vs->wsw->currentwspc) ? on : off;
    736 			PaintWsButton(WSPCWINDOW, vs, buttonw, ws->label, ws->cp, bs);
    737 		}
    738 	}
    739 	else {
    740 		WinList *wl;
    741 
    742 		/*
    743 		 * This is presumably exposing some individual window in the WS
    744 		 * subwindow; find it from the stashed context on the window, and
    745 		 * redraw it.
    746 		 */
    747 		if(XFindContext(dpy, event->xexpose.window, MapWListContext,
    748 		                (XPointer *) &wl) == XCNOENT) {
    749 			return;
    750 		}
    751 		if(wl && wl->twm_win && wl->twm_win->mapped) {
    752 			WMapRedrawName(vs, wl);
    753 		}
    754 	}
    755 }
    756 
    757 
    758 
    759 /*
    760  * Moving the WSM between button and map state
    761  */
    762 void
    763 WMgrToggleState(VirtualScreen *vs)
    764 {
    765 	if(vs->wsw->state == WMS_buttons) {
    766 		WMgrSetMapState(vs);
    767 	}
    768 	else {
    769 		WMgrSetButtonsState(vs);
    770 	}
    771 }
    772 
    773 void
    774 WMgrSetMapState(VirtualScreen *vs)
    775 {
    776 	WorkSpace *ws;
    777 
    778 	if(vs->wsw->state == WMS_map) {
    779 		return;
    780 	}
    781 	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
    782 		XUnmapWindow(dpy, vs->wsw->bswl [ws->number]->w);
    783 		XMapWindow(dpy, vs->wsw->mswl [ws->number]->w);
    784 	}
    785 	vs->wsw->state = WMS_map;
    786 	MaybeAnimate = true;
    787 }
    788 
    789 void
    790 WMgrSetButtonsState(VirtualScreen *vs)
    791 {
    792 	WorkSpace *ws;
    793 
    794 	if(vs->wsw->state == WMS_buttons) {
    795 		return;
    796 	}
    797 	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
    798 		XUnmapWindow(dpy, vs->wsw->mswl [ws->number]->w);
    799 		XMapWindow(dpy, vs->wsw->bswl [ws->number]->w);
    800 	}
    801 	vs->wsw->state = WMS_buttons;
    802 }
    803 
    804 
    805 
    806 
    807 /*
    808  ****************************************************************
    809  *
    810  * Handlers for mouse/key actions in the WSM
    811  *
    812  ****************************************************************
    813  */
    814 
    815 /*
    816  * Key press/release events in the WSM.  A major use (and only for
    817  * release) is the Ctrl-key switching between map and button state.  The
    818  * other use is on-the-fly renaming of workspaces by typing in the
    819  * button-state WSM.
    820  */
    821 void
    822 WMgrHandleKeyReleaseEvent(VirtualScreen *vs, XEvent *event)
    823 {
    824 	KeySym keysym;
    825 
    826 	keysym = XLookupKeysym((XKeyEvent *) event, 0);
    827 	if(! keysym) {
    828 		return;
    829 	}
    830 	if(keysym == XK_Control_L || keysym == XK_Control_R) {
    831 		/* DontToggleWorkSpaceManagerState added 20040607 by dl*/
    832 		if(!Scr->DontToggleWorkspaceManagerState) {
    833 			WMgrToggleState(vs);
    834 		}
    835 		return;
    836 	}
    837 }
    838 
    839 void
    840 WMgrHandleKeyPressEvent(VirtualScreen *vs, XEvent *event)
    841 {
    842 	WorkSpace *ws;
    843 
    844 	/* Check if we're using Control to toggle the state */
    845 	{
    846 		KeySym keysym = XLookupKeysym((XKeyEvent *) event, 0);
    847 		if(! keysym) {
    848 			return;
    849 		}
    850 		if(keysym == XK_Control_L || keysym == XK_Control_R) {
    851 			/* DontToggleWorkSpaceManagerState added 20040607 by dl*/
    852 			if(!Scr->DontToggleWorkspaceManagerState) {
    853 				WMgrToggleState(vs);
    854 			}
    855 			return;
    856 		}
    857 	}
    858 
    859 	/* Otherwise, key presses do nothing in map state */
    860 	if(vs->wsw->state == WMS_map) {
    861 		return;
    862 	}
    863 
    864 	/*
    865 	 * If we're typing in a button-state WSM, and the mouse is on one of
    866 	 * the buttons, that means we're changing the name, so do that dance.
    867 	 */
    868 	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
    869 		if(vs->wsw->bswl[ws->number]->w == event->xkey.subwindow) {
    870 			break;
    871 		}
    872 	}
    873 	if(ws == NULL) {
    874 		/* Not on a button, nothing to do */
    875 		return;
    876 	}
    877 
    878 
    879 	/*
    880 	 * Edit the label.
    881 	 */
    882 	{
    883 		int    nkeys;
    884 		char   keys[16];
    885 		size_t nlen;
    886 		char   *newname;
    887 
    888 		/* Look up what keystrokes are queued.  Arbitrary buf size */
    889 		nkeys = XLookupString(&(event->xkey), keys, 16, NULL, NULL);
    890 
    891 		/* Label length can't grow to more than cur+nkeys */
    892 		nlen = strlen(ws->label);
    893 		newname = malloc(nlen + nkeys + 1);
    894 		strcpy(newname, ws->label);
    895 
    896 		/* Iterate over the passed keystrokes */
    897 		for(int i = 0 ; i < nkeys ; i++) {
    898 			unsigned char k = keys[i];
    899 
    900 			if(isprint(k)) {
    901 				/* Printable chars append to the string */
    902 				newname[nlen++] = k;
    903 			}
    904 			else if((k == 127) || (k == 8)) {
    905 				/*
    906 				 * DEL or BS back up a char.
    907 				 *
    908 				 * XXX Would it be more generally correct to do this via
    909 				 * keysyms, in the face of changed keyboard mappings or
    910 				 * significantly differing locales?
    911 				 */
    912 				if(nlen != 0) {
    913 					nlen--;
    914 				}
    915 			}
    916 			else {
    917 				/* Any other char stops the process dead */
    918 				break;
    919 			}
    920 		}
    921 		/* Now ends where it ends */
    922 		newname[nlen] = '\0';
    923 
    924 		/* Swap it in */
    925 		free(ws->label);
    926 		ws->label = newname;
    927 	}
    928 
    929 
    930 	/* Redraw the button with the new label */
    931 	{
    932 		ButtonState bs = (ws == vs->wsw->currentwspc) ? on : off;
    933 
    934 		PaintWsButton(WSPCWINDOW, vs, vs->wsw->bswl[ws->number]->w, ws->label,
    935 		              ws->cp, bs);
    936 	}
    937 }
    938 
    939 
    940 /*
    941  * Mouse clicking in WSM.  Gets called on button press (not release).  In
    942  * the simple case, that's just switching workspaces.  In the more
    943  * complex, it's changing window occupation in various different ways, or
    944  * even moving windows.  When that's happening, it internally hijacks
    945  * button/motion/exposure events and implements them for the moving, with
    946  * magic escapes if it gets them for something else.  Ew.
    947  */
    948 void
    949 WMgrHandleButtonEvent(VirtualScreen *vs, XEvent *event)
    950 {
    951 	WorkSpace    *oldws, *newws;
    952 	WinList      *wl;
    953 	TwmWindow    *win;
    954 	unsigned int W0, H0;
    955 	XEvent       lastev;
    956 	Window       w = 0;
    957 	Position     newX = 0, newY = 0, winX = 0, winY = 0;
    958 	bool         alreadyvivible, realmovemode;
    959 	const WorkSpaceWindow *mw = vs->wsw;
    960 
    961 	/* Shortcuts into the event */
    962 	const Window parent = event->xbutton.window;    // Map/button for WS
    963 	const Window sw     = event->xbutton.subwindow; // Map mini-win
    964 	const Time etime    = event->xbutton.time;
    965 	const unsigned int button   = event->xbutton.button;
    966 	const unsigned int modifier = event->xbutton.state;
    967 
    968 
    969 	/* If we're in button state, we're just clicking to change */
    970 	if(vs->wsw->state == WMS_buttons) {
    971 		WorkSpace *ws;
    972 		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
    973 			if(vs->wsw->bswl[ws->number]->w == parent) {
    974 				GotoWorkSpace(vs, ws);
    975 				break;
    976 			}
    977 		}
    978 		return;
    979 	}
    980 
    981 	/*
    982 	 * If we get this far, we're in map state, where things are more
    983 	 * complicated.  A simple click/release means change here too, but
    984 	 * there's also the possibility of dragging a subwindow around to
    985 	 * change its window's occupation.
    986 	 */
    987 
    988 	/* Find what workspace we're clicking in */
    989 	for(oldws = Scr->workSpaceMgr.workSpaceList ; oldws != NULL ;
    990 	                oldws = oldws->next) {
    991 		if(vs->wsw->mswl[oldws->number]->w == parent) {
    992 			break;
    993 		}
    994 	}
    995 	if(oldws == NULL) {
    996 		/* None?  We're done here. */
    997 		return;
    998 	}
    999 
   1000 	/*
   1001 	 * If clicked in the workspace but outside a window, we can only be
   1002 	 * switching workspaces.  So just do that, and we're done.
   1003 	 */
   1004 	if(sw == (Window) 0) {
   1005 		GotoWorkSpace(vs, oldws);
   1006 		return;
   1007 	}
   1008 
   1009 	/* Use the context to find the winlist entry for this window */
   1010 	if(XFindContext(dpy, sw, MapWListContext, (XPointer *) &wl) == XCNOENT) {
   1011 		return;
   1012 	}
   1013 	win = wl->twm_win;
   1014 
   1015 	/*
   1016 	 * Sometimes we skip transients, so do so.  XXX Should this
   1017 	 * GotoWorkSpace()?
   1018 	 */
   1019 	if((! Scr->TransientHasOccupation) && win->istransient) {
   1020 		return;
   1021 	}
   1022 
   1023 	/*
   1024 	 * Are we trying to actually move the window, by moving its avatar in
   1025 	 * the WSM?  If ReallyMoveInWorkspaceManager is set, we're moving on
   1026 	 * click, but not in shift-click.  If it's not, it's the reverse.
   1027 	 *
   1028 	 * XXX This interacts really oddly and badly when you're also moving
   1029 	 * the window from WS to WS.
   1030 	 */
   1031 	realmovemode = false;
   1032 	if(Scr->ReallyMoveInWorkspaceManager) {
   1033 		if(!(modifier & ShiftMask)) {
   1034 			realmovemode = true;
   1035 		}
   1036 	}
   1037 	else if(modifier & ShiftMask) {
   1038 		realmovemode = true;
   1039 	}
   1040 
   1041 	/*
   1042 	 * Frob screen-wide OpaqueMove as necessary for this window's
   1043 	 * details.
   1044 	 * XXX Really?
   1045 	 */
   1046 	if(win->OpaqueMove) {
   1047 		if(Scr->OpaqueMoveThreshold >= 200) {
   1048 			Scr->OpaqueMove = true;
   1049 		}
   1050 		else {
   1051 			const unsigned long winsz = win->frame_width * win->frame_height;
   1052 			const unsigned long scrsz = vs->w * vs->h;
   1053 			const float sf = Scr->OpaqueMoveThreshold / 100.0;
   1054 			if(winsz > (scrsz * sf)) {
   1055 				Scr->OpaqueMove = false;
   1056 			}
   1057 			else {
   1058 				Scr->OpaqueMove = true;
   1059 			}
   1060 		}
   1061 	}
   1062 	else {
   1063 		Scr->OpaqueMove = false;
   1064 	}
   1065 
   1066 	/*
   1067 	 * Buttons inside the workspace manager, when clicking on the
   1068 	 * representation of a window:
   1069 	 * 1: drag window to a different workspace
   1070 	 * 2: drag a copy of the window to a different workspace
   1071 	 *    (show it in both workspaces)
   1072 	 * 3: remove the window from this workspace (if it isn't the last).
   1073 	 *
   1074 	 * XXX If you move between workspaces while also doing realmovemode,
   1075 	 * really messed up things happen.
   1076 	 */
   1077 	switch(button) {
   1078 		case 1 : {
   1079 			/*
   1080 			 * Moving from one to another; get rid of the old location,
   1081 			 * then fall through to the "duplicating" case below.
   1082 			 */
   1083 			XUnmapWindow(dpy, sw);
   1084 			/* FALLTHRU */
   1085 		}
   1086 
   1087 		case 2 : {
   1088 			/*
   1089 			 * Duplicating window to another WS.  We create a copy of the
   1090 			 * window, and start moving that.  The 'moving' case above
   1091 			 * falls through to us after unmapping that old window,
   1092 			 * leaving just our copy, which is good enough.  This is
   1093 			 * really just setting up the visual stuff for the move; the
   1094 			 * actual occupation changes etc. come at the end of the
   1095 			 * motion, much lower down.
   1096 			 */
   1097 			int X0, Y0, X1, Y1;
   1098 			unsigned int bw;
   1099 			XSetWindowAttributes attrs;
   1100 			Window junkW;
   1101 
   1102 			/* [XYWH]0 = size/location of the avatar in the map */
   1103 			XGetGeometry(dpy, sw, &junkW, &X0, &Y0, &W0, &H0, &bw, &JunkDepth);
   1104 
   1105 			/*
   1106 			 * [XY]0 are the coordinates of the avatar subwindow inside
   1107 			 * the individual workspace window in the map.  Turn those
   1108 			 * into [XY]1 as the coordinates of it relative to the whole
   1109 			 * WSM window.
   1110 			 */
   1111 			XTranslateCoordinates(dpy, vs->wsw->mswl[oldws->number]->w,
   1112 			                      mw->w, X0, Y0, &X1, &Y1, &junkW);
   1113 
   1114 			/*
   1115 			 * Create the copy window to drag around, as a child of the
   1116 			 * whole WSM (so we can move it between workspaces), and map
   1117 			 * it.
   1118 			 */
   1119 			attrs.event_mask       = ExposureMask;
   1120 			attrs.background_pixel = wl->cp.back;
   1121 			attrs.border_pixel     = wl->cp.back;
   1122 			w = XCreateWindow(dpy, mw->w, X1, Y1, W0, H0, bw,
   1123 			                  CopyFromParent, CopyFromParent, CopyFromParent,
   1124 			                  CWEventMask | CWBackPixel | CWBorderPixel,
   1125 			                  &attrs);
   1126 			XMapRaised(dpy, w);
   1127 
   1128 			/* Do our dance on it to draw the name/color/etc */
   1129 			WMapRedrawWindow(w, W0, H0, wl->cp, wl->twm_win->icon_name);
   1130 
   1131 			/*
   1132 			 * If we're moving the real window and
   1133 			 * AlwaysShowWindowWhenMovingFromWorkspaceManager is set in
   1134 			 * config, we need to be sure the real window is visible
   1135 			 * while we move it.
   1136 			 */
   1137 			if(realmovemode && Scr->ShowWinWhenMovingInWmgr) {
   1138 				if(Scr->OpaqueMove) {
   1139 					DisplayWin(vs, win);
   1140 				}
   1141 				else {
   1142 					MoveOutline(Scr->Root,
   1143 					            win->frame_x - win->frame_bw,
   1144 					            win->frame_y - win->frame_bw,
   1145 					            win->frame_width  + 2 * win->frame_bw,
   1146 					            win->frame_height + 2 * win->frame_bw,
   1147 					            win->frame_bw,
   1148 					            win->title_height + win->frame_bw3D);
   1149 				}
   1150 			}
   1151 
   1152 			/* Move onward */
   1153 			break;
   1154 		}
   1155 
   1156 		/*
   1157 		 * For the button 3 or anything else case, there's no dragging or
   1158 		 * anything, so they do their thing and just immediately return.
   1159 		 */
   1160 		case 3 : {
   1161 			int newocc = win->occupation & ~(1 << oldws->number);
   1162 			if(newocc != 0) {
   1163 				ChangeOccupation(win, newocc);
   1164 			}
   1165 			return;
   1166 		}
   1167 
   1168 		default :
   1169 			return;
   1170 	}
   1171 
   1172 	/*
   1173 	 * Keep dragging the representation of the window
   1174 	 *
   1175 	 * XXX Look back at this and see if we can move it to an inner
   1176 	 * function for readability...
   1177 	 */
   1178 	{
   1179 		const float wf = (float)(mw->wwidth  - 1) / (float) vs->w;
   1180 		const float hf = (float)(mw->wheight - 1) / (float) vs->h;
   1181 		int XW, YW;
   1182 		bool cont;
   1183 		Window junkW;
   1184 
   1185 		/* Figure where in the subwindow the click was, and stash in XW/YW */
   1186 		XTranslateCoordinates(dpy, Scr->Root, sw, event->xbutton.x_root,
   1187 		                      event->xbutton.y_root,
   1188 		                      &XW, &YW, &junkW);
   1189 
   1190 		/*
   1191 		 * Grab the pointer, lock it into the WSM, and get the events
   1192 		 * related to buttons and movement.  We don't need
   1193 		 * PointerMotionMask, since that would only happen after buttons
   1194 		 * are released, and we'll be done by then.
   1195 		 */
   1196 		XGrabPointer(dpy, mw->w, False,
   1197 		             ButtonPressMask | ButtonMotionMask | ButtonReleaseMask,
   1198 		             GrabModeAsync, GrabModeAsync, mw->w, Scr->MoveCursor,
   1199 		             CurrentTime);
   1200 
   1201 		/* Start handling events until we're done */
   1202 		alreadyvivible = false;
   1203 		cont = true;
   1204 		while(cont) {
   1205 			XEvent ev;
   1206 
   1207 			/* Grab the next event and handle */
   1208 			XMaskEvent(dpy, ButtonPressMask | ButtonMotionMask |
   1209 			           ButtonReleaseMask | ExposureMask, &ev);
   1210 			switch(ev.xany.type) {
   1211 				case Expose : {
   1212 					/* Something got exposed */
   1213 					if(ev.xexpose.window == w) {
   1214 						/*
   1215 						 * The win we're working with?  We know how to do
   1216 						 * that.
   1217 						 */
   1218 						WMapRedrawWindow(w, W0, H0, wl->cp,
   1219 						                 wl->twm_win->icon_name);
   1220 						break;
   1221 					}
   1222 
   1223 					/* Else, delegate to our global dispatcher */
   1224 					Event = ev;
   1225 					DispatchEvent();
   1226 					break;
   1227 				}
   1228 
   1229 				case ButtonPress :
   1230 				case ButtonRelease : {
   1231 					/*
   1232 					 * Events for buttons other than the one whose press
   1233 					 * started this activity are totally ignored.
   1234 					 */
   1235 					if(ev.xbutton.button != button) {
   1236 						break;
   1237 					}
   1238 
   1239 					/*
   1240 					 * Otherwise, this is a press/release of the button
   1241 					 * that started things.  Though I'm not sure how it
   1242 					 * could be a press, since it was already pressed.
   1243 					 * Regardless, this is our exit condition.  Fall
   1244 					 * through into the Motion case to handle any
   1245 					 * remaining movement.
   1246 					 */
   1247 					lastev = ev;
   1248 					cont = false;
   1249 					newX = ev.xbutton.x;
   1250 					newY = ev.xbutton.y;
   1251 				}
   1252 
   1253 				/* Everything remaining is motion handling */
   1254 				case MotionNotify : {
   1255 					/* If we fell through from above, no new movement */
   1256 					if(cont) {
   1257 						newX = ev.xmotion.x;
   1258 						newY = ev.xmotion.y;
   1259 					}
   1260 
   1261 					/* Lots to do if we're moving the window for real */
   1262 					if(realmovemode) {
   1263 						int XSW, YSW;
   1264 						WorkSpace *cws;
   1265 						MapSubwindow *msw;
   1266 
   1267 						/* Did the move start in the currently visible WS? */
   1268 						bool startincurrent = (oldws == vs->wsw->currentwspc);
   1269 
   1270 						/* Find the workspace we wound up in */
   1271 						for(cws = Scr->workSpaceMgr.workSpaceList ;
   1272 						                cws != NULL ;
   1273 						                cws = cws->next) {
   1274 							msw = vs->wsw->mswl [cws->number];
   1275 							if((newX >= msw->x)
   1276 							                && (newX <  msw->x + mw->wwidth)
   1277 							                && (newY >= msw->y)
   1278 							                && (newY <  msw->y + mw->wheight)) {
   1279 								break;
   1280 							}
   1281 						}
   1282 						if(!cws) {
   1283 							/* None?  Ignore. */
   1284 							break;
   1285 						}
   1286 
   1287 						/*
   1288 						 * Mouse is wherever it started inside the
   1289 						 * subwindow, so figure the X/Y of the top left
   1290 						 * of the subwindow from there.  (coords relative
   1291 						 * to the whole WSM because of grab)
   1292 						 */
   1293 						winX = newX - XW;
   1294 						winY = newY - YW;
   1295 
   1296 						/* XXX redundant w/previous? */
   1297 						msw = vs->wsw->mswl[cws->number];
   1298 
   1299 						/*
   1300 						 * Figure where those coords are relative to the
   1301 						 * per-workspace window.
   1302 						 */
   1303 						XTranslateCoordinates(dpy, mw->w, msw->w,
   1304 						                      winX, winY, &XSW, &YSW, &junkW);
   1305 
   1306 						/*
   1307 						 * Now rework the win[XY] to be the coordinates
   1308 						 * the window would be in the whole screen, based
   1309 						 * on the [XY]SW inside the map, using our scale
   1310 						 * factors.
   1311 						 */
   1312 						winX = (int)(XSW / wf);
   1313 						winY = (int)(YSW / hf);
   1314 
   1315 						/*
   1316 						 * Clip to the screen if DontMoveOff is set.
   1317 						 *
   1318 						 * XXX Can we use the Constrain*() functions for
   1319 						 * this, instead of implementing icky magic
   1320 						 * internally?
   1321 						 */
   1322 						if(Scr->DontMoveOff) {
   1323 							const int width = win->frame_width;
   1324 							const int height = win->frame_height;
   1325 
   1326 							if((winX < Scr->BorderLeft) && ((Scr->MoveOffResistance < 0) ||
   1327 							                                (winX > Scr->BorderLeft - Scr->MoveOffResistance))) {
   1328 								winX = Scr->BorderLeft;
   1329 								newX = msw->x + XW + Scr->BorderLeft * mw->wwidth / vs->w;
   1330 							}
   1331 							if(((winX + width) > vs->w - Scr->BorderRight) &&
   1332 							                ((Scr->MoveOffResistance < 0) ||
   1333 							                 ((winX + width) < vs->w - Scr->BorderRight + Scr->MoveOffResistance))) {
   1334 								winX = vs->w - Scr->BorderRight - width;
   1335 								newX = msw->x + mw->wwidth *
   1336 								       (1 - Scr->BorderRight / (double) vs->w) - wl->width + XW - 2;
   1337 							}
   1338 							if((winY < Scr->BorderTop) && ((Scr->MoveOffResistance < 0) ||
   1339 							                               (winY > Scr->BorderTop - Scr->MoveOffResistance))) {
   1340 								winY = Scr->BorderTop;
   1341 								newY = msw->y + YW + Scr->BorderTop * mw->height / vs->h;
   1342 							}
   1343 							if(((winY + height) > vs->h - Scr->BorderBottom) &&
   1344 							                ((Scr->MoveOffResistance < 0) ||
   1345 							                 ((winY + height) < vs->h - Scr->BorderBottom + Scr->MoveOffResistance))) {
   1346 								winY = vs->h - Scr->BorderBottom - height;
   1347 								newY = msw->y + mw->wheight
   1348 								       * (1 - Scr->BorderBottom / (double) vs->h)
   1349 								       * - wl->height + YW - 2;
   1350 							}
   1351 						}
   1352 
   1353 
   1354 						/* Setup the avatar for the new position of win */
   1355 						WMapSetupWindow(win, winX, winY, -1, -1);
   1356 
   1357 						/*
   1358 						 * If SWWMIW, we already made sure above the
   1359 						 * window is always visible on the screen.  So we
   1360 						 * don't need to do any of the following steps to
   1361 						 * maybe show it or maybe un-show it, since we
   1362 						 * know it's already always shown.
   1363 						 */
   1364 						if(Scr->ShowWinWhenMovingInWmgr) {
   1365 							goto movewin;
   1366 						}
   1367 
   1368 						/*
   1369 						 * If we wound up in the same workspace as is
   1370 						 * being displayed on the screen, we need to make
   1371 						 * sure that window is showing up on the screen
   1372 						 * as a "about to be occupied here if you release
   1373 						 * the button" indication.
   1374 						 */
   1375 						if(cws == vs->wsw->currentwspc) {
   1376 							if(alreadyvivible) {
   1377 								/* Unless it's already there */
   1378 								goto movewin;
   1379 							}
   1380 							if(Scr->OpaqueMove) {
   1381 								XMoveWindow(dpy, win->frame, winX, winY);
   1382 								DisplayWin(vs, win);
   1383 							}
   1384 							else {
   1385 								MoveOutline(Scr->Root,
   1386 								            winX - win->frame_bw,
   1387 								            winY - win->frame_bw,
   1388 								            win->frame_width  + 2 * win->frame_bw,
   1389 								            win->frame_height + 2 * win->frame_bw,
   1390 								            win->frame_bw,
   1391 								            win->title_height + win->frame_bw3D);
   1392 							}
   1393 							alreadyvivible = true;
   1394 
   1395 							/*
   1396 							 * We already moved it, skip past the other
   1397 							 * code trying to move it in other cases.
   1398 							 */
   1399 							goto move;
   1400 						}
   1401 
   1402 						/*
   1403 						 * If it's for whatever reason not being shown on
   1404 						 * the screen, then we don't need to move it; hop
   1405 						 * straight to moving the avatar.
   1406 						 */
   1407 						if(!alreadyvivible) {
   1408 							goto move;
   1409 						}
   1410 
   1411 						/*
   1412 						 * So it _is_ being shown.  If it's not supposed
   1413 						 * to be here, hide it away (don't need to worry
   1414 						 * about AlwaysShow, it would have skipped past
   1415 						 * us from ab0ve).  Also, if we started moving
   1416 						 * the window out of the visible workspace with
   1417 						 * button 1, it's gonna not be here if you
   1418 						 * release, so hide it away.
   1419 						 */
   1420 						if(!OCCUPY(win, vs->wsw->currentwspc) ||
   1421 						                (startincurrent && (button == 1))) {
   1422 							if(Scr->OpaqueMove) {
   1423 								Vanish(vs, win);
   1424 								XMoveWindow(dpy, win->frame, winX, winY);
   1425 							}
   1426 							else {
   1427 								MoveOutline(Scr->Root, 0, 0, 0, 0, 0, 0);
   1428 							}
   1429 							alreadyvivible = false;
   1430 							goto move;
   1431 						}
   1432 
   1433 movewin:
   1434 						/*
   1435 						 * However we get here, the real window is
   1436 						 * visible and needs to be moved.  So move it.
   1437 						 */
   1438 						if(Scr->OpaqueMove) {
   1439 							XMoveWindow(dpy, win->frame, winX, winY);
   1440 						}
   1441 						else {
   1442 							MoveOutline(Scr->Root,
   1443 							            winX - win->frame_bw,
   1444 							            winY - win->frame_bw,
   1445 							            win->frame_width  + 2 * win->frame_bw,
   1446 							            win->frame_height + 2 * win->frame_bw,
   1447 							            win->frame_bw,
   1448 							            win->title_height + win->frame_bw3D);
   1449 						}
   1450 					}
   1451 
   1452 move:
   1453 					/*
   1454 					 * Just move the subwindow in the map to the new
   1455 					 * location.
   1456 					 */
   1457 					XMoveWindow(dpy, w, newX - XW, newY - YW);
   1458 
   1459 					/* And we're done.  Next event! */
   1460 					break;
   1461 				}
   1462 			}
   1463 		}
   1464 	}
   1465 
   1466 	/*
   1467 	 * Finished with dragging (button released).
   1468 	 */
   1469 	if(realmovemode) {
   1470 		/* Moving the real window?  Move the real window. */
   1471 		if(Scr->ShowWinWhenMovingInWmgr || alreadyvivible) {
   1472 			if(Scr->OpaqueMove && !OCCUPY(win, vs->wsw->currentwspc)) {
   1473 				Vanish(vs, win);
   1474 			}
   1475 			if(!Scr->OpaqueMove) {
   1476 				MoveOutline(Scr->Root, 0, 0, 0, 0, 0, 0);
   1477 				WMapRedrawName(vs, wl);
   1478 			}
   1479 		}
   1480 		SetupWindow(win, winX, winY, win->frame_width, win->frame_height, -1);
   1481 	}
   1482 
   1483 	/*
   1484 	 * Last event that caused us to escape is other code's
   1485 	 * responsibility, put it back in the queue.
   1486 	 */
   1487 	lastev.xbutton.subwindow = (Window) 0;
   1488 	lastev.xbutton.window = parent;
   1489 	XPutBackEvent(dpy, &lastev);
   1490 
   1491 	/* End our grab */
   1492 	XUngrabPointer(dpy, CurrentTime);
   1493 
   1494 	/*
   1495 	 * If you wanted to change workspaces, and clicked _outside_ a
   1496 	 * window, it would have just switched way up near the top of the
   1497 	 * function.  But if you clicked _in_ a window [in the WSM map], it
   1498 	 * would have to go through the whole fun to find out whether you
   1499 	 * wanted to move/reoccupy the window, or were just wanting to change
   1500 	 * WSen.
   1501 	 *
   1502 	 * So if it's been <250ms (completely arbitrary and non-configgable,
   1503 	 * probably should be rethought) since you started, assume you just
   1504 	 * wanted to switch workspaces.  Don't do any occupation change, And
   1505 	 * just switch.  Also do some magic related to the map/button
   1506 	 * toggling.
   1507 	 *
   1508 	 * XXX This still leaves any window _moves_ done.  It seems like it
   1509 	 * should probably revert those too?
   1510 	 */
   1511 	if((lastev.xbutton.time - etime) < 250) {
   1512 		KeyCode control_L_code, control_R_code;
   1513 		char keys [32];
   1514 
   1515 		/* Re-show old miniwindow, destroy the temp, and warp to WS */
   1516 		XMapWindow(dpy, sw);
   1517 		XDestroyWindow(dpy, w);
   1518 		GotoWorkSpace(vs, oldws);
   1519 		if(!Scr->DontWarpCursorInWMap) {
   1520 			WarpToWindow(win, Scr->RaiseOnWarp);
   1521 		}
   1522 
   1523 		/*
   1524 		 * The control keys toggle between map and button state.  If we
   1525 		 * did a short move, and ctrl is being held at the end, flip the
   1526 		 * state.  This has several possible causes and effects.
   1527 		 *
   1528 		 * One is that _during_ a move, we don't do look through KeyPress
   1529 		 * events, so we wouldn't see it happen yet.  But after we're
   1530 		 * done and return back into the event loop, that press will come
   1531 		 * in and cause the state to change.  It may be weird to the user
   1532 		 * to see that happen, not when they hit ctrl, but _later_, after
   1533 		 * they release the mouse button.  The "best" solution may be to
   1534 		 * find that press in the event queue and empty it out, but a
   1535 		 * cheap solution is just to pre-flip it and then let the event
   1536 		 * code flip it back.  Flickery maybe, but easy.  Now, _should_ we be
   1537 		 * doing that?  I'm a little doubtful...
   1538 		 *
   1539 		 * A second is that if the WSM is "naturally" in button mode, and
   1540 		 * you temporarily ctrl-flip it into map mode and then click in a
   1541 		 * window.  This code will cause it to automatically flip back
   1542 		 * after you release the mouse if you haven't released ctrl yet.
   1543 		 * This is apparently needed because, unless you have
   1544 		 * DontWarpCursorInWMap set, the previous few lines of code would
   1545 		 * have shifted the cursor to the window you clicked, which means
   1546 		 * you don't get a chance to release it in the WSM and flip the
   1547 		 * state back.  It seems a reasonable assumption that the user
   1548 		 * wanted a temporary change of state just for the purposes of
   1549 		 * the change.
   1550 		 *
   1551 		 * (if you had released the ctrl before the mouse, the release
   1552 		 * event would already be queued up up on the WSM, so would flip
   1553 		 * it back after we return)
   1554 		 *
   1555 		 * XXX Should we be checking DontToggleWorkSpaceManagerState
   1556 		 * here?
   1557 		 */
   1558 		control_L_code = XKeysymToKeycode(dpy, XStringToKeysym("Control_L"));
   1559 		control_R_code = XKeysymToKeycode(dpy, XStringToKeysym("Control_R"));
   1560 		XQueryKeymap(dpy, keys);
   1561 		if((keys [control_L_code / 8] & ((char) 0x80 >> (control_L_code % 8))) ||
   1562 		                keys [control_R_code / 8] & ((char) 0x80 >> (control_R_code % 8))) {
   1563 			WMgrToggleState(vs);
   1564 		}
   1565 		return;
   1566 	}
   1567 
   1568 
   1569 	/* Find the workspace we finally found up in */
   1570 	for(newws = Scr->workSpaceMgr.workSpaceList ; newws != NULL ;
   1571 	                newws = newws->next) {
   1572 		MapSubwindow *msw = vs->wsw->mswl[newws->number];
   1573 		if((newX >= msw->x) && (newX < msw->x + mw->wwidth) &&
   1574 		                (newY >= msw->y) && (newY < msw->y + mw->wheight)) {
   1575 			break;
   1576 		}
   1577 	}
   1578 
   1579 	/* And finish off whatever we're supposed to be doing */
   1580 	switch(button) {
   1581 		case 1 : { /* moving to another workspace */
   1582 			int occupation;
   1583 
   1584 			/* If nothing to change, re-map avatar and we're done */
   1585 			if((newws == NULL) || (newws == oldws) ||
   1586 			                OCCUPY(wl->twm_win, newws)) {
   1587 				XMapWindow(dpy, sw);
   1588 				break;
   1589 			}
   1590 
   1591 			/* Out of the old, into the new */
   1592 			occupation = win->occupation;
   1593 			occupation |= (1 << newws->number);
   1594 			occupation &= ~(1 << oldws->number);
   1595 			ChangeOccupation(win, occupation);
   1596 
   1597 			/*
   1598 			 * Raise it to the top if it's in our current ws, and
   1599 			 * properly slot it into the WSM map stack if not.
   1600 			 */
   1601 			if(newws == vs->wsw->currentwspc) {
   1602 				OtpRaise(win, WinWin);
   1603 				WMapRaise(win);
   1604 			}
   1605 			else {
   1606 				WMapRestack(newws);
   1607 			}
   1608 
   1609 			break;
   1610 		}
   1611 
   1612 		case 2 : { /* putting in extra workspace */
   1613 			int occupation;
   1614 
   1615 			/* Nothing to do if it's going nowhere or places it already is */
   1616 			if((newws == NULL) || (newws == oldws) ||
   1617 			                OCCUPY(wl->twm_win, newws)) {
   1618 				break;
   1619 			}
   1620 
   1621 			/* Move */
   1622 			occupation = win->occupation | (1 << newws->number);
   1623 			ChangeOccupation(win, occupation);
   1624 
   1625 			/* Raise/stack */
   1626 			if(newws == vs->wsw->currentwspc) {
   1627 				OtpRaise(win, WinWin);
   1628 				WMapRaise(win);
   1629 			}
   1630 			else {
   1631 				WMapRestack(newws);
   1632 			}
   1633 			break;
   1634 		}
   1635 
   1636 		/*
   1637 		 * Should actually never hit this state; only 1/2 would have
   1638 		 * gotten this far into the code...
   1639 		 */
   1640 		default :
   1641 			return;
   1642 	}
   1643 
   1644 	/* Clean up our temporary moving-around-in the WSM window */
   1645 	XDestroyWindow(dpy, w);
   1646 }
   1647 
   1648 
   1649 
   1650 
   1651 /*
   1652  ****************************************************************
   1653  *
   1654  * Functions for doing things with subwindows in the WSM in map state
   1655  *
   1656  ****************************************************************
   1657  */
   1658 
   1659 /*
   1660  * Backend for mapping up windows in the WSM map.
   1661  */
   1662 static void
   1663 wmap_mapwin_backend(TwmWindow *win, bool handleraise)
   1664 {
   1665 	VirtualScreen *vs;
   1666 	WorkSpace *ws;
   1667 	WinList *wl;
   1668 
   1669 	for(vs = Scr->vScreenList; vs != NULL; vs = vs->next) {
   1670 		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
   1671 			for(wl = vs->wsw->mswl[ws->number]->wl; wl != NULL; wl = wl->next) {
   1672 				if(win == wl->twm_win) {
   1673 					/*
   1674 					 * When called via deiconify, we might have to do
   1675 					 * stuff related to auto-raising the window while we
   1676 					 * de-iconify.  When called via a map request, the
   1677 					 * window is always wherever it previously was in the
   1678 					 * stack.
   1679 					 */
   1680 					if(!handleraise || Scr->NoRaiseDeicon) {
   1681 						XMapWindow(dpy, wl->w);
   1682 					}
   1683 					else {
   1684 						XMapRaised(dpy, wl->w);
   1685 					}
   1686 					WMapRedrawName(vs, wl);
   1687 					break;
   1688 				}
   1689 			}
   1690 		}
   1691 	}
   1692 }
   1693 
   1694 /*
   1695  * Map up a window's subwindow in the map-mode WSM.  Happens as a result
   1696  * of getting (or faking) a Map request event.  Notably, _not_ in the
   1697  * process of de-iconifying a window; mostly as a result of _creating_
   1698  * windows, or when a client maps itself without a request from us.
   1699  *
   1700  * x-ref comment on WMapDeIconify() for some subtle distinctions between
   1701  * the two...
   1702  */
   1703 void
   1704 WMapMapWindow(TwmWindow *win)
   1705 {
   1706 	/* We don't do raise handling */
   1707 	wmap_mapwin_backend(win, false);
   1708 }
   1709 
   1710 
   1711 /*
   1712  * De-iconify a window in the WSM map.  The opposite of WMapIconify(),
   1713  * and different from WMapMapWindow() in complicated ways.  This function
   1714  * winds up getting called when a window is de-iconified via a ctwm
   1715  * function.
   1716  */
   1717 void
   1718 WMapDeIconify(TwmWindow *win)
   1719 {
   1720 	/* If it's not showing anywhere, nothing to do.  Is this possible? */
   1721 	if(!win->vs) {
   1722 		return;
   1723 	}
   1724 
   1725 	/* Loop and map, handling raises if necessary */
   1726 	wmap_mapwin_backend(win, true);
   1727 }
   1728 
   1729 
   1730 /*
   1731  * Hide away a window in the WSM map.  Happens when win is iconified;
   1732  * different from destruction.
   1733  */
   1734 void
   1735 WMapIconify(TwmWindow *win)
   1736 {
   1737 	VirtualScreen *vs;
   1738 	WorkSpace *ws;
   1739 	WinList *wl;
   1740 
   1741 	if(!win->vs) {
   1742 		return;
   1743 	}
   1744 
   1745 	for(vs = Scr->vScreenList; vs != NULL; vs = vs->next) {
   1746 		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
   1747 			for(wl = vs->wsw->mswl[ws->number]->wl; wl != NULL; wl = wl->next) {
   1748 				if(win == wl->twm_win) {
   1749 					XUnmapWindow(dpy, wl->w);
   1750 					break;
   1751 				}
   1752 			}
   1753 		}
   1754 	}
   1755 }
   1756 
   1757 
   1758 /*
   1759  * Position a window in the WSM.  Happens as a result of moving things.
   1760  */
   1761 void
   1762 WMapSetupWindow(TwmWindow *win, int x, int y, int w, int h)
   1763 {
   1764 	VirtualScreen *vs;
   1765 
   1766 	/* If it's an icon manager, or not on a vscreen, nothing to do */
   1767 	if(win->isiconmgr || !win->vs) {
   1768 		return;
   1769 	}
   1770 
   1771 	/* If it's a WSM, we may be resetting size/position, but that's it */
   1772 	if(win->iswspmgr) {
   1773 		if(w == -1) {
   1774 			return;
   1775 		}
   1776 		ResizeWorkSpaceManager(win->vs, win);
   1777 		return;
   1778 	}
   1779 
   1780 	/* If it's an occupy window, ditto */
   1781 	if(win == Scr->workSpaceMgr.occupyWindow->twm_win) {
   1782 		if(w == -1) {
   1783 			return;
   1784 		}
   1785 		ResizeOccupyWindow(win);
   1786 		return;
   1787 	}
   1788 
   1789 
   1790 	/* For anything else, we're potentially showing something */
   1791 	for(vs = Scr->vScreenList; vs != NULL; vs = vs->next) {
   1792 		WorkSpaceWindow *wsw = vs->wsw;
   1793 		WorkSpace *ws;
   1794 
   1795 		/* Scale factors for windows in the map */
   1796 		float wf = (float)(wsw->wwidth  - 2) / (float) vs->w;
   1797 		float hf = (float)(wsw->wheight - 2) / (float) vs->h;
   1798 
   1799 		/*
   1800 		 * Loop around windows in each WS to find all the places the
   1801 		 * requested window should show up.
   1802 		 */
   1803 		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
   1804 			MapSubwindow *msw = wsw->mswl[ws->number];
   1805 
   1806 			for(WinList *wl = msw->wl; wl != NULL; wl = wl->next) {
   1807 				if(win == wl->twm_win) {
   1808 					/* New positions */
   1809 					wl->x = (int)(x * wf);
   1810 					wl->y = (int)(y * hf);
   1811 
   1812 					/* Rescale if necessary and move */
   1813 					if(w == -1) {
   1814 						XMoveWindow(dpy, wl->w, wl->x, wl->y);
   1815 					}
   1816 					else {
   1817 						wl->width  = (unsigned int)((w * wf) + 0.5);
   1818 						wl->height = (unsigned int)((h * hf) + 0.5);
   1819 						if(!Scr->use3Dwmap) {
   1820 							wl->width  -= 2;
   1821 							wl->height -= 2;
   1822 						}
   1823 						if(wl->width < 1) {
   1824 							wl->width = 1;
   1825 						}
   1826 						if(wl->height < 1) {
   1827 							wl->height = 1;
   1828 						}
   1829 						XMoveResizeWindow(dpy, wl->w, wl->x, wl->y,
   1830 						                  wl->width, wl->height);
   1831 					}
   1832 					break;
   1833 				}
   1834 			}
   1835 		}
   1836 	}
   1837 }
   1838 
   1839 
   1840 /*
   1841  * Frontends for changing the stacking of windows in the WSM.
   1842  *
   1843  * Strictly speaker, we have no ability to raise or lower a window in the
   1844  * map; we only draw the whole stack.  And these functions don't actually
   1845  * change the stacking of a window, they're called as a _result_ of
   1846  * changing the stacking, to notify the WSM to re-check and update
   1847  * itself.  So while conceptually we maintain a separation for our
   1848  * callers between various reasons this is being called, the
   1849  * implementations are identical.
   1850  */
   1851 void
   1852 WMapRaiseLower(TwmWindow *win)
   1853 {
   1854 	WorkSpace *ws;
   1855 
   1856 	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
   1857 		if(OCCUPY(win, ws)) {
   1858 			WMapRestack(ws);
   1859 		}
   1860 	}
   1861 }
   1862 
   1863 void
   1864 WMapLower(TwmWindow *win)
   1865 {
   1866 	WMapRaiseLower(win);
   1867 }
   1868 
   1869 void
   1870 WMapRaise(TwmWindow *win)
   1871 {
   1872 	WMapRaiseLower(win);
   1873 }
   1874 
   1875 
   1876 /*
   1877  * Backend for redoing the stacking of a window in the WSM.
   1878  *
   1879  * XXX Since this tends to get called iteratively, there's probably
   1880  * something better we can do than doing all this relatively expensive
   1881  * stuff over and over...
   1882  */
   1883 void
   1884 WMapRestack(WorkSpace *ws)
   1885 {
   1886 	WinList *wl;
   1887 	Window  root, parent; // Dummy
   1888 	Window  *children, *smallws;
   1889 	unsigned int nchildren;
   1890 
   1891 	/* Get a whole list of the windows on the screen */
   1892 	nchildren = 0;
   1893 	XQueryTree(dpy, Scr->Root, &root, &parent, &children, &nchildren);
   1894 
   1895 	/*
   1896 	 * We're presumably dealing with a [often very small] subset of them,
   1897 	 * but just allocate space for the whole list; easier than trying to
   1898 	 * shrink down, and really, how big can it possibly be?
   1899 	 */
   1900 	smallws = calloc(nchildren, sizeof(Window));
   1901 
   1902 	/* Work it up per vscreen */
   1903 	for(VirtualScreen *vs = Scr->vScreenList; vs != NULL; vs = vs->next) {
   1904 		int j = 0;
   1905 		const MapSubwindow *msw = vs->wsw->mswl[ws->number];
   1906 
   1907 		/* Loop backward (from top to bottom of stack) */
   1908 		for(int i = nchildren - 1; i >= 0; i--) {
   1909 			TwmWindow *win = GetTwmWindow(children[i]);
   1910 
   1911 			/*
   1912 			 * Only care about certain windows.  If it's not a window we
   1913 			 * know about, or not a frame (e.g., an icon), or doesn't
   1914 			 * occupy this workspace, skip it.
   1915 			 */
   1916 			if(win == NULL || win->frame != children[i] || !OCCUPY(win, ws)) {
   1917 				continue;
   1918 			}
   1919 
   1920 			/* Debug */
   1921 			if(tracefile) {
   1922 				fprintf(tracefile, "WMapRestack : w = %lx, win = %p\n",
   1923 				        children[i], (void *)win);
   1924 				fflush(tracefile);
   1925 			}
   1926 
   1927 			/* Find this window in the list for the map of this WS */
   1928 			for(wl = msw->wl; wl != NULL; wl = wl->next) {
   1929 				/* Debug */
   1930 				if(tracefile) {
   1931 					fprintf(tracefile, "WMapRestack : wl = %p, twm_win = %p\n",
   1932 					        (void *)wl, (void *)wl->twm_win);
   1933 					fflush(tracefile);
   1934 				}
   1935 
   1936 				if(win == wl->twm_win) {
   1937 					/* There you are.  Add into our list to restack. */
   1938 					smallws[j++] = wl->w;
   1939 					break;
   1940 				}
   1941 			}
   1942 		}
   1943 
   1944 		/*
   1945 		 * Restack the windows in the map.  Note that the order is
   1946 		 * reversed from earlier; XQueryTree() returns bottom->top,
   1947 		 * XRestackWindows() is passed top->bottom.
   1948 		 */
   1949 		XRestackWindows(dpy, smallws, j);
   1950 	}
   1951 
   1952 	/* Cleanup */
   1953 	XFree(children);
   1954 	free(smallws);
   1955 	return;
   1956 }
   1957 
   1958 
   1959 
   1960 
   1961 /*
   1962  ****************************************************************
   1963  *
   1964  * Bits related to the actual drawing of the windows in the map.
   1965  *
   1966  ****************************************************************
   1967  */
   1968 
   1969 /*
   1970  * Update stuff in the WSM when win's icon name changes
   1971  */
   1972 void
   1973 WMapUpdateIconName(TwmWindow *win)
   1974 {
   1975 	VirtualScreen *vs;
   1976 	WorkSpace *ws;
   1977 	WinList *wl;
   1978 
   1979 	for(vs = Scr->vScreenList; vs != NULL; vs = vs->next) {
   1980 		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
   1981 			for(wl = vs->wsw->mswl[ws->number]->wl; wl != NULL; wl = wl->next) {
   1982 				if(win == wl->twm_win) {
   1983 					WMapRedrawName(vs, wl);
   1984 					break;
   1985 				}
   1986 			}
   1987 		}
   1988 	}
   1989 }
   1990 
   1991 
   1992 /*
   1993  * Draw a window name into the window's representation in the map-state
   1994  * WSM.
   1995  */
   1996 void
   1997 WMapRedrawName(VirtualScreen *vs, WinList *wl)
   1998 {
   1999 	ColorPair cp = wl->cp;
   2000 
   2001 	if(Scr->ReverseCurrentWorkspace && wl->wlist == vs->wsw->currentwspc) {
   2002 		InvertColorPair(&cp);
   2003 	}
   2004 	WMapRedrawWindow(wl->w, wl->width, wl->height, cp, wl->twm_win->icon_name);
   2005 }
   2006 
   2007 
   2008 /*
   2009  * Draw up a window's representation in the map-state WSM, with the
   2010  * window name.
   2011  *
   2012  * The drawing of the window name could probably be done a bit better.
   2013  * The font size is based on a tiny fraction of the window's height, so
   2014  * is probably usually too small to be useful, and often appears just as
   2015  * some odd colored pixels at the top of the window.
   2016  */
   2017 static void
   2018 WMapRedrawWindow(Window window, int width, int height,
   2019                  ColorPair cp, const char *label)
   2020 {
   2021 	int x, y;
   2022 	const MyFont font = Scr->workSpaceMgr.windowFont;
   2023 
   2024 	/* Blank out window background color */
   2025 	XClearWindow(dpy, window);
   2026 
   2027 	/* Figure out where to position the name */
   2028 	{
   2029 		XRectangle inc_rect;
   2030 		XRectangle logical_rect;
   2031 		int strhei, strwid;
   2032 		int i, descent;
   2033 		XFontStruct **xfonts;
   2034 		char **font_names;
   2035 		int fnum;
   2036 
   2037 		XmbTextExtents(font.font_set, label, strlen(label),
   2038 		               &inc_rect, &logical_rect);
   2039 		strwid = logical_rect.width;
   2040 		strhei = logical_rect.height;
   2041 
   2042 		/*
   2043 		 * If it's too tall to fit, just give up now.
   2044 		 * XXX Probably should still do border stuff below...
   2045 		 */
   2046 		if(strhei > height) {
   2047 			return;
   2048 		}
   2049 
   2050 		x = (width - strwid) / 2;
   2051 		if(x < 1) {
   2052 			x = 1;
   2053 		}
   2054 
   2055 		fnum = XFontsOfFontSet(font.font_set, &xfonts, &font_names);
   2056 		for(i = 0, descent = 0; i < fnum; i++) {
   2057 			/* xf = xfonts[i]; */
   2058 			descent = ((descent < (xfonts[i]->max_bounds.descent)) ?
   2059 			           (xfonts[i]->max_bounds.descent) : descent);
   2060 		}
   2061 
   2062 		y = ((height + strhei) / 2) - descent;
   2063 	}
   2064 
   2065 	/* Draw up borders around the win */
   2066 	if(Scr->use3Dwmap) {
   2067 		Draw3DBorder(window, 0, 0, width, height, 1, cp, off, true, false);
   2068 		FB(cp.fore, cp.back);
   2069 	}
   2070 	else {
   2071 		FB(cp.back, cp.fore);
   2072 		XFillRectangle(dpy, window, Scr->NormalGC, 0, 0, width, height);
   2073 		FB(cp.fore, cp.back);
   2074 	}
   2075 
   2076 	/* Write in the name */
   2077 	if(Scr->Monochrome != COLOR) {
   2078 		XmbDrawImageString(dpy, window, font.font_set, Scr->NormalGC, x, y,
   2079 		                   label, strlen(label));
   2080 	}
   2081 	else {
   2082 		XmbDrawString(dpy, window, font.font_set, Scr->NormalGC, x, y,
   2083 		              label, strlen(label));
   2084 	}
   2085 }
   2086 
   2087 
   2088 
   2089 
   2090 /*
   2091  * Processes for adding/removing windows from the WSM
   2092  */
   2093 
   2094 /*
   2095  * Add a window into any appropriate WSMs' maps.  Called during
   2096  * AddWindow().
   2097  */
   2098 void
   2099 WMapAddWindow(TwmWindow *win)
   2100 {
   2101 	WorkSpace *ws;
   2102 
   2103 	if(!WMapWindowMayBeAdded(win)) {
   2104 		return;
   2105 	}
   2106 
   2107 	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
   2108 		if(OCCUPY(win, ws)) {
   2109 			WMapAddWindowToWorkspace(win, ws);
   2110 		}
   2111 	}
   2112 }
   2113 
   2114 
   2115 /*
   2116  * Create WSM representation of a given window in a given WS.  Called
   2117  * when windows get added to a workspace, either via WMapAddWindow()
   2118  * during the AddWindow() process, or via an occupation change.
   2119  *
   2120  * (previously: WMapAddToList())
   2121  */
   2122 void
   2123 WMapAddWindowToWorkspace(TwmWindow *win, WorkSpace *ws)
   2124 {
   2125 	ColorPair cp;
   2126 
   2127 	/* Setup coloring for the window */
   2128 	cp.back = win->title.back;
   2129 	cp.fore = win->title.fore;
   2130 	if(Scr->workSpaceMgr.windowcpgiven) {
   2131 		cp.back = Scr->workSpaceMgr.windowcp.back;
   2132 		GetColorFromList(Scr->workSpaceMgr.windowBackgroundL,
   2133 		                 win->name, &win->class, &cp.back);
   2134 		cp.fore = Scr->workSpaceMgr.windowcp.fore;
   2135 		GetColorFromList(Scr->workSpaceMgr.windowForegroundL,
   2136 		                 win->name, &win->class, &cp.fore);
   2137 	}
   2138 	if(Scr->use3Dwmap && !Scr->BeNiceToColormap) {
   2139 		GetShadeColors(&cp);
   2140 	}
   2141 
   2142 	/* We need a copy in each VS */
   2143 	for(VirtualScreen *vs = Scr->vScreenList; vs != NULL; vs = vs->next) {
   2144 		unsigned int bw;
   2145 		WinList *wl;
   2146 		const float wf = (float)(vs->wsw->wwidth  - 2) / (float) vs->w;
   2147 		const float hf = (float)(vs->wsw->wheight - 2) / (float) vs->h;
   2148 		MapSubwindow *msw = vs->wsw->mswl[ws->number];
   2149 
   2150 		/* Put together its winlist entry */
   2151 		wl = malloc(sizeof(struct winList));
   2152 		wl->wlist  = ws;
   2153 		wl->x      = (int)(win->frame_x * wf);
   2154 		wl->y      = (int)(win->frame_y * hf);
   2155 		wl->width  = (unsigned int)((win->frame_width  * wf) + 0.5);
   2156 		wl->height = (unsigned int)((win->frame_height * hf) + 0.5);
   2157 		wl->cp     = cp;
   2158 		wl->twm_win = win;
   2159 
   2160 		/* Size the window bits */
   2161 		bw = 0;
   2162 		if(!Scr->use3Dwmap) {
   2163 			bw = 1;
   2164 			wl->width  -= 2;
   2165 			wl->height -= 2;
   2166 		}
   2167 		if(wl->width < 1) {
   2168 			wl->width  = 1;
   2169 		}
   2170 		if(wl->height < 1) {
   2171 			wl->height = 1;
   2172 		}
   2173 
   2174 		/* Create its little window */
   2175 		wl->w = XCreateSimpleWindow(dpy, msw->w, wl->x, wl->y,
   2176 		                            wl->width, wl->height, bw,
   2177 		                            Scr->Black, cp.back);
   2178 
   2179 		/* Setup cursor and attributes for it */
   2180 		{
   2181 			XSetWindowAttributes attr;
   2182 			unsigned long attrmask;
   2183 
   2184 			attr.cursor = handCursor;
   2185 			attrmask = CWCursor;
   2186 
   2187 			if(Scr->BackingStore) {
   2188 				attr.backing_store = WhenMapped;
   2189 				attrmask |= CWBackingStore;
   2190 			}
   2191 
   2192 			XChangeWindowAttributes(dpy, wl->w, attrmask, &attr);
   2193 		}
   2194 
   2195 		/* Setup events and stash context bits */
   2196 		XSelectInput(dpy, wl->w, ExposureMask);
   2197 		XSaveContext(dpy, wl->w, TwmContext, (XPointer) vs->wsw->twm_win);
   2198 		XSaveContext(dpy, wl->w, ScreenContext, (XPointer) Scr);
   2199 		XSaveContext(dpy, wl->w, MapWListContext, (XPointer) wl);
   2200 
   2201 		/* Link it onto the front of the list */
   2202 		wl->next = msw->wl;
   2203 		msw->wl  = wl;
   2204 
   2205 		/*
   2206 		 * And map it, if its window is mapped.  That'll kick an expose
   2207 		 * event, which will work its way down to WMapRedrawWindow(), and
   2208 		 * fill things in.
   2209 		 */
   2210 		if(win->mapped) {
   2211 			XMapWindow(dpy, wl->w);
   2212 		}
   2213 	} // And around to the next vscreen
   2214 }
   2215 
   2216 
   2217 /*
   2218  * Remove a window from any WSM maps it's in.  Called during window
   2219  * destruction process.
   2220  *
   2221  * (previously: WMapDestroyWindow())
   2222  */
   2223 void
   2224 WMapRemoveWindow(TwmWindow *win)
   2225 {
   2226 	WorkSpace *ws;
   2227 
   2228 	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
   2229 		if(OCCUPY(win, ws)) {
   2230 			WMapRemoveWindowFromWorkspace(win, ws);
   2231 		}
   2232 	}
   2233 
   2234 	/*
   2235 	 * If it's a mapped occupy window, manually hide aways its bits in
   2236 	 * here.
   2237 	 *
   2238 	 * XXX Better belongs inline in caller or separate func?  This is the
   2239 	 * only thing exposing occupyWin out of occupation.c.
   2240 	 */
   2241 	if(win == occupyWin) {
   2242 		OccupyWindow *occwin = Scr->workSpaceMgr.occupyWindow;
   2243 		XUnmapWindow(dpy, occwin->twm_win->frame);
   2244 		occwin->twm_win->mapped = false;
   2245 		occwin->twm_win->occupation = 0;
   2246 		occupyWin = NULL;
   2247 	}
   2248 }
   2249 
   2250 
   2251 /*
   2252  * Remove window's WSM representation.  Happens from WMapRemoveWindow()
   2253  * as part of the window destruction process, and in the occupation
   2254  * change process.
   2255  *
   2256  * (previously: WMapRemoveFromList())
   2257  */
   2258 void
   2259 WMapRemoveWindowFromWorkspace(TwmWindow *win, WorkSpace *ws)
   2260 {
   2261 	VirtualScreen *vs;
   2262 
   2263 	for(vs = Scr->vScreenList; vs != NULL; vs = vs->next) {
   2264 		WinList **prev = &vs->wsw->mswl[ws->number]->wl;
   2265 
   2266 		/* Pull it out of the list and destroy it */
   2267 		for(WinList *wl = *prev ; wl != NULL ; wl = wl->next) {
   2268 			if(win != wl->twm_win) {
   2269 				/* Not it */
   2270 				prev = &wl->next;
   2271 				continue;
   2272 			}
   2273 
   2274 			/* There you are.  Unlink and kill */
   2275 			*prev = wl->next;
   2276 
   2277 			XDeleteContext(dpy, wl->w, TwmContext);
   2278 			XDeleteContext(dpy, wl->w, ScreenContext);
   2279 			XDeleteContext(dpy, wl->w, MapWListContext);
   2280 			XDestroyWindow(dpy, wl->w);
   2281 			free(wl);
   2282 
   2283 			/* Around to the next vscreen */
   2284 			break;
   2285 		}
   2286 	}
   2287 }
   2288 
   2289 
   2290 
   2291 
   2292 /*
   2293  ****************************************************************
   2294  *
   2295  * Utils-ish funcs
   2296  *
   2297  ****************************************************************
   2298  */
   2299 
   2300 /*
   2301  * This is really more util.c fodder, but leaving it here for now because
   2302  * it's only used once in WMapRedrawName().  If we start finding external
   2303  * uses for it, it should be moved.
   2304  */
   2305 static void
   2306 InvertColorPair(ColorPair *cp)
   2307 {
   2308 	Pixel save;
   2309 
   2310 	save = cp->fore;
   2311 	cp->fore = cp->back;
   2312 	cp->back = save;
   2313 	save = cp->shadc;
   2314 	cp->shadc = cp->shadd;
   2315 	cp->shadd = save;
   2316 }
   2317 
   2318 
   2319 /*
   2320  * Verify if a window may be added to the workspace map.
   2321  * This is not allowed for
   2322  * - icon managers
   2323  * - the occupy window
   2324  * - workspace manager windows
   2325  * - or, optionally, windows with full occupation.
   2326  */
   2327 bool
   2328 WMapWindowMayBeAdded(TwmWindow *win)
   2329 {
   2330 	if(win->isiconmgr) {
   2331 		return false;
   2332 	}
   2333 	if(win == Scr->workSpaceMgr.occupyWindow->twm_win) {
   2334 		return false;
   2335 	}
   2336 	if(win->iswspmgr) {
   2337 		return false;
   2338 	}
   2339 	if(Scr->workSpaceMgr.noshowoccupyall &&
   2340 	                win->occupation == fullOccupation) {
   2341 		return false;
   2342 	}
   2343 	return true;
   2344 }
   2345