• 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 
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 #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 
33 namespace 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
48 static bool transiencyCheckNonExistent = false;
49 
50 bool 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 
142 static TQString transiencyCheckStartBt;
143 static const Client* transiencyCheckClient;
144 static int transiencyCheck = 0;
145 
146 static 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  }
156 static 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  }
169 class TransiencyChecker
170  {
171  public:
172  TransiencyChecker( const TQString& bt, const Client*c ) { startTransiencyCheck( bt, c, false ); }
173  ~TransiencyChecker() { checkTransiency(); }
174  };
175 
176 void 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 
188 void checkNonExistentClients()
189  {
190  }
191 
192 #endif
193 
194 //********************************************
195 // Group
196 //********************************************
197 
198 Group::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 
216 Group::~Group()
217  {
218  delete leader_info;
219  }
220 
221 TQPixmap 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 
234 TQPixmap 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 
247 void 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 
255 void 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 
273 void Group::ref()
274  {
275  ++refcount;
276  }
277 
278 void Group::deref()
279  {
280  if( --refcount == 0 && _members.isEmpty())
281  {
282  workspace()->removeGroup( this, Allowed );
283  delete this;
284  }
285  }
286 
287 void Group::gotLeader( Client* leader_P )
288  {
289  assert( leader_P->window() == leader_wid );
290  leader_client = leader_P;
291  }
292 
293 void 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 
304 void Group::getIcons()
305  {
306  // TODO - also needs adding the flag to NETWinInfo
307  }
308 
309 //***************************************
310 // Workspace
311 //***************************************
312 
313 Group* 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.
326 Group* 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 
362 void 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 
399 void 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.
411 void 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
428 bool 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 
439 bool 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 ).
488 bool 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 
577 void 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 
594 void 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 
615 void 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.
633 void 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().
703 void 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 
760 Window 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 
833 void 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 
850 void 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.
868 void 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
879 bool 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 
886 bool 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 
919 ClientList 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 
934 Client* 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.
949 void 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()
1084 void 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 
1095 bool Client::check_active_modal = false;
1096 
1097 void 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.1
This website is maintained by Timothy Pearson.