• 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 //#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 <tdestartupinfo.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 TQString transiencyCheckStartBt;
145 static const Client* transiencyCheckClient;
146 static int transiencyCheck = 0;
147 
148 static void startTransiencyCheck( const TQString& 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 TQString& 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( tqt_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 TQPixmap Group::icon() const
224  {
225  if( leader_client != NULL )
226  return leader_client->icon();
227  else if( leader_wid != None )
228  {
229  TQPixmap ic;
230  Client::readIcons( leader_wid, &ic, NULL );
231  return ic;
232  }
233  return TQPixmap();
234  }
235 
236 TQPixmap Group::miniIcon() const
237  {
238  if( leader_client != NULL )
239  return leader_client->miniIcon();
240  else if( leader_wid != None )
241  {
242  TQPixmap ic;
243  Client::readIcons( leader_wid, NULL, &ic );
244  return ic;
245  }
246  return TQPixmap();
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 
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( tqstrncmp( c1->resourceClass(), "xv", 2 ) == 0 && c1->resourceName() == "xv" )
434  return tqstrncmp( 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). TDEMainWindow sets
483 // window role this way by default, and different TDEMainWindow
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( tqt_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  (*it2)->transients_list.remove( *it1 );
751  if( (*it3)->hasTransient( *it2, true ))
752  (*it3)->transients_list.remove( *it1 );
753  }
754  }
755  }
756  }
757  }
758 
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 twin 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(tqt_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( tqt_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

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.8.1.2
This website is maintained by Timothy Pearson.