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