diff --git a/group.cpp b/group.cpp --- a/group.cpp +++ b/group.cpp @@ -19,30 +19,16 @@ along with this program. If not, see . *********************************************************************/ -/* - - This file contains things relevant to window grouping. - -*/ - //#define QT_CLEAN_NAMESPACE #include "group.h" -#include #include "workspace.h" #include "x11client.h" #include "effects.h" -#include #include #include -/* - TODO - Rename as many uses of 'transient' as possible (hasTransient->hasSubwindow,etc.), - or I'll get it backwards in half of the cases again. -*/ - namespace KWin { @@ -147,771 +133,4 @@ } } -//*************************************** -// Workspace -//*************************************** - -Group* Workspace::findGroup(xcb_window_t leader) const -{ - Q_ASSERT(leader != XCB_WINDOW_NONE); - for (auto it = groups.constBegin(); - it != groups.constEnd(); - ++it) - if ((*it)->leader() == leader) - return *it; - return nullptr; -} - -// Client is group transient, but has no group set. Try to find -// group with windows with the same client leader. -Group* Workspace::findClientLeaderGroup(const X11Client *c) const -{ - Group* ret = nullptr; - for (auto it = clients.constBegin(); - it != clients.constEnd(); - ++it) { - if (*it == c) - continue; - if ((*it)->wmClientLeader() == c->wmClientLeader()) { - if (ret == nullptr || ret == (*it)->group()) - ret = (*it)->group(); - else { - // There are already two groups with the same client leader. - // This most probably means the app uses group transients without - // setting group for its windows. Merging the two groups is a bad - // hack, but there's no really good solution for this case. - QList old_group = (*it)->group()->members(); - // old_group autodeletes when being empty - for (int pos = 0; - pos < old_group.count(); - ++pos) { - X11Client *tmp = old_group[ pos ]; - if (tmp != c) - tmp->changeClientLeaderGroup(ret); - } - } - } - } - return ret; -} - -void Workspace::updateMinimizedOfTransients(AbstractClient* c) -{ - // if mainwindow is minimized or shaded, minimize transients too - if (c->isMinimized()) { - for (auto it = c->transients().constBegin(); - it != c->transients().constEnd(); - ++it) { - if ((*it)->isModal()) - continue; // there's no reason to hide modal dialogs with the main client - // but to keep them to eg. watch progress or whatever - if (!(*it)->isMinimized()) { - (*it)->minimize(); - updateMinimizedOfTransients((*it)); - } - } - if (c->isModal()) { // if a modal dialog is minimized, minimize its mainwindow too - foreach (AbstractClient * c2, c->mainClients()) - c2->minimize(); - } - } else { - // else unmiminize the transients - for (auto it = c->transients().constBegin(); - it != c->transients().constEnd(); - ++it) { - if ((*it)->isMinimized()) { - (*it)->unminimize(); - updateMinimizedOfTransients((*it)); - } - } - if (c->isModal()) { - foreach (AbstractClient * c2, c->mainClients()) - c2->unminimize(); - } - } -} - - -/** - * Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops. - */ -void Workspace::updateOnAllDesktopsOfTransients(AbstractClient* c) -{ - for (auto it = c->transients().constBegin(); - it != c->transients().constEnd(); - ++it) { - if ((*it)->isOnAllDesktops() != c->isOnAllDesktops()) - (*it)->setOnAllDesktops(c->isOnAllDesktops()); - } -} - -// A new window has been mapped. Check if it's not a mainwindow for some already existing transient window. -void Workspace::checkTransients(xcb_window_t w) -{ - for (auto it = clients.constBegin(); - it != clients.constEnd(); - ++it) - (*it)->checkTransient(w); -} - - -//**************************************** -// Toplevel -//**************************************** - -// hacks for broken apps here -// all resource classes are forced to be lowercase -bool Toplevel::resourceMatch(const Toplevel* c1, const Toplevel* c2) -{ - return c1->resourceClass() == c2->resourceClass(); -} - - -//**************************************** -// Client -//**************************************** - -bool X11Client::belongToSameApplication(const X11Client *c1, const X11Client *c2, SameApplicationChecks checks) -{ - bool same_app = false; - - // tests that definitely mean they belong together - if (c1 == c2) - same_app = true; - else if (c1->isTransient() && c2->hasTransient(c1, true)) - same_app = true; // c1 has c2 as mainwindow - else if (c2->isTransient() && c1->hasTransient(c2, true)) - same_app = true; // c2 has c1 as mainwindow - else if (c1->group() == c2->group()) - same_app = true; // same group - else if (c1->wmClientLeader() == c2->wmClientLeader() - && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), - && c2->wmClientLeader() != c2->window()) // don't use in this test then - same_app = true; // same client leader - - // tests that mean they most probably don't belong together - else if ((c1->pid() != c2->pid() && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) - || c1->wmClientMachine(false) != c2->wmClientMachine(false)) - ; // different processes - else if (c1->wmClientLeader() != c2->wmClientLeader() - && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), - && c2->wmClientLeader() != c2->window() // don't use in this test then - && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) - ; // different client leader - else if (!resourceMatch(c1, c2)) - ; // different apps - else if (!sameAppWindowRoleMatch(c1, c2, checks.testFlag(SameApplicationCheck::RelaxedForActive)) - && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) - ; // "different" apps - else if (c1->pid() == 0 || c2->pid() == 0) - ; // old apps that don't have _NET_WM_PID, consider them different - // if they weren't found to match above - else - same_app = true; // looks like it's the same app - - return same_app; -} - -// Non-transient windows with window role containing '#' are always -// considered belonging to different applications (unless -// the window role is exactly the same). KMainWindow sets -// window role this way by default, and different KMainWindow -// usually "are" different application from user's point of view. -// This help with no-focus-stealing for e.g. konqy reusing. -// On the other hand, if one of the windows is active, they are -// considered belonging to the same application. This is for -// the cases when opening new mainwindow directly from the application, -// e.g. 'Open New Window' in konqy ( active_hack == true ). -bool X11Client::sameAppWindowRoleMatch(const X11Client *c1, const X11Client *c2, bool active_hack) -{ - if (c1->isTransient()) { - while (const X11Client *t = dynamic_cast(c1->transientFor())) - c1 = t; - if (c1->groupTransient()) - return c1->group() == c2->group(); -#if 0 - // if a group transient is in its own group, it didn't possibly have a group, - // and therefore should be considered belonging to the same app like - // all other windows from the same app - || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; -#endif - } - if (c2->isTransient()) { - while (const X11Client *t = dynamic_cast(c2->transientFor())) - c2 = t; - if (c2->groupTransient()) - return c1->group() == c2->group(); -#if 0 - || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; -#endif - } - int pos1 = c1->windowRole().indexOf('#'); - int pos2 = c2->windowRole().indexOf('#'); - if ((pos1 >= 0 && pos2 >= 0)) { - if (!active_hack) // without the active hack for focus stealing prevention, - return c1 == c2; // different mainwindows are always different apps - if (!c1->isActive() && !c2->isActive()) - return c1 == c2; - else - return true; - } - return true; -} - -/* - - Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3 - - WM_TRANSIENT_FOR is basically means "this is my mainwindow". - For NET::Unknown windows, transient windows are considered to be NET::Dialog - windows, for compatibility with non-NETWM clients. KWin may adjust the value - of this property in some cases (window pointing to itself or creating a loop, - keeping NET::Splash windows above other windows from the same app, etc.). - - X11Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after - possibly being adjusted by KWin. X11Client::transient_for points to the Client - this Client is transient for, or is NULL. If X11Client::transient_for_id is - poiting to the root window, the window is considered to be transient - for the whole window group, as suggested in NETWM 7.3. - - In the case of group transient window, X11Client::transient_for is NULL, - and X11Client::groupTransient() returns true. Such window is treated as - if it were transient for every window in its window group that has been - mapped _before_ it (or, to be exact, was added to the same group before it). - Otherwise two group transients can create loops, which can lead very very - nasty things (bug #67914 and all its dupes). - - X11Client::original_transient_for_id is the value of the property, which - may be different if X11Client::transient_for_id if e.g. forcing NET::Splash - to be kept on top of its window group, or when the mainwindow is not mapped - yet, in which case the window is temporarily made group transient, - and when the mainwindow is mapped, transiency is re-evaluated. - - This can get a bit complicated with with e.g. two Konqueror windows created - by the same process. They should ideally appear like two independent applications - to the user. This should be accomplished by all windows in the same process - having the same window group (needs to be changed in Qt at the moment), and - using non-group transients poiting to their relevant mainwindow for toolwindows - etc. KWin should handle both group and non-group transient dialogs well. - - In other words: - - non-transient windows : isTransient() == false - - normal transients : transientFor() != NULL - - group transients : groupTransient() == true - - - list of mainwindows : mainClients() (call once and loop over the result) - - list of transients : transients() - - every window in the group : group()->members() -*/ - -Xcb::TransientFor X11Client::fetchTransient() const -{ - return Xcb::TransientFor(window()); -} - -void X11Client::readTransientProperty(Xcb::TransientFor &transientFor) -{ - xcb_window_t new_transient_for_id = XCB_WINDOW_NONE; - if (transientFor.getTransientFor(&new_transient_for_id)) { - m_originalTransientForId = new_transient_for_id; - new_transient_for_id = verifyTransientFor(new_transient_for_id, true); - } else { - m_originalTransientForId = XCB_WINDOW_NONE; - new_transient_for_id = verifyTransientFor(XCB_WINDOW_NONE, false); - } - setTransient(new_transient_for_id); -} - -void X11Client::readTransient() -{ - Xcb::TransientFor transientFor = fetchTransient(); - readTransientProperty(transientFor); -} - -void X11Client::setTransient(xcb_window_t new_transient_for_id) -{ - if (new_transient_for_id != m_transientForId) { - removeFromMainClients(); - X11Client *transient_for = nullptr; - m_transientForId = new_transient_for_id; - if (m_transientForId != XCB_WINDOW_NONE && !groupTransient()) { - transient_for = workspace()->findClient(Predicate::WindowMatch, m_transientForId); - Q_ASSERT(transient_for != nullptr); // verifyTransient() had to check this - transient_for->addTransient(this); - } // checkGroup() will check 'check_active_modal' - setTransientFor(transient_for); - checkGroup(nullptr, true); // force, because transiency has changed - workspace()->updateClientLayer(this); - workspace()->resetUpdateToolWindowsTimer(); - emit transientChanged(); - } -} - -void X11Client::removeFromMainClients() -{ - if (transientFor()) - transientFor()->removeTransient(this); - if (groupTransient()) { - for (auto it = group()->members().constBegin(); - it != group()->members().constEnd(); - ++it) - (*it)->removeTransient(this); - } -} - -// *sigh* this transiency handling is madness :( -// This one is called when destroying/releasing a window. -// It makes sure this client is removed from all grouping -// related lists. -void X11Client::cleanGrouping() -{ -// qDebug() << "CLEANGROUPING:" << this; -// for ( auto it = group()->members().begin(); -// it != group()->members().end(); -// ++it ) -// qDebug() << "CL:" << *it; -// QList mains; -// mains = mainClients(); -// for ( auto it = mains.begin(); -// it != mains.end(); -// ++it ) -// qDebug() << "MN:" << *it; - removeFromMainClients(); -// qDebug() << "CLEANGROUPING2:" << this; -// for ( auto it = group()->members().begin(); -// it != group()->members().end(); -// ++it ) -// qDebug() << "CL2:" << *it; -// mains = mainClients(); -// for ( auto it = mains.begin(); -// it != mains.end(); -// ++it ) -// qDebug() << "MN2:" << *it; - for (auto it = transients().constBegin(); - it != transients().constEnd(); - ) { - if ((*it)->transientFor() == this) { - removeTransient(*it); - it = transients().constBegin(); // restart, just in case something more has changed with the list - } else - ++it; - } -// qDebug() << "CLEANGROUPING3:" << this; -// for ( auto it = group()->members().begin(); -// it != group()->members().end(); -// ++it ) -// qDebug() << "CL3:" << *it; -// mains = mainClients(); -// for ( auto it = mains.begin(); -// it != mains.end(); -// ++it ) -// qDebug() << "MN3:" << *it; - // HACK - // removeFromMainClients() did remove 'this' from transient - // lists of all group members, but then made windows that - // were transient for 'this' group transient, which again - // added 'this' to those transient lists :( - QList group_members = group()->members(); - group()->removeMember(this); - in_group = nullptr; - for (auto it = group_members.constBegin(); - it != group_members.constEnd(); - ++it) - (*it)->removeTransient(this); -// qDebug() << "CLEANGROUPING4:" << this; -// for ( auto it = group_members.begin(); -// it != group_members.end(); -// ++it ) -// qDebug() << "CL4:" << *it; - m_transientForId = XCB_WINDOW_NONE; -} - -// Make sure that no group transient is considered transient -// for a window that is (directly or indirectly) transient for it -// (including another group transients). -// Non-group transients not causing loops are checked in verifyTransientFor(). -void X11Client::checkGroupTransients() -{ - for (auto it1 = group()->members().constBegin(); - it1 != group()->members().constEnd(); - ++it1) { - if (!(*it1)->groupTransient()) // check all group transients in the group - continue; // TODO optimize to check only the changed ones? - for (auto it2 = group()->members().constBegin(); - it2 != group()->members().constEnd(); - ++it2) { // group transients can be transient only for others in the group, - // so don't make them transient for the ones that are transient for it - if (*it1 == *it2) - continue; - for (AbstractClient* cl = (*it2)->transientFor(); - cl != nullptr; - cl = cl->transientFor()) { - if (cl == *it1) { - // don't use removeTransient(), that would modify *it2 too - (*it2)->removeTransientFromList(*it1); - continue; - } - } - // if *it1 and *it2 are both group transients, and are transient for each other, - // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later, - // and should be therefore on top of *it1 - // TODO This could possibly be optimized, it also requires hasTransient() to check for loops. - if ((*it2)->groupTransient() && (*it1)->hasTransient(*it2, true) && (*it2)->hasTransient(*it1, true)) - (*it2)->removeTransientFromList(*it1); - // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3 - // is added, make it transient only for W2, not for W1, because it's already indirectly - // transient for it - the indirect transiency actually shouldn't break anything, - // but it can lead to exponentially expensive operations (#95231) - // TODO this is pretty slow as well - for (auto it3 = group()->members().constBegin(); - it3 != group()->members().constEnd(); - ++it3) { - if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3) - continue; - if ((*it2)->hasTransient(*it1, false) && (*it3)->hasTransient(*it1, false)) { - if ((*it2)->hasTransient(*it3, true)) - (*it2)->removeTransientFromList(*it1); - if ((*it3)->hasTransient(*it2, true)) - (*it3)->removeTransientFromList(*it1); - } - } - } - } -} - -/** - * Check that the window is not transient for itself, and similar nonsense. - */ -xcb_window_t X11Client::verifyTransientFor(xcb_window_t new_transient_for, bool set) -{ - xcb_window_t new_property_value = new_transient_for; - // make sure splashscreens are shown above all their app's windows, even though - // they're in Normal layer - if (isSplash() && new_transient_for == XCB_WINDOW_NONE) - new_transient_for = rootWindow(); - if (new_transient_for == XCB_WINDOW_NONE) { - if (set) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window - new_property_value = new_transient_for = rootWindow(); - else - return XCB_WINDOW_NONE; - } - if (new_transient_for == window()) { // pointing to self - // also fix the property itself - qCWarning(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." ; - new_property_value = new_transient_for = rootWindow(); - } -// The transient_for window may be embedded in another application, -// so kwin cannot see it. Try to find the managed client for the -// window and fix the transient_for property if possible. - xcb_window_t before_search = new_transient_for; - while (new_transient_for != XCB_WINDOW_NONE - && new_transient_for != rootWindow() - && !workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { - Xcb::Tree tree(new_transient_for); - if (tree.isNull()) { - break; - } - new_transient_for = tree->parent; - } - if (X11Client *new_transient_for_client = workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { - if (new_transient_for != before_search) { - qCDebug(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window " - << before_search << ", child of " << new_transient_for_client << ", adjusting."; - new_property_value = new_transient_for; // also fix the property - } - } else - new_transient_for = before_search; // nice try -// loop detection -// group transients cannot cause loops, because they're considered transient only for non-transient -// windows in the group - int count = 20; - xcb_window_t loop_pos = new_transient_for; - while (loop_pos != XCB_WINDOW_NONE && loop_pos != rootWindow()) { - X11Client *pos = workspace()->findClient(Predicate::WindowMatch, loop_pos); - if (pos == nullptr) - break; - loop_pos = pos->m_transientForId; - if (--count == 0 || pos == this) { - qCWarning(KWIN_CORE) << "Client " << this << " caused WM_TRANSIENT_FOR loop." ; - new_transient_for = rootWindow(); - } - } - if (new_transient_for != rootWindow() - && workspace()->findClient(Predicate::WindowMatch, new_transient_for) == nullptr) { - // it's transient for a specific window, but that window is not mapped - new_transient_for = rootWindow(); - } - if (new_property_value != m_originalTransientForId) - Xcb::setTransientFor(window(), new_property_value); - return new_transient_for; -} - -void X11Client::addTransient(AbstractClient* cl) -{ - AbstractClient::addTransient(cl); - if (workspace()->mostRecentlyActivatedClient() == this && cl->isModal()) - check_active_modal = true; -// qDebug() << "ADDTRANS:" << this << ":" << cl; -// qDebug() << kBacktrace(); -// for ( auto it = transients_list.begin(); -// it != transients_list.end(); -// ++it ) -// qDebug() << "AT:" << (*it); -} - -void X11Client::removeTransient(AbstractClient* cl) -{ -// qDebug() << "REMOVETRANS:" << this << ":" << cl; -// qDebug() << kBacktrace(); - // cl is transient for this, but this is going away - // make cl group transient - AbstractClient::removeTransient(cl); - if (cl->transientFor() == this) { - if (X11Client *c = dynamic_cast(cl)) { - c->m_transientForId = XCB_WINDOW_NONE; - c->setTransientFor(nullptr); // SELI -// SELI cl->setTransient( rootWindow()); - c->setTransient(XCB_WINDOW_NONE); - } - } -} - -// A new window has been mapped. Check if it's not a mainwindow for this already existing window. -void X11Client::checkTransient(xcb_window_t w) -{ - if (m_originalTransientForId != w) - return; - w = verifyTransientFor(w, true); - setTransient(w); -} - -// returns true if cl is the transient_for window for this client, -// or recursively the transient_for window -bool X11Client::hasTransient(const AbstractClient* cl, bool indirect) const -{ - if (const X11Client *c = dynamic_cast(cl)) { - // checkGroupTransients() uses this to break loops, so hasTransient() must detect them - QList set; - return hasTransientInternal(c, indirect, set); - } - return false; -} - -bool X11Client::hasTransientInternal(const X11Client *cl, bool indirect, QList &set) const -{ - if (const X11Client *t = dynamic_cast(cl->transientFor())) { - if (t == this) - return true; - if (!indirect) - return false; - if (set.contains(cl)) - return false; - set.append(cl); - return hasTransientInternal(t, indirect, set); - } - if (!cl->isTransient()) - return false; - if (group() != cl->group()) - return false; - // cl is group transient, search from top - if (transients().contains(const_cast< X11Client *>(cl))) - return true; - if (!indirect) - return false; - if (set.contains(this)) - return false; - set.append(this); - for (auto it = transients().constBegin(); - it != transients().constEnd(); - ++it) { - const X11Client *c = qobject_cast(*it); - if (!c) { - continue; - } - if (c->hasTransientInternal(cl, indirect, set)) - return true; - } - return false; -} - -QList X11Client::mainClients() const -{ - if (!isTransient()) - return QList(); - if (const AbstractClient *t = transientFor()) - return QList{const_cast< AbstractClient* >(t)}; - QList result; - Q_ASSERT(group()); - for (auto it = group()->members().constBegin(); - it != group()->members().constEnd(); - ++it) - if ((*it)->hasTransient(this, false)) - result.append(*it); - return result; -} - -AbstractClient* X11Client::findModal(bool allow_itself) -{ - for (auto it = transients().constBegin(); - it != transients().constEnd(); - ++it) - if (AbstractClient* ret = (*it)->findModal(true)) - return ret; - if (isModal() && allow_itself) - return this; - return nullptr; -} - -// X11Client::window_group only holds the contents of the hint, -// but it should be used only to find the group, not for anything else -// Argument is only when some specific group needs to be set. -void X11Client::checkGroup(Group* set_group, bool force) -{ - Group* old_group = in_group; - if (old_group != nullptr) - old_group->ref(); // turn off automatic deleting - if (set_group != nullptr) { - if (set_group != in_group) { - if (in_group != nullptr) - in_group->removeMember(this); - in_group = set_group; - in_group->addMember(this); - } - } else if (info->groupLeader() != XCB_WINDOW_NONE) { - Group* new_group = workspace()->findGroup(info->groupLeader()); - X11Client *t = qobject_cast(transientFor()); - if (t != nullptr && t->group() != new_group) { - // move the window to the right group (e.g. a dialog provided - // by different app, but transient for this one, so make it part of that group) - new_group = t->group(); - } - if (new_group == nullptr) // doesn't exist yet - new_group = new Group(info->groupLeader()); - if (new_group != in_group) { - if (in_group != nullptr) - in_group->removeMember(this); - in_group = new_group; - in_group->addMember(this); - } - } else { - if (X11Client *t = qobject_cast(transientFor())) { - // doesn't have window group set, but is transient for something - // so make it part of that group - Group* new_group = t->group(); - if (new_group != in_group) { - if (in_group != nullptr) - in_group->removeMember(this); - in_group = t->group(); - in_group->addMember(this); - } - } else if (groupTransient()) { - // group transient which actually doesn't have a group :( - // try creating group with other windows with the same client leader - Group* new_group = workspace()->findClientLeaderGroup(this); - if (new_group == nullptr) - new_group = new Group(XCB_WINDOW_NONE); - if (new_group != in_group) { - if (in_group != nullptr) - in_group->removeMember(this); - in_group = new_group; - in_group->addMember(this); - } - } else { // Not transient without a group, put it in its client leader group. - // This might be stupid if grouping was used for e.g. taskbar grouping - // or minimizing together the whole group, but as long as it is used - // only for dialogs it's better to keep windows from one app in one group. - Group* new_group = workspace()->findClientLeaderGroup(this); - if (in_group != nullptr && in_group != new_group) { - in_group->removeMember(this); - in_group = nullptr; - } - if (new_group == nullptr) - new_group = new Group(XCB_WINDOW_NONE); - if (in_group != new_group) { - in_group = new_group; - in_group->addMember(this); - } - } - } - if (in_group != old_group || force) { - for (auto it = transients().constBegin(); - it != transients().constEnd(); - ) { - auto *c = *it; - // group transients in the old group are no longer transient for it - if (c->groupTransient() && c->group() != group()) { - removeTransientFromList(c); - it = transients().constBegin(); // restart, just in case something more has changed with the list - } else - ++it; - } - if (groupTransient()) { - // no longer transient for ones in the old group - if (old_group != nullptr) { - for (auto it = old_group->members().constBegin(); - it != old_group->members().constEnd(); - ++it) - (*it)->removeTransient(this); - } - // and make transient for all in the new group - for (auto it = group()->members().constBegin(); - it != group()->members().constEnd(); - ++it) { - if (*it == this) - break; // this means the window is only transient for windows mapped before it - (*it)->addTransient(this); - } - } - // group transient splashscreens should be transient even for windows - // in group mapped later - for (auto it = group()->members().constBegin(); - it != group()->members().constEnd(); - ++it) { - if (!(*it)->isSplash()) - continue; - if (!(*it)->groupTransient()) - continue; - if (*it == this || hasTransient(*it, true)) // TODO indirect? - continue; - addTransient(*it); - } - } - if (old_group != nullptr) - old_group->deref(); // can be now deleted if empty - checkGroupTransients(); - checkActiveModal(); - workspace()->updateClientLayer(this); -} - -// used by Workspace::findClientLeaderGroup() -void X11Client::changeClientLeaderGroup(Group* gr) -{ - // transientFor() != NULL are in the group of their mainwindow, so keep them there - if (transientFor() != nullptr) - return; - // also don't change the group for window which have group set - if (info->groupLeader()) - return; - checkGroup(gr); // change group -} - -bool X11Client::check_active_modal = false; - -void X11Client::checkActiveModal() -{ - // if the active window got new modal transient, activate it. - // cannot be done in AddTransient(), because there may temporarily - // exist loops, breaking findModal - X11Client *check_modal = dynamic_cast(workspace()->mostRecentlyActivatedClient()); - if (check_modal != nullptr && check_modal->check_active_modal) { - X11Client *new_modal = dynamic_cast(check_modal->findModal()); - if (new_modal != nullptr && new_modal != check_modal) { - if (!new_modal->isManaged()) - return; // postpone check until end of manage() - workspace()->activateClient(new_modal); - } - check_modal->check_active_modal = false; - } -} - } // namespace diff --git a/toplevel.cpp b/toplevel.cpp --- a/toplevel.cpp +++ b/toplevel.cpp @@ -228,6 +228,11 @@ emit windowClassChanged(); } +bool Toplevel::resourceMatch(const Toplevel *c1, const Toplevel *c2) +{ + return c1->resourceClass() == c2->resourceClass(); +} + double Toplevel::opacity() const { if (info->opacity() == 0xffffffff) diff --git a/workspace.cpp b/workspace.cpp --- a/workspace.cpp +++ b/workspace.cpp @@ -1830,4 +1830,107 @@ emit internalClientRemoved(client); } +Group* Workspace::findGroup(xcb_window_t leader) const +{ + Q_ASSERT(leader != XCB_WINDOW_NONE); + for (auto it = groups.constBegin(); + it != groups.constEnd(); + ++it) + if ((*it)->leader() == leader) + return *it; + return nullptr; +} + +// Client is group transient, but has no group set. Try to find +// group with windows with the same client leader. +Group* Workspace::findClientLeaderGroup(const X11Client *c) const +{ + Group* ret = nullptr; + for (auto it = clients.constBegin(); + it != clients.constEnd(); + ++it) { + if (*it == c) + continue; + if ((*it)->wmClientLeader() == c->wmClientLeader()) { + if (ret == nullptr || ret == (*it)->group()) + ret = (*it)->group(); + else { + // There are already two groups with the same client leader. + // This most probably means the app uses group transients without + // setting group for its windows. Merging the two groups is a bad + // hack, but there's no really good solution for this case. + QList old_group = (*it)->group()->members(); + // old_group autodeletes when being empty + for (int pos = 0; + pos < old_group.count(); + ++pos) { + X11Client *tmp = old_group[ pos ]; + if (tmp != c) + tmp->changeClientLeaderGroup(ret); + } + } + } + } + return ret; +} + +void Workspace::updateMinimizedOfTransients(AbstractClient* c) +{ + // if mainwindow is minimized or shaded, minimize transients too + if (c->isMinimized()) { + for (auto it = c->transients().constBegin(); + it != c->transients().constEnd(); + ++it) { + if ((*it)->isModal()) + continue; // there's no reason to hide modal dialogs with the main client + // but to keep them to eg. watch progress or whatever + if (!(*it)->isMinimized()) { + (*it)->minimize(); + updateMinimizedOfTransients((*it)); + } + } + if (c->isModal()) { // if a modal dialog is minimized, minimize its mainwindow too + foreach (AbstractClient * c2, c->mainClients()) + c2->minimize(); + } + } else { + // else unmiminize the transients + for (auto it = c->transients().constBegin(); + it != c->transients().constEnd(); + ++it) { + if ((*it)->isMinimized()) { + (*it)->unminimize(); + updateMinimizedOfTransients((*it)); + } + } + if (c->isModal()) { + foreach (AbstractClient * c2, c->mainClients()) + c2->unminimize(); + } + } +} + + +/** + * Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops. + */ +void Workspace::updateOnAllDesktopsOfTransients(AbstractClient* c) +{ + for (auto it = c->transients().constBegin(); + it != c->transients().constEnd(); + ++it) { + if ((*it)->isOnAllDesktops() != c->isOnAllDesktops()) + (*it)->setOnAllDesktops(c->isOnAllDesktops()); + } +} + +// A new window has been mapped. Check if it's not a mainwindow for some already existing transient window. +void Workspace::checkTransients(xcb_window_t w) +{ + for (auto it = clients.constBegin(); + it != clients.constEnd(); + ++it) + (*it)->checkTransient(w); +} + } // namespace diff --git a/x11client.cpp b/x11client.cpp --- a/x11client.cpp +++ b/x11client.cpp @@ -2943,5 +2943,648 @@ emit geometryChanged(); } +bool X11Client::belongToSameApplication(const X11Client *c1, const X11Client *c2, SameApplicationChecks checks) +{ + bool same_app = false; + + // tests that definitely mean they belong together + if (c1 == c2) + same_app = true; + else if (c1->isTransient() && c2->hasTransient(c1, true)) + same_app = true; // c1 has c2 as mainwindow + else if (c2->isTransient() && c1->hasTransient(c2, true)) + same_app = true; // c2 has c1 as mainwindow + else if (c1->group() == c2->group()) + same_app = true; // same group + else if (c1->wmClientLeader() == c2->wmClientLeader() + && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), + && c2->wmClientLeader() != c2->window()) // don't use in this test then + same_app = true; // same client leader + + // tests that mean they most probably don't belong together + else if ((c1->pid() != c2->pid() && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) + || c1->wmClientMachine(false) != c2->wmClientMachine(false)) + ; // different processes + else if (c1->wmClientLeader() != c2->wmClientLeader() + && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), + && c2->wmClientLeader() != c2->window() // don't use in this test then + && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) + ; // different client leader + else if (!resourceMatch(c1, c2)) + ; // different apps + else if (!sameAppWindowRoleMatch(c1, c2, checks.testFlag(SameApplicationCheck::RelaxedForActive)) + && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) + ; // "different" apps + else if (c1->pid() == 0 || c2->pid() == 0) + ; // old apps that don't have _NET_WM_PID, consider them different + // if they weren't found to match above + else + same_app = true; // looks like it's the same app + + return same_app; +} + +// Non-transient windows with window role containing '#' are always +// considered belonging to different applications (unless +// the window role is exactly the same). KMainWindow sets +// window role this way by default, and different KMainWindow +// usually "are" different application from user's point of view. +// This help with no-focus-stealing for e.g. konqy reusing. +// On the other hand, if one of the windows is active, they are +// considered belonging to the same application. This is for +// the cases when opening new mainwindow directly from the application, +// e.g. 'Open New Window' in konqy ( active_hack == true ). +bool X11Client::sameAppWindowRoleMatch(const X11Client *c1, const X11Client *c2, bool active_hack) +{ + if (c1->isTransient()) { + while (const X11Client *t = dynamic_cast(c1->transientFor())) + c1 = t; + if (c1->groupTransient()) + return c1->group() == c2->group(); +#if 0 + // if a group transient is in its own group, it didn't possibly have a group, + // and therefore should be considered belonging to the same app like + // all other windows from the same app + || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; +#endif + } + if (c2->isTransient()) { + while (const X11Client *t = dynamic_cast(c2->transientFor())) + c2 = t; + if (c2->groupTransient()) + return c1->group() == c2->group(); +#if 0 + || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; +#endif + } + int pos1 = c1->windowRole().indexOf('#'); + int pos2 = c2->windowRole().indexOf('#'); + if ((pos1 >= 0 && pos2 >= 0)) { + if (!active_hack) // without the active hack for focus stealing prevention, + return c1 == c2; // different mainwindows are always different apps + if (!c1->isActive() && !c2->isActive()) + return c1 == c2; + else + return true; + } + return true; +} + +/* + + Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3 + + WM_TRANSIENT_FOR is basically means "this is my mainwindow". + For NET::Unknown windows, transient windows are considered to be NET::Dialog + windows, for compatibility with non-NETWM clients. KWin may adjust the value + of this property in some cases (window pointing to itself or creating a loop, + keeping NET::Splash windows above other windows from the same app, etc.). + + X11Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after + possibly being adjusted by KWin. X11Client::transient_for points to the Client + this Client is transient for, or is NULL. If X11Client::transient_for_id is + poiting to the root window, the window is considered to be transient + for the whole window group, as suggested in NETWM 7.3. + + In the case of group transient window, X11Client::transient_for is NULL, + and X11Client::groupTransient() returns true. Such window is treated as + if it were transient for every window in its window group that has been + mapped _before_ it (or, to be exact, was added to the same group before it). + Otherwise two group transients can create loops, which can lead very very + nasty things (bug #67914 and all its dupes). + + X11Client::original_transient_for_id is the value of the property, which + may be different if X11Client::transient_for_id if e.g. forcing NET::Splash + to be kept on top of its window group, or when the mainwindow is not mapped + yet, in which case the window is temporarily made group transient, + and when the mainwindow is mapped, transiency is re-evaluated. + + This can get a bit complicated with with e.g. two Konqueror windows created + by the same process. They should ideally appear like two independent applications + to the user. This should be accomplished by all windows in the same process + having the same window group (needs to be changed in Qt at the moment), and + using non-group transients poiting to their relevant mainwindow for toolwindows + etc. KWin should handle both group and non-group transient dialogs well. + + In other words: + - non-transient windows : isTransient() == false + - normal transients : transientFor() != NULL + - group transients : groupTransient() == true + + - list of mainwindows : mainClients() (call once and loop over the result) + - list of transients : transients() + - every window in the group : group()->members() +*/ + +Xcb::TransientFor X11Client::fetchTransient() const +{ + return Xcb::TransientFor(window()); +} + +void X11Client::readTransientProperty(Xcb::TransientFor &transientFor) +{ + xcb_window_t new_transient_for_id = XCB_WINDOW_NONE; + if (transientFor.getTransientFor(&new_transient_for_id)) { + m_originalTransientForId = new_transient_for_id; + new_transient_for_id = verifyTransientFor(new_transient_for_id, true); + } else { + m_originalTransientForId = XCB_WINDOW_NONE; + new_transient_for_id = verifyTransientFor(XCB_WINDOW_NONE, false); + } + setTransient(new_transient_for_id); +} + +void X11Client::readTransient() +{ + Xcb::TransientFor transientFor = fetchTransient(); + readTransientProperty(transientFor); +} + +void X11Client::setTransient(xcb_window_t new_transient_for_id) +{ + if (new_transient_for_id != m_transientForId) { + removeFromMainClients(); + X11Client *transient_for = nullptr; + m_transientForId = new_transient_for_id; + if (m_transientForId != XCB_WINDOW_NONE && !groupTransient()) { + transient_for = workspace()->findClient(Predicate::WindowMatch, m_transientForId); + Q_ASSERT(transient_for != nullptr); // verifyTransient() had to check this + transient_for->addTransient(this); + } // checkGroup() will check 'check_active_modal' + setTransientFor(transient_for); + checkGroup(nullptr, true); // force, because transiency has changed + workspace()->updateClientLayer(this); + workspace()->resetUpdateToolWindowsTimer(); + emit transientChanged(); + } +} + +void X11Client::removeFromMainClients() +{ + if (transientFor()) + transientFor()->removeTransient(this); + if (groupTransient()) { + for (auto it = group()->members().constBegin(); + it != group()->members().constEnd(); + ++it) + (*it)->removeTransient(this); + } +} + +// *sigh* this transiency handling is madness :( +// This one is called when destroying/releasing a window. +// It makes sure this client is removed from all grouping +// related lists. +void X11Client::cleanGrouping() +{ +// qDebug() << "CLEANGROUPING:" << this; +// for ( auto it = group()->members().begin(); +// it != group()->members().end(); +// ++it ) +// qDebug() << "CL:" << *it; +// QList mains; +// mains = mainClients(); +// for ( auto it = mains.begin(); +// it != mains.end(); +// ++it ) +// qDebug() << "MN:" << *it; + removeFromMainClients(); +// qDebug() << "CLEANGROUPING2:" << this; +// for ( auto it = group()->members().begin(); +// it != group()->members().end(); +// ++it ) +// qDebug() << "CL2:" << *it; +// mains = mainClients(); +// for ( auto it = mains.begin(); +// it != mains.end(); +// ++it ) +// qDebug() << "MN2:" << *it; + for (auto it = transients().constBegin(); + it != transients().constEnd(); + ) { + if ((*it)->transientFor() == this) { + removeTransient(*it); + it = transients().constBegin(); // restart, just in case something more has changed with the list + } else + ++it; + } +// qDebug() << "CLEANGROUPING3:" << this; +// for ( auto it = group()->members().begin(); +// it != group()->members().end(); +// ++it ) +// qDebug() << "CL3:" << *it; +// mains = mainClients(); +// for ( auto it = mains.begin(); +// it != mains.end(); +// ++it ) +// qDebug() << "MN3:" << *it; + // HACK + // removeFromMainClients() did remove 'this' from transient + // lists of all group members, but then made windows that + // were transient for 'this' group transient, which again + // added 'this' to those transient lists :( + QList group_members = group()->members(); + group()->removeMember(this); + in_group = nullptr; + for (auto it = group_members.constBegin(); + it != group_members.constEnd(); + ++it) + (*it)->removeTransient(this); +// qDebug() << "CLEANGROUPING4:" << this; +// for ( auto it = group_members.begin(); +// it != group_members.end(); +// ++it ) +// qDebug() << "CL4:" << *it; + m_transientForId = XCB_WINDOW_NONE; +} + +// Make sure that no group transient is considered transient +// for a window that is (directly or indirectly) transient for it +// (including another group transients). +// Non-group transients not causing loops are checked in verifyTransientFor(). +void X11Client::checkGroupTransients() +{ + for (auto it1 = group()->members().constBegin(); + it1 != group()->members().constEnd(); + ++it1) { + if (!(*it1)->groupTransient()) // check all group transients in the group + continue; // TODO optimize to check only the changed ones? + for (auto it2 = group()->members().constBegin(); + it2 != group()->members().constEnd(); + ++it2) { // group transients can be transient only for others in the group, + // so don't make them transient for the ones that are transient for it + if (*it1 == *it2) + continue; + for (AbstractClient* cl = (*it2)->transientFor(); + cl != nullptr; + cl = cl->transientFor()) { + if (cl == *it1) { + // don't use removeTransient(), that would modify *it2 too + (*it2)->removeTransientFromList(*it1); + continue; + } + } + // if *it1 and *it2 are both group transients, and are transient for each other, + // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later, + // and should be therefore on top of *it1 + // TODO This could possibly be optimized, it also requires hasTransient() to check for loops. + if ((*it2)->groupTransient() && (*it1)->hasTransient(*it2, true) && (*it2)->hasTransient(*it1, true)) + (*it2)->removeTransientFromList(*it1); + // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3 + // is added, make it transient only for W2, not for W1, because it's already indirectly + // transient for it - the indirect transiency actually shouldn't break anything, + // but it can lead to exponentially expensive operations (#95231) + // TODO this is pretty slow as well + for (auto it3 = group()->members().constBegin(); + it3 != group()->members().constEnd(); + ++it3) { + if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3) + continue; + if ((*it2)->hasTransient(*it1, false) && (*it3)->hasTransient(*it1, false)) { + if ((*it2)->hasTransient(*it3, true)) + (*it2)->removeTransientFromList(*it1); + if ((*it3)->hasTransient(*it2, true)) + (*it3)->removeTransientFromList(*it1); + } + } + } + } +} + +/** + * Check that the window is not transient for itself, and similar nonsense. + */ +xcb_window_t X11Client::verifyTransientFor(xcb_window_t new_transient_for, bool set) +{ + xcb_window_t new_property_value = new_transient_for; + // make sure splashscreens are shown above all their app's windows, even though + // they're in Normal layer + if (isSplash() && new_transient_for == XCB_WINDOW_NONE) + new_transient_for = rootWindow(); + if (new_transient_for == XCB_WINDOW_NONE) { + if (set) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window + new_property_value = new_transient_for = rootWindow(); + else + return XCB_WINDOW_NONE; + } + if (new_transient_for == window()) { // pointing to self + // also fix the property itself + qCWarning(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." ; + new_property_value = new_transient_for = rootWindow(); + } +// The transient_for window may be embedded in another application, +// so kwin cannot see it. Try to find the managed client for the +// window and fix the transient_for property if possible. + xcb_window_t before_search = new_transient_for; + while (new_transient_for != XCB_WINDOW_NONE + && new_transient_for != rootWindow() + && !workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { + Xcb::Tree tree(new_transient_for); + if (tree.isNull()) { + break; + } + new_transient_for = tree->parent; + } + if (X11Client *new_transient_for_client = workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { + if (new_transient_for != before_search) { + qCDebug(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window " + << before_search << ", child of " << new_transient_for_client << ", adjusting."; + new_property_value = new_transient_for; // also fix the property + } + } else + new_transient_for = before_search; // nice try +// loop detection +// group transients cannot cause loops, because they're considered transient only for non-transient +// windows in the group + int count = 20; + xcb_window_t loop_pos = new_transient_for; + while (loop_pos != XCB_WINDOW_NONE && loop_pos != rootWindow()) { + X11Client *pos = workspace()->findClient(Predicate::WindowMatch, loop_pos); + if (pos == nullptr) + break; + loop_pos = pos->m_transientForId; + if (--count == 0 || pos == this) { + qCWarning(KWIN_CORE) << "Client " << this << " caused WM_TRANSIENT_FOR loop." ; + new_transient_for = rootWindow(); + } + } + if (new_transient_for != rootWindow() + && workspace()->findClient(Predicate::WindowMatch, new_transient_for) == nullptr) { + // it's transient for a specific window, but that window is not mapped + new_transient_for = rootWindow(); + } + if (new_property_value != m_originalTransientForId) + Xcb::setTransientFor(window(), new_property_value); + return new_transient_for; +} + +void X11Client::addTransient(AbstractClient* cl) +{ + AbstractClient::addTransient(cl); + if (workspace()->mostRecentlyActivatedClient() == this && cl->isModal()) + check_active_modal = true; +// qDebug() << "ADDTRANS:" << this << ":" << cl; +// qDebug() << kBacktrace(); +// for ( auto it = transients_list.begin(); +// it != transients_list.end(); +// ++it ) +// qDebug() << "AT:" << (*it); +} + +void X11Client::removeTransient(AbstractClient* cl) +{ +// qDebug() << "REMOVETRANS:" << this << ":" << cl; +// qDebug() << kBacktrace(); + // cl is transient for this, but this is going away + // make cl group transient + AbstractClient::removeTransient(cl); + if (cl->transientFor() == this) { + if (X11Client *c = dynamic_cast(cl)) { + c->m_transientForId = XCB_WINDOW_NONE; + c->setTransientFor(nullptr); // SELI +// SELI cl->setTransient( rootWindow()); + c->setTransient(XCB_WINDOW_NONE); + } + } +} + +// A new window has been mapped. Check if it's not a mainwindow for this already existing window. +void X11Client::checkTransient(xcb_window_t w) +{ + if (m_originalTransientForId != w) + return; + w = verifyTransientFor(w, true); + setTransient(w); +} + +// returns true if cl is the transient_for window for this client, +// or recursively the transient_for window +bool X11Client::hasTransient(const AbstractClient* cl, bool indirect) const +{ + if (const X11Client *c = dynamic_cast(cl)) { + // checkGroupTransients() uses this to break loops, so hasTransient() must detect them + QList set; + return hasTransientInternal(c, indirect, set); + } + return false; +} + +bool X11Client::hasTransientInternal(const X11Client *cl, bool indirect, QList &set) const +{ + if (const X11Client *t = dynamic_cast(cl->transientFor())) { + if (t == this) + return true; + if (!indirect) + return false; + if (set.contains(cl)) + return false; + set.append(cl); + return hasTransientInternal(t, indirect, set); + } + if (!cl->isTransient()) + return false; + if (group() != cl->group()) + return false; + // cl is group transient, search from top + if (transients().contains(const_cast< X11Client *>(cl))) + return true; + if (!indirect) + return false; + if (set.contains(this)) + return false; + set.append(this); + for (auto it = transients().constBegin(); + it != transients().constEnd(); + ++it) { + const X11Client *c = qobject_cast(*it); + if (!c) { + continue; + } + if (c->hasTransientInternal(cl, indirect, set)) + return true; + } + return false; +} + +QList X11Client::mainClients() const +{ + if (!isTransient()) + return QList(); + if (const AbstractClient *t = transientFor()) + return QList{const_cast< AbstractClient* >(t)}; + QList result; + Q_ASSERT(group()); + for (auto it = group()->members().constBegin(); + it != group()->members().constEnd(); + ++it) + if ((*it)->hasTransient(this, false)) + result.append(*it); + return result; +} + +AbstractClient* X11Client::findModal(bool allow_itself) +{ + for (auto it = transients().constBegin(); + it != transients().constEnd(); + ++it) + if (AbstractClient* ret = (*it)->findModal(true)) + return ret; + if (isModal() && allow_itself) + return this; + return nullptr; +} + +// X11Client::window_group only holds the contents of the hint, +// but it should be used only to find the group, not for anything else +// Argument is only when some specific group needs to be set. +void X11Client::checkGroup(Group* set_group, bool force) +{ + Group* old_group = in_group; + if (old_group != nullptr) + old_group->ref(); // turn off automatic deleting + if (set_group != nullptr) { + if (set_group != in_group) { + if (in_group != nullptr) + in_group->removeMember(this); + in_group = set_group; + in_group->addMember(this); + } + } else if (info->groupLeader() != XCB_WINDOW_NONE) { + Group* new_group = workspace()->findGroup(info->groupLeader()); + X11Client *t = qobject_cast(transientFor()); + if (t != nullptr && t->group() != new_group) { + // move the window to the right group (e.g. a dialog provided + // by different app, but transient for this one, so make it part of that group) + new_group = t->group(); + } + if (new_group == nullptr) // doesn't exist yet + new_group = new Group(info->groupLeader()); + if (new_group != in_group) { + if (in_group != nullptr) + in_group->removeMember(this); + in_group = new_group; + in_group->addMember(this); + } + } else { + if (X11Client *t = qobject_cast(transientFor())) { + // doesn't have window group set, but is transient for something + // so make it part of that group + Group* new_group = t->group(); + if (new_group != in_group) { + if (in_group != nullptr) + in_group->removeMember(this); + in_group = t->group(); + in_group->addMember(this); + } + } else if (groupTransient()) { + // group transient which actually doesn't have a group :( + // try creating group with other windows with the same client leader + Group* new_group = workspace()->findClientLeaderGroup(this); + if (new_group == nullptr) + new_group = new Group(XCB_WINDOW_NONE); + if (new_group != in_group) { + if (in_group != nullptr) + in_group->removeMember(this); + in_group = new_group; + in_group->addMember(this); + } + } else { // Not transient without a group, put it in its client leader group. + // This might be stupid if grouping was used for e.g. taskbar grouping + // or minimizing together the whole group, but as long as it is used + // only for dialogs it's better to keep windows from one app in one group. + Group* new_group = workspace()->findClientLeaderGroup(this); + if (in_group != nullptr && in_group != new_group) { + in_group->removeMember(this); + in_group = nullptr; + } + if (new_group == nullptr) + new_group = new Group(XCB_WINDOW_NONE); + if (in_group != new_group) { + in_group = new_group; + in_group->addMember(this); + } + } + } + if (in_group != old_group || force) { + for (auto it = transients().constBegin(); + it != transients().constEnd(); + ) { + auto *c = *it; + // group transients in the old group are no longer transient for it + if (c->groupTransient() && c->group() != group()) { + removeTransientFromList(c); + it = transients().constBegin(); // restart, just in case something more has changed with the list + } else + ++it; + } + if (groupTransient()) { + // no longer transient for ones in the old group + if (old_group != nullptr) { + for (auto it = old_group->members().constBegin(); + it != old_group->members().constEnd(); + ++it) + (*it)->removeTransient(this); + } + // and make transient for all in the new group + for (auto it = group()->members().constBegin(); + it != group()->members().constEnd(); + ++it) { + if (*it == this) + break; // this means the window is only transient for windows mapped before it + (*it)->addTransient(this); + } + } + // group transient splashscreens should be transient even for windows + // in group mapped later + for (auto it = group()->members().constBegin(); + it != group()->members().constEnd(); + ++it) { + if (!(*it)->isSplash()) + continue; + if (!(*it)->groupTransient()) + continue; + if (*it == this || hasTransient(*it, true)) // TODO indirect? + continue; + addTransient(*it); + } + } + if (old_group != nullptr) + old_group->deref(); // can be now deleted if empty + checkGroupTransients(); + checkActiveModal(); + workspace()->updateClientLayer(this); +} + +// used by Workspace::findClientLeaderGroup() +void X11Client::changeClientLeaderGroup(Group* gr) +{ + // transientFor() != NULL are in the group of their mainwindow, so keep them there + if (transientFor() != nullptr) + return; + // also don't change the group for window which have group set + if (info->groupLeader()) + return; + checkGroup(gr); // change group +} + +bool X11Client::check_active_modal = false; + +void X11Client::checkActiveModal() +{ + // if the active window got new modal transient, activate it. + // cannot be done in AddTransient(), because there may temporarily + // exist loops, breaking findModal + X11Client *check_modal = dynamic_cast(workspace()->mostRecentlyActivatedClient()); + if (check_modal != nullptr && check_modal->check_active_modal) { + X11Client *new_modal = dynamic_cast(check_modal->findModal()); + if (new_modal != nullptr && new_modal != check_modal) { + if (!new_modal->isManaged()) + return; // postpone check until end of manage() + workspace()->activateClient(new_modal); + } + check_modal->check_active_modal = false; + } +} + } // namespace