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