workspace_utils.c revision 0bbfda8a
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				VirtualScreen *tvs;
123
124				Vanish(vs, twmWin);
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					for(tvs = Scr->vScreenList; tvs != NULL; tvs = tvs->next) {
133						if(tvs == vs) { /* no, not back on the old one */
134							continue;
135						}
136						if(OCCUPY(twmWin, tvs->wsw->currentwspc)) {
137							DisplayWin(tvs, twmWin);
138							break;
139						}
140					}
141				}
142			}
143			else if(twmWin->hasfocusvisible) {
144				focuswindow = twmWin;
145				SetFocusVisualAttributes(focuswindow, false);
146			}
147		}
148	}
149	OtpCheckConsistency();
150
151	/*
152	   Reorganize icon manager window lists
153	*/
154	for(twmWin = Scr->FirstWindow; twmWin != NULL; twmWin = twmWin->next) {
155		wl = twmWin->iconmanagerlist;
156		if(wl == NULL) {
157			continue;
158		}
159		if(OCCUPY(wl->iconmgr->twm_win, newws)) {
160			continue;
161		}
162		wl1 = wl;
163		wl  = wl->nextv;
164		while(wl != NULL) {
165			if(OCCUPY(wl->iconmgr->twm_win, newws)) {
166				break;
167			}
168			wl1 = wl;
169			wl  = wl->nextv;
170		}
171		if(wl != NULL) {
172			wl1->nextv = wl->nextv;
173			wl->nextv  = twmWin->iconmanagerlist;
174			twmWin->iconmanagerlist = wl;
175		}
176	}
177	wl = NULL;
178	for(iconmgr = newws->iconmgr; iconmgr; iconmgr = iconmgr->next) {
179		if(iconmgr->first) {
180			wl = iconmgr->first;
181			break;
182		}
183	}
184	CurrentIconManagerEntry(wl);
185	if(focuswindow) {
186		SetFocusVisualAttributes(focuswindow, true);
187	}
188	vs->wsw->currentwspc = newws;
189	if(Scr->ReverseCurrentWorkspace && vs->wsw->state == WMS_map) {
190		MapSubwindow *msw = vs->wsw->mswl [oldws->number];
191		for(winl = msw->wl; winl != NULL; winl = winl->next) {
192			WMapRedrawName(vs, winl);
193		}
194		msw = vs->wsw->mswl [newws->number];
195		for(winl = msw->wl; winl != NULL; winl = winl->next) {
196			WMapRedrawName(vs, winl);
197		}
198	}
199	else if(vs->wsw->state == WMS_buttons) {
200		ButtonSubwindow *bsw = vs->wsw->bswl [oldws->number];
201		PaintWsButton(WSPCWINDOW, vs, bsw->w, oldws->label, oldws->cp, off);
202		bsw = vs->wsw->bswl [newws->number];
203		PaintWsButton(WSPCWINDOW, vs, bsw->w, newws->label, newws->cp,  on);
204	}
205	oldws->iconmgr = Scr->iconmgr;
206	Scr->iconmgr = newws->iconmgr;
207
208	/* XXX X-ref CTAG_BGDRAW in CreateWorkSpaceManager() and above */
209	oldw = vs->wsw->mswl [oldws->number]->w;
210	neww = vs->wsw->mswl [newws->number]->w;
211	if(useBackgroundInfo) {
212		if(oldws->image == NULL || Scr->NoImagesInWorkSpaceManager) {
213			XSetWindowBackground(dpy, oldw, oldws->backcp.back);
214		}
215		else {
216			XSetWindowBackgroundPixmap(dpy, oldw, oldws->image->pixmap);
217		}
218	}
219	else {
220		if(Scr->workSpaceMgr.defImage == NULL || Scr->NoImagesInWorkSpaceManager) {
221			XSetWindowBackground(dpy, oldw, Scr->workSpaceMgr.defColors.back);
222		}
223		else {
224			XSetWindowBackgroundPixmap(dpy, oldw, Scr->workSpaceMgr.defImage->pixmap);
225		}
226	}
227	attr.border_pixel = Scr->workSpaceMgr.defBorderColor;
228	XChangeWindowAttributes(dpy, oldw, CWBorderPixel, &attr);
229
230	if(Scr->workSpaceMgr.curImage == NULL) {
231		if(Scr->workSpaceMgr.curPaint) {
232			XSetWindowBackground(dpy, neww, Scr->workSpaceMgr.curColors.back);
233		}
234	}
235	else {
236		XSetWindowBackgroundPixmap(dpy, neww, Scr->workSpaceMgr.curImage->pixmap);
237	}
238	attr.border_pixel =  Scr->workSpaceMgr.curBorderColor;
239	XChangeWindowAttributes(dpy, neww, CWBorderPixel, &attr);
240
241	XClearWindow(dpy, oldw);
242	XClearWindow(dpy, neww);
243
244	eventMask = mask_out_event(Scr->Root, PropertyChangeMask);
245
246	XChangeProperty(dpy, Scr->Root, XA_WM_CURRENTWORKSPACE, XA_STRING, 8,
247	                PropModeReplace, (unsigned char *) newws->name, strlen(newws->name));
248#ifdef EWMH
249	{
250		long number = newws->number;
251		/*
252		 * TODO: this should probably not use Scr->Root but ->XineramaRoot.
253		 * That is the real root window if we're using virtual screens.
254		 * Also, on the real root it would need values for each of the
255		 * virtual roots, but that doesn't fit in the EWMH ideas.
256		 */
257		XChangeProperty(dpy, Scr->Root, XA__NET_CURRENT_DESKTOP,
258		                XA_CARDINAL, 32,
259		                PropModeReplace, (unsigned char *) &number, 1);
260	}
261#endif /* EWMH */
262
263	restore_mask(Scr->Root, eventMask);
264
265	/*    XDestroyWindow (dpy, cachew);*/
266	if(Scr->ChangeWorkspaceFunction.func != 0) {
267		char *action;
268		XEvent event;
269
270		action = Scr->ChangeWorkspaceFunction.item ?
271		         Scr->ChangeWorkspaceFunction.item->action : NULL;
272		ExecuteFunction(Scr->ChangeWorkspaceFunction.func, action,
273		                (Window) 0, NULL, &event, C_ROOT, false);
274	}
275
276	/* If SaveWorkspaceFocus is on, try to restore the focus to the last
277	   window which was focused when we left this workspace. */
278	if(Scr->SaveWorkspaceFocus && newws->save_focus) {
279		twmWin = newws->save_focus;
280		if(OCCUPY(twmWin, newws)) {     /* check should not even be needed anymore */
281			WarpToWindow(twmWin, false);
282		}
283		else {
284			newws->save_focus = NULL;
285		}
286	}
287
288	/* keep track of the order of the workspaces across restarts */
289	CtwmSetVScreenMap(dpy, Scr->Root, Scr->vScreenList);
290
291	XSync(dpy, 0);
292	if(Scr->ClickToFocus || Scr->SloppyFocus) {
293		set_last_window(newws);
294	}
295	MaybeAnimate = true;
296}
297
298
299
300/*
301 * Various frontends to GotoWorkSpace()
302 */
303
304/*
305 * Simplify redundant checks.  If no multiple workspaces, or no vs given
306 * to the func, there's nothing to do.
307 */
308#define GWS_CHECK do { \
309                if(! Scr->workSpaceManagerActive) {   \
310                        return;                       \
311                }                                     \
312                if(!vs) {                             \
313                        return;                       \
314                }                                     \
315        } while(0)
316
317void
318GotoWorkSpaceByName(VirtualScreen *vs, const char *wname)
319{
320	WorkSpace *ws;
321
322	GWS_CHECK;
323
324	ws = GetWorkspace(wname);
325	if(ws == NULL) {
326		return;
327	}
328	GotoWorkSpace(vs, ws);
329}
330
331
332void
333GotoWorkSpaceByNumber(VirtualScreen *vs, int workspacenum)
334{
335	WorkSpace *ws;
336
337	GWS_CHECK;
338
339	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
340		if(ws->number == workspacenum) {
341			break;
342		}
343	}
344	if(ws == NULL) {
345		return;
346	}
347	GotoWorkSpace(vs, ws);
348}
349
350
351void
352GotoPrevWorkSpace(VirtualScreen *vs)
353{
354	WorkSpace *ws1, *ws2;
355
356	GWS_CHECK;
357
358	ws1 = Scr->workSpaceMgr.workSpaceList;
359	if(ws1 == NULL) {
360		return;
361	}
362	ws2 = ws1->next;
363
364	while((ws2 != vs->wsw->currentwspc) && (ws2 != NULL)) {
365		ws1 = ws2;
366		ws2 = ws2->next;
367	}
368	GotoWorkSpace(vs, ws1);
369}
370
371
372void
373GotoNextWorkSpace(VirtualScreen *vs)
374{
375	WorkSpace *ws;
376
377	GWS_CHECK;
378
379	ws = vs->wsw->currentwspc;
380	ws = (ws->next != NULL) ? ws->next : Scr->workSpaceMgr.workSpaceList;
381	GotoWorkSpace(vs, ws);
382}
383
384
385void
386GotoRightWorkSpace(VirtualScreen *vs)
387{
388	WorkSpace *ws;
389	int number, columns, count;
390
391	GWS_CHECK;
392
393	ws      = vs->wsw->currentwspc;
394	number  = ws->number;
395	columns = Scr->workSpaceMgr.columns;
396	count   = Scr->workSpaceMgr.count;
397	number++;
398	if((number % columns) == 0) {
399		number -= columns;
400	}
401	else if(number >= count) {
402		number = (number / columns) * columns;
403	}
404
405	GotoWorkSpaceByNumber(vs, number);
406}
407
408
409void
410GotoLeftWorkSpace(VirtualScreen *vs)
411{
412	WorkSpace *ws;
413	int number, columns, count;
414
415	GWS_CHECK;
416
417	ws      = vs->wsw->currentwspc;
418	number  = ws->number;
419	columns = Scr->workSpaceMgr.columns;
420	count   = Scr->workSpaceMgr.count;
421	number += (number % columns) ? -1 : (columns - 1);
422	if(number >= count) {
423		number = count - 1;
424	}
425	GotoWorkSpaceByNumber(vs, number);
426}
427
428
429void
430GotoUpWorkSpace(VirtualScreen *vs)
431{
432	WorkSpace *ws;
433	int number, lines, columns, count;
434
435	GWS_CHECK;
436
437	ws      = vs->wsw->currentwspc;
438	number  = ws->number;
439	lines   = Scr->workSpaceMgr.lines;
440	columns = Scr->workSpaceMgr.columns;
441	count   = Scr->workSpaceMgr.count;
442	number -=  columns;
443	if(number < 0) {
444		number += lines * columns;
445		/* If the number of workspaces is not a multiple of nr of columns */
446		if(number >= count) {
447			number -= columns;
448		}
449	}
450	GotoWorkSpaceByNumber(vs, number);
451}
452
453
454void
455GotoDownWorkSpace(VirtualScreen *vs)
456{
457	WorkSpace *ws;
458	int number, columns, count;
459
460	GWS_CHECK;
461
462	ws      = vs->wsw->currentwspc;
463	number  = ws->number;
464	columns = Scr->workSpaceMgr.columns;
465	count   = Scr->workSpaceMgr.count;
466	number +=  columns;
467	if(number >= count) {
468		number %= columns;
469	}
470	GotoWorkSpaceByNumber(vs, number);
471}
472
473#undef GWS_CHECK
474
475
476
477/*
478 * Show the background (by hiding all windows) or undo it.
479 * f.showbackground, also can be called via EWMH bits.
480 *
481 * newstate is the desired showing state.
482 * Pass -1 to toggle, 1 to show the background,
483 * or 0 to re-show the windows.
484 *
485 * XXX Doesn't really belong here; more of a functions.c-ish thing
486 * probably.  But left here for the moment.
487 */
488void
489ShowBackground(VirtualScreen *vs, int newstate)
490{
491	static int state = 0;
492	TwmWindow *twmWin;
493
494	if(newstate == state) {
495		return;
496	}
497
498	if(state) {
499		for(twmWin = Scr->FirstWindow; twmWin != NULL; twmWin = twmWin->next) {
500			if(twmWin->savevs == vs) {
501				DisplayWin(vs, twmWin);
502			}
503			twmWin->savevs = NULL;
504		}
505		state = 0;
506	}
507	else {
508		for(twmWin = Scr->FirstWindow; twmWin != NULL; twmWin = twmWin->next) {
509			if(twmWin->vs == vs
510#ifdef EWMH
511			                /* leave wt_Desktop and wt_Dock visible */
512			                && twmWin->ewmhWindowType == wt_Normal
513#endif /* EWMH */
514			  ) {
515				twmWin->savevs = twmWin->vs;
516				Vanish(vs, twmWin);
517			}
518		}
519		state = 1;
520	}
521#ifdef EWMH
522	EwmhSet_NET_SHOWING_DESKTOP(state);
523#endif /* EWMH */
524}
525
526
527/*
528 * Get the name of the currently active WS.  Used in Execute() for
529 * sub'ing in $currentworkspace in executing commands.
530 */
531char *
532GetCurrentWorkSpaceName(VirtualScreen *vs)
533{
534	if(! Scr->workSpaceManagerActive) {
535		return (NULL);
536	}
537	if(!vs) {
538		vs = Scr->vScreenList;
539	}
540	return vs->wsw->currentwspc->name;
541}
542
543
544/*
545 * Find workspace by name
546 */
547WorkSpace *
548GetWorkspace(const char *wname)
549{
550	WorkSpace *ws;
551
552	/* Guard */
553	if(!wname) {
554		return (NULL);
555	}
556
557	/* Check by label */
558	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
559		if(strcmp(ws->label, wname) == 0) {
560			return ws;
561		}
562	}
563
564	/* Check by name */
565	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
566		if(strcmp(ws->name, wname) == 0) {
567			return ws;
568		}
569	}
570
571	/* Nope */
572	return NULL;
573}
574