diff options
Diffstat (limited to 'twin/manage.cpp')
-rw-r--r-- | twin/manage.cpp | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/twin/manage.cpp b/twin/manage.cpp new file mode 100644 index 000000000..ec6a5d675 --- /dev/null +++ b/twin/manage.cpp @@ -0,0 +1,606 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org> +Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org> + +You can Freely distribute this program under the GNU General Public +License. See the file "COPYING" for the exact licensing terms. +******************************************************************/ + +/* + + This file contains things relevant to handling incoming events. + +*/ + +#include "client.h" + +#include <tdestartupinfo.h> +#include <tdeglobal.h> +#include <X11/extensions/shape.h> + +#include "notifications.h" +#include "rules.h" +#include "group.h" + +namespace KWinInternal +{ + +/*! + Manages the clients. This means handling the very first maprequest: + reparenting, initial geometry, initial state, placement, etc. + Returns false if KWin is not going to manage this window. + */ +bool Client::manage( Window w, bool isMapped ) + { + XWindowAttributes attr; + if( !XGetWindowAttributes(tqt_xdisplay(), w, &attr)) + return false; + + grabXServer(); + + // from this place on, manage() mustn't return false + postpone_geometry_updates = 1; + pending_geometry_update = true; // force update when finishing with geometry changes + + embedClient( w, attr ); + + // SELI order all these things in some sane manner + + bool init_minimize = false; + XWMHints * hints = XGetWMHints(tqt_xdisplay(), w ); + if (hints && (hints->flags & StateHint) && hints->initial_state == IconicState) + init_minimize = true; + if (hints) + XFree(hints); + if( isMapped ) + init_minimize = false; // if it's already mapped, ignore hint + + unsigned long properties[ 2 ]; + properties[ WinInfo::PROTOCOLS ] = + NET::WMDesktop | + NET::WMState | + NET::WMWindowType | + NET::WMStrut | + NET::WMName | + NET::WMIconGeometry | + NET::WMIcon | + NET::WMPid | + NET::WMIconName | + 0; + properties[ WinInfo::PROTOCOLS2 ] = + NET::WM2UserTime | + NET::WM2StartupId | + NET::WM2ExtendedStrut | + 0; + + info = new WinInfo( this, tqt_xdisplay(), client, tqt_xrootwin(), properties, 2 ); + + cmap = attr.colormap; + + XClassHint classHint; + if ( XGetClassHint( tqt_xdisplay(), client, &classHint ) ) + { + // Qt3.2 and older had this all lowercase, Qt3.3 capitalized resource class + // force lowercase, so that workarounds listing resource classes still work + resource_name = TQCString( classHint.res_name ).lower(); + resource_class = TQCString( classHint.res_class ).lower(); + XFree( classHint.res_name ); + XFree( classHint.res_class ); + } + ignore_focus_stealing = options->checkIgnoreFocusStealing( this ); // TODO change to rules + + window_role = staticWindowRole( w ); + getWmClientLeader(); + getWmClientMachine(); + // first only read the caption text, so that setupWindowRules() can use it for matching, + // and only then really set the caption using setCaption(), which checks for duplicates etc. + // and also relies on rules already existing + cap_normal = readName(); + setupWindowRules( false ); + setCaption( cap_normal, true ); + + detectNoBorder(); + detectShapable(); + fetchIconicName(); + getWMHints(); // needs to be done before readTransient() because of reading the group + modal = ( info->state() & NET::Modal ) != 0; // needs to be valid before handling groups + readTransient(); + getIcons(); + getWindowProtocols(); + getWmNormalHints(); // get xSizeHint + getMotifHints(); + + // TODO try to obey all state information from info->state() + + original_skip_taskbar = skip_taskbar = ( info->state() & NET::SkipTaskbar) != 0; + skip_pager = ( info->state() & NET::SkipPager) != 0; + + TDEStartupInfoId asn_id; + TDEStartupInfoData asn_data; + bool asn_valid = workspace()->checkStartupNotification( window(), asn_id, asn_data ); + + workspace()->updateClientLayer( this ); + + SessionInfo* session = workspace()->takeSessionInfo( this ); + + if ( session ) + { + if ( session->minimized ) + init_minimize = true; + if( session->userNoBorder ) + setUserNoBorder( true ); + } + + setShortcut( rules()->checkShortcut( session ? session->shortcut : TQString::null, true )); + + init_minimize = rules()->checkMinimize( init_minimize, !isMapped ); + if( rules()->checkNoBorder( false, !isMapped )) + setUserNoBorder( true ); + + checkAndSetInitialRuledOpacity(); + + // initial desktop placement + if ( session ) + { + desk = session->desktop; + if( session->onAllDesktops ) + desk = NET::OnAllDesktops; + } + else + { + // if this window is transient, ensure that it is opened on the + // same window as its parent. this is necessary when an application + // starts up on a different desktop than is currently displayed + if( isTransient()) + { + ClientList mainclients = mainClients(); + bool on_current = false; + Client* maincl = NULL; + // this is slightly duplicated from Placement::placeOnMainWindow() + for( ClientList::ConstIterator it = mainclients.begin(); + it != mainclients.end(); + ++it ) + { + if( mainclients.count() > 1 && (*it)->isSpecialWindow()) + continue; // don't consider toolbars etc when placing + maincl = *it; + if( (*it)->isOnCurrentDesktop()) + on_current = true; + } + if( on_current ) + desk = workspace()->currentDesktop(); + else if( maincl != NULL ) + desk = maincl->desktop(); + } + if ( info->desktop() ) + desk = info->desktop(); // window had the initial desktop property, force it + if( desktop() == 0 && asn_valid && asn_data.desktop() != 0 ) + desk = asn_data.desktop(); + } + if ( desk == 0 ) // assume window wants to be visible on the current desktop + desk = workspace()->currentDesktop(); + desk = rules()->checkDesktop( desk, !isMapped ); + if( desk != NET::OnAllDesktops ) // do range check + desk = KMAX( 1, KMIN( workspace()->numberOfDesktops(), desk )); + info->setDesktop( desk ); + workspace()->updateOnAllDesktopsOfTransients( this ); // SELI +// onAllDesktopsChange(); decoration doesn't exist here yet + + TQRect geom( attr.x, attr.y, attr.width, attr.height ); + bool placementDone = FALSE; + + if ( session ) + geom = session->geometry; + + TQRect area; + bool partial_keep_in_area = isMapped || session; + if( isMapped || session ) + area = workspace()->clientArea( FullArea, geom.center(), desktop()); + else if( options->xineramaPlacementEnabled ) + { + int screen = options->xineramaPlacementScreen; + if( screen == -1 ) // active screen + screen = asn_data.xinerama() == -1 ? workspace()->activeScreen() : asn_data.xinerama(); + area = workspace()->clientArea( PlacementArea, workspace()->screenGeometry( screen ).center(), desktop()); + } + else + area = workspace()->clientArea( PlacementArea, TQCursor::pos(), desktop()); + + if( int type = checkFullScreenHack( geom )) + { + fullscreen_mode = FullScreenHack; + if( rules()->checkStrictGeometry( false )) + { + geom = type == 2 // 1 - it's xinerama-aware fullscreen hack, 2 - it's full area + ? workspace()->clientArea( FullArea, geom.center(), desktop()) + : workspace()->clientArea( ScreenArea, geom.center(), desktop()); + } + else + geom = workspace()->clientArea( FullScreenArea, geom.center(), desktop()); + placementDone = true; + } + + if ( isDesktop() ) + { + // desktops are treated slightly special + geom = workspace()->clientArea( FullArea, geom.center(), desktop()); + placementDone = true; + } + + bool usePosition = false; + if ( isMapped || session || placementDone ) + placementDone = true; // use geometry + else if( isTransient() && !isUtility() && !isDialog() && !isSplash()) + usePosition = true; + else if( isTransient() && !hasNETSupport()) + usePosition = true; + else if( isDialog() && hasNETSupport()) + // if the dialog is actually non-NETWM transient window, don't try to apply placement to it, + // it breaks with too many things (xmms, display) + { + if( mainClients().count() >= 1 ) + { +#if 1 + // TODO #78082 - Ok, it seems there are after all some cases when an application has a good + // reason to specify a position for its dialog. Too bad other WMs have never bothered + // with placement for dialogs, so apps always specify positions for their dialogs, + // including such silly positions like always centered on the screen or under mouse. + // Using ignoring requested position in window-specific settings helps, but at least + // for Qt apps this should work better. + usePosition = true; +#else + ; // force using placement policy +#endif + } + else + usePosition = true; + } + else if( isSplash()) + ; // force using placement policy + else + usePosition = true; + if( !rules()->checkIgnoreGeometry( !usePosition )) + { + bool ignorePPosition = ( options->ignorePositionClasses.contains(TQString::fromLatin1(resourceClass()))); + + if ( ( (xSizeHint.flags & PPosition) && !ignorePPosition ) || + (xSizeHint.flags & USPosition) ) + { + placementDone = TRUE; + // disobey xinerama placement option for now (#70943) + area = workspace()->clientArea( PlacementArea, geom.center(), desktop()); + } + } + if( true ) // size is always obeyed for now, only with constraints applied + if ( (xSizeHint.flags & USSize) || (xSizeHint.flags & PSize) ) + { + // keep in mind that we now actually have a size :-) + } + + if (xSizeHint.flags & PMaxSize) + geom.setSize( geom.size().boundedTo( + rules()->checkMaxSize( TQSize(xSizeHint.max_width, xSizeHint.max_height ) ) ) ); + if (xSizeHint.flags & PMinSize) + geom.setSize( geom.size().expandedTo( + rules()->checkMinSize( TQSize(xSizeHint.min_width, xSizeHint.min_height ) ) ) ); + + if( isMovable()) + { + if( geom.x() > area.right() || geom.y() > area.bottom()) + placementDone = FALSE; // weird, do not trust. + } + + if ( placementDone ) + move( geom.x(), geom.y() ); // before gravitating + + updateDecoration( false ); // also gravitates + // TODO is CentralGravity right here, when resizing is done after gravitating? + plainResize( rules()->checkSize( sizeForClientSize( geom.size()), !isMapped )); + + TQPoint forced_pos = rules()->checkPosition( invalidPoint, !isMapped ); + if( forced_pos != invalidPoint ) + { + move( forced_pos ); + placementDone = true; + // don't keep inside workarea if the window has specially configured position + partial_keep_in_area = true; + area = workspace()->clientArea( FullArea, geom.center(), desktop()); + } + if( !placementDone ) + { // placement needs to be after setting size + workspace()->place( this, area ); + placementDone = TRUE; + } + + if(( !isSpecialWindow() || isToolbar()) && isMovable()) + keepInArea( area, partial_keep_in_area ); + + XShapeSelectInput( tqt_xdisplay(), window(), ShapeNotifyMask ); + is_shape = Shape::hasShape( window()); + updateShape(); + + //CT extra check for stupid jdk 1.3.1. But should make sense in general + // if client has initial state set to Iconic and is transient with a parent + // window that is not Iconic, set init_state to Normal + if( init_minimize && isTransient()) + { + ClientList mainclients = mainClients(); + for( ClientList::ConstIterator it = mainclients.begin(); + it != mainclients.end(); + ++it ) + if( (*it)->isShown( true )) + init_minimize = false; // SELI even e.g. for NET::Utility? + } + // if a dialog is shown for minimized window, minimize it too + if( !init_minimize && isTransient() && mainClients().count() > 0 ) + { + bool visible_parent = false; + ClientList mainclients = mainClients(); + for( ClientList::ConstIterator it = mainclients.begin(); + it != mainclients.end(); + ++it ) + if( (*it)->isShown( true )) + visible_parent = true; + if( !visible_parent ) + { + init_minimize = true; + demandAttention(); + } + } + + if( init_minimize ) + minimize( true ); // no animation + + // SELI this seems to be mainly for kstart and ksystraycmd + // probably should be replaced by something better + bool doNotShow = false; + if ( workspace()->isNotManaged( caption() ) ) + doNotShow = TRUE; + + // other settings from the previous session + if ( session ) + { + // session restored windows are not considered to be new windows WRT rules, + // i.e. obey only forcing rules + setKeepAbove( session->keepAbove ); + setKeepBelow( session->keepBelow ); + setSkipTaskbar( session->skipTaskbar, true ); + setSkipPager( session->skipPager ); + setShade( session->shaded ? ShadeNormal : ShadeNone ); + setShadowed( session->shadowed ); + if( session->maximized != MaximizeRestore ) + { + maximize( (MaximizeMode) session->maximized ); + geom_restore = session->restore; + } + if( session->fullscreen == FullScreenHack ) + ; // nothing, this should be already set again above + else if( session->fullscreen != FullScreenNone ) + { + setFullScreen( true, false ); + geom_fs_restore = session->fsrestore; + } + } + else + { + geom_restore = geometry(); // remember restore geometry + if ( isMaximizable() + && ( width() >= area.width() || height() >= area.height() ) ) + { + // window is too large for the screen, maximize in the + // directions necessary + if ( width() >= area.width() && height() >= area.height() ) + { + maximize( Client::MaximizeFull ); + geom_restore = TQRect(); // use placement when unmaximizing + } + else if ( width() >= area.width() ) + { + maximize( Client::MaximizeHorizontal ); + geom_restore = TQRect(); // use placement when unmaximizing + geom_restore.setY( y()); // but only for horizontal direction + geom_restore.setHeight( height()); + } + else if ( height() >= area.height() ) + { + maximize( Client::MaximizeVertical ); + geom_restore = TQRect(); // use placement when unmaximizing + geom_restore.setX( x()); // but only for vertical direction + geom_restore.setWidth( width()); + } + } + // window may want to be maximized + // done after checking that the window isn't larger than the workarea, so that + // the restore geometry from the checks above takes precedence, and window + // isn't restored larger than the workarea + MaximizeMode maxmode = static_cast< MaximizeMode > + ((( info->state() & NET::MaxVert ) ? MaximizeVertical : 0 ) + | (( info->state() & NET::MaxHoriz ) ? MaximizeHorizontal : 0 )); + MaximizeMode forced_maxmode = rules()->checkMaximize( maxmode, !isMapped ); + // either hints were set to maximize, or is forced to maximize, + // or is forced to non-maximize and hints were set to maximize + if( forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore ) + maximize( forced_maxmode ); + + // read other initial states + setShade( rules()->checkShade( info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped )); + setKeepAbove( rules()->checkKeepAbove( info->state() & NET::KeepAbove, !isMapped )); + setKeepBelow( rules()->checkKeepBelow( info->state() & NET::KeepBelow, !isMapped )); + setSkipTaskbar( rules()->checkSkipTaskbar( info->state() & NET::SkipTaskbar, !isMapped ), true ); + setSkipPager( rules()->checkSkipPager( info->state() & NET::SkipPager, !isMapped )); + if( info->state() & NET::DemandsAttention ) + demandAttention(); + if( info->state() & NET::Modal ) + setModal( true ); + if( fullscreen_mode != FullScreenHack && isFullScreenable()) + setFullScreen( rules()->checkFullScreen( info->state() & NET::FullScreen, !isMapped ), false ); + } + + updateAllowedActions( true ); + + // TODO this should avoid flicker, because real restacking is done + // only after manage() finishes, but the window is shown sooner + // - keep it? + XLowerWindow( tqt_xdisplay(), frameId()); + + // set initial user time directly + user_time = readUserTimeMapTimestamp( asn_valid ? &asn_id : NULL, asn_valid ? &asn_data : NULL, session ); + group()->updateUserTime( user_time ); // and do what Client::updateUserTime() does + + if( isTopMenu()) // they're shown in Workspace::addClient() if their mainwindow + hideClient( true ); // is the active one + + if( isShown( true ) && !doNotShow ) + { + if( isDialog()) + Notify::raise( Notify::TransNew ); + if( isNormalWindow()) + Notify::raise( Notify::New ); + + bool allow; + if( session ) + allow = session->active + && ( !workspace()->wasUserInteraction() + || workspace()->activeClient() == NULL || workspace()->activeClient()->isDesktop()); + else + allow = workspace()->allowClientActivation( this, userTime(), false ); + + // if session saving, force showing new windows (i.e. "save file?" dialogs etc.) + // also force if activation is allowed + if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || workspace()->sessionSaving())) + workspace()->setCurrentDesktop( desktop()); + + bool belongs_to_desktop = false; + for( ClientList::ConstIterator it = group()->members().begin(); + it != group()->members().end(); + ++it ) + if( (*it)->isDesktop()) + { + belongs_to_desktop = true; + break; + } + if( !belongs_to_desktop && workspace()->showingDesktop()) + workspace()->resetShowingDesktop( options->showDesktopIsMinimizeAll ); + + if( isOnCurrentDesktop() && !isMapped && !allow ) + workspace()->restackClientUnderActive( this ); + else + workspace()->raiseClient( this ); + + updateVisibility(); + + if( !isMapped ) + { + if( allow && isOnCurrentDesktop()) + { + if( !isSpecialWindow()) + if ( options->focusPolicyIsReasonable() && wantsTabFocus() ) + workspace()->requestFocus( this ); + } + else + { + if( !session && !isSpecialWindow()) + demandAttention(); + } + } + } + else if( !doNotShow ) // if( !isShown( true ) && !doNotShow ) + { + updateVisibility(); + } + else // doNotShow + { // SELI HACK !!! + hideClient( true ); + setMappingState( IconicState ); + } + assert( mappingState() != WithdrawnState ); + + if( user_time == CurrentTime || user_time == -1U ) // no known user time, set something old + { + user_time = GET_QT_X_TIME() - 1000000; + if( user_time == CurrentTime || user_time == -1U ) // let's be paranoid + user_time = GET_QT_X_TIME() - 1000000 + 10; + } + + updateWorkareaDiffs(); + +// sendSyntheticConfigureNotify(); done when setting mapping state + + delete session; + + ungrabXServer(); + + client_rules.discardTemporary(); + applyWindowRules(); // just in case + workspace()->discardUsedWindowRules( this, false ); // remove ApplyNow rules + updateWindowRules(); // was blocked while !isManaged() + +// Handle suspended processes + if (isResumeable()) + { + suspendWindow(); // It won't hurt to stop the process again, and this will update the displayed captions + } + +// TODO there's a small problem here - isManaged() depends on the mapping state, +// but this client is not yet in Workspace's client list at this point, will +// be only done in addClient() + return true; + } + +// called only from manage() +void Client::embedClient( Window w, const XWindowAttributes &attr ) + { + assert( client == None ); + assert( frame == None ); + assert( wrapper == None ); + client = w; + // we don't want the window to be destroyed when we are destroyed + XAddToSaveSet( tqt_xdisplay(), client ); + XSelectInput( tqt_xdisplay(), client, NoEventMask ); + XUnmapWindow( tqt_xdisplay(), client ); + XWindowChanges wc; // set the border width to 0 + wc.border_width = 0; // TODO possibly save this, and also use it for initial configuring of the window + XConfigureWindow( tqt_xdisplay(), client, CWBorderWidth, &wc ); + + XSetWindowAttributes swa; + swa.colormap = attr.colormap; + swa.background_pixmap = None; + swa.border_pixel = 0; + + frame = XCreateWindow( tqt_xdisplay(), tqt_xrootwin(), 0, 0, 1, 1, 0, + attr.depth, InputOutput, attr.visual, + CWColormap | CWBackPixmap | CWBorderPixel, &swa ); + wrapper = XCreateWindow( tqt_xdisplay(), frame, 0, 0, 1, 1, 0, + attr.depth, InputOutput, attr.visual, + CWColormap | CWBackPixmap | CWBorderPixel, &swa ); + + XDefineCursor( tqt_xdisplay(), frame, tqarrowCursor.handle()); + // some apps are stupid and don't define their own cursor - set the arrow one for them + XDefineCursor( tqt_xdisplay(), wrapper, tqarrowCursor.handle()); + XReparentWindow( tqt_xdisplay(), client, wrapper, 0, 0 ); + XSelectInput( tqt_xdisplay(), frame, + KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | + KeymapStateMask | + ButtonMotionMask | + PointerMotionMask | + EnterWindowMask | LeaveWindowMask | + FocusChangeMask | + ExposureMask | + PropertyChangeMask | + StructureNotifyMask | SubstructureRedirectMask ); + XSelectInput( tqt_xdisplay(), wrapper, ClientWinMask | SubstructureNotifyMask ); + XSelectInput( tqt_xdisplay(), client, + FocusChangeMask | + PropertyChangeMask | + ColormapChangeMask | + EnterWindowMask | LeaveWindowMask | + KeyPressMask | KeyReleaseMask + ); + updateMouseGrab(); + } + +} // namespace |