diff options
Diffstat (limited to 'kwin/group.cpp')
-rw-r--r-- | kwin/group.cpp | 1118 |
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 <ettrich@kde.org> +Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org> + +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 |