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