1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
7
8This program is free software; you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation; either version 2 of the License, or
11(at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program. If not, see <http://www.gnu.org/licenses/>.
20*********************************************************************/
21
22/*
23
24 This file contains things relevant to window grouping.
25
26*/
27
28//#define QT_CLEAN_NAMESPACE
29
30#include "group.h"
31#include <QTextStream>
32#include "workspace.h"
33#include "client.h"
34#include "effects.h"
35
36#include <assert.h>
37#include <kstartupinfo.h>
38#include <QX11Info>
39
40
41/*
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.
45*/
46
47namespace KWin
48{
49
50/*
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.
54*/
55//#define ENABLE_TRANSIENCY_CHECK
56
57#ifdef NDEBUG
58#undef ENABLE_TRANSIENCY_CHECK
59#endif
60
61#ifdef ENABLE_TRANSIENCY_CHECK
62static bool transiencyCheckNonExistent = false;
63
64bool performTransiencyCheck()
65{
66 bool ret = true;
67 ClientList clients = Workspace::self()->clients;
68 for (ClientList::ConstIterator it1 = clients.constBegin();
69 it1 != clients.constEnd();
70 ++it1) {
71 if ((*it1)->deleting)
72 continue;
73 if ((*it1)->in_group == NULL) {
74 kDebug(1212) << "TC: " << *it1 << " in not in a group" << endl;
75 ret = false;
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;
78 ret = false;
79 }
80 if (!(*it1)->isTransient()) {
81 if (!(*it1)->mainClients().isEmpty()) {
82 kDebug(1212) << "TC: " << *it1 << " is not transient, has main clients:" << (*it1)->mainClients() << endl;
83 ret = false;
84 }
85 } else {
86 ClientList mains = (*it1)->mainClients();
87 for (ClientList::ConstIterator it2 = mains.constBegin();
88 it2 != mains.constEnd();
89 ++it2) {
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
95 ret = false;
96 continue;
97 }
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;
100 ret = false;
101 }
102 }
103 }
104 ClientList trans = (*it1)->transients_list;
105 for (ClientList::ConstIterator it2 = trans.constBegin();
106 it2 != trans.constEnd();
107 ++it2) {
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
113 ret = false;
114 continue;
115 }
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;
118 ret = false;
119 }
120 }
121 }
122 GroupList groups = Workspace::self()->groups;
123 for (GroupList::ConstIterator it1 = groups.constBegin();
124 it1 != groups.constEnd();
125 ++it1) {
126 ClientList members = (*it1)->members();
127 for (ClientList::ConstIterator it2 = members.constBegin();
128 it2 != members.constEnd();
129 ++it2) {
130 if ((*it2)->in_group != *it1) {
131 kDebug(1212) << "TC: Group " << *it1 << " contains client " << *it2 << " but client is not in that group" << endl;
132 ret = false;
133 }
134 }
135 }
136 return ret;
137}
138
139static QString transiencyCheckStartBt;
140static const Client* transiencyCheckClient;
141static int transiencyCheck = 0;
142
143static void startTransiencyCheck(const QString& bt, const Client* c, bool ne)
144{
145 if (++transiencyCheck == 1) {
146 transiencyCheckStartBt = bt;
147 transiencyCheckClient = c;
148 }
149 if (ne)
150 transiencyCheckNonExistent = true;
151}
152static void checkTransiency()
153{
154 if (--transiencyCheck == 0) {
155 if (!performTransiencyCheck()) {
156 kDebug(1212) << "BT:" << transiencyCheckStartBt << endl;
157 kDebug(1212) << "CLIENT:" << transiencyCheckClient << endl;
158 abort();
159 }
160 transiencyCheckNonExistent = false;
161 }
162}
163class TransiencyChecker
164{
165public:
166 TransiencyChecker(const QString& bt, const Client*c) {
167 startTransiencyCheck(bt, c, false);
168 }
169 ~TransiencyChecker() {
170 checkTransiency();
171 }
172};
173
174void checkNonExistentClients()
175{
176 startTransiencyCheck(kdBacktrace(), NULL, true);
177 checkTransiency();
178}
179
180#define TRANSIENCY_CHECK( c ) TransiencyChecker transiency_checker( kdBacktrace(), c )
181
182#else
183
184#define TRANSIENCY_CHECK( c )
185
186void checkNonExistentClients()
187{
188}
189
190#endif
191
192//********************************************
193// Group
194//********************************************
195
196Group::Group(Window leader_P)
197 : leader_client(NULL),
198 leader_wid(leader_P),
199 leader_info(NULL),
200 user_time(-1U),
201 refcount(0)
202{
203 if (leader_P != None) {
204 leader_client = workspace()->findClient(WindowMatchPredicate(leader_P));
205 unsigned long properties[ 2 ] = { 0, NET::WM2StartupId };
206 leader_info = new NETWinInfo2(display(), leader_P, rootWindow(),
207 properties, 2);
208 }
209 effect_group = new EffectWindowGroupImpl(this);
210 workspace()->addGroup(this);
211}
212
213Group::~Group()
214{
215 delete leader_info;
216 delete effect_group;
217}
218
219QPixmap Group::icon() const
220{
221 if (leader_client != NULL)
222 return leader_client->icon();
223 else if (leader_wid != None) {
224 QPixmap ic;
225 Client::readIcons(leader_wid, &ic, NULL, NULL, NULL);
226 return ic;
227 }
228 return QPixmap();
229}
230
231QPixmap Group::miniIcon() const
232{
233 if (leader_client != NULL)
234 return leader_client->miniIcon();
235 else if (leader_wid != None) {
236 QPixmap ic;
237 Client::readIcons(leader_wid, NULL, &ic, NULL, NULL);
238 return ic;
239 }
240 return QPixmap();
241}
242
243QPixmap Group::bigIcon() const
244{
245 if (leader_client != NULL)
246 return leader_client->bigIcon();
247 else if (leader_wid != None) {
248 QPixmap ic;
249 Client::readIcons(leader_wid, NULL, NULL, &ic, NULL);
250 return ic;
251 }
252 return QPixmap();
253}
254
255QPixmap Group::hugeIcon() const
256{
257 if (leader_client != NULL)
258 return leader_client->hugeIcon();
259 else if (leader_wid != None) {
260 QPixmap ic;
261 Client::readIcons(leader_wid, NULL, NULL, NULL, &ic);
262 return ic;
263 }
264 return QPixmap();
265}
266
267void Group::addMember(Client* member_P)
268{
269 TRANSIENCY_CHECK(member_P);
270 _members.append(member_P);
271// kDebug(1212) << "GROUPADD:" << this << ":" << member_P;
272// kDebug(1212) << kBacktrace();
273}
274
275void Group::removeMember(Client* member_P)
276{
277 TRANSIENCY_CHECK(member_P);
278// kDebug(1212) << "GROUPREMOVE:" << this << ":" << member_P;
279// kDebug(1212) << kBacktrace();
280 Q_ASSERT(_members.contains(member_P));
281 _members.removeAll(member_P);
282// there are cases when automatic deleting of groups must be delayed,
283// e.g. when removing a member and doing some operation on the possibly
284// other members of the group (which would be however deleted already
285// if there were no other members)
286 if (refcount == 0 && _members.isEmpty()) {
287 workspace()->removeGroup(this);
288 delete this;
289 }
290}
291
292void Group::ref()
293{
294 ++refcount;
295}
296
297void Group::deref()
298{
299 if (--refcount == 0 && _members.isEmpty()) {
300 workspace()->removeGroup(this);
301 delete this;
302 }
303}
304
305void Group::gotLeader(Client* leader_P)
306{
307 assert(leader_P->window() == leader_wid);
308 leader_client = leader_P;
309}
310
311void Group::lostLeader()
312{
313 assert(!_members.contains(leader_client));
314 leader_client = NULL;
315 if (_members.isEmpty()) {
316 workspace()->removeGroup(this);
317 delete this;
318 }
319}
320
321//***************************************
322// Workspace
323//***************************************
324
325Group* Workspace::findGroup(xcb_window_t leader) const
326{
327 assert(leader != None);
328 for (GroupList::ConstIterator it = groups.constBegin();
329 it != groups.constEnd();
330 ++it)
331 if ((*it)->leader() == leader)
332 return *it;
333 return NULL;
334}
335
336// Client is group transient, but has no group set. Try to find
337// group with windows with the same client leader.
338Group* Workspace::findClientLeaderGroup(const Client* c) const
339{
340 TRANSIENCY_CHECK(c);
341 Group* ret = NULL;
342 for (ClientList::ConstIterator it = clients.constBegin();
343 it != clients.constEnd();
344 ++it) {
345 if (*it == c)
346 continue;
347 if ((*it)->wmClientLeader() == c->wmClientLeader()) {
348 if (ret == NULL || ret == (*it)->group())
349 ret = (*it)->group();
350 else {
351 // There are already two groups with the same client leader.
352 // This most probably means the app uses group transients without
353 // setting group for its windows. Merging the two groups is a bad
354 // hack, but there's no really good solution for this case.
355 ClientList old_group = (*it)->group()->members();
356 // old_group autodeletes when being empty
357 for (int pos = 0;
358 pos < old_group.count();
359 ++pos) {
360 Client* tmp = old_group[ pos ];
361 if (tmp != c)
362 tmp->changeClientLeaderGroup(ret);
363 }
364 }
365 }
366 }
367 return ret;
368}
369
370void Workspace::updateMinimizedOfTransients(Client* c)
371{
372 // if mainwindow is minimized or shaded, minimize transients too
373 if (c->isMinimized()) {
374 for (ClientList::ConstIterator it = c->transients().constBegin();
375 it != c->transients().constEnd();
376 ++it) {
377 if ((*it)->isModal())
378 continue; // there's no reason to hide modal dialogs with the main client
379 // but to keep them to eg. watch progress or whatever
380 if (!(*it)->isMinimized()) {
381 (*it)->minimize();
382 updateMinimizedOfTransients((*it));
383 }
384 }
385 if (c->isModal()) { // if a modal dialog is minimized, minimize its mainwindow too
386 foreach (Client * c2, c->mainClients())
387 c2->minimize();
388 }
389 } else {
390 // else unmiminize the transients
391 for (ClientList::ConstIterator it = c->transients().constBegin();
392 it != c->transients().constEnd();
393 ++it) {
394 if ((*it)->isMinimized()) {
395 (*it)->unminimize();
396 updateMinimizedOfTransients((*it));
397 }
398 }
399 if (c->isModal()) {
400 foreach (Client * c2, c->mainClients())
401 c2->unminimize();
402 }
403 }
404}
405
406
407/*!
408 Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops.
409 */
410void Workspace::updateOnAllDesktopsOfTransients(Client* c)
411{
412 for (ClientList::ConstIterator it = c->transients().constBegin();
413 it != c->transients().constEnd();
414 ++it) {
415 if ((*it)->isOnAllDesktops() != c->isOnAllDesktops())
416 (*it)->setOnAllDesktops(c->isOnAllDesktops());
417 }
418}
419
420// A new window has been mapped. Check if it's not a mainwindow for some already existing transient window.
421void Workspace::checkTransients(xcb_window_t w)
422{
423 TRANSIENCY_CHECK(NULL);
424 for (ClientList::ConstIterator it = clients.constBegin();
425 it != clients.constEnd();
426 ++it)
427 (*it)->checkTransient(w);
428}
429
430
431//****************************************
432// Toplevel
433//****************************************
434
435// hacks for broken apps here
436// all resource classes are forced to be lowercase
437bool Toplevel::resourceMatch(const Toplevel* c1, const Toplevel* c2)
438{
439 // xv has "xv" as resource name, and different strings starting with "XV" as resource class
440 if (qstrncmp(c1->resourceClass(), "xv", 2) == 0 && c1->resourceName() == "xv")
441 return qstrncmp(c2->resourceClass(), "xv", 2) == 0 && c2->resourceName() == "xv";
442 // Mozilla has "Mozilla" as resource name, and different strings as resource class
443 if (c1->resourceName() == "mozilla")
444 return c2->resourceName() == "mozilla";
445 return c1->resourceClass() == c2->resourceClass();
446}
447
448
449//****************************************
450// Client
451//****************************************
452
453bool Client::belongToSameApplication(const Client* c1, const Client* c2, bool active_hack)
454{
455 bool same_app = false;
456
457 // tests that definitely mean they belong together
458 if (c1 == c2)
459 same_app = true;
460 else if (c1->isTransient() && c2->hasTransient(c1, true))
461 same_app = true; // c1 has c2 as mainwindow
462 else if (c2->isTransient() && c1->hasTransient(c2, true))
463 same_app = true; // c2 has c1 as mainwindow
464 else if (c1->group() == c2->group())
465 same_app = true; // same group
466 else if (c1->wmClientLeader() == c2->wmClientLeader()
467 && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
468 && c2->wmClientLeader() != c2->window()) // don't use in this test then
469 same_app = true; // same client leader
470
471 // tests that mean they most probably don't belong together
472 else if (c1->pid() != c2->pid()
473 || c1->wmClientMachine(false) != c2->wmClientMachine(false))
474 ; // different processes
475 else if (c1->wmClientLeader() != c2->wmClientLeader()
476 && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(),
477 && c2->wmClientLeader() != c2->window()) // don't use in this test then
478 ; // different client leader
479 else if (!resourceMatch(c1, c2))
480 ; // different apps
481 else if (!sameAppWindowRoleMatch(c1, c2, active_hack))
482 ; // "different" apps
483 else if (c1->pid() == 0 || c2->pid() == 0)
484 ; // old apps that don't have _NET_WM_PID, consider them different
485 // if they weren't found to match above
486 else
487 same_app = true; // looks like it's the same app
488
489 return same_app;
490}
491
492// Non-transient windows with window role containing '#' are always
493// considered belonging to different applications (unless
494// the window role is exactly the same). KMainWindow sets
495// window role this way by default, and different KMainWindow
496// usually "are" different application from user's point of view.
497// This help with no-focus-stealing for e.g. konqy reusing.
498// On the other hand, if one of the windows is active, they are
499// considered belonging to the same application. This is for
500// the cases when opening new mainwindow directly from the application,
501// e.g. 'Open New Window' in konqy ( active_hack == true ).
502bool Client::sameAppWindowRoleMatch(const Client* c1, const Client* c2, bool active_hack)
503{
504 if (c1->isTransient()) {
505 while (c1->transientFor() != NULL)
506 c1 = c1->transientFor();
507 if (c1->groupTransient())
508 return c1->group() == c2->group();
509#if 0
510 // if a group transient is in its own group, it didn't possibly have a group,
511 // and therefore should be considered belonging to the same app like
512 // all other windows from the same app
513 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
514#endif
515 }
516 if (c2->isTransient()) {
517 while (c2->transientFor() != NULL)
518 c2 = c2->transientFor();
519 if (c2->groupTransient())
520 return c1->group() == c2->group();
521#if 0
522 || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
523#endif
524 }
525 int pos1 = c1->windowRole().indexOf('#');
526 int pos2 = c2->windowRole().indexOf('#');
527 if ((pos1 >= 0 && pos2 >= 0)
528 ||
529 // hacks here
530 // Mozilla has resourceName() and resourceClass() swapped
531 (c1->resourceName() == "mozilla" && c2->resourceName() == "mozilla")) {
532 if (!active_hack) // without the active hack for focus stealing prevention,
533 return c1 == c2; // different mainwindows are always different apps
534 if (!c1->isActive() && !c2->isActive())
535 return c1 == c2;
536 else
537 return true;
538 }
539 return true;
540}
541
542/*
543
544 Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3
545
546 WM_TRANSIENT_FOR is basically means "this is my mainwindow".
547 For NET::Unknown windows, transient windows are considered to be NET::Dialog
548 windows, for compatibility with non-NETWM clients. KWin may adjust the value
549 of this property in some cases (window pointing to itself or creating a loop,
550 keeping NET::Splash windows above other windows from the same app, etc.).
551
552 Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after
553 possibly being adjusted by KWin. Client::transient_for points to the Client
554 this Client is transient for, or is NULL. If Client::transient_for_id is
555 poiting to the root window, the window is considered to be transient
556 for the whole window group, as suggested in NETWM 7.3.
557
558 In the case of group transient window, Client::transient_for is NULL,
559 and Client::groupTransient() returns true. Such window is treated as
560 if it were transient for every window in its window group that has been
561 mapped _before_ it (or, to be exact, was added to the same group before it).
562 Otherwise two group transients can create loops, which can lead very very
563 nasty things (bug #67914 and all its dupes).
564
565 Client::original_transient_for_id is the value of the property, which
566 may be different if Client::transient_for_id if e.g. forcing NET::Splash
567 to be kept on top of its window group, or when the mainwindow is not mapped
568 yet, in which case the window is temporarily made group transient,
569 and when the mainwindow is mapped, transiency is re-evaluated.
570
571 This can get a bit complicated with with e.g. two Konqueror windows created
572 by the same process. They should ideally appear like two independent applications
573 to the user. This should be accomplished by all windows in the same process
574 having the same window group (needs to be changed in Qt at the moment), and
575 using non-group transients poiting to their relevant mainwindow for toolwindows
576 etc. KWin should handle both group and non-group transient dialogs well.
577
578 In other words:
579 - non-transient windows : isTransient() == false
580 - normal transients : transientFor() != NULL
581 - group transients : groupTransient() == true
582
583 - list of mainwindows : mainClients() (call once and loop over the result)
584 - list of transients : transients()
585 - every window in the group : group()->members()
586*/
587
588void Client::readTransient()
589{
590 TRANSIENCY_CHECK(this);
591 Xcb::TransientFor transientFor(window());
592 xcb_window_t new_transient_for_id = XCB_WINDOW_NONE;
593 if (transientFor.getTransientFor(&new_transient_for_id)) {
594 m_originalTransientForId = new_transient_for_id;
595 new_transient_for_id = verifyTransientFor(new_transient_for_id, true);
596 } else {
597 m_originalTransientForId = XCB_WINDOW_NONE;
598 new_transient_for_id = verifyTransientFor(XCB_WINDOW_NONE, false);
599 }
600 setTransient(new_transient_for_id);
601}
602
603void Client::setTransient(xcb_window_t new_transient_for_id)
604{
605 TRANSIENCY_CHECK(this);
606 if (new_transient_for_id != m_transientForId) {
607 removeFromMainClients();
608 transient_for = NULL;
609 m_transientForId = new_transient_for_id;
610 if (m_transientForId != XCB_WINDOW_NONE && !groupTransient()) {
611 transient_for = workspace()->findClient(WindowMatchPredicate(m_transientForId));
612 assert(transient_for != NULL); // verifyTransient() had to check this
613 transient_for->addTransient(this);
614 } // checkGroup() will check 'check_active_modal'
615 checkGroup(NULL, true); // force, because transiency has changed
616 workspace()->updateClientLayer(this);
617 workspace()->resetUpdateToolWindowsTimer();
618 emit transientChanged();
619 }
620}
621
622void Client::removeFromMainClients()
623{
624 TRANSIENCY_CHECK(this);
625 if (transientFor() != NULL)
626 transientFor()->removeTransient(this);
627 if (groupTransient()) {
628 for (ClientList::ConstIterator it = group()->members().constBegin();
629 it != group()->members().constEnd();
630 ++it)
631 (*it)->removeTransient(this);
632 }
633}
634
635// *sigh* this transiency handling is madness :(
636// This one is called when destroying/releasing a window.
637// It makes sure this client is removed from all grouping
638// related lists.
639void Client::cleanGrouping()
640{
641 TRANSIENCY_CHECK(this);
642// kDebug(1212) << "CLEANGROUPING:" << this;
643// for ( ClientList::ConstIterator it = group()->members().begin();
644// it != group()->members().end();
645// ++it )
646// kDebug(1212) << "CL:" << *it;
647// ClientList mains;
648// mains = mainClients();
649// for ( ClientList::ConstIterator it = mains.begin();
650// it != mains.end();
651// ++it )
652// kDebug(1212) << "MN:" << *it;
653 removeFromMainClients();
654// kDebug(1212) << "CLEANGROUPING2:" << this;
655// for ( ClientList::ConstIterator it = group()->members().begin();
656// it != group()->members().end();
657// ++it )
658// kDebug(1212) << "CL2:" << *it;
659// mains = mainClients();
660// for ( ClientList::ConstIterator it = mains.begin();
661// it != mains.end();
662// ++it )
663// kDebug(1212) << "MN2:" << *it;
664 for (ClientList::ConstIterator it = transients_list.constBegin();
665 it != transients_list.constEnd();
666 ) {
667 if ((*it)->transientFor() == this) {
668 removeTransient(*it);
669 it = transients_list.constBegin(); // restart, just in case something more has changed with the list
670 } else
671 ++it;
672 }
673// kDebug(1212) << "CLEANGROUPING3:" << this;
674// for ( ClientList::ConstIterator it = group()->members().begin();
675// it != group()->members().end();
676// ++it )
677// kDebug(1212) << "CL3:" << *it;
678// mains = mainClients();
679// for ( ClientList::ConstIterator it = mains.begin();
680// it != mains.end();
681// ++it )
682// kDebug(1212) << "MN3:" << *it;
683 // HACK
684 // removeFromMainClients() did remove 'this' from transient
685 // lists of all group members, but then made windows that
686 // were transient for 'this' group transient, which again
687 // added 'this' to those transient lists :(
688 ClientList group_members = group()->members();
689 group()->removeMember(this);
690 in_group = NULL;
691 for (ClientList::ConstIterator it = group_members.constBegin();
692 it != group_members.constEnd();
693 ++it)
694 (*it)->removeTransient(this);
695// kDebug(1212) << "CLEANGROUPING4:" << this;
696// for ( ClientList::ConstIterator it = group_members.begin();
697// it != group_members.end();
698// ++it )
699// kDebug(1212) << "CL4:" << *it;
700}
701
702// Make sure that no group transient is considered transient
703// for a window that is (directly or indirectly) transient for it
704// (including another group transients).
705// Non-group transients not causing loops are checked in verifyTransientFor().
706void Client::checkGroupTransients()
707{
708 TRANSIENCY_CHECK(this);
709 for (ClientList::ConstIterator it1 = group()->members().constBegin();
710 it1 != group()->members().constEnd();
711 ++it1) {
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().constBegin();
715 it2 != group()->members().constEnd();
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
718 if (*it1 == *it2)
719 continue;
720 for (Client* cl = (*it2)->transientFor();
721 cl != NULL;
722 cl = cl->transientFor()) {
723 if (cl == *it1) {
724 // don't use removeTransient(), that would modify *it2 too
725 (*it2)->transients_list.removeAll(*it1);
726 continue;
727 }
728 }
729 // if *it1 and *it2 are both group transients, and are transient for each other,
730 // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later,
731 // and should be therefore on top of *it1
732 // TODO This could possibly be optimized, it also requires hasTransient() to check for loops.
733 if ((*it2)->groupTransient() && (*it1)->hasTransient(*it2, true) && (*it2)->hasTransient(*it1, true))
734 (*it2)->transients_list.removeAll(*it1);
735 // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3
736 // is added, make it transient only for W2, not for W1, because it's already indirectly
737 // transient for it - the indirect transiency actually shouldn't break anything,
738 // but it can lead to exponentially expensive operations (#95231)
739 // TODO this is pretty slow as well
740 for (ClientList::ConstIterator it3 = group()->members().constBegin();
741 it3 != group()->members().constEnd();
742 ++it3) {
743 if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3)
744 continue;
745 if ((*it2)->hasTransient(*it1, false) && (*it3)->hasTransient(*it1, false)) {
746 if ((*it2)->hasTransient(*it3, true))
747 (*it2)->transients_list.removeAll(*it1);
748 if ((*it3)->hasTransient(*it2, true))
749 (*it3)->transients_list.removeAll(*it1);
750 }
751 }
752 }
753 }
754}
755
756/*!
757 Check that the window is not transient for itself, and similar nonsense.
758 */
759xcb_window_t Client::verifyTransientFor(xcb_window_t new_transient_for, bool set)
760{
761 xcb_window_t new_property_value = new_transient_for;
762 // make sure splashscreens are shown above all their app's windows, even though
763 // they're in Normal layer
764 if (isSplash() && new_transient_for == XCB_WINDOW_NONE)
765 new_transient_for = rootWindow();
766 if (new_transient_for == XCB_WINDOW_NONE) {
767 if (set) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window
768 new_property_value = new_transient_for = rootWindow();
769 else
770 return XCB_WINDOW_NONE;
771 }
772 if (new_transient_for == window()) { // pointing to self
773 // also fix the property itself
774 kWarning(1216) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." ;
775 new_property_value = new_transient_for = rootWindow();
776 }
777// The transient_for window may be embedded in another application,
778// so kwin cannot see it. Try to find the managed client for the
779// window and fix the transient_for property if possible.
780 xcb_window_t before_search = new_transient_for;
781 while (new_transient_for != XCB_WINDOW_NONE
782 && new_transient_for != rootWindow()
783 && !workspace()->findClient(WindowMatchPredicate(new_transient_for))) {
784 Xcb::Tree tree(new_transient_for);
785 if (tree.isNull()) {
786 break;
787 }
788 new_transient_for = tree->parent;
789 }
790 if (Client* new_transient_for_client = workspace()->findClient(WindowMatchPredicate(new_transient_for))) {
791 if (new_transient_for != before_search) {
792 kDebug(1212) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window "
793 << before_search << ", child of " << new_transient_for_client << ", adjusting." << endl;
794 new_property_value = new_transient_for; // also fix the property
795 }
796 } else
797 new_transient_for = before_search; // nice try
798// loop detection
799// group transients cannot cause loops, because they're considered transient only for non-transient
800// windows in the group
801 int count = 20;
802 xcb_window_t loop_pos = new_transient_for;
803 while (loop_pos != XCB_WINDOW_NONE && loop_pos != rootWindow()) {
804 Client* pos = workspace()->findClient(WindowMatchPredicate(loop_pos));
805 if (pos == NULL)
806 break;
807 loop_pos = pos->m_transientForId;
808 if (--count == 0 || pos == this) {
809 kWarning(1216) << "Client " << this << " caused WM_TRANSIENT_FOR loop." ;
810 new_transient_for = rootWindow();
811 }
812 }
813 if (new_transient_for != rootWindow()
814 && workspace()->findClient(WindowMatchPredicate(new_transient_for)) == NULL) {
815 // it's transient for a specific window, but that window is not mapped
816 new_transient_for = rootWindow();
817 }
818 if (new_property_value != m_originalTransientForId)
819 Xcb::setTransientFor(window(), new_property_value);
820 return new_transient_for;
821}
822
823void Client::addTransient(Client* cl)
824{
825 TRANSIENCY_CHECK(this);
826 assert(!transients_list.contains(cl));
827// assert( !cl->hasTransient( this, true )); will be fixed in checkGroupTransients()
828 assert(cl != this);
829 transients_list.append(cl);
830 if (workspace()->mostRecentlyActivatedClient() == this && cl->isModal())
831 check_active_modal = true;
832// kDebug(1212) << "ADDTRANS:" << this << ":" << cl;
833// kDebug(1212) << kBacktrace();
834// for ( ClientList::ConstIterator it = transients_list.begin();
835// it != transients_list.end();
836// ++it )
837// kDebug(1212) << "AT:" << (*it);
838}
839
840void Client::removeTransient(Client* cl)
841{
842 TRANSIENCY_CHECK(this);
843// kDebug(1212) << "REMOVETRANS:" << this << ":" << cl;
844// kDebug(1212) << kBacktrace();
845 transients_list.removeAll(cl);
846 // cl is transient for this, but this is going away
847 // make cl group transient
848 if (cl->transientFor() == this) {
849 cl->m_transientForId = XCB_WINDOW_NONE;
850 cl->transient_for = NULL; // SELI
851// SELI cl->setTransient( rootWindow());
852 cl->setTransient(XCB_WINDOW_NONE);
853 }
854}
855
856// A new window has been mapped. Check if it's not a mainwindow for this already existing window.
857void Client::checkTransient(xcb_window_t w)
858{
859 TRANSIENCY_CHECK(this);
860 if (m_originalTransientForId != w)
861 return;
862 w = verifyTransientFor(w, true);
863 setTransient(w);
864}
865
866// returns true if cl is the transient_for window for this client,
867// or recursively the transient_for window
868bool Client::hasTransient(const Client* cl, bool indirect) const
869{
870 // checkGroupTransients() uses this to break loops, so hasTransient() must detect them
871 ConstClientList set;
872 return hasTransientInternal(cl, indirect, set);
873}
874
875bool Client::hasTransientInternal(const Client* cl, bool indirect, ConstClientList& set) const
876{
877 if (cl->transientFor() != NULL) {
878 if (cl->transientFor() == this)
879 return true;
880 if (!indirect)
881 return false;
882 if (set.contains(cl))
883 return false;
884 set.append(cl);
885 return hasTransientInternal(cl->transientFor(), indirect, set);
886 }
887 if (!cl->isTransient())
888 return false;
889 if (group() != cl->group())
890 return false;
891 // cl is group transient, search from top
892 if (transients().contains(const_cast< Client* >(cl)))
893 return true;
894 if (!indirect)
895 return false;
896 if (set.contains(this))
897 return false;
898 set.append(this);
899 for (ClientList::ConstIterator it = transients().constBegin();
900 it != transients().constEnd();
901 ++it)
902 if ((*it)->hasTransientInternal(cl, indirect, set))
903 return true;
904 return false;
905}
906
907ClientList Client::mainClients() const
908{
909 if (!isTransient())
910 return ClientList();
911 if (transientFor() != NULL)
912 return ClientList() << const_cast< Client* >(transientFor());
913 ClientList result;
914 for (ClientList::ConstIterator it = group()->members().constBegin();
915 it != group()->members().constEnd();
916 ++it)
917 if ((*it)->hasTransient(this, false))
918 result.append(*it);
919 return result;
920}
921
922ClientList Client::allMainClients() const
923{
924 ClientList result = mainClients();
925 foreach (const Client * cl, result)
926 result += cl->allMainClients();
927 return result;
928}
929
930Client* Client::findModal(bool allow_itself)
931{
932 for (ClientList::ConstIterator it = transients().constBegin();
933 it != transients().constEnd();
934 ++it)
935 if (Client* ret = (*it)->findModal(true))
936 return ret;
937 if (isModal() && allow_itself)
938 return this;
939 return NULL;
940}
941
942// Client::window_group only holds the contents of the hint,
943// but it should be used only to find the group, not for anything else
944// Argument is only when some specific group needs to be set.
945void Client::checkGroup(Group* set_group, bool force)
946{
947 TRANSIENCY_CHECK(this);
948 Group* old_group = in_group;
949 if (old_group != NULL)
950 old_group->ref(); // turn off automatic deleting
951 if (set_group != NULL) {
952 if (set_group != in_group) {
953 if (in_group != NULL)
954 in_group->removeMember(this);
955 in_group = set_group;
956 in_group->addMember(this);
957 }
958 } else if (m_windowGroup != XCB_WINDOW_NONE) {
959 Group* new_group = workspace()->findGroup(m_windowGroup);
960 if (transientFor() != NULL && transientFor()->group() != new_group) {
961 // move the window to the right group (e.g. a dialog provided
962 // by different app, but transient for this one, so make it part of that group)
963 new_group = transientFor()->group();
964 }
965 if (new_group == NULL) // doesn't exist yet
966 new_group = new Group(m_windowGroup);
967 if (new_group != in_group) {
968 if (in_group != NULL)
969 in_group->removeMember(this);
970 in_group = new_group;
971 in_group->addMember(this);
972 }
973 } else {
974 if (transientFor() != NULL) {
975 // doesn't have window group set, but is transient for something
976 // so make it part of that group
977 Group* new_group = transientFor()->group();
978 if (new_group != in_group) {
979 if (in_group != NULL)
980 in_group->removeMember(this);
981 in_group = transientFor()->group();
982 in_group->addMember(this);
983 }
984 } else if (groupTransient()) {
985 // group transient which actually doesn't have a group :(
986 // try creating group with other windows with the same client leader
987 Group* new_group = workspace()->findClientLeaderGroup(this);
988 if (new_group == NULL)
989 new_group = new Group(None);
990 if (new_group != in_group) {
991 if (in_group != NULL)
992 in_group->removeMember(this);
993 in_group = new_group;
994 in_group->addMember(this);
995 }
996 } else { // Not transient without a group, put it in its client leader group.
997 // This might be stupid if grouping was used for e.g. taskbar grouping
998 // or minimizing together the whole group, but as long as it is used
999 // only for dialogs it's better to keep windows from one app in one group.
1000 Group* new_group = workspace()->findClientLeaderGroup(this);
1001 if (in_group != NULL && in_group != new_group) {
1002 in_group->removeMember(this);
1003 in_group = NULL;
1004 }
1005 if (new_group == NULL)
1006 new_group = new Group(None);
1007 if (in_group != new_group) {
1008 in_group = new_group;
1009 in_group->addMember(this);
1010 }
1011 }
1012 }
1013 if (in_group != old_group || force) {
1014 for (ClientList::Iterator it = transients_list.begin();
1015 it != transients_list.end();
1016 ) {
1017 // group transients in the old group are no longer transient for it
1018 if ((*it)->groupTransient() && (*it)->group() != group())
1019 it = transients_list.erase(it);
1020 else
1021 ++it;
1022 }
1023 if (groupTransient()) {
1024 // no longer transient for ones in the old group
1025 if (old_group != NULL) {
1026 for (ClientList::ConstIterator it = old_group->members().constBegin();
1027 it != old_group->members().constEnd();
1028 ++it)
1029 (*it)->removeTransient(this);
1030 }
1031 // and make transient for all in the new group
1032 for (ClientList::ConstIterator it = group()->members().constBegin();
1033 it != group()->members().constEnd();
1034 ++it) {
1035 if (*it == this)
1036 break; // this means the window is only transient for windows mapped before it
1037 (*it)->addTransient(this);
1038 }
1039 }
1040 // group transient splashscreens should be transient even for windows
1041 // in group mapped later
1042 for (ClientList::ConstIterator it = group()->members().constBegin();
1043 it != group()->members().constEnd();
1044 ++it) {
1045 if (!(*it)->isSplash())
1046 continue;
1047 if (!(*it)->groupTransient())
1048 continue;
1049 if (*it == this || hasTransient(*it, true)) // TODO indirect?
1050 continue;
1051 addTransient(*it);
1052 }
1053 }
1054 if (old_group != NULL)
1055 old_group->deref(); // can be now deleted if empty
1056 checkGroupTransients();
1057 checkActiveModal();
1058 workspace()->updateClientLayer(this);
1059}
1060
1061// used by Workspace::findClientLeaderGroup()
1062void Client::changeClientLeaderGroup(Group* gr)
1063{
1064 // transientFor() != NULL are in the group of their mainwindow, so keep them there
1065 if (transientFor() != NULL)
1066 return;
1067 // also don't change the group for window which have group set
1068 if (m_windowGroup)
1069 return;
1070 checkGroup(gr); // change group
1071}
1072
1073bool Client::check_active_modal = false;
1074
1075void Client::checkActiveModal()
1076{
1077 // if the active window got new modal transient, activate it.
1078 // cannot be done in AddTransient(), because there may temporarily
1079 // exist loops, breaking findModal
1080 Client* check_modal = workspace()->mostRecentlyActivatedClient();
1081 if (check_modal != NULL && check_modal->check_active_modal) {
1082 Client* new_modal = check_modal->findModal();
1083 if (new_modal != NULL && new_modal != check_modal) {
1084 if (!new_modal->isManaged())
1085 return; // postpone check until end of manage()
1086 workspace()->activateClient(new_modal);
1087 }
1088 check_modal->check_active_modal = false;
1089 }
1090}
1091
1092} // namespace
1093