~ubuntu-branches/ubuntu/lucid/kdebase/lucid

« back to all changes in this revision

Viewing changes to kwin/group.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Jonathan Riddell
  • Date: 2008-05-27 12:09:48 UTC
  • mfrom: (1.1.13 upstream)
  • Revision ID: james.westby@ubuntu.com-20080527120948-dottsyd5rcwhzd36
Tags: 4:4.0.80-1ubuntu1
* Merge with Debian
 - remove 97_fix_target_link_libraries.diff
 - Add replaces/conflicts on -kde4 packages

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