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
31
#include <QTextStream>
32
#include "workspace.h"
37
#include <kstartupinfo.h>
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.
51
Consistency checks for window relations. Since transients are determined
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
58
#undef ENABLE_TRANSIENCY_CHECK
61
#ifdef ENABLE_TRANSIENCY_CHECK
62
static bool transiencyCheckNonExistent = false;
64
bool performTransiencyCheck()
67
ClientList clients = Workspace::self()->clients;
68
for (ClientList::ConstIterator it1 = clients.constBegin();
69
it1 != clients.constEnd();
73
if ((*it1)->in_group == NULL) {
74
kDebug(1212) << "TC: " << *it1 << " in not in a group" << endl;
76
} else if (!(*it1)->in_group->members().contains(*it1)) {
77
kDebug(1212) << "TC: " << *it1 << " has a group " << (*it1)->in_group << " but group does not contain it" << endl;
80
if (!(*it1)->isTransient()) {
81
if (!(*it1)->mainClients().isEmpty()) {
82
kDebug(1212) << "TC: " << *it1 << " is not transient, has main clients:" << (*it1)->mainClients() << endl;
86
ClientList mains = (*it1)->mainClients();
87
for (ClientList::ConstIterator it2 = mains.constBegin();
88
it2 != mains.constEnd();
90
if (transiencyCheckNonExistent
91
&& !Workspace::self()->clients.contains(*it2)
92
&& !Workspace::self()->desktops.contains(*it2)) {
93
kDebug(1212) << "TC:" << *it1 << " has non-existent main client ";
94
kDebug(1212) << "TC2:" << *it2; // this may crash
98
if (!(*it2)->transients_list.contains(*it1)) {
99
kdDebug(1212) << "TC:" << *it1 << " has main client " << *it2 << " but main client does not have it as a transient" << endl;
104
ClientList trans = (*it1)->transients_list;
105
for (ClientList::ConstIterator it2 = trans.constBegin();
106
it2 != trans.constEnd();
108
if (transiencyCheckNonExistent
109
&& !Workspace::self()->clients.contains(*it2)
110
&& !Workspace::self()->desktops.contains(*it2)) {
111
kDebug(1212) << "TC:" << *it1 << " has non-existent transient ";
112
kDebug(1212) << "TC2:" << *it2; // this may crash
116
if (!(*it2)->mainClients().contains(*it1)) {
117
kdDebug(1212) << "TC:" << *it1 << " has transient " << *it2 << " but transient does not have it as a main client" << endl;
122
GroupList groups = Workspace::self()->groups;
123
for (GroupList::ConstIterator it1 = groups.constBegin();
124
it1 != groups.constEnd();
126
ClientList members = (*it1)->members();
127
for (ClientList::ConstIterator it2 = members.constBegin();
128
it2 != members.constEnd();
130
if ((*it2)->in_group != *it1) {
131
kDebug(1212) << "TC: Group " << *it1 << " contains client " << *it2 << " but client is not in that group" << endl;
139
static QString transiencyCheckStartBt;
140
static const Client* transiencyCheckClient;
141
static int transiencyCheck = 0;
143
static void startTransiencyCheck(const QString& bt, const Client* c, bool ne)
145
if (++transiencyCheck == 1) {
146
transiencyCheckStartBt = bt;
147
transiencyCheckClient = c;
150
transiencyCheckNonExistent = true;
152
static void checkTransiency()
154
if (--transiencyCheck == 0) {
155
if (!performTransiencyCheck()) {
156
kDebug(1212) << "BT:" << transiencyCheckStartBt << endl;
157
kDebug(1212) << "CLIENT:" << transiencyCheckClient << endl;
160
transiencyCheckNonExistent = false;
163
class TransiencyChecker
166
TransiencyChecker(const QString& bt, const Client*c) {
167
startTransiencyCheck(bt, c, false);
169
~TransiencyChecker() {
174
void checkNonExistentClients()
176
startTransiencyCheck(kdBacktrace(), NULL, true);
180
#define TRANSIENCY_CHECK( c ) TransiencyChecker transiency_checker( kdBacktrace(), c )
184
#define TRANSIENCY_CHECK( c )
186
void checkNonExistentClients()
192
//********************************************
194
//********************************************
196
Group::Group(Window leader_P, Workspace* workspace_P)
197
: leader_client(NULL),
198
leader_wid(leader_P),
199
_workspace(workspace_P),
204
if (leader_P != None) {
205
leader_client = workspace_P->findClient(WindowMatchPredicate(leader_P));
206
unsigned long properties[ 2 ] = { 0, NET::WM2StartupId };
207
leader_info = new NETWinInfo2(display(), leader_P, rootWindow(),
210
effect_group = new EffectWindowGroupImpl(this);
211
workspace()->addGroup(this, Allowed);
220
QPixmap Group::icon() const
222
if (leader_client != NULL)
223
return leader_client->icon();
224
else if (leader_wid != None) {
226
Client::readIcons(leader_wid, &ic, NULL, NULL, NULL);
232
QPixmap Group::miniIcon() const
234
if (leader_client != NULL)
235
return leader_client->miniIcon();
236
else if (leader_wid != None) {
238
Client::readIcons(leader_wid, NULL, &ic, NULL, NULL);
244
QPixmap Group::bigIcon() const
246
if (leader_client != NULL)
247
return leader_client->bigIcon();
248
else if (leader_wid != None) {
250
Client::readIcons(leader_wid, NULL, NULL, &ic, NULL);
256
QPixmap Group::hugeIcon() const
258
if (leader_client != NULL)
259
return leader_client->hugeIcon();
260
else if (leader_wid != None) {
262
Client::readIcons(leader_wid, NULL, NULL, NULL, &ic);
268
void Group::addMember(Client* member_P)
270
TRANSIENCY_CHECK(member_P);
271
_members.append(member_P);
272
// kDebug(1212) << "GROUPADD:" << this << ":" << member_P;
273
// kDebug(1212) << kBacktrace();
276
void Group::removeMember(Client* member_P)
278
TRANSIENCY_CHECK(member_P);
279
// kDebug(1212) << "GROUPREMOVE:" << this << ":" << member_P;
280
// kDebug(1212) << kBacktrace();
281
Q_ASSERT(_members.contains(member_P));
282
_members.removeAll(member_P);
283
// there are cases when automatic deleting of groups must be delayed,
284
// e.g. when removing a member and doing some operation on the possibly
285
// other members of the group (which would be however deleted already
286
// if there were no other members)
287
if (refcount == 0 && _members.isEmpty()) {
288
workspace()->removeGroup(this, Allowed);
300
if (--refcount == 0 && _members.isEmpty()) {
301
workspace()->removeGroup(this, Allowed);
306
void Group::gotLeader(Client* leader_P)
308
assert(leader_P->window() == leader_wid);
309
leader_client = leader_P;
312
void Group::lostLeader()
314
assert(!_members.contains(leader_client));
315
leader_client = NULL;
316
if (_members.isEmpty()) {
317
workspace()->removeGroup(this, Allowed);
322
void Group::getIcons()
324
// TODO - also needs adding the flag to NETWinInfo
327
//***************************************
329
//***************************************
331
Group* Workspace::findGroup(Window leader) const
333
assert(leader != None);
334
for (GroupList::ConstIterator it = groups.constBegin();
335
it != groups.constEnd();
337
if ((*it)->leader() == leader)
342
// Client is group transient, but has no group set. Try to find
343
// group with windows with the same client leader.
344
Group* Workspace::findClientLeaderGroup(const Client* c) const
348
for (ClientList::ConstIterator it = clients.constBegin();
349
it != clients.constEnd();
353
if ((*it)->wmClientLeader() == c->wmClientLeader()) {
354
if (ret == NULL || ret == (*it)->group())
355
ret = (*it)->group();
357
// There are already two groups with the same client leader.
358
// This most probably means the app uses group transients without
359
// setting group for its windows. Merging the two groups is a bad
360
// hack, but there's no really good solution for this case.
361
ClientList old_group = (*it)->group()->members();
362
// old_group autodeletes when being empty
364
pos < old_group.count();
366
Client* tmp = old_group[ pos ];
368
tmp->changeClientLeaderGroup(ret);
376
void Workspace::updateMinimizedOfTransients(Client* c)
378
// if mainwindow is minimized or shaded, minimize transients too
379
if (c->isMinimized()) {
380
for (ClientList::ConstIterator it = c->transients().constBegin();
381
it != c->transients().constEnd();
383
if ((*it)->isModal())
384
continue; // there's no reason to hide modal dialogs with the main client
385
// but to keep them to eg. watch progress or whatever
386
if (!(*it)->isMinimized()
387
&& !(*it)->isTopMenu()) { // topmenus are not minimized, they're hidden
389
updateMinimizedOfTransients((*it));
392
if (c->isModal()) { // if a modal dialog is minimized, minimize its mainwindow too
393
foreach (Client * c2, c->mainClients())
397
// else unmiminize the transients
398
for (ClientList::ConstIterator it = c->transients().constBegin();
399
it != c->transients().constEnd();
401
if ((*it)->isMinimized()
402
&& !(*it)->isTopMenu()) {
404
updateMinimizedOfTransients((*it));
408
foreach (Client * c2, c->mainClients())
416
Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops.
418
void Workspace::updateOnAllDesktopsOfTransients(Client* c)
420
for (ClientList::ConstIterator it = c->transients().constBegin();
421
it != c->transients().constEnd();
423
if ((*it)->isOnAllDesktops() != c->isOnAllDesktops())
424
(*it)->setOnAllDesktops(c->isOnAllDesktops());
429
Sets the client \a c's transient windows' on_all_activities property to \a on_all_desktops.
431
void Workspace::updateOnAllActivitiesOfTransients(Client* c)
433
for (ClientList::ConstIterator it = c->transients().constBegin();
434
it != c->transients().constEnd();
436
if ((*it)->isOnAllActivities() != c->isOnAllActivities())
437
(*it)->setOnAllActivities(c->isOnAllActivities());
441
// A new window has been mapped. Check if it's not a mainwindow for some already existing transient window.
442
void Workspace::checkTransients(Window w)
444
TRANSIENCY_CHECK(NULL);
445
for (ClientList::ConstIterator it = clients.constBegin();
446
it != clients.constEnd();
448
(*it)->checkTransient(w);
452
//****************************************
454
//****************************************
456
// hacks for broken apps here
457
// all resource classes are forced to be lowercase
458
bool Toplevel::resourceMatch(const Toplevel* c1, const Toplevel* c2)
460
// xv has "xv" as resource name, and different strings starting with "XV" as resource class
461
if (qstrncmp(c1->resourceClass(), "xv", 2) == 0 && c1->resourceName() == "xv")
462
return qstrncmp(c2->resourceClass(), "xv", 2) == 0 && c2->resourceName() == "xv";
463
// Mozilla has "Mozilla" as resource name, and different strings as resource class
464
if (c1->resourceName() == "mozilla")
465
return c2->resourceName() == "mozilla";
466
return c1->resourceClass() == c2->resourceClass();
470
//****************************************
472
//****************************************
474
bool Client::belongToSameApplication(const Client* c1, const Client* c2, bool active_hack)
476
bool same_app = false;
478
// tests that definitely mean they belong together
481
else if (c1->isTransient() && c2->hasTransient(c1, true))
482
same_app = true; // c1 has c2 as mainwindow
483
else if (c2->isTransient() && c1->hasTransient(c2, true))
484
same_app = true; // c2 has c1 as mainwindow
485
else if (c1->group() == c2->group())
486
same_app = true; // same group
487
else if (c1->wmClientLeader() == c2->wmClientLeader()
488
&& c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
489
&& c2->wmClientLeader() != c2->window()) // don't use in this test then
490
same_app = true; // same client leader
492
// tests that mean they most probably don't belong together
493
else if (c1->pid() != c2->pid()
494
|| c1->wmClientMachine(false) != c2->wmClientMachine(false))
495
; // different processes
496
else if (c1->wmClientLeader() != c2->wmClientLeader()
497
&& c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
498
&& c2->wmClientLeader() != c2->window()) // don't use in this test then
499
; // different client leader
500
else if (!resourceMatch(c1, c2))
502
else if (!sameAppWindowRoleMatch(c1, c2, active_hack))
503
; // "different" apps
504
else if (c1->pid() == 0 || c2->pid() == 0)
505
; // old apps that don't have _NET_WM_PID, consider them different
506
// if they weren't found to match above
508
same_app = true; // looks like it's the same app
513
// Non-transient windows with window role containing '#' are always
514
// considered belonging to different applications (unless
515
// the window role is exactly the same). KMainWindow sets
516
// window role this way by default, and different KMainWindow
517
// usually "are" different application from user's point of view.
518
// This help with no-focus-stealing for e.g. konqy reusing.
519
// On the other hand, if one of the windows is active, they are
520
// considered belonging to the same application. This is for
521
// the cases when opening new mainwindow directly from the application,
522
// e.g. 'Open New Window' in konqy ( active_hack == true ).
523
bool Client::sameAppWindowRoleMatch(const Client* c1, const Client* c2, bool active_hack)
525
if (c1->isTransient()) {
526
while (c1->transientFor() != NULL)
527
c1 = c1->transientFor();
528
if (c1->groupTransient())
529
return c1->group() == c2->group();
531
// if a group transient is in its own group, it didn't possibly have a group,
532
// and therefore should be considered belonging to the same app like
533
// all other windows from the same app
534
|| c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
537
if (c2->isTransient()) {
538
while (c2->transientFor() != NULL)
539
c2 = c2->transientFor();
540
if (c2->groupTransient())
541
return c1->group() == c2->group();
543
|| c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
546
int pos1 = c1->windowRole().indexOf('#');
547
int pos2 = c2->windowRole().indexOf('#');
548
if ((pos1 >= 0 && pos2 >= 0)
551
// Mozilla has resourceName() and resourceClass() swapped
552
(c1->resourceName() == "mozilla" && c2->resourceName() == "mozilla")) {
553
if (!active_hack) // without the active hack for focus stealing prevention,
554
return c1 == c2; // different mainwindows are always different apps
555
if (!c1->isActive() && !c2->isActive())
565
Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3
567
WM_TRANSIENT_FOR is basically means "this is my mainwindow".
568
For NET::Unknown windows, transient windows are considered to be NET::Dialog
569
windows, for compatibility with non-NETWM clients. KWin may adjust the value
570
of this property in some cases (window pointing to itself or creating a loop,
571
keeping NET::Splash windows above other windows from the same app, etc.).
573
Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after
574
possibly being adjusted by KWin. Client::transient_for points to the Client
575
this Client is transient for, or is NULL. If Client::transient_for_id is
576
poiting to the root window, the window is considered to be transient
577
for the whole window group, as suggested in NETWM 7.3.
579
In the case of group transient window, Client::transient_for is NULL,
580
and Client::groupTransient() returns true. Such window is treated as
581
if it were transient for every window in its window group that has been
582
mapped _before_ it (or, to be exact, was added to the same group before it).
583
Otherwise two group transients can create loops, which can lead very very
584
nasty things (bug #67914 and all its dupes).
586
Client::original_transient_for_id is the value of the property, which
587
may be different if Client::transient_for_id if e.g. forcing NET::Splash
588
to be kept on top of its window group, or when the mainwindow is not mapped
589
yet, in which case the window is temporarily made group transient,
590
and when the mainwindow is mapped, transiency is re-evaluated.
592
This can get a bit complicated with with e.g. two Konqueror windows created
593
by the same process. They should ideally appear like two independent applications
594
to the user. This should be accomplished by all windows in the same process
595
having the same window group (needs to be changed in Qt at the moment), and
596
using non-group transients poiting to their relevant mainwindow for toolwindows
597
etc. KWin should handle both group and non-group transient dialogs well.
600
- non-transient windows : isTransient() == false
601
- normal transients : transientFor() != NULL
602
- group transients : groupTransient() == true
604
- list of mainwindows : mainClients() (call once and loop over the result)
605
- list of transients : transients()
606
- every window in the group : group()->members()
609
void Client::readTransient()
611
TRANSIENCY_CHECK(this);
612
Window new_transient_for_id;
613
if (XGetTransientForHint(display(), window(), &new_transient_for_id)) {
614
original_transient_for_id = new_transient_for_id;
615
new_transient_for_id = verifyTransientFor(new_transient_for_id, true);
617
original_transient_for_id = None;
618
new_transient_for_id = verifyTransientFor(None, false);
620
setTransient(new_transient_for_id);
623
void Client::setTransient(Window new_transient_for_id)
625
TRANSIENCY_CHECK(this);
626
if (new_transient_for_id != transient_for_id) {
627
removeFromMainClients();
628
transient_for = NULL;
629
transient_for_id = new_transient_for_id;
630
if (transient_for_id != None && !groupTransient()) {
631
transient_for = workspace()->findClient(WindowMatchPredicate(transient_for_id));
632
assert(transient_for != NULL); // verifyTransient() had to check this
633
transient_for->addTransient(this);
634
} // checkGroup() will check 'check_active_modal'
635
checkGroup(NULL, true); // force, because transiency has changed
637
workspace()->updateCurrentTopMenu();
638
workspace()->updateClientLayer(this);
639
workspace()->resetUpdateToolWindowsTimer();
643
void Client::removeFromMainClients()
645
TRANSIENCY_CHECK(this);
646
if (transientFor() != NULL)
647
transientFor()->removeTransient(this);
648
if (groupTransient()) {
649
for (ClientList::ConstIterator it = group()->members().constBegin();
650
it != group()->members().constEnd();
652
(*it)->removeTransient(this);
656
// *sigh* this transiency handling is madness :(
657
// This one is called when destroying/releasing a window.
658
// It makes sure this client is removed from all grouping
660
void Client::cleanGrouping()
662
TRANSIENCY_CHECK(this);
663
// kDebug(1212) << "CLEANGROUPING:" << this;
664
// for ( ClientList::ConstIterator it = group()->members().begin();
665
// it != group()->members().end();
667
// kDebug(1212) << "CL:" << *it;
669
// mains = mainClients();
670
// for ( ClientList::ConstIterator it = mains.begin();
671
// it != mains.end();
673
// kDebug(1212) << "MN:" << *it;
674
removeFromMainClients();
675
// kDebug(1212) << "CLEANGROUPING2:" << this;
676
// for ( ClientList::ConstIterator it = group()->members().begin();
677
// it != group()->members().end();
679
// kDebug(1212) << "CL2:" << *it;
680
// mains = mainClients();
681
// for ( ClientList::ConstIterator it = mains.begin();
682
// it != mains.end();
684
// kDebug(1212) << "MN2:" << *it;
685
for (ClientList::ConstIterator it = transients_list.constBegin();
686
it != transients_list.constEnd();
688
if ((*it)->transientFor() == this) {
689
removeTransient(*it);
690
it = transients_list.constBegin(); // restart, just in case something more has changed with the list
694
// kDebug(1212) << "CLEANGROUPING3:" << this;
695
// for ( ClientList::ConstIterator it = group()->members().begin();
696
// it != group()->members().end();
698
// kDebug(1212) << "CL3:" << *it;
699
// mains = mainClients();
700
// for ( ClientList::ConstIterator it = mains.begin();
701
// it != mains.end();
703
// kDebug(1212) << "MN3:" << *it;
705
// removeFromMainClients() did remove 'this' from transient
706
// lists of all group members, but then made windows that
707
// were transient for 'this' group transient, which again
708
// added 'this' to those transient lists :(
709
ClientList group_members = group()->members();
710
group()->removeMember(this);
712
for (ClientList::ConstIterator it = group_members.constBegin();
713
it != group_members.constEnd();
715
(*it)->removeTransient(this);
716
// kDebug(1212) << "CLEANGROUPING4:" << this;
717
// for ( ClientList::ConstIterator it = group_members.begin();
718
// it != group_members.end();
720
// kDebug(1212) << "CL4:" << *it;
723
// Make sure that no group transient is considered transient
724
// for a window that is (directly or indirectly) transient for it
725
// (including another group transients).
726
// Non-group transients not causing loops are checked in verifyTransientFor().
727
void Client::checkGroupTransients()
729
TRANSIENCY_CHECK(this);
730
for (ClientList::ConstIterator it1 = group()->members().constBegin();
731
it1 != group()->members().constEnd();
733
if (!(*it1)->groupTransient()) // check all group transients in the group
734
continue; // TODO optimize to check only the changed ones?
735
for (ClientList::ConstIterator it2 = group()->members().constBegin();
736
it2 != group()->members().constEnd();
737
++it2) { // group transients can be transient only for others in the group,
738
// so don't make them transient for the ones that are transient for it
741
for (Client* cl = (*it2)->transientFor();
743
cl = cl->transientFor()) {
745
// don't use removeTransient(), that would modify *it2 too
746
(*it2)->transients_list.removeAll(*it1);
750
// if *it1 and *it2 are both group transients, and are transient for each other,
751
// make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later,
752
// and should be therefore on top of *it1
753
// TODO This could possibly be optimized, it also requires hasTransient() to check for loops.
754
if ((*it2)->groupTransient() && (*it1)->hasTransient(*it2, true) && (*it2)->hasTransient(*it1, true))
755
(*it2)->transients_list.removeAll(*it1);
756
// if there are already windows W1 and W2, W2 being transient for W1, and group transient W3
757
// is added, make it transient only for W2, not for W1, because it's already indirectly
758
// transient for it - the indirect transiency actually shouldn't break anything,
759
// but it can lead to exponentially expensive operations (#95231)
760
// TODO this is pretty slow as well
761
for (ClientList::ConstIterator it3 = group()->members().constBegin();
762
it3 != group()->members().constEnd();
764
if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3)
766
if ((*it2)->hasTransient(*it1, false) && (*it3)->hasTransient(*it1, false)) {
767
if ((*it2)->hasTransient(*it3, true))
768
(*it2)->transients_list.removeAll(*it1);
769
if ((*it3)->hasTransient(*it2, true))
770
(*it3)->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();
793
if (new_transient_for == window()) { // pointing to self
794
// also fix the property itself
795
kWarning(1216) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." ;
796
new_property_value = new_transient_for = rootWindow();
798
// The transient_for window may be embedded in another application,
799
// so kwin cannot see it. Try to find the managed client for the
800
// window and fix the transient_for property if possible.
801
WId before_search = new_transient_for;
802
while (new_transient_for != None
803
&& new_transient_for != rootWindow()
804
&& !workspace()->findClient(WindowMatchPredicate(new_transient_for))) {
805
Window root_return, parent_return;
808
int r = XQueryTree(display(), new_transient_for, &root_return, &parent_return, &wins, &nwins);
810
XFree((void *) wins);
813
new_transient_for = parent_return;
815
if (Client* new_transient_for_client = workspace()->findClient(WindowMatchPredicate(new_transient_for))) {
816
if (new_transient_for != before_search) {
817
kDebug(1212) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window "
818
<< before_search << ", child of " << new_transient_for_client << ", adjusting." << endl;
819
new_property_value = new_transient_for; // also fix the property
822
new_transient_for = before_search; // nice try
824
// group transients cannot cause loops, because they're considered transient only for non-transient
825
// windows in the group
827
Window loop_pos = new_transient_for;
828
while (loop_pos != None && loop_pos != rootWindow()) {
829
Client* pos = workspace()->findClient(WindowMatchPredicate(loop_pos));
832
loop_pos = pos->transient_for_id;
833
if (--count == 0 || pos == this) {
834
kWarning(1216) << "Client " << this << " caused WM_TRANSIENT_FOR loop." ;
835
new_transient_for = rootWindow();
838
if (new_transient_for != rootWindow()
839
&& workspace()->findClient(WindowMatchPredicate(new_transient_for)) == NULL) {
840
// it's transient for a specific window, but that window is not mapped
841
new_transient_for = rootWindow();
843
if (new_property_value != original_transient_for_id)
844
XSetTransientForHint(display(), window(), new_property_value);
845
return new_transient_for;
848
void Client::addTransient(Client* cl)
850
TRANSIENCY_CHECK(this);
851
assert(!transients_list.contains(cl));
852
// assert( !cl->hasTransient( this, true )); will be fixed in checkGroupTransients()
854
transients_list.append(cl);
855
if (workspace()->mostRecentlyActivatedClient() == this && cl->isModal())
856
check_active_modal = true;
857
// kDebug(1212) << "ADDTRANS:" << this << ":" << cl;
858
// kDebug(1212) << kBacktrace();
859
// for ( ClientList::ConstIterator it = transients_list.begin();
860
// it != transients_list.end();
862
// kDebug(1212) << "AT:" << (*it);
865
void Client::removeTransient(Client* cl)
867
TRANSIENCY_CHECK(this);
868
// kDebug(1212) << "REMOVETRANS:" << this << ":" << cl;
869
// kDebug(1212) << kBacktrace();
870
transients_list.removeAll(cl);
871
// cl is transient for this, but this is going away
872
// make cl group transient
873
if (cl->transientFor() == this) {
874
cl->transient_for_id = None;
875
cl->transient_for = NULL; // SELI
876
// SELI cl->setTransient( rootWindow());
877
cl->setTransient(None);
881
// A new window has been mapped. Check if it's not a mainwindow for this already existing window.
882
void Client::checkTransient(Window w)
884
TRANSIENCY_CHECK(this);
885
if (original_transient_for_id != w)
887
w = verifyTransientFor(w, true);
891
// returns true if cl is the transient_for window for this client,
892
// or recursively the transient_for window
893
bool Client::hasTransient(const Client* cl, bool indirect) const
895
// checkGroupTransients() uses this to break loops, so hasTransient() must detect them
897
return hasTransientInternal(cl, indirect, set);
900
bool Client::hasTransientInternal(const Client* cl, bool indirect, ConstClientList& set) const
902
if (cl->transientFor() != NULL) {
903
if (cl->transientFor() == this)
907
if (set.contains(cl))
910
return hasTransientInternal(cl->transientFor(), indirect, set);
912
if (!cl->isTransient())
914
if (group() != cl->group())
916
// cl is group transient, search from top
917
if (transients().contains(const_cast< Client* >(cl)))
921
if (set.contains(this))
924
for (ClientList::ConstIterator it = transients().constBegin();
925
it != transients().constEnd();
927
if ((*it)->hasTransientInternal(cl, indirect, set))
932
ClientList Client::mainClients() const
936
if (transientFor() != NULL)
937
return ClientList() << const_cast< Client* >(transientFor());
939
for (ClientList::ConstIterator it = group()->members().constBegin();
940
it != group()->members().constEnd();
942
if ((*it)->hasTransient(this, false))
947
ClientList Client::allMainClients() const
949
ClientList result = mainClients();
950
foreach (const Client * cl, result)
951
result += cl->allMainClients();
955
Client* Client::findModal(bool allow_itself)
957
for (ClientList::ConstIterator it = transients().constBegin();
958
it != transients().constEnd();
960
if (Client* ret = (*it)->findModal(true))
962
if (isModal() && allow_itself)
967
// Client::window_group only holds the contents of the hint,
968
// but it should be used only to find the group, not for anything else
969
// Argument is only when some specific group needs to be set.
970
void Client::checkGroup(Group* set_group, bool force)
972
TRANSIENCY_CHECK(this);
973
Group* old_group = in_group;
974
if (old_group != NULL)
975
old_group->ref(); // turn off automatic deleting
976
if (set_group != NULL) {
977
if (set_group != in_group) {
978
if (in_group != NULL)
979
in_group->removeMember(this);
980
in_group = set_group;
981
in_group->addMember(this);
983
} else if (window_group != None) {
984
Group* new_group = workspace()->findGroup(window_group);
985
if (transientFor() != NULL && transientFor()->group() != new_group) {
986
// move the window to the right group (e.g. a dialog provided
987
// by different app, but transient for this one, so make it part of that group)
988
new_group = transientFor()->group();
990
if (new_group == NULL) // doesn't exist yet
991
new_group = new Group(window_group, workspace());
992
if (new_group != in_group) {
993
if (in_group != NULL)
994
in_group->removeMember(this);
995
in_group = new_group;
996
in_group->addMember(this);
999
if (transientFor() != NULL) {
1000
// doesn't have window group set, but is transient for something
1001
// so make it part of that group
1002
Group* new_group = transientFor()->group();
1003
if (new_group != in_group) {
1004
if (in_group != NULL)
1005
in_group->removeMember(this);
1006
in_group = transientFor()->group();
1007
in_group->addMember(this);
1009
} else if (groupTransient()) {
1010
// group transient which actually doesn't have a group :(
1011
// try creating group with other windows with the same client leader
1012
Group* new_group = workspace()->findClientLeaderGroup(this);
1013
if (new_group == NULL)
1014
new_group = new Group(None, workspace());
1015
if (new_group != in_group) {
1016
if (in_group != NULL)
1017
in_group->removeMember(this);
1018
in_group = new_group;
1019
in_group->addMember(this);
1021
} else { // Not transient without a group, put it in its client leader group.
1022
// This might be stupid if grouping was used for e.g. taskbar grouping
1023
// or minimizing together the whole group, but as long as it is used
1024
// only for dialogs it's better to keep windows from one app in one group.
1025
Group* new_group = workspace()->findClientLeaderGroup(this);
1026
if (in_group != NULL && in_group != new_group) {
1027
in_group->removeMember(this);
1030
if (new_group == NULL)
1031
new_group = new Group(None, workspace());
1032
if (in_group != new_group) {
1033
in_group = new_group;
1034
in_group->addMember(this);
1038
if (in_group != old_group || force) {
1039
for (ClientList::Iterator it = transients_list.begin();
1040
it != transients_list.end();
1042
// group transients in the old group are no longer transient for it
1043
if ((*it)->groupTransient() && (*it)->group() != group())
1044
it = transients_list.erase(it);
1048
if (groupTransient()) {
1049
// no longer transient for ones in the old group
1050
if (old_group != NULL) {
1051
for (ClientList::ConstIterator it = old_group->members().constBegin();
1052
it != old_group->members().constEnd();
1054
(*it)->removeTransient(this);
1056
// and make transient for all in the new group
1057
for (ClientList::ConstIterator it = group()->members().constBegin();
1058
it != group()->members().constEnd();
1061
break; // this means the window is only transient for windows mapped before it
1062
(*it)->addTransient(this);
1065
// group transient splashscreens should be transient even for windows
1066
// in group mapped later
1067
for (ClientList::ConstIterator it = group()->members().constBegin();
1068
it != group()->members().constEnd();
1070
if (!(*it)->isSplash())
1072
if (!(*it)->groupTransient())
1074
if (*it == this || hasTransient(*it, true)) // TODO indirect?
1079
if (old_group != NULL)
1080
old_group->deref(); // can be now deleted if empty
1081
checkGroupTransients();
1083
workspace()->updateClientLayer(this);
1086
// used by Workspace::findClientLeaderGroup()
1087
void Client::changeClientLeaderGroup(Group* gr)
1089
// transientFor() != NULL are in the group of their mainwindow, so keep them there
1090
if (transientFor() != NULL)
1092
// also don't change the group for window which have group set
1095
checkGroup(gr); // change group
1098
bool Client::check_active_modal = false;
1100
void Client::checkActiveModal()
1102
// if the active window got new modal transient, activate it.
1103
// cannot be done in AddTransient(), because there may temporarily
1104
// exist loops, breaking findModal
1105
Client* check_modal = workspace()->mostRecentlyActivatedClient();
1106
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) {
1109
if (!new_modal->isManaged())
1110
return; // postpone check until end of manage()
1111
workspace()->activateClient(new_modal);
1113
check_modal->check_active_modal = false;