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