kwin Library API Documentation

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 // Group
00040 //********************************************
00041 
00042 Group::Group( Window leader_P, Workspace* workspace_P )
00043     :   leader_client( NULL ),
00044         leader_wid( leader_P ),
00045         _workspace( workspace_P ),
00046         leader_info( NULL ),
00047         user_time( -1U )
00048     {
00049     if( leader_P != None )
00050         {
00051         leader_client = workspace_P->findClient( WindowMatchPredicate( leader_P ));
00052         unsigned long properties[ 2 ] = { 0, NET::WM2StartupId };
00053         leader_info = new NETWinInfo( qt_xdisplay(), leader_P, workspace()->rootWin(),
00054             properties, 2 );
00055         }
00056     workspace()->addGroup( this, Allowed );
00057     }
00058 
00059 Group::~Group()
00060     {
00061     delete leader_info;
00062     }
00063 
00064 QPixmap Group::icon() const
00065     {
00066     if( leader_client != NULL )
00067         return leader_client->icon();
00068     else if( leader_wid != None )
00069         {
00070         QPixmap ic;
00071         Client::readIcons( leader_wid, &ic, NULL );
00072         return ic;
00073         }
00074     return QPixmap();
00075     }
00076 
00077 QPixmap Group::miniIcon() const
00078     {
00079     if( leader_client != NULL )
00080         return leader_client->miniIcon();
00081     else if( leader_wid != None )
00082         {
00083         QPixmap ic;
00084         Client::readIcons( leader_wid, NULL, &ic );
00085         return ic;
00086         }
00087     return QPixmap();
00088     }
00089 
00090 void Group::addMember( Client* member_P )
00091     {
00092     _members.append( member_P );
00093 //    kdDebug() << "GROUPADD:" << this << ":" << member_P << endl;
00094 //    kdDebug() << kdBacktrace() << endl;
00095     }
00096 
00097 void Group::removeMember( Client* member_P )
00098     {
00099 //    kdDebug() << "GROUPREMOVE:" << this << ":" << member_P << endl;
00100 //    kdDebug() << kdBacktrace() << endl;
00101     Q_ASSERT( _members.contains( member_P ));
00102     _members.remove( member_P );
00103     if( _members.isEmpty())
00104         {
00105         workspace()->removeGroup( this, Allowed );
00106         delete this;
00107         }
00108     }
00109 
00110 void Group::gotLeader( Client* leader_P )
00111     {
00112     assert( leader_P->window() == leader_wid );
00113     leader_client = leader_P;
00114     }
00115 
00116 void Group::lostLeader()
00117     {
00118     assert( !_members.contains( leader_client ));
00119     leader_client = NULL;
00120     if( _members.isEmpty())
00121         {
00122         workspace()->removeGroup( this, Allowed );
00123         delete this;
00124         }
00125     }
00126 
00127 void Group::getIcons()
00128     {
00129     // TODO - also needs adding the flag to NETWinInfo
00130     }
00131 
00132 //***************************************
00133 // Workspace
00134 //***************************************
00135 
00136 Group* Workspace::findGroup( Window leader ) const
00137     {
00138     assert( leader != None );
00139     for( GroupList::ConstIterator it = groups.begin();
00140          it != groups.end();
00141          ++it )
00142         if( (*it)->leader() == leader )
00143             return *it;
00144     return NULL;
00145     }
00146 
00147 // Client is group transient, but has no group set. Try to find
00148 // group with windows with the same client leader.
00149 Group* Workspace::findClientLeaderGroup( const Client* c ) const
00150     {
00151     Group* ret = NULL;
00152     for( ClientList::ConstIterator it = clients.begin();
00153          it != clients.end();
00154          ++it )
00155         {
00156         if( *it == c )
00157             continue;
00158         if( (*it)->wmClientLeader() == c->wmClientLeader())
00159             {
00160             if( ret == NULL || ret == (*it)->group())
00161                 ret = (*it)->group();
00162             else
00163                 {
00164                 // There are already two groups with the same client leader.
00165                 // This most probably means the app uses group transients without
00166                 // setting group for its windows. Merging the two groups is a bad
00167                 // hack, but there's no really good solution for this case.
00168                 Group* old_group = (*it)->group();
00169                 // old_group autodeletes when being empty
00170                 for( int cnt = old_group->members().count();
00171                      cnt > 0;
00172                      --cnt )
00173                     {
00174                     Client* tmp = old_group->members().first();
00175                     tmp->checkGroup( ret ); // change group
00176                     }
00177                 }
00178             }
00179         }
00180     return ret;
00181     }
00182 
00183 void Workspace::updateMinimizedOfTransients( Client* c )
00184     {
00185     // if mainwindow is minimized or shaded, minimize transients too
00186     if ( c->isMinimized() || c->isShade() )
00187         {
00188         for( ClientList::ConstIterator it = c->transients().begin();
00189              it != c->transients().end();
00190              ++it )
00191             {
00192             if( !(*it)->isMinimized()
00193                  && !(*it)->isTopMenu() ) // topmenus are not minimized, they're hidden
00194                 {
00195                 (*it)->minimize( true ); // avoid animation
00196                 updateMinimizedOfTransients( (*it) );
00197                 }
00198             }
00199         }
00200     else
00201         { // else unmiminize the transients
00202         for( ClientList::ConstIterator it = c->transients().begin();
00203              it != c->transients().end();
00204              ++it )
00205             {
00206             if( (*it)->isMinimized()
00207                 && !(*it)->isTopMenu())
00208                 {
00209                 (*it)->unminimize( true ); // avoid animation
00210                 updateMinimizedOfTransients( (*it) );
00211                 }
00212             }
00213         }
00214     }
00215 
00216 
00220 void Workspace::updateOnAllDesktopsOfTransients( Client* c )
00221     {
00222     for( ClientList::ConstIterator it = c->transients().begin();
00223          it != c->transients().end();
00224          ++it)
00225         {
00226         if( (*it)->isOnAllDesktops() != c->isOnAllDesktops())
00227             (*it)->setOnAllDesktops( c->isOnAllDesktops());
00228         }
00229     }
00230 
00231 // A new window has been mapped. Check if it's not a mainwindow for some already existing transient window.
00232 void Workspace::checkTransients( Window w )
00233     {
00234     for( ClientList::ConstIterator it = clients.begin();
00235          it != clients.end();
00236          ++it )
00237         (*it)->checkTransient( w );
00238     }
00239 
00240 
00241 
00242 //****************************************
00243 // Client
00244 //****************************************
00245 
00246 // hacks for broken apps here
00247 // all resource classes are forced to be lowercase
00248 bool Client::resourceMatch( const Client* c1, const Client* c2 )
00249     {
00250     // xv has "xv" as resource name, and different strings starting with "XV" as resource class
00251     if( qstrncmp( c1->resourceClass(), "xv", 2 ) == 0 && c1->resourceName() == "xv" )
00252          return qstrncmp( c2->resourceClass(), "xv", 2 ) == 0 && c2->resourceName() == "xv";
00253     // Mozilla has "Mozilla" as resource name, and different strings as resource class
00254     if( c1->resourceName() == "mozilla" )
00255         return c2->resourceName() == "mozilla";
00256     return c1->resourceClass() == c2->resourceClass();
00257     }
00258 
00259 bool Client::belongToSameApplication( const Client* c1, const Client* c2, bool active_hack )
00260     {
00261     bool same_app = false;
00262     if( c1 == c2 )
00263         same_app = true;
00264     else if( c1->isTransient() && c2->hasTransient( c1, true ))
00265         same_app = true; // c1 has c2 as mainwindow
00266     else if( c2->isTransient() && c1->hasTransient( c2, true ))
00267         same_app = true; // c2 has c1 as mainwindow
00268     else if( c1->pid() != c2->pid()
00269         || c1->wmClientMachine( false ) != c2->wmClientMachine( false ))
00270         ; // different processes
00271     else if( c1->wmClientLeader() != c2->wmClientLeader()
00272         && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
00273         && c2->wmClientLeader() != c2->window()) // don't use in this test then
00274         ; // different client leader
00275     else if( !resourceMatch( c1, c2 ))
00276         ; // different apps
00277     else if( !sameAppWindowRoleMatch( c1, c2, active_hack ))
00278         ; // "different" apps
00279     else if( c1->wmClientLeader() == c2->wmClientLeader()
00280         && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
00281         && c2->wmClientLeader() != c2->window()) // don't use in this test then
00282         same_app = true; // same client leader
00283     else if( c1->group() == c2->group())
00284         same_app = true; // same group
00285     else if( c1->pid() == 0 || c2->pid() == 0 )
00286         ; // old apps that don't have _NET_WM_PID, consider them different
00287           // if they weren't found to match above
00288     else
00289         same_app = true; // looks like it's the same app
00290     return same_app;
00291     }
00292 
00293 // Non-transient windows with window role containing '#' are always
00294 // considered belonging to different applications (unless
00295 // the window role is exactly the same). KMainWindow sets
00296 // window role this way by default, and different KMainWindow
00297 // usually "are" different application from user's point of view.
00298 // This help with no-focus-stealing for e.g. konqy reusing.
00299 // On the other hand, if one of the windows is active, they are
00300 // considered belonging to the same application. This is for
00301 // the cases when opening new mainwindow directly from the application,
00302 // e.g. 'Open New Window' in konqy ( active_hack == true ).
00303 bool Client::sameAppWindowRoleMatch( const Client* c1, const Client* c2, bool active_hack )
00304     {
00305     if( c1->isTransient())
00306         {
00307         while( c1->transientFor() != NULL )
00308             c1 = c1->transientFor();
00309         if( c1->groupTransient())
00310             return c1->group() == c2->group();
00311 #if 0
00312                 // if a group transient is in its own group, it didn't possibly have a group,
00313                 // and therefore should be considered belonging to the same app like
00314                 // all other windows from the same app
00315                 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
00316 #endif
00317         }
00318     if( c2->isTransient())
00319         {
00320         while( c2->transientFor() != NULL )
00321             c2 = c2->transientFor();
00322         if( c2->groupTransient())
00323             return c1->group() == c2->group();
00324 #if 0
00325                 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
00326 #endif
00327         }
00328     int pos1 = c1->windowRole().find( '#' );
00329     int pos2 = c2->windowRole().find( '#' );
00330     if(( pos1 >= 0 && pos2 >= 0 )
00331         ||
00332     // hacks here
00333         // Mozilla has resourceName() and resourceClass() swapped
00334         c1->resourceName() == "mozilla" && c2->resourceName() == "mozilla" )
00335         {
00336         if( !active_hack )   // without the active hack for focus stealing prevention,
00337             return c1 == c2; // different mainwindows are always different apps
00338         if( !c1->isActive() && !c2->isActive())
00339             return c1 == c2;
00340         else
00341             return true;
00342         }
00343     return true;
00344     }
00345 
00346 /*
00347 
00348  Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3
00349 
00350  WM_TRANSIENT_FOR is basically means "this is my mainwindow".
00351  For NET::Unknown windows, transient windows are considered to be NET::Dialog
00352  windows, for compatibility with non-NETWM clients. KWin may adjust the value
00353  of this property in some cases (window pointing to itself or creating a loop,
00354  keeping NET::Splash windows above other windows from the same app, etc.).
00355 
00356  Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after
00357  possibly being adjusted by KWin. Client::transient_for points to the Client
00358  this Client is transient for, or is NULL. If Client::transient_for_id is
00359  poiting to the root window, the window is considered to be transient
00360  for the whole window group, as suggested in NETWM 7.3.
00361 
00362  In the case of group transient window, Client::transient_for is NULL,
00363  and Client::groupTransient() returns true. Such window is treated as
00364  if it were transient for every window in its window group that has been
00365  mapped _before_ it (or, to be exact, was added to the same group before it).
00366  Otherwise two group transients can create loops, which can lead very very
00367  nasty things (bug #67914 and all its dupes).
00368 
00369  Client::original_transient_for_id is the value of the property, which
00370  may be different if Client::transient_for_id if e.g. forcing NET::Splash
00371  to be kept on top of its window group, or when the mainwindow is not mapped
00372  yet, in which case the window is temporarily made group transient,
00373  and when the mainwindow is mapped, transiency is re-evaluated.
00374 
00375  This can get a bit complicated with with e.g. two Konqueror windows created
00376  by the same process. They should ideally appear like two independent applications
00377  to the user. This should be accomplished by all windows in the same process
00378  having the same window group (needs to be changed in Qt at the moment), and
00379  using non-group transients poiting to their relevant mainwindow for toolwindows
00380  etc. KWin should handle both group and non-group transient dialogs well.
00381 
00382  In other words:
00383  - non-transient windows     : isTransient() == false
00384  - normal transients         : transientFor() != NULL
00385  - group transients          : groupTransient() == true
00386 
00387  - list of mainwindows       : mainClients()  (call once and loop over the result)
00388  - list of transients        : transients()
00389  - every window in the group : group()->members()
00390 */
00391 
00392 void Client::readTransient()
00393     {
00394     Window new_transient_for_id;
00395     if( XGetTransientForHint( qt_xdisplay(), window(), &new_transient_for_id ))
00396         {
00397         original_transient_for_id = new_transient_for_id;
00398         new_transient_for_id = verifyTransientFor( new_transient_for_id, true );
00399         }
00400     else
00401         {
00402         original_transient_for_id = None;
00403         new_transient_for_id = verifyTransientFor( None, false );
00404         }
00405     setTransient( new_transient_for_id );
00406     }
00407 
00408 void Client::setTransient( Window new_transient_for_id )
00409     {
00410     if( new_transient_for_id != transient_for_id )
00411         {
00412         removeFromMainClients();
00413         transient_for = NULL;
00414         transient_for_id = new_transient_for_id;
00415         if( transient_for_id != None && !groupTransient())
00416             {
00417             transient_for = workspace()->findClient( WindowMatchPredicate( transient_for_id ));
00418             assert( transient_for != NULL ); // verifyTransient() had to check this
00419             transient_for->addTransient( this );
00420             } // checkGroup() will check 'check_active_modal'
00421         checkGroup( NULL, true ); // force, because transiency has changed
00422         if( isTopMenu())
00423             workspace()->updateCurrentTopMenu();
00424         workspace()->updateClientLayer( this );
00425         }
00426     }
00427 
00428 void Client::removeFromMainClients()
00429     {
00430     if( transientFor() != NULL )
00431         transientFor()->removeTransient( this );
00432     if( groupTransient())
00433         {
00434         for( ClientList::ConstIterator it = group()->members().begin();
00435              it != group()->members().end();
00436              ++it )
00437             (*it)->removeTransient( this );
00438         }
00439     }
00440 
00441 // *sigh* this transiency handling is madness :(
00442 // This one is called when destroying/releasing a window.
00443 // It makes sure this client is removed from all grouping
00444 // related lists.
00445 void Client::cleanGrouping()
00446     {
00447 //    kdDebug() << "CLEANGROUPING:" << this << endl;
00448 //    for( ClientList::ConstIterator it = group()->members().begin();
00449 //         it != group()->members().end();
00450 //         ++it )
00451 //        kdDebug() << "CL:" << *it << endl;
00452 //    ClientList mains;
00453 //    mains = mainClients();
00454 //    for( ClientList::ConstIterator it = mains.begin();
00455 //         it != mains.end();
00456 //         ++it )
00457 //        kdDebug() << "MN:" << *it << endl;
00458     removeFromMainClients();
00459 //    kdDebug() << "CLEANGROUPING2:" << this << endl;
00460 //    for( ClientList::ConstIterator it = group()->members().begin();
00461 //         it != group()->members().end();
00462 //         ++it )
00463 //        kdDebug() << "CL2:" << *it << endl;
00464 //    mains = mainClients();
00465 //    for( ClientList::ConstIterator it = mains.begin();
00466 //         it != mains.end();
00467 //         ++it )
00468 //        kdDebug() << "MN2:" << *it << endl;
00469     for( ClientList::ConstIterator it = transients_list.begin();
00470          it != transients_list.end();
00471          )
00472         {
00473         if( (*it)->transientFor() == this )
00474             {
00475             ClientList::ConstIterator it2 = it++;
00476             removeTransient( *it2 );
00477             }
00478         else
00479             ++it;
00480         }
00481 //    kdDebug() << "CLEANGROUPING3:" << this << endl;
00482 //    for( ClientList::ConstIterator it = group()->members().begin();
00483 //         it != group()->members().end();
00484 //         ++it )
00485 //        kdDebug() << "CL3:" << *it << endl;
00486 //    mains = mainClients();
00487 //    for( ClientList::ConstIterator it = mains.begin();
00488 //         it != mains.end();
00489 //         ++it )
00490 //        kdDebug() << "MN3:" << *it << endl;
00491     // HACK
00492     // removeFromMainClients() did remove 'this' from transient
00493     // lists of all group members, but then made windows that
00494     // were transient for 'this' group transient, which again
00495     // added 'this' to those transient lists :(
00496     ClientList group_members = group()->members();
00497     group()->removeMember( this );
00498     in_group = NULL;
00499     for( ClientList::ConstIterator it = group_members.begin();
00500          it != group_members.end();
00501          ++it )
00502         (*it)->removeTransient( this );
00503 //    kdDebug() << "CLEANGROUPING4:" << this << endl;
00504 //    for( ClientList::ConstIterator it = group_members.begin();
00505 //         it != group_members.end();
00506 //         ++it )
00507 //        kdDebug() << "CL4:" << *it << endl;
00508     }
00509 
00510 // Make sure that no group transient is considered transient
00511 // for a window that is (directly or indirectly) transient for it
00512 // (including another group transients).
00513 // Non-group transients not causing loops are checked in verifyTransientFor().
00514 void Client::checkGroupTransients()
00515     {
00516     for( ClientList::ConstIterator it1 = group()->members().begin();
00517          it1 != group()->members().end();
00518          ++it1 )
00519         {
00520         if( !(*it1)->groupTransient()) // check all group transients in the group
00521             continue;                  // TODO optimize to check only the changed ones?
00522         for( ClientList::ConstIterator it2 = group()->members().begin();
00523              it2 != group()->members().end();
00524              ++it2 ) // group transients can be transient only for others in the group,
00525             {        // so don't make them transient for the ones that are transient for it
00526             if( *it1 == *it2 )
00527                 continue;
00528             for( Client* cl = (*it2)->transientFor();
00529                  cl != NULL;
00530                  cl = cl->transientFor())
00531                 {
00532                 if( cl == *it1 )
00533                     { // don't use removeTransient(), that would modify *it2 too
00534                     (*it2)->transients_list.remove( *it1 );
00535                     continue;
00536                     }
00537                 }
00538             // if *it1 and *it2 are both group transients, and are transient for each other,
00539             // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later,
00540             // and should be therefore on top of *it1
00541             // TODO This could possibly be optimized, it also requires hasTransient() to check for loops.
00542             if( (*it2)->groupTransient() && (*it1)->hasTransient( *it2, true ) && (*it2)->hasTransient( *it1, true ))
00543                 (*it2)->transients_list.remove( *it1 );
00544             // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3
00545             // is added, make it transient only for W2, not for W1, because it's already indirectly
00546             // transient for it - the indirect transiency actually shouldn't break anything,
00547             // but it can lead to exponentially expensive operations (#95231)
00548             // TODO this is pretty slow as well
00549             for( ClientList::ConstIterator it3 = group()->members().begin();
00550                  it3 != group()->members().end();
00551                  ++it3 )
00552                 {
00553                 if( *it1 == *it2 || *it2 == *it3 || *it1 == *it3 )
00554                     continue;
00555                 if( (*it2)->hasTransient( *it1, false ) && (*it3)->hasTransient( *it1, false ))
00556                     {
00557                     if( (*it2)->hasTransient( *it3, true ))
00558                         (*it3)->transients_list.remove( *it1 );
00559                     if( (*it3)->hasTransient( *it2, true ))
00560                         (*it2)->transients_list.remove( *it1 );
00561                     }
00562                 }
00563             }
00564         }
00565     }
00566 
00570 Window Client::verifyTransientFor( Window new_transient_for, bool defined )
00571     {
00572     Window new_property_value = new_transient_for;
00573     // make sure splashscreens are shown above all their app's windows, even though
00574     // they're in Normal layer
00575     if( isSplash() && new_transient_for == None )
00576         new_transient_for = workspace()->rootWin();
00577     if( new_transient_for == None )
00578         if( defined ) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window
00579             new_property_value = new_transient_for = workspace()->rootWin();
00580         else
00581             return None;
00582     if( new_transient_for == window()) // pointing to self
00583         { // also fix the property itself
00584         kdWarning( 1216 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." << endl;
00585         new_property_value = new_transient_for = workspace()->rootWin();
00586         }
00587 //  The transient_for window may be embedded in another application,
00588 //  so kwin cannot see it. Try to find the managed client for the
00589 //  window and fix the transient_for property if possible.
00590     WId before_search = new_transient_for;
00591     while( new_transient_for != None
00592            && new_transient_for != workspace()->rootWin()
00593            && !workspace()->findClient( WindowMatchPredicate( new_transient_for )))
00594         {
00595         Window root_return, parent_return;
00596         Window* wins = NULL;
00597         unsigned int nwins;
00598         int r = XQueryTree(qt_xdisplay(), new_transient_for, &root_return, &parent_return,  &wins, &nwins);
00599         if ( wins )
00600             XFree((void *) wins);
00601         if ( r == 0)
00602             break;
00603         new_transient_for = parent_return;
00604         }
00605     if( Client* new_transient_for_client = workspace()->findClient( WindowMatchPredicate( new_transient_for )))
00606         {
00607         if( new_transient_for != before_search )
00608             {
00609             kdDebug( 1212 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window "
00610                 << before_search << ", child of " << new_transient_for_client << ", adjusting." << endl;
00611             new_property_value = new_transient_for; // also fix the property
00612             }
00613         }
00614     else
00615         new_transient_for = before_search; // nice try
00616 // loop detection
00617 // group transients cannot cause loops, because they're considered transient only for non-transient
00618 // windows in the group
00619     int count = 20;
00620     Window loop_pos = new_transient_for;
00621     while( loop_pos != None && loop_pos != workspace()->rootWin())
00622         {
00623         Client* pos = workspace()->findClient( WindowMatchPredicate( loop_pos ));
00624         if( pos == NULL )
00625             break;
00626         loop_pos = pos->transient_for_id;
00627         if( --count == 0 )
00628             {
00629             kdWarning( 1216 ) << "Client " << this << " caused WM_TRANSIENT_FOR loop." << endl;
00630             new_transient_for = workspace()->rootWin();
00631             }
00632         }
00633     if( new_transient_for != workspace()->rootWin()
00634         && workspace()->findClient( WindowMatchPredicate( new_transient_for )) == NULL )
00635         { // it's transient for a specific window, but that window is not mapped
00636         new_transient_for = workspace()->rootWin();
00637         }
00638     if( new_property_value != original_transient_for_id )
00639         XSetTransientForHint( qt_xdisplay(), window(), new_property_value );
00640     return new_transient_for;
00641     }
00642 
00643 void Client::addTransient( Client* cl )
00644     {
00645     assert( !transients_list.contains( cl ));
00646 //    assert( !cl->hasTransient( this, true )); will be fixed in checkGroupTransients()
00647     assert( cl != this );
00648     transients_list.append( cl );
00649     if( workspace()->mostRecentlyActivatedClient() == this && cl->isModal())        
00650         check_active_modal = true;
00651 //    kdDebug() << "ADDTRANS:" << this << ":" << cl << endl;
00652 //    kdDebug() << kdBacktrace() << endl;
00653 //    for( ClientList::ConstIterator it = transients_list.begin();
00654 //         it != transients_list.end();
00655 //         ++it )
00656 //        kdDebug() << "AT:" << (*it) << endl;
00657     }
00658 
00659 void Client::removeTransient( Client* cl )
00660     {
00661 //    kdDebug() << "REMOVETRANS:" << this << ":" << cl << endl;
00662 //    kdDebug() << kdBacktrace() << endl;
00663     transients_list.remove( cl );
00664     // cl is transient for this, but this is going away
00665     // make cl group transient
00666     if( cl->transientFor() == this )
00667         {
00668         cl->transient_for_id = None;
00669         cl->transient_for = NULL; // SELI
00670 // SELI       cl->setTransient( workspace()->rootWin());
00671         cl->setTransient( None );
00672         }
00673     }
00674 
00675 // A new window has been mapped. Check if it's not a mainwindow for this already existing window.
00676 void Client::checkTransient( Window w )
00677     {
00678     if( original_transient_for_id != w )
00679         return;
00680     w = verifyTransientFor( w, true );
00681     setTransient( w );
00682     }
00683 
00684 // returns true if cl is the transient_for window for this client,
00685 // or recursively the transient_for window
00686 bool Client::hasTransient( const Client* cl, bool indirect ) const
00687     {
00688     // checkGroupTransients() uses this to break loops, so hasTransient() must detect them
00689     ConstClientList set;
00690     return hasTransientInternal( cl, indirect, set );
00691     }
00692 
00693 bool Client::hasTransientInternal( const Client* cl, bool indirect, ConstClientList& set ) const
00694     {
00695     if( cl->transientFor() != NULL )
00696         {
00697         if( cl->transientFor() == this )
00698             return true;
00699         if( !indirect )
00700             return false;
00701         if( set.contains( cl ))
00702             return false;
00703         set.append( cl );
00704         return hasTransientInternal( cl->transientFor(), indirect, set );
00705         }
00706     if( !cl->isTransient())
00707         return false;
00708     if( group() != cl->group())
00709         return false;
00710     // cl is group transient, search from top
00711     if( transients().contains( const_cast< Client* >( cl )))
00712         return true;
00713     if( !indirect )
00714         return false;
00715     if( set.contains( this ))
00716         return false;
00717     set.append( this );
00718     for( ClientList::ConstIterator it = transients().begin();
00719          it != transients().end();
00720          ++it )
00721         if( (*it)->hasTransientInternal( cl, indirect, set ))
00722             return true;
00723     return false;
00724     }
00725 
00726 ClientList Client::mainClients() const
00727     {
00728     if( !isTransient())
00729         return ClientList();
00730     if( transientFor() != NULL )
00731         return ClientList() << const_cast< Client* >( transientFor());
00732     ClientList result;
00733     for( ClientList::ConstIterator it = group()->members().begin();
00734          it != group()->members().end();
00735          ++it )
00736         if((*it)->hasTransient( this, false ))
00737             result.append( *it );
00738     return result;
00739     }
00740 
00741 Client* Client::findModal()
00742     {
00743     for( ClientList::ConstIterator it = transients().begin();
00744          it != transients().end();
00745          ++it )
00746         if( Client* ret = (*it)->findModal())
00747             return ret;
00748     if( isModal())
00749         return this;
00750     return NULL;
00751     }
00752 
00753 // Client::window_group only holds the contents of the hint,
00754 // but it should be used only to find the group, not for anything else
00755 // Argument is only when some specific group needs to be set.
00756 void Client::checkGroup( Group* set_group, bool force )
00757     {
00758     Group* old_group = in_group;
00759     if( set_group != NULL )
00760         {
00761         if( set_group != in_group )
00762             {
00763             if( in_group != NULL )
00764                 in_group->removeMember( this );
00765             in_group = set_group;
00766             in_group->addMember( this );
00767             }
00768         }
00769     else if( window_group != None )
00770         {
00771         Group* new_group = workspace()->findGroup( window_group );
00772         if( transientFor() != NULL && transientFor()->group() != new_group )
00773             { // move the window to the right group (e.g. a dialog provided
00774               // by different app, but transient for this one, so make it part of that group)
00775             new_group = transientFor()->group();
00776             }
00777         if( new_group == NULL ) // doesn't exist yet
00778             new_group = new Group( window_group, workspace());
00779         if( new_group != in_group )
00780             {
00781             if( in_group != NULL )
00782                 in_group->removeMember( this );
00783             in_group = new_group;
00784             in_group->addMember( this );
00785             }
00786         }
00787     else
00788         {
00789         if( transientFor() != NULL )
00790             { // doesn't have window group set, but is transient for something
00791           // so make it part of that group
00792             Group* new_group = transientFor()->group();
00793             if( new_group != in_group )
00794                 {
00795                 if( in_group != NULL )
00796                     in_group->removeMember( this );
00797                 in_group = transientFor()->group();
00798                 in_group->addMember( this );
00799                 }
00800             }
00801         else if( groupTransient())
00802             { // group transient which actually doesn't have a group :(
00803               // try creating group with other windows with the same client leader
00804             Group* new_group = workspace()->findClientLeaderGroup( this );
00805             if( new_group == NULL )
00806                 new_group = new Group( None, workspace());
00807             if( new_group != in_group )
00808                 {
00809                 if( in_group != NULL )
00810                     in_group->removeMember( this );
00811                 in_group = new_group;
00812                 in_group->addMember( this );
00813                 }
00814             }
00815         else // not transient without a group, put it in its own group
00816             {
00817             if( in_group != NULL && in_group->leader() != window())
00818                 {
00819                 in_group->removeMember( this );            
00820                 in_group = NULL;
00821                 }
00822             if( in_group == NULL )
00823                 {
00824                 in_group = new Group( None, workspace());
00825                 in_group->addMember( this );
00826                 }
00827             }
00828         }
00829     if( in_group != old_group || force )
00830         {
00831         for( ClientList::Iterator it = transients_list.begin();
00832              it != transients_list.end();
00833              )
00834             { // group transients in the old group are no longer transient for it
00835             if( (*it)->groupTransient() && (*it)->group() != group())
00836                 it = transients_list.remove( it );
00837             else
00838                 ++it;
00839             }
00840         if( groupTransient())
00841             { // and make transient for all in the group
00842             for( ClientList::ConstIterator it = group()->members().begin();
00843                  it != group()->members().end();
00844                  ++it )
00845                 {
00846                 if( *it == this )
00847                     break; // this means the window is only transient for windows mapped before it
00848                 (*it)->addTransient( this );
00849                 }
00850             }
00851 #if 0 // TODO
00852         if( groupTransient())
00853             {
00854             if( workspace()->findGroup( old_group )) // if it still exists
00855                 { // it's no longer transient for windows in the old group
00856                 for( ClientList::ConstIterator it = old_group->members().begin();
00857                      it != old_group->members().end();
00858                      ++it )
00859                     (*it)->removeTransient( this );
00860                 }
00861             // and it's transiet for all windows in the new group (this one is the most recent
00862             // in the group, so it is transient only for all previous windows)
00863             // loops are checked in checkGroupTransients()
00864             for( ClientList::ConstIterator it = group()->members().begin();
00865                  it != group()->members().end();
00866                  ++it )
00867                 (*it)->addTransient( this );
00868             }
00869 #endif
00870         // group transient splashscreens should be transient even for windows
00871         // in group mapped later
00872         for( ClientList::ConstIterator it = group()->members().begin();
00873              it != group()->members().end();
00874              ++it )
00875             {
00876             if( !(*it)->isSplash())
00877                 continue;
00878             if( !(*it)->groupTransient())
00879                 continue;
00880             if( *it == this || hasTransient( *it, true )) // TODO indirect?
00881                 continue;
00882             addTransient( *it );
00883         }
00884         }
00885     checkGroupTransients();
00886     checkActiveModal();
00887     workspace()->updateClientLayer( this );
00888     }
00889 
00890 bool Client::check_active_modal = false;
00891 
00892 void Client::checkActiveModal()
00893     {
00894     // if the active window got new modal transient, activate it.
00895     // cannot be done in AddTransient(), because there may temporarily
00896     // exist loops, breaking findModal
00897     Client* check_modal = workspace()->mostRecentlyActivatedClient();
00898     if( check_modal != NULL && check_modal->check_active_modal )
00899         {
00900         Client* new_modal = check_modal->findModal();
00901         if( new_modal != NULL && new_modal != check_modal )
00902             {
00903             if( !new_modal->isManaged())
00904                 return; // postpone check until end of manage()
00905             workspace()->activateClient( new_modal );
00906             }
00907         check_modal->check_active_modal = false;
00908         }
00909     }
00910 
00911 } // namespace
KDE Logo
This file is part of the documentation for kwin Library Version 3.4.3.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Jun 14 01:54:12 2006 by doxygen 1.4.4 written by Dimitri van Heesch, © 1997-2003