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