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