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