/* * Copyright © 2011 Canonical Ltd. * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without * fee, provided that the above copyright notice appear in all copies * and that both that copyright notice and this permission notice * appear in supporting documentation, and that the name of * Canonical Ltd. not be used in advertising or publicity pertaining to * distribution of the software without specific, written prior permission. * Canonical Ltd. makes no representations about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. * * CANONICAL, LTD. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN * NO EVENT SHALL CANONICAL, LTD. BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Authored by: Sam Spilsbury */ #include "privatestackdebugger.h" #include "privatescreen.h" #include "privatewindow.h" #include namespace { StackDebugger * gStackDebugger = NULL; } StackDebugger * StackDebugger::Default () { return gStackDebugger; } void StackDebugger::SetDefault (StackDebugger *dbg) { if (gStackDebugger) delete gStackDebugger; gStackDebugger = dbg; } StackDebugger::StackDebugger (Display *dpy, Window root, FetchXEventInterface *fetchXEvent) : mServerNChildren (0), mServerChildren (NULL), mWindowsChanged (false), mServerWindowsChanged (false), mRoot (root), mDpy (dpy), mFetchXEvent (fetchXEvent), mTimeoutRequired (false) { } StackDebugger::~StackDebugger () { if (mServerChildren) { XFree (mServerChildren); mServerChildren = NULL; mServerNChildren = 0; } } bool StackDebugger::timedOut () { return mTimeoutRequired; } void StackDebugger::removeServerWindow (Window id) { /* Find the toplevel window in the list and remove it */ for (CompWindowList::iterator it = mLastServerWindows.begin (); it != mLastServerWindows.end (); ++it) { if ((*it)->id () == id) { mLastServerWindows.erase (it); break; } } } void StackDebugger::overrideRedirectRestack (Window toplevel, Window sibling) { CompWindow *tl = screen->findWindow (toplevel); removeServerWindow (toplevel); /* Find the sibling of this window and insert above it or at * the bottom if the sibling is 0 */ if (sibling) { for (CompWindowList::iterator it = mLastServerWindows.begin (); it != mLastServerWindows.end (); ++it) { if (sibling == (*it)->id () || sibling == (*it)->frame ()) { mLastServerWindows.insert (++it, tl); break; } } } else mLastServerWindows.push_front (tl); } bool StackDebugger::getNextEvent (XEvent &ev) { if (mEvents.empty ()) return false; ev = mEvents.front (); mEvents.pop_front (); return true; } void StackDebugger::loadStack (CompWindowList &serverWindows, bool wait) { Window rootRet, parentRet; if (mServerChildren) XFree (mServerChildren); XSync (mDpy, FALSE); XGrabServer (mDpy); XQueryTree (mDpy, mRoot, &rootRet, &parentRet, &mServerChildren, &mServerNChildren); unsigned int n = XEventsQueued (mDpy, QueuedAfterFlush); mEvents.clear (); mEvents.resize (n); std::list ::iterator it = mEvents.begin (); while (it != mEvents.end ()) { mFetchXEvent->getNextXEvent ((*it)); ++it; } XSync (mDpy, FALSE); /* It is possible that X might not be keeping up with us, so * we should give it about 300 ms in case the stacks are out of sync * in order to deliver any more events that might be pending */ mTimeoutRequired = false; mLastServerWindows = serverWindows; if (mServerNChildren != serverWindows.size () && wait) { struct pollfd pfd; pfd.events = POLLIN; pfd.revents = 0; pfd.fd = ConnectionNumber (mDpy); poll (&pfd, 1, 300); XEvent e; while (mFetchXEvent->getNextXEvent (e)) mEvents.push_back (e); mTimeoutRequired = true; } mDestroyedFrames.clear (); XUngrabServer (mDpy); XSync (mDpy, FALSE); } void StackDebugger::addDestroyedFrame (Window f) { mDestroyedFrames.push_back (f); } void StackDebugger::removeDestroyedFrame (Window f) { mDestroyedFrames.remove (f); } bool StackDebugger::cmpStack (CompWindowList &windows, CompWindowList &serverWindows, bool verbose) { std::vector serverSideWindows; CompWindowList::reverse_iterator lrrit = windows.rbegin (); CompWindowList::reverse_iterator lsrit = mLastServerWindows.rbegin (); unsigned int i = 0; bool err = false; for (unsigned int n = 0; n < mServerNChildren; n++) { if (std::find (mDestroyedFrames.begin (), mDestroyedFrames.end (), mServerChildren[n]) == mDestroyedFrames.end ()) serverSideWindows.push_back (mServerChildren[n]); } if (verbose) compLogMessage ("core", CompLogLevelDebug, "sent | recv | server |"); for (;(lrrit != windows.rend () || lsrit != mLastServerWindows.rend () || i != serverSideWindows.size ());) { Window lrXid = 0; Window lsXid = 0; Window sXid = 0; if (lrrit != windows.rend ()) lrXid = (*lrrit)->priv->frame ? (*lrrit)->priv->frame : (*lrrit)->id (); if (lsrit != mLastServerWindows.rend ()) lsXid = (*lsrit)->priv->frame ? (*lsrit)->priv->frame : (*lsrit)->id (); if (i != serverSideWindows.size ()) sXid = serverSideWindows[serverSideWindows.size () - (i + 1)]; if (verbose) compLogMessage ("core", CompLogLevelDebug, "id 0x%x id 0x%x id 0x%x %s", (unsigned int) lsXid, (unsigned int) lrXid, (unsigned int) sXid, (lrXid != sXid) ? " /!\\ " : ""); if (lrXid != sXid) err = true; if (lrrit != windows.rend ()) ++lrrit; if (lsrit != mLastServerWindows.rend()) ++lsrit; if (i != serverSideWindows.size ()) i++; } return err; } /* Checks the sanity of the list of windows last sent to the server. * * There are a few stacking "layers" here. From top to bottom: * - 1) Docks stacked above toplevel windows which are stacked * above fullscreen windows * - 2) "Keep above" toplevel windows above fullscreen windows * where a toplevel is in focus * - 3) Toplevel windows in focus above fullscreen windows * - 4) Fullscreen windows * - 5) Dock windows * - 6) Keep above windows * - 7) Toplevel windows * - 8) Docks which are marked "Keep Below" * - 9) "Keep Below" windows * - 10) Desktop windows * * There are also a few rules which apply here: * - 1) Dock windows should always be above normal windows * except if marked keep below on any layer. * - 2) Dock windows should ONLY be on one layer at a time, * eg if they are on layer 1 then there cannot * also be dock windows on layer 5 (except in the * case of below dock windows on layer 8) * - 3) Fullscreen windows must always be above docks when in * focus, no matter if there is another window with "Keep Above" * - 4) Focused windows take priority over fullscreen windows and * docks must always be above them (see rule 1) * * As we pass through each layer, this function flags each one from * lowest being the most bits set to highest being the least bits * set. If a window violates this it raises a warning */ #define DOCKS_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN 0xffffffff >> 1 #define KEEP_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN 0xffffffff >> 2 #define TOPLEVELS_ABOVE_FULLSCREEN 0xffffffff >> 3 #define FULLSCREEN 0xffffffff >> 4 #define DOCKS 0xffffffff >> 5 #define KEEP_ABOVE 0xffffffff >> 6 #define TOPLEVELS 0xffffffff >> 7 #define DOCKS_BELOW 0xffffffff >> 8 #define KEEP_BELOW 0xffffffff >> 9 #define DESKTOP 0xffffffff >> 10 namespace { bool setCurrentLayer (Window requestingWindow, int layer, int ¤t) { bool ret = false; /* Only allow steps down */ if ((current & layer) != layer) { ret = true; compLogMessage ("stackdebugger", CompLogLevelWarn, "0x%x requested invalid layer 0x%x", requestingWindow, layer, current); } current = layer; return ret; } } bool StackDebugger::checkSanity (CompWindowList &serverWindows, bool verbose) { int current = 0xffffffff; int oldCurrent = current; bool err = false; if (verbose) compLogMessage ("stackdebugger", CompLogLevelDebug, "processing new stack --------"); /* go backwards down the stack */ for (CompWindowList::reverse_iterator rit = serverWindows.rbegin (); rit != serverWindows.rend (); ++rit) { CompWindow *w = (*rit); /* Override redirect windows set all kinds * of crazy stuff and are required to stack * themselves so skip those */ if (w->overrideRedirect ()) continue; /* ignore non-override redirect unmanaged windows */ if (!w->managed ()) continue; /* ignore any windows that just got created */ if (!w->mapNum ()) continue; /* determine the current layer */ if (w->type () == CompWindowTypeDockMask) { if ((current & DOCKS_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN) == DOCKS_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN) { bool fullscreenWindow = false; /* search down the stack to check if there is a fullscreen * window, otherwise we are not on the fullscreen layer */ for (CompWindow *rw = w->prev; rw; rw = rw->prev) { if (rw->type () & CompWindowTypeFullscreenMask) { fullscreenWindow = true; break; } } /* if there is no fullscreen window, change the layer */ if (!fullscreenWindow) err |= setCurrentLayer (w->id (), DOCKS, current); else err |= setCurrentLayer (w->id (), DOCKS_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN, current); } else if (w->state () & CompWindowStateBelowMask) err |= setCurrentLayer (w->id (), DOCKS_BELOW, current); else err |= setCurrentLayer (w->id (), DOCKS, current); } else if (w->type () == CompWindowTypeFullscreenMask) { err |= setCurrentLayer (w->id (), FULLSCREEN, current); } else if (w->type () == CompWindowTypeDesktopMask) { err |= setCurrentLayer (w->id (), DESKTOP, current); } /* everything else that is not a fullscreen window or a desktop */ else { if (w->state () & CompWindowStateAboveMask) { if ((current & KEEP_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN) == KEEP_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN) { bool fullscreenWindow = false; /* search down the stack to check if there is a fullscreen * window, otherwise we are not on the fullscreen layer */ for (CompWindow *rw = w->prev; rw; rw = rw->prev) { if (rw->type () & CompWindowTypeFullscreenMask) { fullscreenWindow = true; break; } } if (!fullscreenWindow) err |= setCurrentLayer (w->id (), KEEP_ABOVE, current); else err |= setCurrentLayer (w->id (), KEEP_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN, current); } else err |= setCurrentLayer (w->id (), KEEP_ABOVE, current); } else if (w->state () & CompWindowStateBelowMask) err |= setCurrentLayer (w->id (), KEEP_BELOW, current); else { if ((current & TOPLEVELS_ABOVE_FULLSCREEN) == TOPLEVELS_ABOVE_FULLSCREEN) { bool fullscreenWindow = false; /* search down the stack to check if there is a fullscreen * window, otherwise we are not on the fullscreen layer */ for (CompWindow *rw = w->prev; rw; rw = rw->prev) { if (rw->type () & CompWindowTypeFullscreenMask) { fullscreenWindow = true; break; } } if (!fullscreenWindow) err |= setCurrentLayer (w->id (), TOPLEVELS, current); else err |= setCurrentLayer (w->id (), TOPLEVELS_ABOVE_FULLSCREEN, current); } else err |= setCurrentLayer (w->id (), TOPLEVELS, current); } } if ((current & DOCKS_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN) == DOCKS_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN) { if (verbose && current != oldCurrent) compLogMessage ("stackdebugger", CompLogLevelDebug, "on layer DOCKS_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN"); } else if ((current & KEEP_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN) == KEEP_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN) { if (verbose && current != oldCurrent) compLogMessage ("stackdebugger", CompLogLevelDebug, "on layer KEEP_ABOVE_TOPLEVELS_ABOVE_FULLSCREEN"); } else if ((current & TOPLEVELS_ABOVE_FULLSCREEN) == TOPLEVELS_ABOVE_FULLSCREEN) { if (verbose && current != oldCurrent) compLogMessage ("stackdebugger", CompLogLevelDebug, "on layer TOPLEVELS_ABOVE_FULLSCREEN"); } else if ((current & FULLSCREEN) == FULLSCREEN) { if (verbose && current != oldCurrent) compLogMessage ("stackdebugger", CompLogLevelDebug, "on layer FULLSCREEN"); } else if ((current & DOCKS) == DOCKS) { if (verbose && current != oldCurrent) compLogMessage ("stackdebugger", CompLogLevelDebug, "on layer DOCKS"); } else if ((current & KEEP_ABOVE) == KEEP_ABOVE) { if (verbose && current != oldCurrent) compLogMessage ("stackdebugger", CompLogLevelDebug, "on layer KEEP_ABOVE"); } else if ((current & TOPLEVELS) == TOPLEVELS) { if (verbose && current != oldCurrent) compLogMessage ("stackdebugger", CompLogLevelDebug, "on layer TOPLEVELS"); } else if ((current & DOCKS_BELOW) == DOCKS_BELOW) { if (verbose && current != oldCurrent) compLogMessage ("stackdebugger", CompLogLevelDebug, "on layer DOCKS_BELOW"); } else if ((current & KEEP_BELOW) == KEEP_BELOW) { if (verbose && current != oldCurrent) compLogMessage ("stackdebugger", CompLogLevelDebug, "on layer KEEP_BELOW"); } else if ((current & DESKTOP) == DESKTOP) { if (verbose && current != oldCurrent) compLogMessage ("stackdebugger", CompLogLevelDebug, "on layer DESKTOP"); } oldCurrent = current; } return err; }