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 
00263     // tests that definitely mean they belong together
00264     if( c1 == c2 )
00265         same_app = true;
00266     else if( c1->isTransient() && c2->hasTransient( c1, true ))
00267         same_app = true; // c1 has c2 as mainwindow
00268     else if( c2->isTransient() && c1->hasTransient( c2, true ))
00269         same_app = true; // c2 has c1 as mainwindow
00270     else if( c1->group() == c2->group())
00271         same_app = true; // same group
00272     else if( c1->wmClientLeader() == c2->wmClientLeader()
00273         && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
00274         && c2->wmClientLeader() != c2->window()) // don't use in this test then
00275         same_app = true; // same client leader
00276 
00277     // tests that mean they most probably don't belong together
00278     else if( c1->pid() != c2->pid()
00279         || c1->wmClientMachine( false ) != c2->wmClientMachine( false ))
00280         ; // different processes
00281     else if( c1->wmClientLeader() != c2->wmClientLeader()
00282         && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
00283         && c2->wmClientLeader() != c2->window()) // don't use in this test then
00284         ; // different client leader
00285     else if( !resourceMatch( c1, c2 ))
00286         ; // different apps
00287     else if( !sameAppWindowRoleMatch( c1, c2, active_hack ))
00288         ; // "different" apps
00289     else if( c1->pid() == 0 || c2->pid() == 0 )
00290         ; // old apps that don't have _NET_WM_PID, consider them different
00291           // if they weren't found to match above
00292     else
00293         same_app = true; // looks like it's the same app
00294 
00295     return same_app;
00296     }
00297 
00298 // Non-transient windows with window role containing '#' are always
00299 // considered belonging to different applications (unless
00300 // the window role is exactly the same). KMainWindow sets
00301 // window role this way by default, and different KMainWindow
00302 // usually "are" different application from user's point of view.
00303 // This help with no-focus-stealing for e.g. konqy reusing.
00304 // On the other hand, if one of the windows is active, they are
00305 // considered belonging to the same application. This is for
00306 // the cases when opening new mainwindow directly from the application,
00307 // e.g. 'Open New Window' in konqy ( active_hack == true ).
00308 bool Client::sameAppWindowRoleMatch( const Client* c1, const Client* c2, bool active_hack )
00309     {
00310     if( c1->isTransient())
00311         {
00312         while( c1->transientFor() != NULL )
00313             c1 = c1->transientFor();
00314         if( c1->groupTransient())
00315             return c1->group() == c2->group();
00316 #if 0
00317                 // if a group transient is in its own group, it didn't possibly have a group,
00318                 // and therefore should be considered belonging to the same app like
00319                 // all other windows from the same app
00320                 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
00321 #endif
00322         }
00323     if( c2->isTransient())
00324         {
00325         while( c2->transientFor() != NULL )
00326             c2 = c2->transientFor();
00327         if( c2->groupTransient())
00328             return c1->group() == c2->group();
00329 #if 0
00330                 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
00331 #endif
00332         }
00333     int pos1 = c1->windowRole().find( '#' );
00334     int pos2 = c2->windowRole().find( '#' );
00335     if(( pos1 >= 0 && pos2 >= 0 )
00336         ||
00337     // hacks here
00338         // Mozilla has resourceName() and resourceClass() swapped
00339         c1->resourceName() == "mozilla" && c2->resourceName() == "mozilla" )
00340         {
00341         if( !active_hack )   // without the active hack for focus stealing prevention,
00342             return c1 == c2; // different mainwindows are always different apps
00343         if( !c1->isActive() && !c2->isActive())
00344             return c1 == c2;
00345         else
00346             return true;
00347         }
00348     return true;
00349     }
00350 
00351 /*
00352 
00353  Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3
00354 
00355  WM_TRANSIENT_FOR is basically means "this is my mainwindow".
00356  For NET::Unknown windows, transient windows are considered to be NET::Dialog
00357  windows, for compatibility with non-NETWM clients. KWin may adjust the value
00358  of this property in some cases (window pointing to itself or creating a loop,
00359  keeping NET::Splash windows above other windows from the same app, etc.).
00360 
00361  Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after
00362  possibly being adjusted by KWin. Client::transient_for points to the Client
00363  this Client is transient for, or is NULL. If Client::transient_for_id is
00364  poiting to the root window, the window is considered to be transient
00365  for the whole window group, as suggested in NETWM 7.3.
00366 
00367  In the case of group transient window, Client::transient_for is NULL,
00368  and Client::groupTransient() returns true. Such window is treated as
00369  if it were transient for every window in its window group that has been
00370  mapped _before_ it (or, to be exact, was added to the same group before it).
00371  Otherwise two group transients can create loops, which can lead very very
00372  nasty things (bug #67914 and all its dupes).
00373 
00374  Client::original_transient_for_id is the value of the property, which
00375  may be different if Client::transient_for_id if e.g. forcing NET::Splash
00376  to be kept on top of its window group, or when the mainwindow is not mapped
00377  yet, in which case the window is temporarily made group transient,
00378  and when the mainwindow is mapped, transiency is re-evaluated.
00379 
00380  This can get a bit complicated with with e.g. two Konqueror windows created
00381  by the same process. They should ideally appear like two independent applications
00382  to the user. This should be accomplished by all windows in the same process
00383  having the same window group (needs to be changed in Qt at the moment), and
00384  using non-group transients poiting to their relevant mainwindow for toolwindows
00385  etc. KWin should handle both group and non-group transient dialogs well.
00386 
00387  In other words:
00388  - non-transient windows     : isTransient() == false
00389  - normal transients         : transientFor() != NULL
00390  - group transients          : groupTransient() == true
00391 
00392  - list of mainwindows       : mainClients()  (call once and loop over the result)
00393  - list of transients        : transients()
00394  - every window in the group : group()->members()
00395 */
00396 
00397 void Client::readTransient()
00398     {
00399     Window new_transient_for_id;
00400     if( XGetTransientForHint( qt_xdisplay(), window(), &new_transient_for_id ))
00401         {
00402         original_transient_for_id = new_transient_for_id;
00403         new_transient_for_id = verifyTransientFor( new_transient_for_id, true );
00404         }
00405     else
00406         {
00407         original_transient_for_id = None;
00408         new_transient_for_id = verifyTransientFor( None, false );
00409         }
00410     setTransient( new_transient_for_id );
00411     }
00412 
00413 void Client::setTransient( Window new_transient_for_id )
00414     {
00415     if( new_transient_for_id != transient_for_id )
00416         {
00417         removeFromMainClients();
00418         transient_for = NULL;
00419         transient_for_id = new_transient_for_id;
00420         if( transient_for_id != None && !groupTransient())
00421             {
00422             transient_for = workspace()->findClient( WindowMatchPredicate( transient_for_id ));
00423             assert( transient_for != NULL ); // verifyTransient() had to check this
00424             transient_for->addTransient( this );
00425             } // checkGroup() will check 'check_active_modal'
00426         checkGroup( NULL, true ); // force, because transiency has changed
00427         if( isTopMenu())
00428             workspace()->updateCurrentTopMenu();
00429         workspace()->updateClientLayer( this );
00430         }
00431     }
00432 
00433 void Client::removeFromMainClients()
00434     {
00435     if( transientFor() != NULL )
00436         transientFor()->removeTransient( this );
00437     if( groupTransient())
00438         {
00439         for( ClientList::ConstIterator it = group()->members().begin();
00440              it != group()->members().end();
00441              ++it )
00442             (*it)->removeTransient( this );
00443         }
00444     }
00445 
00446 // *sigh* this transiency handling is madness :(
00447 // This one is called when destroying/releasing a window.
00448 // It makes sure this client is removed from all grouping
00449 // related lists.
00450 void Client::cleanGrouping()
00451     {
00452 //    kdDebug() << "CLEANGROUPING:" << this << endl;
00453 //    for( ClientList::ConstIterator it = group()->members().begin();
00454 //         it != group()->members().end();
00455 //         ++it )
00456 //        kdDebug() << "CL:" << *it << endl;
00457 //    ClientList mains;
00458 //    mains = mainClients();
00459 //    for( ClientList::ConstIterator it = mains.begin();
00460 //         it != mains.end();
00461 //         ++it )
00462 //        kdDebug() << "MN:" << *it << endl;
00463     removeFromMainClients();
00464 //    kdDebug() << "CLEANGROUPING2:" << this << endl;
00465 //    for( ClientList::ConstIterator it = group()->members().begin();
00466 //         it != group()->members().end();
00467 //         ++it )
00468 //        kdDebug() << "CL2:" << *it << endl;
00469 //    mains = mainClients();
00470 //    for( ClientList::ConstIterator it = mains.begin();
00471 //         it != mains.end();
00472 //         ++it )
00473 //        kdDebug() << "MN2:" << *it << endl;
00474     for( ClientList::ConstIterator it = transients_list.begin();
00475          it != transients_list.end();
00476          )
00477         {
00478         if( (*it)->transientFor() == this )
00479             {
00480             ClientList::ConstIterator it2 = it++;
00481             removeTransient( *it2 );
00482             }
00483         else
00484             ++it;
00485         }
00486 //    kdDebug() << "CLEANGROUPING3:" << this << endl;
00487 //    for( ClientList::ConstIterator it = group()->members().begin();
00488 //         it != group()->members().end();
00489 //         ++it )
00490 //        kdDebug() << "CL3:" << *it << endl;
00491 //    mains = mainClients();
00492 //    for( ClientList::ConstIterator it = mains.begin();
00493 //         it != mains.end();
00494 //         ++it )
00495 //        kdDebug() << "MN3:" << *it << endl;
00496     // HACK
00497     // removeFromMainClients() did remove 'this' from transient
00498     // lists of all group members, but then made windows that
00499     // were transient for 'this' group transient, which again
00500     // added 'this' to those transient lists :(
00501     ClientList group_members = group()->members();
00502     group()->removeMember( this );
00503     in_group = NULL;
00504     for( ClientList::ConstIterator it = group_members.begin();
00505          it != group_members.end();
00506          ++it )
00507         (*it)->removeTransient( this );
00508 //    kdDebug() << "CLEANGROUPING4:" << this << endl;
00509 //    for( ClientList::ConstIterator it = group_members.begin();
00510 //         it != group_members.end();
00511 //         ++it )
00512 //        kdDebug() << "CL4:" << *it << endl;
00513     }
00514 
00515 // Make sure that no group transient is considered transient
00516 // for a window that is (directly or indirectly) transient for it
00517 // (including another group transients).
00518 // Non-group transients not causing loops are checked in verifyTransientFor().
00519 void Client::checkGroupTransients()
00520     {
00521     for( ClientList::ConstIterator it1 = group()->members().begin();
00522          it1 != group()->members().end();
00523          ++it1 )
00524         {
00525         if( !(*it1)->groupTransient()) // check all group transients in the group
00526             continue;                  // TODO optimize to check only the changed ones?
00527         for( ClientList::ConstIterator it2 = group()->members().begin();
00528              it2 != group()->members().end();
00529              ++it2 ) // group transients can be transient only for others in the group,
00530             {        // so don't make them transient for the ones that are transient for it
00531             if( *it1 == *it2 )
00532                 continue;
00533             for( Client* cl = (*it2)->transientFor();
00534                  cl != NULL;
00535                  cl = cl->transientFor())
00536                 {
00537                 if( cl == *it1 )
00538                     { // don't use removeTransient(), that would modify *it2 too
00539                     (*it2)->transients_list.remove( *it1 );
00540                     continue;
00541                     }
00542                 }
00543             // if *it1 and *it2 are both group transients, and are transient for each other,
00544             // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later,
00545             // and should be therefore on top of *it1
00546             // TODO This could possibly be optimized, it also requires hasTransient() to check for loops.
00547             if( (*it2)->groupTransient() && (*it1)->hasTransient( *it2, true ) && (*it2)->hasTransient( *it1, true ))
00548                 (*it2)->transients_list.remove( *it1 );
00549             // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3
00550             // is added, make it transient only for W2, not for W1, because it's already indirectly
00551             // transient for it - the indirect transiency actually shouldn't break anything,
00552             // but it can lead to exponentially expensive operations (#95231)
00553             // TODO this is pretty slow as well
00554             for( ClientList::ConstIterator it3 = group()->members().begin();
00555                  it3 != group()->members().end();
00556                  ++it3 )
00557                 {
00558                 if( *it1 == *it2 || *it2 == *it3 || *it1 == *it3 )
00559                     continue;
00560                 if( (*it2)->hasTransient( *it1, false ) && (*it3)->hasTransient( *it1, false ))
00561                     {
00562                     if( (*it2)->hasTransient( *it3, true ))
00563                         (*it3)->transients_list.remove( *it1 );
00564                     if( (*it3)->hasTransient( *it2, true ))
00565                         (*it2)->transients_list.remove( *it1 );
00566                     }
00567                 }
00568             }
00569         }
00570     }
00571 
00575 Window Client::verifyTransientFor( Window new_transient_for, bool defined )
00576     {
00577     Window new_property_value = new_transient_for;
00578     // make sure splashscreens are shown above all their app's windows, even though
00579     // they're in Normal layer
00580     if( isSplash() && new_transient_for == None )
00581         new_transient_for = workspace()->rootWin();
00582     if( new_transient_for == None )
00583         if( defined ) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window
00584             new_property_value = new_transient_for = workspace()->rootWin();
00585         else
00586             return None;
00587     if( new_transient_for == window()) // pointing to self
00588         { // also fix the property itself
00589         kdWarning( 1216 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." << endl;
00590         new_property_value = new_transient_for = workspace()->rootWin();
00591         }
00592 //  The transient_for window may be embedded in another application,
00593 //  so kwin cannot see it. Try to find the managed client for the
00594 //  window and fix the transient_for property if possible.
00595     WId before_search = new_transient_for;
00596     while( new_transient_for != None
00597            && new_transient_for != workspace()->rootWin()
00598            && !workspace()->findClient( WindowMatchPredicate( new_transient_for )))
00599         {
00600         Window root_return, parent_return;
00601         Window* wins = NULL;
00602         unsigned int nwins;
00603         int r = XQueryTree(qt_xdisplay(), new_transient_for, &root_return, &parent_return,  &wins, &nwins);
00604         if ( wins )
00605             XFree((void *) wins);
00606         if ( r == 0)
00607             break;
00608         new_transient_for = parent_return;
00609         }
00610     if( Client* new_transient_for_client = workspace()->findClient( WindowMatchPredicate( new_transient_for )))
00611         {
00612         if( new_transient_for != before_search )
00613             {
00614             kdDebug( 1212 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window "
00615                 << before_search << ", child of " << new_transient_for_client << ", adjusting." << endl;
00616             new_property_value = new_transient_for; // also fix the property
00617             }
00618         }
00619     else
00620         new_transient_for = before_search; // nice try
00621 // loop detection
00622 // group transients cannot cause loops, because they're considered transient only for non-transient
00623 // windows in the group
00624     int count = 20;
00625     Window loop_pos = new_transient_for;
00626     while( loop_pos != None && loop_pos != workspace()->rootWin())
00627         {
00628         Client* pos = workspace()->findClient( WindowMatchPredicate( loop_pos ));
00629         if( pos == NULL )
00630             break;
00631         loop_pos = pos->transient_for_id;
00632         if( --count == 0 )
00633             {
00634             kdWarning( 1216 ) << "Client " << this << " caused WM_TRANSIENT_FOR loop." << endl;
00635             new_transient_for = workspace()->rootWin();
00636             }
00637         }
00638     if( new_transient_for != workspace()->rootWin()
00639         && workspace()->findClient( WindowMatchPredicate( new_transient_for )) == NULL )
00640         { // it's transient for a specific window, but that window is not mapped
00641         new_transient_for = workspace()->rootWin();
00642         }
00643     if( new_property_value != original_transient_for_id )
00644         XSetTransientForHint( qt_xdisplay(), window(), new_property_value );
00645     return new_transient_for;
00646     }
00647 
00648 void Client::addTransient( Client* cl )
00649     {
00650     assert( !transients_list.contains( cl ));
00651 //    assert( !cl->hasTransient( this, true )); will be fixed in checkGroupTransients()
00652     assert( cl != this );
00653     transients_list.append( cl );
00654     if( workspace()->mostRecentlyActivatedClient() == this && cl->isModal())        
00655         check_active_modal = true;
00656 //    kdDebug() << "ADDTRANS:" << this << ":" << cl << endl;
00657 //    kdDebug() << kdBacktrace() << endl;
00658 //    for( ClientList::ConstIterator it = transients_list.begin();
00659 //         it != transients_list.end();
00660 //         ++it )
00661 //        kdDebug() << "AT:" << (*it) << endl;
00662     }
00663 
00664 void Client::removeTransient( Client* cl )
00665     {
00666 //    kdDebug() << "REMOVETRANS:" << this << ":" << cl << endl;
00667 //    kdDebug() << kdBacktrace() << endl;
00668     transients_list.remove( cl );
00669     // cl is transient for this, but this is going away
00670     // make cl group transient
00671     if( cl->transientFor() == this )
00672         {
00673         cl->transient_for_id = None;
00674         cl->transient_for = NULL; // SELI
00675 // SELI       cl->setTransient( workspace()->rootWin());
00676         cl->setTransient( None );
00677         }
00678     }
00679 
00680 // A new window has been mapped. Check if it's not a mainwindow for this already existing window.
00681 void Client::checkTransient( Window w )
00682     {
00683     if( original_transient_for_id != w )
00684         return;
00685     w = verifyTransientFor( w, true );
00686     setTransient( w );
00687     }
00688 
00689 // returns true if cl is the transient_for window for this client,
00690 // or recursively the transient_for window
00691 bool Client::hasTransient( const Client* cl, bool indirect ) const
00692     {
00693     // checkGroupTransients() uses this to break loops, so hasTransient() must detect them
00694     ConstClientList set;
00695     return hasTransientInternal( cl, indirect, set );
00696     }
00697 
00698 bool Client::hasTransientInternal( const Client* cl, bool indirect, ConstClientList& set ) const
00699     {
00700     if( cl->transientFor() != NULL )
00701         {
00702         if( cl->transientFor() == this )
00703             return true;
00704         if( !indirect )
00705             return false;
00706         if( set.contains( cl ))
00707             return false;
00708         set.append( cl );
00709         return hasTransientInternal( cl->transientFor(), indirect, set );
00710         }
00711     if( !cl->isTransient())
00712         return false;
00713     if( group() != cl->group())
00714         return false;
00715     // cl is group transient, search from top
00716     if( transients().contains( const_cast< Client* >( cl )))
00717         return true;
00718     if( !indirect )
00719         return false;
00720     if( set.contains( this ))
00721         return false;
00722     set.append( this );
00723     for( ClientList::ConstIterator it = transients().begin();
00724          it != transients().end();
00725          ++it )
00726         if( (*it)->hasTransientInternal( cl, indirect, set ))
00727             return true;
00728     return false;
00729     }
00730 
00731 ClientList Client::mainClients() const
00732     {
00733     if( !isTransient())
00734         return ClientList();
00735     if( transientFor() != NULL )
00736         return ClientList() << const_cast< Client* >( transientFor());
00737     ClientList result;
00738     for( ClientList::ConstIterator it = group()->members().begin();
00739          it != group()->members().end();
00740          ++it )
00741         if((*it)->hasTransient( this, false ))
00742             result.append( *it );
00743     return result;
00744     }
00745 
00746 Client* Client::findModal()
00747     {
00748     for( ClientList::ConstIterator it = transients().begin();
00749          it != transients().end();
00750          ++it )
00751         if( Client* ret = (*it)->findModal())
00752             return ret;
00753     if( isModal())
00754         return this;
00755     return NULL;
00756     }
00757 
00758 // Client::window_group only holds the contents of the hint,
00759 // but it should be used only to find the group, not for anything else
00760 // Argument is only when some specific group needs to be set.
00761 void Client::checkGroup( Group* set_group, bool force )
00762     {
00763     Group* old_group = in_group;
00764     Window old_group_leader = old_group != NULL ? old_group->leader() : None;
00765     if( set_group != NULL )
00766         {
00767         if( set_group != in_group )
00768             {
00769             if( in_group != NULL )
00770                 in_group->removeMember( this );
00771             in_group = set_group;
00772             in_group->addMember( this );
00773             }
00774         }
00775     else if( window_group != None )
00776         {
00777         Group* new_group = workspace()->findGroup( window_group );
00778         if( transientFor() != NULL && transientFor()->group() != new_group )
00779             { // move the window to the right group (e.g. a dialog provided
00780               // by different app, but transient for this one, so make it part of that group)
00781             new_group = transientFor()->group();
00782             }
00783         if( new_group == NULL ) // doesn't exist yet
00784             new_group = new Group( window_group, workspace());
00785         if( new_group != in_group )
00786             {
00787             if( in_group != NULL )
00788                 in_group->removeMember( this );
00789             in_group = new_group;
00790             in_group->addMember( this );
00791             }
00792         }
00793     else
00794         {
00795         if( transientFor() != NULL )
00796             { // doesn't have window group set, but is transient for something
00797           // so make it part of that group
00798             Group* new_group = transientFor()->group();
00799             if( new_group != in_group )
00800                 {
00801                 if( in_group != NULL )
00802                     in_group->removeMember( this );
00803                 in_group = transientFor()->group();
00804                 in_group->addMember( this );
00805                 }
00806             }
00807         else if( groupTransient())
00808             { // group transient which actually doesn't have a group :(
00809               // try creating group with other windows with the same client leader
00810             Group* new_group = workspace()->findClientLeaderGroup( this );
00811             if( new_group == NULL )
00812                 new_group = new Group( None, workspace());
00813             if( new_group != in_group )
00814                 {
00815                 if( in_group != NULL )
00816                     in_group->removeMember( this );
00817                 in_group = new_group;
00818                 in_group->addMember( this );
00819                 }
00820             }
00821         else // Not transient without a group, put it in its client leader group.
00822             { // This might be stupid if grouping was used for e.g. taskbar grouping
00823               // or minimizing together the whole group, but as long as its used
00824               // only for dialogs it's better to keep windows from one app in one group.
00825             Group* new_group = workspace()->findClientLeaderGroup( this );
00826             if( in_group != NULL && in_group != new_group )
00827                 {
00828                 in_group->removeMember( this );            
00829                 in_group = NULL;
00830                 }
00831             if( new_group == NULL )
00832                 new_group = new Group( None, workspace() );
00833             if( in_group != new_group )
00834              {
00835                 in_group = new_group;
00836                 in_group->addMember( this );
00837                 }
00838             }
00839         }
00840     if( in_group != old_group || force )
00841         {
00842         for( ClientList::Iterator it = transients_list.begin();
00843              it != transients_list.end();
00844              )
00845             { // group transients in the old group are no longer transient for it
00846             if( (*it)->groupTransient() && (*it)->group() != group())
00847                 it = transients_list.remove( it );
00848             else
00849                 ++it;
00850             }
00851         if( groupTransient())
00852             {
00853             // no longer transient for ones in the old group
00854             if( old_group != NULL && workspace()->findGroup( old_group_leader ) == old_group ) // if it still exists
00855                 {
00856                 for( ClientList::ConstIterator it = old_group->members().begin();
00857                      it != old_group->members().end();
00858                      ++it )
00859                     (*it)->removeTransient( this );
00860                 }
00861             // and make transient for all in the new group
00862             for( ClientList::ConstIterator it = group()->members().begin();
00863                  it != group()->members().end();
00864                  ++it )
00865                 {
00866                 if( *it == this )
00867                     break; // this means the window is only transient for windows mapped before it
00868                 (*it)->addTransient( this );
00869                 }
00870             }
00871         // group transient splashscreens should be transient even for windows
00872         // in group mapped later
00873         for( ClientList::ConstIterator it = group()->members().begin();
00874              it != group()->members().end();
00875              ++it )
00876             {
00877             if( !(*it)->isSplash())
00878                 continue;
00879             if( !(*it)->groupTransient())
00880                 continue;
00881             if( *it == this || hasTransient( *it, true )) // TODO indirect?
00882                 continue;
00883             addTransient( *it );
00884         }
00885         }
00886     checkGroupTransients();
00887     checkActiveModal();
00888     workspace()->updateClientLayer( this );
00889     }
00890 
00891 bool Client::check_active_modal = false;
00892 
00893 void Client::checkActiveModal()
00894     {
00895     // if the active window got new modal transient, activate it.
00896     // cannot be done in AddTransient(), because there may temporarily
00897     // exist loops, breaking findModal
00898     Client* check_modal = workspace()->mostRecentlyActivatedClient();
00899     if( check_modal != NULL && check_modal->check_active_modal )
00900         {
00901         Client* new_modal = check_modal->findModal();
00902         if( new_modal != NULL && new_modal != check_modal )
00903             {
00904             if( !new_modal->isManaged())
00905                 return; // postpone check until end of manage()
00906             workspace()->activateClient( new_modal );
00907             }
00908         check_modal->check_active_modal = false;
00909         }
00910     }
00911 
00912 } // namespace
KDE Home | KDE Accessibility Home | Description of Access Keys