occupation.c revision 0bbfda8a
1/*
2 * Occupation handling bits
3 *
4 * This is in fact pretty tightly tied and extremely similar to the
5 * handling of the WorkSpaceManager in workmgr.c, and used to be there.
6 * It makes sense to consider them together (and indeed, many of the
7 * configs that affect how this works are really WorkSpaceManager* or
8 * WMgr* commands.  But having them crammed together in one file is
9 * unwieldy.
10 */
11
12#include "ctwm.h"
13
14#include <stdio.h>
15#include <string.h>
16#include <stdlib.h>
17
18#include <X11/Xatom.h>
19
20#include "add_window.h"
21#include "ctwm_atoms.h"
22#include "drawing.h"
23#include "events.h"
24#include "iconmgr.h"
25#include "list.h"
26#include "screen.h"
27#include "occupation.h"
28#include "otp.h"
29#include "util.h"
30#include "vscreen.h"
31#include "win_iconify.h"
32#include "win_regions.h"
33#include "win_utils.h"
34#include "workspace_manager.h"
35#include "workspace_utils.h"
36
37
38static int GetMaskFromResource(TwmWindow *win, char *res);
39static char *mk_nullsep_string(const char *prop, int len);
40
41static bool CanChangeOccupation(TwmWindow **twm_winp);
42
43int fullOccupation = 0;
44
45/*
46 * The window whose occupation is currently being manipulated.
47 *
48 * XXX Should probably be static, but currently needed in
49 * WMapRemoveWindow().  Revisit.
50 */
51TwmWindow *occupyWin = NULL;
52
53
54/* XXX Share with captive.c? */
55static XrmOptionDescRec table [] = {
56	{"-xrm",            NULL,           XrmoptionResArg, (XPointer) NULL},
57};
58
59
60
61
62/*
63 ****************************************************************
64 *
65 * First, funcs related to setting and changing a window's occupation.
66 *
67 ****************************************************************
68 */
69
70
71/*
72 * Setup the occupation of a TwmWindow.  Called as part of the
73 * AddWindow() process.
74 *
75 * XXX The logic flow in this is kinda weird, and it's not at all clear
76 * to what extent it's really doing the right on on what should override
77 * what, or which things should expand/contract on others...
78 */
79void
80SetupOccupation(TwmWindow *twm_win, int occupation_hint)
81{
82	char      **cliargv = NULL;
83	int       cliargc;
84	WorkSpace *ws;
85
86	/* If there aren't any config'd workspaces, there's only 0 */
87	if(! Scr->workSpaceManagerActive) {
88		twm_win->occupation = 1 << 0;   /* occupy workspace #0 */
89		/* more?... */
90
91		return;
92	}
93
94	/* Workspace manager window doesn't get futzed with */
95	if(twm_win->iswspmgr) {
96		return;
97	}
98
99	/*twm_win->occupation = twm_win->iswinbox ? fullOccupation : 0;*/
100	twm_win->occupation = 0;
101
102	/* Specified in any Occupy{} config params? */
103	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
104		if(LookInList(ws->clientlist, twm_win->name, &twm_win->class)) {
105			twm_win->occupation |= 1 << ws->number;
106		}
107	}
108
109	/* OccupyAll{} */
110	if(LookInList(Scr->OccupyAll, twm_win->name, &twm_win->class)) {
111		twm_win->occupation = fullOccupation;
112	}
113
114	/* See if it specified in -xrm stuff */
115	if(XGetCommand(dpy, twm_win->w, &cliargv, &cliargc)) {
116		Bool status;
117		char *str_type;
118		XrmValue value;
119		XrmDatabase db = NULL;
120
121		XrmParseCommand(&db, table, 1, "ctwm", &cliargc, cliargv);
122		XFreeStringList(cliargv);
123		status = XrmGetResource(db, "ctwm.workspace", "Ctwm.Workspace",
124		                        &str_type, &value);
125		if((status == True) && (value.size != 0)) {
126			/* Copy the value.addr because it's in XRM memory not ours */
127			char wrkSpcList[512];
128			safe_strncpy(wrkSpcList, value.addr, MIN(value.size, 512));
129
130			twm_win->occupation = GetMaskFromResource(twm_win, wrkSpcList);
131		}
132		XrmDestroyDatabase(db);
133	}
134
135	/* Does it have a property telling us */
136	if(RestartPreviousState) {
137		Atom actual_type;
138		int actual_format;
139		unsigned long nitems, bytesafter;
140		unsigned char *prop;
141
142		if(XGetWindowProperty(dpy, twm_win->w, XA_WM_OCCUPATION, 0L, 2500, False,
143		                      XA_STRING, &actual_type, &actual_format, &nitems,
144		                      &bytesafter, &prop) == Success) {
145			if(nitems != 0) {
146				twm_win->occupation = GetMaskFromProperty(prop, nitems);
147				XFree(prop);
148			}
149		}
150	}
151
152#ifdef EWMH
153	/* Maybe EWMH has something to tell us? */
154	if(twm_win->occupation == 0) {
155		twm_win->occupation = EwmhGetOccupation(twm_win);
156	}
157#endif /* EWMH */
158
159	/* Icon Managers shouldn't get altered */
160	/* XXX Should this be up near the top? */
161	if(twm_win->isiconmgr) {
162		return;        /* someone tried to modify occupation of icon managers */
163	}
164
165
166	/*
167	 * Transient-ish things go with their parents unless
168	 * TransientHasOccupation set in the config.
169	 */
170	if(! Scr->TransientHasOccupation) {
171		TwmWindow *t;
172
173		if(twm_win->istransient) {
174			t = GetTwmWindow(twm_win->transientfor);
175			if(t != NULL) {
176				twm_win->occupation = t->occupation;
177			}
178		}
179		else if(twm_win->group != 0) {
180			t = GetTwmWindow(twm_win->group);
181			if(t != NULL) {
182				twm_win->occupation = t->occupation;
183			}
184		}
185	}
186
187
188	/* If we were told something specific, go with that */
189	if(occupation_hint != 0) {
190		twm_win->occupation = occupation_hint;
191	}
192
193	/* If it's apparently-nonsensical, put it in its vs's workspace */
194	if((twm_win->occupation & fullOccupation) == 0) {
195		twm_win->occupation = 1 << twm_win->vs->wsw->currentwspc->number;
196	}
197
198	/*
199	 * If the occupation would not show it in the current vscreen,
200	 * make it vanish.
201	 *
202	 * If it could be shown in one of the other vscreens, change the vscreen.
203	 */
204	if(!OCCUPY(twm_win, twm_win->vs->wsw->currentwspc)) {
205		VirtualScreen *vs;
206
207		twm_win->vs = NULL;
208
209		if(Scr->numVscreens > 1) {
210			for(vs = Scr->vScreenList; vs != NULL; vs = vs->next) {
211				if(OCCUPY(twm_win, vs->wsw->currentwspc)) {
212					twm_win->vs = vs;
213					twm_win->parent_vs = vs;
214					break;
215				}
216			}
217		}
218	}
219
220
221	/* Set the property for the occupation */
222	{
223		long eventMask;
224		char *wsstr;
225		int  len;
226
227		/* Ignore the PropertyChange we're about to do */
228		if((eventMask = mask_out_event(twm_win->w, PropertyChangeMask)) < 0) {
229			/* Window is horked, not much we can do */
230			return;
231		}
232
233		/* Set the property for the occupation */
234		len = GetPropertyFromMask(twm_win->occupation, &wsstr);
235		XChangeProperty(dpy, twm_win->w, XA_WM_OCCUPATION, XA_STRING, 8,
236		                PropModeReplace, (unsigned char *) wsstr, len);
237		free(wsstr);
238
239#ifdef EWMH
240		EwmhSet_NET_WM_DESKTOP(twm_win);
241#endif
242
243		/* Restore event mask */
244		restore_mask(twm_win->w, eventMask);
245	}
246
247	/* Set WM_STATE prop */
248	{
249		int state = NormalState;
250		Window icon;
251
252		if(!(RestartPreviousState
253		                && GetWMState(twm_win->w, &state, &icon)
254		                && (state == NormalState || state == IconicState
255		                    || state == InactiveState))) {
256			if(twm_win->wmhints->flags & StateHint) {
257				state = twm_win->wmhints->initial_state;
258			}
259		}
260		if(visible(twm_win)) {
261			if(state == InactiveState) {
262				SetMapStateProp(twm_win, NormalState);
263			}
264		}
265		else {
266			if(state == NormalState) {
267				SetMapStateProp(twm_win, InactiveState);
268			}
269		}
270	}
271}
272
273
274/*
275 * Make sure a window is marked in a given workspace.  f.addtoworkspace.
276 * Also gets called as part of the process of mapping a window; if we're
277 * mapping it here, it should know that it's here.  And Xinerama magic
278 * moves.
279 */
280void
281AddToWorkSpace(char *wname, TwmWindow *twm_win)
282{
283	WorkSpace *ws;
284	int newoccupation;
285
286	if(!CanChangeOccupation(&twm_win)) {
287		return;
288	}
289	ws = GetWorkspace(wname);
290	if(!ws) {
291		return;
292	}
293
294	if(twm_win->occupation & (1 << ws->number)) {
295		return;
296	}
297	newoccupation = twm_win->occupation | (1 << ws->number);
298	ChangeOccupation(twm_win, newoccupation);
299}
300
301
302/*
303 * Converse of the above.  f.removefromworkspace, also called from
304 * Xinerama-related magic.
305 */
306void
307RemoveFromWorkSpace(char *wname, TwmWindow *twm_win)
308{
309	WorkSpace *ws;
310	int newoccupation;
311
312	if(!CanChangeOccupation(&twm_win)) {
313		return;
314	}
315	ws = GetWorkspace(wname);
316	if(!ws) {
317		return;
318	}
319
320	newoccupation = twm_win->occupation & ~(1 << ws->number);
321	if(!newoccupation) {
322		return;
323	}
324	ChangeOccupation(twm_win, newoccupation);
325}
326
327
328/* f.toggleoccupation - flip setting for [current] workspace */
329void
330ToggleOccupation(char *wname, TwmWindow *twm_win)
331{
332	WorkSpace *ws;
333	int newoccupation;
334
335	if(!CanChangeOccupation(&twm_win)) {
336		return;
337	}
338	ws = GetWorkspace(wname);
339	if(!ws) {
340		return;
341	}
342
343	newoccupation = twm_win->occupation ^ (1 << ws->number);
344	if(!newoccupation) {
345		/* Don't allow de-occupying _every_ ws */
346		return;
347	}
348	ChangeOccupation(twm_win, newoccupation);
349}
350
351
352/* f.movetonextworkspace */
353void
354MoveToNextWorkSpace(VirtualScreen *vs, TwmWindow *twm_win)
355{
356	WorkSpace *wlist1, *wlist2;
357	int newoccupation;
358
359	if(!CanChangeOccupation(&twm_win)) {
360		return;
361	}
362
363	wlist1 = vs->wsw->currentwspc;
364	wlist2 = wlist1->next;
365	wlist2 = wlist2 ? wlist2 : Scr->workSpaceMgr.workSpaceList;
366
367	/* Out of (here), into (here+1) */
368	newoccupation = (twm_win->occupation ^ (1 << wlist1->number))
369	                | (1 << wlist2->number);
370	ChangeOccupation(twm_win, newoccupation);
371}
372
373
374/* f.movetonextworkspaceandfollow */
375void
376MoveToNextWorkSpaceAndFollow(VirtualScreen *vs, TwmWindow *twm_win)
377{
378	if(!CanChangeOccupation(&twm_win)) {
379		return;
380	}
381
382	MoveToNextWorkSpace(vs, twm_win);
383	GotoNextWorkSpace(vs);
384#if 0
385	OtpRaise(twm_win, WinWin);  /* XXX really do this? */
386#endif
387}
388
389
390/* f.movetoprevworkspaceand */
391void
392MoveToPrevWorkSpace(VirtualScreen *vs, TwmWindow *twm_win)
393{
394	WorkSpace *wlist1, *wlist2;
395	int newoccupation;
396
397	if(!CanChangeOccupation(&twm_win)) {
398		return;
399	}
400
401	wlist1 = Scr->workSpaceMgr.workSpaceList;
402	wlist2 = vs->wsw->currentwspc;
403	if(wlist1 == NULL) {
404		return;
405	}
406
407	while(wlist1->next != wlist2 && wlist1->next != NULL) {
408		wlist1 = wlist1->next;
409	}
410
411	/* Out of (here), into (here-1) */
412	newoccupation = (twm_win->occupation ^ (1 << wlist2->number))
413	                | (1 << wlist1->number);
414	ChangeOccupation(twm_win, newoccupation);
415}
416
417
418/* f.movetoprevworkspaceandfollow */
419void
420MoveToPrevWorkSpaceAndFollow(VirtualScreen *vs, TwmWindow *twm_win)
421{
422	if(!CanChangeOccupation(&twm_win)) {
423		return;
424	}
425
426	MoveToPrevWorkSpace(vs, twm_win);
427	GotoPrevWorkSpace(vs);
428#if 0
429	OtpRaise(twm_win, WinWin);          /* XXX really do this? */
430#endif
431}
432
433
434/*
435 * Set the occupation based on the window name.  This is called if
436 * AutoOccupy is set, when we get a notification about a window name
437 * change.
438 */
439void
440WmgrRedoOccupation(TwmWindow *win)
441{
442	WorkSpace *ws;
443	int       newoccupation;
444
445	if(LookInList(Scr->OccupyAll, win->name, &win->class)) {
446		newoccupation = fullOccupation;
447	}
448	else {
449		newoccupation = 0;
450		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
451			if(LookInList(ws->clientlist, win->name, &win->class)) {
452				newoccupation |= 1 << ws->number;
453			}
454		}
455	}
456	if(newoccupation != 0) {
457		ChangeOccupation(win, newoccupation);
458	}
459}
460
461
462/* f.vanish */
463void
464WMgrRemoveFromCurrentWorkSpace(VirtualScreen *vs, TwmWindow *win)
465{
466	WorkSpace *ws;
467	int       newoccupation;
468
469	ws = vs->wsw->currentwspc;
470	if(!ws) {
471		/* Impossible? */
472		return;
473	}
474	if(! OCCUPY(win, ws)) {
475		return;
476	}
477
478	newoccupation = win->occupation & ~(1 << ws->number);
479	if(newoccupation == 0) {
480		return;
481	}
482
483	ChangeOccupation(win, newoccupation);
484}
485
486
487/* f.warphere */
488void
489WMgrAddToCurrentWorkSpaceAndWarp(VirtualScreen *vs, char *winname)
490{
491	TwmWindow *tw;
492	int       newoccupation;
493
494	/* Find named window on this screen */
495	for(tw = Scr->FirstWindow; tw != NULL; tw = tw->next) {
496		if(match(winname, tw->name)) {
497			break;
498		}
499	}
500
501	/* Didn't find it by name?  Try by class */
502	if(!tw) {
503		for(tw = Scr->FirstWindow; tw != NULL; tw = tw->next) {
504			if(match(winname, tw->class.res_name)) {
505				break;
506			}
507		}
508	}
509	if(!tw) {
510		for(tw = Scr->FirstWindow; tw != NULL; tw = tw->next) {
511			if(match(winname, tw->class.res_class)) {
512				break;
513			}
514		}
515	}
516
517	/* Still didn't find?  Beep at the user and bail. */
518	if(!tw) {
519		XBell(dpy, 0);
520		return;
521	}
522
523	/* If WarpUnmapped isn't set and this isn't mapped, beep and bail */
524	if((! Scr->WarpUnmapped) && (! tw->mapped)) {
525		XBell(dpy, 0);
526		return;
527	}
528
529	/* Move it here if it's not */
530	if(! OCCUPY(tw, vs->wsw->currentwspc)) {
531		newoccupation = tw->occupation | (1 << vs->wsw->currentwspc->number);
532		ChangeOccupation(tw, newoccupation);
533	}
534
535	/* If we get here, WarpUnmapped is set, so map it if we need to */
536	if(! tw->mapped) {
537		DeIconify(tw);
538	}
539
540	/* And go */
541	WarpToWindow(tw, Scr->RaiseOnWarp);
542}
543
544
545/* f.occupyall backend */
546void
547OccupyAll(TwmWindow *twm_win)
548{
549	IconMgr *save;
550
551	if(!CanChangeOccupation(&twm_win)) {
552		return;
553	}
554
555	/*
556	 * Temporarily alter Scr->iconmgr because stuff down in
557	 * ChangeOccupation winds up adding/removing bits, and that doesn't
558	 * work right when we're setting all?  XXX Investigate further.
559	 */
560	save = Scr->iconmgr;
561	Scr->iconmgr = Scr->workSpaceMgr.workSpaceList->iconmgr;
562	ChangeOccupation(twm_win, fullOccupation);
563	Scr->iconmgr = save;
564}
565
566
567
568/*
569 ****************************************************************
570 *
571 * Pieces related to the Occupy window
572 *
573 ****************************************************************
574 */
575
576static ColorPair occupyButtoncp;
577
578static char *ok_string         = "OK",
579             *cancel_string     = "Cancel",
580              *everywhere_string = "All";
581
582/*
583 * Create the Occupy window.  Part of startup process.
584 *
585 * Do not do the layout of the parts, only calculate the initial total
586 * size. For the layout, call ResizeOccupyWindow() at the end.
587 *
588 * There is only one Occupy window (per Screen), it is reparented to each
589 * virtual screen as needed.
590 */
591void
592CreateOccupyWindow(void)
593{
594	int           width; // Caculated and altered, unlike most others
595	int           Dummy = 1;
596	TwmWindow     *tmp_win;
597	/* Shorthands for the Occupy window */
598	OccupyWindow  *occwin = Scr->workSpaceMgr.occupyWindow;
599	Window        w; // occwin->w
600	/* Misc other shorthands */
601	const int lines   = Scr->workSpaceMgr.lines;
602	const int columns = Scr->workSpaceMgr.columns;
603	const int bwidth  = Scr->vScreenList->wsw->bwidth;
604	const int bheight = Scr->vScreenList->wsw->bheight;
605	const int vspace = occwin->vspace;
606	const int hspace = occwin->hspace;
607	const int height = ((bheight + vspace) * lines) + bheight + (2 * vspace);
608
609	/* Struct embedded in [struct embedded in] Scr, so memory's waiting */
610
611	/* There isn't anything we should do without workspaces... */
612	if(!Scr->workSpaceManagerActive) {
613		return;
614	}
615
616	/* Initialize font and colorpair bits */
617	occwin->font     = Scr->IconManagerFont;
618	occwin->cp       = Scr->IconManagerC;
619#ifdef COLOR_BLIND_USER
620	occwin->cp.shadc = Scr->White;
621	occwin->cp.shadd = Scr->Black;
622#else
623	if(!Scr->BeNiceToColormap) {
624		GetShadeColors(&occwin->cp);
625	}
626#endif
627
628	/* We already know that these should be too */
629	occwin->lines   = lines;
630	occwin->columns = columns;
631
632
633	/*
634	 * Work out the necessary size of the OK/Cancel/All buttons at the
635	 * bottom.
636	 */
637	{
638		XRectangle inc_rect;
639		XRectangle logical_rect;
640		MyFont font = occwin->font;
641		int bbwidth;  // Bottom button width
642		/* Window min width based on bottom vs. workspace btns */
643		int bb_width, ws_width;
644
645		/* Buttons gotta be as wide as the biggest of the three strings */
646		XmbTextExtents(font.font_set, ok_string, strlen(ok_string),
647		               &inc_rect, &logical_rect);
648		bbwidth = logical_rect.width;
649
650		XmbTextExtents(font.font_set, cancel_string, strlen(cancel_string),
651		               &inc_rect, &logical_rect);
652		bbwidth = MAX(bbwidth, logical_rect.width);
653
654		XmbTextExtents(font.font_set, everywhere_string,
655		               strlen(everywhere_string),
656		               &inc_rect, &logical_rect);
657		bbwidth = MAX(bbwidth, logical_rect.width);
658
659		/* Plus the padding width */
660		bbwidth += hspace;
661
662		/*
663		 * So, the final width of those bottom buttons is that, plus the
664		 * 3d button look extra on both sides, plus a little extra.  I
665		 * guess that extra + 2 is similar to TitlePadding or
666		 * ButtonIndent on titlebars, but we don't have a config param
667		 * for it on the workspace manager (which is the config used for
668		 * the occupy window), so leave it as a magic constant for now.
669		 */
670		occwin->owidth = bbwidth + 2 * Scr->WMgrButtonShadowDepth + 2;
671
672		/*
673		 * The whole thing has to be at least triple the min width of
674		 * those bottom buttons, since there are three of them.  The
675		 * layout is "hspace button hspace button [...] hspace", to pad
676		 * between and on both sides.
677		 */
678		bb_width = 3 * (bbwidth + hspace) + hspace;
679
680		/*
681		 * It also has to be the width of our per-WS buttons.  Per-ws
682		 * buttons are sized the same as in the button-state WSM, and
683		 * then we add the padding to them as above.
684		 */
685		ws_width = columns * (bwidth + hspace) + hspace;
686
687		/* So the window has to be as wide as the wider of those */
688		width = MAX(bb_width, ws_width);
689	}
690
691
692	/* Now we know the size, so make the window */
693	w = occwin->w = XCreateSimpleWindow(dpy, Scr->Root, 0, 0, width, height,
694	                                    1, Scr->Black, occwin->cp.back);
695
696	/* Take those base sizes as a minimum */
697	occwin->minwidth  = width;
698	occwin->minheight = height;
699
700
701	/*
702	 * Make subwindows as buttons for the workspaces.  They're laid out
703	 * in a grid mirroring the workspace manager's.
704	 */
705	{
706		int i = 0, j = 0;
707		WorkSpace *ws;
708
709		occwin->obuttonw = calloc(Scr->workSpaceMgr.count, sizeof(Window));
710
711		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
712			int idx = (j * columns) + i;
713
714			/*
715			 * Make and map.  Note that we're not setting the size or
716			 * location at all here; ResizeOccupyWindow() does all that.
717			 * We just make 'em.
718			 */
719			occwin->obuttonw[idx] = XCreateSimpleWindow(dpy, w,
720			                        Dummy /* x */,
721			                        Dummy /* y */,
722			                        Dummy /* width */,
723			                        Dummy /* height */,
724			                        0, Scr->Black, ws->cp.back);
725			XMapWindow(dpy, occwin->obuttonw[idx]);
726
727			/* Inc around to the next location */
728			i++;
729			if(i == columns) {
730				i = 0;
731				j++;
732			}
733		}
734	}
735
736
737	/*
738	 * Now start putting together the OK/Cancel/All buttons
739	 */
740
741	/* Background for them is hardcoded */
742	GetColor(Scr->Monochrome, &(occupyButtoncp.back), "gray50");
743
744	/* Foreground (not used here) is too */
745	occupyButtoncp.fore = Scr->White;
746
747	/* Override (probably historical */
748	if(!Scr->BeNiceToColormap) {
749		GetShadeColors(&occupyButtoncp);
750	}
751
752	/* Make 'em */
753	{
754		Window tw;
755
756		tw = XCreateSimpleWindow(dpy, w, Dummy, Dummy, Dummy, Dummy, 0,
757		                         Scr->Black, occupyButtoncp.back);
758		XMapWindow(dpy, tw);
759		occwin->OK = tw;
760
761		tw = XCreateSimpleWindow(dpy, w, Dummy, Dummy, Dummy, Dummy, 0,
762		                         Scr->Black, occupyButtoncp.back);
763		XMapWindow(dpy, tw);
764		occwin->cancel = tw;
765
766		tw = XCreateSimpleWindow(dpy, w, Dummy, Dummy, Dummy, Dummy, 0,
767		                         Scr->Black, occupyButtoncp.back);
768		XMapWindow(dpy, tw);
769		occwin->allworkspc = tw;
770	}
771
772
773	/* Setup various window properties */
774	{
775		XSizeHints sizehints;
776		XWMHints wmhints;
777
778		sizehints.flags       = PBaseSize | PMinSize | PMaxSize;
779		sizehints.min_width   = width;
780		sizehints.min_height  = height;
781		sizehints.base_width  = width;
782		sizehints.base_height = height;
783		sizehints.max_width   = width;
784		sizehints.max_height  = height;
785
786		wmhints.flags         = InputHint | StateHint;
787		wmhints.input         = True;
788		wmhints.initial_state = NormalState;
789
790		XmbSetWMProperties(dpy, w, occwin->name, occwin->icon_name,
791		                   NULL, 0, &sizehints, &wmhints, NULL);
792	}
793
794
795	/*
796	 * Create the TwmWindow wrapping around it, with decorations etc.  We
797	 * do this so early in startup that we're not listening for window
798	 * creation events yet.
799	 */
800	tmp_win = AddWindow(w, AWT_OCCUPY, Scr->iconmgr, Scr->currentvs);
801	if(! tmp_win) {
802		fprintf(stderr, "cannot create occupy window, exiting...\n");
803		exit(1);
804	}
805	tmp_win->vs = NULL;
806	tmp_win->occupation = 0;
807
808	/* tmp_win is more convenient the rest of the func, but put in place */
809	occwin->twm_win = tmp_win;
810
811
812	/*
813	 * Setup the window to have a button-pushing cursor and listen for
814	 * clicks.
815	 */
816	{
817		unsigned long attrmask;
818		XSetWindowAttributes attr;
819		XWindowAttributes wattr;
820
821		attr.cursor = Scr->ButtonCursor;
822		attrmask = CWCursor;
823		XChangeWindowAttributes(dpy, w, attrmask, &attr);
824
825		XGetWindowAttributes(dpy, w, &wattr);
826		attrmask = wattr.your_event_mask | KeyPressMask | KeyReleaseMask
827		           | ExposureMask;
828		XSelectInput(dpy, w, attrmask);
829	}
830
831
832	/*
833	 * Now for each of the buttons (workspaces + OK/Cancel/All), we mark
834	 * them as listening to click and exposure events.  We also stash
835	 * away the screen and wrapping TwmWindow in contexts so other code
836	 * can dredge them up.
837	 */
838#define EVT (ButtonPressMask | ButtonReleaseMask | ExposureMask)
839#define BTN_IPT_CTX(win) \
840        XSelectInput(dpy, (win), EVT); \
841        XSaveContext(dpy, (win), TwmContext, (XPointer) tmp_win); \
842        XSaveContext(dpy, (win), ScreenContext, (XPointer) Scr);
843
844	for(WorkSpace *ws = Scr->workSpaceMgr.workSpaceList
845	                    ; ws != NULL ; ws = ws->next) {
846		BTN_IPT_CTX(occwin->obuttonw[ws->number]);
847	}
848
849	BTN_IPT_CTX(occwin->OK);
850	BTN_IPT_CTX(occwin->cancel);
851	BTN_IPT_CTX(occwin->allworkspc);
852
853#undef BTN_IPT_CTX
854#undef EVT
855
856
857	/* Mark that we're not mapped */
858	SetMapStateProp(tmp_win, WithdrawnState);
859
860	/* Now call that func that sizes all the buttons */
861	ResizeOccupyWindow(tmp_win);
862}
863
864
865/*
866 * Slightly misleading name: layout the internals of the Occupy window
867 * based on its current size.  That does happen when it's resized, but
868 * also when it's initially created.  I guess you could call "creation" a
869 * resize of a sort...
870 */
871void
872ResizeOccupyWindow(TwmWindow *win)
873{
874	int        bwidth, bheight, owidth, oheight;
875	int        hspace, vspace;
876	int        lines, columns;
877	int        neww, newh;
878	WorkSpace  *ws;
879	int        i, j, x, y;
880	OccupyWindow *occwin = Scr->workSpaceMgr.occupyWindow;
881
882	/* Floor at the original size */
883	neww = MAX(win->attr.width,  occwin->minwidth);
884	newh = MAX(win->attr.height, occwin->minheight);
885	if(occwin->width == neww && occwin->height == newh) {
886		return;
887	}
888
889	/* Space between WS buttons.  From WMgr{Horiz,Vert}ButtonIndent. */
890	hspace  = occwin->hspace;
891	vspace  = occwin->vspace;
892
893	/* Lines/cols in the layout.  Same as WorkspaceManager's */
894	lines   = Scr->workSpaceMgr.lines;
895	columns = Scr->workSpaceMgr.columns;
896
897	/* Width/height of each button, based on the above and window size */
898	bwidth  = (neww -  columns    * hspace) / columns;
899	bheight = (newh - (lines + 2) * vspace) / (lines + 1);
900
901	/* Width/height of the OK/Cancel/All buttons */
902	owidth  = occwin->owidth;
903	oheight = bheight;
904
905
906	/*
907	 * Lay out the workspace buttons
908	 */
909	i = 0;
910	j = 0;
911	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
912		XMoveResizeWindow(dpy, occwin->obuttonw [j * columns + i],
913		                  i * (bwidth  + hspace) + (hspace / 2),
914		                  j * (bheight + vspace) + (vspace / 2),
915		                  bwidth, bheight);
916		i++;
917		if(i == columns) {
918			i = 0;
919			j++;
920		}
921	}
922
923
924	/*
925	 * Now the action buttons
926	 */
927	hspace = (neww - 3 * owidth) / 4;  // Padding between
928	x = hspace;
929	y = ((bheight + vspace) * lines) + ((3 * vspace) / 2);
930	XMoveResizeWindow(dpy, occwin->OK, x, y, owidth, oheight);
931	x += owidth + hspace;
932	XMoveResizeWindow(dpy, occwin->cancel, x, y, owidth, oheight);
933	x += owidth + hspace;
934	XMoveResizeWindow(dpy, occwin->allworkspc, x, y, owidth, oheight);
935
936
937	/* Save all those dimensions we figured */
938	occwin->width   = neww;
939	occwin->height  = newh;
940	occwin->bwidth  = bwidth;
941	occwin->bheight = bheight;
942	occwin->owidth  = owidth;
943
944	/* Don't need to repaint it; it'll get expose events */
945}
946
947
948/*
949 * Draw the window when we need to (e.g., on expose)
950 */
951void
952PaintOccupyWindow(void)
953{
954	WorkSpace    *ws;
955	OccupyWindow *occwin;
956	int          width, height;
957
958	occwin = Scr->workSpaceMgr.occupyWindow;
959	width  = occwin->width;
960	height = occwin->height;
961
962	Draw3DBorder(occwin->w, 0, 0, width, height, 2, occwin->cp, off, true, false);
963
964	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
965		Window bw = occwin->obuttonw [ws->number];
966		ButtonState bs = (occwin->tmpOccupation & (1 << ws->number)) ? on : off;
967
968		PaintWsButton(OCCUPYWINDOW, NULL, bw, ws->label, ws->cp, bs);
969	}
970	PaintWsButton(OCCUPYBUTTON, NULL, occwin->OK,         ok_string,
971	              occupyButtoncp, off);
972	PaintWsButton(OCCUPYBUTTON, NULL, occwin->cancel,     cancel_string,
973	              occupyButtoncp, off);
974	PaintWsButton(OCCUPYBUTTON, NULL, occwin->allworkspc, everywhere_string,
975	              occupyButtoncp, off);
976}
977
978
979/*
980 * Somebody clicked in the Occupy window
981 */
982void
983OccupyHandleButtonEvent(XEvent *event)
984{
985	WorkSpace    *ws;
986	OccupyWindow *occupyW;
987	Window       buttonW;
988
989	/*
990	 * Doesn't make sense that this can even happen if there are no
991	 * workspaces...
992	 */
993	if(! Scr->workSpaceManagerActive) {
994		return;
995	}
996
997	/* ... or if there's no Occupy window up for anything */
998	if(occupyWin == NULL) {
999		return;
1000	}
1001
1002	/* Which sub-window (button) was clicked */
1003	buttonW = event->xbutton.window;
1004	if(buttonW == 0) {
1005		return;        /* icon */
1006	}
1007
1008	/* Grab onto the pointer for the duration of our action */
1009	XGrabPointer(dpy, Scr->Root, True,
1010	             ButtonPressMask | ButtonReleaseMask,
1011	             GrabModeAsync, GrabModeAsync,
1012	             Scr->Root, None, CurrentTime);
1013
1014	/* Find the workspace button that was clicked */
1015	occupyW = Scr->workSpaceMgr.occupyWindow;
1016	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
1017		if(occupyW->obuttonw [ws->number] == buttonW) {
1018			break;
1019		}
1020	}
1021
1022	if(ws != NULL) {
1023		/* If one was, toggle it */
1024		int mask = 1 << ws->number;
1025		ButtonState bs = (occupyW->tmpOccupation & mask) ? off : on;
1026
1027		PaintWsButton(OCCUPYWINDOW, NULL, occupyW->obuttonw [ws->number],
1028		              ws->label, ws->cp, bs);
1029		occupyW->tmpOccupation ^= mask;
1030	}
1031	else if(buttonW == occupyW->OK) {
1032		/* Else if we clicked OK, set things and close the window */
1033		if(occupyW->tmpOccupation == 0) {
1034			return;
1035		}
1036		ChangeOccupation(occupyWin, occupyW->tmpOccupation);
1037		XUnmapWindow(dpy, occupyW->twm_win->frame);
1038		occupyW->twm_win->mapped = false;
1039		occupyW->twm_win->occupation = 0;
1040		occupyWin = NULL;
1041		XSync(dpy, 0);
1042	}
1043	else if(buttonW == occupyW->cancel) {
1044		/* Or cancel, do nothing and close the window */
1045		XUnmapWindow(dpy, occupyW->twm_win->frame);
1046		occupyW->twm_win->mapped = false;
1047		occupyW->twm_win->occupation = 0;
1048		occupyWin = NULL;
1049		XSync(dpy, 0);
1050	}
1051	else if(buttonW == occupyW->allworkspc) {
1052		/* Or All, set 'em all */
1053		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
1054			PaintWsButton(OCCUPYWINDOW, NULL, occupyW->obuttonw [ws->number],
1055			              ws->label, ws->cp, on);
1056		}
1057		occupyW->tmpOccupation = fullOccupation;
1058	}
1059
1060	/* Release the pointer, if ??? */
1061	if(ButtonPressed == -1) {
1062		XUngrabPointer(dpy, CurrentTime);
1063	}
1064}
1065
1066
1067/*
1068 * f.occupy backend - pop up Occupy control for some window
1069 */
1070void
1071Occupy(TwmWindow *twm_win)
1072{
1073	int          x, y;
1074	unsigned int width, height;
1075	int          xoffset, yoffset;
1076	Window       w;
1077	struct OccupyWindow    *occupyWindow;
1078	TwmWindow *occupy_twm;
1079
1080	/* Don't pop up on stuff we can't change */
1081	if(!CanChangeOccupation(&twm_win)) {
1082		return;
1083	}
1084
1085	/* Grab our one screen-wide f.occupy window */
1086	occupyWindow = Scr->workSpaceMgr.occupyWindow;
1087	occupyWindow->tmpOccupation = twm_win->occupation;
1088	w = occupyWindow->w;
1089
1090	/* Figure where to put it so it's centered on the cursor */
1091	XGetGeometry(dpy, w, &JunkRoot, &JunkX, &JunkY, &width, &height,
1092	             &JunkBW, &JunkDepth);
1093	XQueryPointer(dpy, Scr->Root, &JunkRoot, &JunkRoot, &JunkX, &JunkY,
1094	              &x, &y, &JunkMask);
1095	x -= (width  / 2);
1096	y -= (height / 2);
1097	if(x < 0) {
1098		x = 0;
1099	}
1100	if(y < 0) {
1101		y = 0;
1102	}
1103	xoffset = width  + 2 * Scr->BorderWidth;
1104	yoffset = height + 2 * Scr->BorderWidth + Scr->TitleHeight;
1105
1106	/* ... (but not off the screen!) */
1107	if((x + xoffset) > Scr->rootw) {
1108		x = Scr->rootw - xoffset;
1109	}
1110	if((y + yoffset) > Scr->rooth) {
1111		y = Scr->rooth - yoffset;
1112	}
1113
1114	occupy_twm = occupyWindow->twm_win;
1115	occupy_twm->occupation = twm_win->occupation;
1116
1117	/* Move the occupy window to where it should be */
1118	if(occupy_twm->parent_vs != twm_win->parent_vs) {
1119		occupy_twm->vs = twm_win->parent_vs;
1120		occupy_twm->frame_x = x;
1121		occupy_twm->frame_y = y;
1122		/*
1123		 * XXX Should this be using DisplayWin() like everything else,
1124		 * rather than manually grubbing beneath it?
1125		 */
1126		ReparentFrameAndIcon(occupy_twm);
1127	}
1128	else {
1129		XMoveWindow(dpy, occupyWindow->twm_win->frame, x, y);
1130	}
1131
1132	/* And show it */
1133	SetMapStateProp(occupy_twm, NormalState);
1134	XMapWindow(dpy, occupyWindow->w);
1135	XMapWindow(dpy, occupy_twm->frame);
1136
1137	/* XXX Must be a better way to express "all the way on top" */
1138	OtpSetPriority(occupy_twm, WinWin, 0, Above);
1139
1140	/* Mark it shown, and stash what window we're showing it for */
1141	occupyWindow->twm_win->mapped = true;
1142	occupyWin = twm_win;
1143}
1144
1145
1146
1147
1148/*
1149 ****************************************************************
1150 *
1151 * Backend and misc
1152 *
1153 ****************************************************************
1154 */
1155
1156
1157/*
1158 * The actual meat of occupation-changing; [re-]set the occupation for
1159 * the window.  This is the func that actually sets and saves the new
1160 * occupation, moves the window where it should be, etc.  Should maybe be
1161 * called something more like "SetOccupation()".
1162 */
1163void
1164ChangeOccupation(TwmWindow *tmp_win, int newoccupation)
1165{
1166	TwmWindow *t;
1167	WorkSpace *ws;
1168	int oldoccupation;
1169	int changedoccupation;
1170
1171	if((newoccupation == 0)
1172	                || (newoccupation == tmp_win->occupation)) {
1173		/*
1174		 * occupation=0 we interpret as "leave it alone".  == current,
1175		 * ditto.  Go ahead and re-set the WM_OCCUPATION property though,
1176		 * in case it's been broken by another client.
1177		 */
1178		char *namelist;
1179		int  len;
1180		long eventMask;
1181
1182		/* Mask out the PropertyChange events while we change the prop */
1183		eventMask = mask_out_event(tmp_win->w, PropertyChangeMask);
1184
1185		len = GetPropertyFromMask(tmp_win->occupation, &namelist);
1186		XChangeProperty(dpy, tmp_win->w, XA_WM_OCCUPATION, XA_STRING, 8,
1187		                PropModeReplace, (unsigned char *) namelist, len);
1188		free(namelist);
1189#ifdef EWMH
1190		EwmhSet_NET_WM_DESKTOP(tmp_win);
1191#endif
1192
1193		/* Reset event mask */
1194		restore_mask(tmp_win->w, eventMask);
1195		return;
1196	}
1197
1198	/*
1199	 * OK, there's something to change.  Stash the current state.
1200	 */
1201	oldoccupation = tmp_win->occupation;
1202
1203	/*
1204	 * Add it to IconManager in the new WS[en], remove from old.  We have
1205	 * to do the rather odd dance because AddIconManager() loops through
1206	 * workspaces, and will add it to any workspaces it occupies (even if
1207	 * it's already there).  RemoveIconManager() goes over the window's
1208	 * list of what icon managers it's on and removes it from any that
1209	 * don't match the current occupation, so it can just be told "here's
1210	 * where I should be".
1211	 */
1212	tmp_win->occupation = newoccupation & ~oldoccupation;
1213	AddIconManager(tmp_win);
1214	tmp_win->occupation = newoccupation;
1215	RemoveIconManager(tmp_win);
1216
1217	/* If it shouldn't be "here", vanish it */
1218	if(tmp_win->vs && !OCCUPY(tmp_win, tmp_win->vs->wsw->currentwspc)) {
1219		Vanish(tmp_win->vs, tmp_win);
1220	}
1221
1222	/*
1223	 * Try to find an(other) virtual screen which shows a workspace
1224	 * where the window has occupation, so that the window can be shown
1225	 * there now.
1226	 */
1227	if(!tmp_win->vs) {
1228		VirtualScreen *vs;
1229		for(vs = Scr->vScreenList; vs != NULL; vs = vs->next) {
1230			if(OCCUPY(tmp_win, vs->wsw->currentwspc)) {
1231				DisplayWin(vs, tmp_win);
1232				break;
1233			}
1234		}
1235	}
1236
1237	/*
1238	 * Loop over workspaces.  Find the first one that it used to be in.
1239	 * If it's not there anymore, take it out of the WindowRegion there
1240	 * (RWFR() silently returns if we're not using WindowRegion's), and
1241	 * add it the WindowRegion in the first WS it now occupies.
1242	 *
1243	 * XXX I'm not sure this is entirely sensible; it seems like just
1244	 * unconditionally Remove/Place'ing would have the same effect?
1245	 */
1246	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
1247		int mask = 1 << ws->number;
1248		if(oldoccupation & mask) {
1249			if(!(newoccupation & mask)) {
1250				int final_x, final_y;
1251				RemoveWindowFromRegion(tmp_win);
1252				if(PlaceWindowInRegion(tmp_win, &final_x, &final_y)) {
1253					XMoveWindow(dpy, tmp_win->frame, final_x, final_y);
1254				}
1255			}
1256			break;
1257		}
1258	}
1259
1260	/* Now set the WM_OCCUPATION property */
1261	{
1262		char *namelist;
1263		int  len;
1264		long eventMask;
1265
1266		eventMask = mask_out_event(tmp_win->w, PropertyChangeMask);
1267
1268		len = GetPropertyFromMask(newoccupation, &namelist);
1269		XChangeProperty(dpy, tmp_win->w, XA_WM_OCCUPATION, XA_STRING, 8,
1270		                PropModeReplace, (unsigned char *) namelist, len);
1271		free(namelist);
1272#ifdef EWMH
1273		EwmhSet_NET_WM_DESKTOP(tmp_win);
1274#endif
1275
1276		restore_mask(tmp_win->w, eventMask);
1277	}
1278
1279
1280	/*
1281	 * Handle showing it up in the workspace map in the appropriate
1282	 * places.
1283	 *
1284	 * Note that this whole block messes with the {new,old}occupation
1285	 * vars.  That's "safe" because they're no longer used for their
1286	 * original purposes, only for the WSmap changes, but it's still
1287	 * kinda fugly.  Change to local vars at the drop of a hat with later
1288	 * changes...
1289	 */
1290	if(!WMapWindowMayBeAdded(tmp_win)) {
1291		/* Not showing in the map, so pretend it's nowhere */
1292		newoccupation = 0;
1293	}
1294	if(Scr->workSpaceMgr.noshowoccupyall) {
1295		/*
1296		 * Don't show OccupyAll.  Note that this means both OccupyAll
1297		 * window, AND windows manually set to occupy everything.  We
1298		 * don't have to adjust newoccupation, because the above
1299		 * conditional would have caught it, so we only need to edit old.
1300		 */
1301		if(oldoccupation == fullOccupation) {
1302			oldoccupation = 0;
1303		}
1304	}
1305
1306	/* Flip the ones that need flipping */
1307	changedoccupation = oldoccupation ^ newoccupation;
1308	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
1309		int mask = 1 << ws->number;
1310		if(changedoccupation & mask) {
1311			if(newoccupation & mask) {
1312				/* Add to WS */
1313				WMapAddWindowToWorkspace(tmp_win, ws);
1314			}
1315			else {
1316				/*
1317				 * Remove from WS.  We have to take it out of saved focus
1318				 * if it were there.  Maybe there are other places we
1319				 * might need to remove it from (warpring?)?
1320				 */
1321				WMapRemoveWindowFromWorkspace(tmp_win, ws);
1322				if(Scr->SaveWorkspaceFocus && ws->save_focus == tmp_win) {
1323					ws->save_focus = NULL;
1324				}
1325			}
1326		}
1327	}
1328
1329
1330	/*
1331	 * If transients don't have their own occupation, find any transients
1332	 * of this window and move them with it.
1333	 */
1334	if(! Scr->TransientHasOccupation) {
1335		for(t = Scr->FirstWindow; t != NULL; t = t->next) {
1336			if(t != tmp_win &&
1337			                ((t->istransient && t->transientfor == tmp_win->w) ||
1338			                 t->group == tmp_win->w)) {
1339				ChangeOccupation(t, tmp_win->occupation);
1340			}
1341		}
1342	}
1343
1344	/* All done */
1345	return;
1346}
1347
1348
1349/*
1350 * There are various reasons you might not be able to change the
1351 * occupation of a window (either due to attributes of it, or the state
1352 * of your session/WM), so provide a function to check them all when we
1353 * try a change.
1354 *
1355 * Note that this is _not_ called from ChangeOccupation(); only from
1356 * other things that wrap it.  Since CO() gets called from states where
1357 * this would [falsely] fail, it would be a bad idea to put it there.
1358 */
1359static bool
1360CanChangeOccupation(TwmWindow **twm_winp)
1361{
1362	TwmWindow *twm_win;
1363
1364	/* No workspaces config'd?  Changing is nonsensical. */
1365	if(!Scr->workSpaceManagerActive) {
1366		return false;
1367	}
1368
1369	/*
1370	 * f.occupy window up?  Can't change in the middle of changing.
1371	 * Though if it's not mapped, still pull it up, else iconifying the
1372	 * occupy window breaks it forever.
1373	 */
1374	if(occupyWin != NULL && Scr->workSpaceMgr.occupyWindow->twm_win->mapped) {
1375		return false;
1376	}
1377
1378	/* XXX Can we jut do this in the init?  Check all callers. */
1379	twm_win = *twm_winp;
1380
1381	/* Don't change occupation of icon managers */
1382	if(twm_win->isiconmgr) {
1383		return false;
1384	}
1385
1386	/* XXX Should check iswspmgr here too? */
1387
1388	/*
1389	 * If transients don't have their own occupation, check
1390	 * transient/group bits.
1391	 */
1392	if(!Scr->TransientHasOccupation) {
1393		if(twm_win->istransient) {
1394			return false;
1395		}
1396		if(twm_win->group != (Window) 0 && twm_win->group != twm_win->w) {
1397			/*
1398			 * When trying to modify a group member window,
1399			 * operate on the group leader instead
1400			 * (and thereby on all group member windows as well).
1401			 * If we can't find the group leader, pretend it isn't set.
1402			 */
1403			twm_win = GetTwmWindow(twm_win->group);
1404			if(!twm_win) {
1405				return true;
1406			}
1407			*twm_winp = twm_win;
1408		}
1409	}
1410
1411	/* Sure, go ahead, change it */
1412	return true;
1413}
1414
1415
1416/*
1417 * Add a client name to a list determining which workspaces it will
1418 * occupy.  Used in handling the Occupy { } block in config file.
1419 */
1420bool
1421AddToClientsList(char *workspace, char *client)
1422{
1423	WorkSpace *ws;
1424
1425	/* "all" is a magic workspace value which makes it occupy anywhere */
1426	if(strcmp(workspace, "all") == 0) {
1427		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
1428			AddToList(&ws->clientlist, client, "");
1429		}
1430		return true;
1431	}
1432
1433	/* If prefixed with "ws:", strip the prefix and lookup by WS name */
1434	if(strncmp(workspace, "ws:", 3) == 0) {
1435		if((ws = GetWorkspace(workspace + 3)) != NULL) {
1436			AddToList(&ws->clientlist, client, "");
1437			return true;
1438		}
1439	}
1440
1441	/* Else find that named workspace and all this to it */
1442	if((ws = GetWorkspace(workspace)) != NULL) {
1443		AddToList(&ws->clientlist, client, "");
1444		return true;
1445	}
1446
1447	/* Couldn't figure where to put it */
1448	return false;
1449}
1450
1451
1452/*
1453 * Turn a ctwm.workspace resource string into an occupation mask.  n.b.;
1454 * this changes the 'res' arg in-place.
1455 */
1456static int
1457GetMaskFromResource(TwmWindow *win, char *res)
1458{
1459	WorkSpace *ws;
1460	int       mask;
1461	enum { O_SET, O_ADD, O_REM } mode;
1462	char *wrkSpcName, *tokst;
1463
1464	/*
1465	 * This can set the occupation to a specific set of workspaces ("ws1
1466	 * ws3"), add to the set it woudl have otherwise ("+ws1 ws3"), or
1467	 * remove from the set it would otherwise ("-ws1 ws3").  The +/-
1468	 * apply to the whole expression, not to the individual entries in
1469	 * it.  So first, figure out what we're doing.
1470	 */
1471	mode = O_SET;
1472	if(*res == '+') {
1473		mode = O_ADD;
1474		res++;
1475	}
1476	else if(*res == '-') {
1477		mode = O_REM;
1478		res++;
1479	}
1480
1481	/*
1482	 * Walk through the string adding the workspaces specified into the
1483	 * mask of what we're doing.
1484	 */
1485	mask = 0;
1486	for(wrkSpcName = strtok_r(res, " ", &tokst) ; wrkSpcName
1487	                ; wrkSpcName = strtok_r(NULL, " ", &tokst)) {
1488		if(strcmp(wrkSpcName, "all") == 0) {
1489			mask = fullOccupation;
1490			break;
1491		}
1492		if(strcmp(wrkSpcName, "current") == 0) {
1493			VirtualScreen *vs = Scr->currentvs;
1494			if(vs) {
1495				mask |= (1 << vs->wsw->currentwspc->number);
1496			}
1497			continue;
1498		}
1499
1500		ws = GetWorkspace(wrkSpcName);
1501		if(ws != NULL) {
1502			mask |= (1 << ws->number);
1503		}
1504		else {
1505			fprintf(stderr, "unknown workspace : %s\n", wrkSpcName);
1506		}
1507	}
1508
1509	/*
1510	 * And return that mask, with necessary alterations depending on +/-
1511	 * specified.
1512	 */
1513	switch(mode) {
1514		case O_SET:
1515			return (mask);
1516		case O_ADD:
1517			return (mask | win->occupation);
1518		case O_REM:
1519			return (win->occupation & ~mask);
1520	}
1521
1522	/* Can't get here */
1523	fprintf(stderr, "%s(): Unreachable.\n", __func__);
1524	return 0;
1525}
1526
1527
1528/*
1529 * Turns a \0-separated buffer of workspace names into an occupation
1530 * bitmask.
1531 */
1532unsigned int
1533GetMaskFromProperty(unsigned char *_prop, unsigned long len)
1534{
1535	char         wrkSpcName[256];
1536	WorkSpace    *ws;
1537	unsigned int mask;
1538	int          l;
1539	char         *prop;
1540
1541	mask = 0;
1542	l = 0;
1543	prop = (char *) _prop;
1544	while(l < len) {
1545		/* If you have WS names longer than 256 chars, that's just Too Bad */
1546		safe_strncpy(wrkSpcName, prop, 256);
1547		l    += strlen(prop) + 1;
1548		prop += strlen(prop) + 1;
1549		if(strcmp(wrkSpcName, "all") == 0) {
1550			mask = fullOccupation;
1551			break;
1552		}
1553
1554		ws = GetWorkspace(wrkSpcName);
1555		if(ws != NULL) {
1556			mask |= (1 << ws->number);
1557		}
1558		else {
1559			fprintf(stderr, "unknown workspace : %s\n", wrkSpcName);
1560		}
1561	}
1562
1563#if 0
1564	{
1565		char *dbs = mk_nullsep_string((char *)_prop, len);
1566		fprintf(stderr, "%s('%s') -> 0x%x\n", __func__, dbs, mask);
1567		free(dbs);
1568	}
1569#endif
1570
1571	return (mask);
1572}
1573
1574
1575/*
1576 * Turns an occupation mask into a \0-separated buffer (not really a
1577 * string) of the workspace names.
1578 */
1579int
1580GetPropertyFromMask(unsigned int mask, char **prop)
1581{
1582	WorkSpace *ws;
1583	int       len;
1584	char      *wss[MAXWORKSPACE];
1585	int       i;
1586
1587	/* If it's everything, just say 'all' */
1588	if(mask == fullOccupation) {
1589		*prop = strdup("all");
1590		return 3;
1591	}
1592
1593	/* Stash up pointers to all the labels for WSen it's in */
1594	memset(wss, 0, sizeof(wss));
1595	i = 0;
1596	len = 0;
1597	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
1598		if(mask & (1 << ws->number)) {
1599			wss[i++] = ws->label;
1600			len += strlen(ws->label) + 1;
1601		}
1602	}
1603
1604	/* Assemble them into \0-separated string */
1605	*prop = malloc(len);
1606	len = 0;
1607	for(i = 0 ; wss[i] != NULL ; i++) {
1608		strcpy((*prop + len), wss[i]);
1609		len += strlen(wss[i]) + 1; // Skip past \0
1610	}
1611
1612#if 0
1613	{
1614		char *dbs = mk_nullsep_string(*prop, len);
1615		fprintf(stderr, "%s(0x%x) -> %d:'%s'\n", __func__, mask, len, dbs);
1616		free(dbs);
1617	}
1618#endif
1619
1620	return len;
1621}
1622
1623
1624/*
1625 * Generate a printable variant of the null-separated strings we use for
1626 * stashing in XA_WM_OCCUPATION.  Used for debugging
1627 * Get{Property,Mask}From{Mask,Property}().
1628 */
1629#ifdef __GNUC__
1630# pragma GCC diagnostic push
1631# pragma GCC diagnostic ignored "-Wunused-function"
1632#endif
1633static char *
1634mk_nullsep_string(const char *prop, int len)
1635{
1636	char *dbs;
1637	int i, j;
1638
1639	/*
1640	 * '\0' => "\\0" means we need longer than input; *2 is overkill,
1641	 * but always sufficient, and it's cheap.
1642	 */
1643	dbs = malloc(len * 2);
1644	i = j = 0;
1645	while(i < len) {
1646		size_t slen = strlen(prop + i);
1647
1648		strcpy(dbs + j, (prop + i));
1649		i += slen + 1;
1650		strcpy(dbs + j + slen, "\\0");
1651		j += slen + 2;
1652	}
1653
1654	return dbs;
1655}
1656#ifdef __GNUC__
1657# pragma GCC diagnostic pop
1658#endif
1659