/* toon_root.c - finding the correct background window / virtual root
 * -copyright-
#-# 
#-# Copyright (C) 1999-2001  Robin Hogan, 2021 Willem Vermin
#-# 
#-# This program is free software; you can redistribute it and/or modify
#-# it under the terms of the GNU General Public License as published by
#-# the Free Software Foundation; either version 2 of the License, or
#-# (at your option) any later version.
#-# 
#-# This program is distributed in the hope that it will be useful,
#-# but WITHOUT ANY WARRANTY; without even the implied warranty of
#-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#-# GNU General Public License for more details.
#-# 
#-# You should have received a copy of the GNU General Public License
#-# along with this program; if not, write to the Free Software
#-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#-# 
*/

/* Not used since version 3.0, but left here, you never know ...*/

/* Since xpenguins version 2.1, the ToonGetRootWindow() function
 * attempts to find the window IDs of
 *
 * 1) The background window that is behind the toplevel client
 *    windows; this is the window that we draw the toons on.
 *
 * 2) The parent window of the toplevel client windows; this is used
 *    by ToonLocateWindows() to build up a map of the space that the
 *    toons can occupy.
 * 
 * In simple (sensible?) window managers (e.g. blackbox, sawfish, fvwm
 * and countless others), both of these are the root window. The other
 * more complex scenarios that ToonGetRootWindow() attempts to cope
 * with are:
 *
 * Some `virtual' window managers (e.g. amiwm, swm and tvtwm) that
 * reparent all client windows to a desktop window that sits on top of
 * the root window. This desktop window is easy to find - we just look
 * for a property __SWM_VROOT in the immediate children of the root
 * window that contains the window ID of this desktop window. The
 * desktop plays both roles (1 and 2 above). This functionality was
 * detected in xpenguins 1.x with the vroot.h header file.
 *
 * Enlightenment (0.16) can have a number of desktops with different
 * backgrounds; client windows on these are reparented, except for
 * Desktop 0 which is the root window. Therefore versions less than
 * 2.1 of xpenguins worked on Desktop 0 but not on any others. To fix
 * this we look for a root-window property _WIN_WORKSPACE which
 * contains the numerical index of the currently active desktop. The
 * active desktop is then simply the immediate child of the root
 * window that has a property ENLIGHTENMENT_DESKTOP set to this value.
 *
 * KDE 2.0: Oh dear. The kdesktop is a program separate from the
 * window manager that launches a window which sits behind all the
 * other client windows and has all the icons on it. Thus the other
 * client windows are still children of the root window, but we want
 * to draw to the uppermost window of the kdesktop. This is difficult
 * to find - it is the great-great-grandchild of the root window and
 * in KDE 2.0 has nothing to identify it from its siblings other than
 * its size. KDE 2.1+ usefully implements the __SWM_VROOT property in
 * a child of the root window, but the client windows are still
 * children of the root window. A problem is that the penguins erase
 * the desktop icons when they walk which is a bit messy. The icons
 * are not lost - they reappear when the desktop window gets an expose
 * event (i.e. move some windows over where they were and back again).
 *
 * Nautilus (GNOME 1.4+): Creates a background window to draw icons
 * on, but does not reparent the client windows. The toplevel window
 * of the desktop is indicated by the root window property
 * NAUTILUS_DESKTOP_WINDOW_ID, but then we must descend down the tree
 * from this toplevel window looking for subwindows that are the same
 * size as the screen. The bottom one is the one to draw to. Hopefully
 * one day Nautilus will implement __SWM_VROOT in exactly the same way
 * as KDE 2.1+.
 *
 * Other cases: CDE, the common desktop environment. This is a
 * commercial product that has been packaged with Sun (and other)
 * workstations. It typically implements four virtual desktops but
 * provides NO properties at all for apps such as xpenguins to use to
 * work out where to draw to. Seeing as Sun are moving over to GNOME,
 * CDE use is on the decline so I don't have any current plans to try
 * and get xpenguins to work with it.
 *
 * As a note to developers of window managers and big screen hoggers
 * like kdesktop, please visit www.freedesktop.org and implement their
 * Extended Window Manager Hints spec that help pagers and apps like
 * xpenguins and xearth to find their way around. In particular,
 * please use the _NET_CURRENT_DESKTOP and _NET_VIRTUAL_ROOTS
 * properties if you reparent any windows (e.g. Enlightenment). Since
 * no window managers that I know yet use these particular hints, I
 * haven't yet added any code to parse them.  */


#include "toon.h"
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xproto.h>
#include <stdio.h>
#include <string.h>

/* Time to throw up. Here is a kludgey function that recursively calls
 * itself (up to a limit) to find the window ID of the KDE desktop to
 * draw on. It works with KDE 2.0, but since KDE 2.0 is less stable
 * than Windows 95, I don't expect many people to remain using it now
 * that 2.1 is available, which implements __SWM_VROOT and makes this
 * function redundant. This is the hierarchy we're trying to traverse:
 *
 * -> The root window
 * 0 -> window with name="KDE Desktop"
 * 1   -> window with no name
 * 2     -> window with name="KDE Desktop" & _NET_WM_WINDOW_TYPE_DESKTOP
 * 3       -> window with no name and width >= width of screen
 * 
 * The last window in the hierarchy is the one to draw to.  The
 * numbers show the value of the `depth' argument.  */
static Window ToonGetKDEDesktop(Display *display, int screen, Window window,
      Atom atom, char *atomname, int depth)
{
   char *name = NULL;
   Atom *wintype = NULL;
   Window winreturn = 0;
   unsigned long nitems, bytesafter;
   Atom actual_type;
   int actual_format;
   Window rootReturn, parentReturn, *children;
   unsigned int nChildren;
   int go_deeper = 0;

   if (XFetchName(display, window, &name)) {
      if (strcasecmp(name, "KDE Desktop") == 0) {
	 /* Presumably either at depth 0 or 2 */
	 if (XGetWindowProperty(display, window, atom, 0, 1,
		  False, XA_ATOM,
		  &actual_type, &actual_format,
		  &nitems, &bytesafter,
		  (unsigned char **) &wintype) == Success
	       && wintype) {
	    char *tmpatomname = XGetAtomName(display, *wintype);
	    if (tmpatomname) {
	       if (strcmp(atomname, tmpatomname) == 0 && depth == 2) {
		  /* OK, at depth 2 */
		  go_deeper = 1;
	       }
	       XFree((char *) tmpatomname);
	    }
	 }
	 else if (depth < 2) {
	    go_deeper = 1;
	 }
      }
      else if (depth == 1) {
	 go_deeper = 1;
      }
      XFree((char *) name);
   }
   else if (depth == 1) {
      go_deeper = 1;
   }

   /* If go_deeper is 1 then there is a possibility that the background
    * window is a descendant of the current window; otherwise we're
    * barking up the wrong tree. */
   if (go_deeper && XQueryTree(display, window, &rootReturn,
	    &parentReturn, &children,
	    &nChildren)) {
      int i;
      for (i = 0; i < (int)nChildren; ++i) {
	 /* children[i] is now at depth 3 */
	 if (depth == 2) {
	    XWindowAttributes attributes;
	    if (XGetWindowAttributes(display, children[i], &attributes)) {
	       if (attributes.width >= DisplayWidth(display, screen)/2
		     && attributes.height > 0) {
		  /* Found it! */
		  winreturn = children[i];
		  break;
	       }
	    }
	 }
	 else if ((winreturn = ToonGetKDEDesktop(display, screen,
		     children[i],
		     atom, atomname,
		     depth+1))) {
	    break;
	 }
      }
      XFree((char *) children);
   }

   return winreturn;
}

/* Look for the Nautilus desktop window to draw to, given the toplevel
 * window of the Nautilus desktop. Basically recursively calls itself
 * looking for subwindows the same size as the root window. */
static Window ToonGetNautilusDesktop(Display *display, int screen, Window window,
      int depth)
{
   Window rootReturn, parentReturn, *children;
   Window winreturn = window;
   unsigned int nChildren;

   if (depth > 5) {
      return ((Window) 0);
   }
   else if (XQueryTree(display, window, &rootReturn, &parentReturn,
	    &children, &nChildren)) {
      unsigned int i;
      for (i = 0; i < nChildren; ++i) {
	 XWindowAttributes attributes;
	 if (XGetWindowAttributes(display, children[i], &attributes)) {
	    if (attributes.width == DisplayWidth(display, screen)
		  && attributes.height == DisplayHeight(display, screen)) {
	       /* Found a possible desktop window */
	       winreturn = ToonGetNautilusDesktop(display, screen,
		     children[i], depth+1);
	    }
	 }  
      }
      XFree((char *) children);
   }
   return winreturn;
}


/* 
 * Returns the window ID of the `background' window on to which the
 * toons should be drawn. Also returned (in clientparent) is the ID of
 * the parent of all the client windows, since this may not be the
 * same as the background window. If no recognised virtual window
 * manager or desktop environment is found then the root window is
 * returned in both cases. The string toon_message contains
 * information about the window manager that was found.
 */
Window ToonGetRootWindow(Display *display, int screen, Window *clientparent)
{
   Window background = 0; /* The return value */
   Window root = RootWindow(display, screen);
   Window rootReturn, parentReturn, *children;
   Window *toplevel = (Window *) 0;
   unsigned int nChildren;
   unsigned long nitems, bytesafter;
   Atom actual_type;
   int actual_format;
   unsigned long *workspace = NULL;
   unsigned long *desktop = NULL;
   Atom NAUTILUS_DESKTOP_WINDOW_ID = XInternAtom(display,
	 "NAUTILUS_DESKTOP_WINDOW_ID",
	 False);

   *clientparent = root;

   if (XGetWindowProperty(display, root,
	    NAUTILUS_DESKTOP_WINDOW_ID,
	    0, 1, False, XA_WINDOW,
	    &actual_type, &actual_format,
	    &nitems, &bytesafter,
	    (unsigned char **) &toplevel) == Success
	 && toplevel) {
      /* Nautilus is running */
      background = ToonGetNautilusDesktop(display, screen,
	    *toplevel, 0);
      XFree((char *) toplevel);
      if (background) {
	 snprintf(toon_message, TOON_MESSAGE_LENGTH,
	       _("Drawing to Nautilus Desktop"));
	 toon_message[TOON_MESSAGE_LENGTH-1] = '\0';
      }
   }

   /* Next look for a virtual root or a KDE Desktop */
   if (!background
	 && XQueryTree(display, root, &rootReturn, &parentReturn,
	    &children, &nChildren)) {
      unsigned int i;
      Atom _NET_WM_WINDOW_TYPE = XInternAtom(display, 
	    "_NET_WM_WINDOW_TYPE",
	    False);
      Atom __SWM_VROOT = XInternAtom(display, "__SWM_VROOT", False);

      for (i = 0; i < nChildren && !background; ++i) {
	 Window *newroot = (Window *) 0;
	 if (XGetWindowProperty(display, children[i],
		  __SWM_VROOT, 0, 1, False, XA_WINDOW,
		  &actual_type, &actual_format,
		  &nitems, &bytesafter,
		  (unsigned char **) &newroot) == Success
	       && newroot) {
	    /* Found a window with a __SWM_VROOT property that contains
	     * the window ID of the virtual root. Now we must check
	     * whether it is KDE (2.1+) or not. If it is KDE then it does
	     * not reparent the clients. If the root window has the
	     * _NET_SUPPORTED property but not the _NET_VIRTUAL_ROOTS
	     * property then we assume it is KDE. */
	    Atom _NET_SUPPORTED = XInternAtom(display,
		  "_NET_SUPPORTED",
		  False);
	    Atom *tmpatom;
	    if (XGetWindowProperty(display, root,
		     _NET_SUPPORTED, 0, 1, False,
		     XA_ATOM, &actual_type, &actual_format,
		     &nitems, &bytesafter,
		     (unsigned char **) &tmpatom) == Success
		  && tmpatom) {
	       Window *tmpwindow = (Window *) 0;
	       Atom _NET_VIRTUAL_ROOTS = XInternAtom(display,
		     "_NET_VIRTUAL_ROOTS",
		     False);
	       XFree((char *) tmpatom);
	       if (XGetWindowProperty(display, root,
			_NET_VIRTUAL_ROOTS, 0, 1, False,
			XA_WINDOW, &actual_type, &actual_format,
			&nitems, &bytesafter,
			(unsigned char **) &tmpwindow) != Success
		     || !tmpwindow) {
		  /* Must be KDE 2.1+ */
		  snprintf(toon_message, TOON_MESSAGE_LENGTH,
			_("Drawing to KDE Desktop"));
		  toon_message[TOON_MESSAGE_LENGTH-1] = '\0';
		  background = *newroot;
	       }
	       else if (tmpwindow) {
		  XFree((char *) tmpwindow);
	       }
	    }

	    if (!background) {  
	       /* Not KDE: assume windows are reparented */
	       snprintf(toon_message, TOON_MESSAGE_LENGTH,
		     _("Drawing to virtual root window"));
	       toon_message[TOON_MESSAGE_LENGTH-1] = '\0';
	       background = *clientparent = *newroot;
	    }
	    XFree((char *) newroot);
	 }
	 else if ((background = ToonGetKDEDesktop(display, screen, children[i],
		     _NET_WM_WINDOW_TYPE,
		     _("_NET_WM_WINDOW_TYPE_DESKTOP"),
		     0))) {
	    /* Found a KDE 2.0 desktop and located the background window */
	    /* Note that the clientparent is still the root window */
	    snprintf(toon_message, TOON_MESSAGE_LENGTH,
		  _("Drawing to KDE desktop"));
	    toon_message[TOON_MESSAGE_LENGTH-1] = '\0';
	 }
      }
      XFree((char *) children);
   }

   if (!background) {
      /* Look for a _WIN_WORKSPACE property, used by Enlightenment */
      Atom _WIN_WORKSPACE = XInternAtom(display, "_WIN_WORKSPACE", False);
      if (XGetWindowProperty(display, root, _WIN_WORKSPACE,
	       0, 1, False, XA_CARDINAL,
	       &actual_type, &actual_format,
	       &nitems, &bytesafter,
	       (unsigned char **) &workspace) == Success
	    && workspace) {
	 /* Found a _WIN_WORKSPACE property - this is the desktop to look for.
	  * For now assume that this is Enlightenment.
	  * We're looking for a child of the root window that has an
	  * ENLIGHTENMENT_DESKTOP atom with a value equal to the root window's
	  * _WIN_WORKSPACE atom. */
	 Atom ENLIGHTENMENT_DESKTOP = XInternAtom(display, 
	       "ENLIGHTENMENT_DESKTOP",
	       False);
	 /* First check to see if the root window is the current desktop... */
	 if (XGetWindowProperty(display, root,
		  ENLIGHTENMENT_DESKTOP, 0, 1,
		  False, XA_CARDINAL,
		  &actual_type, &actual_format,
		  &nitems, &bytesafter,
		  (unsigned char **) &desktop) == Success
	       && desktop && *desktop == *workspace) {
	    /* The root window is the current Enlightenment desktop */
	    snprintf(toon_message, TOON_MESSAGE_LENGTH,
		  _("Drawing to Enlightenment Desktop %lu (the root window)"),
		  *desktop);
	    toon_message[TOON_MESSAGE_LENGTH-1] = '\0';
	    background = root;
	    XFree((char *) desktop);
	 }
	 /* Now look at each immediate child window of root to see if it is
	  * the current desktop */
	 else if (XQueryTree(display, root, &rootReturn, &parentReturn,
		  &children, &nChildren)) {
	    unsigned int i;
	    for (i = 0; i < nChildren; ++i) {
	       if (XGetWindowProperty(display, children[i],
			ENLIGHTENMENT_DESKTOP, 0, 1,
			False, XA_CARDINAL,
			&actual_type, &actual_format,
			&nitems, &bytesafter,
			(unsigned char **) &desktop) == Success
		     && desktop && *desktop == *workspace) {
		  /* Found current Enlightenment desktop */
		  snprintf(toon_message, TOON_MESSAGE_LENGTH,
			_("Drawing to Enlightenment Desktop %lu"),
			*desktop);
		  toon_message[TOON_MESSAGE_LENGTH-1] = '\0';
		  background = *clientparent = children[i];
		  XFree((char *) desktop);
	       }
	    }
	    XFree((char *) children);
	 }
	 XFree((char *) workspace);
      }
   }
   if (background) {
      return background;
   }
   else {
      toon_message[0] = '\0';
      return root;
   }
}
