1/*
2 * Various workspace handling and utilities.
3 */
4
5#include "ctwm.h"
6
7#include <stdio.h>
8#include <string.h>
9#include <stdlib.h>
10
11#include <X11/Xatom.h>
12
13#include "animate.h"
14#include "clicktofocus.h"
15#include "ctwm_atoms.h"
16#include "drawing.h"
17#include "functions.h"
18#include "iconmgr.h"
19#include "image.h"
20#include "otp.h"
21#include "screen.h"
22#include "vscreen.h"
23#include "win_ops.h"
24#include "win_utils.h"
25#include "workspace_manager.h"
26#include "workspace_utils.h"
27
28#ifdef EWMH
29#  include "ewmh_atoms.h"
30#endif
31
32
33/*
34 * XXX I'm not sure this should be here; maybe it's more of a per-screen
35 * thing, and so should be in the Screen struct?
36 */
37bool useBackgroundInfo = false;
38
39
40/*
41 * Move the display (of a given vs) over to a new workspace.
42 */
43void
44GotoWorkSpace(VirtualScreen *vs, WorkSpace *ws)
45{
46	TwmWindow            *twmWin;
47	WorkSpace            *oldws, *newws;
48	WList                *wl, *wl1;
49	WinList              *winl;
50	XSetWindowAttributes attr;
51	long                 eventMask;
52	IconMgr              *iconmgr;
53	Window               oldw;
54	Window               neww;
55	TwmWindow            *focuswindow;
56	VirtualScreen        *tmpvs;
57
58	if(! Scr->workSpaceManagerActive) {
59		return;
60	}
61	for(tmpvs = Scr->vScreenList; tmpvs != NULL; tmpvs = tmpvs->next) {
62		if(ws == tmpvs->wsw->currentwspc) {
63			XBell(dpy, 0);
64			return;
65		}
66	}
67	oldws = vs->wsw->currentwspc;
68	newws = ws;
69	if(oldws == newws) {
70		return;
71	}
72
73	/* XXX X-ref CTAG_BGDRAW in CreateWorkSpaceManager() and below */
74	if(useBackgroundInfo && ! Scr->DontPaintRootWindow) {
75		if(newws->image == NULL) {
76			XSetWindowBackground(dpy, vs->window, newws->backcp.back);
77		}
78		else {
79			XSetWindowBackgroundPixmap(dpy, vs->window, newws->image->pixmap);
80		}
81		XClearWindow(dpy, vs->window);
82	}
83
84	/* If SaveWorkspaceFocus is on, save the focus of the last window. */
85	if(Scr->SaveWorkspaceFocus) {
86		oldws->save_focus = Scr->Focus;
87	}
88
89	focuswindow = NULL;
90	/* For better visual effect, the order or map/unmap is important:
91	   - map from top to bottom.
92	   - unmap from bottom to top.
93	   - unmap after mapping.
94	   The guiding factor: at any point during the transition, something
95	   should be visible only if it was visible before the transition or if
96	   it will be visible at the end.  */
97	OtpCheckConsistency();
98
99	for(twmWin = OtpTopWin(); twmWin != NULL;
100	                twmWin = OtpNextWinDown(twmWin)) {
101
102		if(OCCUPY(twmWin, newws)) {
103			if(!twmWin->vs) {
104				DisplayWin(vs, twmWin);
105			}
106#ifdef EWMH
107			if(OCCUPY(twmWin, oldws)) {
108				/*
109				 * If the window remains visible, re-order the workspace
110				 * numbers in NET_WM_DESKTOP.
111				 */
112				EwmhSet_NET_WM_DESKTOP_ws(twmWin, newws);
113			}
114#endif
115		}
116	}
117
118	for(twmWin = OtpBottomWin(); twmWin != NULL;
119	                twmWin = OtpNextWinUp(twmWin)) {
120		if(twmWin->vs == vs) {
121			if(!OCCUPY(twmWin, newws)) {
122
123				Vanish(vs, twmWin);
124#ifdef VSCREEN
125				/*
126				 * Now that the window has Vanished from one virtual screen,
127				 * check to see if it is wanted on another one.
128				 * This is relatively rare, so don't bother with the
129				 * top-to-bottom order here.
130				 */
131				if(Scr->numVscreens > 1) {
132					VirtualScreen *tvs;
133					for(tvs = Scr->vScreenList; tvs != NULL; tvs = tvs->next) {
134						if(tvs == vs) { /* no, not back on the old one */
135							continue;
136						}
137						if(OCCUPY(twmWin, tvs->wsw->currentwspc)) {
138							DisplayWin(tvs, twmWin);
139							break;
140						}
141					}
142				}
143#endif
144			}
145			else if(twmWin->hasfocusvisible) {
146				focuswindow = twmWin;
147				SetFocusVisualAttributes(focuswindow, false);
148			}
149		}
150	}
151	OtpCheckConsistency();
152
153	/*
154	   Reorganize icon manager window lists
155	*/
156	for(twmWin = Scr->FirstWindow; twmWin != NULL; twmWin = twmWin->next) {
157		wl = twmWin->iconmanagerlist;
158		if(wl == NULL) {
159			continue;
160		}
161		if(OCCUPY(wl->iconmgr->twm_win, newws)) {
162			continue;
163		}
164		wl1 = wl;
165		wl  = wl->nextv;
166		while(wl != NULL) {
167			if(OCCUPY(wl->iconmgr->twm_win, newws)) {
168				break;
169			}
170			wl1 = wl;
171			wl  = wl->nextv;
172		}
173		if(wl != NULL) {
174			wl1->nextv = wl->nextv;
175			wl->nextv  = twmWin->iconmanagerlist;
176			twmWin->iconmanagerlist = wl;
177		}
178	}
179	wl = NULL;
180	for(iconmgr = newws->iconmgr; iconmgr; iconmgr = iconmgr->next) {
181		if(iconmgr->first) {
182			wl = iconmgr->first;
183			break;
184		}
185	}
186	CurrentIconManagerEntry(wl);
187	if(focuswindow) {
188		SetFocusVisualAttributes(focuswindow, true);
189	}
190	vs->wsw->currentwspc = newws;
191	if(Scr->ReverseCurrentWorkspace && vs->wsw->state == WMS_map) {
192		MapSubwindow *msw = vs->wsw->mswl [oldws->number];
193		for(winl = msw->wl; winl != NULL; winl = winl->next) {
194			WMapRedrawName(vs, winl);
195		}
196		msw = vs->wsw->mswl [newws->number];
197		for(winl = msw->wl; winl != NULL; winl = winl->next) {
198			WMapRedrawName(vs, winl);
199		}
200	}
201	else if(vs->wsw->state == WMS_buttons) {
202		ButtonSubwindow *bsw = vs->wsw->bswl [oldws->number];
203		PaintWsButton(WSPCWINDOW, vs, bsw->w, oldws->label, oldws->cp, off);
204		bsw = vs->wsw->bswl [newws->number];
205		PaintWsButton(WSPCWINDOW, vs, bsw->w, newws->label, newws->cp,  on);
206	}
207	oldws->iconmgr = Scr->iconmgr;
208	Scr->iconmgr = newws->iconmgr;
209
210	/* XXX X-ref CTAG_BGDRAW in CreateWorkSpaceManager() and above */
211	oldw = vs->wsw->mswl [oldws->number]->w;
212	neww = vs->wsw->mswl [newws->number]->w;
213	if(useBackgroundInfo) {
214		if(oldws->image == NULL || Scr->NoImagesInWorkSpaceManager) {
215			XSetWindowBackground(dpy, oldw, oldws->backcp.back);
216		}
217		else {
218			XSetWindowBackgroundPixmap(dpy, oldw, oldws->image->pixmap);
219		}
220	}
221	else {
222		if(Scr->workSpaceMgr.defImage == NULL || Scr->NoImagesInWorkSpaceManager) {
223			XSetWindowBackground(dpy, oldw, Scr->workSpaceMgr.defColors.back);
224		}
225		else {
226			XSetWindowBackgroundPixmap(dpy, oldw, Scr->workSpaceMgr.defImage->pixmap);
227		}
228	}
229	attr.border_pixel = Scr->workSpaceMgr.defBorderColor;
230	XChangeWindowAttributes(dpy, oldw, CWBorderPixel, &attr);
231
232	if(Scr->workSpaceMgr.curImage == NULL) {
233		if(Scr->workSpaceMgr.curPaint) {
234			XSetWindowBackground(dpy, neww, Scr->workSpaceMgr.curColors.back);
235		}
236	}
237	else {
238		XSetWindowBackgroundPixmap(dpy, neww, Scr->workSpaceMgr.curImage->pixmap);
239	}
240	attr.border_pixel =  Scr->workSpaceMgr.curBorderColor;
241	XChangeWindowAttributes(dpy, neww, CWBorderPixel, &attr);
242
243	XClearWindow(dpy, oldw);
244	XClearWindow(dpy, neww);
245
246	eventMask = mask_out_event(Scr->Root, PropertyChangeMask);
247
248	XChangeProperty(dpy, Scr->Root, XA_WM_CURRENTWORKSPACE, XA_STRING, 8,
249	                PropModeReplace, (unsigned char *) newws->name, strlen(newws->name));
250#ifdef EWMH
251	{
252		long number = newws->number;
253		/*
254		 * TODO: this should probably not use Scr->Root but ->XineramaRoot.
255		 * That is the real root window if we're using virtual screens.
256		 * Also, on the real root it would need values for each of the
257		 * virtual roots, but that doesn't fit in the EWMH ideas.
258		 */
259		XChangeProperty(dpy, Scr->Root, XA__NET_CURRENT_DESKTOP,
260		                XA_CARDINAL, 32,
261		                PropModeReplace, (unsigned char *) &number, 1);
262	}
263#endif /* EWMH */
264
265	restore_mask(Scr->Root, eventMask);
266
267	/*    XDestroyWindow (dpy, cachew);*/
268	if(Scr->ChangeWorkspaceFunction.func != 0) {
269		char *action;
270		XEvent event;
271
272		action = Scr->ChangeWorkspaceFunction.item ?
273		         Scr->ChangeWorkspaceFunction.item->action : NULL;
274		ExecuteFunction(Scr->ChangeWorkspaceFunction.func, action,
275		                (Window) 0, NULL, &event, C_ROOT, false);
276	}
277
278	/* If SaveWorkspaceFocus is on, try to restore the focus to the last
279	   window which was focused when we left this workspace. */
280	if(Scr->SaveWorkspaceFocus && newws->save_focus) {
281		twmWin = newws->save_focus;
282		if(OCCUPY(twmWin, newws)) {     /* check should not even be needed anymore */
283			WarpToWindow(twmWin, false);
284		}
285		else {
286			newws->save_focus = NULL;
287		}
288	}
289
290	/* keep track of the order of the workspaces across restarts */
291	CtwmSetVScreenMap(dpy, Scr->Root, Scr->vScreenList);
292
293	XSync(dpy, 0);
294	if(Scr->ClickToFocus || Scr->SloppyFocus) {
295		set_last_window(newws);
296	}
297	MaybeAnimate = true;
298}
299
300
301
302/*
303 * Various frontends to GotoWorkSpace()
304 */
305
306/*
307 * Simplify redundant checks.  If no multiple workspaces, or no vs given
308 * to the func, there's nothing to do.
309 */
310#define GWS_CHECK do { \
311                if(! Scr->workSpaceManagerActive) {   \
312                        return;                       \
313                }                                     \
314                if(!vs) {                             \
315                        return;                       \
316                }                                     \
317        } while(0)
318
319void
320GotoWorkSpaceByName(VirtualScreen *vs, const char *wname)
321{
322	WorkSpace *ws;
323
324	GWS_CHECK;
325
326	ws = GetWorkspace(wname);
327	if(ws == NULL) {
328		return;
329	}
330	GotoWorkSpace(vs, ws);
331}
332
333
334void
335GotoWorkSpaceByNumber(VirtualScreen *vs, int workspacenum)
336{
337	WorkSpace *ws;
338
339	GWS_CHECK;
340
341	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
342		if(ws->number == workspacenum) {
343			break;
344		}
345	}
346	if(ws == NULL) {
347		return;
348	}
349	GotoWorkSpace(vs, ws);
350}
351
352
353void
354GotoPrevWorkSpace(VirtualScreen *vs)
355{
356	WorkSpace *ws1, *ws2;
357
358	GWS_CHECK;
359
360	ws1 = Scr->workSpaceMgr.workSpaceList;
361	if(ws1 == NULL) {
362		return;
363	}
364	ws2 = ws1->next;
365
366	while((ws2 != vs->wsw->currentwspc) && (ws2 != NULL)) {
367		ws1 = ws2;
368		ws2 = ws2->next;
369	}
370	GotoWorkSpace(vs, ws1);
371}
372
373
374void
375GotoNextWorkSpace(VirtualScreen *vs)
376{
377	WorkSpace *ws;
378
379	GWS_CHECK;
380
381	ws = vs->wsw->currentwspc;
382	ws = (ws->next != NULL) ? ws->next : Scr->workSpaceMgr.workSpaceList;
383	GotoWorkSpace(vs, ws);
384}
385
386
387void
388GotoRightWorkSpace(VirtualScreen *vs)
389{
390	WorkSpace *ws;
391	int number, columns, count;
392
393	GWS_CHECK;
394
395	ws      = vs->wsw->currentwspc;
396	number  = ws->number;
397	columns = Scr->workSpaceMgr.columns;
398	count   = Scr->workSpaceMgr.count;
399	number++;
400	if((number % columns) == 0) {
401		number -= columns;
402	}
403	else if(number >= count) {
404		number = (number / columns) * columns;
405	}
406
407	GotoWorkSpaceByNumber(vs, number);
408}
409
410
411void
412GotoLeftWorkSpace(VirtualScreen *vs)
413{
414	WorkSpace *ws;
415	int number, columns, count;
416
417	GWS_CHECK;
418
419	ws      = vs->wsw->currentwspc;
420	number  = ws->number;
421	columns = Scr->workSpaceMgr.columns;
422	count   = Scr->workSpaceMgr.count;
423	number += (number % columns) ? -1 : (columns - 1);
424	if(number >= count) {
425		number = count - 1;
426	}
427	GotoWorkSpaceByNumber(vs, number);
428}
429
430
431void
432GotoUpWorkSpace(VirtualScreen *vs)
433{
434	WorkSpace *ws;
435	int number, lines, columns, count;
436
437	GWS_CHECK;
438
439	ws      = vs->wsw->currentwspc;
440	number  = ws->number;
441	lines   = Scr->workSpaceMgr.lines;
442	columns = Scr->workSpaceMgr.columns;
443	count   = Scr->workSpaceMgr.count;
444	number -=  columns;
445	if(number < 0) {
446		number += lines * columns;
447		/* If the number of workspaces is not a multiple of nr of columns */
448		if(number >= count) {
449			number -= columns;
450		}
451	}
452	GotoWorkSpaceByNumber(vs, number);
453}
454
455
456void
457GotoDownWorkSpace(VirtualScreen *vs)
458{
459	WorkSpace *ws;
460	int number, columns, count;
461
462	GWS_CHECK;
463
464	ws      = vs->wsw->currentwspc;
465	number  = ws->number;
466	columns = Scr->workSpaceMgr.columns;
467	count   = Scr->workSpaceMgr.count;
468	number +=  columns;
469	if(number >= count) {
470		number %= columns;
471	}
472	GotoWorkSpaceByNumber(vs, number);
473}
474
475#undef GWS_CHECK
476
477
478
479/*
480 * Show the background (by hiding all windows) or undo it.
481 * f.showbackground, also can be called via EWMH bits.
482 *
483 * newstate is the desired showing state.
484 * Pass -1 to toggle, 1 to show the background,
485 * or 0 to re-show the windows.
486 *
487 * XXX Doesn't really belong here; more of a functions.c-ish thing
488 * probably.  But left here for the moment.
489 */
490void
491ShowBackground(VirtualScreen *vs, int newstate)
492{
493	static int state = 0;
494	TwmWindow *twmWin;
495
496	if(newstate == state) {
497		return;
498	}
499
500	if(state) {
501		for(twmWin = Scr->FirstWindow; twmWin != NULL; twmWin = twmWin->next) {
502			if(twmWin->savevs == vs) {
503				DisplayWin(vs, twmWin);
504			}
505			twmWin->savevs = NULL;
506		}
507		state = 0;
508	}
509	else {
510		for(twmWin = Scr->FirstWindow; twmWin != NULL; twmWin = twmWin->next) {
511			if(twmWin->vs == vs
512#ifdef EWMH
513			                /* leave wt_Desktop and wt_Dock visible */
514			                && twmWin->ewmhWindowType == wt_Normal
515#endif /* EWMH */
516			  ) {
517				twmWin->savevs = twmWin->vs;
518				Vanish(vs, twmWin);
519			}
520		}
521		state = 1;
522	}
523#ifdef EWMH
524	EwmhSet_NET_SHOWING_DESKTOP(state);
525#endif /* EWMH */
526}
527
528
529/*
530 * Get the name of the currently active WS.  Used in Execute() for
531 * sub'ing in $currentworkspace in executing commands.
532 */
533char *
534GetCurrentWorkSpaceName(VirtualScreen *vs)
535{
536	if(! Scr->workSpaceManagerActive) {
537		return (NULL);
538	}
539	if(!vs) {
540		vs = Scr->vScreenList;
541	}
542	return vs->wsw->currentwspc->name;
543}
544
545
546/*
547 * Find workspace by name
548 */
549WorkSpace *
550GetWorkspace(const char *wname)
551{
552	WorkSpace *ws;
553
554	/* Guard */
555	if(!wname) {
556		return (NULL);
557	}
558
559	/* Check by label */
560	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
561		if(strcmp(ws->label, wname) == 0) {
562			return ws;
563		}
564	}
565
566	/* Check by name */
567	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
568		if(strcmp(ws->name, wname) == 0) {
569			return ws;
570		}
571	}
572
573	/* Nope */
574	return NULL;
575}
576