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
You can Freely distribute this program under the GNU General Public
9
License. See the file "COPYING" for the exact licensing terms.
10
******************************************************************/
14
This file contains things relevant to window grouping.
18
//#define QT_CLEAN_NAMESPACE
22
#include "workspace.h"
26
#include <kstartupinfo.h>
31
Rename as many uses of 'transient' as possible (hasTransient->hasSubwindow,etc.),
32
or I'll get it backwards in half of the cases again.
35
namespace KWinInternal
39
Consistency checks for window relations. Since transients are determinated
40
using Client::transiency_list and main windows are determined using Client::transientFor()
41
or the group for group transients, these have to match both ways.
43
//#define ENABLE_TRANSIENCY_CHECK
46
#undef ENABLE_TRANSIENCY_CHECK
49
#ifdef ENABLE_TRANSIENCY_CHECK
50
static bool transiencyCheckNonExistent = false;
52
bool performTransiencyCheck()
55
ClientList clients = Workspace::self()->clients;
56
for( ClientList::ConstIterator it1 = clients.begin();
60
if( (*it1)->deleting )
62
if( (*it1)->in_group == NULL )
64
kdDebug() << "TC: " << *it1 << " in not in a group" << endl;
67
else if( !(*it1)->in_group->members().contains( *it1 ))
69
kdDebug() << "TC: " << *it1 << " has a group " << (*it1)->in_group << " but group does not contain it" << endl;
72
if( !(*it1)->isTransient())
74
if( !(*it1)->mainClients().isEmpty())
76
kdDebug() << "TC: " << *it1 << " is not transient, has main clients:" << (*it1)->mainClients() << endl;
82
ClientList mains = (*it1)->mainClients();
83
for( ClientList::ConstIterator it2 = mains.begin();
87
if( transiencyCheckNonExistent
88
&& !Workspace::self()->clients.contains( *it2 )
89
&& !Workspace::self()->desktops.contains( *it2 ))
91
kdDebug() << "TC:" << *it1 << " has non-existent main client " << endl;
92
kdDebug() << "TC2:" << *it2 << endl; // this may crash
96
if( !(*it2)->transients_list.contains( *it1 ))
98
kdDebug() << "TC:" << *it1 << " has main client " << *it2 << " but main client does not have it as a transient" << endl;
103
ClientList trans = (*it1)->transients_list;
104
for( ClientList::ConstIterator it2 = trans.begin();
108
if( transiencyCheckNonExistent
109
&& !Workspace::self()->clients.contains( *it2 )
110
&& !Workspace::self()->desktops.contains( *it2 ))
112
kdDebug() << "TC:" << *it1 << " has non-existent transient " << endl;
113
kdDebug() << "TC2:" << *it2 << endl; // this may crash
117
if( !(*it2)->mainClients().contains( *it1 ))
119
kdDebug() << "TC:" << *it1 << " has transient " << *it2 << " but transient does not have it as a main client" << endl;
124
GroupList groups = Workspace::self()->groups;
125
for( GroupList::ConstIterator it1 = groups.begin();
129
ClientList members = (*it1)->members();
130
for( ClientList::ConstIterator it2 = members.begin();
131
it2 != members.end();
134
if( (*it2)->in_group != *it1 )
136
kdDebug() << "TC: Group " << *it1 << " contains client " << *it2 << " but client is not in that group" << endl;
144
static QString transiencyCheckStartBt;
145
static const Client* transiencyCheckClient;
146
static int transiencyCheck = 0;
148
static void startTransiencyCheck( const QString& bt, const Client* c, bool ne )
150
if( ++transiencyCheck == 1 )
152
transiencyCheckStartBt = bt;
153
transiencyCheckClient = c;
156
transiencyCheckNonExistent = true;
158
static void checkTransiency()
160
if( --transiencyCheck == 0 )
162
if( !performTransiencyCheck())
164
kdDebug() << "BT:" << transiencyCheckStartBt << endl;
165
kdDebug() << "CLIENT:" << transiencyCheckClient << endl;
168
transiencyCheckNonExistent = false;
171
class TransiencyChecker
174
TransiencyChecker( const QString& bt, const Client*c ) { startTransiencyCheck( bt, c, false ); }
175
~TransiencyChecker() { checkTransiency(); }
178
void checkNonExistentClients()
180
startTransiencyCheck( kdBacktrace(), NULL, true );
184
#define TRANSIENCY_CHECK( c ) TransiencyChecker transiency_checker( kdBacktrace(), c )
188
#define TRANSIENCY_CHECK( c )
190
void checkNonExistentClients()
196
//********************************************
198
//********************************************
200
Group::Group( Window leader_P, Workspace* workspace_P )
201
: leader_client( NULL ),
202
leader_wid( leader_P ),
203
_workspace( workspace_P ),
208
if( leader_P != None )
210
leader_client = workspace_P->findClient( WindowMatchPredicate( leader_P ));
211
unsigned long properties[ 2 ] = { 0, NET::WM2StartupId };
212
leader_info = new NETWinInfo( qt_xdisplay(), leader_P, workspace()->rootWin(),
215
workspace()->addGroup( this, Allowed );
223
QPixmap Group::icon() const
225
if( leader_client != NULL )
226
return leader_client->icon();
227
else if( leader_wid != None )
230
Client::readIcons( leader_wid, &ic, NULL );
236
QPixmap Group::miniIcon() const
238
if( leader_client != NULL )
239
return leader_client->miniIcon();
240
else if( leader_wid != None )
243
Client::readIcons( leader_wid, NULL, &ic );
249
void Group::addMember( Client* member_P )
251
TRANSIENCY_CHECK( member_P );
252
_members.append( member_P );
253
// kdDebug() << "GROUPADD:" << this << ":" << member_P << endl;
254
// kdDebug() << kdBacktrace() << endl;
257
void Group::removeMember( Client* member_P )
259
TRANSIENCY_CHECK( member_P );
260
// kdDebug() << "GROUPREMOVE:" << this << ":" << member_P << endl;
261
// kdDebug() << kdBacktrace() << endl;
262
Q_ASSERT( _members.contains( member_P ));
263
_members.remove( member_P );
264
// there are cases when automatic deleting of groups must be delayed,
265
// e.g. when removing a member and doing some operation on the possibly
266
// other members of the group (which would be however deleted already
267
// if there were no other members)
268
if( refcount == 0 && _members.isEmpty())
270
workspace()->removeGroup( this, Allowed );
282
if( --refcount == 0 && _members.isEmpty())
284
workspace()->removeGroup( this, Allowed );
289
void Group::gotLeader( Client* leader_P )
291
assert( leader_P->window() == leader_wid );
292
leader_client = leader_P;
295
void Group::lostLeader()
297
assert( !_members.contains( leader_client ));
298
leader_client = NULL;
299
if( _members.isEmpty())
301
workspace()->removeGroup( this, Allowed );
306
void Group::getIcons()
308
// TODO - also needs adding the flag to NETWinInfo
311
//***************************************
313
//***************************************
315
Group* Workspace::findGroup( Window leader ) const
317
assert( leader != None );
318
for( GroupList::ConstIterator it = groups.begin();
321
if( (*it)->leader() == leader )
326
// Client is group transient, but has no group set. Try to find
327
// group with windows with the same client leader.
328
Group* Workspace::findClientLeaderGroup( const Client* c ) const
330
TRANSIENCY_CHECK( c );
332
for( ClientList::ConstIterator it = clients.begin();
338
if( (*it)->wmClientLeader() == c->wmClientLeader())
340
if( ret == NULL || ret == (*it)->group())
341
ret = (*it)->group();
344
// There are already two groups with the same client leader.
345
// This most probably means the app uses group transients without
346
// setting group for its windows. Merging the two groups is a bad
347
// hack, but there's no really good solution for this case.
348
ClientList old_group = (*it)->group()->members();
349
// old_group autodeletes when being empty
350
for( unsigned int pos = 0;
351
pos < old_group.count();
354
Client* tmp = old_group[ pos ];
356
tmp->changeClientLeaderGroup( ret );
364
void Workspace::updateMinimizedOfTransients( Client* c )
366
// if mainwindow is minimized or shaded, minimize transients too
367
if ( c->isMinimized() || c->isShade() )
369
for( ClientList::ConstIterator it = c->transients().begin();
370
it != c->transients().end();
373
if( !(*it)->isMinimized()
374
&& !(*it)->isTopMenu() ) // topmenus are not minimized, they're hidden
376
(*it)->minimize( true ); // avoid animation
377
updateMinimizedOfTransients( (*it) );
382
{ // else unmiminize the transients
383
for( ClientList::ConstIterator it = c->transients().begin();
384
it != c->transients().end();
387
if( (*it)->isMinimized()
388
&& !(*it)->isTopMenu())
390
(*it)->unminimize( true ); // avoid animation
391
updateMinimizedOfTransients( (*it) );
399
Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops.
401
void Workspace::updateOnAllDesktopsOfTransients( Client* c )
403
for( ClientList::ConstIterator it = c->transients().begin();
404
it != c->transients().end();
407
if( (*it)->isOnAllDesktops() != c->isOnAllDesktops())
408
(*it)->setOnAllDesktops( c->isOnAllDesktops());
412
// A new window has been mapped. Check if it's not a mainwindow for some already existing transient window.
413
void Workspace::checkTransients( Window w )
415
TRANSIENCY_CHECK( NULL );
416
for( ClientList::ConstIterator it = clients.begin();
419
(*it)->checkTransient( w );
424
//****************************************
426
//****************************************
428
// hacks for broken apps here
429
// all resource classes are forced to be lowercase
430
bool Client::resourceMatch( const Client* c1, const Client* c2 )
432
// xv has "xv" as resource name, and different strings starting with "XV" as resource class
433
if( qstrncmp( c1->resourceClass(), "xv", 2 ) == 0 && c1->resourceName() == "xv" )
434
return qstrncmp( c2->resourceClass(), "xv", 2 ) == 0 && c2->resourceName() == "xv";
435
// Mozilla has "Mozilla" as resource name, and different strings as resource class
436
if( c1->resourceName() == "mozilla" )
437
return c2->resourceName() == "mozilla";
438
return c1->resourceClass() == c2->resourceClass();
441
bool Client::belongToSameApplication( const Client* c1, const Client* c2, bool active_hack )
443
bool same_app = false;
445
// tests that definitely mean they belong together
448
else if( c1->isTransient() && c2->hasTransient( c1, true ))
449
same_app = true; // c1 has c2 as mainwindow
450
else if( c2->isTransient() && c1->hasTransient( c2, true ))
451
same_app = true; // c2 has c1 as mainwindow
452
else if( c1->group() == c2->group())
453
same_app = true; // same group
454
else if( c1->wmClientLeader() == c2->wmClientLeader()
455
&& c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
456
&& c2->wmClientLeader() != c2->window()) // don't use in this test then
457
same_app = true; // same client leader
459
// tests that mean they most probably don't belong together
460
else if( c1->pid() != c2->pid()
461
|| c1->wmClientMachine( false ) != c2->wmClientMachine( false ))
462
; // different processes
463
else if( c1->wmClientLeader() != c2->wmClientLeader()
464
&& c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
465
&& c2->wmClientLeader() != c2->window()) // don't use in this test then
466
; // different client leader
467
else if( !resourceMatch( c1, c2 ))
469
else if( !sameAppWindowRoleMatch( c1, c2, active_hack ))
470
; // "different" apps
471
else if( c1->pid() == 0 || c2->pid() == 0 )
472
; // old apps that don't have _NET_WM_PID, consider them different
473
// if they weren't found to match above
475
same_app = true; // looks like it's the same app
480
// Non-transient windows with window role containing '#' are always
481
// considered belonging to different applications (unless
482
// the window role is exactly the same). KMainWindow sets
483
// window role this way by default, and different KMainWindow
484
// usually "are" different application from user's point of view.
485
// This help with no-focus-stealing for e.g. konqy reusing.
486
// On the other hand, if one of the windows is active, they are
487
// considered belonging to the same application. This is for
488
// the cases when opening new mainwindow directly from the application,
489
// e.g. 'Open New Window' in konqy ( active_hack == true ).
490
bool Client::sameAppWindowRoleMatch( const Client* c1, const Client* c2, bool active_hack )
492
if( c1->isTransient())
494
while( c1->transientFor() != NULL )
495
c1 = c1->transientFor();
496
if( c1->groupTransient())
497
return c1->group() == c2->group();
499
// if a group transient is in its own group, it didn't possibly have a group,
500
// and therefore should be considered belonging to the same app like
501
// all other windows from the same app
502
|| c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
505
if( c2->isTransient())
507
while( c2->transientFor() != NULL )
508
c2 = c2->transientFor();
509
if( c2->groupTransient())
510
return c1->group() == c2->group();
512
|| c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
515
int pos1 = c1->windowRole().find( '#' );
516
int pos2 = c2->windowRole().find( '#' );
517
if(( pos1 >= 0 && pos2 >= 0 )
520
// Mozilla has resourceName() and resourceClass() swapped
521
c1->resourceName() == "mozilla" && c2->resourceName() == "mozilla" )
523
if( !active_hack ) // without the active hack for focus stealing prevention,
524
return c1 == c2; // different mainwindows are always different apps
525
if( !c1->isActive() && !c2->isActive())
535
Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3
537
WM_TRANSIENT_FOR is basically means "this is my mainwindow".
538
For NET::Unknown windows, transient windows are considered to be NET::Dialog
539
windows, for compatibility with non-NETWM clients. KWin may adjust the value
540
of this property in some cases (window pointing to itself or creating a loop,
541
keeping NET::Splash windows above other windows from the same app, etc.).
543
Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after
544
possibly being adjusted by KWin. Client::transient_for points to the Client
545
this Client is transient for, or is NULL. If Client::transient_for_id is
546
poiting to the root window, the window is considered to be transient
547
for the whole window group, as suggested in NETWM 7.3.
549
In the case of group transient window, Client::transient_for is NULL,
550
and Client::groupTransient() returns true. Such window is treated as
551
if it were transient for every window in its window group that has been
552
mapped _before_ it (or, to be exact, was added to the same group before it).
553
Otherwise two group transients can create loops, which can lead very very
554
nasty things (bug #67914 and all its dupes).
556
Client::original_transient_for_id is the value of the property, which
557
may be different if Client::transient_for_id if e.g. forcing NET::Splash
558
to be kept on top of its window group, or when the mainwindow is not mapped
559
yet, in which case the window is temporarily made group transient,
560
and when the mainwindow is mapped, transiency is re-evaluated.
562
This can get a bit complicated with with e.g. two Konqueror windows created
563
by the same process. They should ideally appear like two independent applications
564
to the user. This should be accomplished by all windows in the same process
565
having the same window group (needs to be changed in Qt at the moment), and
566
using non-group transients poiting to their relevant mainwindow for toolwindows
567
etc. KWin should handle both group and non-group transient dialogs well.
570
- non-transient windows : isTransient() == false
571
- normal transients : transientFor() != NULL
572
- group transients : groupTransient() == true
574
- list of mainwindows : mainClients() (call once and loop over the result)
575
- list of transients : transients()
576
- every window in the group : group()->members()
579
void Client::readTransient()
581
TRANSIENCY_CHECK( this );
582
Window new_transient_for_id;
583
if( XGetTransientForHint( qt_xdisplay(), window(), &new_transient_for_id ))
585
original_transient_for_id = new_transient_for_id;
586
new_transient_for_id = verifyTransientFor( new_transient_for_id, true );
590
original_transient_for_id = None;
591
new_transient_for_id = verifyTransientFor( None, false );
593
setTransient( new_transient_for_id );
596
void Client::setTransient( Window new_transient_for_id )
598
TRANSIENCY_CHECK( this );
599
if( new_transient_for_id != transient_for_id )
601
removeFromMainClients();
602
transient_for = NULL;
603
transient_for_id = new_transient_for_id;
604
if( transient_for_id != None && !groupTransient())
606
transient_for = workspace()->findClient( WindowMatchPredicate( transient_for_id ));
607
assert( transient_for != NULL ); // verifyTransient() had to check this
608
transient_for->addTransient( this );
609
} // checkGroup() will check 'check_active_modal'
610
checkGroup( NULL, true ); // force, because transiency has changed
612
workspace()->updateCurrentTopMenu();
613
workspace()->updateClientLayer( this );
617
void Client::removeFromMainClients()
619
TRANSIENCY_CHECK( this );
620
if( transientFor() != NULL )
621
transientFor()->removeTransient( this );
622
if( groupTransient())
624
for( ClientList::ConstIterator it = group()->members().begin();
625
it != group()->members().end();
627
(*it)->removeTransient( this );
631
// *sigh* this transiency handling is madness :(
632
// This one is called when destroying/releasing a window.
633
// It makes sure this client is removed from all grouping
635
void Client::cleanGrouping()
637
TRANSIENCY_CHECK( this );
638
// kdDebug() << "CLEANGROUPING:" << this << endl;
639
// for( ClientList::ConstIterator it = group()->members().begin();
640
// it != group()->members().end();
642
// kdDebug() << "CL:" << *it << endl;
644
// mains = mainClients();
645
// for( ClientList::ConstIterator it = mains.begin();
646
// it != mains.end();
648
// kdDebug() << "MN:" << *it << endl;
649
removeFromMainClients();
650
// kdDebug() << "CLEANGROUPING2:" << this << endl;
651
// for( ClientList::ConstIterator it = group()->members().begin();
652
// it != group()->members().end();
654
// kdDebug() << "CL2:" << *it << endl;
655
// mains = mainClients();
656
// for( ClientList::ConstIterator it = mains.begin();
657
// it != mains.end();
659
// kdDebug() << "MN2:" << *it << endl;
660
for( ClientList::ConstIterator it = transients_list.begin();
661
it != transients_list.end();
664
if( (*it)->transientFor() == this )
666
ClientList::ConstIterator it2 = it++;
667
removeTransient( *it2 );
672
// kdDebug() << "CLEANGROUPING3:" << this << endl;
673
// for( ClientList::ConstIterator it = group()->members().begin();
674
// it != group()->members().end();
676
// kdDebug() << "CL3:" << *it << endl;
677
// mains = mainClients();
678
// for( ClientList::ConstIterator it = mains.begin();
679
// it != mains.end();
681
// kdDebug() << "MN3:" << *it << endl;
683
// removeFromMainClients() did remove 'this' from transient
684
// lists of all group members, but then made windows that
685
// were transient for 'this' group transient, which again
686
// added 'this' to those transient lists :(
687
ClientList group_members = group()->members();
688
group()->removeMember( this );
690
for( ClientList::ConstIterator it = group_members.begin();
691
it != group_members.end();
693
(*it)->removeTransient( this );
694
// kdDebug() << "CLEANGROUPING4:" << this << endl;
695
// for( ClientList::ConstIterator it = group_members.begin();
696
// it != group_members.end();
698
// kdDebug() << "CL4:" << *it << endl;
701
// Make sure that no group transient is considered transient
702
// for a window that is (directly or indirectly) transient for it
703
// (including another group transients).
704
// Non-group transients not causing loops are checked in verifyTransientFor().
705
void Client::checkGroupTransients()
707
TRANSIENCY_CHECK( this );
708
for( ClientList::ConstIterator it1 = group()->members().begin();
709
it1 != group()->members().end();
712
if( !(*it1)->groupTransient()) // check all group transients in the group
713
continue; // TODO optimize to check only the changed ones?
714
for( ClientList::ConstIterator it2 = group()->members().begin();
715
it2 != group()->members().end();
716
++it2 ) // group transients can be transient only for others in the group,
717
{ // so don't make them transient for the ones that are transient for it
720
for( Client* cl = (*it2)->transientFor();
722
cl = cl->transientFor())
725
{ // don't use removeTransient(), that would modify *it2 too
726
(*it2)->transients_list.remove( *it1 );
730
// if *it1 and *it2 are both group transients, and are transient for each other,
731
// make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later,
732
// and should be therefore on top of *it1
733
// TODO This could possibly be optimized, it also requires hasTransient() to check for loops.
734
if( (*it2)->groupTransient() && (*it1)->hasTransient( *it2, true ) && (*it2)->hasTransient( *it1, true ))
735
(*it2)->transients_list.remove( *it1 );
736
// if there are already windows W1 and W2, W2 being transient for W1, and group transient W3
737
// is added, make it transient only for W2, not for W1, because it's already indirectly
738
// transient for it - the indirect transiency actually shouldn't break anything,
739
// but it can lead to exponentially expensive operations (#95231)
740
// TODO this is pretty slow as well
741
for( ClientList::ConstIterator it3 = group()->members().begin();
742
it3 != group()->members().end();
745
if( *it1 == *it2 || *it2 == *it3 || *it1 == *it3 )
747
if( (*it2)->hasTransient( *it1, false ) && (*it3)->hasTransient( *it1, false ))
749
if( (*it2)->hasTransient( *it3, true ))
750
(*it3)->transients_list.remove( *it1 );
751
if( (*it3)->hasTransient( *it2, true ))
752
(*it2)->transients_list.remove( *it1 );
760
Check that the window is not transient for itself, and similar nonsense.
762
Window Client::verifyTransientFor( Window new_transient_for, bool defined )
764
Window new_property_value = new_transient_for;
765
// make sure splashscreens are shown above all their app's windows, even though
766
// they're in Normal layer
767
if( isSplash() && new_transient_for == None )
768
new_transient_for = workspace()->rootWin();
769
if( new_transient_for == None )
770
if( defined ) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window
771
new_property_value = new_transient_for = workspace()->rootWin();
774
if( new_transient_for == window()) // pointing to self
775
{ // also fix the property itself
776
kdWarning( 1216 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." << endl;
777
new_property_value = new_transient_for = workspace()->rootWin();
779
// The transient_for window may be embedded in another application,
780
// so kwin cannot see it. Try to find the managed client for the
781
// window and fix the transient_for property if possible.
782
WId before_search = new_transient_for;
783
while( new_transient_for != None
784
&& new_transient_for != workspace()->rootWin()
785
&& !workspace()->findClient( WindowMatchPredicate( new_transient_for )))
787
Window root_return, parent_return;
790
int r = XQueryTree(qt_xdisplay(), new_transient_for, &root_return, &parent_return, &wins, &nwins);
792
XFree((void *) wins);
795
new_transient_for = parent_return;
797
if( Client* new_transient_for_client = workspace()->findClient( WindowMatchPredicate( new_transient_for )))
799
if( new_transient_for != before_search )
801
kdDebug( 1212 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window "
802
<< before_search << ", child of " << new_transient_for_client << ", adjusting." << endl;
803
new_property_value = new_transient_for; // also fix the property
807
new_transient_for = before_search; // nice try
809
// group transients cannot cause loops, because they're considered transient only for non-transient
810
// windows in the group
812
Window loop_pos = new_transient_for;
813
while( loop_pos != None && loop_pos != workspace()->rootWin())
815
Client* pos = workspace()->findClient( WindowMatchPredicate( loop_pos ));
818
loop_pos = pos->transient_for_id;
819
if( --count == 0 || pos == this )
821
kdWarning( 1216 ) << "Client " << this << " caused WM_TRANSIENT_FOR loop." << endl;
822
new_transient_for = workspace()->rootWin();
825
if( new_transient_for != workspace()->rootWin()
826
&& workspace()->findClient( WindowMatchPredicate( new_transient_for )) == NULL )
827
{ // it's transient for a specific window, but that window is not mapped
828
new_transient_for = workspace()->rootWin();
830
if( new_property_value != original_transient_for_id )
831
XSetTransientForHint( qt_xdisplay(), window(), new_property_value );
832
return new_transient_for;
835
void Client::addTransient( Client* cl )
837
TRANSIENCY_CHECK( this );
838
assert( !transients_list.contains( cl ));
839
// assert( !cl->hasTransient( this, true )); will be fixed in checkGroupTransients()
840
assert( cl != this );
841
transients_list.append( cl );
842
if( workspace()->mostRecentlyActivatedClient() == this && cl->isModal())
843
check_active_modal = true;
844
// kdDebug() << "ADDTRANS:" << this << ":" << cl << endl;
845
// kdDebug() << kdBacktrace() << endl;
846
// for( ClientList::ConstIterator it = transients_list.begin();
847
// it != transients_list.end();
849
// kdDebug() << "AT:" << (*it) << endl;
852
void Client::removeTransient( Client* cl )
854
TRANSIENCY_CHECK( this );
855
// kdDebug() << "REMOVETRANS:" << this << ":" << cl << endl;
856
// kdDebug() << kdBacktrace() << endl;
857
transients_list.remove( cl );
858
// cl is transient for this, but this is going away
859
// make cl group transient
860
if( cl->transientFor() == this )
862
cl->transient_for_id = None;
863
cl->transient_for = NULL; // SELI
864
// SELI cl->setTransient( workspace()->rootWin());
865
cl->setTransient( None );
869
// A new window has been mapped. Check if it's not a mainwindow for this already existing window.
870
void Client::checkTransient( Window w )
872
TRANSIENCY_CHECK( this );
873
if( original_transient_for_id != w )
875
w = verifyTransientFor( w, true );
879
// returns true if cl is the transient_for window for this client,
880
// or recursively the transient_for window
881
bool Client::hasTransient( const Client* cl, bool indirect ) const
883
// checkGroupTransients() uses this to break loops, so hasTransient() must detect them
885
return hasTransientInternal( cl, indirect, set );
888
bool Client::hasTransientInternal( const Client* cl, bool indirect, ConstClientList& set ) const
890
if( cl->transientFor() != NULL )
892
if( cl->transientFor() == this )
896
if( set.contains( cl ))
899
return hasTransientInternal( cl->transientFor(), indirect, set );
901
if( !cl->isTransient())
903
if( group() != cl->group())
905
// cl is group transient, search from top
906
if( transients().contains( const_cast< Client* >( cl )))
910
if( set.contains( this ))
913
for( ClientList::ConstIterator it = transients().begin();
914
it != transients().end();
916
if( (*it)->hasTransientInternal( cl, indirect, set ))
921
ClientList Client::mainClients() const
925
if( transientFor() != NULL )
926
return ClientList() << const_cast< Client* >( transientFor());
928
for( ClientList::ConstIterator it = group()->members().begin();
929
it != group()->members().end();
931
if((*it)->hasTransient( this, false ))
932
result.append( *it );
936
Client* Client::findModal()
938
for( ClientList::ConstIterator it = transients().begin();
939
it != transients().end();
941
if( Client* ret = (*it)->findModal())
948
// Client::window_group only holds the contents of the hint,
949
// but it should be used only to find the group, not for anything else
950
// Argument is only when some specific group needs to be set.
951
void Client::checkGroup( Group* set_group, bool force )
953
TRANSIENCY_CHECK( this );
954
Group* old_group = in_group;
955
if( old_group != NULL )
956
old_group->ref(); // turn off automatic deleting
957
if( set_group != NULL )
959
if( set_group != in_group )
961
if( in_group != NULL )
962
in_group->removeMember( this );
963
in_group = set_group;
964
in_group->addMember( this );
967
else if( window_group != None )
969
Group* new_group = workspace()->findGroup( window_group );
970
if( transientFor() != NULL && transientFor()->group() != new_group )
971
{ // move the window to the right group (e.g. a dialog provided
972
// by different app, but transient for this one, so make it part of that group)
973
new_group = transientFor()->group();
975
if( new_group == NULL ) // doesn't exist yet
976
new_group = new Group( window_group, workspace());
977
if( new_group != in_group )
979
if( in_group != NULL )
980
in_group->removeMember( this );
981
in_group = new_group;
982
in_group->addMember( this );
987
if( transientFor() != NULL )
988
{ // doesn't have window group set, but is transient for something
989
// so make it part of that group
990
Group* new_group = transientFor()->group();
991
if( new_group != in_group )
993
if( in_group != NULL )
994
in_group->removeMember( this );
995
in_group = transientFor()->group();
996
in_group->addMember( this );
999
else if( groupTransient())
1000
{ // group transient which actually doesn't have a group :(
1001
// try creating group with other windows with the same client leader
1002
Group* new_group = workspace()->findClientLeaderGroup( this );
1003
if( new_group == NULL )
1004
new_group = new Group( None, workspace());
1005
if( new_group != in_group )
1007
if( in_group != NULL )
1008
in_group->removeMember( this );
1009
in_group = new_group;
1010
in_group->addMember( this );
1013
else // Not transient without a group, put it in its client leader group.
1014
{ // This might be stupid if grouping was used for e.g. taskbar grouping
1015
// or minimizing together the whole group, but as long as its used
1016
// only for dialogs it's better to keep windows from one app in one group.
1017
Group* new_group = workspace()->findClientLeaderGroup( this );
1018
if( in_group != NULL && in_group != new_group )
1020
in_group->removeMember( this );
1023
if( new_group == NULL )
1024
new_group = new Group( None, workspace() );
1025
if( in_group != new_group )
1027
in_group = new_group;
1028
in_group->addMember( this );
1032
if( in_group != old_group || force )
1034
for( ClientList::Iterator it = transients_list.begin();
1035
it != transients_list.end();
1037
{ // group transients in the old group are no longer transient for it
1038
if( (*it)->groupTransient() && (*it)->group() != group())
1039
it = transients_list.remove( it );
1043
if( groupTransient())
1045
// no longer transient for ones in the old group
1046
if( old_group != NULL )
1048
for( ClientList::ConstIterator it = old_group->members().begin();
1049
it != old_group->members().end();
1051
(*it)->removeTransient( this );
1053
// and make transient for all in the new group
1054
for( ClientList::ConstIterator it = group()->members().begin();
1055
it != group()->members().end();
1059
break; // this means the window is only transient for windows mapped before it
1060
(*it)->addTransient( this );
1063
// group transient splashscreens should be transient even for windows
1064
// in group mapped later
1065
for( ClientList::ConstIterator it = group()->members().begin();
1066
it != group()->members().end();
1069
if( !(*it)->isSplash())
1071
if( !(*it)->groupTransient())
1073
if( *it == this || hasTransient( *it, true )) // TODO indirect?
1075
addTransient( *it );
1078
if( old_group != NULL )
1079
old_group->deref(); // can be now deleted if empty
1080
checkGroupTransients();
1082
workspace()->updateClientLayer( this );
1085
// used by Workspace::findClientLeaderGroup()
1086
void Client::changeClientLeaderGroup( Group* gr )
1088
// transientFor() != NULL are in the group of their mainwindow, so keep them there
1089
if( transientFor() != NULL )
1091
// also don't change the group for window which have group set
1094
checkGroup( gr ); // change group
1097
bool Client::check_active_modal = false;
1099
void Client::checkActiveModal()
1101
// if the active window got new modal transient, activate it.
1102
// cannot be done in AddTransient(), because there may temporarily
1103
// exist loops, breaking findModal
1104
Client* check_modal = workspace()->mostRecentlyActivatedClient();
1105
if( check_modal != NULL && check_modal->check_active_modal )
1107
Client* new_modal = check_modal->findModal();
1108
if( new_modal != NULL && new_modal != check_modal )
1110
if( !new_modal->isManaged())
1111
return; // postpone check until end of manage()
1112
workspace()->activateClient( new_modal );
1114
check_modal->check_active_modal = false;