• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

KWin

group.cpp

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

KWin

Skip menu "KWin"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • KWin
  •   KWin Libraries
  • Libraries
  •   libkworkspace
  •   libplasma
  •   libsolidcontrol
  •   libtaskmanager
  • Plasma
  •   Animators
  •   Applets
  •   Engines
  • Solid Modules
Generated for API Reference by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal