summaryrefslogtreecommitdiffstats
path: root/kwin/group.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kwin/group.cpp')
-rw-r--r--kwin/group.cpp1118
1 files changed, 1118 insertions, 0 deletions
diff --git a/kwin/group.cpp b/kwin/group.cpp
new file mode 100644
index 000000000..405e32927
--- /dev/null
+++ b/kwin/group.cpp
@@ -0,0 +1,1118 @@
+/*****************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright (C) 1999, 2000 Matthias Ettrich <[email protected]>
+Copyright (C) 2003 Lubos Lunak <[email protected]>
+
+You can Freely distribute this program under the GNU General Public
+License. See the file "COPYING" for the exact licensing terms.
+******************************************************************/
+
+/*
+
+ This file contains things relevant to window grouping.
+
+*/
+
+//#define QT_CLEAN_NAMESPACE
+
+#include "group.h"
+
+#include "workspace.h"
+#include "client.h"
+
+#include <assert.h>
+#include <kstartupinfo.h>
+
+
+/*
+ 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 KWinInternal
+{
+
+/*
+ Consistency checks for window relations. Since transients are determinated
+ using Client::transiency_list and main windows are determined using Client::transientFor()
+ or the group for group transients, these have to match both ways.
+*/
+//#define ENABLE_TRANSIENCY_CHECK
+
+#ifdef NDEBUG
+#undef ENABLE_TRANSIENCY_CHECK
+#endif
+
+#ifdef ENABLE_TRANSIENCY_CHECK
+static bool transiencyCheckNonExistent = false;
+
+bool performTransiencyCheck()
+ {
+ bool ret = true;
+ ClientList clients = Workspace::self()->clients;
+ for( ClientList::ConstIterator it1 = clients.begin();
+ it1 != clients.end();
+ ++it1 )
+ {
+ if( (*it1)->deleting )
+ continue;
+ if( (*it1)->in_group == NULL )
+ {
+ kdDebug() << "TC: " << *it1 << " in not in a group" << endl;
+ ret = false;
+ }
+ else if( !(*it1)->in_group->members().contains( *it1 ))
+ {
+ kdDebug() << "TC: " << *it1 << " has a group " << (*it1)->in_group << " but group does not contain it" << endl;
+ ret = false;
+ }
+ if( !(*it1)->isTransient())
+ {
+ if( !(*it1)->mainClients().isEmpty())
+ {
+ kdDebug() << "TC: " << *it1 << " is not transient, has main clients:" << (*it1)->mainClients() << endl;
+ ret = false;
+ }
+ }
+ else
+ {
+ ClientList mains = (*it1)->mainClients();
+ for( ClientList::ConstIterator it2 = mains.begin();
+ it2 != mains.end();
+ ++it2 )
+ {
+ if( transiencyCheckNonExistent
+ && !Workspace::self()->clients.contains( *it2 )
+ && !Workspace::self()->desktops.contains( *it2 ))
+ {
+ kdDebug() << "TC:" << *it1 << " has non-existent main client " << endl;
+ kdDebug() << "TC2:" << *it2 << endl; // this may crash
+ ret = false;
+ continue;
+ }
+ if( !(*it2)->transients_list.contains( *it1 ))
+ {
+ kdDebug() << "TC:" << *it1 << " has main client " << *it2 << " but main client does not have it as a transient" << endl;
+ ret = false;
+ }
+ }
+ }
+ ClientList trans = (*it1)->transients_list;
+ for( ClientList::ConstIterator it2 = trans.begin();
+ it2 != trans.end();
+ ++it2 )
+ {
+ if( transiencyCheckNonExistent
+ && !Workspace::self()->clients.contains( *it2 )
+ && !Workspace::self()->desktops.contains( *it2 ))
+ {
+ kdDebug() << "TC:" << *it1 << " has non-existent transient " << endl;
+ kdDebug() << "TC2:" << *it2 << endl; // this may crash
+ ret = false;
+ continue;
+ }
+ if( !(*it2)->mainClients().contains( *it1 ))
+ {
+ kdDebug() << "TC:" << *it1 << " has transient " << *it2 << " but transient does not have it as a main client" << endl;
+ ret = false;
+ }
+ }
+ }
+ GroupList groups = Workspace::self()->groups;
+ for( GroupList::ConstIterator it1 = groups.begin();
+ it1 != groups.end();
+ ++it1 )
+ {
+ ClientList members = (*it1)->members();
+ for( ClientList::ConstIterator it2 = members.begin();
+ it2 != members.end();
+ ++it2 )
+ {
+ if( (*it2)->in_group != *it1 )
+ {
+ kdDebug() << "TC: Group " << *it1 << " contains client " << *it2 << " but client is not in that group" << endl;
+ ret = false;
+ }
+ }
+ }
+ return ret;
+ }
+
+static QString transiencyCheckStartBt;
+static const Client* transiencyCheckClient;
+static int transiencyCheck = 0;
+
+static void startTransiencyCheck( const QString& bt, const Client* c, bool ne )
+ {
+ if( ++transiencyCheck == 1 )
+ {
+ transiencyCheckStartBt = bt;
+ transiencyCheckClient = c;
+ }
+ if( ne )
+ transiencyCheckNonExistent = true;
+ }
+static void checkTransiency()
+ {
+ if( --transiencyCheck == 0 )
+ {
+ if( !performTransiencyCheck())
+ {
+ kdDebug() << "BT:" << transiencyCheckStartBt << endl;
+ kdDebug() << "CLIENT:" << transiencyCheckClient << endl;
+ assert( false );
+ }
+ transiencyCheckNonExistent = false;
+ }
+ }
+class TransiencyChecker
+ {
+ public:
+ TransiencyChecker( const QString& bt, const Client*c ) { startTransiencyCheck( bt, c, false ); }
+ ~TransiencyChecker() { checkTransiency(); }
+ };
+
+void checkNonExistentClients()
+ {
+ startTransiencyCheck( kdBacktrace(), NULL, true );
+ checkTransiency();
+ }
+
+#define TRANSIENCY_CHECK( c ) TransiencyChecker transiency_checker( kdBacktrace(), c )
+
+#else
+
+#define TRANSIENCY_CHECK( c )
+
+void checkNonExistentClients()
+ {
+ }
+
+#endif
+
+//********************************************
+// Group
+//********************************************
+
+Group::Group( Window leader_P, Workspace* workspace_P )
+ : leader_client( NULL ),
+ leader_wid( leader_P ),
+ _workspace( workspace_P ),
+ leader_info( NULL ),
+ user_time( -1U ),
+ refcount( 0 )
+ {
+ if( leader_P != None )
+ {
+ leader_client = workspace_P->findClient( WindowMatchPredicate( leader_P ));
+ unsigned long properties[ 2 ] = { 0, NET::WM2StartupId };
+ leader_info = new NETWinInfo( qt_xdisplay(), leader_P, workspace()->rootWin(),
+ properties, 2 );
+ }
+ workspace()->addGroup( this, Allowed );
+ }
+
+Group::~Group()
+ {
+ delete leader_info;
+ }
+
+QPixmap Group::icon() const
+ {
+ if( leader_client != NULL )
+ return leader_client->icon();
+ else if( leader_wid != None )
+ {
+ QPixmap ic;
+ Client::readIcons( leader_wid, &ic, NULL );
+ return ic;
+ }
+ return QPixmap();
+ }
+
+QPixmap Group::miniIcon() const
+ {
+ if( leader_client != NULL )
+ return leader_client->miniIcon();
+ else if( leader_wid != None )
+ {
+ QPixmap ic;
+ Client::readIcons( leader_wid, NULL, &ic );
+ return ic;
+ }
+ return QPixmap();
+ }
+
+void Group::addMember( Client* member_P )
+ {
+ TRANSIENCY_CHECK( member_P );
+ _members.append( member_P );
+// kdDebug() << "GROUPADD:" << this << ":" << member_P << endl;
+// kdDebug() << kdBacktrace() << endl;
+ }
+
+void Group::removeMember( Client* member_P )
+ {
+ TRANSIENCY_CHECK( member_P );
+// kdDebug() << "GROUPREMOVE:" << this << ":" << member_P << endl;
+// kdDebug() << kdBacktrace() << endl;
+ Q_ASSERT( _members.contains( member_P ));
+ _members.remove( member_P );
+// there are cases when automatic deleting of groups must be delayed,
+// e.g. when removing a member and doing some operation on the possibly
+// other members of the group (which would be however deleted already
+// if there were no other members)
+ if( refcount == 0 && _members.isEmpty())
+ {
+ workspace()->removeGroup( this, Allowed );
+ delete this;
+ }
+ }
+
+void Group::ref()
+ {
+ ++refcount;
+ }
+
+void Group::deref()
+ {
+ if( --refcount == 0 && _members.isEmpty())
+ {
+ workspace()->removeGroup( this, Allowed );
+ delete this;
+ }
+ }
+
+void Group::gotLeader( Client* leader_P )
+ {
+ assert( leader_P->window() == leader_wid );
+ leader_client = leader_P;
+ }
+
+void Group::lostLeader()
+ {
+ assert( !_members.contains( leader_client ));
+ leader_client = NULL;
+ if( _members.isEmpty())
+ {
+ workspace()->removeGroup( this, Allowed );
+ delete this;
+ }
+ }
+
+void Group::getIcons()
+ {
+ // TODO - also needs adding the flag to NETWinInfo
+ }
+
+//***************************************
+// Workspace
+//***************************************
+
+Group* Workspace::findGroup( Window leader ) const
+ {
+ assert( leader != None );
+ for( GroupList::ConstIterator it = groups.begin();
+ it != groups.end();
+ ++it )
+ if( (*it)->leader() == leader )
+ return *it;
+ return NULL;
+ }
+
+// Client is group transient, but has no group set. Try to find
+// group with windows with the same client leader.
+Group* Workspace::findClientLeaderGroup( const Client* c ) const
+ {
+ TRANSIENCY_CHECK( c );
+ Group* ret = NULL;
+ for( ClientList::ConstIterator it = clients.begin();
+ it != clients.end();
+ ++it )
+ {
+ if( *it == c )
+ continue;
+ if( (*it)->wmClientLeader() == c->wmClientLeader())
+ {
+ if( ret == NULL || 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.
+ ClientList old_group = (*it)->group()->members();
+ // old_group autodeletes when being empty
+ for( unsigned int pos = 0;
+ pos < old_group.count();
+ ++pos )
+ {
+ Client* tmp = old_group[ pos ];
+ if( tmp != c )
+ tmp->changeClientLeaderGroup( ret );
+ }
+ }
+ }
+ }
+ return ret;
+ }
+
+void Workspace::updateMinimizedOfTransients( Client* c )
+ {
+ // if mainwindow is minimized or shaded, minimize transients too
+ if ( c->isMinimized() || c->isShade() )
+ {
+ for( ClientList::ConstIterator it = c->transients().begin();
+ it != c->transients().end();
+ ++it )
+ {
+ if( !(*it)->isMinimized()
+ && !(*it)->isTopMenu() ) // topmenus are not minimized, they're hidden
+ {
+ (*it)->minimize( true ); // avoid animation
+ updateMinimizedOfTransients( (*it) );
+ }
+ }
+ }
+ else
+ { // else unmiminize the transients
+ for( ClientList::ConstIterator it = c->transients().begin();
+ it != c->transients().end();
+ ++it )
+ {
+ if( (*it)->isMinimized()
+ && !(*it)->isTopMenu())
+ {
+ (*it)->unminimize( true ); // avoid animation
+ updateMinimizedOfTransients( (*it) );
+ }
+ }
+ }
+ }
+
+
+/*!
+ Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops.
+ */
+void Workspace::updateOnAllDesktopsOfTransients( Client* c )
+ {
+ for( ClientList::ConstIterator it = c->transients().begin();
+ it != c->transients().end();
+ ++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( Window w )
+ {
+ TRANSIENCY_CHECK( NULL );
+ for( ClientList::ConstIterator it = clients.begin();
+ it != clients.end();
+ ++it )
+ (*it)->checkTransient( w );
+ }
+
+
+
+//****************************************
+// Client
+//****************************************
+
+// hacks for broken apps here
+// all resource classes are forced to be lowercase
+bool Client::resourceMatch( const Client* c1, const Client* c2 )
+ {
+ // xv has "xv" as resource name, and different strings starting with "XV" as resource class
+ if( qstrncmp( c1->resourceClass(), "xv", 2 ) == 0 && c1->resourceName() == "xv" )
+ return qstrncmp( c2->resourceClass(), "xv", 2 ) == 0 && c2->resourceName() == "xv";
+ // Mozilla has "Mozilla" as resource name, and different strings as resource class
+ if( c1->resourceName() == "mozilla" )
+ return c2->resourceName() == "mozilla";
+ return c1->resourceClass() == c2->resourceClass();
+ }
+
+bool Client::belongToSameApplication( const Client* c1, const Client* c2, bool active_hack )
+ {
+ 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()
+ || 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
+ ; // different client leader
+ else if( !resourceMatch( c1, c2 ))
+ ; // different apps
+ else if( !sameAppWindowRoleMatch( c1, c2, active_hack ))
+ ; // "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 Client::sameAppWindowRoleMatch( const Client* c1, const Client* c2, bool active_hack )
+ {
+ if( c1->isTransient())
+ {
+ while( c1->transientFor() != NULL )
+ c1 = c1->transientFor();
+ 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( c2->transientFor() != NULL )
+ c2 = c2->transientFor();
+ if( c2->groupTransient())
+ return c1->group() == c2->group();
+#if 0
+ || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
+#endif
+ }
+ int pos1 = c1->windowRole().find( '#' );
+ int pos2 = c2->windowRole().find( '#' );
+ if(( pos1 >= 0 && pos2 >= 0 )
+ ||
+ // hacks here
+ // Mozilla has resourceName() and resourceClass() swapped
+ c1->resourceName() == "mozilla" && c2->resourceName() == "mozilla" )
+ {
+ 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.).
+
+ Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after
+ possibly being adjusted by KWin. Client::transient_for points to the Client
+ this Client is transient for, or is NULL. If Client::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, Client::transient_for is NULL,
+ and Client::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).
+
+ Client::original_transient_for_id is the value of the property, which
+ may be different if Client::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()
+*/
+
+void Client::readTransient()
+ {
+ TRANSIENCY_CHECK( this );
+ Window new_transient_for_id;
+ if( XGetTransientForHint( qt_xdisplay(), window(), &new_transient_for_id ))
+ {
+ original_transient_for_id = new_transient_for_id;
+ new_transient_for_id = verifyTransientFor( new_transient_for_id, true );
+ }
+ else
+ {
+ original_transient_for_id = None;
+ new_transient_for_id = verifyTransientFor( None, false );
+ }
+ setTransient( new_transient_for_id );
+ }
+
+void Client::setTransient( Window new_transient_for_id )
+ {
+ TRANSIENCY_CHECK( this );
+ if( new_transient_for_id != transient_for_id )
+ {
+ removeFromMainClients();
+ transient_for = NULL;
+ transient_for_id = new_transient_for_id;
+ if( transient_for_id != None && !groupTransient())
+ {
+ transient_for = workspace()->findClient( WindowMatchPredicate( transient_for_id ));
+ assert( transient_for != NULL ); // verifyTransient() had to check this
+ transient_for->addTransient( this );
+ } // checkGroup() will check 'check_active_modal'
+ checkGroup( NULL, true ); // force, because transiency has changed
+ if( isTopMenu())
+ workspace()->updateCurrentTopMenu();
+ workspace()->updateClientLayer( this );
+ }
+ }
+
+void Client::removeFromMainClients()
+ {
+ TRANSIENCY_CHECK( this );
+ if( transientFor() != NULL )
+ transientFor()->removeTransient( this );
+ if( groupTransient())
+ {
+ for( ClientList::ConstIterator it = group()->members().begin();
+ it != group()->members().end();
+ ++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 Client::cleanGrouping()
+ {
+ TRANSIENCY_CHECK( this );
+// kdDebug() << "CLEANGROUPING:" << this << endl;
+// for( ClientList::ConstIterator it = group()->members().begin();
+// it != group()->members().end();
+// ++it )
+// kdDebug() << "CL:" << *it << endl;
+// ClientList mains;
+// mains = mainClients();
+// for( ClientList::ConstIterator it = mains.begin();
+// it != mains.end();
+// ++it )
+// kdDebug() << "MN:" << *it << endl;
+ removeFromMainClients();
+// kdDebug() << "CLEANGROUPING2:" << this << endl;
+// for( ClientList::ConstIterator it = group()->members().begin();
+// it != group()->members().end();
+// ++it )
+// kdDebug() << "CL2:" << *it << endl;
+// mains = mainClients();
+// for( ClientList::ConstIterator it = mains.begin();
+// it != mains.end();
+// ++it )
+// kdDebug() << "MN2:" << *it << endl;
+ for( ClientList::ConstIterator it = transients_list.begin();
+ it != transients_list.end();
+ )
+ {
+ if( (*it)->transientFor() == this )
+ {
+ ClientList::ConstIterator it2 = it++;
+ removeTransient( *it2 );
+ }
+ else
+ ++it;
+ }
+// kdDebug() << "CLEANGROUPING3:" << this << endl;
+// for( ClientList::ConstIterator it = group()->members().begin();
+// it != group()->members().end();
+// ++it )
+// kdDebug() << "CL3:" << *it << endl;
+// mains = mainClients();
+// for( ClientList::ConstIterator it = mains.begin();
+// it != mains.end();
+// ++it )
+// kdDebug() << "MN3:" << *it << endl;
+ // 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 :(
+ ClientList group_members = group()->members();
+ group()->removeMember( this );
+ in_group = NULL;
+ for( ClientList::ConstIterator it = group_members.begin();
+ it != group_members.end();
+ ++it )
+ (*it)->removeTransient( this );
+// kdDebug() << "CLEANGROUPING4:" << this << endl;
+// for( ClientList::ConstIterator it = group_members.begin();
+// it != group_members.end();
+// ++it )
+// kdDebug() << "CL4:" << *it << endl;
+ }
+
+// 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 Client::checkGroupTransients()
+ {
+ TRANSIENCY_CHECK( this );
+ for( ClientList::ConstIterator it1 = group()->members().begin();
+ it1 != group()->members().end();
+ ++it1 )
+ {
+ if( !(*it1)->groupTransient()) // check all group transients in the group
+ continue; // TODO optimize to check only the changed ones?
+ for( ClientList::ConstIterator it2 = group()->members().begin();
+ it2 != group()->members().end();
+ ++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( Client* cl = (*it2)->transientFor();
+ cl != NULL;
+ cl = cl->transientFor())
+ {
+ if( cl == *it1 )
+ { // don't use removeTransient(), that would modify *it2 too
+ (*it2)->transients_list.remove( *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)->transients_list.remove( *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( ClientList::ConstIterator it3 = group()->members().begin();
+ it3 != group()->members().end();
+ ++it3 )
+ {
+ if( *it1 == *it2 || *it2 == *it3 || *it1 == *it3 )
+ continue;
+ if( (*it2)->hasTransient( *it1, false ) && (*it3)->hasTransient( *it1, false ))
+ {
+ if( (*it2)->hasTransient( *it3, true ))
+ (*it2)->transients_list.remove( *it1 );
+ if( (*it3)->hasTransient( *it2, true ))
+ (*it3)->transients_list.remove( *it1 );
+ }
+ }
+ }
+ }
+ }
+
+/*!
+ Check that the window is not transient for itself, and similar nonsense.
+ */
+Window Client::verifyTransientFor( Window new_transient_for, bool defined )
+ {
+ Window 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 == None )
+ new_transient_for = workspace()->rootWin();
+ if( new_transient_for == None )
+ if( defined ) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window
+ new_property_value = new_transient_for = workspace()->rootWin();
+ else
+ return None;
+ if( new_transient_for == window()) // pointing to self
+ { // also fix the property itself
+ kdWarning( 1216 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." << endl;
+ new_property_value = new_transient_for = workspace()->rootWin();
+ }
+// 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.
+ WId before_search = new_transient_for;
+ while( new_transient_for != None
+ && new_transient_for != workspace()->rootWin()
+ && !workspace()->findClient( WindowMatchPredicate( new_transient_for )))
+ {
+ Window root_return, parent_return;
+ Window* wins = NULL;
+ unsigned int nwins;
+ int r = XQueryTree(qt_xdisplay(), new_transient_for, &root_return, &parent_return, &wins, &nwins);
+ if ( wins )
+ XFree((void *) wins);
+ if ( r == 0)
+ break;
+ new_transient_for = parent_return;
+ }
+ if( Client* new_transient_for_client = workspace()->findClient( WindowMatchPredicate( new_transient_for )))
+ {
+ if( new_transient_for != before_search )
+ {
+ kdDebug( 1212 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window "
+ << before_search << ", child of " << new_transient_for_client << ", adjusting." << endl;
+ 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;
+ Window loop_pos = new_transient_for;
+ while( loop_pos != None && loop_pos != workspace()->rootWin())
+ {
+ Client* pos = workspace()->findClient( WindowMatchPredicate( loop_pos ));
+ if( pos == NULL )
+ break;
+ loop_pos = pos->transient_for_id;
+ if( --count == 0 || pos == this )
+ {
+ kdWarning( 1216 ) << "Client " << this << " caused WM_TRANSIENT_FOR loop." << endl;
+ new_transient_for = workspace()->rootWin();
+ }
+ }
+ if( new_transient_for != workspace()->rootWin()
+ && workspace()->findClient( WindowMatchPredicate( new_transient_for )) == NULL )
+ { // it's transient for a specific window, but that window is not mapped
+ new_transient_for = workspace()->rootWin();
+ }
+ if( new_property_value != original_transient_for_id )
+ XSetTransientForHint( qt_xdisplay(), window(), new_property_value );
+ return new_transient_for;
+ }
+
+void Client::addTransient( Client* cl )
+ {
+ TRANSIENCY_CHECK( this );
+ assert( !transients_list.contains( cl ));
+// assert( !cl->hasTransient( this, true )); will be fixed in checkGroupTransients()
+ assert( cl != this );
+ transients_list.append( cl );
+ if( workspace()->mostRecentlyActivatedClient() == this && cl->isModal())
+ check_active_modal = true;
+// kdDebug() << "ADDTRANS:" << this << ":" << cl << endl;
+// kdDebug() << kdBacktrace() << endl;
+// for( ClientList::ConstIterator it = transients_list.begin();
+// it != transients_list.end();
+// ++it )
+// kdDebug() << "AT:" << (*it) << endl;
+ }
+
+void Client::removeTransient( Client* cl )
+ {
+ TRANSIENCY_CHECK( this );
+// kdDebug() << "REMOVETRANS:" << this << ":" << cl << endl;
+// kdDebug() << kdBacktrace() << endl;
+ transients_list.remove( cl );
+ // cl is transient for this, but this is going away
+ // make cl group transient
+ if( cl->transientFor() == this )
+ {
+ cl->transient_for_id = None;
+ cl->transient_for = NULL; // SELI
+// SELI cl->setTransient( workspace()->rootWin());
+ cl->setTransient( None );
+ }
+ }
+
+// A new window has been mapped. Check if it's not a mainwindow for this already existing window.
+void Client::checkTransient( Window w )
+ {
+ TRANSIENCY_CHECK( this );
+ if( original_transient_for_id != 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 Client::hasTransient( const Client* cl, bool indirect ) const
+ {
+ // checkGroupTransients() uses this to break loops, so hasTransient() must detect them
+ ConstClientList set;
+ return hasTransientInternal( cl, indirect, set );
+ }
+
+bool Client::hasTransientInternal( const Client* cl, bool indirect, ConstClientList& set ) const
+ {
+ if( cl->transientFor() != NULL )
+ {
+ if( cl->transientFor() == this )
+ return true;
+ if( !indirect )
+ return false;
+ if( set.contains( cl ))
+ return false;
+ set.append( cl );
+ return hasTransientInternal( cl->transientFor(), 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< Client* >( cl )))
+ return true;
+ if( !indirect )
+ return false;
+ if( set.contains( this ))
+ return false;
+ set.append( this );
+ for( ClientList::ConstIterator it = transients().begin();
+ it != transients().end();
+ ++it )
+ if( (*it)->hasTransientInternal( cl, indirect, set ))
+ return true;
+ return false;
+ }
+
+ClientList Client::mainClients() const
+ {
+ if( !isTransient())
+ return ClientList();
+ if( transientFor() != NULL )
+ return ClientList() << const_cast< Client* >( transientFor());
+ ClientList result;
+ for( ClientList::ConstIterator it = group()->members().begin();
+ it != group()->members().end();
+ ++it )
+ if((*it)->hasTransient( this, false ))
+ result.append( *it );
+ return result;
+ }
+
+Client* Client::findModal()
+ {
+ for( ClientList::ConstIterator it = transients().begin();
+ it != transients().end();
+ ++it )
+ if( Client* ret = (*it)->findModal())
+ return ret;
+ if( isModal())
+ return this;
+ return NULL;
+ }
+
+// Client::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 Client::checkGroup( Group* set_group, bool force )
+ {
+ TRANSIENCY_CHECK( this );
+ Group* old_group = in_group;
+ if( old_group != NULL )
+ old_group->ref(); // turn off automatic deleting
+ if( set_group != NULL )
+ {
+ if( set_group != in_group )
+ {
+ if( in_group != NULL )
+ in_group->removeMember( this );
+ in_group = set_group;
+ in_group->addMember( this );
+ }
+ }
+ else if( window_group != None )
+ {
+ Group* new_group = workspace()->findGroup( window_group );
+ if( transientFor() != NULL && transientFor()->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 = transientFor()->group();
+ }
+ if( new_group == NULL ) // doesn't exist yet
+ new_group = new Group( window_group, workspace());
+ if( new_group != in_group )
+ {
+ if( in_group != NULL )
+ in_group->removeMember( this );
+ in_group = new_group;
+ in_group->addMember( this );
+ }
+ }
+ else
+ {
+ if( transientFor() != NULL )
+ { // doesn't have window group set, but is transient for something
+ // so make it part of that group
+ Group* new_group = transientFor()->group();
+ if( new_group != in_group )
+ {
+ if( in_group != NULL )
+ in_group->removeMember( this );
+ in_group = transientFor()->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 == NULL )
+ new_group = new Group( None, workspace());
+ if( new_group != in_group )
+ {
+ if( in_group != NULL )
+ 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 its 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 != NULL && in_group != new_group )
+ {
+ in_group->removeMember( this );
+ in_group = NULL;
+ }
+ if( new_group == NULL )
+ new_group = new Group( None, workspace() );
+ if( in_group != new_group )
+ {
+ in_group = new_group;
+ in_group->addMember( this );
+ }
+ }
+ }
+ if( in_group != old_group || force )
+ {
+ for( ClientList::Iterator it = transients_list.begin();
+ it != transients_list.end();
+ )
+ { // group transients in the old group are no longer transient for it
+ if( (*it)->groupTransient() && (*it)->group() != group())
+ it = transients_list.remove( it );
+ else
+ ++it;
+ }
+ if( groupTransient())
+ {
+ // no longer transient for ones in the old group
+ if( old_group != NULL )
+ {
+ for( ClientList::ConstIterator it = old_group->members().begin();
+ it != old_group->members().end();
+ ++it )
+ (*it)->removeTransient( this );
+ }
+ // and make transient for all in the new group
+ for( ClientList::ConstIterator it = group()->members().begin();
+ it != group()->members().end();
+ ++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( ClientList::ConstIterator it = group()->members().begin();
+ it != group()->members().end();
+ ++it )
+ {
+ if( !(*it)->isSplash())
+ continue;
+ if( !(*it)->groupTransient())
+ continue;
+ if( *it == this || hasTransient( *it, true )) // TODO indirect?
+ continue;
+ addTransient( *it );
+ }
+ }
+ if( old_group != NULL )
+ old_group->deref(); // can be now deleted if empty
+ checkGroupTransients();
+ checkActiveModal();
+ workspace()->updateClientLayer( this );
+ }
+
+// used by Workspace::findClientLeaderGroup()
+void Client::changeClientLeaderGroup( Group* gr )
+ {
+ // transientFor() != NULL are in the group of their mainwindow, so keep them there
+ if( transientFor() != NULL )
+ return;
+ // also don't change the group for window which have group set
+ if( window_group )
+ return;
+ checkGroup( gr ); // change group
+ }
+
+bool Client::check_active_modal = false;
+
+void Client::checkActiveModal()
+ {
+ // if the active window got new modal transient, activate it.
+ // cannot be done in AddTransient(), because there may temporarily
+ // exist loops, breaking findModal
+ Client* check_modal = workspace()->mostRecentlyActivatedClient();
+ if( check_modal != NULL && check_modal->check_active_modal )
+ {
+ Client* new_modal = check_modal->findModal();
+ if( new_modal != NULL && 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