kwin Library API Documentation

layers.cpp

00001 /*****************************************************************
00002  KWin - the KDE window manager
00003  This file is part of the KDE project.
00004 
00005 Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
00006 Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
00007 
00008 You can Freely distribute this program under the GNU General Public
00009 License. See the file "COPYING" for the exact licensing terms.
00010 ******************************************************************/
00011 
00012 // SELI zmenit doc
00013 
00014 /*
00015 
00016  This file contains things relevant to stacking order and layers.
00017 
00018  Design:
00019 
00020  Normal unconstrained stacking order, as requested by the user (by clicking
00021  on windows to raise them, etc.), is in Workspace::unconstrained_stacking_order.
00022  That list shouldn't be used at all, except for building
00023  Workspace::stacking_order. The building is done
00024  in Workspace::constrainedStackingOrder(). Only Workspace::stackingOrder() should
00025  be used to get the stacking order, because it also checks the stacking order
00026  is up to date.
00027  All clients are also stored in Workspace::clients (except for isDesktop() clients,
00028  as those are very special, and are stored in Workspace::desktops), in the order
00029  the clients were created.
00030 
00031  Every window has one layer assigned in which it is. There are 6 layers,
00032  from bottom : DesktopLayer, BelowLayer, NormalLayer, DockLayer, AboveLayer
00033  and ActiveLayer (see also NETWM sect.7.10.). The layer a window is in depends
00034  on the window type, and on other things like whether the window is active.
00035 
00036  NET::Splash clients belong to the Normal layer. NET::TopMenu clients
00037  belong to Dock layer. Clients that are both NET::Dock and NET::KeepBelow
00038  are in the Normal layer in order to keep the 'allow window to cover
00039  the panel' Kicker setting to work as intended (this may look like a slight
00040  spec violation, but a) I have no better idea, b) the spec allows adjusting
00041  the stacking order if the WM thinks it's a good idea . We put all
00042  NET::KeepAbove above all Docks too, even though the spec suggests putting
00043  them in the same layer.
00044 
00045  Most transients are in the same layer as their mainwindow,
00046  see Workspace::constrainedStackingOrder(), they may also be in higher layers, but
00047  they should never be below their mainwindow.
00048 
00049  When some client attribute changes (above/below flag, transiency...),
00050  Workspace::updateClientLayer() should be called in order to make
00051  sure it's moved to the appropriate layer ClientList if needed.
00052 
00053  Currently the things that affect client in which layer a client
00054  belongs: KeepAbove/Keep Below flags, window type, fullscreen
00055  state and whether the client is active, mainclient (transiency).
00056 
00057  Make sure updateStackingOrder() is called in order to make
00058  Workspace::stackingOrder() up to date and propagated to the world.
00059  Using Workspace::blockStackingUpdates() (or the StackingUpdatesBlocker
00060  helper class) it's possible to temporarily disable updates
00061  and the stacking order will be updated once after it's allowed again.
00062 
00063 */
00064 
00065 #include <assert.h>
00066 
00067 #include "utils.h"
00068 #include "client.h"
00069 #include "workspace.h"
00070 #include "tabbox.h"
00071 #include "popupinfo.h"
00072 #include "group.h"
00073 #include <kdebug.h>
00074 
00075 namespace KWinInternal
00076 {
00077 
00078 //*******************************
00079 // Workspace
00080 //*******************************
00081 
00082 void Workspace::updateClientLayer( Client* c )
00083     {
00084     if( c == NULL )
00085         return;
00086     if( c->layer() == c->belongsToLayer())
00087         return;
00088     StackingUpdatesBlocker blocker( this );
00089     c->invalidateLayer(); // invalidate, will be updated when doing restacking
00090     for( ClientList::ConstIterator it = c->transients().begin();
00091          it != c->transients().end();
00092          ++it )
00093         updateClientLayer( *it );
00094     }
00095 
00096 void Workspace::updateStackingOrder( bool propagate_new_clients )
00097     {
00098     if( block_stacking_updates > 0 )
00099         {
00100         blocked_propagating_new_clients |= propagate_new_clients;
00101         return;
00102         }
00103     ClientList new_stacking_order = constrainedStackingOrder();
00104     bool changed = ( new_stacking_order != stacking_order );
00105     stacking_order = new_stacking_order;
00106 #if 0
00107     kdDebug() << "stacking:" << changed << endl;
00108     if( changed || propagate_new_clients )
00109         {
00110         for( ClientList::ConstIterator it = stacking_order.begin();
00111              it != stacking_order.end();
00112              ++it )
00113             kdDebug() << (void*)(*it) << *it << endl;
00114         }
00115 #endif
00116     if( changed || propagate_new_clients )
00117         propagateClients( propagate_new_clients );
00118     }
00119 
00124 void Workspace::propagateClients( bool propagate_new_clients )
00125     {
00126     Window *cl; // MW we should not assume WId and Window to be compatible
00127                                 // when passig pointers around.
00128 
00129     // restack the windows according to the stacking order
00130     Window* new_stack = new Window[ stacking_order.count() + 2 ];
00131     int pos = 0;
00132     // Stack all windows under the support window. The support window is
00133     // not used for anything (besides the NETWM property), and it's not shown,
00134     // but it was lowered after kwin startup. Stacking all clients below
00135     // it ensures that no client will be ever shown above override-redirect
00136     // windows (e.g. popups).
00137     new_stack[ pos++ ] = supportWindow->winId();
00138     int topmenu_space_pos = 1; // not 0, that's supportWindow !!!
00139     for( ClientList::ConstIterator it = stacking_order.fromLast();
00140          it != stacking_order.end();
00141          --it )
00142         {
00143         new_stack[ pos++ ] = (*it)->frameId();
00144         if( (*it)->isTopMenu())
00145             topmenu_space_pos = pos;
00146         }
00147     if( topmenu_space != NULL )
00148         { // make sure the topmenu space is below all topmenus, if there are any
00149         for( int i = pos;
00150              i > topmenu_space_pos;
00151              --i )
00152             new_stack[ i ] = new_stack[ i - 1 ];
00153         new_stack[ topmenu_space_pos ] = topmenu_space->winId();
00154         ++pos;
00155         }
00156     // TODO isn't it too inefficient to restart always all clients?
00157     // TODO don't restack not visible windows?
00158     assert( new_stack[ 0 ] = supportWindow->winId());
00159     XRestackWindows(qt_xdisplay(), new_stack, pos);
00160     delete [] new_stack;
00161 
00162     if ( propagate_new_clients )
00163         {
00164         cl = new Window[ desktops.count() + clients.count()];
00165         pos = 0;
00166     // TODO this is still not completely in the map order
00167         for ( ClientList::ConstIterator it = desktops.begin(); it != desktops.end(); ++it )
00168             cl[pos++] =  (*it)->window();
00169         for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it )
00170             cl[pos++] =  (*it)->window();
00171         rootInfo->setClientList( cl, pos );
00172         delete [] cl;
00173         }
00174 
00175     cl = new Window[ stacking_order.count()];
00176     pos = 0;
00177     for ( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it)
00178         cl[pos++] =  (*it)->window();
00179     rootInfo->setClientListStacking( cl, pos );
00180     delete [] cl;
00181 
00182 #if 0 // not necessary anymore?
00183     if ( tab_box->isVisible() )
00184         tab_box->raise();
00185 
00186     if ( popupinfo->isVisible() )
00187         popupinfo->raise();
00188 
00189     raiseElectricBorders();
00190 #endif
00191     }
00192 
00193 
00199 // TODO misleading name for this method
00200 Client* Workspace::topClientOnDesktop( int desktop, bool unconstrained ) const
00201     {
00202 // TODO    Q_ASSERT( block_stacking_updates == 0 );
00203     ClientList::ConstIterator begin, end;
00204     if( !unconstrained )
00205         {
00206         begin = stacking_order.fromLast();
00207         end = stacking_order.end();
00208         }
00209     else
00210         {
00211         begin = unconstrained_stacking_order.fromLast();
00212         end = unconstrained_stacking_order.end();
00213         }
00214     for( ClientList::ConstIterator it = begin;
00215         it != end;
00216         --it )
00217         {
00218         if ( (*it)->isOnDesktop( desktop ) && !(*it)->isSpecialWindow()
00219             && (*it)->isShown( false ) && (*it)->wantsTabFocus())
00220             return *it;
00221         }
00222     return 0;
00223     }
00224 
00225 Client* Workspace::findDesktop( bool topmost, int desktop ) const
00226     {
00227 // TODO    Q_ASSERT( block_stacking_updates == 0 );
00228     if( topmost )
00229         {
00230         for ( ClientList::ConstIterator it = stacking_order.fromLast(); it != stacking_order.end(); --it)
00231             {
00232             if ( (*it)->isOnDesktop( desktop ) && (*it)->isDesktop()
00233                 && (*it)->isShown( true ))
00234                 return *it;
00235             }
00236         }
00237     else // bottom-most
00238         {
00239         for ( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it)
00240             {
00241             if ( (*it)->isOnDesktop( desktop ) && (*it)->isDesktop()
00242                 && (*it)->isShown( true ))
00243                 return *it;
00244             }
00245         }
00246     return NULL;
00247     }
00248 
00249 void Workspace::raiseOrLowerClient( Client *c)
00250     {
00251     if (!c) return;
00252     Client* topmost = NULL;
00253 // TODO    Q_ASSERT( block_stacking_updates == 0 );
00254     if ( most_recently_raised && stacking_order.contains( most_recently_raised ) &&
00255          most_recently_raised->isShown( true ) && c->isOnCurrentDesktop())
00256         topmost = most_recently_raised;
00257     else
00258         topmost = topClientOnDesktop( c->isOnAllDesktops() ? currentDesktop() : c->desktop());
00259 
00260     if( c == topmost)
00261         lowerClient(c);
00262     else
00263         raiseClient(c);
00264     }
00265 
00266 
00267 void Workspace::lowerClient( Client* c )
00268     {
00269     if ( !c )
00270         return;
00271 
00272     c->cancelAutoRaise();
00273 
00274     StackingUpdatesBlocker blocker( this );
00275 
00276     unconstrained_stacking_order.remove( c );
00277     unconstrained_stacking_order.prepend( c );
00278     if( c->isTransient())
00279         {
00280         // lower also mainclients, in their reversed stacking order
00281         ClientList mainclients = ensureStackingOrder( c->mainClients());
00282         for( ClientList::ConstIterator it = mainclients.fromLast();
00283              it != mainclients.end();
00284              ++it )
00285             lowerClient( *it );
00286         }
00287 
00288     if ( c == most_recently_raised )
00289         most_recently_raised = 0;
00290     }
00291 
00292 void Workspace::lowerClientWithinApplication( Client* c )
00293     {
00294     if ( !c )
00295         return;
00296 
00297     c->cancelAutoRaise();
00298 
00299     StackingUpdatesBlocker blocker( this );
00300 
00301     unconstrained_stacking_order.remove( c );
00302     bool lowered = false;
00303     // first try to put it below the bottom-most window of the application
00304     for( ClientList::Iterator it = unconstrained_stacking_order.begin();
00305          it != unconstrained_stacking_order.end();
00306          ++it )
00307         if( Client::belongToSameApplication( *it, c ))
00308             {
00309             unconstrained_stacking_order.insert( it, c );
00310             lowered = true;
00311             break;
00312             }
00313     if( !lowered )
00314         unconstrained_stacking_order.prepend( c );
00315     // ignore mainwindows
00316     }
00317 
00318 void Workspace::raiseClient( Client* c )
00319     {
00320     if ( !c )
00321         return;
00322 
00323     c->cancelAutoRaise();
00324 
00325     StackingUpdatesBlocker blocker( this );
00326 
00327     if( c->isTransient())
00328         {
00329         ClientList mainclients = ensureStackingOrder( c->mainClients());
00330         for( ClientList::ConstIterator it = mainclients.begin();
00331              it != mainclients.end();
00332              ++it )
00333             raiseClient( *it );
00334         }
00335 
00336     unconstrained_stacking_order.remove( c );
00337     unconstrained_stacking_order.append( c );
00338 
00339     if( !c->isSpecialWindow())
00340         most_recently_raised = c;
00341     }
00342 
00343 void Workspace::raiseClientWithinApplication( Client* c )
00344     {
00345     if ( !c )
00346         return;
00347 
00348     c->cancelAutoRaise();
00349 
00350     StackingUpdatesBlocker blocker( this );
00351     // ignore mainwindows
00352     
00353     // first try to put it above the top-most window of the application
00354     for( ClientList::Iterator it = unconstrained_stacking_order.fromLast();
00355          it != unconstrained_stacking_order.end();
00356          --it )
00357         {
00358         if( *it == c ) // don't lower it just because it asked to be raised
00359             return;
00360         if( Client::belongToSameApplication( *it, c ))
00361             {
00362             unconstrained_stacking_order.remove( c );
00363             ++it; // insert after the found one
00364             unconstrained_stacking_order.insert( it, c );
00365             return;
00366             }
00367         }
00368     }
00369 
00370 void Workspace::raiseClientRequest( Client* c )
00371     {
00372     if( allowFullClientRaising( c ))
00373         raiseClient( c );
00374     else
00375         {
00376         raiseClientWithinApplication( c );
00377         c->demandAttention();
00378         }
00379     }
00380 
00381 void Workspace::lowerClientRequest( Client* c )
00382     {
00383     // If the client has support for all this focus stealing prevention stuff,
00384     // do only lowering within the application, as that's the more logical
00385     // variant of lowering when application requests it.
00386     // No demanding of attention here of course.
00387     if( c->hasUserTimeSupport())
00388         lowerClientWithinApplication( c );
00389     else
00390         lowerClient( c );
00391     }
00392 
00393 void Workspace::restackClientUnderActive( Client* c )
00394     {
00395     if( !active_client || active_client == c )
00396         {
00397         raiseClient( c );
00398         return;
00399         }
00400 
00401     // put in the stacking order below _all_ windows belonging to the active application
00402     assert( unconstrained_stacking_order.contains( active_client ));
00403     for( ClientList::Iterator it = unconstrained_stacking_order.begin();
00404          it != unconstrained_stacking_order.end();
00405          ++it )
00406         { // TODO ignore topmenus?
00407         if( Client::belongToSameApplication( active_client, *it ))
00408             {
00409             if( *it != c )
00410                 {
00411                 unconstrained_stacking_order.remove( c );
00412                 unconstrained_stacking_order.insert( it, c );
00413                 }
00414             break;
00415             }
00416         }
00417     assert( unconstrained_stacking_order.contains( c ));
00418     if( c->wantsTabFocus() && focus_chain.contains( active_client ))
00419         {
00420         // also put in focus_chain after all windows belonging to the active application
00421         focus_chain.remove( c );
00422         for( ClientList::Iterator it = focus_chain.fromLast();
00423              it != focus_chain.end();
00424              --it )
00425             {
00426             if( Client::belongToSameApplication( active_client, *it ))
00427                 {
00428                 focus_chain.insert( it, c );
00429                 break;
00430                 }
00431             }
00432         }
00433     updateStackingOrder();
00434     }
00435 
00436 void Workspace::circulateDesktopApplications()
00437     {
00438     if ( desktops.count() > 1 )
00439         {
00440         bool change_active = activeClient()->isDesktop();
00441         raiseClient( findDesktop( false, currentDesktop()));
00442         if( change_active ) // if the previously topmost Desktop was active, activate this new one
00443             activateClient( findDesktop( true, currentDesktop()));
00444         }
00445     // if there's no active client, make desktop the active one
00446     if( desktops.count() > 0 && activeClient() == NULL && should_get_focus.count() == 0 )
00447         activateClient( findDesktop( true, currentDesktop()));
00448     }
00449 
00450 
00454 ClientList Workspace::constrainedStackingOrder()
00455     {
00456     ClientList layer[ NumLayers ];
00457 
00458 #if 0
00459     kdDebug() << "stacking1:" << endl;
00460 #endif
00461     // build the order from layers
00462     for( ClientList::ConstIterator it = unconstrained_stacking_order.begin();
00463          it != unconstrained_stacking_order.end();
00464          ++it )
00465         {
00466 #if 0
00467         kdDebug() << (void*)(*it) << *it << endl;
00468 #endif
00469         layer[ (*it)->layer() ].append( *it );
00470         }
00471     ClientList stacking;    
00472     for( Layer lay = FirstLayer;
00473          lay < NumLayers;
00474          ++lay )    
00475         stacking += layer[ lay ];
00476 #if 0
00477     kdDebug() << "stacking2:" << endl;
00478     for( ClientList::ConstIterator it = stacking.begin();
00479          it != stacking.end();
00480          ++it )
00481         kdDebug() << (void*)(*it) << *it << endl;
00482 #endif
00483     // now keep transients above their mainwindows
00484     // TODO this could(?) use some optimization
00485     for( ClientList::Iterator it = stacking.fromLast();
00486          it != stacking.end();
00487          )
00488         {
00489         if( !(*it)->isTransient())
00490             {
00491             --it;
00492             continue;
00493             }
00494         ClientList::Iterator it2 = stacking.end();
00495         if( (*it)->groupTransient())
00496             {
00497             if( (*it)->group()->members().count() > 0 )
00498                 { // find topmost client this one is transient for
00499                 for( it2 = stacking.fromLast();
00500                      it2 != stacking.end();
00501                      --it2 )
00502                     {
00503                     if( *it2 == *it )
00504                         {
00505                         it2 = stacking.end(); // don't reorder
00506                         break;
00507                         }
00508                     if( (*it2)->hasTransient( *it, true ) && keepTransientAbove( *it2, *it ))
00509                         break;
00510                     }
00511                 } // else it2 remains pointing at stacking.end()
00512             }
00513         else
00514             {
00515             for( it2 = stacking.fromLast();
00516                  it2 != stacking.end();
00517                  --it2 )
00518                 {
00519                 if( *it2 == *it )
00520                     {
00521                     it2 = stacking.end(); // don't reorder
00522                     break;
00523                     }
00524                 if( *it2 == (*it)->transientFor() && keepTransientAbove( *it2, *it ))
00525                     break;
00526                 }
00527             }
00528 //        kdDebug() << "STACK:" << (*it) << ":" << ( it2 == stacking.end() ? ((Client*)0) : (*it2)) << endl;
00529         if( it2 == stacking.end())
00530             {
00531             --it;
00532             continue;
00533             }
00534         Client* current = *it;
00535         ClientList::Iterator remove_it = it;
00536         --it;
00537         stacking.remove( remove_it );
00538         if( !current->transients().isEmpty())  // this one now can be possibly above its transients,
00539             it = it2; // so go again higher in the stack order and possibly move those transients again
00540         ++it2; // insert after the mainwindow, it's ok if it2 is now stacking.end()
00541         stacking.insert( it2, current );
00542         }
00543 #if 0
00544     kdDebug() << "stacking3:" << endl;
00545     for( ClientList::ConstIterator it = stacking.begin();
00546          it != stacking.end();
00547          ++it )
00548         kdDebug() << (void*)(*it) << *it << endl;
00549     kdDebug() << "\n\n" << endl;
00550 #endif
00551     return stacking;
00552     }
00553 
00554 void Workspace::blockStackingUpdates( bool block )
00555     {
00556     if( block )
00557         {
00558         if( block_stacking_updates == 0 )
00559             blocked_propagating_new_clients = false;
00560         ++block_stacking_updates;
00561         }
00562     else // !block
00563         if( --block_stacking_updates == 0 )
00564             updateStackingOrder( blocked_propagating_new_clients );
00565     }
00566 
00567 // Ensure list is in stacking order
00568 ClientList Workspace::ensureStackingOrder( const ClientList& list ) const
00569     {
00570 // TODO    Q_ASSERT( block_stacking_updates == 0 );
00571     if( list.count() < 2 )
00572         return list;
00573     // TODO is this worth optimizing?
00574     ClientList result = list;
00575     for( ClientList::ConstIterator it = stacking_order.begin();
00576          it != stacking_order.end();
00577          ++it )
00578         if( result.remove( *it ) != 0 )
00579             result.append( *it );
00580     return result;
00581     }
00582 
00583 // check whether a transient should be actually kept above its mainwindow
00584 // there may be some special cases where this rule shouldn't be enfored
00585 bool Workspace::keepTransientAbove( const Client* mainwindow, const Client* transient )
00586     {
00587     // When topmenu's mainwindow becomes active, topmenu is raised and shown.
00588     // They also belong to the Dock layer. This makes them to be very high.
00589     // Therefore don't keep group transients above them, otherwise this would move
00590     // group transients way too high.
00591     if( mainwindow->isTopMenu() && transient->groupTransient())
00592         return false;
00593     return true;
00594     // #63223 - don't keep transients above docks, because the dock is kept high,
00595     // and e.g. dialogs for them would be too high too
00596     // TODO this doesn't really work - the transient should be raised after clicking
00597     // on the dock, but docks don't become active after clicking them
00598     if( mainwindow->isDock() && !mainwindow->keepBelow()
00599         && !mainwindow->isActive() && !transient->isActive()) // TODO !w->group()->isActive() ???
00600         return false;
00601     return true;
00602     }
00603 
00604 //*******************************
00605 // Client
00606 //*******************************
00607 
00608 void Client::restackWindow( Window /*above TODO */, int detail, NET::RequestSource source, bool send_event )
00609     {
00610     switch ( detail )
00611         {
00612         case Above:
00613         case TopIf:
00614             if( source == NET::FromTool )
00615                 workspace()->raiseClient( this );
00616             else
00617                 workspace()->raiseClientRequest( this );
00618             break;
00619         case Below:
00620         case BottomIf:
00621             if( source == NET::FromTool )
00622                 workspace()->lowerClient( this );
00623             else
00624                 workspace()->lowerClientRequest( this );
00625             break;
00626         case Opposite:
00627         default:
00628             break;
00629         }
00630     if( send_event )
00631         sendSyntheticConfigureNotify();
00632     }
00633     
00634 void Client::setKeepAbove( bool b )
00635     {
00636     if ( b == keepAbove() )
00637         return;
00638     setKeepBelow( false );
00639     keep_above = b;
00640     info->setState( b ? NET::KeepAbove : 0, NET::KeepAbove );
00641     // TODO emit a signal about the change to the style plugin
00642     workspace()->updateClientLayer( this );
00643     }
00644 
00645 void Client::setKeepBelow( bool b )
00646     {
00647     if ( b == keepBelow() )
00648         return;
00649     setKeepAbove( false );
00650     keep_below = b;
00651     info->setState( b ? NET::KeepBelow : 0, NET::KeepBelow );
00652     workspace()->updateClientLayer( this );
00653     }
00654 
00655 Layer Client::layer() const
00656     {
00657     if( in_layer == UnknownLayer )
00658         const_cast< Client* >( this )->in_layer = belongsToLayer();
00659     return in_layer;
00660     }
00661 
00662 Layer Client::belongsToLayer() const
00663     {
00664     if( isDesktop())
00665         return DesktopLayer;
00666     if( isSplash())         // no damn annoying splashscreens
00667         return NormalLayer; // getting in the way of everything else
00668     if( isDock() && keepBelow())
00669         // slight hack for the 'allow window to cover panel' Kicker setting
00670         // don't move keepbelow docks below normal window, but only to the same
00671         // layer, so that both may be raised to cover the other
00672         return NormalLayer;
00673     if( keepBelow())
00674         return BelowLayer;
00675     if( isDock() && !keepBelow())
00676         return DockLayer;
00677     if( isTopMenu())
00678         return DockLayer;
00679     // only raise fullscreen above docks if it's the topmost window in unconstrained stacking order,
00680     // i.e. the window set to be topmost by the user
00681     bool raise_special_active_windows = ( workspace()->topClientOnDesktop( desktop(), true ) == this );
00682     if( keepAbove())
00683         return AboveLayer;
00684     if( isFullScreen() && workspace()->activeClient() != NULL
00685         && ( workspace()->activeClient() == this || this->hasTransient( workspace()->activeClient(), true ))
00686         && raise_special_active_windows )
00687         return ActiveLayer;
00688     return NormalLayer;
00689     }
00690 
00691 } // namespace
KDE Logo
This file is part of the documentation for kwin Library Version 3.2.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Apr 29 21:20:52 2004 by doxygen 1.2.15 written by Dimitri van Heesch, © 1997-2003