Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / workspace / kwin / group.cpp
blob974844e7b94b4864368e501786d59e62b7e81a4f
1 /********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
5 Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6 Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 *********************************************************************/
24 This file contains things relevant to window grouping.
28 //#define QT_CLEAN_NAMESPACE
30 #include "group.h"
31 #include <QTextStream>
32 #include "workspace.h"
33 #include "client.h"
34 #include "effects.h"
36 #include <assert.h>
37 #include <kstartupinfo.h>
38 #include <QX11Info>
42 TODO
43 Rename as many uses of 'transient' as possible (hasTransient->hasSubwindow,etc.),
44 or I'll get it backwards in half of the cases again.
47 namespace KWin
51 Consistency checks for window relations. Since transients are determinated
52 using Client::transiency_list and main windows are determined using Client::transientFor()
53 or the group for group transients, these have to match both ways.
55 //#define ENABLE_TRANSIENCY_CHECK
57 #ifdef NDEBUG
58 #undef ENABLE_TRANSIENCY_CHECK
59 #endif
61 #ifdef ENABLE_TRANSIENCY_CHECK
62 static bool transiencyCheckNonExistent = false;
64 bool performTransiencyCheck()
66 bool ret = true;
67 ClientList clients = Workspace::self()->clients;
68 for( ClientList::ConstIterator it1 = clients.begin();
69 it1 != clients.end();
70 ++it1 )
72 if( (*it1)->deleting )
73 continue;
74 if( (*it1)->in_group == NULL )
76 kdDebug() << "TC: " << *it1 << " in not in a group" << endl;
77 ret = false;
79 else if( !(*it1)->in_group->members().contains( *it1 ))
81 kdDebug() << "TC: " << *it1 << " has a group " << (*it1)->in_group << " but group does not contain it" << endl;
82 ret = false;
84 if( !(*it1)->isTransient())
86 if( !(*it1)->mainClients().isEmpty())
88 kdDebug() << "TC: " << *it1 << " is not transient, has main clients:" << (*it1)->mainClients() << endl;
89 ret = false;
92 else
94 ClientList mains = (*it1)->mainClients();
95 for( ClientList::ConstIterator it2 = mains.begin();
96 it2 != mains.end();
97 ++it2 )
99 if( transiencyCheckNonExistent
100 && !Workspace::self()->clients.contains( *it2 )
101 && !Workspace::self()->desktops.contains( *it2 ))
103 kDebug() << "TC:" << *it1 << " has non-existent main client ";
104 kDebug() << "TC2:" << *it2; // this may crash
105 ret = false;
106 continue;
108 if( !(*it2)->transients_list.contains( *it1 ))
110 kdDebug() << "TC:" << *it1 << " has main client " << *it2 << " but main client does not have it as a transient" << endl;
111 ret = false;
115 ClientList trans = (*it1)->transients_list;
116 for( ClientList::ConstIterator it2 = trans.begin();
117 it2 != trans.end();
118 ++it2 )
120 if( transiencyCheckNonExistent
121 && !Workspace::self()->clients.contains( *it2 )
122 && !Workspace::self()->desktops.contains( *it2 ))
124 kDebug() << "TC:" << *it1 << " has non-existent transient ";
125 kDebug() << "TC2:" << *it2; // this may crash
126 ret = false;
127 continue;
129 if( !(*it2)->mainClients().contains( *it1 ))
131 kdDebug() << "TC:" << *it1 << " has transient " << *it2 << " but transient does not have it as a main client" << endl;
132 ret = false;
136 GroupList groups = Workspace::self()->groups;
137 for( GroupList::ConstIterator it1 = groups.begin();
138 it1 != groups.end();
139 ++it1 )
141 ClientList members = (*it1)->members();
142 for( ClientList::ConstIterator it2 = members.begin();
143 it2 != members.end();
144 ++it2 )
146 if( (*it2)->in_group != *it1 )
148 kdDebug() << "TC: Group " << *it1 << " contains client " << *it2 << " but client is not in that group" << endl;
149 ret = false;
153 return ret;
156 static QString transiencyCheckStartBt;
157 static const Client* transiencyCheckClient;
158 static int transiencyCheck = 0;
160 static void startTransiencyCheck( const QString& bt, const Client* c, bool ne )
162 if( ++transiencyCheck == 1 )
164 transiencyCheckStartBt = bt;
165 transiencyCheckClient = c;
167 if( ne )
168 transiencyCheckNonExistent = true;
170 static void checkTransiency()
172 if( --transiencyCheck == 0 )
174 if( !performTransiencyCheck())
176 kdDebug() << "BT:" << transiencyCheckStartBt << endl;
177 kdDebug() << "CLIENT:" << transiencyCheckClient << endl;
178 assert( false );
180 transiencyCheckNonExistent = false;
183 class TransiencyChecker
185 public:
186 TransiencyChecker( const QString& bt, const Client*c ) { startTransiencyCheck( bt, c, false ); }
187 ~TransiencyChecker() { checkTransiency(); }
190 void checkNonExistentClients()
192 startTransiencyCheck( kdBacktrace(), NULL, true );
193 checkTransiency();
196 #define TRANSIENCY_CHECK( c ) TransiencyChecker transiency_checker( kdBacktrace(), c )
198 #else
200 #define TRANSIENCY_CHECK( c )
202 void checkNonExistentClients()
206 #endif
208 //********************************************
209 // Group
210 //********************************************
212 Group::Group( Window leader_P, Workspace* workspace_P )
213 : leader_client( NULL ),
214 leader_wid( leader_P ),
215 _workspace( workspace_P ),
216 leader_info( NULL ),
217 user_time( -1U ),
218 refcount( 0 )
220 if( leader_P != None )
222 leader_client = workspace_P->findClient( WindowMatchPredicate( leader_P ));
223 unsigned long properties[ 2 ] = { 0, NET::WM2StartupId };
224 leader_info = new NETWinInfo( display(), leader_P, rootWindow(),
225 properties, 2 );
227 effect_group = new EffectWindowGroupImpl( this );
228 workspace()->addGroup( this, Allowed );
231 Group::~Group()
233 delete leader_info;
234 delete effect_group;
237 QPixmap Group::icon() const
239 if( leader_client != NULL )
240 return leader_client->icon();
241 else if( leader_wid != None )
243 QPixmap ic;
244 Client::readIcons( leader_wid, &ic, NULL );
245 return ic;
247 return QPixmap();
250 QPixmap Group::miniIcon() const
252 if( leader_client != NULL )
253 return leader_client->miniIcon();
254 else if( leader_wid != None )
256 QPixmap ic;
257 Client::readIcons( leader_wid, NULL, &ic );
258 return ic;
260 return QPixmap();
263 void Group::addMember( Client* member_P )
265 TRANSIENCY_CHECK( member_P );
266 _members.append( member_P );
267 // kDebug() << "GROUPADD:" << this << ":" << member_P;
268 // kDebug() << kBacktrace();
271 void Group::removeMember( Client* member_P )
273 TRANSIENCY_CHECK( member_P );
274 // kDebug() << "GROUPREMOVE:" << this << ":" << member_P;
275 // kDebug() << kBacktrace();
276 Q_ASSERT( _members.contains( member_P ));
277 _members.removeAll( member_P );
278 // there are cases when automatic deleting of groups must be delayed,
279 // e.g. when removing a member and doing some operation on the possibly
280 // other members of the group (which would be however deleted already
281 // if there were no other members)
282 if( refcount == 0 && _members.isEmpty())
284 workspace()->removeGroup( this, Allowed );
285 delete this;
289 void Group::ref()
291 ++refcount;
294 void Group::deref()
296 if( --refcount == 0 && _members.isEmpty())
298 workspace()->removeGroup( this, Allowed );
299 delete this;
303 void Group::gotLeader( Client* leader_P )
305 assert( leader_P->window() == leader_wid );
306 leader_client = leader_P;
309 void Group::lostLeader()
311 assert( !_members.contains( leader_client ));
312 leader_client = NULL;
313 if( _members.isEmpty())
315 workspace()->removeGroup( this, Allowed );
316 delete this;
320 void Group::getIcons()
322 // TODO - also needs adding the flag to NETWinInfo
325 //***************************************
326 // Workspace
327 //***************************************
329 Group* Workspace::findGroup( Window leader ) const
331 assert( leader != None );
332 for( GroupList::ConstIterator it = groups.begin();
333 it != groups.end();
334 ++it )
335 if( (*it)->leader() == leader )
336 return *it;
337 return NULL;
340 // Client is group transient, but has no group set. Try to find
341 // group with windows with the same client leader.
342 Group* Workspace::findClientLeaderGroup( const Client* c ) const
344 TRANSIENCY_CHECK( c );
345 Group* ret = NULL;
346 for( ClientList::ConstIterator it = clients.begin();
347 it != clients.end();
348 ++it )
350 if( *it == c )
351 continue;
352 if( (*it)->wmClientLeader() == c->wmClientLeader())
354 if( ret == NULL || ret == (*it)->group())
355 ret = (*it)->group();
356 else
358 // There are already two groups with the same client leader.
359 // This most probably means the app uses group transients without
360 // setting group for its windows. Merging the two groups is a bad
361 // hack, but there's no really good solution for this case.
362 ClientList old_group = (*it)->group()->members();
363 // old_group autodeletes when being empty
364 for( int pos = 0;
365 pos < old_group.count();
366 ++pos )
368 Client* tmp = old_group[ pos ];
369 if( tmp != c )
370 tmp->changeClientLeaderGroup( ret );
375 return ret;
378 void Workspace::updateMinimizedOfTransients( Client* c )
380 // if mainwindow is minimized or shaded, minimize transients too
381 if ( c->isMinimized() || c->isShade() )
383 for( ClientList::ConstIterator it = c->transients().begin();
384 it != c->transients().end();
385 ++it )
387 if( !(*it)->isMinimized()
388 && !(*it)->isTopMenu() ) // topmenus are not minimized, they're hidden
390 (*it)->minimize( true ); // avoid animation
391 updateMinimizedOfTransients( (*it) );
395 else
396 { // else unmiminize the transients
397 for( ClientList::ConstIterator it = c->transients().begin();
398 it != c->transients().end();
399 ++it )
401 if( (*it)->isMinimized()
402 && !(*it)->isTopMenu())
404 (*it)->unminimize( true ); // avoid animation
405 updateMinimizedOfTransients( (*it) );
413 Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops.
415 void Workspace::updateOnAllDesktopsOfTransients( Client* c )
417 for( ClientList::ConstIterator it = c->transients().begin();
418 it != c->transients().end();
419 ++it)
421 if( (*it)->isOnAllDesktops() != c->isOnAllDesktops())
422 (*it)->setOnAllDesktops( c->isOnAllDesktops());
426 // A new window has been mapped. Check if it's not a mainwindow for some already existing transient window.
427 void Workspace::checkTransients( Window w )
429 TRANSIENCY_CHECK( NULL );
430 for( ClientList::ConstIterator it = clients.begin();
431 it != clients.end();
432 ++it )
433 (*it)->checkTransient( w );
437 //****************************************
438 // Toplevel
439 //****************************************
441 // hacks for broken apps here
442 // all resource classes are forced to be lowercase
443 bool Toplevel::resourceMatch( const Toplevel* c1, const Toplevel* c2 )
445 // xv has "xv" as resource name, and different strings starting with "XV" as resource class
446 if( qstrncmp( c1->resourceClass(), "xv", 2 ) == 0 && c1->resourceName() == "xv" )
447 return qstrncmp( c2->resourceClass(), "xv", 2 ) == 0 && c2->resourceName() == "xv";
448 // Mozilla has "Mozilla" as resource name, and different strings as resource class
449 if( c1->resourceName() == "mozilla" )
450 return c2->resourceName() == "mozilla";
451 return c1->resourceClass() == c2->resourceClass();
455 //****************************************
456 // Client
457 //****************************************
459 bool Client::belongToSameApplication( const Client* c1, const Client* c2, bool active_hack )
461 bool same_app = false;
463 // tests that definitely mean they belong together
464 if( c1 == c2 )
465 same_app = true;
466 else if( c1->isTransient() && c2->hasTransient( c1, true ))
467 same_app = true; // c1 has c2 as mainwindow
468 else if( c2->isTransient() && c1->hasTransient( c2, true ))
469 same_app = true; // c2 has c1 as mainwindow
470 else if( c1->group() == c2->group())
471 same_app = true; // same group
472 else if( c1->wmClientLeader() == c2->wmClientLeader()
473 && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
474 && c2->wmClientLeader() != c2->window()) // don't use in this test then
475 same_app = true; // same client leader
477 // tests that mean they most probably don't belong together
478 else if( c1->pid() != c2->pid()
479 || c1->wmClientMachine( false ) != c2->wmClientMachine( false ))
480 ; // different processes
481 else if( c1->wmClientLeader() != c2->wmClientLeader()
482 && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
483 && c2->wmClientLeader() != c2->window()) // don't use in this test then
484 ; // different client leader
485 else if( !resourceMatch( c1, c2 ))
486 ; // different apps
487 else if( !sameAppWindowRoleMatch( c1, c2, active_hack ))
488 ; // "different" apps
489 else if( c1->pid() == 0 || c2->pid() == 0 )
490 ; // old apps that don't have _NET_WM_PID, consider them different
491 // if they weren't found to match above
492 else
493 same_app = true; // looks like it's the same app
495 return same_app;
498 // Non-transient windows with window role containing '#' are always
499 // considered belonging to different applications (unless
500 // the window role is exactly the same). KMainWindow sets
501 // window role this way by default, and different KMainWindow
502 // usually "are" different application from user's point of view.
503 // This help with no-focus-stealing for e.g. konqy reusing.
504 // On the other hand, if one of the windows is active, they are
505 // considered belonging to the same application. This is for
506 // the cases when opening new mainwindow directly from the application,
507 // e.g. 'Open New Window' in konqy ( active_hack == true ).
508 bool Client::sameAppWindowRoleMatch( const Client* c1, const Client* c2, bool active_hack )
510 if( c1->isTransient())
512 while( c1->transientFor() != NULL )
513 c1 = c1->transientFor();
514 if( c1->groupTransient())
515 return c1->group() == c2->group();
516 #if 0
517 // if a group transient is in its own group, it didn't possibly have a group,
518 // and therefore should be considered belonging to the same app like
519 // all other windows from the same app
520 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
521 #endif
523 if( c2->isTransient())
525 while( c2->transientFor() != NULL )
526 c2 = c2->transientFor();
527 if( c2->groupTransient())
528 return c1->group() == c2->group();
529 #if 0
530 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
531 #endif
533 int pos1 = c1->windowRole().indexOf( '#' );
534 int pos2 = c2->windowRole().indexOf( '#' );
535 if(( pos1 >= 0 && pos2 >= 0 )
537 // hacks here
538 // Mozilla has resourceName() and resourceClass() swapped
539 c1->resourceName() == "mozilla" && c2->resourceName() == "mozilla" )
541 if( !active_hack ) // without the active hack for focus stealing prevention,
542 return c1 == c2; // different mainwindows are always different apps
543 if( !c1->isActive() && !c2->isActive())
544 return c1 == c2;
545 else
546 return true;
548 return true;
553 Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3
555 WM_TRANSIENT_FOR is basically means "this is my mainwindow".
556 For NET::Unknown windows, transient windows are considered to be NET::Dialog
557 windows, for compatibility with non-NETWM clients. KWin may adjust the value
558 of this property in some cases (window pointing to itself or creating a loop,
559 keeping NET::Splash windows above other windows from the same app, etc.).
561 Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after
562 possibly being adjusted by KWin. Client::transient_for points to the Client
563 this Client is transient for, or is NULL. If Client::transient_for_id is
564 poiting to the root window, the window is considered to be transient
565 for the whole window group, as suggested in NETWM 7.3.
567 In the case of group transient window, Client::transient_for is NULL,
568 and Client::groupTransient() returns true. Such window is treated as
569 if it were transient for every window in its window group that has been
570 mapped _before_ it (or, to be exact, was added to the same group before it).
571 Otherwise two group transients can create loops, which can lead very very
572 nasty things (bug #67914 and all its dupes).
574 Client::original_transient_for_id is the value of the property, which
575 may be different if Client::transient_for_id if e.g. forcing NET::Splash
576 to be kept on top of its window group, or when the mainwindow is not mapped
577 yet, in which case the window is temporarily made group transient,
578 and when the mainwindow is mapped, transiency is re-evaluated.
580 This can get a bit complicated with with e.g. two Konqueror windows created
581 by the same process. They should ideally appear like two independent applications
582 to the user. This should be accomplished by all windows in the same process
583 having the same window group (needs to be changed in Qt at the moment), and
584 using non-group transients poiting to their relevant mainwindow for toolwindows
585 etc. KWin should handle both group and non-group transient dialogs well.
587 In other words:
588 - non-transient windows : isTransient() == false
589 - normal transients : transientFor() != NULL
590 - group transients : groupTransient() == true
592 - list of mainwindows : mainClients() (call once and loop over the result)
593 - list of transients : transients()
594 - every window in the group : group()->members()
597 void Client::readTransient()
599 TRANSIENCY_CHECK( this );
600 Window new_transient_for_id;
601 if( XGetTransientForHint( display(), window(), &new_transient_for_id ))
603 original_transient_for_id = new_transient_for_id;
604 new_transient_for_id = verifyTransientFor( new_transient_for_id, true );
606 else
608 original_transient_for_id = None;
609 new_transient_for_id = verifyTransientFor( None, false );
611 setTransient( new_transient_for_id );
614 void Client::setTransient( Window new_transient_for_id )
616 TRANSIENCY_CHECK( this );
617 if( new_transient_for_id != transient_for_id )
619 removeFromMainClients();
620 transient_for = NULL;
621 transient_for_id = new_transient_for_id;
622 if( transient_for_id != None && !groupTransient())
624 transient_for = workspace()->findClient( WindowMatchPredicate( transient_for_id ));
625 assert( transient_for != NULL ); // verifyTransient() had to check this
626 transient_for->addTransient( this );
627 } // checkGroup() will check 'check_active_modal'
628 checkGroup( NULL, true ); // force, because transiency has changed
629 if( isTopMenu())
630 workspace()->updateCurrentTopMenu();
631 workspace()->updateClientLayer( this );
635 void Client::removeFromMainClients()
637 TRANSIENCY_CHECK( this );
638 if( transientFor() != NULL )
639 transientFor()->removeTransient( this );
640 if( groupTransient())
642 for( ClientList::ConstIterator it = group()->members().begin();
643 it != group()->members().end();
644 ++it )
645 (*it)->removeTransient( this );
649 // *sigh* this transiency handling is madness :(
650 // This one is called when destroying/releasing a window.
651 // It makes sure this client is removed from all grouping
652 // related lists.
653 void Client::cleanGrouping()
655 TRANSIENCY_CHECK( this );
656 // kDebug() << "CLEANGROUPING:" << this;
657 // for( ClientList::ConstIterator it = group()->members().begin();
658 // it != group()->members().end();
659 // ++it )
660 // kDebug() << "CL:" << *it;
661 // ClientList mains;
662 // mains = mainClients();
663 // for( ClientList::ConstIterator it = mains.begin();
664 // it != mains.end();
665 // ++it )
666 // kDebug() << "MN:" << *it;
667 removeFromMainClients();
668 // kDebug() << "CLEANGROUPING2:" << this;
669 // for( ClientList::ConstIterator it = group()->members().begin();
670 // it != group()->members().end();
671 // ++it )
672 // kDebug() << "CL2:" << *it;
673 // mains = mainClients();
674 // for( ClientList::ConstIterator it = mains.begin();
675 // it != mains.end();
676 // ++it )
677 // kDebug() << "MN2:" << *it;
678 for( ClientList::ConstIterator it = transients_list.begin();
679 it != transients_list.end();
682 if( (*it)->transientFor() == this )
684 removeTransient( *it );
685 it = transients_list.begin(); // restart, just in case something more has changed with the list
687 else
688 ++it;
690 // kDebug() << "CLEANGROUPING3:" << this;
691 // for( ClientList::ConstIterator it = group()->members().begin();
692 // it != group()->members().end();
693 // ++it )
694 // kDebug() << "CL3:" << *it;
695 // mains = mainClients();
696 // for( ClientList::ConstIterator it = mains.begin();
697 // it != mains.end();
698 // ++it )
699 // kDebug() << "MN3:" << *it;
700 // HACK
701 // removeFromMainClients() did remove 'this' from transient
702 // lists of all group members, but then made windows that
703 // were transient for 'this' group transient, which again
704 // added 'this' to those transient lists :(
705 ClientList group_members = group()->members();
706 group()->removeMember( this );
707 in_group = NULL;
708 for( ClientList::ConstIterator it = group_members.begin();
709 it != group_members.end();
710 ++it )
711 (*it)->removeTransient( this );
712 // kDebug() << "CLEANGROUPING4:" << this;
713 // for( ClientList::ConstIterator it = group_members.begin();
714 // it != group_members.end();
715 // ++it )
716 // kDebug() << "CL4:" << *it;
719 // Make sure that no group transient is considered transient
720 // for a window that is (directly or indirectly) transient for it
721 // (including another group transients).
722 // Non-group transients not causing loops are checked in verifyTransientFor().
723 void Client::checkGroupTransients()
725 TRANSIENCY_CHECK( this );
726 for( ClientList::ConstIterator it1 = group()->members().begin();
727 it1 != group()->members().end();
728 ++it1 )
730 if( !(*it1)->groupTransient()) // check all group transients in the group
731 continue; // TODO optimize to check only the changed ones?
732 for( ClientList::ConstIterator it2 = group()->members().begin();
733 it2 != group()->members().end();
734 ++it2 ) // group transients can be transient only for others in the group,
735 { // so don't make them transient for the ones that are transient for it
736 if( *it1 == *it2 )
737 continue;
738 for( Client* cl = (*it2)->transientFor();
739 cl != NULL;
740 cl = cl->transientFor())
742 if( cl == *it1 )
743 { // don't use removeTransient(), that would modify *it2 too
744 (*it2)->transients_list.removeAll( *it1 );
745 continue;
748 // if *it1 and *it2 are both group transients, and are transient for each other,
749 // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later,
750 // and should be therefore on top of *it1
751 // TODO This could possibly be optimized, it also requires hasTransient() to check for loops.
752 if( (*it2)->groupTransient() && (*it1)->hasTransient( *it2, true ) && (*it2)->hasTransient( *it1, true ))
753 (*it2)->transients_list.removeAll( *it1 );
754 // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3
755 // is added, make it transient only for W2, not for W1, because it's already indirectly
756 // transient for it - the indirect transiency actually shouldn't break anything,
757 // but it can lead to exponentially expensive operations (#95231)
758 // TODO this is pretty slow as well
759 for( ClientList::ConstIterator it3 = group()->members().begin();
760 it3 != group()->members().end();
761 ++it3 )
763 if( *it1 == *it2 || *it2 == *it3 || *it1 == *it3 )
764 continue;
765 if( (*it2)->hasTransient( *it1, false ) && (*it3)->hasTransient( *it1, false ))
767 if( (*it2)->hasTransient( *it3, true ))
768 (*it3)->transients_list.removeAll( *it1 );
769 if( (*it3)->hasTransient( *it2, true ))
770 (*it2)->transients_list.removeAll( *it1 );
778 Check that the window is not transient for itself, and similar nonsense.
780 Window Client::verifyTransientFor( Window new_transient_for, bool defined )
782 Window new_property_value = new_transient_for;
783 // make sure splashscreens are shown above all their app's windows, even though
784 // they're in Normal layer
785 if( isSplash() && new_transient_for == None )
786 new_transient_for = rootWindow();
787 if( new_transient_for == None )
788 if( defined ) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window
789 new_property_value = new_transient_for = rootWindow();
790 else
791 return None;
792 if( new_transient_for == window()) // pointing to self
793 { // also fix the property itself
794 kWarning( 1216 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." ;
795 new_property_value = new_transient_for = rootWindow();
797 // The transient_for window may be embedded in another application,
798 // so kwin cannot see it. Try to find the managed client for the
799 // window and fix the transient_for property if possible.
800 WId before_search = new_transient_for;
801 while( new_transient_for != None
802 && new_transient_for != rootWindow()
803 && !workspace()->findClient( WindowMatchPredicate( new_transient_for )))
805 Window root_return, parent_return;
806 Window* wins = NULL;
807 unsigned int nwins;
808 int r = XQueryTree(display(), new_transient_for, &root_return, &parent_return, &wins, &nwins);
809 if ( wins )
810 XFree((void *) wins);
811 if ( r == 0)
812 break;
813 new_transient_for = parent_return;
815 if( Client* new_transient_for_client = workspace()->findClient( WindowMatchPredicate( new_transient_for )))
817 if( new_transient_for != before_search )
819 kDebug( 1212 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window "
820 << before_search << ", child of " << new_transient_for_client << ", adjusting." << endl;
821 new_property_value = new_transient_for; // also fix the property
824 else
825 new_transient_for = before_search; // nice try
826 // loop detection
827 // group transients cannot cause loops, because they're considered transient only for non-transient
828 // windows in the group
829 int count = 20;
830 Window loop_pos = new_transient_for;
831 while( loop_pos != None && loop_pos != rootWindow())
833 Client* pos = workspace()->findClient( WindowMatchPredicate( loop_pos ));
834 if( pos == NULL )
835 break;
836 loop_pos = pos->transient_for_id;
837 if( --count == 0 || pos == this )
839 kWarning( 1216 ) << "Client " << this << " caused WM_TRANSIENT_FOR loop." ;
840 new_transient_for = rootWindow();
843 if( new_transient_for != rootWindow()
844 && workspace()->findClient( WindowMatchPredicate( new_transient_for )) == NULL )
845 { // it's transient for a specific window, but that window is not mapped
846 new_transient_for = rootWindow();
848 if( new_property_value != original_transient_for_id )
849 XSetTransientForHint( display(), window(), new_property_value );
850 return new_transient_for;
853 void Client::addTransient( Client* cl )
855 TRANSIENCY_CHECK( this );
856 assert( !transients_list.contains( cl ));
857 // assert( !cl->hasTransient( this, true )); will be fixed in checkGroupTransients()
858 assert( cl != this );
859 transients_list.append( cl );
860 if( workspace()->mostRecentlyActivatedClient() == this && cl->isModal())
861 check_active_modal = true;
862 // kDebug() << "ADDTRANS:" << this << ":" << cl;
863 // kDebug() << kBacktrace();
864 // for( ClientList::ConstIterator it = transients_list.begin();
865 // it != transients_list.end();
866 // ++it )
867 // kDebug() << "AT:" << (*it);
870 void Client::removeTransient( Client* cl )
872 TRANSIENCY_CHECK( this );
873 // kDebug() << "REMOVETRANS:" << this << ":" << cl;
874 // kDebug() << kBacktrace();
875 transients_list.removeAll( cl );
876 // cl is transient for this, but this is going away
877 // make cl group transient
878 if( cl->transientFor() == this )
880 cl->transient_for_id = None;
881 cl->transient_for = NULL; // SELI
882 // SELI cl->setTransient( rootWindow());
883 cl->setTransient( None );
887 // A new window has been mapped. Check if it's not a mainwindow for this already existing window.
888 void Client::checkTransient( Window w )
890 TRANSIENCY_CHECK( this );
891 if( original_transient_for_id != w )
892 return;
893 w = verifyTransientFor( w, true );
894 setTransient( w );
897 // returns true if cl is the transient_for window for this client,
898 // or recursively the transient_for window
899 bool Client::hasTransient( const Client* cl, bool indirect ) const
901 // checkGroupTransients() uses this to break loops, so hasTransient() must detect them
902 ConstClientList set;
903 return hasTransientInternal( cl, indirect, set );
906 bool Client::hasTransientInternal( const Client* cl, bool indirect, ConstClientList& set ) const
908 if( cl->transientFor() != NULL )
910 if( cl->transientFor() == this )
911 return true;
912 if( !indirect )
913 return false;
914 if( set.contains( cl ))
915 return false;
916 set.append( cl );
917 return hasTransientInternal( cl->transientFor(), indirect, set );
919 if( !cl->isTransient())
920 return false;
921 if( group() != cl->group())
922 return false;
923 // cl is group transient, search from top
924 if( transients().contains( const_cast< Client* >( cl )))
925 return true;
926 if( !indirect )
927 return false;
928 if( set.contains( this ))
929 return false;
930 set.append( this );
931 for( ClientList::ConstIterator it = transients().begin();
932 it != transients().end();
933 ++it )
934 if( (*it)->hasTransientInternal( cl, indirect, set ))
935 return true;
936 return false;
939 ClientList Client::mainClients() const
941 if( !isTransient())
942 return ClientList();
943 if( transientFor() != NULL )
944 return ClientList() << const_cast< Client* >( transientFor());
945 ClientList result;
946 for( ClientList::ConstIterator it = group()->members().begin();
947 it != group()->members().end();
948 ++it )
949 if((*it)->hasTransient( this, false ))
950 result.append( *it );
951 return result;
954 Client* Client::findModal( bool allow_itself )
956 for( ClientList::ConstIterator it = transients().begin();
957 it != transients().end();
958 ++it )
959 if( Client* ret = (*it)->findModal( true ))
960 return ret;
961 if( isModal() && allow_itself )
962 return this;
963 return NULL;
966 // Client::window_group only holds the contents of the hint,
967 // but it should be used only to find the group, not for anything else
968 // Argument is only when some specific group needs to be set.
969 void Client::checkGroup( Group* set_group, bool force )
971 TRANSIENCY_CHECK( this );
972 Group* old_group = in_group;
973 if( old_group != NULL )
974 old_group->ref(); // turn off automatic deleting
975 if( set_group != NULL )
977 if( set_group != in_group )
979 if( in_group != NULL )
980 in_group->removeMember( this );
981 in_group = set_group;
982 in_group->addMember( this );
985 else if( window_group != None )
987 Group* new_group = workspace()->findGroup( window_group );
988 if( transientFor() != NULL && transientFor()->group() != new_group )
989 { // move the window to the right group (e.g. a dialog provided
990 // by different app, but transient for this one, so make it part of that group)
991 new_group = transientFor()->group();
993 if( new_group == NULL ) // doesn't exist yet
994 new_group = new Group( window_group, workspace());
995 if( new_group != in_group )
997 if( in_group != NULL )
998 in_group->removeMember( this );
999 in_group = new_group;
1000 in_group->addMember( this );
1003 else
1005 if( transientFor() != NULL )
1006 { // doesn't have window group set, but is transient for something
1007 // so make it part of that group
1008 Group* new_group = transientFor()->group();
1009 if( new_group != in_group )
1011 if( in_group != NULL )
1012 in_group->removeMember( this );
1013 in_group = transientFor()->group();
1014 in_group->addMember( this );
1017 else if( groupTransient())
1018 { // group transient which actually doesn't have a group :(
1019 // try creating group with other windows with the same client leader
1020 Group* new_group = workspace()->findClientLeaderGroup( this );
1021 if( new_group == NULL )
1022 new_group = new Group( None, workspace());
1023 if( new_group != in_group )
1025 if( in_group != NULL )
1026 in_group->removeMember( this );
1027 in_group = new_group;
1028 in_group->addMember( this );
1031 else // Not transient without a group, put it in its client leader group.
1032 { // This might be stupid if grouping was used for e.g. taskbar grouping
1033 // or minimizing together the whole group, but as long as its used
1034 // only for dialogs it's better to keep windows from one app in one group.
1035 Group* new_group = workspace()->findClientLeaderGroup( this );
1036 if( in_group != NULL && in_group != new_group )
1038 in_group->removeMember( this );
1039 in_group = NULL;
1041 if( new_group == NULL )
1042 new_group = new Group( None, workspace() );
1043 if( in_group != new_group )
1045 in_group = new_group;
1046 in_group->addMember( this );
1050 if( in_group != old_group || force )
1052 for( ClientList::Iterator it = transients_list.begin();
1053 it != transients_list.end();
1055 { // group transients in the old group are no longer transient for it
1056 if( (*it)->groupTransient() && (*it)->group() != group())
1057 it = transients_list.erase( it );
1058 else
1059 ++it;
1061 if( groupTransient())
1063 // no longer transient for ones in the old group
1064 if( old_group != NULL )
1066 for( ClientList::ConstIterator it = old_group->members().begin();
1067 it != old_group->members().end();
1068 ++it )
1069 (*it)->removeTransient( this );
1071 // and make transient for all in the new group
1072 for( ClientList::ConstIterator it = group()->members().begin();
1073 it != group()->members().end();
1074 ++it )
1076 if( *it == this )
1077 break; // this means the window is only transient for windows mapped before it
1078 (*it)->addTransient( this );
1081 // group transient splashscreens should be transient even for windows
1082 // in group mapped later
1083 for( ClientList::ConstIterator it = group()->members().begin();
1084 it != group()->members().end();
1085 ++it )
1087 if( !(*it)->isSplash())
1088 continue;
1089 if( !(*it)->groupTransient())
1090 continue;
1091 if( *it == this || hasTransient( *it, true )) // TODO indirect?
1092 continue;
1093 addTransient( *it );
1096 if( old_group != NULL )
1097 old_group->deref(); // can be now deleted if empty
1098 checkGroupTransients();
1099 checkActiveModal();
1100 workspace()->updateClientLayer( this );
1103 // used by Workspace::findClientLeaderGroup()
1104 void Client::changeClientLeaderGroup( Group* gr )
1106 // transientFor() != NULL are in the group of their mainwindow, so keep them there
1107 if( transientFor() != NULL )
1108 return;
1109 // also don't change the group for window which have group set
1110 if( window_group )
1111 return;
1112 checkGroup( gr ); // change group
1115 bool Client::check_active_modal = false;
1117 void Client::checkActiveModal()
1119 // if the active window got new modal transient, activate it.
1120 // cannot be done in AddTransient(), because there may temporarily
1121 // exist loops, breaking findModal
1122 Client* check_modal = workspace()->mostRecentlyActivatedClient();
1123 if( check_modal != NULL && check_modal->check_active_modal )
1125 Client* new_modal = check_modal->findModal();
1126 if( new_modal != NULL && new_modal != check_modal )
1128 if( !new_modal->isManaged())
1129 return; // postpone check until end of manage()
1130 workspace()->activateClient( new_modal );
1132 check_modal->check_active_modal = false;
1136 } // namespace