add more spacing
[personal-kdebase.git] / workspace / kwin / group.cpp
blob0b90b2ca5bd0344223260527060e5542dfab004a
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.constBegin();
69 it1 != clients.constEnd();
70 ++it1 )
72 if( (*it1)->deleting )
73 continue;
74 if( (*it1)->in_group == NULL )
76 kDebug(1212) << "TC: " << *it1 << " in not in a group" << endl;
77 ret = false;
79 else if( !(*it1)->in_group->members().contains( *it1 ))
81 kDebug(1212) << "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 kDebug(1212) << "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.constBegin();
96 it2 != mains.constEnd();
97 ++it2 )
99 if( transiencyCheckNonExistent
100 && !Workspace::self()->clients.contains( *it2 )
101 && !Workspace::self()->desktops.contains( *it2 ))
103 kDebug(1212) << "TC:" << *it1 << " has non-existent main client ";
104 kDebug(1212) << "TC2:" << *it2; // this may crash
105 ret = false;
106 continue;
108 if( !(*it2)->transients_list.contains( *it1 ))
110 kdDebug(1212) << "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.constBegin();
117 it2 != trans.constEnd();
118 ++it2 )
120 if( transiencyCheckNonExistent
121 && !Workspace::self()->clients.contains( *it2 )
122 && !Workspace::self()->desktops.contains( *it2 ))
124 kDebug(1212) << "TC:" << *it1 << " has non-existent transient ";
125 kDebug(1212) << "TC2:" << *it2; // this may crash
126 ret = false;
127 continue;
129 if( !(*it2)->mainClients().contains( *it1 ))
131 kdDebug(1212) << "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.constBegin();
138 it1 != groups.constEnd();
139 ++it1 )
141 ClientList members = (*it1)->members();
142 for( ClientList::ConstIterator it2 = members.constBegin();
143 it2 != members.constEnd();
144 ++it2 )
146 if( (*it2)->in_group != *it1 )
148 kDebug(1212) << "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 kDebug(1212) << "BT:" << transiencyCheckStartBt << endl;
177 kDebug(1212) << "CLIENT:" << transiencyCheckClient << endl;
178 abort();
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 NETWinInfo2( 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(1212) << "GROUPADD:" << this << ":" << member_P;
268 // kDebug(1212) << kBacktrace();
271 void Group::removeMember( Client* member_P )
273 TRANSIENCY_CHECK( member_P );
274 // kDebug(1212) << "GROUPREMOVE:" << this << ":" << member_P;
275 // kDebug(1212) << 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.constBegin();
333 it != groups.constEnd();
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.constBegin();
347 it != clients.constEnd();
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().constBegin();
384 it != c->transients().constEnd();
385 ++it )
387 if( !(*it)->isMinimized()
388 && !(*it)->isTopMenu() ) // topmenus are not minimized, they're hidden
390 (*it)->minimize();
391 updateMinimizedOfTransients( (*it) );
394 if( c->isModal()) // if a modal dialog is minimized, minimize its mainwindow too
396 foreach( Client* c2, c->mainClients())
397 c2->minimize();
400 else
401 { // else unmiminize the transients
402 for( ClientList::ConstIterator it = c->transients().constBegin();
403 it != c->transients().constEnd();
404 ++it )
406 if( (*it)->isMinimized()
407 && !(*it)->isTopMenu())
409 (*it)->unminimize();
410 updateMinimizedOfTransients( (*it) );
413 if( c->isModal())
415 foreach( Client* c2, c->mainClients())
416 c2->unminimize();
423 Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops.
425 void Workspace::updateOnAllDesktopsOfTransients( Client* c )
427 for( ClientList::ConstIterator it = c->transients().constBegin();
428 it != c->transients().constEnd();
429 ++it)
431 if( (*it)->isOnAllDesktops() != c->isOnAllDesktops())
432 (*it)->setOnAllDesktops( c->isOnAllDesktops());
436 // A new window has been mapped. Check if it's not a mainwindow for some already existing transient window.
437 void Workspace::checkTransients( Window w )
439 TRANSIENCY_CHECK( NULL );
440 for( ClientList::ConstIterator it = clients.constBegin();
441 it != clients.constEnd();
442 ++it )
443 (*it)->checkTransient( w );
447 //****************************************
448 // Toplevel
449 //****************************************
451 // hacks for broken apps here
452 // all resource classes are forced to be lowercase
453 bool Toplevel::resourceMatch( const Toplevel* c1, const Toplevel* c2 )
455 // xv has "xv" as resource name, and different strings starting with "XV" as resource class
456 if( qstrncmp( c1->resourceClass(), "xv", 2 ) == 0 && c1->resourceName() == "xv" )
457 return qstrncmp( c2->resourceClass(), "xv", 2 ) == 0 && c2->resourceName() == "xv";
458 // Mozilla has "Mozilla" as resource name, and different strings as resource class
459 if( c1->resourceName() == "mozilla" )
460 return c2->resourceName() == "mozilla";
461 return c1->resourceClass() == c2->resourceClass();
465 //****************************************
466 // Client
467 //****************************************
469 bool Client::belongToSameApplication( const Client* c1, const Client* c2, bool active_hack )
471 bool same_app = false;
473 // tests that definitely mean they belong together
474 if( c1 == c2 )
475 same_app = true;
476 else if( c1->isTransient() && c2->hasTransient( c1, true ))
477 same_app = true; // c1 has c2 as mainwindow
478 else if( c2->isTransient() && c1->hasTransient( c2, true ))
479 same_app = true; // c2 has c1 as mainwindow
480 else if( c1->group() == c2->group())
481 same_app = true; // same group
482 else if( c1->wmClientLeader() == c2->wmClientLeader()
483 && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
484 && c2->wmClientLeader() != c2->window()) // don't use in this test then
485 same_app = true; // same client leader
487 // tests that mean they most probably don't belong together
488 else if( c1->pid() != c2->pid()
489 || c1->wmClientMachine( false ) != c2->wmClientMachine( false ))
490 ; // different processes
491 else if( c1->wmClientLeader() != c2->wmClientLeader()
492 && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
493 && c2->wmClientLeader() != c2->window()) // don't use in this test then
494 ; // different client leader
495 else if( !resourceMatch( c1, c2 ))
496 ; // different apps
497 else if( !sameAppWindowRoleMatch( c1, c2, active_hack ))
498 ; // "different" apps
499 else if( c1->pid() == 0 || c2->pid() == 0 )
500 ; // old apps that don't have _NET_WM_PID, consider them different
501 // if they weren't found to match above
502 else
503 same_app = true; // looks like it's the same app
505 return same_app;
508 // Non-transient windows with window role containing '#' are always
509 // considered belonging to different applications (unless
510 // the window role is exactly the same). KMainWindow sets
511 // window role this way by default, and different KMainWindow
512 // usually "are" different application from user's point of view.
513 // This help with no-focus-stealing for e.g. konqy reusing.
514 // On the other hand, if one of the windows is active, they are
515 // considered belonging to the same application. This is for
516 // the cases when opening new mainwindow directly from the application,
517 // e.g. 'Open New Window' in konqy ( active_hack == true ).
518 bool Client::sameAppWindowRoleMatch( const Client* c1, const Client* c2, bool active_hack )
520 if( c1->isTransient())
522 while( c1->transientFor() != NULL )
523 c1 = c1->transientFor();
524 if( c1->groupTransient())
525 return c1->group() == c2->group();
526 #if 0
527 // if a group transient is in its own group, it didn't possibly have a group,
528 // and therefore should be considered belonging to the same app like
529 // all other windows from the same app
530 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
531 #endif
533 if( c2->isTransient())
535 while( c2->transientFor() != NULL )
536 c2 = c2->transientFor();
537 if( c2->groupTransient())
538 return c1->group() == c2->group();
539 #if 0
540 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
541 #endif
543 int pos1 = c1->windowRole().indexOf( '#' );
544 int pos2 = c2->windowRole().indexOf( '#' );
545 if(( pos1 >= 0 && pos2 >= 0 )
547 // hacks here
548 // Mozilla has resourceName() and resourceClass() swapped
549 ( c1->resourceName() == "mozilla" && c2->resourceName() == "mozilla" ))
551 if( !active_hack ) // without the active hack for focus stealing prevention,
552 return c1 == c2; // different mainwindows are always different apps
553 if( !c1->isActive() && !c2->isActive())
554 return c1 == c2;
555 else
556 return true;
558 return true;
563 Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3
565 WM_TRANSIENT_FOR is basically means "this is my mainwindow".
566 For NET::Unknown windows, transient windows are considered to be NET::Dialog
567 windows, for compatibility with non-NETWM clients. KWin may adjust the value
568 of this property in some cases (window pointing to itself or creating a loop,
569 keeping NET::Splash windows above other windows from the same app, etc.).
571 Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after
572 possibly being adjusted by KWin. Client::transient_for points to the Client
573 this Client is transient for, or is NULL. If Client::transient_for_id is
574 poiting to the root window, the window is considered to be transient
575 for the whole window group, as suggested in NETWM 7.3.
577 In the case of group transient window, Client::transient_for is NULL,
578 and Client::groupTransient() returns true. Such window is treated as
579 if it were transient for every window in its window group that has been
580 mapped _before_ it (or, to be exact, was added to the same group before it).
581 Otherwise two group transients can create loops, which can lead very very
582 nasty things (bug #67914 and all its dupes).
584 Client::original_transient_for_id is the value of the property, which
585 may be different if Client::transient_for_id if e.g. forcing NET::Splash
586 to be kept on top of its window group, or when the mainwindow is not mapped
587 yet, in which case the window is temporarily made group transient,
588 and when the mainwindow is mapped, transiency is re-evaluated.
590 This can get a bit complicated with with e.g. two Konqueror windows created
591 by the same process. They should ideally appear like two independent applications
592 to the user. This should be accomplished by all windows in the same process
593 having the same window group (needs to be changed in Qt at the moment), and
594 using non-group transients poiting to their relevant mainwindow for toolwindows
595 etc. KWin should handle both group and non-group transient dialogs well.
597 In other words:
598 - non-transient windows : isTransient() == false
599 - normal transients : transientFor() != NULL
600 - group transients : groupTransient() == true
602 - list of mainwindows : mainClients() (call once and loop over the result)
603 - list of transients : transients()
604 - every window in the group : group()->members()
607 void Client::readTransient()
609 TRANSIENCY_CHECK( this );
610 Window new_transient_for_id;
611 if( XGetTransientForHint( display(), window(), &new_transient_for_id ))
613 original_transient_for_id = new_transient_for_id;
614 new_transient_for_id = verifyTransientFor( new_transient_for_id, true );
616 else
618 original_transient_for_id = None;
619 new_transient_for_id = verifyTransientFor( None, false );
621 setTransient( new_transient_for_id );
624 void Client::setTransient( Window new_transient_for_id )
626 TRANSIENCY_CHECK( this );
627 if( new_transient_for_id != transient_for_id )
629 removeFromMainClients();
630 transient_for = NULL;
631 transient_for_id = new_transient_for_id;
632 if( transient_for_id != None && !groupTransient())
634 transient_for = workspace()->findClient( WindowMatchPredicate( transient_for_id ));
635 assert( transient_for != NULL ); // verifyTransient() had to check this
636 transient_for->addTransient( this );
637 } // checkGroup() will check 'check_active_modal'
638 checkGroup( NULL, true ); // force, because transiency has changed
639 if( isTopMenu())
640 workspace()->updateCurrentTopMenu();
641 workspace()->updateClientLayer( this );
645 void Client::removeFromMainClients()
647 TRANSIENCY_CHECK( this );
648 if( transientFor() != NULL )
649 transientFor()->removeTransient( this );
650 if( groupTransient())
652 for( ClientList::ConstIterator it = group()->members().constBegin();
653 it != group()->members().constEnd();
654 ++it )
655 (*it)->removeTransient( this );
659 // *sigh* this transiency handling is madness :(
660 // This one is called when destroying/releasing a window.
661 // It makes sure this client is removed from all grouping
662 // related lists.
663 void Client::cleanGrouping()
665 TRANSIENCY_CHECK( this );
666 // kDebug(1212) << "CLEANGROUPING:" << this;
667 // for( ClientList::ConstIterator it = group()->members().begin();
668 // it != group()->members().end();
669 // ++it )
670 // kDebug(1212) << "CL:" << *it;
671 // ClientList mains;
672 // mains = mainClients();
673 // for( ClientList::ConstIterator it = mains.begin();
674 // it != mains.end();
675 // ++it )
676 // kDebug(1212) << "MN:" << *it;
677 removeFromMainClients();
678 // kDebug(1212) << "CLEANGROUPING2:" << this;
679 // for( ClientList::ConstIterator it = group()->members().begin();
680 // it != group()->members().end();
681 // ++it )
682 // kDebug(1212) << "CL2:" << *it;
683 // mains = mainClients();
684 // for( ClientList::ConstIterator it = mains.begin();
685 // it != mains.end();
686 // ++it )
687 // kDebug(1212) << "MN2:" << *it;
688 for( ClientList::ConstIterator it = transients_list.constBegin();
689 it != transients_list.constEnd();
692 if( (*it)->transientFor() == this )
694 removeTransient( *it );
695 it = transients_list.constBegin(); // restart, just in case something more has changed with the list
697 else
698 ++it;
700 // kDebug(1212) << "CLEANGROUPING3:" << this;
701 // for( ClientList::ConstIterator it = group()->members().begin();
702 // it != group()->members().end();
703 // ++it )
704 // kDebug(1212) << "CL3:" << *it;
705 // mains = mainClients();
706 // for( ClientList::ConstIterator it = mains.begin();
707 // it != mains.end();
708 // ++it )
709 // kDebug(1212) << "MN3:" << *it;
710 // HACK
711 // removeFromMainClients() did remove 'this' from transient
712 // lists of all group members, but then made windows that
713 // were transient for 'this' group transient, which again
714 // added 'this' to those transient lists :(
715 ClientList group_members = group()->members();
716 group()->removeMember( this );
717 in_group = NULL;
718 for( ClientList::ConstIterator it = group_members.constBegin();
719 it != group_members.constEnd();
720 ++it )
721 (*it)->removeTransient( this );
722 // kDebug(1212) << "CLEANGROUPING4:" << this;
723 // for( ClientList::ConstIterator it = group_members.begin();
724 // it != group_members.end();
725 // ++it )
726 // kDebug(1212) << "CL4:" << *it;
729 // Make sure that no group transient is considered transient
730 // for a window that is (directly or indirectly) transient for it
731 // (including another group transients).
732 // Non-group transients not causing loops are checked in verifyTransientFor().
733 void Client::checkGroupTransients()
735 TRANSIENCY_CHECK( this );
736 for( ClientList::ConstIterator it1 = group()->members().constBegin();
737 it1 != group()->members().constEnd();
738 ++it1 )
740 if( !(*it1)->groupTransient()) // check all group transients in the group
741 continue; // TODO optimize to check only the changed ones?
742 for( ClientList::ConstIterator it2 = group()->members().constBegin();
743 it2 != group()->members().constEnd();
744 ++it2 ) // group transients can be transient only for others in the group,
745 { // so don't make them transient for the ones that are transient for it
746 if( *it1 == *it2 )
747 continue;
748 for( Client* cl = (*it2)->transientFor();
749 cl != NULL;
750 cl = cl->transientFor())
752 if( cl == *it1 )
753 { // don't use removeTransient(), that would modify *it2 too
754 (*it2)->transients_list.removeAll( *it1 );
755 continue;
758 // if *it1 and *it2 are both group transients, and are transient for each other,
759 // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later,
760 // and should be therefore on top of *it1
761 // TODO This could possibly be optimized, it also requires hasTransient() to check for loops.
762 if( (*it2)->groupTransient() && (*it1)->hasTransient( *it2, true ) && (*it2)->hasTransient( *it1, true ))
763 (*it2)->transients_list.removeAll( *it1 );
764 // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3
765 // is added, make it transient only for W2, not for W1, because it's already indirectly
766 // transient for it - the indirect transiency actually shouldn't break anything,
767 // but it can lead to exponentially expensive operations (#95231)
768 // TODO this is pretty slow as well
769 for( ClientList::ConstIterator it3 = group()->members().constBegin();
770 it3 != group()->members().constEnd();
771 ++it3 )
773 if( *it1 == *it2 || *it2 == *it3 || *it1 == *it3 )
774 continue;
775 if( (*it2)->hasTransient( *it1, false ) && (*it3)->hasTransient( *it1, false ))
777 if( (*it2)->hasTransient( *it3, true ))
778 (*it2)->transients_list.removeAll( *it1 );
779 if( (*it3)->hasTransient( *it2, true ))
780 (*it3)->transients_list.removeAll( *it1 );
788 Check that the window is not transient for itself, and similar nonsense.
790 Window Client::verifyTransientFor( Window new_transient_for, bool defined )
792 Window new_property_value = new_transient_for;
793 // make sure splashscreens are shown above all their app's windows, even though
794 // they're in Normal layer
795 if( isSplash() && new_transient_for == None )
796 new_transient_for = rootWindow();
797 if( new_transient_for == None )
799 if( defined ) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window
800 new_property_value = new_transient_for = rootWindow();
801 else
802 return None;
804 if( new_transient_for == window()) // pointing to self
805 { // also fix the property itself
806 kWarning( 1216 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." ;
807 new_property_value = new_transient_for = rootWindow();
809 // The transient_for window may be embedded in another application,
810 // so kwin cannot see it. Try to find the managed client for the
811 // window and fix the transient_for property if possible.
812 WId before_search = new_transient_for;
813 while( new_transient_for != None
814 && new_transient_for != rootWindow()
815 && !workspace()->findClient( WindowMatchPredicate( new_transient_for )))
817 Window root_return, parent_return;
818 Window* wins = NULL;
819 unsigned int nwins;
820 int r = XQueryTree(display(), new_transient_for, &root_return, &parent_return, &wins, &nwins);
821 if ( wins )
822 XFree((void *) wins);
823 if ( r == 0)
824 break;
825 new_transient_for = parent_return;
827 if( Client* new_transient_for_client = workspace()->findClient( WindowMatchPredicate( new_transient_for )))
829 if( new_transient_for != before_search )
831 kDebug( 1212 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window "
832 << before_search << ", child of " << new_transient_for_client << ", adjusting." << endl;
833 new_property_value = new_transient_for; // also fix the property
836 else
837 new_transient_for = before_search; // nice try
838 // loop detection
839 // group transients cannot cause loops, because they're considered transient only for non-transient
840 // windows in the group
841 int count = 20;
842 Window loop_pos = new_transient_for;
843 while( loop_pos != None && loop_pos != rootWindow())
845 Client* pos = workspace()->findClient( WindowMatchPredicate( loop_pos ));
846 if( pos == NULL )
847 break;
848 loop_pos = pos->transient_for_id;
849 if( --count == 0 || pos == this )
851 kWarning( 1216 ) << "Client " << this << " caused WM_TRANSIENT_FOR loop." ;
852 new_transient_for = rootWindow();
855 if( new_transient_for != rootWindow()
856 && workspace()->findClient( WindowMatchPredicate( new_transient_for )) == NULL )
857 { // it's transient for a specific window, but that window is not mapped
858 new_transient_for = rootWindow();
860 if( new_property_value != original_transient_for_id )
861 XSetTransientForHint( display(), window(), new_property_value );
862 return new_transient_for;
865 void Client::addTransient( Client* cl )
867 TRANSIENCY_CHECK( this );
868 assert( !transients_list.contains( cl ));
869 // assert( !cl->hasTransient( this, true )); will be fixed in checkGroupTransients()
870 assert( cl != this );
871 transients_list.append( cl );
872 if( workspace()->mostRecentlyActivatedClient() == this && cl->isModal())
873 check_active_modal = true;
874 // kDebug(1212) << "ADDTRANS:" << this << ":" << cl;
875 // kDebug(1212) << kBacktrace();
876 // for( ClientList::ConstIterator it = transients_list.begin();
877 // it != transients_list.end();
878 // ++it )
879 // kDebug(1212) << "AT:" << (*it);
882 void Client::removeTransient( Client* cl )
884 TRANSIENCY_CHECK( this );
885 // kDebug(1212) << "REMOVETRANS:" << this << ":" << cl;
886 // kDebug(1212) << kBacktrace();
887 transients_list.removeAll( cl );
888 // cl is transient for this, but this is going away
889 // make cl group transient
890 if( cl->transientFor() == this )
892 cl->transient_for_id = None;
893 cl->transient_for = NULL; // SELI
894 // SELI cl->setTransient( rootWindow());
895 cl->setTransient( None );
899 // A new window has been mapped. Check if it's not a mainwindow for this already existing window.
900 void Client::checkTransient( Window w )
902 TRANSIENCY_CHECK( this );
903 if( original_transient_for_id != w )
904 return;
905 w = verifyTransientFor( w, true );
906 setTransient( w );
909 // returns true if cl is the transient_for window for this client,
910 // or recursively the transient_for window
911 bool Client::hasTransient( const Client* cl, bool indirect ) const
913 // checkGroupTransients() uses this to break loops, so hasTransient() must detect them
914 ConstClientList set;
915 return hasTransientInternal( cl, indirect, set );
918 bool Client::hasTransientInternal( const Client* cl, bool indirect, ConstClientList& set ) const
920 if( cl->transientFor() != NULL )
922 if( cl->transientFor() == this )
923 return true;
924 if( !indirect )
925 return false;
926 if( set.contains( cl ))
927 return false;
928 set.append( cl );
929 return hasTransientInternal( cl->transientFor(), indirect, set );
931 if( !cl->isTransient())
932 return false;
933 if( group() != cl->group())
934 return false;
935 // cl is group transient, search from top
936 if( transients().contains( const_cast< Client* >( cl )))
937 return true;
938 if( !indirect )
939 return false;
940 if( set.contains( this ))
941 return false;
942 set.append( this );
943 for( ClientList::ConstIterator it = transients().constBegin();
944 it != transients().constEnd();
945 ++it )
946 if( (*it)->hasTransientInternal( cl, indirect, set ))
947 return true;
948 return false;
951 ClientList Client::mainClients() const
953 if( !isTransient())
954 return ClientList();
955 if( transientFor() != NULL )
956 return ClientList() << const_cast< Client* >( transientFor());
957 ClientList result;
958 for( ClientList::ConstIterator it = group()->members().constBegin();
959 it != group()->members().constEnd();
960 ++it )
961 if((*it)->hasTransient( this, false ))
962 result.append( *it );
963 return result;
966 ClientList Client::allMainClients() const
968 ClientList result = mainClients();
969 foreach( const Client* cl, result )
970 result += cl->allMainClients();
971 return result;
974 Client* Client::findModal( bool allow_itself )
976 for( ClientList::ConstIterator it = transients().constBegin();
977 it != transients().constEnd();
978 ++it )
979 if( Client* ret = (*it)->findModal( true ))
980 return ret;
981 if( isModal() && allow_itself )
982 return this;
983 return NULL;
986 // Client::window_group only holds the contents of the hint,
987 // but it should be used only to find the group, not for anything else
988 // Argument is only when some specific group needs to be set.
989 void Client::checkGroup( Group* set_group, bool force )
991 TRANSIENCY_CHECK( this );
992 Group* old_group = in_group;
993 if( old_group != NULL )
994 old_group->ref(); // turn off automatic deleting
995 if( set_group != NULL )
997 if( set_group != in_group )
999 if( in_group != NULL )
1000 in_group->removeMember( this );
1001 in_group = set_group;
1002 in_group->addMember( this );
1005 else if( window_group != None )
1007 Group* new_group = workspace()->findGroup( window_group );
1008 if( transientFor() != NULL && transientFor()->group() != new_group )
1009 { // move the window to the right group (e.g. a dialog provided
1010 // by different app, but transient for this one, so make it part of that group)
1011 new_group = transientFor()->group();
1013 if( new_group == NULL ) // doesn't exist yet
1014 new_group = new Group( window_group, workspace());
1015 if( new_group != in_group )
1017 if( in_group != NULL )
1018 in_group->removeMember( this );
1019 in_group = new_group;
1020 in_group->addMember( this );
1023 else
1025 if( transientFor() != NULL )
1026 { // doesn't have window group set, but is transient for something
1027 // so make it part of that group
1028 Group* new_group = transientFor()->group();
1029 if( new_group != in_group )
1031 if( in_group != NULL )
1032 in_group->removeMember( this );
1033 in_group = transientFor()->group();
1034 in_group->addMember( this );
1037 else if( groupTransient())
1038 { // group transient which actually doesn't have a group :(
1039 // try creating group with other windows with the same client leader
1040 Group* new_group = workspace()->findClientLeaderGroup( this );
1041 if( new_group == NULL )
1042 new_group = new Group( None, workspace());
1043 if( new_group != in_group )
1045 if( in_group != NULL )
1046 in_group->removeMember( this );
1047 in_group = new_group;
1048 in_group->addMember( this );
1051 else // Not transient without a group, put it in its client leader group.
1052 { // This might be stupid if grouping was used for e.g. taskbar grouping
1053 // or minimizing together the whole group, but as long as its used
1054 // only for dialogs it's better to keep windows from one app in one group.
1055 Group* new_group = workspace()->findClientLeaderGroup( this );
1056 if( in_group != NULL && in_group != new_group )
1058 in_group->removeMember( this );
1059 in_group = NULL;
1061 if( new_group == NULL )
1062 new_group = new Group( None, workspace() );
1063 if( in_group != new_group )
1065 in_group = new_group;
1066 in_group->addMember( this );
1070 if( in_group != old_group || force )
1072 for( ClientList::Iterator it = transients_list.begin();
1073 it != transients_list.end();
1075 { // group transients in the old group are no longer transient for it
1076 if( (*it)->groupTransient() && (*it)->group() != group())
1077 it = transients_list.erase( it );
1078 else
1079 ++it;
1081 if( groupTransient())
1083 // no longer transient for ones in the old group
1084 if( old_group != NULL )
1086 for( ClientList::ConstIterator it = old_group->members().constBegin();
1087 it != old_group->members().constEnd();
1088 ++it )
1089 (*it)->removeTransient( this );
1091 // and make transient for all in the new group
1092 for( ClientList::ConstIterator it = group()->members().constBegin();
1093 it != group()->members().constEnd();
1094 ++it )
1096 if( *it == this )
1097 break; // this means the window is only transient for windows mapped before it
1098 (*it)->addTransient( this );
1101 // group transient splashscreens should be transient even for windows
1102 // in group mapped later
1103 for( ClientList::ConstIterator it = group()->members().constBegin();
1104 it != group()->members().constEnd();
1105 ++it )
1107 if( !(*it)->isSplash())
1108 continue;
1109 if( !(*it)->groupTransient())
1110 continue;
1111 if( *it == this || hasTransient( *it, true )) // TODO indirect?
1112 continue;
1113 addTransient( *it );
1116 if( old_group != NULL )
1117 old_group->deref(); // can be now deleted if empty
1118 checkGroupTransients();
1119 checkActiveModal();
1120 workspace()->updateClientLayer( this );
1123 // used by Workspace::findClientLeaderGroup()
1124 void Client::changeClientLeaderGroup( Group* gr )
1126 // transientFor() != NULL are in the group of their mainwindow, so keep them there
1127 if( transientFor() != NULL )
1128 return;
1129 // also don't change the group for window which have group set
1130 if( window_group )
1131 return;
1132 checkGroup( gr ); // change group
1135 bool Client::check_active_modal = false;
1137 void Client::checkActiveModal()
1139 // if the active window got new modal transient, activate it.
1140 // cannot be done in AddTransient(), because there may temporarily
1141 // exist loops, breaking findModal
1142 Client* check_modal = workspace()->mostRecentlyActivatedClient();
1143 if( check_modal != NULL && check_modal->check_active_modal )
1145 Client* new_modal = check_modal->findModal();
1146 if( new_modal != NULL && new_modal != check_modal )
1148 if( !new_modal->isManaged())
1149 return; // postpone check until end of manage()
1150 workspace()->activateClient( new_modal );
1152 check_modal->check_active_modal = false;
1156 } // namespace