manage.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 /*
00013 
00014  This file contains things relevant to handling incoming events.
00015 
00016 */
00017 
00018 #include "client.h"
00019 
00020 #include <kstartupinfo.h>
00021 #include <kglobal.h>
00022 #include <X11/extensions/shape.h>
00023 
00024 #include "notifications.h"
00025 #include "rules.h"
00026 #include "group.h"
00027 
00028 extern Time qt_x_time;
00029 
00030 namespace KWinInternal
00031 {
00032 
00038 bool Client::manage( Window w, bool isMapped )
00039     {
00040     XWindowAttributes attr;
00041     if( !XGetWindowAttributes(qt_xdisplay(), w, &attr))
00042         return false;
00043 
00044     grabXServer();
00045 
00046     // from this place on, manage() mustn't return false
00047     postpone_geometry_updates = 1;
00048     pending_geometry_update = true; // force update when finishing with geometry changes
00049 
00050     embedClient( w, attr );
00051 
00052     // SELI order all these things in some sane manner
00053 
00054     bool init_minimize = false;
00055     XWMHints * hints = XGetWMHints(qt_xdisplay(), w );
00056     if (hints && (hints->flags & StateHint) && hints->initial_state == IconicState)
00057         init_minimize = true;
00058     if (hints)
00059         XFree(hints);
00060     if( isMapped )
00061         init_minimize = false; // if it's already mapped, ignore hint
00062 
00063     unsigned long properties[ 2 ];
00064     properties[ WinInfo::PROTOCOLS ] =
00065         NET::WMDesktop |
00066         NET::WMState |
00067         NET::WMWindowType |
00068         NET::WMStrut |
00069         NET::WMName |
00070         NET::WMIconGeometry |
00071         NET::WMIcon |
00072         NET::WMPid |
00073         NET::WMIconName |
00074         0;
00075     properties[ WinInfo::PROTOCOLS2 ] =
00076         NET::WM2UserTime |
00077         NET::WM2StartupId |
00078         NET::WM2ExtendedStrut |
00079         0;
00080 
00081     info = new WinInfo( this, qt_xdisplay(), client, qt_xrootwin(), properties, 2 );
00082 
00083     cmap = attr.colormap;
00084 
00085     XClassHint classHint;
00086     if ( XGetClassHint( qt_xdisplay(), client, &classHint ) ) 
00087         {
00088         // Qt3.2 and older had this all lowercase, Qt3.3 capitalized resource class
00089         // force lowercase, so that workarounds listing resource classes still work
00090         resource_name = QCString( classHint.res_name ).lower();
00091         resource_class = QCString( classHint.res_class ).lower();
00092         XFree( classHint.res_name );
00093         XFree( classHint.res_class );
00094         }
00095     ignore_focus_stealing = options->checkIgnoreFocusStealing( this ); // TODO change to rules
00096 
00097     window_role = staticWindowRole( w );
00098     getWmClientLeader();
00099     getWmClientMachine();
00100     // first only read the caption text, so that setupWindowRules() can use it for matching,
00101     // and only then really set the caption using setCaption(), which checks for duplicates etc.
00102     // and also relies on rules already existing
00103     cap_normal = readName();
00104     setupWindowRules( false );
00105     setCaption( cap_normal, true );
00106 
00107     detectNoBorder();
00108     detectShapable();
00109     fetchIconicName();
00110     getWMHints(); // needs to be done before readTransient() because of reading the group
00111     modal = ( info->state() & NET::Modal ) != 0; // needs to be valid before handling groups
00112     readTransient();
00113     getIcons();
00114     getWindowProtocols();
00115     getWmNormalHints(); // get xSizeHint
00116     getMotifHints();
00117 
00118     // TODO try to obey all state information from info->state()
00119 
00120     original_skip_taskbar = skip_taskbar = ( info->state() & NET::SkipTaskbar) != 0;
00121     skip_pager = ( info->state() & NET::SkipPager) != 0;
00122 
00123     KStartupInfoId asn_id;
00124     KStartupInfoData asn_data;
00125     bool asn_valid = workspace()->checkStartupNotification( window(), asn_id, asn_data );
00126 
00127     workspace()->updateClientLayer( this );
00128 
00129     SessionInfo* session = workspace()->takeSessionInfo( this );
00130     
00131     if ( session )
00132         {
00133         if ( session->minimized )
00134             init_minimize = true;
00135         if( session->userNoBorder )
00136             setUserNoBorder( true );
00137         }
00138 
00139     setShortcut( rules()->checkShortcut( session ? session->shortcut : QString::null, true ));
00140 
00141     init_minimize = rules()->checkMinimize( init_minimize, !isMapped );
00142     if( rules()->checkNoBorder( false, !isMapped ))
00143         setUserNoBorder( true );
00144     
00145     checkAndSetInitialRuledOpacity();
00146 
00147     // initial desktop placement
00148     if ( session ) 
00149         {
00150         desk = session->desktop;
00151         if( session->onAllDesktops )
00152             desk = NET::OnAllDesktops;
00153         }
00154     else
00155         {
00156         // if this window is transient, ensure that it is opened on the
00157         // same window as its parent.  this is necessary when an application
00158         // starts up on a different desktop than is currently displayed
00159         if( isTransient())
00160             {
00161             ClientList mainclients = mainClients();
00162             bool on_current = false;
00163             Client* maincl = NULL;
00164             // this is slightly duplicated from Placement::placeOnMainWindow()
00165             for( ClientList::ConstIterator it = mainclients.begin();
00166                  it != mainclients.end();
00167                  ++it )
00168                 {
00169                 if( mainclients.count() > 1 && (*it)->isSpecialWindow())
00170                     continue; // don't consider toolbars etc when placing
00171                 maincl = *it;
00172                 if( (*it)->isOnCurrentDesktop())
00173                     on_current = true;
00174                 }
00175             if( on_current )
00176                 desk = workspace()->currentDesktop();
00177             else if( maincl != NULL )
00178                 desk = maincl->desktop();
00179             }
00180         if ( info->desktop() )
00181             desk = info->desktop(); // window had the initial desktop property, force it
00182         if( desktop() == 0 && asn_valid && asn_data.desktop() != 0 )
00183             desk = asn_data.desktop();
00184         }
00185     if ( desk == 0 ) // assume window wants to be visible on the current desktop
00186         desk = workspace()->currentDesktop();
00187     desk = rules()->checkDesktop( desk, !isMapped );
00188     if( desk != NET::OnAllDesktops ) // do range check
00189         desk = KMAX( 1, KMIN( workspace()->numberOfDesktops(), desk ));
00190     info->setDesktop( desk );
00191     workspace()->updateOnAllDesktopsOfTransients( this ); // SELI
00192 //    onAllDesktopsChange(); decoration doesn't exist here yet
00193 
00194     QRect geom( attr.x, attr.y, attr.width, attr.height );
00195     bool placementDone = FALSE;
00196 
00197     if ( session )
00198         geom = session->geometry;
00199 
00200     QRect area;
00201     bool partial_keep_in_area = isMapped || session;
00202     if( isMapped || session )
00203         area = workspace()->clientArea( FullArea, geom.center(), desktop());
00204     else if( options->xineramaPlacementEnabled )
00205         {
00206         int screen = options->xineramaPlacementScreen;
00207         if( screen == -1 ) // active screen
00208             screen = asn_data.xinerama() == -1 ? workspace()->activeScreen() : asn_data.xinerama();
00209         area = workspace()->clientArea( PlacementArea, workspace()->screenGeometry( screen ).center(), desktop());
00210         }
00211     else
00212         area = workspace()->clientArea( PlacementArea, QCursor::pos(), desktop());
00213 
00214     if( int type = checkFullScreenHack( geom ))
00215         {
00216         fullscreen_mode = FullScreenHack;
00217         if( rules()->checkStrictGeometry( false ))
00218             {
00219             geom = type == 2 // 1 - it's xinerama-aware fullscreen hack, 2 - it's full area
00220                 ? workspace()->clientArea( FullArea, geom.center(), desktop())
00221                 : workspace()->clientArea( ScreenArea, geom.center(), desktop());
00222             }
00223         else
00224             geom = workspace()->clientArea( FullScreenArea, geom.center(), desktop());
00225         placementDone = true;
00226         }
00227 
00228     if ( isDesktop() ) 
00229         {
00230         // desktops are treated slightly special
00231         geom = workspace()->clientArea( FullArea, geom.center(), desktop());
00232         placementDone = true;
00233         }
00234 
00235     bool usePosition = false;
00236     if ( isMapped || session || placementDone )
00237         placementDone = true; // use geometry
00238     else if( isTransient() && !isUtility() && !isDialog() && !isSplash())
00239         usePosition = true;
00240     else if( isTransient() && !hasNETSupport())
00241         usePosition = true;
00242     else if( isDialog() && hasNETSupport())
00243     // if the dialog is actually non-NETWM transient window, don't try to apply placement to it,
00244     // it breaks with too many things (xmms, display)
00245         {
00246         if( mainClients().count() >= 1 )
00247             {
00248 #if 1
00249             // TODO #78082 - Ok, it seems there are after all some cases when an application has a good
00250             // reason to specify a position for its dialog. Too bad other WMs have never bothered
00251             // with placement for dialogs, so apps always specify positions for their dialogs,
00252             // including such silly positions like always centered on the screen or under mouse.
00253             // Using ignoring requested position in window-specific settings helps, but at least
00254             // for Qt apps this should work better.
00255             usePosition = true;
00256 #else
00257             ; // force using placement policy
00258 #endif
00259             }
00260         else
00261             usePosition = true;
00262         }
00263     else if( isSplash())
00264         ; // force using placement policy
00265     else
00266         usePosition = true;
00267     if( !rules()->checkIgnoreGeometry( !usePosition ))
00268         {
00269         bool ignorePPosition = ( options->ignorePositionClasses.contains(QString::fromLatin1(resourceClass())));
00270 
00271         if ( ( (xSizeHint.flags & PPosition) && !ignorePPosition ) ||
00272              (xSizeHint.flags & USPosition) ) 
00273             {
00274             placementDone = TRUE;
00275             // disobey xinerama placement option for now (#70943)
00276             area = workspace()->clientArea( PlacementArea, geom.center(), desktop());
00277             }
00278         }
00279     if( true ) // size is always obeyed for now, only with constraints applied
00280         if ( (xSizeHint.flags & USSize) || (xSizeHint.flags & PSize) ) 
00281             {
00282             // keep in mind that we now actually have a size :-)
00283             }
00284 
00285     if (xSizeHint.flags & PMaxSize)
00286         geom.setSize( geom.size().boundedTo(
00287             rules()->checkMaxSize( QSize(xSizeHint.max_width, xSizeHint.max_height ) ) ) );
00288     if (xSizeHint.flags & PMinSize)
00289         geom.setSize( geom.size().expandedTo(
00290             rules()->checkMinSize( QSize(xSizeHint.min_width, xSizeHint.min_height ) ) ) );
00291 
00292     if( isMovable())
00293         {
00294         if( geom.x() > area.right() || geom.y() > area.bottom())
00295             placementDone = FALSE; // weird, do not trust.
00296         }
00297 
00298     if ( placementDone )
00299         move( geom.x(), geom.y() ); // before gravitating
00300 
00301     updateDecoration( false ); // also gravitates
00302     // TODO is CentralGravity right here, when resizing is done after gravitating?
00303     plainResize( rules()->checkSize( sizeForClientSize( geom.size()), !isMapped ));
00304 
00305     QPoint forced_pos = rules()->checkPosition( invalidPoint, !isMapped );
00306     if( forced_pos != invalidPoint )
00307         {
00308         move( forced_pos );
00309         placementDone = true;
00310         // don't keep inside workarea if the window has specially configured position
00311         partial_keep_in_area = true;
00312         area = workspace()->clientArea( FullArea, geom.center(), desktop());
00313         }
00314     if( !placementDone ) 
00315         { // placement needs to be after setting size
00316         workspace()->place( this, area );
00317         placementDone = TRUE;
00318         }
00319 
00320     if(( !isSpecialWindow() || isToolbar()) && isMovable())
00321         keepInArea( area, partial_keep_in_area );
00322 
00323     XShapeSelectInput( qt_xdisplay(), window(), ShapeNotifyMask );
00324     is_shape = Shape::hasShape( window());
00325     updateShape();
00326     
00327     //CT extra check for stupid jdk 1.3.1. But should make sense in general
00328     // if client has initial state set to Iconic and is transient with a parent
00329     // window that is not Iconic, set init_state to Normal
00330     if( init_minimize && isTransient())
00331         {
00332         ClientList mainclients = mainClients();
00333         for( ClientList::ConstIterator it = mainclients.begin();
00334              it != mainclients.end();
00335              ++it )
00336             if( (*it)->isShown( true ))
00337                 init_minimize = false; // SELI even e.g. for NET::Utility?
00338         }
00339     // if a dialog is shown for minimized window, minimize it too
00340     if( !init_minimize && isTransient() && mainClients().count() > 0 )
00341         {
00342         bool visible_parent = false;
00343         ClientList mainclients = mainClients();
00344         for( ClientList::ConstIterator it = mainclients.begin();
00345              it != mainclients.end();
00346              ++it )
00347             if( (*it)->isShown( true ))
00348                 visible_parent = true;
00349         if( !visible_parent )
00350             {
00351             init_minimize = true;
00352             demandAttention();
00353             }
00354         }
00355 
00356     if( init_minimize )
00357         minimize( true ); // no animation
00358 
00359     // SELI this seems to be mainly for kstart and ksystraycmd
00360     // probably should be replaced by something better
00361     bool doNotShow = false;
00362     if ( workspace()->isNotManaged( caption() ) )
00363         doNotShow = TRUE;
00364 
00365     // other settings from the previous session
00366     if ( session ) 
00367         {
00368         // session restored windows are not considered to be new windows WRT rules,
00369         // i.e. obey only forcing rules
00370         setKeepAbove( session->keepAbove );
00371         setKeepBelow( session->keepBelow );
00372         setSkipTaskbar( session->skipTaskbar, true );
00373         setSkipPager( session->skipPager );
00374         setShade( session->shaded ? ShadeNormal : ShadeNone );
00375         if( session->maximized != MaximizeRestore )
00376             {
00377             maximize( (MaximizeMode) session->maximized );
00378             geom_restore = session->restore;
00379             }
00380         if( session->fullscreen == FullScreenHack )
00381             ; // nothing, this should be already set again above
00382         else if( session->fullscreen != FullScreenNone )
00383             {
00384             setFullScreen( true, false );
00385             geom_fs_restore = session->fsrestore;
00386             }
00387         }
00388     else 
00389         {
00390         geom_restore = geometry(); // remember restore geometry
00391         if ( isMaximizable()
00392              && ( width() >= area.width() || height() >= area.height() ) ) 
00393             {
00394             // window is too large for the screen, maximize in the
00395             // directions necessary
00396             if ( width() >= area.width() && height() >= area.height() ) 
00397                 {
00398                 maximize( Client::MaximizeFull );
00399                 geom_restore = QRect(); // use placement when unmaximizing
00400                 }
00401             else if ( width() >= area.width() ) 
00402                 {
00403                 maximize( Client::MaximizeHorizontal );
00404                 geom_restore = QRect(); // use placement when unmaximizing
00405                 geom_restore.setY( y()); // but only for horizontal direction
00406                 geom_restore.setHeight( height());
00407                 }
00408             else if ( height() >= area.height() ) 
00409                 {
00410                 maximize( Client::MaximizeVertical );
00411                 geom_restore = QRect(); // use placement when unmaximizing
00412                 geom_restore.setX( x()); // but only for vertical direction
00413                 geom_restore.setWidth( width());
00414                 }
00415             }
00416         // window may want to be maximized
00417         // done after checking that the window isn't larger than the workarea, so that
00418         // the restore geometry from the checks above takes precedence, and window
00419         // isn't restored larger than the workarea
00420         MaximizeMode maxmode = static_cast< MaximizeMode >
00421             ((( info->state() & NET::MaxVert ) ? MaximizeVertical : 0 )
00422             | (( info->state() & NET::MaxHoriz ) ? MaximizeHorizontal : 0 ));
00423         MaximizeMode forced_maxmode = rules()->checkMaximize( maxmode, !isMapped );
00424         // either hints were set to maximize, or is forced to maximize,
00425         // or is forced to non-maximize and hints were set to maximize
00426         if( forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore )
00427             maximize( forced_maxmode );
00428 
00429         // read other initial states
00430         setShade( rules()->checkShade( info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped ));
00431         setKeepAbove( rules()->checkKeepAbove( info->state() & NET::KeepAbove, !isMapped ));
00432         setKeepBelow( rules()->checkKeepBelow( info->state() & NET::KeepBelow, !isMapped ));
00433         setSkipTaskbar( rules()->checkSkipTaskbar( info->state() & NET::SkipTaskbar, !isMapped ), true );
00434         setSkipPager( rules()->checkSkipPager( info->state() & NET::SkipPager, !isMapped ));
00435         if( info->state() & NET::DemandsAttention )
00436             demandAttention();
00437         if( info->state() & NET::Modal )
00438             setModal( true );
00439         if( fullscreen_mode != FullScreenHack && isFullScreenable())
00440             setFullScreen( rules()->checkFullScreen( info->state() & NET::FullScreen, !isMapped ), false );
00441         }
00442 
00443     updateAllowedActions( true );
00444 
00445     // TODO this should avoid flicker, because real restacking is done
00446     // only after manage() finishes, but the window is shown sooner
00447     // - keep it?
00448     XLowerWindow( qt_xdisplay(), frameId());
00449 
00450     // set initial user time directly
00451     user_time = readUserTimeMapTimestamp( asn_valid ? &asn_id : NULL, asn_valid ? &asn_data : NULL, session );
00452     group()->updateUserTime( user_time ); // and do what Client::updateUserTime() does
00453 
00454     if( isTopMenu()) // they're shown in Workspace::addClient() if their mainwindow
00455         hideClient( true ); // is the active one
00456 
00457     if( isShown( true ) && !doNotShow )
00458         {
00459         if( isDialog())
00460             Notify::raise( Notify::TransNew );
00461         if( isNormalWindow())
00462             Notify::raise( Notify::New );
00463 
00464         bool allow;
00465         if( session )
00466             allow = session->active
00467                 && ( !workspace()->wasUserInteraction()
00468                     || workspace()->activeClient() == NULL || workspace()->activeClient()->isDesktop());
00469         else
00470             allow = workspace()->allowClientActivation( this, userTime(), false );
00471 
00472         // if session saving, force showing new windows (i.e. "save file?" dialogs etc.)
00473         // also force if activation is allowed
00474         if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || workspace()->sessionSaving()))
00475             workspace()->setCurrentDesktop( desktop());
00476 
00477         bool belongs_to_desktop = false;
00478         for( ClientList::ConstIterator it = group()->members().begin();
00479              it != group()->members().end();
00480              ++it )
00481             if( (*it)->isDesktop())
00482                 {
00483                 belongs_to_desktop = true;
00484                 break;
00485                 }
00486         if( !belongs_to_desktop && workspace()->showingDesktop())
00487             workspace()->resetShowingDesktop( options->showDesktopIsMinimizeAll );
00488 
00489         if( isOnCurrentDesktop() && !isMapped && !allow )
00490             workspace()->restackClientUnderActive( this );
00491         else
00492             workspace()->raiseClient( this );
00493 
00494         updateVisibility();
00495 
00496         if( !isMapped )
00497             {
00498             if( allow && isOnCurrentDesktop())
00499                 {
00500                 if( !isSpecialWindow())
00501                     if ( options->focusPolicyIsReasonable() && wantsTabFocus() )
00502                         workspace()->requestFocus( this );
00503                 }
00504             else
00505                 {
00506                 if( !session && !isSpecialWindow())
00507                         demandAttention();
00508                 }
00509             }
00510         }
00511     else if( !doNotShow ) // if( !isShown( true ) && !doNotShow )
00512         {
00513         updateVisibility();
00514         }
00515     else // doNotShow
00516         { // SELI HACK !!!
00517         hideClient( true );
00518         setMappingState( IconicState );
00519         }
00520     assert( mappingState() != WithdrawnState );
00521 
00522     if( user_time == CurrentTime || user_time == -1U ) // no known user time, set something old
00523         {
00524         user_time = qt_x_time - 1000000;
00525         if( user_time == CurrentTime || user_time == -1U ) // let's be paranoid
00526             user_time = qt_x_time - 1000000 + 10;
00527         }
00528 
00529     updateWorkareaDiffs();
00530 
00531 //    sendSyntheticConfigureNotify(); done when setting mapping state
00532 
00533     delete session;
00534 
00535     ungrabXServer();
00536     
00537     client_rules.discardTemporary();
00538     applyWindowRules(); // just in case
00539     workspace()->discardUsedWindowRules( this, false ); // remove ApplyNow rules
00540     updateWindowRules(); // was blocked while !isManaged()
00541 
00542 // TODO there's a small problem here - isManaged() depends on the mapping state,
00543 // but this client is not yet in Workspace's client list at this point, will
00544 // be only done in addClient()
00545     return true;
00546     }
00547 
00548 // called only from manage()
00549 void Client::embedClient( Window w, const XWindowAttributes &attr )
00550     {
00551     assert( client == None );
00552     assert( frame == None );
00553     assert( wrapper == None );
00554     client = w;
00555     // we don't want the window to be destroyed when we are destroyed
00556     XAddToSaveSet( qt_xdisplay(), client );
00557     XSelectInput( qt_xdisplay(), client, NoEventMask );
00558     XUnmapWindow( qt_xdisplay(), client );
00559     XWindowChanges wc;     // set the border width to 0
00560     wc.border_width = 0; // TODO possibly save this, and also use it for initial configuring of the window
00561     XConfigureWindow( qt_xdisplay(), client, CWBorderWidth, &wc );
00562 
00563     XSetWindowAttributes swa;
00564     swa.colormap = attr.colormap;
00565     swa.background_pixmap = None;
00566     swa.border_pixel = 0;
00567 
00568     frame = XCreateWindow( qt_xdisplay(), qt_xrootwin(), 0, 0, 1, 1, 0,
00569             attr.depth, InputOutput, attr.visual,
00570             CWColormap | CWBackPixmap | CWBorderPixel, &swa );
00571     wrapper = XCreateWindow( qt_xdisplay(), frame, 0, 0, 1, 1, 0,
00572             attr.depth, InputOutput, attr.visual,
00573             CWColormap | CWBackPixmap | CWBorderPixel, &swa );
00574 
00575     XDefineCursor( qt_xdisplay(), frame, arrowCursor.handle());
00576     // some apps are stupid and don't define their own cursor - set the arrow one for them
00577     XDefineCursor( qt_xdisplay(), wrapper, arrowCursor.handle());
00578     XReparentWindow( qt_xdisplay(), client, wrapper, 0, 0 );
00579     XSelectInput( qt_xdisplay(), frame,
00580             KeyPressMask | KeyReleaseMask |
00581             ButtonPressMask | ButtonReleaseMask |
00582             KeymapStateMask |
00583             ButtonMotionMask |
00584             PointerMotionMask |
00585             EnterWindowMask | LeaveWindowMask |
00586             FocusChangeMask |
00587             ExposureMask |
00588             PropertyChangeMask |
00589             StructureNotifyMask | SubstructureRedirectMask );
00590     XSelectInput( qt_xdisplay(), wrapper, ClientWinMask | SubstructureNotifyMask );
00591     XSelectInput( qt_xdisplay(), client,
00592                   FocusChangeMask |
00593                   PropertyChangeMask |
00594                   ColormapChangeMask |
00595                   EnterWindowMask | LeaveWindowMask |
00596                   KeyPressMask | KeyReleaseMask
00597                   );
00598     updateMouseGrab();
00599     }
00600 
00601 } // namespace
KDE Home | KDE Accessibility Home | Description of Access Keys