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
39extern bool useBackgroundInfo;
40
41
42static void CreateWorkSpaceManagerWindow(VirtualScreen *vs);
43static void ResizeWorkSpaceManager(VirtualScreen *vs, TwmWindow *win);
44static void PaintWorkSpaceManagerBorder(VirtualScreen *vs);
45
46static void wmap_mapwin_backend(TwmWindow *win, bool handleraise);
47
48static void WMapRedrawWindow(Window window, int width, int height,
49                             ColorPair cp, const char *label);
50
51static void InvertColorPair(ColorPair *cp);
52
53
54static XContext MapWListContext = None;
55static 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 */
71void
72InitWorkSpaceManagerContext(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 */
90void
91ConfigureWorkSpaceManager(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 */
154void
155CreateWorkSpaceManager(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 */
253static void
254CreateWorkSpaceManagerWindow(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 */
588static void
589ResizeWorkSpaceManager(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 */
677void
678PaintWorkSpaceManager(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 */
699static void
700PaintWorkSpaceManagerBorder(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 */
715void
716WMgrHandleExposeEvent(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 */
762void
763WMgrToggleState(VirtualScreen *vs)
764{
765	if(vs->wsw->state == WMS_buttons) {
766		WMgrSetMapState(vs);
767	}
768	else {
769		WMgrSetButtonsState(vs);
770	}
771}
772
773void
774WMgrSetMapState(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
789void
790WMgrSetButtonsState(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 */
821void
822WMgrHandleKeyReleaseEvent(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
839void
840WMgrHandleKeyPressEvent(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 */
948void
949WMgrHandleButtonEvent(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
1433movewin:
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
1452move:
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 */
1662static void
1663wmap_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 */
1703void
1704WMapMapWindow(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 */
1717void
1718WMapDeIconify(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 */
1734void
1735WMapIconify(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 */
1761void
1762WMapSetupWindow(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 */
1851void
1852WMapRaiseLower(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
1863void
1864WMapLower(TwmWindow *win)
1865{
1866	WMapRaiseLower(win);
1867}
1868
1869void
1870WMapRaise(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 */
1883void
1884WMapRestack(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 */
1972void
1973WMapUpdateIconName(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 */
1996void
1997WMapRedrawName(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 */
2017static void
2018WMapRedrawWindow(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 */
2098void
2099WMapAddWindow(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 */
2122void
2123WMapAddWindowToWorkspace(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 */
2223void
2224WMapRemoveWindow(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 */
2258void
2259WMapRemoveWindowFromWorkspace(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 */
2305static void
2306InvertColorPair(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 */
2327bool
2328WMapWindowMayBeAdded(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