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     restackClientUnderActive( c );
00369     }
00370 
00371 void Workspace::raiseClientRequest( Client* c )
00372     {
00373     if( allowFullClientRaising( c ))
00374         raiseClient( c );
00375     else
00376         {
00377         raiseClientWithinApplication( c );
00378         c->demandAttention();
00379         }
00380     }
00381 
00382 void Workspace::lowerClientRequest( Client* c )
00383     {
00384     // If the client has support for all this focus stealing prevention stuff,
00385     // do only lowering within the application, as that's the more logical
00386     // variant of lowering when application requests it.
00387     // No demanding of attention here of course.
00388     if( c->hasUserTimeSupport())
00389         lowerClientWithinApplication( c );
00390     else
00391         lowerClient( c );
00392     }
00393 
00394 void Workspace::restackClientUnderActive( Client* c )
00395     {
00396     if( !active_client || active_client == c )
00397         {
00398         raiseClient( c );
00399         return;
00400         }
00401 
00402     // put in the stacking order below _all_ windows belonging to the active application
00403     assert( unconstrained_stacking_order.contains( active_client ));
00404     for( ClientList::Iterator it = unconstrained_stacking_order.begin();
00405          it != unconstrained_stacking_order.end();
00406          ++it )
00407         { // TODO ignore topmenus?
00408         if( Client::belongToSameApplication( active_client, *it ))
00409             {
00410             if( *it != c )
00411                 {
00412                 unconstrained_stacking_order.remove( c );
00413                 unconstrained_stacking_order.insert( it, c );
00414                 }
00415             break;
00416             }
00417         }
00418     assert( unconstrained_stacking_order.contains( c ));
00419     if( c->wantsTabFocus() && focus_chain.contains( active_client ))
00420         {
00421         // also put in focus_chain after all windows belonging to the active application
00422         focus_chain.remove( c );
00423         for( ClientList::Iterator it = focus_chain.fromLast();
00424              it != focus_chain.end();
00425              --it )
00426             {
00427             if( Client::belongToSameApplication( active_client, *it ))
00428                 {
00429                 focus_chain.insert( it, c );
00430                 break;
00431                 }
00432             }
00433         }
00434     updateStackingOrder();
00435     }
00436 
00437 void Workspace::circulateDesktopApplications()
00438     {
00439     if ( desktops.count() > 1 )
00440         {
00441         bool change_active = activeClient()->isDesktop();
00442         raiseClient( findDesktop( false, currentDesktop()));
00443         if( change_active ) // if the previously topmost Desktop was active, activate this new one
00444             activateClient( findDesktop( true, currentDesktop()));
00445         }
00446     // if there's no active client, make desktop the active one
00447     if( desktops.count() > 0 && activeClient() == NULL && should_get_focus.count() == 0 )
00448         activateClient( findDesktop( true, currentDesktop()));
00449     }
00450 
00451 
00455 ClientList Workspace::constrainedStackingOrder()
00456     {
00457     ClientList layer[ NumLayers ];
00458 
00459 #if 0
00460     kdDebug() << "stacking1:" << endl;
00461 #endif
00462     // build the order from layers
00463     for( ClientList::ConstIterator it = unconstrained_stacking_order.begin();
00464          it != unconstrained_stacking_order.end();
00465          ++it )
00466         {
00467 #if 0
00468         kdDebug() << (void*)(*it) << *it << endl;
00469 #endif
00470         layer[ (*it)->layer() ].append( *it );
00471         }
00472     ClientList stacking;    
00473     for( Layer lay = FirstLayer;
00474          lay < NumLayers;
00475          ++lay )    
00476         stacking += layer[ lay ];
00477 #if 0
00478     kdDebug() << "stacking2:" << endl;
00479     for( ClientList::ConstIterator it = stacking.begin();
00480          it != stacking.end();
00481          ++it )
00482         kdDebug() << (void*)(*it) << *it << endl;
00483 #endif
00484     // now keep transients above their mainwindows
00485     // TODO this could(?) use some optimization
00486     for( ClientList::Iterator it = stacking.fromLast();
00487          it != stacking.end();
00488          )
00489         {
00490         if( !(*it)->isTransient())
00491             {
00492             --it;
00493             continue;
00494             }
00495         ClientList::Iterator it2 = stacking.end();
00496         if( (*it)->groupTransient())
00497             {
00498             if( (*it)->group()->members().count() > 0 )
00499                 { // find topmost client this one is transient for
00500                 for( it2 = stacking.fromLast();
00501                      it2 != stacking.end();
00502                      --it2 )
00503                     {
00504                     if( *it2 == *it )
00505                         {
00506                         it2 = stacking.end(); // don't reorder
00507                         break;
00508                         }
00509                     if( (*it2)->hasTransient( *it, true ) && keepTransientAbove( *it2, *it ))
00510                         break;
00511                     }
00512                 } // else it2 remains pointing at stacking.end()
00513             }
00514         else
00515             {
00516             for( it2 = stacking.fromLast();
00517                  it2 != stacking.end();
00518                  --it2 )
00519                 {
00520                 if( *it2 == *it )
00521                     {
00522                     it2 = stacking.end(); // don't reorder
00523                     break;
00524                     }
00525                 if( *it2 == (*it)->transientFor() && keepTransientAbove( *it2, *it ))
00526                     break;
00527                 }
00528             }
00529 //        kdDebug() << "STACK:" << (*it) << ":" << ( it2 == stacking.end() ? ((Client*)0) : (*it2)) << endl;
00530         if( it2 == stacking.end())
00531             {
00532             --it;
00533             continue;
00534             }
00535         Client* current = *it;
00536         ClientList::Iterator remove_it = it;
00537         --it;
00538         stacking.remove( remove_it );
00539         if( !current->transients().isEmpty())  // this one now can be possibly above its transients,
00540             it = it2; // so go again higher in the stack order and possibly move those transients again
00541         ++it2; // insert after the mainwindow, it's ok if it2 is now stacking.end()
00542         stacking.insert( it2, current );
00543         }
00544 #if 0
00545     kdDebug() << "stacking3:" << endl;
00546     for( ClientList::ConstIterator it = stacking.begin();
00547          it != stacking.end();
00548          ++it )
00549         kdDebug() << (void*)(*it) << *it << endl;
00550     kdDebug() << "\n\n" << endl;
00551 #endif
00552     return stacking;
00553     }
00554 
00555 void Workspace::blockStackingUpdates( bool block )
00556     {
00557     if( block )
00558         {
00559         if( block_stacking_updates == 0 )
00560             blocked_propagating_new_clients = false;
00561         ++block_stacking_updates;
00562         }
00563     else // !block
00564         if( --block_stacking_updates == 0 )
00565             updateStackingOrder( blocked_propagating_new_clients );
00566     }
00567 
00568 // Ensure list is in stacking order
00569 ClientList Workspace::ensureStackingOrder( const ClientList& list ) const
00570     {
00571 // TODO    Q_ASSERT( block_stacking_updates == 0 );
00572     if( list.count() < 2 )
00573         return list;
00574     // TODO is this worth optimizing?
00575     ClientList result = list;
00576     for( ClientList::ConstIterator it = stacking_order.begin();
00577          it != stacking_order.end();
00578          ++it )
00579         if( result.remove( *it ) != 0 )
00580             result.append( *it );
00581     return result;
00582     }
00583 
00584 // check whether a transient should be actually kept above its mainwindow
00585 // there may be some special cases where this rule shouldn't be enfored
00586 bool Workspace::keepTransientAbove( const Client* mainwindow, const Client* transient )
00587     {
00588     // When topmenu's mainwindow becomes active, topmenu is raised and shown.
00589     // They also belong to the Dock layer. This makes them to be very high.
00590     // Therefore don't keep group transients above them, otherwise this would move
00591     // group transients way too high.
00592     if( mainwindow->isTopMenu() && transient->groupTransient())
00593         return false;
00594     return true;
00595     // #63223 - don't keep transients above docks, because the dock is kept high,
00596     // and e.g. dialogs for them would be too high too
00597     // TODO this doesn't really work - the transient should be raised after clicking
00598     // on the dock, but docks don't become active after clicking them
00599     if( mainwindow->isDock() && !mainwindow->keepBelow()
00600         && !mainwindow->isActive() && !transient->isActive()) // TODO !w->group()->isActive() ???
00601         return false;
00602     return true;
00603     }
00604 
00605 //*******************************
00606 // Client
00607 //*******************************
00608 
00609 void Client::restackWindow( Window /*above TODO */, int detail, NET::RequestSource source, bool send_event )
00610     {
00611     switch ( detail )
00612         {
00613         case Above:
00614         case TopIf:
00615             if( source == NET::FromTool )
00616                 workspace()->raiseClient( this );
00617             else
00618                 workspace()->raiseClientRequest( this );
00619             break;
00620         case Below:
00621         case BottomIf:
00622             if( source == NET::FromTool )
00623                 workspace()->lowerClient( this );
00624             else
00625                 workspace()->lowerClientRequest( this );
00626             break;
00627         case Opposite:
00628         default:
00629             break;
00630         }
00631     if( send_event )
00632         sendSyntheticConfigureNotify();
00633     }
00634     
00635 void Client::setKeepAbove( bool b )
00636     {
00637     if ( b == keepAbove() )
00638         return;
00639     setKeepBelow( false );
00640     keep_above = b;
00641     info->setState( b ? NET::KeepAbove : 0, NET::KeepAbove );
00642     // TODO emit a signal about the change to the style plugin
00643     workspace()->updateClientLayer( this );
00644     }
00645 
00646 void Client::setKeepBelow( bool b )
00647     {
00648     if ( b == keepBelow() )
00649         return;
00650     setKeepAbove( false );
00651     keep_below = b;
00652     info->setState( b ? NET::KeepBelow : 0, NET::KeepBelow );
00653     workspace()->updateClientLayer( this );
00654     }
00655 
00656 Layer Client::layer() const
00657     {
00658     if( in_layer == UnknownLayer )
00659         const_cast< Client* >( this )->in_layer = belongsToLayer();
00660     return in_layer;
00661     }
00662 
00663 Layer Client::belongsToLayer() const
00664     {
00665     if( isDesktop())
00666         return DesktopLayer;
00667     if( isSplash())         // no damn annoying splashscreens
00668         return NormalLayer; // getting in the way of everything else
00669     if( isDock() && keepBelow())
00670         // slight hack for the 'allow window to cover panel' Kicker setting
00671         // don't move keepbelow docks below normal window, but only to the same
00672         // layer, so that both may be raised to cover the other
00673         return NormalLayer;
00674     if( keepBelow())
00675         return BelowLayer;
00676     if( isDock() && !keepBelow())
00677         return DockLayer;
00678     if( isTopMenu())
00679         return DockLayer;
00680     // only raise fullscreen above docks if it's the topmost window in unconstrained stacking order,
00681     // i.e. the window set to be topmost by the user
00682     bool raise_special_active_windows = ( workspace()->topClientOnDesktop( desktop(), true ) == this );
00683     if( keepAbove())
00684         return AboveLayer;
00685     if( isFullScreen() && workspace()->activeClient() != NULL
00686         && ( workspace()->activeClient() == this || this->hasTransient( workspace()->activeClient(), true ))
00687         && raise_special_active_windows )
00688         return ActiveLayer;
00689     return NormalLayer;
00690     }
00691 
00692 } // namespace
KDE Logo
This file is part of the documentation for kwin Library Version 3.2.1.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Fri Mar 5 04:41:14 2004 by doxygen 1.3.6-20040222 written by Dimitri van Heesch, © 1997-2003