• Skip to content
  • Skip to link menu
Trinity API Reference
  • Trinity API Reference
  • twin
 

twin

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

twin

Skip menu "twin"
  • Main Page
  • Alphabetical List
  • Class List
  • File List
  • Class Members

twin

Skip menu "twin"
  • kate
  • libkonq
  • twin
  •   lib
Generated for twin by doxygen 1.9.4
This website is maintained by Timothy Pearson.