1 /*
2  * Stolen from XPenguins (http://xpenguins.seul.org/)
3  *
4  * Some coding style/indentation changes.
5  *
6  * Copyright (C) 2003 Daniel Caujolle-Bert <segfault@club-internet.fr>
7  *
8  */
9 
10 /* toon_root.c - finding the correct background window / virtual root
11  * Copyright (C) 1999-2001  Robin Hogan
12  *
13  *  This program is free software; you can redistribute it and/or modify
14  *  it under the terms of the GNU General Public License as published by
15  *  the Free Software Foundation; either version 2 of the License, or
16  *  (at your option) any later version.
17  *
18  *  This program is distributed in the hope that it will be useful,
19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *  GNU General Public License for more details.
22  *
23  *  You should have received a copy of the GNU General Public License
24  *  along with this program; if not, write to the Free Software
25  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
26  */
27 
28 /* Since xpenguins version 2.1, the ToonGetRootWindow() function
29  * attempts to find the window IDs of
30  *
31  * 1) The background window that is behind the toplevel client
32  *    windows; this is the window that we draw the toons on.
33  *
34  * 2) The parent window of the toplevel client windows; this is used
35  *    by ToonLocateWindows() to build up a map of the space that the
36  *    toons can occupy.
37  *
38  * In simple (sensible?) window managers (e.g. blackbox, sawfish, fvwm
39  * and countless others), both of these are the root window. The other
40  * more complex scenarios that ToonGetRootWindow() attempts to cope
41  * with are:
42  *
43  * Some `virtual' window managers (e.g. amiwm, swm and tvtwm) that
44  * reparent all client windows to a desktop window that sits on top of
45  * the root window. This desktop window is easy to find - we just look
46  * for a property __SWM_VROOT in the immediate children of the root
47  * window that contains the window ID of this desktop window. The
48  * desktop plays both roles (1 and 2 above). This functionality was
49  * detected in xpenguins 1.x with the vroot.h header file.
50  *
51  * Enlightenment (0.16) can have a number of desktops with different
52  * backgrounds; client windows on these are reparented, except for
53  * Desktop 0 which is the root window. Therefore versions less than
54  * 2.1 of xpenguins worked on Desktop 0 but not on any others. To fix
55  * this we look for a root-window property _WIN_WORKSPACE which
56  * contains the numerical index of the currently active desktop. The
57  * active desktop is then simply the immediate child of the root
58  * window that has a property ENLIGHTENMENT_DESKTOP set to this value.
59  *
60  * KDE 2.0: Oh dear. The kdesktop is a program separate from the
61  * window manager that launches a window which sits behind all the
62  * other client windows and has all the icons on it. Thus the other
63  * client windows are still children of the root window, but we want
64  * to draw to the uppermost window of the kdesktop. This is difficult
65  * to find - it is the great-great-grandchild of the root window and
66  * in KDE 2.0 has nothing to identify it from its siblings other than
67  * its size. KDE 2.1+ usefully implements the __SWM_VROOT property in
68  * a child of the root window, but the client windows are still
69  * children of the root window. A problem is that the penguins erase
70  * the desktop icons when they walk which is a bit messy. The icons
71  * are not lost - they reappear when the desktop window gets an expose
72  * event (i.e. move some windows over where they were and back again).
73  *
74  * Nautilus (GNOME 1.4+): Creates a background window to draw icons
75  * on, but does not reparent the client windows. The toplevel window
76  * of the desktop is indicated by the root window property
77  * NAUTILUS_DESKTOP_WINDOW_ID, but then we must descend down the tree
78  * from this toplevel window looking for subwindows that are the same
79  * size as the screen. The bottom one is the one to draw to. Hopefully
80  * one day Nautilus will implement __SWM_VROOT in exactly the same way
81  * as KDE 2.1+.
82  *
83  * Other cases: CDE, the common desktop environment. This is a
84  * commercial product that has been packaged with Sun (and other)
85  * workstations. It typically implements four virtual desktops but
86  * provides NO properties at all for apps such as xpenguins to use to
87  * work out where to draw to. Seeing as Sun are moving over to GNOME,
88  * CDE use is on the decline so I don't have any current plans to try
89  * and get xpenguins to work with it.
90  *
91  * As a note to developers of window managers and big screen hoggers
92  * like kdesktop, please visit www.freedesktop.org and implement their
93  * Extended Window Manager Hints spec that help pagers and apps like
94  * xpenguins and xearth to find their way around. In particular,
95  * please use the _NET_CURRENT_DESKTOP and _NET_VIRTUAL_ROOTS
96  * properties if you reparent any windows (e.g. Enlightenment). Since
97  * no window managers that I know yet use these particular hints, I
98  * haven't yet added any code to parse them.  */
99 
100 #include <stdio.h>
101 #include <string.h>
102 #include <X11/Xlib.h>
103 #include <X11/Xatom.h>
104 #include <X11/Xproto.h>
105 
106 #include "_xitk.h"
107 
108 /* Time to throw up. Here is a kludgey function that recursively calls
109  * itself (up to a limit) to find the window ID of the KDE desktop to
110  * draw on. It works with KDE 2.0, but since KDE 2.0 is less stable
111  * than Windows 95, I don't expect many people to remain using it now
112  * that 2.1 is available, which implements __SWM_VROOT and makes this
113  * function redundant. This is the hierarchy we're trying to traverse:
114  *
115  * -> The root window
116  * 0 -> window with name="KDE Desktop"
117  * 1   -> window with no name
118  * 2     -> window with name="KDE Desktop" & _NET_WM_WINDOW_TYPE_DESKTOP
119  * 3       -> window with no name and width >= width of screen
120  *
121  * The last window in the hierarchy is the one to draw to.  The
122  * numbers show the value of the `depth' argument.  */
__GetKDEDesktop(Display * display,int screen,Window window,Atom atom,const char * atomname,int depth)123 static Window __GetKDEDesktop(Display *display, int screen,
124                               Window window, Atom atom,
125                               const char *atomname, int depth) {
126   char         *name = NULL;
127   Atom         *wintype = NULL;
128   Window        winreturn = 0;
129   unsigned long nitems, bytesafter;
130   Atom          actual_type;
131   int           actual_format;
132   Window        rootReturn, parentReturn, *children;
133   unsigned int  nChildren;
134   char          go_deeper = 0;
135 
136   if (XFetchName(display, window, &name)) {
137     if (strcasecmp(name, "KDE Desktop") == 0) {
138       /* Presumably either at depth 0 or 2 */
139       if ((XGetWindowProperty(display, window, atom, 0, 1,
140 			      False, XA_ATOM, &actual_type, &actual_format,
141 			      &nitems, &bytesafter,
142 			      (unsigned char **) &wintype)) == Success && wintype) {
143 	char *tmpatomname = XGetAtomName(display, *wintype);
144 
145 	if (tmpatomname) {
146 	  if ((strcmp(atomname, tmpatomname) == 0) && depth == 2) {
147 	    /* OK, at depth 2 */
148 	    go_deeper = 1;
149 	  }
150           XFree(tmpatomname);
151 	}
152       }
153       else if (depth < 2) {
154 	go_deeper = 1;
155       }
156     }
157     else if (depth == 1) {
158       go_deeper = 1;
159     }
160     XFree((char *) name);
161   }
162   else if (depth == 1) {
163     go_deeper = 1;
164   }
165 
166   /* If go_deeper is 1 then there is a possibility that the background
167    * window is a descendant of the current window; otherwise we're
168    * barking up the wrong tree. */
169   if (go_deeper && (XQueryTree(display, window,
170 			       &rootReturn, &parentReturn, &children, &nChildren))) {
171     unsigned int i;
172 
173     for (i = 0; i < nChildren; ++i) {
174       /* children[i] is now at depth 3 */
175       if (depth == 2) {
176 	XWindowAttributes attributes;
177 
178 	if (XGetWindowAttributes(display, children[i], &attributes)) {
179 	  if ((attributes.width >= (DisplayWidth(display, screen)/2)) && attributes.height > 0) {
180 	    /* Found it! */
181 	    winreturn = children[i];
182 	    break;
183 	  }
184 	}
185       }
186       else if ((winreturn = __GetKDEDesktop(display, screen,
187 					    children[i], atom, atomname, depth+1))) {
188 	break;
189       }
190     }
191     XFree((char *) children);
192   }
193 
194   return winreturn;
195 }
196 
197 /* Look for the Nautilus desktop window to draw to, given the toplevel
198  * window of the Nautilus desktop. Basically recursively calls itself
199  * looking for subwindows the same size as the root window. */
__GetNautilusDesktop(Display * display,int screen,Window window,int depth)200 static Window __GetNautilusDesktop(Display *display, int screen, Window window, int depth) {
201   Window       rootReturn, parentReturn, *children;
202   Window       winreturn = window;
203   unsigned int nChildren;
204 
205   if (depth > 5) {
206     return ((Window) 0);
207   }
208   else if (XQueryTree(display, window, &rootReturn, &parentReturn, &children, &nChildren)) {
209     unsigned int i;
210 
211     for (i = 0; i < nChildren; ++i) {
212       XWindowAttributes attributes;
213 
214       if (XGetWindowAttributes(display, children[i], &attributes)) {
215 	if ((attributes.width == DisplayWidth(display, screen)) &&
216 	    (attributes.height == DisplayHeight(display, screen))) {
217 	  /* Found a possible desktop window */
218 	  winreturn = __GetNautilusDesktop(display, screen, children[i], depth+1);
219 	}
220       }
221     }
222     XFree((char *) children);
223   }
224   return winreturn;
225 }
226 
227 
228 /*
229  * Returns the window ID of the `background' window on to which the
230  * toons should be drawn. Also returned (in clientparent) is the ID of
231  * the parent of all the client windows, since this may not be the
232  * same as the background window. If no recognised virtual window
233  * manager or desktop environment is found then the root window is
234  * returned in both cases. The string toon_message contains
235  * information about the window manager that was found.
236  */
xitk_get_desktop_root_window(Display * display,int screen,Window * clientparent)237 Window xitk_get_desktop_root_window(Display *display, int screen, Window *clientparent) {
238   Window         background = None; /* The return value */
239   Window         root = RootWindow(display, screen);
240   Window         rootReturn, parentReturn, *children;
241   Window        *toplevel = (Window *) 0;
242   unsigned int   nChildren;
243   unsigned long  nitems, bytesafter;
244   Atom           actual_type;
245   int            actual_format;
246   unsigned long *workspace = NULL;
247   unsigned long *desktop = NULL;
248   Atom           NAUTILUS_DESKTOP_WINDOW_ID;
249 
250   NAUTILUS_DESKTOP_WINDOW_ID = XInternAtom(display, "NAUTILUS_DESKTOP_WINDOW_ID", False);
251 
252   *clientparent = root;
253 
254   if ((XGetWindowProperty(display, root, NAUTILUS_DESKTOP_WINDOW_ID,
255 			  0, 1, False, XA_WINDOW, &actual_type,
256 			  &actual_format, &nitems, &bytesafter,
257 			  (unsigned char **) &toplevel) == Success) && toplevel) {
258     /* Nautilus is running */
259     background = __GetNautilusDesktop(display, screen, *toplevel, 0);
260     XFree((char *) toplevel);
261   }
262 
263   /* Next look for a virtual root or a KDE Desktop */
264   if ((background == None)
265       && (XQueryTree(display, root, &rootReturn, &parentReturn, &children, &nChildren))) {
266     Atom  _NET_WM_WINDOW_TYPE, __SWM_VROOT;
267     unsigned int   i;
268 
269     _NET_WM_WINDOW_TYPE = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
270     __SWM_VROOT = XInternAtom(display, "__SWM_VROOT", False);
271 
272     for (i = 0; i < nChildren && (background == None); ++i) {
273       Window *newroot = (Window *) 0;
274 
275       if ((XGetWindowProperty(display, children[i],
276 			      __SWM_VROOT, 0, 1, False, XA_WINDOW,
277 			      &actual_type, &actual_format, &nitems, &bytesafter,
278 			      (unsigned char **) &newroot) == Success) && newroot) {
279 	/* Found a window with a __SWM_VROOT property that contains
280 	 * the window ID of the virtual root. Now we must check
281 	 * whether it is KDE (2.1+) or not. If it is KDE then it does
282 	 * not reparent the clients. If the root window has the
283 	 * _NET_SUPPORTED property but not the _NET_VIRTUAL_ROOTS
284 	 * property then we assume it is KDE. */
285 	Atom  _NET_SUPPORTED, *tmpatom;
286 
287 	_NET_SUPPORTED = XInternAtom(display, "_NET_SUPPORTED", False);
288 
289 	if ((XGetWindowProperty(display, root,
290 				_NET_SUPPORTED, 0, 1, False, XA_ATOM,
291 				&actual_type, &actual_format, &nitems, &bytesafter,
292 				(unsigned char **) &tmpatom) == Success) && tmpatom) {
293 	  Window  *tmpwindow = (Window *) 0;
294 	  Atom    _NET_VIRTUAL_ROOTS;
295 
296 	  _NET_VIRTUAL_ROOTS = XInternAtom(display, "_NET_VIRTUAL_ROOTS", False);
297 
298 	  XFree((char *) tmpatom);
299 
300 	  if ((XGetWindowProperty(display, root,
301 				  _NET_VIRTUAL_ROOTS, 0, 1, False, XA_WINDOW, &actual_type,
302 				  &actual_format, &nitems, &bytesafter,
303 				  (unsigned char **) &tmpwindow) != Success) || !tmpwindow) {
304 	    /* Must be KDE 2.1+ */
305 	    background = *newroot;
306 	  }
307 	  else if (tmpwindow) {
308 	    XFree((char *) tmpwindow);
309 	  }
310 	}
311 
312 	if (background == None) {
313 	  /* Not KDE: assume windows are reparented */
314 	  background = *clientparent = *newroot;
315 	}
316 	XFree((char *) newroot);
317       }
318       else if ((background = __GetKDEDesktop(display, screen, children[i],
319 					     _NET_WM_WINDOW_TYPE,
320 					     "_NET_WM_WINDOW_TYPE_DESKTOP", 0))) {
321 	/* Found a KDE 2.0 desktop and located the background window */
322 	/* Note that the clientparent is still the root window */
323       }
324     }
325     XFree((char *) children);
326   }
327 
328   if (background == None) {
329     /* Look for a _WIN_WORKSPACE property, used by Enlightenment */
330     Atom _WIN_WORKSPACE;
331 
332     _WIN_WORKSPACE = XInternAtom(display, "_WIN_WORKSPACE", False);
333 
334     if ((XGetWindowProperty(display, root, _WIN_WORKSPACE, 0, 1, False, XA_CARDINAL,
335 			    &actual_type, &actual_format, &nitems, &bytesafter,
336 			    (unsigned char **) &workspace) == Success) && workspace) {
337       /* Found a _WIN_WORKSPACE property - this is the desktop to look for.
338        * For now assume that this is Enlightenment.
339        * We're looking for a child of the root window that has an
340        * ENLIGHTENMENT_DESKTOP atom with a value equal to the root window's
341        * _WIN_WORKSPACE atom. */
342       Atom  ENLIGHTENMENT_DESKTOP;
343 
344       ENLIGHTENMENT_DESKTOP = XInternAtom(display, "ENLIGHTENMENT_DESKTOP", False);
345 
346       /* First check to see if the root window is the current desktop... */
347       if ((XGetWindowProperty(display, root,
348 			      ENLIGHTENMENT_DESKTOP, 0, 1, False, XA_CARDINAL,
349 			      &actual_type, &actual_format, &nitems, &bytesafter,
350 			      (unsigned char **) &desktop) == Success) &&
351 	  desktop && (*desktop == *workspace)) {
352 	/* The root window is the current Enlightenment desktop */
353 	background = root;
354 	XFree((char *) desktop);
355       }
356       /* Now look at each immediate child window of root to see if it is
357        * the current desktop */
358       else if (XQueryTree(display, root, &rootReturn, &parentReturn, &children, &nChildren)) {
359 	unsigned int i;
360 
361 	for (i = 0; i < nChildren; ++i) {
362 	  if ((XGetWindowProperty(display, children[i],
363 				  ENLIGHTENMENT_DESKTOP, 0, 1, False, XA_CARDINAL,
364 				  &actual_type, &actual_format, &nitems, &bytesafter,
365 				  (unsigned char **) &desktop) == Success)
366 	      && desktop && *desktop == *workspace) {
367 
368 	    /* Found current Enlightenment desktop */
369 	    background = *clientparent = children[i];
370 	    XFree((char *) desktop);
371 	  }
372 	}
373 	XFree((char *) children);
374       }
375       XFree((char *) workspace);
376     }
377   }
378 
379   if (background != None)
380     return background;
381 
382   return root;
383 }
384