diff --git a/layers.cpp b/layers.cpp index 93c243f72..d01cba023 100644 --- a/layers.cpp +++ b/layers.cpp @@ -1,889 +1,878 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak 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, see . *********************************************************************/ // SELI zmenit doc /* This file contains things relevant to stacking order and layers. Design: Normal unconstrained stacking order, as requested by the user (by clicking on windows to raise them, etc.), is in Workspace::unconstrained_stacking_order. That list shouldn't be used at all, except for building Workspace::stacking_order. The building is done in Workspace::constrainedStackingOrder(). Only Workspace::stackingOrder() should be used to get the stacking order, because it also checks the stacking order is up to date. All clients are also stored in Workspace::clients (except for isDesktop() clients, as those are very special, and are stored in Workspace::desktops), in the order the clients were created. Every window has one layer assigned in which it is. There are 6 layers, from bottom : DesktopLayer, BelowLayer, NormalLayer, DockLayer, AboveLayer and ActiveLayer (see also NETWM sect.7.10.). The layer a window is in depends on the window type, and on other things like whether the window is active. NET::Splash clients belong to the Normal layer. NET::TopMenu clients belong to Dock layer. Clients that are both NET::Dock and NET::KeepBelow are in the Normal layer in order to keep the 'allow window to cover the panel' Kicker setting to work as intended (this may look like a slight spec violation, but a) I have no better idea, b) the spec allows adjusting the stacking order if the WM thinks it's a good idea . We put all NET::KeepAbove above all Docks too, even though the spec suggests putting them in the same layer. Most transients are in the same layer as their mainwindow, see Workspace::constrainedStackingOrder(), they may also be in higher layers, but they should never be below their mainwindow. When some client attribute changes (above/below flag, transiency...), Workspace::updateClientLayer() should be called in order to make sure it's moved to the appropriate layer ClientList if needed. Currently the things that affect client in which layer a client belongs: KeepAbove/Keep Below flags, window type, fullscreen state and whether the client is active, mainclient (transiency). Make sure updateStackingOrder() is called in order to make Workspace::stackingOrder() up to date and propagated to the world. Using Workspace::blockStackingUpdates() (or the StackingUpdatesBlocker helper class) it's possible to temporarily disable updates and the stacking order will be updated once after it's allowed again. */ #include #include #include "utils.h" #include "client.h" #include "focuschain.h" #include "workspace.h" #include "tabbox.h" #include "group.h" #include "rules.h" #include "unmanaged.h" #include "deleted.h" #include "effects.h" #include #include "composite.h" #ifdef KWIN_BUILD_SCREENEDGES #include "screenedge.h" #endif namespace KWin { //******************************* // Workspace //******************************* void Workspace::updateClientLayer(Client* c) { if (c == NULL) return; if (c->layer() == c->belongsToLayer()) return; StackingUpdatesBlocker blocker(this); c->invalidateLayer(); // invalidate, will be updated when doing restacking for (ClientList::ConstIterator it = c->transients().constBegin(); it != c->transients().constEnd(); ++it) updateClientLayer(*it); } void Workspace::updateStackingOrder(bool propagate_new_clients) { if (block_stacking_updates > 0) { if (propagate_new_clients) blocked_propagating_new_clients = true; return; } ToplevelList new_stacking_order = constrainedStackingOrder(); bool changed = (force_restacking || new_stacking_order != stacking_order); force_restacking = false; stacking_order = new_stacking_order; #if 0 kDebug(1212) << "stacking:" << changed; if (changed || propagate_new_clients) { for (ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it) kDebug(1212) << (void*)(*it) << *it << ":" << (*it)->layer(); } #endif if (changed || propagate_new_clients) { propagateClients(propagate_new_clients); emit stackingOrderChanged(); if (m_compositor) { m_compositor->addRepaintFull(); } if (active_client) active_client->updateMouseGrab(); } } #ifdef KWIN_BUILD_SCREENEDGES /*! * Some fullscreen effects have to raise the screenedge on top of an input window, thus all windows * this function puts them back where they belong for regular use and is some cheap variant of * the regular propagateClients function in that it completely ignores managed clients and everything * else and also does not update the NETWM property. * Called from Effects::destroyInputWindow so far. */ void Workspace::stackScreenEdgesUnderOverrideRedirect() { Xcb::restackWindows(QVector() << supportWindow->winId() << ScreenEdges::self()->windows()); } #endif /*! Propagates the managed clients to the world. Called ONLY from updateStackingOrder(). */ void Workspace::propagateClients(bool propagate_new_clients) { // restack the windows according to the stacking order // supportWindow > electric borders > clients > hidden clients QVector newWindowStack; // Stack all windows under the support window. The support window is // not used for anything (besides the NETWM property), and it's not shown, // but it was lowered after kwin startup. Stacking all clients below // it ensures that no client will be ever shown above override-redirect // windows (e.g. popups). newWindowStack << supportWindow->winId(); #ifdef KWIN_BUILD_SCREENEDGES newWindowStack << ScreenEdges::self()->windows(); #endif newWindowStack.reserve(newWindowStack.size() + 2*stacking_order.size()); // *2 for inputWindow for (int i = stacking_order.size() - 1; i >= 0; --i) { Client *client = qobject_cast(stacking_order.at(i)); if (!client || client->hiddenPreview()) { continue; } if (client->inputId()) // Stack the input window above the frame newWindowStack << client->inputId(); newWindowStack << client->frameId(); } // when having hidden previews, stack hidden windows below everything else // (as far as pure X stacking order is concerned), in order to avoid having // these windows that should be unmapped to interfere with other windows for (int i = stacking_order.size() - 1; i >= 0; --i) { Client *client = qobject_cast(stacking_order.at(i)); if (!client || !client->hiddenPreview()) continue; newWindowStack << client->frameId(); } // TODO isn't it too inefficient to restack always all clients? // TODO don't restack not visible windows? assert(newWindowStack.at(0) == supportWindow->winId()); Xcb::restackWindows(newWindowStack); int pos = 0; Window *cl(NULL); if (propagate_new_clients) { cl = new Window[ desktops.count() + clients.count()]; // TODO this is still not completely in the map order for (ClientList::ConstIterator it = desktops.constBegin(); it != desktops.constEnd(); ++it) cl[pos++] = (*it)->window(); for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) cl[pos++] = (*it)->window(); rootInfo->setClientList(cl, pos); delete [] cl; } cl = new Window[ stacking_order.count()]; pos = 0; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { if ((*it)->isClient()) cl[pos++] = (*it)->window(); } rootInfo->setClientListStacking(cl, pos); delete [] cl; // Make the cached stacking order invalid here, in case we need the new stacking order before we get // the matching event, due to X being asynchronous. x_stacking_dirty = true; } /*! Returns topmost visible client. Windows on the dock, the desktop or of any other special kind are excluded. Also if the window doesn't accept focus it's excluded. */ // TODO misleading name for this method, too many slightly different ways to use it Client* Workspace::topClientOnDesktop(int desktop, int screen, bool unconstrained, bool only_normal) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); ToplevelList list; if (!unconstrained) list = stacking_order; else list = unconstrained_stacking_order; for (int i = list.size() - 1; i >= 0; --i) { Client *c = qobject_cast(list.at(i)); if (!c) { continue; } if (c->isOnDesktop(desktop) && c->isShown(false) && c->isOnCurrentActivity()) { if (screen != -1 && c->screen() != screen) continue; if (!only_normal) return c; if (c->wantsTabFocus() && !c->isSpecialWindow()) return c; } } return 0; } Client* Workspace::findDesktop(bool topmost, int desktop) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); if (topmost) { for (int i = stacking_order.size() - 1; i >= 0; i--) { Client *c = qobject_cast(stacking_order.at(i)); if (c && c->isOnDesktop(desktop) && c->isDesktop() && c->isShown(true)) return c; } } else { // bottom-most foreach (Toplevel * c, stacking_order) { Client *client = qobject_cast(c); if (client && c->isOnDesktop(desktop) && c->isDesktop() && client->isShown(true)) return client; } } return NULL; } void Workspace::raiseOrLowerClient(Client *c) { if (!c) return; Client* topmost = NULL; // TODO Q_ASSERT( block_stacking_updates == 0 ); if (most_recently_raised && stacking_order.contains(most_recently_raised) && most_recently_raised->isShown(true) && c->isOnCurrentDesktop()) topmost = most_recently_raised; else topmost = topClientOnDesktop(c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop(), options->isSeparateScreenFocus() ? c->screen() : -1); if (c == topmost) lowerClient(c); else raiseClient(c); } void Workspace::lowerClient(Client* c, bool nogroup) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.prepend(c); if (!nogroup && c->isTransient()) { // lower also all windows in the group, in their reversed stacking order ClientList wins = ensureStackingOrder(c->group()->members()); for (int i = wins.size() - 1; i >= 0; --i) { if (wins[ i ] != c) lowerClient(wins[ i ], true); } } if (c == most_recently_raised) most_recently_raised = 0; } void Workspace::lowerClientWithinApplication(Client* c) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); bool lowered = false; // first try to put it below the bottom-most window of the application for (ToplevelList::Iterator it = unconstrained_stacking_order.begin(); it != unconstrained_stacking_order.end(); ++it) { Client *client = qobject_cast(*it); if (!client) { continue; } if (Client::belongToSameApplication(client, c)) { unconstrained_stacking_order.insert(it, c); lowered = true; break; } } if (!lowered) unconstrained_stacking_order.prepend(c); // ignore mainwindows } void Workspace::raiseClient(Client* c, bool nogroup) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); if (!nogroup && c->isTransient()) { ClientList transients; Client *transient_parent = c; while ((transient_parent = transient_parent->transientFor())) transients << transient_parent; foreach (transient_parent, transients) raiseClient(transient_parent, true); } unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.append(c); if (!c->isSpecialWindow()) { most_recently_raised = c; pending_take_activity = NULL; } } void Workspace::raiseClientWithinApplication(Client* c) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); // ignore mainwindows // first try to put it above the top-most window of the application for (int i = unconstrained_stacking_order.size() - 1; i > -1 ; --i) { Client *other = qobject_cast(unconstrained_stacking_order.at(i)); if (!other) { continue; } if (other == c) // don't lower it just because it asked to be raised return; if (Client::belongToSameApplication(other, c)) { unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(other) + 1, c); // insert after the found one break; } } } void Workspace::raiseClientRequest(Client* c, NET::RequestSource src, Time timestamp) { if (src == NET::FromTool || allowFullClientRaising(c, timestamp)) raiseClient(c); else { raiseClientWithinApplication(c); c->demandAttention(); } } void Workspace::lowerClientRequest(Client* c, NET::RequestSource src, Time /*timestamp*/) { // If the client has support for all this focus stealing prevention stuff, // do only lowering within the application, as that's the more logical // variant of lowering when application requests it. // No demanding of attention here of course. if (src == NET::FromTool || !c->hasUserTimeSupport()) lowerClient(c); else lowerClientWithinApplication(c); } void Workspace::restack(Client* c, Client* under) { assert(unconstrained_stacking_order.contains(under)); if (!Client::belongToSameApplication(under, c)) { // put in the stacking order below _all_ windows belonging to the active application for (int i = 0; i < unconstrained_stacking_order.size(); ++i) { Client *other = qobject_cast(unconstrained_stacking_order.at(i)); if (other && Client::belongToSameApplication(under, other)) { under = (c == other) ? 0 : other; break; } } } if (under) { unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(under), c); } assert(unconstrained_stacking_order.contains(c)); FocusChain::self()->moveAfterClient(c, under); updateStackingOrder(); } void Workspace::restackClientUnderActive(Client* c) { if (!active_client || active_client == c) { raiseClient(c); return; } restack(c, active_client); } void Workspace::restoreSessionStackingOrder(Client* c) { if (c->sessionStackingOrder() < 0) return; StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); for (ToplevelList::Iterator it = unconstrained_stacking_order.begin(); // from bottom it != unconstrained_stacking_order.end(); ++it) { Client *current = qobject_cast(*it); if (!current) { continue; } if (current->sessionStackingOrder() > c->sessionStackingOrder()) { unconstrained_stacking_order.insert(it, c); return; } } unconstrained_stacking_order.append(c); } /*! Returns a stacking order based upon \a list that fulfills certain contained. */ ToplevelList Workspace::constrainedStackingOrder() { ToplevelList layer[ NumLayers ]; #if 0 kDebug(1212) << "stacking1:"; for (ClientList::ConstIterator it = unconstrained_stacking_order.begin(); it != unconstrained_stacking_order.end(); ++it) kDebug(1212) << (void*)(*it) << *it << ":" << (*it)->layer(); #endif // build the order from layers QVector< QMap > minimum_layer(numScreens()); for (ToplevelList::ConstIterator it = unconstrained_stacking_order.constBegin(), end = unconstrained_stacking_order.constEnd(); it != end; ++it) { Layer l = (*it)->layer(); const int screen = (*it)->screen(); Client *c = qobject_cast(*it); QMap< Group*, Layer >::iterator mLayer = minimum_layer[screen].find(c ? c->group() : NULL); if (mLayer != minimum_layer[screen].end()) { // If a window is raised above some other window in the same window group // which is in the ActiveLayer (i.e. it's fulscreened), make sure it stays // above that window (see #95731). if (*mLayer == ActiveLayer && (l == NormalLayer || l == AboveLayer)) l = ActiveLayer; *mLayer = l; } else if (c) { minimum_layer[screen].insertMulti(c->group(), l); } layer[ l ].append(*it); } ToplevelList stacking; for (Layer lay = FirstLayer; lay < NumLayers; ++lay) stacking += layer[ lay ]; #if 0 kDebug(1212) << "stacking2:"; for (ClientList::ConstIterator it = stacking.begin(); it != stacking.end(); ++it) kDebug(1212) << (void*)(*it) << *it << ":" << (*it)->layer(); #endif // now keep transients above their mainwindows // TODO this could(?) use some optimization for (int i = stacking.size() - 1; i >= 0; ) { Client *current = qobject_cast(stacking[i]); if (!current || !current->isTransient()) { --i; continue; } int i2 = -1; if (current->groupTransient()) { if (current->group()->members().count() > 0) { // find topmost client this one is transient for for (i2 = stacking.size() - 1; i2 >= 0; --i2) { if (stacking[ i2 ] == stacking[ i ]) { i2 = -1; // don't reorder, already the topmost in the group break; } Client *c2 = qobject_cast(stacking[ i2 ]); if (!c2) { continue; } if (c2->hasTransient(current, true) && keepTransientAbove(c2, current)) break; } } // else i2 remains pointing at -1 } else { for (i2 = stacking.size() - 1; i2 >= 0; --i2) { Client *c2 = qobject_cast(stacking[ i2 ]); if (!c2) { continue; } if (c2 == current) { i2 = -1; // don't reorder, already on top of its mainwindow break; } if (c2 == current->transientFor() && keepTransientAbove(c2, current)) break; } } if (i2 == -1) { --i; continue; } stacking.removeAt(i); --i; // move onto the next item (for next for () iteration) --i2; // adjust index of the mainwindow after the remove above if (!current->transients().isEmpty()) // this one now can be possibly above its transients, i = i2; // so go again higher in the stack order and possibly move those transients again ++i2; // insert after (on top of) the mainwindow, it's ok if it2 is now stacking.end() stacking.insert(i2, current); } #if 0 kDebug(1212) << "stacking3:"; for (ClientList::ConstIterator it = stacking.begin(); it != stacking.end(); ++it) kDebug(1212) << (void*)(*it) << *it << ":" << (*it)->layer(); kDebug(1212) << "\n\n"; #endif return stacking; } void Workspace::blockStackingUpdates(bool block) { if (block) { if (block_stacking_updates == 0) blocked_propagating_new_clients = false; ++block_stacking_updates; } else // !block if (--block_stacking_updates == 0) { updateStackingOrder(blocked_propagating_new_clients); if (effects) static_cast(effects)->checkInputWindowStacking(); } } // Ensure list is in stacking order ClientList Workspace::ensureStackingOrder(const ClientList& list) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); if (list.count() < 2) return list; // TODO is this worth optimizing? ClientList result = list; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (result.removeAll(c) != 0) result.append(c); } return result; } // check whether a transient should be actually kept above its mainwindow // there may be some special cases where this rule shouldn't be enfored bool Workspace::keepTransientAbove(const Client* mainwindow, const Client* transient) { // #93832 - don't keep splashscreens above dialogs if (transient->isSplash() && mainwindow->isDialog()) return false; // This is rather a hack for #76026. Don't keep non-modal dialogs above // the mainwindow, but only if they're group transient (since only such dialogs // have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker) // needs to be found. if (transient->isDialog() && !transient->isModal() && transient->groupTransient()) return false; // #63223 - don't keep transients above docks, because the dock is kept high, // and e.g. dialogs for them would be too high too if (mainwindow->isDock()) return false; return true; } // Returns all windows in their stacking order on the root window. ToplevelList Workspace::xStackingOrder() const { if (!x_stacking_dirty) return x_stacking; x_stacking_dirty = false; x_stacking.clear(); Window dummy; Window* windows = NULL; unsigned int count = 0; XQueryTree(display(), rootWindow(), &dummy, &dummy, &windows, &count); // use our own stacking order, not the X one, as they may differ foreach (Toplevel * c, stacking_order) x_stacking.append(c); for (unsigned int i = 0; i < count; ++i) { if (Unmanaged* c = findUnmanaged(WindowMatchPredicate(windows[ i ]))) x_stacking.append(c); } if (windows != NULL) XFree(windows); if (m_compositor) { const_cast< Workspace* >(this)->m_compositor->checkUnredirect(); } return x_stacking; } //******************************* // Client //******************************* void Client::restackWindow(Window above, int detail, NET::RequestSource src, Time timestamp, bool send_event) { Client *other = 0; if (detail == Opposite) { other = workspace()->findClient(WindowMatchPredicate(above)); if (!other) { workspace()->raiseOrLowerClient(this); return; } ToplevelList::const_iterator it = workspace()->stackingOrder().constBegin(), end = workspace()->stackingOrder().constEnd(); while (it != end) { if (*it == this) { detail = Above; break; } else if (*it == other) { detail = Below; break; } ++it; } } else if (detail == TopIf) { other = workspace()->findClient(WindowMatchPredicate(above)); if (other && other->geometry().intersects(geometry())) workspace()->raiseClientRequest(this, src, timestamp); return; } else if (detail == BottomIf) { other = workspace()->findClient(WindowMatchPredicate(above)); if (other && other->geometry().intersects(geometry())) workspace()->lowerClientRequest(this, src, timestamp); return; } if (!other) other = workspace()->findClient(WindowMatchPredicate(above)); if (other && detail == Above) { ToplevelList::const_iterator it = workspace()->stackingOrder().constEnd(), begin = workspace()->stackingOrder().constBegin(); while (--it != begin) { if (*it == other) { // the other one is top on stack it = begin; // invalidate src = NET::FromTool; // force break; } Client *c = qobject_cast(*it); if (!c || !( (*it)->isNormalWindow() && c->isShown(true) && (*it)->isOnCurrentDesktop() && (*it)->isOnCurrentActivity() && (*it)->isOnScreen(screen()) )) continue; // irrelevant clients if (*(it - 1) == other) break; // "it" is the one above the target one, stack below "it" } if (it != begin && (*(it - 1) == other)) other = qobject_cast(*it); else other = 0; } if (other) workspace()->restack(this, other); else if (detail == Below) workspace()->lowerClientRequest(this, src, timestamp); else if (detail == Above) workspace()->raiseClientRequest(this, src, timestamp); if (send_event) sendSyntheticConfigureNotify(); } void Client::setKeepAbove(bool b) { b = rules()->checkKeepAbove(b); if (b && !rules()->checkKeepBelow(false)) setKeepBelow(false); if (b == keepAbove()) { // force hint change if different if (bool(info->state() & NET::KeepAbove) != keepAbove()) info->setState(keepAbove() ? NET::KeepAbove : 0, NET::KeepAbove); return; } keep_above = b; info->setState(keepAbove() ? NET::KeepAbove : 0, NET::KeepAbove); if (decoration != NULL) decoration->emitKeepAboveChanged(keepAbove()); workspace()->updateClientLayer(this); updateWindowRules(Rules::Above); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Layer); emit keepAboveChanged(); } void Client::setKeepBelow(bool b) { b = rules()->checkKeepBelow(b); if (b && !rules()->checkKeepAbove(false)) setKeepAbove(false); if (b == keepBelow()) { // force hint change if different if (bool(info->state() & NET::KeepBelow) != keepBelow()) info->setState(keepBelow() ? NET::KeepBelow : 0, NET::KeepBelow); return; } keep_below = b; info->setState(keepBelow() ? NET::KeepBelow : 0, NET::KeepBelow); if (decoration != NULL) decoration->emitKeepBelowChanged(keepBelow()); workspace()->updateClientLayer(this); updateWindowRules(Rules::Below); // Update states of all other windows in this group if (tabGroup()) tabGroup()->updateStates(this, TabGroup::Layer); emit keepBelowChanged(); } Layer Client::layer() const { if (in_layer == UnknownLayer) const_cast< Client* >(this)->in_layer = belongsToLayer(); return in_layer; } Layer Client::belongsToLayer() const { if (isDesktop()) return DesktopLayer; if (isSplash()) // no damn annoying splashscreens return NormalLayer; // getting in the way of everything else if (isDock()) { // slight hack for the 'allow window to cover panel' Kicker setting // don't move keepbelow docks below normal window, but only to the same // layer, so that both may be raised to cover the other if (keepBelow()) return NormalLayer; if (keepAbove()) // slight hack for the autohiding panels return AboveLayer; return DockLayer; } if (keepBelow()) return BelowLayer; if (isActiveFullScreen()) return ActiveLayer; if (keepAbove()) return AboveLayer; return NormalLayer; } bool rec_checkTransientOnTop(const ClientList &transients, const Client *topmost) { foreach (const Client *transient, transients) { if (transient == topmost || rec_checkTransientOnTop(transient->transients(), topmost)) { return true; } } return false; } bool Client::isActiveFullScreen() const { if (!isFullScreen()) return false; -// const Client* ac = workspace()->mostRecentlyActivatedClient(); // instead of activeClient() - avoids flicker -// if (!ac) -// return false; -// not needed, for xinerama -> && ( ac == this || this->group() == ac->group()) - - // only raise fullscreen above docks if it's the topmost window in unconstrained stacking order, - // i.e. the window set to be topmost by the user (also includes transients of the fullscreen window) - const Client* top = workspace()->topClientOnDesktop(VirtualDesktopManager::self()->current(), screen(), true, false); - if (!top) - return false; - - // check whether we ... - if (top == this) - return true; - // ... or one of our transients is topmost - return rec_checkTransientOnTop(transients_list, top); + const Client* ac = workspace()->mostRecentlyActivatedClient(); // instead of activeClient() - avoids flicker + // according to NETWM spec implementation notes suggests + // "focused windows having state _NET_WM_STATE_FULLSCREEN" to be on the highest layer. + // we'll also take the screen into account + return ac && (ac == this || this->group() == ac->group() || ac->screen() != screen()); } } // namespace diff --git a/workspace.cpp b/workspace.cpp index 46c103440..11b76cc27 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -1,2063 +1,2070 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak 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, see . *********************************************************************/ //#define QT_CLEAN_NAMESPACE #include "workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "client.h" #include "composite.h" #include "focuschain.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "atoms.h" #include "cursor.h" #include "placement.h" #include "notifications.h" #include "outline.h" #include "group.h" #include "rules.h" #include "dbusinterface.h" #include "unmanaged.h" #include "deleted.h" #include "effects.h" #include "overlaywindow.h" #include "useractions.h" #include "virtualdesktops.h" #include "xcbutils.h" #include #include #ifdef KWIN_BUILD_SCREENEDGES #include "screenedge.h" #endif #ifdef KWIN_BUILD_SCRIPTING #include "scripting/scripting.h" #endif #include #include #include #include #include #include #include #include namespace KWin { extern int screen_number; extern bool is_multihead; #ifdef KWIN_BUILD_KAPPMENU static const char *KDED_SERVICE = "org.kde.kded"; static const char *KDED_APPMENU_PATH = "/modules/appmenu"; static const char *KDED_INTERFACE = "org.kde.kded"; #endif Workspace* Workspace::_self = 0; Workspace::Workspace(bool restore) : QObject(0) , m_compositor(NULL) // Unsorted , active_popup(NULL) , active_popup_client(NULL) , temporaryRulesMessages("_KDE_NET_WM_TEMPORARY_RULES", NULL) , rules_updates_disabled(false) , active_client(0) , last_active_client(0) , most_recently_raised(0) , movingClient(0) , pending_take_activity(NULL) , active_screen(0) , delayfocus_client(0) , force_restacking(false) , x_stacking_dirty(true) , showing_desktop(false) , block_showing_desktop(0) , was_user_interaction(false) , session_saving(false) , block_focus(0) #ifdef KWIN_BUILD_TABBOX , tab_box(0) #endif , m_userActionsMenu(new UserActionsMenu(this)) , keys(0) , client_keys(NULL) , disable_shortcuts_keys(NULL) , client_keys_dialog(NULL) , client_keys_client(NULL) , global_shortcuts_disabled(false) , global_shortcuts_disabled_for_client(false) , workspaceInit(true) , startup(0) , set_active_client_recursion(0) , block_stacking_updates(0) , forced_global_mouse_grab(false) , m_scripting(NULL) { // If KWin was already running it saved its configuration after loosing the selection -> Reread QFuture reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration); #ifdef KWIN_BUILD_KAPPMENU QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.connect(KDED_SERVICE, KDED_APPMENU_PATH, KDED_INTERFACE, "showRequest", this, SLOT(slotShowRequest(qulonglong))); dbus.connect(KDED_SERVICE, KDED_APPMENU_PATH, KDED_INTERFACE, "menuAvailable", this, SLOT(slotMenuAvailable(qulonglong))); dbus.connect(KDED_SERVICE, KDED_APPMENU_PATH, KDED_INTERFACE, "menuHidden", this, SLOT(slotMenuHidden(qulonglong))); dbus.connect(KDED_SERVICE, KDED_APPMENU_PATH, KDED_INTERFACE, "clearMenus", this, SLOT(slotClearMenus())); #endif _self = this; // first initialize the extensions Extensions::init(); Xcb::Extensions::self(); // start the cursor support Cursor::create(this); // PluginMgr needs access to the config file, so we need to wait for it for finishing reparseConfigFuture.waitForFinished(); options->loadConfig(); options->loadCompositingConfig(false); mgr = new PluginMgr; default_colormap = DefaultColormap(display(), screen_number); installed_colormap = default_colormap; connect(&temporaryRulesMessages, SIGNAL(gotMessage(QString)), this, SLOT(gotTemporaryRulesMessage(QString))); connect(&rulesUpdatedTimer, SIGNAL(timeout()), this, SLOT(writeWindowRules())); updateXTime(); // Needed for proper initialization of user_time in Client ctor delayFocusTimer = 0; if (restore) loadSessionInfo(); loadWindowRules(); // Call this before XSelectInput() on the root window startup = new KStartupInfo( KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this); // Select windowmanager privileges XSelectInput(display(), rootWindow(), KeyPressMask | PropertyChangeMask | ColormapChangeMask | SubstructureRedirectMask | SubstructureNotifyMask | FocusChangeMask | // For NotifyDetailNone ExposureMask ); #ifdef KWIN_BUILD_SCREENEDGES ScreenEdges::create(this); #endif // VirtualDesktopManager needs to be created prior to init shortcuts // and prior to TabBox, due to TabBox connecting to signals // actual initialization happens in init() VirtualDesktopManager::create(this); #ifdef KWIN_BUILD_TABBOX // need to create the tabbox before compositing scene is setup tab_box = new TabBox::TabBox(this); #endif m_compositor = Compositor::createCompositor(this); connect(this, SIGNAL(currentDesktopChanged(int,KWin::Client*)), m_compositor, SLOT(addRepaintFull())); connect(m_compositor, SIGNAL(compositingToggled(bool)), SLOT(slotCompositingToggled())); new DBusInterface(this); // Compatibility long data = 1; XChangeProperty( display(), rootWindow(), atoms->kwin_running, atoms->kwin_running, 32, PropModeAppend, (unsigned char*)(&data), 1 ); client_keys = new KActionCollection(this); m_outline = new Outline(); initShortcuts(); init(); connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), &screenChangedTimer, SLOT(start())); connect(QApplication::desktop(), SIGNAL(resized(int)), &screenChangedTimer, SLOT(start())); #ifdef KWIN_BUILD_ACTIVITIES connect(&activityController_, SIGNAL(currentActivityChanged(QString)), SLOT(updateCurrentActivity(QString))); connect(&activityController_, SIGNAL(activityRemoved(QString)), SLOT(slotActivityRemoved(QString))); connect(&activityController_, SIGNAL(activityRemoved(QString)), SIGNAL(activityRemoved(QString))); connect(&activityController_, SIGNAL(activityAdded(QString)), SLOT(slotActivityAdded(QString))); connect(&activityController_, SIGNAL(activityAdded(QString)), SIGNAL(activityAdded(QString))); connect(&activityController_, SIGNAL(currentActivityChanged(QString)), SIGNAL(currentActivityChanged(QString))); #endif connect(&screenChangedTimer, SIGNAL(timeout()), SLOT(screenChangeTimeout())); screenChangedTimer.setSingleShot(true); screenChangedTimer.setInterval(100); } void Workspace::screenChangeTimeout() { kDebug() << "It is time to call desktopResized"; desktopResized(); } void Workspace::init() { #ifdef KWIN_BUILD_SCREENEDGES ScreenEdges *screenEdges = ScreenEdges::self(); screenEdges->setConfig(KGlobal::config()); screenEdges->init(); connect(options, SIGNAL(configChanged()), screenEdges, SLOT(reconfigure())); connect(VirtualDesktopManager::self(), SIGNAL(layoutChanged(int,int)), screenEdges, SLOT(updateLayout())); connect(this, SIGNAL(clientActivated(KWin::Client*)), screenEdges, SIGNAL(checkBlocking())); #endif FocusChain *focusChain = FocusChain::create(this); connect(this, SIGNAL(clientRemoved(KWin::Client*)), focusChain, SLOT(remove(KWin::Client*))); connect(this, SIGNAL(clientActivated(KWin::Client*)), focusChain, SLOT(setActiveClient(KWin::Client*))); connect(VirtualDesktopManager::self(), SIGNAL(countChanged(uint,uint)), focusChain, SLOT(resize(uint,uint))); connect(VirtualDesktopManager::self(), SIGNAL(currentChanged(uint,uint)), focusChain, SLOT(setCurrentDesktop(uint,uint))); connect(options, SIGNAL(separateScreenFocusChanged(bool)), focusChain, SLOT(setSeparateScreenFocus(bool))); focusChain->setSeparateScreenFocus(options->isSeparateScreenFocus()); supportWindow = new QWidget(NULL, Qt::X11BypassWindowManagerHint); XLowerWindow(display(), supportWindow->winId()); // See usage in layers.cpp XSetWindowAttributes attr; attr.override_redirect = 1; null_focus_window = XCreateWindow(display(), rootWindow(), -1, -1, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWOverrideRedirect, &attr); XMapWindow(display(), null_focus_window); unsigned long protocols[5] = { NET::Supported | NET::SupportingWMCheck | NET::ClientList | NET::ClientListStacking | NET::DesktopGeometry | NET::NumberOfDesktops | NET::CurrentDesktop | NET::ActiveWindow | NET::WorkArea | NET::CloseWindow | NET::DesktopNames | NET::WMName | NET::WMVisibleName | NET::WMDesktop | NET::WMWindowType | NET::WMState | NET::WMStrut | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMMoveResize | NET::WMFrameExtents | NET::WMPing , NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::UtilityMask | NET::SplashMask | // No compositing window types here unless we support them also as managed window types 0 , NET::Modal | //NET::Sticky | // Large desktops not supported (and probably never will be) NET::MaxVert | NET::MaxHoriz | NET::Shaded | NET::SkipTaskbar | NET::KeepAbove | //NET::StaysOnTop | // The same like KeepAbove NET::SkipPager | NET::Hidden | NET::FullScreen | NET::KeepBelow | NET::DemandsAttention | 0 , NET::WM2UserTime | NET::WM2StartupId | NET::WM2AllowedActions | NET::WM2RestackWindow | NET::WM2MoveResizeWindow | NET::WM2ExtendedStrut | NET::WM2KDETemporaryRules | NET::WM2ShowingDesktop | NET::WM2DesktopLayout | NET::WM2FullPlacement | NET::WM2FullscreenMonitors | NET::WM2KDEShadow | 0 , NET::ActionMove | NET::ActionResize | NET::ActionMinimize | NET::ActionShade | //NET::ActionStick | // Sticky state is not supported NET::ActionMaxVert | NET::ActionMaxHoriz | NET::ActionFullScreen | NET::ActionChangeDesktop | NET::ActionClose | 0 , }; if (hasDecorationPlugin() && mgr->factory()->supports(AbilityExtendIntoClientArea)) protocols[ NETRootInfo::PROTOCOLS2 ] |= NET::WM2FrameOverlap; rootInfo = new RootInfo(this, display(), supportWindow->winId(), "KWin", protocols, 5, screen_number); // create VirtualDesktopManager and perform dependency injection VirtualDesktopManager *vds = VirtualDesktopManager::self(); connect(vds, SIGNAL(desktopsRemoved(uint)), SLOT(moveClientsFromRemovedDesktops())); connect(vds, SIGNAL(countChanged(uint,uint)), SLOT(slotDesktopCountChanged(uint,uint))); connect(vds, SIGNAL(currentChanged(uint,uint)), SLOT(slotCurrentDesktopChanged(uint,uint))); vds->setNavigationWrappingAround(options->isRollOverDesktops()); connect(options, SIGNAL(rollOverDesktopsChanged(bool)), vds, SLOT(setNavigationWrappingAround(bool))); vds->setRootInfo(rootInfo); vds->setConfig(KGlobal::config()); // Now we know how many desktops we'll have, thus we initialize the positioning object Placement::create(this); // positioning object needs to be created before the virtual desktops are loaded. vds->load(); vds->updateLayout(); // Extra NETRootInfo instance in Client mode is needed to get the values of the properties NETRootInfo client_info(display(), NET::ActiveWindow | NET::CurrentDesktop); int initial_desktop; if (!kapp->isSessionRestored()) initial_desktop = client_info.currentDesktop(); else { KConfigGroup group(kapp->sessionConfig(), "Session"); initial_desktop = group.readEntry("desktop", 1); } if (!VirtualDesktopManager::self()->setCurrent(initial_desktop)) VirtualDesktopManager::self()->setCurrent(1); #ifdef KWIN_BUILD_ACTIVITIES updateActivityList(false, true); #endif reconfigureTimer.setSingleShot(true); updateToolWindowsTimer.setSingleShot(true); connect(&reconfigureTimer, SIGNAL(timeout()), this, SLOT(slotReconfigure())); connect(&updateToolWindowsTimer, SIGNAL(timeout()), this, SLOT(slotUpdateToolWindows())); connect(KGlobalSettings::self(), SIGNAL(appearanceChanged()), this, SLOT(reconfigure())); connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), this, SLOT(slotSettingsChanged(int))); connect(KGlobalSettings::self(), SIGNAL(blockShortcuts(int)), this, SLOT(slotBlockShortcuts(int))); active_client = NULL; rootInfo->setActiveWindow(None); focusToNull(); if (!kapp->isSessionRestored()) ++block_focus; // Because it will be set below { // Begin updates blocker block StackingUpdatesBlocker blocker(this); bool fixoffset = KCmdLineArgs::parsedArgs()->getOption("crashes").toInt() > 0; Xcb::Tree tree(rootWindow()); xcb_window_t *wins = xcb_query_tree_children(tree.data()); QVector windowAttributes(tree->children_len); QVector windowGeometries(tree->children_len); // Request the attributes and geometries of all toplevel windows for (int i = 0; i < tree->children_len; i++) { windowAttributes[i] = Xcb::WindowAttributes(wins[i]); windowGeometries[i] = Xcb::WindowGeometry(wins[i]); } // Get the replies for (int i = 0; i < tree->children_len; i++) { Xcb::WindowAttributes attr(windowAttributes.at(i)); if (attr.isNull()) { continue; } if (attr->override_redirect) { if (attr->map_state == XCB_MAP_STATE_VIEWABLE && attr->_class != XCB_WINDOW_CLASS_INPUT_ONLY) // ### This will request the attributes again createUnmanaged(wins[i]); } else if (attr->map_state != XCB_MAP_STATE_UNMAPPED) { if (fixoffset) { fixPositionAfterCrash(wins[i], windowGeometries[i].data()); } // ### This will request the attributes again createClient(wins[i], true); } } // Propagate clients, will really happen at the end of the updates blocker block updateStackingOrder(true); saveOldScreenSizes(); updateClientArea(); // NETWM spec says we have to set it to (0,0) if we don't support it NETPoint* viewports = new NETPoint[VirtualDesktopManager::self()->count()]; rootInfo->setDesktopViewport(VirtualDesktopManager::self()->count(), *viewports); delete[] viewports; QRect geom; for (int i = 0; i < QApplication::desktop()->screenCount(); i++) { geom |= QApplication::desktop()->screenGeometry(i); } NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo->setDesktopGeometry(-1, desktop_geometry); setShowingDesktop(false); } // End updates blocker block Client* new_active_client = NULL; if (!kapp->isSessionRestored()) { --block_focus; new_active_client = findClient(WindowMatchPredicate(client_info.activeWindow())); } if (new_active_client == NULL && activeClient() == NULL && should_get_focus.count() == 0) { // No client activated in manage() if (new_active_client == NULL) new_active_client = topClientOnDesktop(VirtualDesktopManager::self()->current(), -1); if (new_active_client == NULL && !desktops.isEmpty()) new_active_client = findDesktop(true, VirtualDesktopManager::self()->current()); } if (new_active_client != NULL) activateClient(new_active_client); #ifdef KWIN_BUILD_SCRIPTING m_scripting = new Scripting(this); #endif // SELI TODO: This won't work with unreasonable focus policies, // and maybe in rare cases also if the selected client doesn't // want focus workspaceInit = false; // broadcast that Workspace is ready, but first process all events. QMetaObject::invokeMethod(this, "workspaceInitialized", Qt::QueuedConnection); // TODO: ungrabXServer() } Workspace::~Workspace() { delete m_compositor; m_compositor = NULL; blockStackingUpdates(true); // TODO: grabXServer(); // Use stacking_order, so that kwin --replace keeps stacking order for (ToplevelList::iterator it = stacking_order.begin(), end = stacking_order.end(); it != end; ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } // Only release the window c->releaseWindow(true); // No removeClient() is called, it does more than just removing. // However, remove from some lists to e.g. prevent performTransiencyCheck() // from crashing. clients.removeAll(c); desktops.removeAll(c); } for (UnmanagedList::iterator it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it) (*it)->release(true); delete m_outline; XDeleteProperty(display(), rootWindow(), atoms->kwin_running); writeWindowRules(); KGlobal::config()->sync(); delete rootInfo; delete supportWindow; delete mgr; delete startup; delete Placement::self(); delete client_keys_dialog; while (!rules.isEmpty()) { delete rules.front(); rules.pop_front(); } foreach (SessionInfo * s, session) delete s; XDestroyWindow(display(), null_focus_window); // TODO: ungrabXServer(); Xcb::Extensions::destroy(); _self = 0; } Client* Workspace::createClient(Window w, bool is_mapped) { StackingUpdatesBlocker blocker(this); Client* c = new Client(this); connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); connect(c, SIGNAL(activeChanged()), m_compositor, SLOT(checkUnredirect())); connect(c, SIGNAL(fullScreenChanged()), m_compositor, SLOT(checkUnredirect())); connect(c, SIGNAL(geometryChanged()), m_compositor, SLOT(checkUnredirect())); connect(c, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), m_compositor, SLOT(checkUnredirect())); connect(c, SIGNAL(blockingCompositingChanged(KWin::Client*)), m_compositor, SLOT(updateCompositeBlocking(KWin::Client*))); #ifdef KWIN_BUILD_SCREENEDGES connect(c, SIGNAL(clientFullScreenSet(KWin::Client*,bool,bool)), ScreenEdges::self(), SIGNAL(checkBlocking())); #endif if (!c->manage(w, is_mapped)) { Client::deleteClient(c, Allowed); return NULL; } addClient(c, Allowed); return c; } Unmanaged* Workspace::createUnmanaged(Window w) { if (m_compositor && m_compositor->checkForOverlayWindow(w)) return NULL; Unmanaged* c = new Unmanaged(this); if (!c->track(w)) { Unmanaged::deleteUnmanaged(c, Allowed); return NULL; } connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); addUnmanaged(c, Allowed); emit unmanagedAdded(c); return c; } void Workspace::addClient(Client* c, allowed_t) { Group* grp = findGroup(c->window()); KWindowInfo info = KWindowSystem::windowInfo(c->window(), -1U, NET::WM2WindowClass); emit clientAdded(c); if (grp != NULL) grp->gotLeader(c); if (c->isDesktop()) { desktops.append(c); if (active_client == NULL && should_get_focus.isEmpty() && c->isOnCurrentDesktop()) requestFocus(c); // TODO: Make sure desktop is active after startup if there's no other window active } else { FocusChain::self()->update(c, FocusChain::Update); clients.append(c); } if (!unconstrained_stacking_order.contains(c)) unconstrained_stacking_order.append(c); // Raise if it hasn't got any stacking position yet if (!stacking_order.contains(c)) // It'll be updated later, and updateToolWindows() requires stacking_order.append(c); // c to be in stacking_order x_stacking_dirty = true; updateClientArea(); // This cannot be in manage(), because the client got added only now updateClientLayer(c); if (c->isDesktop()) { raiseClient(c); // If there's no active client, make this desktop the active one if (activeClient() == NULL && should_get_focus.count() == 0) activateClient(findDesktop(true, VirtualDesktopManager::self()->current())); } c->checkActiveModal(); checkTransients(c->window()); // SELI TODO: Does this really belong here? updateStackingOrder(true); // Propagate new client if (c->isUtility() || c->isMenu() || c->isToolbar()) updateToolWindows(true); checkNonExistentClients(); #ifdef KWIN_BUILD_TABBOX if (tabBox()->isDisplayed()) tab_box->reset(true); #endif #ifdef KWIN_BUILD_KAPPMENU if (m_windowsMenu.removeOne(c->window())) c->setAppMenuAvailable(); #endif } void Workspace::addUnmanaged(Unmanaged* c, allowed_t) { unmanaged.append(c); x_stacking_dirty = true; } /** * Destroys the client \a c */ void Workspace::removeClient(Client* c, allowed_t) { emit clientRemoved(c); if (c == active_popup_client) closeActivePopup(); if (m_userActionsMenu->isMenuClient(c)) { m_userActionsMenu->close(); } c->untab(QRect(), true); if (client_keys_client == c) setupWindowShortcutDone(false); if (!c->shortcut().isEmpty()) { c->setShortcut(QString()); // Remove from client_keys clientShortcutUpdated(c); // Needed, since this is otherwise delayed by setShortcut() and wouldn't run } if (c->isDialog()) Notify::raise(Notify::TransDelete); if (c->isNormalWindow()) Notify::raise(Notify::Delete); #ifdef KWIN_BUILD_TABBOX if (tabBox()->isDisplayed() && tabBox()->currentClient() == c) tab_box->nextPrev(true); #endif Q_ASSERT(clients.contains(c) || desktops.contains(c)); // TODO: if marked client is removed, notify the marked list clients.removeAll(c); desktops.removeAll(c); x_stacking_dirty = true; attention_chain.removeAll(c); showing_desktop_clients.removeAll(c); Group* group = findGroup(c->window()); if (group != NULL) group->lostLeader(); if (c == most_recently_raised) most_recently_raised = 0; should_get_focus.removeAll(c); Q_ASSERT(c != active_client); if (c == last_active_client) last_active_client = 0; if (c == pending_take_activity) pending_take_activity = NULL; if (c == delayfocus_client) cancelDelayFocus(); updateStackingOrder(true); if (m_compositor) { m_compositor->updateCompositeBlocking(); } #ifdef KWIN_BUILD_TABBOX if (tabBox()->isDisplayed()) tab_box->reset(true); #endif updateClientArea(); } void Workspace::removeUnmanaged(Unmanaged* c, allowed_t) { assert(unmanaged.contains(c)); unmanaged.removeAll(c); x_stacking_dirty = true; } void Workspace::addDeleted(Deleted* c, Toplevel *orig, allowed_t) { assert(!deleted.contains(c)); deleted.append(c); const int unconstraintedIndex = unconstrained_stacking_order.indexOf(orig); if (unconstraintedIndex != -1) { unconstrained_stacking_order.replace(unconstraintedIndex, c); } else { unconstrained_stacking_order.append(c); } const int index = stacking_order.indexOf(orig); if (index != -1) { stacking_order.replace(index, c); } else { stacking_order.append(c); } x_stacking_dirty = true; connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); } void Workspace::removeDeleted(Deleted* c, allowed_t) { assert(deleted.contains(c)); emit deletedRemoved(c); deleted.removeAll(c); unconstrained_stacking_order.removeAll(c); stacking_order.removeAll(c); x_stacking_dirty = true; } void Workspace::updateToolWindows(bool also_hide) { // TODO: What if Client's transiency/group changes? should this be called too? (I'm paranoid, am I not?) if (!options->isHideUtilityWindowsForInactive()) { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) if (!(*it)->tabGroup() || (*it)->tabGroup()->current() == *it) (*it)->hideClient(false); return; } const Group* group = NULL; const Client* client = active_client; // Go up in transiency hiearchy, if the top is found, only tool transients for the top mainwindow // will be shown; if a group transient is group, all tools in the group will be shown while (client != NULL) { if (!client->isTransient()) break; if (client->groupTransient()) { group = client->group(); break; } client = client->transientFor(); } // Use stacking order only to reduce flicker, it doesn't matter if block_stacking_updates == 0, // I.e. if it's not up to date // SELI TODO: But maybe it should - what if a new client has been added that's not in stacking order yet? ClientList to_show, to_hide; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (c->isUtility() || c->isMenu() || c->isToolbar()) { bool show = true; if (!c->isTransient()) { if (c->group()->members().count() == 1) // Has its own group, keep always visible show = true; else if (client != NULL && c->group() == client->group()) show = true; else show = false; } else { if (group != NULL && c->group() == group) show = true; else if (client != NULL && client->hasTransient(c, true)) show = true; else show = false; } if (!show && also_hide) { const ClientList mainclients = c->mainClients(); // Don't hide utility windows which are standalone(?) or // have e.g. kicker as mainwindow if (mainclients.isEmpty()) show = true; for (ClientList::ConstIterator it2 = mainclients.constBegin(); it2 != mainclients.constEnd(); ++it2) { if (c->isSpecialWindow()) show = true; } if (!show) to_hide.append(c); } if (show) to_show.append(c); } } // First show new ones, then hide for (int i = to_show.size() - 1; i >= 0; --i) // From topmost // TODO: Since this is in stacking order, the order of taskbar entries changes :( to_show.at(i)->hideClient(false); if (also_hide) { for (ClientList::ConstIterator it = to_hide.constBegin(); it != to_hide.constEnd(); ++it) // From bottommost (*it)->hideClient(true); updateToolWindowsTimer.stop(); } else // setActiveClient() is after called with NULL client, quickly followed // by setting a new client, which would result in flickering resetUpdateToolWindowsTimer(); } void Workspace::resetUpdateToolWindowsTimer() { updateToolWindowsTimer.start(200); } void Workspace::slotUpdateToolWindows() { updateToolWindows(true); } /** * Updates the current colormap according to the currently active client */ void Workspace::updateColormap() { Colormap cmap = default_colormap; if (activeClient() && activeClient()->colormap() != None) cmap = activeClient()->colormap(); if (cmap != installed_colormap) { XInstallColormap(display(), cmap); installed_colormap = cmap; } } void Workspace::slotReloadConfig() { reconfigure(); } #ifdef KWIN_BUILD_KAPPMENU void Workspace::slotShowRequest(qulonglong wid) { if (Client *c = findClient(WindowMatchPredicate(wid))) c->emitShowRequest(); } void Workspace::slotMenuAvailable(qulonglong wid) { if (Client *c = findClient(WindowMatchPredicate(wid))) c->setAppMenuAvailable(); else m_windowsMenu.append(wid); } void Workspace::slotMenuHidden(qulonglong wid) { if (Client *c = findClient(WindowMatchPredicate(wid))) c->emitMenuHidden(); } void Workspace::slotClearMenus() { foreach (Client *c, clients) { c->setAppMenuUnavailable(); } } #endif void Workspace::reconfigure() { reconfigureTimer.start(200); } /** * This D-Bus call is used by the compositing kcm. Since the reconfigure() * D-Bus call delays the actual reconfiguring, it is not possible to immediately * call compositingActive(). Therefore the kcm will instead call this to ensure * the reconfiguring has already happened. */ bool Workspace::waitForCompositingSetup() { if (reconfigureTimer.isActive()) { reconfigureTimer.stop(); slotReconfigure(); } if (m_compositor) { return m_compositor->isActive(); } return false; } void Workspace::slotSettingsChanged(int category) { kDebug(1212) << "Workspace::slotSettingsChanged()"; if (category == KGlobalSettings::SETTINGS_SHORTCUTS) m_userActionsMenu->discard(); } /** * Reread settings */ KWIN_PROCEDURE(CheckBorderSizesProcedure, Client, cl->checkBorderSizes(true)); void Workspace::slotReconfigure() { kDebug(1212) << "Workspace::slotReconfigure()"; reconfigureTimer.stop(); bool borderlessMaximizedWindows = options->borderlessMaximizedWindows(); KGlobal::config()->reparseConfiguration(); unsigned long changed = options->updateSettings(); emit configChanged(); m_userActionsMenu->discard(); updateToolWindows(true); if (hasDecorationPlugin() && mgr->reset(changed)) { // Decorations need to be recreated // This actually seems to make things worse now //QWidget curtain; //curtain.setBackgroundMode( NoBackground ); //curtain.setGeometry( Kephal::ScreenUtils::desktopGeometry() ); //curtain.show(); for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->updateDecoration(true, true); // If the new decoration doesn't supports tabs then ungroup clients if (!decorationSupportsTabbing()) { foreach (Client * c, clients) c->untab(); } mgr->destroyPreviousPlugin(); } else { forEachClient(CheckBorderSizesProcedure()); foreach (Client * c, clients) c->triggerDecorationRepaint(); } loadWindowRules(); for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { (*it)->setupWindowRules(true); (*it)->applyWindowRules(); discardUsedWindowRules(*it, false); } if (borderlessMaximizedWindows != options->borderlessMaximizedWindows() && !options->borderlessMaximizedWindows()) { // in case borderless maximized windows option changed and new option // is to have borders, we need to unset the borders for all maximized windows for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) { if ((*it)->maximizeMode() == MaximizeFull) (*it)->checkNoBorder(); } } if (hasDecorationPlugin()) { rootInfo->setSupported(NET::WM2FrameOverlap, mgr->factory()->supports(AbilityExtendIntoClientArea)); } else { rootInfo->setSupported(NET::WM2FrameOverlap, false); } } /** * Avoids managing a window with title \a title */ void Workspace::doNotManage(const QString& title) { doNotManageList.append(title); } /** * Hack for java applets */ bool Workspace::isNotManaged(const QString& title) { for (QStringList::Iterator it = doNotManageList.begin(); it != doNotManageList.end(); ++it) { QRegExp r((*it)); if (r.indexIn(title) != -1) { doNotManageList.erase(it); return true; } } return false; } /** * During virt. desktop switching, desktop areas covered by windows that are * going to be hidden are first obscured by new windows with no background * ( i.e. transparent ) placed right below the windows. These invisible windows * are removed after the switch is complete. * Reduces desktop ( wallpaper ) repaints during desktop switching */ class ObscuringWindows { public: ~ObscuringWindows(); void create(Client* c); private: QList obscuring_windows; static QList* cached; static unsigned int max_cache_size; }; QList* ObscuringWindows::cached = 0; unsigned int ObscuringWindows::max_cache_size = 0; void ObscuringWindows::create(Client* c) { if (cached == 0) cached = new QList; Window obs_win; XWindowChanges chngs; int mask = CWSibling | CWStackMode; if (cached->count() > 0) { cached->removeAll(obs_win = cached->first()); chngs.x = c->x(); chngs.y = c->y(); chngs.width = c->width(); chngs.height = c->height(); mask |= CWX | CWY | CWWidth | CWHeight; } else { XSetWindowAttributes a; a.background_pixmap = None; a.override_redirect = True; obs_win = XCreateWindow(display(), rootWindow(), c->x(), c->y(), c->width(), c->height(), 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixmap | CWOverrideRedirect, &a); } chngs.sibling = c->frameId(); chngs.stack_mode = Below; XConfigureWindow(display(), obs_win, mask, &chngs); XMapWindow(display(), obs_win); obscuring_windows.append(obs_win); } ObscuringWindows::~ObscuringWindows() { max_cache_size = qMax(int(max_cache_size), obscuring_windows.count() + 4) - 1; for (QList::ConstIterator it = obscuring_windows.constBegin(); it != obscuring_windows.constEnd(); ++it) { XUnmapWindow(display(), *it); if (cached->count() < int(max_cache_size)) cached->prepend(*it); else XDestroyWindow(display(), *it); } } void Workspace::slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop) { closeActivePopup(); ++block_focus; StackingUpdatesBlocker blocker(this); updateClientVisibilityOnDesktopChange(oldDesktop, newDesktop); // Restore the focus on this desktop --block_focus; activateClientOnNewDesktop(newDesktop); emit currentDesktopChanged(oldDesktop, movingClient); } void Workspace::updateClientVisibilityOnDesktopChange(uint oldDesktop, uint newDesktop) { ++block_showing_desktop; ObscuringWindows obs_wins; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnDesktop(newDesktop) && c != movingClient && c->isOnCurrentActivity()) { if (c->isShown(true) && c->isOnDesktop(oldDesktop) && !compositing()) obs_wins.create(c); (c)->updateVisibility(); } } // Now propagate the change, after hiding, before showing rootInfo->setCurrentDesktop(VirtualDesktopManager::self()->current()); if (movingClient && !movingClient->isOnDesktop(newDesktop)) { movingClient->setDesktop(newDesktop); } for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnDesktop(newDesktop) && c->isOnCurrentActivity()) c->updateVisibility(); } --block_showing_desktop; if (showingDesktop()) // Do this only after desktop change to avoid flicker resetShowingDesktop(false); } void Workspace::activateClientOnNewDesktop(uint desktop) { Client* c = NULL; if (options->focusPolicyIsReasonable()) { c = findClientToActivateOnDesktop(desktop); } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop()) c = active_client; if (c == NULL && !desktops.isEmpty()) c = findDesktop(true, desktop); if (c != active_client) setActiveClient(NULL, Allowed); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, desktop)); else focusToNull(); } Client *Workspace::findClientToActivateOnDesktop(uint desktop) { if (movingClient != NULL && active_client == movingClient && FocusChain::self()->contains(active_client, desktop) && active_client->isShown(true) && active_client->isOnCurrentDesktop()) { // A requestFocus call will fail, as the client is already active return active_client; } // from actiavtion.cpp if (options->isNextFocusPrefersMouse()) { ToplevelList::const_iterator it = stackingOrder().constEnd(); while (it != stackingOrder().constBegin()) { Client *client = qobject_cast(*(--it)); if (!client) { continue; } if (!(client->isShown(false) && client->isOnDesktop(desktop) && client->isOnCurrentActivity() && client->isOnScreen(activeScreen()))) continue; if (client->geometry().contains(Cursor::pos())) { if (!client->isDesktop()) return client; break; // unconditional break - we do not pass the focus to some client below an unusable one } } } return FocusChain::self()->getForActivation(desktop); } #ifdef KWIN_BUILD_ACTIVITIES //BEGIN threaded activity list fetching typedef QPair AssignedList; typedef QPair CurrentAndList; static AssignedList fetchActivityList(KActivities::Controller *controller, QStringList *target, bool running) // could be member function, but actually it's much simpler this way { return AssignedList(target, running ? controller->listActivities(KActivities::Info::Running) : controller->listActivities()); } static CurrentAndList fetchActivityListAndCurrent(KActivities::Controller *controller) { QStringList l = controller->listActivities(); QString c = controller->currentActivity(); return CurrentAndList(c, l); } void Workspace::updateActivityList(bool running, bool updateCurrent, QObject *target, QString slot) { if (updateCurrent) { QFutureWatcher* watcher = new QFutureWatcher; connect( watcher, SIGNAL(finished()), SLOT(handleActivityReply()) ); if (!slot.isEmpty()) { watcher->setProperty("activityControllerCallback", slot); // "activity reply trigger" watcher->setProperty("activityControllerCallbackTarget", qVariantFromValue((void*)target)); } watcher->setFuture(QtConcurrent::run(fetchActivityListAndCurrent, &activityController_ )); } else { QFutureWatcher* watcher = new QFutureWatcher; connect(watcher, SIGNAL(finished()), SLOT(handleActivityReply())); if (!slot.isEmpty()) { watcher->setProperty("activityControllerCallback", slot); // "activity reply trigger" watcher->setProperty("activityControllerCallbackTarget", qVariantFromValue((void*)target)); } QStringList *target = running ? &openActivities_ : &allActivities_; watcher->setFuture(QtConcurrent::run(fetchActivityList, &activityController_, target, running)); } } void Workspace::handleActivityReply() { QObject *watcherObject = 0; if (QFutureWatcher* watcher = dynamic_cast< QFutureWatcher* >(sender())) { *(watcher->result().first) = watcher->result().second; // cool trick, ehh? :-) watcherObject = watcher; } if (!watcherObject) { if (QFutureWatcher* watcher = dynamic_cast< QFutureWatcher* >(sender())) { allActivities_ = watcher->result().second; updateCurrentActivity(watcher->result().first); emit currentActivityChanged(currentActivity()); watcherObject = watcher; } } if (watcherObject) { QString slot = watcherObject->property("activityControllerCallback").toString(); QObject *target = static_cast(watcherObject->property("activityControllerCallbackTarget").value()); watcherObject->deleteLater(); // has done it's job if (!slot.isEmpty()) QMetaObject::invokeMethod(target, slot.toAscii().data(), Qt::DirectConnection); } } //END threaded activity list fetching #else // make gcc happy - stupd moc cannot handle preproc defs so we MUST define void Workspace::handleActivityReply() {} #endif // KWIN_BUILD_ACTIVITIES /** * Updates the current activity when it changes * do *not* call this directly; it does not set the activity. * * Shows/Hides windows according to the stacking order */ void Workspace::updateCurrentActivity(const QString &new_activity) { //closeActivePopup(); ++block_focus; // TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date StackingUpdatesBlocker blocker(this); if (new_activity != activity_) { ++block_showing_desktop; //FIXME should I be using that? // Optimized Desktop switching: unmapping done from back to front // mapping done from front to back => less exposure events //Notify::raise((Notify::Event) (Notify::DesktopChange+new_desktop)); ObscuringWindows obs_wins; QString old_activity = activity_; activity_ = new_activity; for (ToplevelList::ConstIterator it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnActivity(new_activity) && c != movingClient && c->isOnCurrentDesktop()) { if (c->isShown(true) && c->isOnActivity(old_activity) && !compositing()) obs_wins.create(c); c->updateVisibility(); } } // Now propagate the change, after hiding, before showing //rootInfo->setCurrentDesktop( currentDesktop() ); /* TODO someday enable dragging windows to other activities if ( movingClient && !movingClient->isOnDesktop( new_desktop )) { movingClient->setDesktop( new_desktop ); */ for (int i = stacking_order.size() - 1; i >= 0 ; --i) { Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnActivity(new_activity)) c->updateVisibility(); } --block_showing_desktop; //FIXME not sure if I should do this either if (showingDesktop()) // Do this only after desktop change to avoid flicker resetShowingDesktop(false); } // Restore the focus on this desktop --block_focus; Client* c = 0; //FIXME below here is a lot of focuschain stuff, probably all wrong now if (options->focusPolicyIsReasonable()) { // Search in focus chain c = FocusChain::self()->getForActivation(VirtualDesktopManager::self()->current()); } // If "unreasonable focus policy" and active_client is on_all_desktops and // under mouse (Hence == old_active_client), conserve focus. // (Thanks to Volker Schatz ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop() && active_client->isOnCurrentActivity()) c = active_client; if (c == NULL && !desktops.isEmpty()) c = findDesktop(true, VirtualDesktopManager::self()->current()); if (c != active_client) setActiveClient(NULL, Allowed); if (c) requestFocus(c); else if (!desktops.isEmpty()) requestFocus(findDesktop(true, VirtualDesktopManager::self()->current())); else focusToNull(); // Not for the very first time, only if something changed and there are more than 1 desktops //if ( effects != NULL && old_desktop != 0 && old_desktop != new_desktop ) // static_cast( effects )->desktopChanged( old_desktop ); if (compositing() && m_compositor) m_compositor->addRepaintFull(); } /** * updates clients when an activity is destroyed. * this ensures that a client does not get 'lost' if the only activity it's on is removed. */ void Workspace::slotActivityRemoved(const QString &activity) { allActivities_.removeOne(activity); foreach (Toplevel * toplevel, stacking_order) { if (Client *client = qobject_cast(toplevel)) { client->setOnActivity(activity, false); } } //toss out any session data for it KConfigGroup cg(KGlobal::config(), QString("SubSession: ") + activity); cg.deleteGroup(); } void Workspace::slotActivityAdded(const QString &activity) { allActivities_ << activity; } void Workspace::moveClientsFromRemovedDesktops() { for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { if (!(*it)->isOnAllDesktops() && (*it)->desktop() > static_cast(VirtualDesktopManager::self()->count())) sendClientToDesktop(*it, VirtualDesktopManager::self()->count(), true); } } void Workspace::slotDesktopCountChanged(uint previousCount, uint newCount) { Q_UNUSED(previousCount) Placement::self()->reinitCascading(0); resetClientAreas(newCount); } void Workspace::resetClientAreas(uint desktopCount) { // Make it +1, so that it can be accessed as [1..numberofdesktops] workarea.clear(); workarea.resize(desktopCount + 1); restrictedmovearea.clear(); restrictedmovearea.resize(desktopCount + 1); screenarea.clear(); updateClientArea(true); } /** * Sends client \a c to desktop \a desk. * * Takes care of transients as well. */ void Workspace::sendClientToDesktop(Client* c, int desk, bool dont_activate) { if ((desk < 1 && desk != NET::OnAllDesktops) || desk > static_cast(VirtualDesktopManager::self()->count())) return; int old_desktop = c->desktop(); bool was_on_desktop = c->isOnDesktop(desk) || c->isOnAllDesktops(); c->setDesktop(desk); if (c->desktop() != desk) // No change or desktop forced return; desk = c->desktop(); // Client did range checking emit desktopPresenceChanged(c, old_desktop); if (c->isOnDesktop(VirtualDesktopManager::self()->current())) { if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_desktop && // for stickyness changes !dont_activate) requestFocus(c); else restackClientUnderActive(c); } else raiseClient(c); c->checkWorkspacePosition( QRect(), old_desktop ); ClientList transients_stacking_order = ensureStackingOrder(c->transients()); for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) sendClientToDesktop(*it, desk, dont_activate); updateClientArea(); } /** * Adds/removes client \a c to/from \a activity. * * Takes care of transients as well. */ void Workspace::toggleClientOnActivity(Client* c, const QString &activity, bool dont_activate) { //int old_desktop = c->desktop(); bool was_on_activity = c->isOnActivity(activity); bool was_on_all = c->isOnAllActivities(); //note: all activities === no activities bool enable = was_on_all || !was_on_activity; c->setOnActivity(activity, enable); if (c->isOnActivity(activity) == was_on_activity && c->isOnAllActivities() == was_on_all) // No change return; if (c->isOnCurrentActivity()) { if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_activity && // for stickyness changes //FIXME not sure if the line above refers to the correct activity !dont_activate) requestFocus(c); else restackClientUnderActive(c); } else raiseClient(c); //notifyWindowDesktopChanged( c, old_desktop ); ClientList transients_stacking_order = ensureStackingOrder(c->transients()); for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) toggleClientOnActivity(*it, activity, dont_activate); updateClientArea(); } int Workspace::numScreens() const { return QApplication::desktop()->screenCount(); } int Workspace::activeScreen() const { if (!options->isActiveMouseScreen()) { if (activeClient() != NULL && !activeClient()->isOnScreen(active_screen)) return activeClient()->screen(); return active_screen; } return QApplication::desktop()->screenNumber(cursorPos()); } /** * Check whether a client moved completely out of what's considered the active screen, * if yes, set a new active screen. */ void Workspace::checkActiveScreen(const Client* c) { if (!c->isActive()) return; if (!c->isOnScreen(active_screen)) active_screen = c->screen(); } /** * checks whether the X Window with the input focus is on our X11 screen * if the window cannot be determined or inspected, resturn depends on whether there's actually * more than one screen * * this is NOT in any way related to XRandR multiscreen * */ extern bool is_multihead; // main.cpp bool Workspace::isOnCurrentHead() { if (!is_multihead) { return true; } Xcb::CurrentInput currentInput; if (currentInput.window() == XCB_WINDOW_NONE) { return !is_multihead; } Xcb::WindowGeometry geometry(currentInput.window()); if (geometry.isNull()) { // should not happen return !is_multihead; } return rootWindow() == geometry->root; } /** * Called e.g. when a user clicks on a window, set active screen to be the screen * where the click occurred */ void Workspace::setActiveScreenMouse(const QPoint& mousepos) { active_screen = QApplication::desktop()->screenNumber(mousepos); } QRect Workspace::screenGeometry(int screen) const { return QApplication::desktop()->screenGeometry(screen); } int Workspace::screenNumber(const QPoint& pos) const { return QApplication::desktop()->screenNumber(pos); } void Workspace::sendClientToScreen(Client* c, int screen) { screen = c->rules()->checkScreen(screen); + if (c->isActive()) { + active_screen = screen; + // might impact the layer of a fullscreen window + foreach (Client *cc, clientList()) { + if (cc->isFullScreen() && cc->screen() == screen) { + updateClientLayer(cc); + } + } + } if (c->screen() == screen) // Don't use isOnScreen(), that's true even when only partially return; GeometryUpdatesBlocker blocker(c); QRect old_sarea = clientArea(MaximizeArea, c); QRect sarea = clientArea(MaximizeArea, screen, c->desktop()); QRect oldgeom = c->geometry(); QRect geom = c->geometry(); // move the window to have the same relative position to the center of the screen // (i.e. one near the middle of the right edge will also end up near the middle of the right edge) geom.moveCenter( QPoint(( geom.center().x() - old_sarea.center().x()) * sarea.width() / old_sarea.width() + sarea.center().x(), ( geom.center().y() - old_sarea.center().y()) * sarea.height() / old_sarea.height() + sarea.center().y())); c->setGeometry( geom ); // If the window was inside the old screen area, explicitly make sure its inside also the new screen area. // Calling checkWorkspacePosition() should ensure that, but when moving to a small screen the window could // be big enough to overlap outside of the new screen area, making struts from other screens come into effect, // which could alter the resulting geometry. if( old_sarea.contains( oldgeom )) c->keepInArea( sarea ); c->checkWorkspacePosition( oldgeom ); ClientList transients_stacking_order = ensureStackingOrder(c->transients()); for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) sendClientToScreen(*it, screen); - if (c->isActive()) - active_screen = screen; } void Workspace::killWindowId(Window window_to_kill) { if (window_to_kill == None) return; Window window = window_to_kill; Client* client = NULL; for (;;) { client = findClient(FrameIdMatchPredicate(window)); if (client != NULL) break; // Found the client Window parent, root; Window* children; unsigned int children_count; XQueryTree(display(), window, &root, &parent, &children, &children_count); if (children != NULL) XFree(children); if (window == root) // We didn't find the client, probably an override-redirect window break; window = parent; // Go up } if (client != NULL) client->killWindow(); else XKillClient(display(), window_to_kill); } void Workspace::sendPingToWindow(Window window, Time timestamp) { rootInfo->sendPing(window, timestamp); } void Workspace::sendTakeActivity(Client* c, Time timestamp, long flags) { rootInfo->takeActivity(c->window(), timestamp, flags); pending_take_activity = c; } /** * Delayed focus functions */ void Workspace::delayFocus() { requestFocus(delayfocus_client); cancelDelayFocus(); } void Workspace::requestDelayFocus(Client* c) { delayfocus_client = c; delete delayFocusTimer; delayFocusTimer = new QTimer(this); connect(delayFocusTimer, SIGNAL(timeout()), this, SLOT(delayFocus())); delayFocusTimer->setSingleShot(true); delayFocusTimer->start(options->delayFocusInterval()); } void Workspace::cancelDelayFocus() { delete delayFocusTimer; delayFocusTimer = 0; } KDecoration* Workspace::createDecoration(KDecorationBridge* bridge) { if (!hasDecorationPlugin()) { return NULL; } return mgr->createDecoration(bridge); } /** * Returns a list of all colors (KDecorationDefines::ColorType) the current * decoration supports */ QList Workspace::decorationSupportedColors() const { QList ret; if (!hasDecorationPlugin()) { return ret; } KDecorationFactory* factory = mgr->factory(); for (Ability ab = ABILITYCOLOR_FIRST; ab < ABILITYCOLOR_END; ab = static_cast(ab + 1)) if (factory->supports(ab)) ret << ab; return ret; } bool Workspace::checkStartupNotification(Window w, KStartupInfoId& id, KStartupInfoData& data) { return startup->checkStartup(w, id, data) == KStartupInfo::Match; } /** * Puts the focus on a dummy window * Just using XSetInputFocus() with None would block keyboard input */ void Workspace::focusToNull() { XSetInputFocus(display(), null_focus_window, RevertToPointerRoot, xTime()); } void Workspace::setShowingDesktop(bool showing) { rootInfo->setShowingDesktop(showing); showing_desktop = showing; ++block_showing_desktop; if (showing_desktop) { showing_desktop_clients.clear(); ++block_focus; ToplevelList cls = stackingOrder(); // Find them first, then minimize, otherwise transients may get minimized with the window // they're transient for for (ToplevelList::ConstIterator it = cls.constBegin(); it != cls.constEnd(); ++it) { Client *c = qobject_cast(*it); if (!c) { continue; } if (c->isOnCurrentActivity() && c->isOnCurrentDesktop() && c->isShown(true) && !c->isSpecialWindow()) showing_desktop_clients.prepend(c); // Topmost first to reduce flicker } for (ClientList::ConstIterator it = showing_desktop_clients.constBegin(); it != showing_desktop_clients.constEnd(); ++it) (*it)->minimize(); --block_focus; if (Client* desk = findDesktop(true, VirtualDesktopManager::self()->current())) requestFocus(desk); } else { for (ClientList::ConstIterator it = showing_desktop_clients.constBegin(); it != showing_desktop_clients.constEnd(); ++it) (*it)->unminimize(); if (showing_desktop_clients.count() > 0) requestFocus(showing_desktop_clients.first()); showing_desktop_clients.clear(); } --block_showing_desktop; } /** * Following Kicker's behavior: * Changing a virtual desktop resets the state and shows the windows again. * Unminimizing a window resets the state but keeps the windows hidden (except * the one that was unminimized). * A new window resets the state and shows the windows again, with the new window * being active. Due to popular demand (#67406) by people who apparently * don't see a difference between "show desktop" and "minimize all", this is not * true if "showDesktopIsMinimizeAll" is set in kwinrc. In such case showing * a new window resets the state but doesn't show windows. */ void Workspace::resetShowingDesktop(bool keep_hidden) { if (block_showing_desktop > 0) return; rootInfo->setShowingDesktop(false); showing_desktop = false; ++block_showing_desktop; if (!keep_hidden) { for (ClientList::ConstIterator it = showing_desktop_clients.constBegin(); it != showing_desktop_clients.constEnd(); ++it) (*it)->unminimize(); } showing_desktop_clients.clear(); --block_showing_desktop; } /** * Activating/deactivating this feature works like this: * When nothing is active, and the shortcut is pressed, global shortcuts are disabled * (using global_shortcuts_disabled) * When a window that has disabling forced is activated, global shortcuts are disabled. * (using global_shortcuts_disabled_for_client) * When a shortcut is pressed and global shortcuts are disabled (either by a shortcut * or for a client), they are enabled again. */ void Workspace::slotDisableGlobalShortcuts() { if (global_shortcuts_disabled || global_shortcuts_disabled_for_client) disableGlobalShortcuts(false); else disableGlobalShortcuts(true); } static bool pending_dfc = false; void Workspace::disableGlobalShortcutsForClient(bool disable) { if (global_shortcuts_disabled_for_client == disable) return; if (!global_shortcuts_disabled) { if (disable) pending_dfc = true; KGlobalSettings::self()->emitChange(KGlobalSettings::BlockShortcuts, disable); // KWin will get the kipc message too } } void Workspace::disableGlobalShortcuts(bool disable) { KGlobalSettings::self()->emitChange(KGlobalSettings::BlockShortcuts, disable); // KWin will get the kipc message too } void Workspace::slotBlockShortcuts(int data) { if (pending_dfc && data) { global_shortcuts_disabled_for_client = true; pending_dfc = false; } else { global_shortcuts_disabled = data; global_shortcuts_disabled_for_client = false; } // Update also Alt+LMB actions etc. for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->updateMouseGrab(); } Outline* Workspace::outline() { return m_outline; } bool Workspace::hasTabBox() const { #ifdef KWIN_BUILD_TABBOX return (tab_box != NULL); #else return false; #endif } #ifdef KWIN_BUILD_TABBOX TabBox::TabBox* Workspace::tabBox() const { return tab_box; } #endif QString Workspace::supportInformation() const { QString support; support.append(ki18nc("Introductory text shown in the support information.", "KWin Support Information:\n" "The following information should be used when requesting support on e.g. http://forum.kde.org.\n" "It provides information about the currently running instance, which options are used,\n" "what OpenGL driver and which effects are running.\n" "Please post the information provided underneath this introductory text to a paste bin service\n" "like http://paste.kde.org instead of pasting into support threads.\n").toString()); support.append("\n==========================\n\n"); // all following strings are intended for support. They need to be pasted to e.g forums.kde.org // it is expected that the support will happen in English language or that the people providing // help understand English. Because of that all texts are not translated support.append("Version\n"); support.append("=======\n"); support.append("KWin version: "); support.append(KWIN_VERSION_STRING); support.append('\n'); support.append("KDE SC version (runtime): "); support.append(KDE::versionString()); support.append('\n'); support.append("KDE SC version (compile): "); support.append(KDE_VERSION_STRING); support.append('\n'); support.append("Qt Version: "); support.append(qVersion()); support.append("\n\n"); support.append("Options\n"); support.append("=======\n"); const QMetaObject *metaOptions = options->metaObject(); for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaOptions->property(i); if (QLatin1String(property.name()) == "objectName") { continue; } support.append(QLatin1String(property.name()) % ": " % options->property(property.name()).toString() % '\n'); } #ifdef KWIN_BUILD_SCREENEDGES support.append("\nScreen Edges\n"); support.append( "============\n"); const QMetaObject *metaScreenEdges = ScreenEdges::self()->metaObject(); for (int i=0; ipropertyCount(); ++i) { const QMetaProperty property = metaScreenEdges->property(i); if (QLatin1String(property.name()) == "objectName") { continue; } support.append(QLatin1String(property.name()) % ": " % ScreenEdges::self()->property(property.name()).toString() % '\n'); } #endif support.append("\nScreens\n"); support.append( "=======\n"); support.append("Multi-Head: "); if (is_multihead) { support.append("yes\n"); support.append(QString("Head: %1\n").arg(screen_number)); } else { support.append("no\n"); } support.append(QString("Number of Screens: %1\n").arg(QApplication::desktop()->screenCount())); for (int i=0; iscreenCount(); ++i) { const QRect geo = QApplication::desktop()->screenGeometry(i); support.append(QString("Screen %1 Geometry: %2,%3,%4x%5\n") .arg(i) .arg(geo.x()) .arg(geo.y()) .arg(geo.width()) .arg(geo.height())); } support.append("\nCompositing\n"); support.append( "===========\n"); support.append("Qt Graphics System: "); if (Extensions::nonNativePixmaps()) { support.append("raster\n"); } else { support.append("native\n"); } if (effects) { support.append("Compositing is active\n"); switch (effects->compositingType()) { case OpenGL1Compositing: case OpenGL2Compositing: case OpenGLCompositing: { #ifdef KWIN_HAVE_OPENGLES support.append("Compositing Type: OpenGL ES 2.0\n"); #else support.append("Compositing Type: OpenGL\n"); #endif GLPlatform *platform = GLPlatform::instance(); support.append("OpenGL vendor string: " % platform->glVendorString() % '\n'); support.append("OpenGL renderer string: " % platform->glRendererString() % '\n'); support.append("OpenGL version string: " % platform->glVersionString() % '\n'); if (platform->supports(LimitedGLSL)) support.append("OpenGL shading language version string: " % platform->glShadingLanguageVersionString() % '\n'); support.append("Driver: " % GLPlatform::driverToString(platform->driver()) % '\n'); if (!platform->isMesaDriver()) support.append("Driver version: " % GLPlatform::versionToString(platform->driverVersion()) % '\n'); support.append("GPU class: " % GLPlatform::chipClassToString(platform->chipClass()) % '\n'); support.append("OpenGL version: " % GLPlatform::versionToString(platform->glVersion()) % '\n'); if (platform->supports(LimitedGLSL)) support.append("GLSL version: " % GLPlatform::versionToString(platform->glslVersion()) % '\n'); if (platform->isMesaDriver()) support.append("Mesa version: " % GLPlatform::versionToString(platform->mesaVersion()) % '\n'); if (platform->serverVersion() > 0) support.append("X server version: " % GLPlatform::versionToString(platform->serverVersion()) % '\n'); if (platform->kernelVersion() > 0) support.append("Linux kernel version: " % GLPlatform::versionToString(platform->kernelVersion()) % '\n'); support.append("Direct rendering: "); if (platform->isDirectRendering()) { support.append("yes\n"); } else { support.append("no\n"); } support.append("Requires strict binding: "); if (!platform->isLooseBinding()) { support.append("yes\n"); } else { support.append("no\n"); } support.append("GLSL shaders: "); if (platform->supports(GLSL)) { if (platform->supports(LimitedGLSL)) { support.append(" limited\n"); } else { support.append(" yes\n"); } } else { support.append(" no\n"); } support.append("Texture NPOT support: "); if (platform->supports(TextureNPOT)) { if (platform->supports(LimitedNPOT)) { support.append(" limited\n"); } else { support.append(" yes\n"); } } else { support.append(" no\n"); } support.append("Virtual Machine: "); if (platform->isVirtualMachine()) { support.append(" yes\n"); } else { support.append(" no\n"); } if (effects->compositingType() == OpenGL2Compositing) { support.append("OpenGL 2 Shaders are used\n"); } else { support.append("OpenGL 2 Shaders are not used. Legacy OpenGL 1.x code path is used.\n"); } break; } case XRenderCompositing: support.append("Compositing Type: XRender\n"); break; case NoCompositing: default: support.append("Something is really broken, neither OpenGL nor XRender is used"); } support.append("\nLoaded Effects:\n"); support.append( "---------------\n"); foreach (const QString &effect, static_cast(effects)->loadedEffects()) { support.append(effect % '\n'); } support.append("\nCurrently Active Effects:\n"); support.append( "-------------------------\n"); foreach (const QString &effect, static_cast(effects)->activeEffects()) { support.append(effect % '\n'); } support.append("\nEffect Settings:\n"); support.append( "----------------\n"); foreach (const QString &effect, static_cast(effects)->loadedEffects()) { support.append(static_cast(effects)->supportInformation(effect)); support.append('\n'); } } else { support.append("Compositing is not active\n"); } return support; } void Workspace::slotCompositingToggled() { // notify decorations that composition state has changed if (hasDecorationPlugin()) { KDecorationFactory* factory = mgr->factory(); factory->reset(SettingCompositing); } } void Workspace::slotToggleCompositing() { if (m_compositor) { m_compositor->slotToggleCompositing(); } } } // namespace #include "workspace.moc"