Home | History | Annotate | Line # | Download | only in dist
      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 
     38 static int GetMaskFromResource(TwmWindow *win, char *res);
     39 static char *mk_nullsep_string(const char *prop, int len);
     40 
     41 static bool CanChangeOccupation(TwmWindow **twm_winp);
     42 
     43 int 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  */
     51 TwmWindow *occupyWin = NULL;
     52 
     53 
     54 /* XXX Share with captive.c? */
     55 static 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  */
     79 void
     80 SetupOccupation(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  */
    282 void
    283 AddToWorkSpace(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  */
    308 void
    309 RemoveFromWorkSpace(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 */
    331 void
    332 ToggleOccupation(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 */
    355 void
    356 MoveToNextWorkSpace(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 */
    377 void
    378 MoveToNextWorkSpaceAndFollow(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 */
    393 void
    394 MoveToPrevWorkSpace(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 */
    421 void
    422 MoveToPrevWorkSpaceAndFollow(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  */
    441 void
    442 WmgrRedoOccupation(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 */
    465 void
    466 WMgrRemoveFromCurrentWorkSpace(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 */
    490 void
    491 WMgrAddToCurrentWorkSpaceAndWarp(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 */
    548 void
    549 OccupyAll(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 
    578 static ColorPair occupyButtoncp;
    579 
    580 static 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  */
    593 void
    594 CreateOccupyWindow(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  */
    873 void
    874 ResizeOccupyWindow(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  */
    953 void
    954 PaintOccupyWindow(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  */
    984 void
    985 OccupyHandleButtonEvent(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  */
   1072 void
   1073 Occupy(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  */
   1154 void
   1155 ChangeOccupation(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  */
   1350 static bool
   1351 CanChangeOccupation(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  */
   1411 bool
   1412 AddToClientsList(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  */
   1447 static int
   1448 GetMaskFromResource(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  */
   1523 unsigned int
   1524 GetMaskFromProperty(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  */
   1570 int
   1571 GetPropertyFromMask(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
   1624 static char *
   1625 mk_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