~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to kwin/group.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/********************************************************************
 
2
 KWin - the KDE window manager
 
3
 This file is part of the KDE project.
 
4
 
 
5
Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
 
6
Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
 
7
 
 
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.
 
12
 
 
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.
 
17
 
 
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
*********************************************************************/
 
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
 
 
47
namespace 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
 
62
static bool transiencyCheckNonExistent = false;
 
63
 
 
64
bool 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
 
 
139
static QString transiencyCheckStartBt;
 
140
static const Client* transiencyCheckClient;
 
141
static int transiencyCheck = 0;
 
142
 
 
143
static 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
}
 
152
static 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
}
 
163
class TransiencyChecker
 
164
{
 
165
public:
 
166
    TransiencyChecker(const QString& bt, const Client*c) {
 
167
        startTransiencyCheck(bt, c, false);
 
168
    }
 
169
    ~TransiencyChecker() {
 
170
        checkTransiency();
 
171
    }
 
172
};
 
173
 
 
174
void 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
 
 
186
void checkNonExistentClients()
 
187
{
 
188
}
 
189
 
 
190
#endif
 
191
 
 
192
//********************************************
 
193
// Group
 
194
//********************************************
 
195
 
 
196
Group::Group(Window leader_P, Workspace* workspace_P)
 
197
    :   leader_client(NULL),
 
198
        leader_wid(leader_P),
 
199
        _workspace(workspace_P),
 
200
        leader_info(NULL),
 
201
        user_time(-1U),
 
202
        refcount(0)
 
203
{
 
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(),
 
208
                                      properties, 2);
 
209
    }
 
210
    effect_group = new EffectWindowGroupImpl(this);
 
211
    workspace()->addGroup(this, Allowed);
 
212
}
 
213
 
 
214
Group::~Group()
 
215
{
 
216
    delete leader_info;
 
217
    delete effect_group;
 
218
}
 
219
 
 
220
QPixmap Group::icon() const
 
221
{
 
222
    if (leader_client != NULL)
 
223
        return leader_client->icon();
 
224
    else if (leader_wid != None) {
 
225
        QPixmap ic;
 
226
        Client::readIcons(leader_wid, &ic, NULL, NULL, NULL);
 
227
        return ic;
 
228
    }
 
229
    return QPixmap();
 
230
}
 
231
 
 
232
QPixmap Group::miniIcon() const
 
233
{
 
234
    if (leader_client != NULL)
 
235
        return leader_client->miniIcon();
 
236
    else if (leader_wid != None) {
 
237
        QPixmap ic;
 
238
        Client::readIcons(leader_wid, NULL, &ic, NULL, NULL);
 
239
        return ic;
 
240
    }
 
241
    return QPixmap();
 
242
}
 
243
 
 
244
QPixmap Group::bigIcon() const
 
245
{
 
246
    if (leader_client != NULL)
 
247
        return leader_client->bigIcon();
 
248
    else if (leader_wid != None) {
 
249
        QPixmap ic;
 
250
        Client::readIcons(leader_wid, NULL, NULL, &ic, NULL);
 
251
        return ic;
 
252
    }
 
253
    return QPixmap();
 
254
}
 
255
 
 
256
QPixmap Group::hugeIcon() const
 
257
{
 
258
    if (leader_client != NULL)
 
259
        return leader_client->hugeIcon();
 
260
    else if (leader_wid != None) {
 
261
        QPixmap ic;
 
262
        Client::readIcons(leader_wid, NULL, NULL, NULL, &ic);
 
263
        return ic;
 
264
    }
 
265
    return QPixmap();
 
266
}
 
267
 
 
268
void Group::addMember(Client* member_P)
 
269
{
 
270
    TRANSIENCY_CHECK(member_P);
 
271
    _members.append(member_P);
 
272
//    kDebug(1212) << "GROUPADD:" << this << ":" << member_P;
 
273
//    kDebug(1212) << kBacktrace();
 
274
}
 
275
 
 
276
void Group::removeMember(Client* member_P)
 
277
{
 
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);
 
289
        delete this;
 
290
    }
 
291
}
 
292
 
 
293
void Group::ref()
 
294
{
 
295
    ++refcount;
 
296
}
 
297
 
 
298
void Group::deref()
 
299
{
 
300
    if (--refcount == 0 && _members.isEmpty()) {
 
301
        workspace()->removeGroup(this, Allowed);
 
302
        delete this;
 
303
    }
 
304
}
 
305
 
 
306
void Group::gotLeader(Client* leader_P)
 
307
{
 
308
    assert(leader_P->window() == leader_wid);
 
309
    leader_client = leader_P;
 
310
}
 
311
 
 
312
void Group::lostLeader()
 
313
{
 
314
    assert(!_members.contains(leader_client));
 
315
    leader_client = NULL;
 
316
    if (_members.isEmpty()) {
 
317
        workspace()->removeGroup(this, Allowed);
 
318
        delete this;
 
319
    }
 
320
}
 
321
 
 
322
void Group::getIcons()
 
323
{
 
324
    // TODO - also needs adding the flag to NETWinInfo
 
325
}
 
326
 
 
327
//***************************************
 
328
// Workspace
 
329
//***************************************
 
330
 
 
331
Group* Workspace::findGroup(Window leader) const
 
332
{
 
333
    assert(leader != None);
 
334
    for (GroupList::ConstIterator it = groups.constBegin();
 
335
            it != groups.constEnd();
 
336
            ++it)
 
337
        if ((*it)->leader() == leader)
 
338
            return *it;
 
339
    return NULL;
 
340
}
 
341
 
 
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
 
345
{
 
346
    TRANSIENCY_CHECK(c);
 
347
    Group* ret = NULL;
 
348
    for (ClientList::ConstIterator it = clients.constBegin();
 
349
            it != clients.constEnd();
 
350
            ++it) {
 
351
        if (*it == c)
 
352
            continue;
 
353
        if ((*it)->wmClientLeader() == c->wmClientLeader()) {
 
354
            if (ret == NULL || ret == (*it)->group())
 
355
                ret = (*it)->group();
 
356
            else {
 
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
 
363
                for (int pos = 0;
 
364
                        pos < old_group.count();
 
365
                        ++pos) {
 
366
                    Client* tmp = old_group[ pos ];
 
367
                    if (tmp != c)
 
368
                        tmp->changeClientLeaderGroup(ret);
 
369
                }
 
370
            }
 
371
        }
 
372
    }
 
373
    return ret;
 
374
}
 
375
 
 
376
void Workspace::updateMinimizedOfTransients(Client* c)
 
377
{
 
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();
 
382
                ++it) {
 
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
 
388
                (*it)->minimize();
 
389
                updateMinimizedOfTransients((*it));
 
390
            }
 
391
        }
 
392
        if (c->isModal()) { // if a modal dialog is minimized, minimize its mainwindow too
 
393
            foreach (Client * c2, c->mainClients())
 
394
            c2->minimize();
 
395
        }
 
396
    } else {
 
397
        // else unmiminize the transients
 
398
        for (ClientList::ConstIterator it = c->transients().constBegin();
 
399
                it != c->transients().constEnd();
 
400
                ++it) {
 
401
            if ((*it)->isMinimized()
 
402
                    && !(*it)->isTopMenu()) {
 
403
                (*it)->unminimize();
 
404
                updateMinimizedOfTransients((*it));
 
405
            }
 
406
        }
 
407
        if (c->isModal()) {
 
408
            foreach (Client * c2, c->mainClients())
 
409
            c2->unminimize();
 
410
        }
 
411
    }
 
412
}
 
413
 
 
414
 
 
415
/*!
 
416
  Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops.
 
417
 */
 
418
void Workspace::updateOnAllDesktopsOfTransients(Client* c)
 
419
{
 
420
    for (ClientList::ConstIterator it = c->transients().constBegin();
 
421
            it != c->transients().constEnd();
 
422
            ++it) {
 
423
        if ((*it)->isOnAllDesktops() != c->isOnAllDesktops())
 
424
            (*it)->setOnAllDesktops(c->isOnAllDesktops());
 
425
    }
 
426
}
 
427
 
 
428
/*!
 
429
  Sets the client \a c's transient windows' on_all_activities property to \a on_all_desktops.
 
430
 */
 
431
void Workspace::updateOnAllActivitiesOfTransients(Client* c)
 
432
{
 
433
    for (ClientList::ConstIterator it = c->transients().constBegin();
 
434
            it != c->transients().constEnd();
 
435
            ++it) {
 
436
        if ((*it)->isOnAllActivities() != c->isOnAllActivities())
 
437
            (*it)->setOnAllActivities(c->isOnAllActivities());
 
438
    }
 
439
}
 
440
 
 
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)
 
443
{
 
444
    TRANSIENCY_CHECK(NULL);
 
445
    for (ClientList::ConstIterator it = clients.constBegin();
 
446
            it != clients.constEnd();
 
447
            ++it)
 
448
        (*it)->checkTransient(w);
 
449
}
 
450
 
 
451
 
 
452
//****************************************
 
453
// Toplevel
 
454
//****************************************
 
455
 
 
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)
 
459
{
 
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();
 
467
}
 
468
 
 
469
 
 
470
//****************************************
 
471
// Client
 
472
//****************************************
 
473
 
 
474
bool Client::belongToSameApplication(const Client* c1, const Client* c2, bool active_hack)
 
475
{
 
476
    bool same_app = false;
 
477
 
 
478
    // tests that definitely mean they belong together
 
479
    if (c1 == c2)
 
480
        same_app = true;
 
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
 
491
 
 
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))
 
501
        ; // different apps
 
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
 
507
    else
 
508
        same_app = true; // looks like it's the same app
 
509
 
 
510
    return same_app;
 
511
}
 
512
 
 
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)
 
524
{
 
525
    if (c1->isTransient()) {
 
526
        while (c1->transientFor() != NULL)
 
527
            c1 = c1->transientFor();
 
528
        if (c1->groupTransient())
 
529
            return c1->group() == c2->group();
 
530
#if 0
 
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;
 
535
#endif
 
536
    }
 
537
    if (c2->isTransient()) {
 
538
        while (c2->transientFor() != NULL)
 
539
            c2 = c2->transientFor();
 
540
        if (c2->groupTransient())
 
541
            return c1->group() == c2->group();
 
542
#if 0
 
543
        || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2;
 
544
#endif
 
545
    }
 
546
    int pos1 = c1->windowRole().indexOf('#');
 
547
    int pos2 = c2->windowRole().indexOf('#');
 
548
    if ((pos1 >= 0 && pos2 >= 0)
 
549
            ||
 
550
            // hacks here
 
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())
 
556
            return c1 == c2;
 
557
        else
 
558
            return true;
 
559
    }
 
560
    return true;
 
561
}
 
562
 
 
563
/*
 
564
 
 
565
 Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3
 
566
 
 
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.).
 
572
 
 
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.
 
578
 
 
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).
 
585
 
 
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.
 
591
 
 
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.
 
598
 
 
599
 In other words:
 
600
 - non-transient windows     : isTransient() == false
 
601
 - normal transients         : transientFor() != NULL
 
602
 - group transients          : groupTransient() == true
 
603
 
 
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()
 
607
*/
 
608
 
 
609
void Client::readTransient()
 
610
{
 
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);
 
616
    } else {
 
617
        original_transient_for_id = None;
 
618
        new_transient_for_id = verifyTransientFor(None, false);
 
619
    }
 
620
    setTransient(new_transient_for_id);
 
621
}
 
622
 
 
623
void Client::setTransient(Window new_transient_for_id)
 
624
{
 
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
 
636
        if (isTopMenu())
 
637
            workspace()->updateCurrentTopMenu();
 
638
        workspace()->updateClientLayer(this);
 
639
        workspace()->resetUpdateToolWindowsTimer();
 
640
    }
 
641
}
 
642
 
 
643
void Client::removeFromMainClients()
 
644
{
 
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();
 
651
                ++it)
 
652
            (*it)->removeTransient(this);
 
653
    }
 
654
}
 
655
 
 
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
 
659
// related lists.
 
660
void Client::cleanGrouping()
 
661
{
 
662
    TRANSIENCY_CHECK(this);
 
663
//    kDebug(1212) << "CLEANGROUPING:" << this;
 
664
//    for ( ClientList::ConstIterator it = group()->members().begin();
 
665
//         it != group()->members().end();
 
666
//         ++it )
 
667
//        kDebug(1212) << "CL:" << *it;
 
668
//    ClientList mains;
 
669
//    mains = mainClients();
 
670
//    for ( ClientList::ConstIterator it = mains.begin();
 
671
//         it != mains.end();
 
672
//         ++it )
 
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();
 
678
//         ++it )
 
679
//        kDebug(1212) << "CL2:" << *it;
 
680
//    mains = mainClients();
 
681
//    for ( ClientList::ConstIterator it = mains.begin();
 
682
//         it != mains.end();
 
683
//         ++it )
 
684
//        kDebug(1212) << "MN2:" << *it;
 
685
    for (ClientList::ConstIterator it = transients_list.constBegin();
 
686
            it != transients_list.constEnd();
 
687
       ) {
 
688
        if ((*it)->transientFor() == this) {
 
689
            removeTransient(*it);
 
690
            it = transients_list.constBegin(); // restart, just in case something more has changed with the list
 
691
        } else
 
692
            ++it;
 
693
    }
 
694
//    kDebug(1212) << "CLEANGROUPING3:" << this;
 
695
//    for ( ClientList::ConstIterator it = group()->members().begin();
 
696
//         it != group()->members().end();
 
697
//         ++it )
 
698
//        kDebug(1212) << "CL3:" << *it;
 
699
//    mains = mainClients();
 
700
//    for ( ClientList::ConstIterator it = mains.begin();
 
701
//         it != mains.end();
 
702
//         ++it )
 
703
//        kDebug(1212) << "MN3:" << *it;
 
704
    // HACK
 
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);
 
711
    in_group = NULL;
 
712
    for (ClientList::ConstIterator it = group_members.constBegin();
 
713
            it != group_members.constEnd();
 
714
            ++it)
 
715
        (*it)->removeTransient(this);
 
716
//    kDebug(1212) << "CLEANGROUPING4:" << this;
 
717
//    for ( ClientList::ConstIterator it = group_members.begin();
 
718
//         it != group_members.end();
 
719
//         ++it )
 
720
//        kDebug(1212) << "CL4:" << *it;
 
721
}
 
722
 
 
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()
 
728
{
 
729
    TRANSIENCY_CHECK(this);
 
730
    for (ClientList::ConstIterator it1 = group()->members().constBegin();
 
731
            it1 != group()->members().constEnd();
 
732
            ++it1) {
 
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
 
739
            if (*it1 == *it2)
 
740
                continue;
 
741
            for (Client* cl = (*it2)->transientFor();
 
742
                    cl != NULL;
 
743
                    cl = cl->transientFor()) {
 
744
                if (cl == *it1) {
 
745
                    // don't use removeTransient(), that would modify *it2 too
 
746
                    (*it2)->transients_list.removeAll(*it1);
 
747
                    continue;
 
748
                }
 
749
            }
 
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();
 
763
                    ++it3) {
 
764
                if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3)
 
765
                    continue;
 
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);
 
771
                }
 
772
            }
 
773
        }
 
774
    }
 
775
}
 
776
 
 
777
/*!
 
778
  Check that the window is not transient for itself, and similar nonsense.
 
779
 */
 
780
Window Client::verifyTransientFor(Window new_transient_for, bool defined)
 
781
{
 
782
    Window new_property_value = new_transient_for;
 
783
    // make sure splashscreens are shown above all their app's windows, even though
 
784
    // they're in Normal layer
 
785
    if (isSplash() && new_transient_for == None)
 
786
        new_transient_for = rootWindow();
 
787
    if (new_transient_for == None) {
 
788
        if (defined)   // sometimes WM_TRANSIENT_FOR is set to None, instead of root window
 
789
            new_property_value = new_transient_for = rootWindow();
 
790
        else
 
791
            return None;
 
792
    }
 
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();
 
797
    }
 
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;
 
806
        Window* wins = NULL;
 
807
        unsigned int nwins;
 
808
        int r = XQueryTree(display(), new_transient_for, &root_return, &parent_return, &wins, &nwins);
 
809
        if (wins)
 
810
            XFree((void *) wins);
 
811
        if (r == 0)
 
812
            break;
 
813
        new_transient_for = parent_return;
 
814
    }
 
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
 
820
        }
 
821
    } else
 
822
        new_transient_for = before_search; // nice try
 
823
// loop detection
 
824
// group transients cannot cause loops, because they're considered transient only for non-transient
 
825
// windows in the group
 
826
    int count = 20;
 
827
    Window loop_pos = new_transient_for;
 
828
    while (loop_pos != None && loop_pos != rootWindow()) {
 
829
        Client* pos = workspace()->findClient(WindowMatchPredicate(loop_pos));
 
830
        if (pos == NULL)
 
831
            break;
 
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();
 
836
        }
 
837
    }
 
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();
 
842
    }
 
843
    if (new_property_value != original_transient_for_id)
 
844
        XSetTransientForHint(display(), window(), new_property_value);
 
845
    return new_transient_for;
 
846
}
 
847
 
 
848
void Client::addTransient(Client* cl)
 
849
{
 
850
    TRANSIENCY_CHECK(this);
 
851
    assert(!transients_list.contains(cl));
 
852
//    assert( !cl->hasTransient( this, true )); will be fixed in checkGroupTransients()
 
853
    assert(cl != this);
 
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();
 
861
//         ++it )
 
862
//        kDebug(1212) << "AT:" << (*it);
 
863
}
 
864
 
 
865
void Client::removeTransient(Client* cl)
 
866
{
 
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);
 
878
    }
 
879
}
 
880
 
 
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)
 
883
{
 
884
    TRANSIENCY_CHECK(this);
 
885
    if (original_transient_for_id != w)
 
886
        return;
 
887
    w = verifyTransientFor(w, true);
 
888
    setTransient(w);
 
889
}
 
890
 
 
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
 
894
{
 
895
    // checkGroupTransients() uses this to break loops, so hasTransient() must detect them
 
896
    ConstClientList set;
 
897
    return hasTransientInternal(cl, indirect, set);
 
898
}
 
899
 
 
900
bool Client::hasTransientInternal(const Client* cl, bool indirect, ConstClientList& set) const
 
901
{
 
902
    if (cl->transientFor() != NULL) {
 
903
        if (cl->transientFor() == this)
 
904
            return true;
 
905
        if (!indirect)
 
906
            return false;
 
907
        if (set.contains(cl))
 
908
            return false;
 
909
        set.append(cl);
 
910
        return hasTransientInternal(cl->transientFor(), indirect, set);
 
911
    }
 
912
    if (!cl->isTransient())
 
913
        return false;
 
914
    if (group() != cl->group())
 
915
        return false;
 
916
    // cl is group transient, search from top
 
917
    if (transients().contains(const_cast< Client* >(cl)))
 
918
        return true;
 
919
    if (!indirect)
 
920
        return false;
 
921
    if (set.contains(this))
 
922
        return false;
 
923
    set.append(this);
 
924
    for (ClientList::ConstIterator it = transients().constBegin();
 
925
            it != transients().constEnd();
 
926
            ++it)
 
927
        if ((*it)->hasTransientInternal(cl, indirect, set))
 
928
            return true;
 
929
    return false;
 
930
}
 
931
 
 
932
ClientList Client::mainClients() const
 
933
{
 
934
    if (!isTransient())
 
935
        return ClientList();
 
936
    if (transientFor() != NULL)
 
937
        return ClientList() << const_cast< Client* >(transientFor());
 
938
    ClientList result;
 
939
    for (ClientList::ConstIterator it = group()->members().constBegin();
 
940
            it != group()->members().constEnd();
 
941
            ++it)
 
942
        if ((*it)->hasTransient(this, false))
 
943
            result.append(*it);
 
944
    return result;
 
945
}
 
946
 
 
947
ClientList Client::allMainClients() const
 
948
{
 
949
    ClientList result = mainClients();
 
950
    foreach (const Client * cl, result)
 
951
    result += cl->allMainClients();
 
952
    return result;
 
953
}
 
954
 
 
955
Client* Client::findModal(bool allow_itself)
 
956
{
 
957
    for (ClientList::ConstIterator it = transients().constBegin();
 
958
            it != transients().constEnd();
 
959
            ++it)
 
960
        if (Client* ret = (*it)->findModal(true))
 
961
            return ret;
 
962
    if (isModal() && allow_itself)
 
963
        return this;
 
964
    return NULL;
 
965
}
 
966
 
 
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)
 
971
{
 
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);
 
982
        }
 
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();
 
989
        }
 
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);
 
997
        }
 
998
    } else {
 
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);
 
1008
            }
 
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);
 
1020
            }
 
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);
 
1028
                in_group = NULL;
 
1029
            }
 
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);
 
1035
            }
 
1036
        }
 
1037
    }
 
1038
    if (in_group != old_group || force) {
 
1039
        for (ClientList::Iterator it = transients_list.begin();
 
1040
                it != transients_list.end();
 
1041
           ) {
 
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);
 
1045
            else
 
1046
                ++it;
 
1047
        }
 
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();
 
1053
                        ++it)
 
1054
                    (*it)->removeTransient(this);
 
1055
            }
 
1056
            // and make transient for all in the new group
 
1057
            for (ClientList::ConstIterator it = group()->members().constBegin();
 
1058
                    it != group()->members().constEnd();
 
1059
                    ++it) {
 
1060
                if (*it == this)
 
1061
                    break; // this means the window is only transient for windows mapped before it
 
1062
                (*it)->addTransient(this);
 
1063
            }
 
1064
        }
 
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();
 
1069
                ++it) {
 
1070
            if (!(*it)->isSplash())
 
1071
                continue;
 
1072
            if (!(*it)->groupTransient())
 
1073
                continue;
 
1074
            if (*it == this || hasTransient(*it, true))    // TODO indirect?
 
1075
                continue;
 
1076
            addTransient(*it);
 
1077
        }
 
1078
    }
 
1079
    if (old_group != NULL)
 
1080
        old_group->deref(); // can be now deleted if empty
 
1081
    checkGroupTransients();
 
1082
    checkActiveModal();
 
1083
    workspace()->updateClientLayer(this);
 
1084
}
 
1085
 
 
1086
// used by Workspace::findClientLeaderGroup()
 
1087
void Client::changeClientLeaderGroup(Group* gr)
 
1088
{
 
1089
    // transientFor() != NULL are in the group of their mainwindow, so keep them there
 
1090
    if (transientFor() != NULL)
 
1091
        return;
 
1092
    // also don't change the group for window which have group set
 
1093
    if (window_group)
 
1094
        return;
 
1095
    checkGroup(gr);   // change group
 
1096
}
 
1097
 
 
1098
bool Client::check_active_modal = false;
 
1099
 
 
1100
void Client::checkActiveModal()
 
1101
{
 
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);
 
1112
        }
 
1113
        check_modal->check_active_modal = false;
 
1114
    }
 
1115
}
 
1116
 
 
1117
} // namespace