group.cpp

00001 /*****************************************************************
00002  KWin - the KDE window manager
00003  This file is part of the KDE project.
00004 
00005 Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
00006 Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
00007 
00008 You can Freely distribute this program under the GNU General Public
00009 License. See the file "COPYING" for the exact licensing terms.
00010 ******************************************************************/
00011 
00012 /*
00013 
00014  This file contains things relevant to window grouping.
00015 
00016 */
00017 
00018 //#define QT_CLEAN_NAMESPACE
00019 
00020 #include "group.h"
00021 
00022 #include "workspace.h"
00023 #include "client.h"
00024 
00025 #include <assert.h>
00026 #include <kstartupinfo.h>
00027 
00028 
00029 /*
00030  TODO
00031  Rename as many uses of 'transient' as possible (hasTransient->hasSubwindow,etc.),
00032  or I'll get it backwards in half of the cases again.
00033 */
00034 
00035 namespace KWinInternal
00036 {
00037 
00038 /*
00039  Consistency checks for window relations. Since transients are determinated
00040  using Client::transiency_list and main windows are determined using Client::transientFor()
00041  or the group for group transients, these have to match both ways.
00042 */
00043 //#define ENABLE_TRANSIENCY_CHECK
00044 
00045 #ifdef NDEBUG
00046 #undef ENABLE_TRANSIENCY_CHECK
00047 #endif
00048 
00049 #ifdef ENABLE_TRANSIENCY_CHECK
00050 static bool transiencyCheckNonExistent = false;
00051 
00052 bool performTransiencyCheck()
00053     {
00054     bool ret = true;
00055     ClientList clients = Workspace::self()->clients;
00056     for( ClientList::ConstIterator it1 = clients.begin();
00057          it1 != clients.end();
00058          ++it1 )
00059         {
00060         if( (*it1)->deleting )
00061             continue;
00062         if( (*it1)->in_group == NULL )
00063             {
00064             kdDebug() << "TC: " << *it1 << " in not in a group" << endl;
00065             ret = false;
00066             }
00067         else if( !(*it1)->in_group->members().contains( *it1 ))
00068             {
00069             kdDebug() << "TC: " << *it1 << " has a group " << (*it1)->in_group << " but group does not contain it" << endl;
00070             ret = false;
00071             }
00072         if( !(*it1)->isTransient())
00073             {
00074             if( !(*it1)->mainClients().isEmpty())
00075                 {
00076                 kdDebug() << "TC: " << *it1 << " is not transient, has main clients:" << (*it1)->mainClients() << endl;
00077                 ret = false;
00078                 }
00079             }
00080         else
00081             {
00082             ClientList mains = (*it1)->mainClients();
00083             for( ClientList::ConstIterator it2 = mains.begin();
00084                  it2 != mains.end();
00085                  ++it2 )
00086                 {
00087                 if( transiencyCheckNonExistent
00088                     && !Workspace::self()->clients.contains( *it2 )
00089                     && !Workspace::self()->desktops.contains( *it2 ))
00090                     {
00091                     kdDebug() << "TC:" << *it1 << " has non-existent main client " << endl;
00092                     kdDebug() << "TC2:" << *it2 << endl; // this may crash
00093                     ret = false;
00094                     continue;
00095                     }
00096                 if( !(*it2)->transients_list.contains( *it1 ))
00097                     {
00098                     kdDebug() << "TC:" << *it1 << " has main client " << *it2 << " but main client does not have it as a transient" << endl;
00099                     ret = false;
00100                     }
00101                 }
00102             }
00103         ClientList trans = (*it1)->transients_list;
00104         for( ClientList::ConstIterator it2 = trans.begin();
00105              it2 != trans.end();
00106              ++it2 )
00107             {
00108             if( transiencyCheckNonExistent
00109                 && !Workspace::self()->clients.contains( *it2 )
00110                 && !Workspace::self()->desktops.contains( *it2 ))
00111                 {
00112                 kdDebug() << "TC:" << *it1 << " has non-existent transient " << endl;
00113                 kdDebug() << "TC2:" << *it2 << endl; // this may crash
00114                 ret = false;
00115                 continue;
00116                 }
00117             if( !(*it2)->mainClients().contains( *it1 ))
00118                 {
00119                 kdDebug() << "TC:" << *it1 << " has transient " << *it2 << " but transient does not have it as a main client" << endl;
00120                 ret = false;
00121                 }
00122             }
00123         }
00124     GroupList groups = Workspace::self()->groups;
00125     for( GroupList::ConstIterator it1 = groups.begin();
00126          it1 != groups.end();
00127          ++it1 )
00128         {
00129         ClientList members = (*it1)->members();
00130         for( ClientList::ConstIterator it2 = members.begin();
00131              it2 != members.end();
00132              ++it2 )
00133             {
00134             if( (*it2)->in_group != *it1 )
00135                 {
00136                 kdDebug() << "TC: Group " << *it1 << " contains client " << *it2 << " but client is not in that group" << endl;
00137                 ret = false;
00138                 }
00139             }
00140         }
00141     return ret;
00142     }
00143 
00144 static QString transiencyCheckStartBt;
00145 static const Client* transiencyCheckClient;
00146 static int transiencyCheck = 0;
00147 
00148 static void startTransiencyCheck( const QString& bt, const Client* c, bool ne )
00149     {
00150     if( ++transiencyCheck == 1 )
00151         {
00152         transiencyCheckStartBt = bt;
00153         transiencyCheckClient = c;
00154         }
00155     if( ne )
00156         transiencyCheckNonExistent = true;
00157     }
00158 static void checkTransiency()
00159     {
00160     if( --transiencyCheck == 0 )
00161         {
00162         if( !performTransiencyCheck())
00163             {
00164             kdDebug() << "BT:" << transiencyCheckStartBt << endl;
00165             kdDebug() << "CLIENT:" << transiencyCheckClient << endl;
00166             assert( false );
00167             }
00168         transiencyCheckNonExistent = false;
00169         }
00170     }
00171 class TransiencyChecker
00172     {
00173     public:
00174         TransiencyChecker( const QString& bt, const Client*c ) { startTransiencyCheck( bt, c, false ); }
00175         ~TransiencyChecker() { checkTransiency(); }
00176     };
00177 
00178 void checkNonExistentClients()
00179     {
00180     startTransiencyCheck( kdBacktrace(), NULL, true );
00181     checkTransiency();
00182     }
00183 
00184 #define TRANSIENCY_CHECK( c ) TransiencyChecker transiency_checker( kdBacktrace(), c )
00185 
00186 #else
00187 
00188 #define TRANSIENCY_CHECK( c )
00189 
00190 void checkNonExistentClients()
00191     {
00192     }
00193 
00194 #endif
00195 
00196 //********************************************
00197 // Group
00198 //********************************************
00199 
00200 Group::Group( Window leader_P, Workspace* workspace_P )
00201     :   leader_client( NULL ),
00202         leader_wid( leader_P ),
00203         _workspace( workspace_P ),
00204         leader_info( NULL ),
00205         user_time( -1U ),
00206         refcount( 0 )
00207     {
00208     if( leader_P != None )
00209         {
00210         leader_client = workspace_P->findClient( WindowMatchPredicate( leader_P ));
00211         unsigned long properties[ 2 ] = { 0, NET::WM2StartupId };
00212         leader_info = new NETWinInfo( qt_xdisplay(), leader_P, workspace()->rootWin(),
00213             properties, 2 );
00214         }
00215     workspace()->addGroup( this, Allowed );
00216     }
00217 
00218 Group::~Group()
00219     {
00220     delete leader_info;
00221     }
00222 
00223 QPixmap Group::icon() const
00224     {
00225     if( leader_client != NULL )
00226         return leader_client->icon();
00227     else if( leader_wid != None )
00228         {
00229         QPixmap ic;
00230         Client::readIcons( leader_wid, &ic, NULL );
00231         return ic;
00232         }
00233     return QPixmap();
00234     }
00235 
00236 QPixmap Group::miniIcon() const
00237     {
00238     if( leader_client != NULL )
00239         return leader_client->miniIcon();
00240     else if( leader_wid != None )
00241         {
00242         QPixmap ic;
00243         Client::readIcons( leader_wid, NULL, &ic );
00244         return ic;
00245         }
00246     return QPixmap();
00247     }
00248 
00249 void Group::addMember( Client* member_P )
00250     {
00251     TRANSIENCY_CHECK( member_P );
00252     _members.append( member_P );
00253 //    kdDebug() << "GROUPADD:" << this << ":" << member_P << endl;
00254 //    kdDebug() << kdBacktrace() << endl;
00255     }
00256 
00257 void Group::removeMember( Client* member_P )
00258     {
00259     TRANSIENCY_CHECK( member_P );
00260 //    kdDebug() << "GROUPREMOVE:" << this << ":" << member_P << endl;
00261 //    kdDebug() << kdBacktrace() << endl;
00262     Q_ASSERT( _members.contains( member_P ));
00263     _members.remove( member_P );
00264 // there are cases when automatic deleting of groups must be delayed,
00265 // e.g. when removing a member and doing some operation on the possibly
00266 // other members of the group (which would be however deleted already
00267 // if there were no other members)
00268     if( refcount == 0 && _members.isEmpty())
00269         {
00270         workspace()->removeGroup( this, Allowed );
00271         delete this;
00272         }
00273     }
00274 
00275 void Group::ref()
00276     {
00277     ++refcount;
00278     }
00279 
00280 void Group::deref()
00281     {
00282     if( --refcount == 0 && _members.isEmpty())
00283         {
00284         workspace()->removeGroup( this, Allowed );
00285         delete this;
00286         }
00287     }
00288 
00289 void Group::gotLeader( Client* leader_P )
00290     {
00291     assert( leader_P->window() == leader_wid );
00292     leader_client = leader_P;
00293     }
00294 
00295 void Group::lostLeader()
00296     {
00297     assert( !_members.contains( leader_client ));
00298     leader_client = NULL;
00299     if( _members.isEmpty())
00300         {
00301         workspace()->removeGroup( this, Allowed );
00302         delete this;
00303         }
00304     }
00305 
00306 void Group::getIcons()
00307     {
00308     // TODO - also needs adding the flag to NETWinInfo
00309     }
00310 
00311 //***************************************
00312 // Workspace
00313 //***************************************
00314 
00315 Group* Workspace::findGroup( Window leader ) const
00316     {
00317     assert( leader != None );
00318     for( GroupList::ConstIterator it = groups.begin();
00319          it != groups.end();
00320          ++it )
00321         if( (*it)->leader() == leader )
00322             return *it;
00323     return NULL;
00324     }
00325 
00326 // Client is group transient, but has no group set. Try to find
00327 // group with windows with the same client leader.
00328 Group* Workspace::findClientLeaderGroup( const Client* c ) const
00329     {
00330     TRANSIENCY_CHECK( c );
00331     Group* ret = NULL;
00332     for( ClientList::ConstIterator it = clients.begin();
00333          it != clients.end();
00334          ++it )
00335         {
00336         if( *it == c )
00337             continue;
00338         if( (*it)->wmClientLeader() == c->wmClientLeader())
00339             {
00340             if( ret == NULL || ret == (*it)->group())
00341                 ret = (*it)->group();
00342             else
00343                 {
00344                 // There are already two groups with the same client leader.
00345                 // This most probably means the app uses group transients without
00346                 // setting group for its windows. Merging the two groups is a bad
00347                 // hack, but there's no really good solution for this case.
00348                 ClientList old_group = (*it)->group()->members();
00349                 // old_group autodeletes when being empty
00350                 for( unsigned int pos = 0;
00351                      pos < old_group.count();
00352                      ++pos )
00353                     {
00354                     Client* tmp = old_group[ pos ];
00355                     if( tmp != c )
00356                         tmp->changeClientLeaderGroup( ret );
00357                     }
00358                 }
00359             }
00360         }
00361     return ret;
00362     }
00363 
00364 void Workspace::updateMinimizedOfTransients( Client* c )
00365     {
00366     // if mainwindow is minimized or shaded, minimize transients too
00367     if ( c->isMinimized() || c->isShade() )
00368         {
00369         for( ClientList::ConstIterator it = c->transients().begin();
00370              it != c->transients().end();
00371              ++it )
00372             {
00373             if( !(*it)->isMinimized()
00374                  && !(*it)->isTopMenu() ) // topmenus are not minimized, they're hidden
00375                 {
00376                 (*it)->minimize( true ); // avoid animation
00377                 updateMinimizedOfTransients( (*it) );
00378                 }
00379             }
00380         }
00381     else
00382         { // else unmiminize the transients
00383         for( ClientList::ConstIterator it = c->transients().begin();
00384              it != c->transients().end();
00385              ++it )
00386             {
00387             if( (*it)->isMinimized()
00388                 && !(*it)->isTopMenu())
00389                 {
00390                 (*it)->unminimize( true ); // avoid animation
00391                 updateMinimizedOfTransients( (*it) );
00392                 }
00393             }
00394         }
00395     }
00396 
00397 
00401 void Workspace::updateOnAllDesktopsOfTransients( Client* c )
00402     {
00403     for( ClientList::ConstIterator it = c->transients().begin();
00404          it != c->transients().end();
00405          ++it)
00406         {
00407         if( (*it)->isOnAllDesktops() != c->isOnAllDesktops())
00408             (*it)->setOnAllDesktops( c->isOnAllDesktops());
00409         }
00410     }
00411 
00412 // A new window has been mapped. Check if it's not a mainwindow for some already existing transient window.
00413 void Workspace::checkTransients( Window w )
00414     {
00415     TRANSIENCY_CHECK( NULL );
00416     for( ClientList::ConstIterator it = clients.begin();
00417          it != clients.end();
00418          ++it )
00419         (*it)->checkTransient( w );
00420     }
00421 
00422 
00423 
00424 //****************************************
00425 // Client
00426 //****************************************
00427 
00428 // hacks for broken apps here
00429 // all resource classes are forced to be lowercase
00430 bool Client::resourceMatch( const Client* c1, const Client* c2 )
00431     {
00432     // xv has "xv" as resource name, and different strings starting with "XV" as resource class
00433     if( qstrncmp( c1->resourceClass(), "xv", 2 ) == 0 && c1->resourceName() == "xv" )
00434          return qstrncmp( c2->resourceClass(), "xv", 2 ) == 0 && c2->resourceName() == "xv";
00435     // Mozilla has "Mozilla" as resource name, and different strings as resource class
00436     if( c1->resourceName() == "mozilla" )
00437         return c2->resourceName() == "mozilla";
00438     return c1->resourceClass() == c2->resourceClass();
00439     }
00440 
00441 bool Client::belongToSameApplication( const Client* c1, const Client* c2, bool active_hack )
00442     {
00443     bool same_app = false;
00444 
00445     // tests that definitely mean they belong together
00446     if( c1 == c2 )
00447         same_app = true;
00448     else if( c1->isTransient() && c2->hasTransient( c1, true ))
00449         same_app = true; // c1 has c2 as mainwindow
00450     else if( c2->isTransient() && c1->hasTransient( c2, true ))
00451         same_app = true; // c2 has c1 as mainwindow
00452     else if( c1->group() == c2->group())
00453         same_app = true; // same group
00454     else if( c1->wmClientLeader() == c2->wmClientLeader()
00455         && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
00456         && c2->wmClientLeader() != c2->window()) // don't use in this test then
00457         same_app = true; // same client leader
00458 
00459     // tests that mean they most probably don't belong together
00460     else if( c1->pid() != c2->pid()
00461         || c1->wmClientMachine( false ) != c2->wmClientMachine( false ))
00462         ; // different processes
00463     else if( c1->wmClientLeader() != c2->wmClientLeader()
00464         && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
00465         && c2->wmClientLeader() != c2->window()) // don't use in this test then
00466         ; // different client leader
00467     else if( !resourceMatch( c1, c2 ))
00468         ; // different apps
00469     else if( !sameAppWindowRoleMatch( c1, c2, active_hack ))
00470         ; // "different" apps
00471     else if( c1->pid() == 0 || c2->pid() == 0 )
00472         ; // old apps that don't have _NET_WM_PID, consider them different
00473           // if they weren't found to match above
00474     else
00475         same_app = true; // looks like it's the same app
00476 
00477     return same_app;
00478     }
00479 
00480 // Non-transient windows with window role containing '#' are always
00481 // considered belonging to different applications (unless
00482 // the window role is exactly the same). KMainWindow sets
00483 // window role this way by default, and different KMainWindow
00484 // usually "are" different application from user's point of view.
00485 // This help with no-focus-stealing for e.g. konqy reusing.
00486 // On the other hand, if one of the windows is active, they are
00487 // considered belonging to the same application. This is for
00488 // the cases when opening new mainwindow directly from the application,
00489 // e.g. 'Open New Window' in konqy ( active_hack == true ).
00490 bool Client::sameAppWindowRoleMatch( const Client* c1, const Client* c2, bool active_hack )
00491     {
00492     if( c1->isTransient())
00493         {
00494         while( c1->transientFor() != NULL )
00495             c1 = c1->transientFor();
00496         if( c1->groupTransient())
00497             return c1->group() == c2->group();
00498 #if 0
00499                 // if a group transient is in its own group, it didn't possibly have a group,
00500                 // and therefore should be considered belonging to the same app like
00501                 // all other windows from the same app
00502                 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
00503 #endif
00504         }
00505     if( c2->isTransient())
00506         {
00507         while( c2->transientFor() != NULL )
00508             c2 = c2->transientFor();
00509         if( c2->groupTransient())
00510             return c1->group() == c2->group();
00511 #if 0
00512                 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
00513 #endif
00514         }
00515     int pos1 = c1->windowRole().find( '#' );
00516     int pos2 = c2->windowRole().find( '#' );
00517     if(( pos1 >= 0 && pos2 >= 0 )
00518         ||
00519     // hacks here
00520         // Mozilla has resourceName() and resourceClass() swapped
00521         c1->resourceName() == "mozilla" && c2->resourceName() == "mozilla" )
00522         {
00523         if( !active_hack )   // without the active hack for focus stealing prevention,
00524             return c1 == c2; // different mainwindows are always different apps
00525         if( !c1->isActive() && !c2->isActive())
00526             return c1 == c2;
00527         else
00528             return true;
00529         }
00530     return true;
00531     }
00532 
00533 /*
00534 
00535  Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3
00536 
00537  WM_TRANSIENT_FOR is basically means "this is my mainwindow".
00538  For NET::Unknown windows, transient windows are considered to be NET::Dialog
00539  windows, for compatibility with non-NETWM clients. KWin may adjust the value
00540  of this property in some cases (window pointing to itself or creating a loop,
00541  keeping NET::Splash windows above other windows from the same app, etc.).
00542 
00543  Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after
00544  possibly being adjusted by KWin. Client::transient_for points to the Client
00545  this Client is transient for, or is NULL. If Client::transient_for_id is
00546  poiting to the root window, the window is considered to be transient
00547  for the whole window group, as suggested in NETWM 7.3.
00548 
00549  In the case of group transient window, Client::transient_for is NULL,
00550  and Client::groupTransient() returns true. Such window is treated as
00551  if it were transient for every window in its window group that has been
00552  mapped _before_ it (or, to be exact, was added to the same group before it).
00553  Otherwise two group transients can create loops, which can lead very very
00554  nasty things (bug #67914 and all its dupes).
00555 
00556  Client::original_transient_for_id is the value of the property, which
00557  may be different if Client::transient_for_id if e.g. forcing NET::Splash
00558  to be kept on top of its window group, or when the mainwindow is not mapped
00559  yet, in which case the window is temporarily made group transient,
00560  and when the mainwindow is mapped, transiency is re-evaluated.
00561 
00562  This can get a bit complicated with with e.g. two Konqueror windows created
00563  by the same process. They should ideally appear like two independent applications
00564  to the user. This should be accomplished by all windows in the same process
00565  having the same window group (needs to be changed in Qt at the moment), and
00566  using non-group transients poiting to their relevant mainwindow for toolwindows
00567  etc. KWin should handle both group and non-group transient dialogs well.
00568 
00569  In other words:
00570  - non-transient windows     : isTransient() == false
00571  - normal transients         : transientFor() != NULL
00572  - group transients          : groupTransient() == true
00573 
00574  - list of mainwindows       : mainClients()  (call once and loop over the result)
00575  - list of transients        : transients()
00576  - every window in the group : group()->members()
00577 */
00578 
00579 void Client::readTransient()
00580     {
00581     TRANSIENCY_CHECK( this );
00582     Window new_transient_for_id;
00583     if( XGetTransientForHint( qt_xdisplay(), window(), &new_transient_for_id ))
00584         {
00585         original_transient_for_id = new_transient_for_id;
00586         new_transient_for_id = verifyTransientFor( new_transient_for_id, true );
00587         }
00588     else
00589         {
00590         original_transient_for_id = None;
00591         new_transient_for_id = verifyTransientFor( None, false );
00592         }
00593     setTransient( new_transient_for_id );
00594     }
00595 
00596 void Client::setTransient( Window new_transient_for_id )
00597     {
00598     TRANSIENCY_CHECK( this );
00599     if( new_transient_for_id != transient_for_id )
00600         {
00601         removeFromMainClients();
00602         transient_for = NULL;
00603         transient_for_id = new_transient_for_id;
00604         if( transient_for_id != None && !groupTransient())
00605             {
00606             transient_for = workspace()->findClient( WindowMatchPredicate( transient_for_id ));
00607             assert( transient_for != NULL ); // verifyTransient() had to check this
00608             transient_for->addTransient( this );
00609             } // checkGroup() will check 'check_active_modal'
00610         checkGroup( NULL, true ); // force, because transiency has changed
00611         if( isTopMenu())
00612             workspace()->updateCurrentTopMenu();
00613         workspace()->updateClientLayer( this );
00614         }
00615     }
00616 
00617 void Client::removeFromMainClients()
00618     {
00619     TRANSIENCY_CHECK( this );
00620     if( transientFor() != NULL )
00621         transientFor()->removeTransient( this );
00622     if( groupTransient())
00623         {
00624         for( ClientList::ConstIterator it = group()->members().begin();
00625              it != group()->members().end();
00626              ++it )
00627             (*it)->removeTransient( this );
00628         }
00629     }
00630 
00631 // *sigh* this transiency handling is madness :(
00632 // This one is called when destroying/releasing a window.
00633 // It makes sure this client is removed from all grouping
00634 // related lists.
00635 void Client::cleanGrouping()
00636     {
00637     TRANSIENCY_CHECK( this );
00638 //    kdDebug() << "CLEANGROUPING:" << this << endl;
00639 //    for( ClientList::ConstIterator it = group()->members().begin();
00640 //         it != group()->members().end();
00641 //         ++it )
00642 //        kdDebug() << "CL:" << *it << endl;
00643 //    ClientList mains;
00644 //    mains = mainClients();
00645 //    for( ClientList::ConstIterator it = mains.begin();
00646 //         it != mains.end();
00647 //         ++it )
00648 //        kdDebug() << "MN:" << *it << endl;
00649     removeFromMainClients();
00650 //    kdDebug() << "CLEANGROUPING2:" << this << endl;
00651 //    for( ClientList::ConstIterator it = group()->members().begin();
00652 //         it != group()->members().end();
00653 //         ++it )
00654 //        kdDebug() << "CL2:" << *it << endl;
00655 //    mains = mainClients();
00656 //    for( ClientList::ConstIterator it = mains.begin();
00657 //         it != mains.end();
00658 //         ++it )
00659 //        kdDebug() << "MN2:" << *it << endl;
00660     for( ClientList::ConstIterator it = transients_list.begin();
00661          it != transients_list.end();
00662          )
00663         {
00664         if( (*it)->transientFor() == this )
00665             {
00666             ClientList::ConstIterator it2 = it++;
00667             removeTransient( *it2 );
00668             }
00669         else
00670             ++it;
00671         }
00672 //    kdDebug() << "CLEANGROUPING3:" << this << endl;
00673 //    for( ClientList::ConstIterator it = group()->members().begin();
00674 //         it != group()->members().end();
00675 //         ++it )
00676 //        kdDebug() << "CL3:" << *it << endl;
00677 //    mains = mainClients();
00678 //    for( ClientList::ConstIterator it = mains.begin();
00679 //         it != mains.end();
00680 //         ++it )
00681 //        kdDebug() << "MN3:" << *it << endl;
00682     // HACK
00683     // removeFromMainClients() did remove 'this' from transient
00684     // lists of all group members, but then made windows that
00685     // were transient for 'this' group transient, which again
00686     // added 'this' to those transient lists :(
00687     ClientList group_members = group()->members();
00688     group()->removeMember( this );
00689     in_group = NULL;
00690     for( ClientList::ConstIterator it = group_members.begin();
00691          it != group_members.end();
00692          ++it )
00693         (*it)->removeTransient( this );
00694 //    kdDebug() << "CLEANGROUPING4:" << this << endl;
00695 //    for( ClientList::ConstIterator it = group_members.begin();
00696 //         it != group_members.end();
00697 //         ++it )
00698 //        kdDebug() << "CL4:" << *it << endl;
00699     }
00700 
00701 // Make sure that no group transient is considered transient
00702 // for a window that is (directly or indirectly) transient for it
00703 // (including another group transients).
00704 // Non-group transients not causing loops are checked in verifyTransientFor().
00705 void Client::checkGroupTransients()
00706     {
00707     TRANSIENCY_CHECK( this );
00708     for( ClientList::ConstIterator it1 = group()->members().begin();
00709          it1 != group()->members().end();
00710          ++it1 )
00711         {
00712         if( !(*it1)->groupTransient()) // check all group transients in the group
00713             continue;                  // TODO optimize to check only the changed ones?
00714         for( ClientList::ConstIterator it2 = group()->members().begin();
00715              it2 != group()->members().end();
00716              ++it2 ) // group transients can be transient only for others in the group,
00717             {        // so don't make them transient for the ones that are transient for it
00718             if( *it1 == *it2 )
00719                 continue;
00720             for( Client* cl = (*it2)->transientFor();
00721                  cl != NULL;
00722                  cl = cl->transientFor())
00723                 {
00724                 if( cl == *it1 )
00725                     { // don't use removeTransient(), that would modify *it2 too
00726                     (*it2)->transients_list.remove( *it1 );
00727                     continue;
00728                     }
00729                 }
00730             // if *it1 and *it2 are both group transients, and are transient for each other,
00731             // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later,
00732             // and should be therefore on top of *it1
00733             // TODO This could possibly be optimized, it also requires hasTransient() to check for loops.
00734             if( (*it2)->groupTransient() && (*it1)->hasTransient( *it2, true ) && (*it2)->hasTransient( *it1, true ))
00735                 (*it2)->transients_list.remove( *it1 );
00736             // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3
00737             // is added, make it transient only for W2, not for W1, because it's already indirectly
00738             // transient for it - the indirect transiency actually shouldn't break anything,
00739             // but it can lead to exponentially expensive operations (#95231)
00740             // TODO this is pretty slow as well
00741             for( ClientList::ConstIterator it3 = group()->members().begin();
00742                  it3 != group()->members().end();
00743                  ++it3 )
00744                 {
00745                 if( *it1 == *it2 || *it2 == *it3 || *it1 == *it3 )
00746                     continue;
00747                 if( (*it2)->hasTransient( *it1, false ) && (*it3)->hasTransient( *it1, false ))
00748                     {
00749                     if( (*it2)->hasTransient( *it3, true ))
00750                         (*it3)->transients_list.remove( *it1 );
00751                     if( (*it3)->hasTransient( *it2, true ))
00752                         (*it2)->transients_list.remove( *it1 );
00753                     }
00754                 }
00755             }
00756         }
00757     }
00758 
00762 Window Client::verifyTransientFor( Window new_transient_for, bool defined )
00763     {
00764     Window new_property_value = new_transient_for;
00765     // make sure splashscreens are shown above all their app's windows, even though
00766     // they're in Normal layer
00767     if( isSplash() && new_transient_for == None )
00768         new_transient_for = workspace()->rootWin();
00769     if( new_transient_for == None )
00770         if( defined ) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window
00771             new_property_value = new_transient_for = workspace()->rootWin();
00772         else
00773             return None;
00774     if( new_transient_for == window()) // pointing to self
00775         { // also fix the property itself
00776         kdWarning( 1216 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." << endl;
00777         new_property_value = new_transient_for = workspace()->rootWin();
00778         }
00779 //  The transient_for window may be embedded in another application,
00780 //  so kwin cannot see it. Try to find the managed client for the
00781 //  window and fix the transient_for property if possible.
00782     WId before_search = new_transient_for;
00783     while( new_transient_for != None
00784            && new_transient_for != workspace()->rootWin()
00785            && !workspace()->findClient( WindowMatchPredicate( new_transient_for )))
00786         {
00787         Window root_return, parent_return;
00788         Window* wins = NULL;
00789         unsigned int nwins;
00790         int r = XQueryTree(qt_xdisplay(), new_transient_for, &root_return, &parent_return,  &wins, &nwins);
00791         if ( wins )
00792             XFree((void *) wins);
00793         if ( r == 0)
00794             break;
00795         new_transient_for = parent_return;
00796         }
00797     if( Client* new_transient_for_client = workspace()->findClient( WindowMatchPredicate( new_transient_for )))
00798         {
00799         if( new_transient_for != before_search )
00800             {
00801             kdDebug( 1212 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window "
00802                 << before_search << ", child of " << new_transient_for_client << ", adjusting." << endl;
00803             new_property_value = new_transient_for; // also fix the property
00804             }
00805         }
00806     else
00807         new_transient_for = before_search; // nice try
00808 // loop detection
00809 // group transients cannot cause loops, because they're considered transient only for non-transient
00810 // windows in the group
00811     int count = 20;
00812     Window loop_pos = new_transient_for;
00813     while( loop_pos != None && loop_pos != workspace()->rootWin())
00814         {
00815         Client* pos = workspace()->findClient( WindowMatchPredicate( loop_pos ));
00816         if( pos == NULL )
00817             break;
00818         loop_pos = pos->transient_for_id;
00819         if( --count == 0 )
00820             {
00821             kdWarning( 1216 ) << "Client " << this << " caused WM_TRANSIENT_FOR loop." << endl;
00822             new_transient_for = workspace()->rootWin();
00823             }
00824         }
00825     if( new_transient_for != workspace()->rootWin()
00826         && workspace()->findClient( WindowMatchPredicate( new_transient_for )) == NULL )
00827         { // it's transient for a specific window, but that window is not mapped
00828         new_transient_for = workspace()->rootWin();
00829         }
00830     if( new_property_value != original_transient_for_id )
00831         XSetTransientForHint( qt_xdisplay(), window(), new_property_value );
00832     return new_transient_for;
00833     }
00834 
00835 void Client::addTransient( Client* cl )
00836     {
00837     TRANSIENCY_CHECK( this );
00838     assert( !transients_list.contains( cl ));
00839 //    assert( !cl->hasTransient( this, true )); will be fixed in checkGroupTransients()
00840     assert( cl != this );
00841     transients_list.append( cl );
00842     if( workspace()->mostRecentlyActivatedClient() == this && cl->isModal())        
00843         check_active_modal = true;
00844 //    kdDebug() << "ADDTRANS:" << this << ":" << cl << endl;
00845 //    kdDebug() << kdBacktrace() << endl;
00846 //    for( ClientList::ConstIterator it = transients_list.begin();
00847 //         it != transients_list.end();
00848 //         ++it )
00849 //        kdDebug() << "AT:" << (*it) << endl;
00850     }
00851 
00852 void Client::removeTransient( Client* cl )
00853     {
00854     TRANSIENCY_CHECK( this );
00855 //    kdDebug() << "REMOVETRANS:" << this << ":" << cl << endl;
00856 //    kdDebug() << kdBacktrace() << endl;
00857     transients_list.remove( cl );
00858     // cl is transient for this, but this is going away
00859     // make cl group transient
00860     if( cl->transientFor() == this )
00861         {
00862         cl->transient_for_id = None;
00863         cl->transient_for = NULL; // SELI
00864 // SELI       cl->setTransient( workspace()->rootWin());
00865         cl->setTransient( None );
00866         }
00867     }
00868 
00869 // A new window has been mapped. Check if it's not a mainwindow for this already existing window.
00870 void Client::checkTransient( Window w )
00871     {
00872     TRANSIENCY_CHECK( this );
00873     if( original_transient_for_id != w )
00874         return;
00875     w = verifyTransientFor( w, true );
00876     setTransient( w );
00877     }
00878 
00879 // returns true if cl is the transient_for window for this client,
00880 // or recursively the transient_for window
00881 bool Client::hasTransient( const Client* cl, bool indirect ) const
00882     {
00883     // checkGroupTransients() uses this to break loops, so hasTransient() must detect them
00884     ConstClientList set;
00885     return hasTransientInternal( cl, indirect, set );
00886     }
00887 
00888 bool Client::hasTransientInternal( const Client* cl, bool indirect, ConstClientList& set ) const
00889     {
00890     if( cl->transientFor() != NULL )
00891         {
00892         if( cl->transientFor() == this )
00893             return true;
00894         if( !indirect )
00895             return false;
00896         if( set.contains( cl ))
00897             return false;
00898         set.append( cl );
00899         return hasTransientInternal( cl->transientFor(), indirect, set );
00900         }
00901     if( !cl->isTransient())
00902         return false;
00903     if( group() != cl->group())
00904         return false;
00905     // cl is group transient, search from top
00906     if( transients().contains( const_cast< Client* >( cl )))
00907         return true;
00908     if( !indirect )
00909         return false;
00910     if( set.contains( this ))
00911         return false;
00912     set.append( this );
00913     for( ClientList::ConstIterator it = transients().begin();
00914          it != transients().end();
00915          ++it )
00916         if( (*it)->hasTransientInternal( cl, indirect, set ))
00917             return true;
00918     return false;
00919     }
00920 
00921 ClientList Client::mainClients() const
00922     {
00923     if( !isTransient())
00924         return ClientList();
00925     if( transientFor() != NULL )
00926         return ClientList() << const_cast< Client* >( transientFor());
00927     ClientList result;
00928     for( ClientList::ConstIterator it = group()->members().begin();
00929          it != group()->members().end();
00930          ++it )
00931         if((*it)->hasTransient( this, false ))
00932             result.append( *it );
00933     return result;
00934     }
00935 
00936 Client* Client::findModal()
00937     {
00938     for( ClientList::ConstIterator it = transients().begin();
00939          it != transients().end();
00940          ++it )
00941         if( Client* ret = (*it)->findModal())
00942             return ret;
00943     if( isModal())
00944         return this;
00945     return NULL;
00946     }
00947 
00948 // Client::window_group only holds the contents of the hint,
00949 // but it should be used only to find the group, not for anything else
00950 // Argument is only when some specific group needs to be set.
00951 void Client::checkGroup( Group* set_group, bool force )
00952     {
00953     TRANSIENCY_CHECK( this );
00954     Group* old_group = in_group;
00955     if( old_group != NULL )
00956         old_group->ref(); // turn off automatic deleting
00957     if( set_group != NULL )
00958         {
00959         if( set_group != in_group )
00960             {
00961             if( in_group != NULL )
00962                 in_group->removeMember( this );
00963             in_group = set_group;
00964             in_group->addMember( this );
00965             }
00966         }
00967     else if( window_group != None )
00968         {
00969         Group* new_group = workspace()->findGroup( window_group );
00970         if( transientFor() != NULL && transientFor()->group() != new_group )
00971             { // move the window to the right group (e.g. a dialog provided
00972               // by different app, but transient for this one, so make it part of that group)
00973             new_group = transientFor()->group();
00974             }
00975         if( new_group == NULL ) // doesn't exist yet
00976             new_group = new Group( window_group, workspace());
00977         if( new_group != in_group )
00978             {
00979             if( in_group != NULL )
00980                 in_group->removeMember( this );
00981             in_group = new_group;
00982             in_group->addMember( this );
00983             }
00984         }
00985     else
00986         {
00987         if( transientFor() != NULL )
00988             { // doesn't have window group set, but is transient for something
00989           // so make it part of that group
00990             Group* new_group = transientFor()->group();
00991             if( new_group != in_group )
00992                 {
00993                 if( in_group != NULL )
00994                     in_group->removeMember( this );
00995                 in_group = transientFor()->group();
00996                 in_group->addMember( this );
00997                 }
00998             }
00999         else if( groupTransient())
01000             { // group transient which actually doesn't have a group :(
01001               // try creating group with other windows with the same client leader
01002             Group* new_group = workspace()->findClientLeaderGroup( this );
01003             if( new_group == NULL )
01004                 new_group = new Group( None, workspace());
01005             if( new_group != in_group )
01006                 {
01007                 if( in_group != NULL )
01008                     in_group->removeMember( this );
01009                 in_group = new_group;
01010                 in_group->addMember( this );
01011                 }
01012             }
01013         else // Not transient without a group, put it in its client leader group.
01014             { // This might be stupid if grouping was used for e.g. taskbar grouping
01015               // or minimizing together the whole group, but as long as its used
01016               // only for dialogs it's better to keep windows from one app in one group.
01017             Group* new_group = workspace()->findClientLeaderGroup( this );
01018             if( in_group != NULL && in_group != new_group )
01019                 {
01020                 in_group->removeMember( this );            
01021                 in_group = NULL;
01022                 }
01023             if( new_group == NULL )
01024                 new_group = new Group( None, workspace() );
01025             if( in_group != new_group )
01026                 {
01027                 in_group = new_group;
01028                 in_group->addMember( this );
01029                 }
01030             }
01031         }
01032     if( in_group != old_group || force )
01033         {
01034         for( ClientList::Iterator it = transients_list.begin();
01035              it != transients_list.end();
01036              )
01037             { // group transients in the old group are no longer transient for it
01038             if( (*it)->groupTransient() && (*it)->group() != group())
01039                 it = transients_list.remove( it );
01040             else
01041                 ++it;
01042             }
01043         if( groupTransient())
01044             {
01045             // no longer transient for ones in the old group
01046             if( old_group != NULL )
01047                 {
01048                 for( ClientList::ConstIterator it = old_group->members().begin();
01049                      it != old_group->members().end();
01050                      ++it )
01051                     (*it)->removeTransient( this );
01052                 }
01053             // and make transient for all in the new group
01054             for( ClientList::ConstIterator it = group()->members().begin();
01055                  it != group()->members().end();
01056                  ++it )
01057                 {
01058                 if( *it == this )
01059                     break; // this means the window is only transient for windows mapped before it
01060                 (*it)->addTransient( this );
01061                 }
01062             }
01063         // group transient splashscreens should be transient even for windows
01064         // in group mapped later
01065         for( ClientList::ConstIterator it = group()->members().begin();
01066              it != group()->members().end();
01067              ++it )
01068             {
01069             if( !(*it)->isSplash())
01070                 continue;
01071             if( !(*it)->groupTransient())
01072                 continue;
01073             if( *it == this || hasTransient( *it, true )) // TODO indirect?
01074                 continue;
01075             addTransient( *it );
01076         }
01077         }
01078     if( old_group != NULL )
01079         old_group->deref(); // can be now deleted if empty
01080     checkGroupTransients();
01081     checkActiveModal();
01082     workspace()->updateClientLayer( this );
01083     }
01084 
01085 // used by Workspace::findClientLeaderGroup()
01086 void Client::changeClientLeaderGroup( Group* gr )
01087     {
01088     // transientFor() != NULL are in the group of their mainwindow, so keep them there
01089     if( transientFor() != NULL )
01090         return;
01091     // also don't change the group for window which have group set
01092     if( window_group )
01093         return;
01094     checkGroup( gr ); // change group
01095     }
01096 
01097 bool Client::check_active_modal = false;
01098 
01099 void Client::checkActiveModal()
01100     {
01101     // if the active window got new modal transient, activate it.
01102     // cannot be done in AddTransient(), because there may temporarily
01103     // exist loops, breaking findModal
01104     Client* check_modal = workspace()->mostRecentlyActivatedClient();
01105     if( check_modal != NULL && check_modal->check_active_modal )
01106         {
01107         Client* new_modal = check_modal->findModal();
01108         if( new_modal != NULL && new_modal != check_modal )
01109             {
01110             if( !new_modal->isManaged())
01111                 return; // postpone check until end of manage()
01112             workspace()->activateClient( new_modal );
01113             }
01114         check_modal->check_active_modal = false;
01115         }
01116     }
01117 
01118 } // namespace
KDE Home | KDE Accessibility Home | Description of Access Keys