diff --git a/kwin/activation.cpp b/kwin/activation.cpp index cac87bdd4d..e859e229cf 100644 --- a/kwin/activation.cpp +++ b/kwin/activation.cpp @@ -1,757 +1,757 @@ /***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak 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 window activation and focus stealing prevention. */ #include "client.h" #include "workspace.h" #include #include #include #include "notifications.h" #include "atoms.h" extern Time qt_x_time; namespace KWinInternal { /* Prevention of focus stealing: KWin tries to prevent unwanted changes of focus, that would result from mapping a new window. Also, some nasty applications may try to force focus change even in cases when ICCCM 4.2.7 doesn't allow it (e.g. they may try to activate their main window because the user definitely "needs" to see something happened - misusing of QWidget::setActiveWindow() may be such case). There are 4 ways how a window may become active: - the user changes the active window (e.g. focus follows mouse, clicking on some window's titlebar) - the change of focus will be done by KWin, so there's nothing to solve in this case - the change of active window will be requested using the _NET_ACTIVE_WINDOW message (handled in RootInfo::changeActiveWindow()) - such requests will be obeyed, because this request is meant mainly for e.g. taskbar asking the WM to change the active window as a result of some user action. Normal applications should use this request only rarely in special cases. See also below the discussion of _NET_ACTIVE_WINDOW_TRANSFER. - the change of active window will be done by performing XSetInputFocus() on a window that's not currently active. ICCCM 4.2.7 describes when the application may perform change of input focus. In order to handle misbehaving applications, KWin will try to detect focus changes to windows that don't belong to currently active application, and restore focus back to the currently active window, instead of activating the window that got focus (unfortunately there's no way to FocusChangeRedirect similar to e.g. SubstructureRedirect, so there will be short time when the focus will be changed). The check itself that's done is Workspace::allowClientActivation() (see below). - a new window will be mapped - this is the most complicated case. If the new window belongs to the currently active application, it may be safely mapped on top and activated. The same if there's no active window, or the active window is the desktop. These checks are done by Workspace::allowClientActivation(). Following checks need to compare times. One time is the timestamp of last user action in the currently active window, the other time is the timestamp of the action that originally caused mapping of the new window (e.g. when the application was started). If the first time is newer than the second one, the window will not be activated, as that indicates futher user actions took place after the action leading to this new mapped window. This check is done by Workspace::allowClientActivation(). There are several ways how to get the timestamp of action that caused the new mapped window (done in Client::readUserTimeMapTimestamp()) : - the window may have the _NET_WM_USER_TIME property. This way the application may either explicitly request that the window is not activated (by using 0 timestamp), or the property contains the time of last user action in the application. - KWin itself tries to detect time of last user action in every window, by watching KeyPress and ButtonPress events on windows. This way some events may be missed (if they don't propagate to the toplevel window), but it's good as a fallback for applications that don't provide _NET_WM_USER_TIME, and missing some events may at most lead to unwanted focus stealing. - the timestamp may come from application startup notification. Application startup notification, if it exists for the new mapped window, should include time of the user action that caused it. - if there's no timestamp available, it's checked whether the new window belongs to some already running application - if yes, the timestamp will be 0 (i.e. refuse activation) - if the window is from session restored window, the timestamp will be 0 too, unless this application was the active one at the time when the session was saved, in which case the window will be activated if there wasn't any user interaction since the time KWin was started. - as the last resort, the _KDE_NET_USER_CREATION_TIME timestamp is used. For every toplevel window that is created (see CreateNotify handling), this property is set to the at that time current time. Since at this time it's known that the new window doesn't belong to any existing application (better said, the application doesn't have any other window mapped), it is either the very first window of the application, or its the only window of the application that was hidden before. The latter case is handled by removing the property from windows before withdrawing them, making the timestamp empty for next mapping of the window. In the sooner case, the timestamp will be used. This helps in case when an application is launched without application startup notification, it creates its mainwindow, and starts its initialization (that may possibly take long time). The timestamp used will be older than any user action done after launching this application. - if no timestamp is found at all, the window is activated. The check whether two windows belong to the same application (same process) is done in Client::belongToSameApplication(). Not 100% reliable, but hopefully 99,99% reliable. As a somewhat special case, window activation is always enabled when session saving is in progress. When session saving, the session manager allows only one application to interact with the user. Not allowing window activation in such case would result in e.g. dialogs not becoming active, so focus stealing prevention would cause here more harm than good. Windows that attempted to become active but KWin prevented this will be marked as demanding user attention. They'll get the _NET_WM_STATE_DEMANDS_ATTENTION state, and the taskbar should mark them specially (blink, etc.). The state will be reset when the window eventually really becomes active. There are one more ways how a window can become obstrusive, window stealing focus: By showing above the active window, by either raising itself, or by moving itself on the active desktop. - KWin will refuse raising non-active window above the active one, unless they belong to the same application. Applications shouldn't raise their windows anyway (unless the app wants to raise one of its windows above another of its windows). - KWin activates windows moved to the current desktop (as that seems logical from the user's point of view, after sending the window there directly from KWin, or e.g. using pager). This means applications shouldn't send their windows to another desktop (SELI TODO - but what if they do?) Special cases I can think of: - konqueror reusing, i.e. kfmclient tells running Konqueror instance to open new window - without focus stealing prevention - no problem - with ASN (application startup notification) - ASN is forwarded, and because it's newer than the instance's user timestamp, it takes precedence - without ASN - user timestamp needs to be reset, otherwise it would be used, and it's old; moreover this new window mustn't be detected as window belonging to already running application, or it wouldn't be activated - see Client::sameAppWindowRoleMatch() for the (rather ugly) hack - konqueror preloading, i.e. window is created in advance, and kfmclient tells this Konqueror instance to show it later - without focus stealing prevention - no problem - with ASN - ASN is forwarded, and because it's newer than the instance's user timestamp, it takes precedence - without ASN - user timestamp needs to be reset, otherwise it would be used, and it's old; also, creation timestamp is changed to the time the instance starts (re-)initializing the window, this ensures creation timestamp will still work somewhat even in this case - KUniqueApplication - when the window is already visible, and the new instance wants it to activate - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem - with ASN - ASN is forwarded, and set on the already visible window, KWin treats the window as new with that ASN - without ASN - _NET_ACTIVE_WINDOW as application request is used, and there's no really usable timestamp, only timestamp from the time the (new) application instance was started, so KWin will activate the window *sigh* - the bad thing here is that there's absolutely no chance to recognize the case of starting this KUniqueApp from Konsole (and thus wanting the already visible window to become active) from the case when something started this KUniqueApp without ASN (in which case the already visible window shouldn't become active) - the only solution is using ASN for starting applications, at least silent (i.e. without feedback) - when one application wants to activate another application's window (e.g. KMail activating already running KAddressBook window ?) - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem - with ASN - can't be here, it's the KUniqueApp case then - without ASN - _NET_ACTIVE_WINDOW as application request should be used, KWin will activate the new window depending on the timestamp and whether it belongs to the currently active application _NET_ACTIVE_WINDOW usage: data.l[0]= 1 ->app request = 2 ->pager request = 0 - backwards compatibility data.l[1]= timestamp */ //**************************************** // Workspace //**************************************** /*! Informs the workspace about the active client, i.e. the client that has the focus (or None if no client has the focus). This functions is called by the client itself that gets focus. It has no other effect than fixing the focus chain and the return value of activeClient(). And of course, to propagate the active client to the world. */ void Workspace::setActiveClient( Client* c, allowed_t ) { if ( active_client == c ) return; if( popup && popup_client != c && set_active_client_recursion == 0 ) { popup->close(); popup_client = 0; } StackingUpdatesBlocker blocker( this ); ++set_active_client_recursion; if( active_client != NULL ) { // note that this may call setActiveClient( NULL ), therefore the recursion counter active_client->setActive( false ); } active_client = c; Q_ASSERT( c == NULL || c->isActive()); if( active_client != NULL ) last_active_client = active_client; if ( active_client ) { focus_chain.remove( c ); if ( c->wantsTabFocus() ) focus_chain.append( c ); active_client->demandAttention( false ); } updateCurrentTopMenu(); updateToolWindows( false ); updateStackingOrder(); // e.g. fullscreens have different layer when active/not-active rootInfo->setActiveWindow( active_client? active_client->window() : 0 ); updateColormap(); --set_active_client_recursion; } /*! Tries to activate the client \a c. This function performs what you expect when clicking the respective entry in a taskbar: showing and raising the client (this may imply switching to the another virtual desktop) and putting the focus onto it. Once X really gave focus to the client window as requested, the client itself will call setActiveClient() and the operation is complete. This may not happen with certain focus policies, though. \sa stActiveClient(), requestFocus() */ void Workspace::activateClient( Client* c, bool force ) { if( c == NULL ) { setActiveClient( NULL, Allowed ); return; } raiseClient( c ); if (!c->isOnDesktop(currentDesktop()) ) { setCurrentDesktop( c->desktop() ); // popupinfo->showInfo( desktopName(currentDesktop()) ); // AK - not sure } if( c->isMinimized()) c->unminimize(); if( options->focusPolicyIsReasonable()) requestFocus( c, force ); else { - if( activeClient() != c ) + if( mostRecentlyActivatedClient() != c ) c->demandAttention(); } c->updateUserTime(); } /*! Tries to activate the client by asking X for the input focus. This function does not perform any show, raise or desktop switching. See Workspace::activateClient() instead. \sa Workspace::activateClient() */ void Workspace::requestFocus( Client* c, bool force ) { // the 'if( c == active_client ) return;' optimization mustn't be done here if (!focusChangeEnabled() && ( c != active_client) ) return; //TODO will be different for non-root clients. (subclassing?) if ( !c ) { focusToNull(); return; } if( !c->isOnCurrentDesktop()) // shouldn't happen, call activateClient() if needed { kdWarning( 1212 ) << "requestFocus: not on current desktop" << endl; return; } Client* modal = c->findModal(); if( modal != NULL && modal != c ) { if( !modal->isOnDesktop( c->desktop())) modal->setDesktop( c->desktop()); requestFocus( modal, force ); return; } if ( c->isShown( false ) ) { c->takeFocus( force, Allowed ); should_get_focus.append( c ); focus_chain.remove( c ); if ( c->wantsTabFocus() ) focus_chain.append( c ); } else if ( c->isShade() && c->wantsInput()) { // client cannot accept focus, but at least the window should be active (window menu, et. al. ) c->setActive( true ); focusToNull(); } } /*! Informs the workspace that the client \a c has been hidden. If it was the active client (or to-become the active client), the workspace activates another one. \a c may already be destroyed */ extern bool block_focus; // SELI void Workspace::clientHidden( Client* c ) { assert( !c->isShown( true ) || !c->isOnCurrentDesktop()); if( !( c == active_client || ( should_get_focus.count() > 0 && c == should_get_focus.last()))) return; if( popup ) popup->close(); if( c == active_client ) setActiveClient( NULL, Allowed ); should_get_focus.remove( c ); if( !block_focus ) { if ( c->wantsTabFocus() && focus_chain.contains( c ) ) { focus_chain.remove( c ); focus_chain.prepend( c ); } if ( options->focusPolicyIsReasonable()) { // search the focus_chain for a client to transfer focus to // if 'c' is transient, transfer focus to the first suitable mainwindow Client* get_focus = NULL; const ClientList mainwindows = c->mainClients(); for( ClientList::ConstIterator it = focus_chain.fromLast(); it != focus_chain.end(); --it ) { if( !(*it)->isShown( false ) || !(*it)->isOnCurrentDesktop()) continue; if( mainwindows.contains( *it )) { get_focus = *it; break; } if( get_focus == NULL ) get_focus = *it; } if( get_focus == NULL ) get_focus = findDesktop( true, currentDesktop()); if( get_focus != NULL ) requestFocus( get_focus ); else focusToNull(); } } else // if blocking focus, move focus to the desktop later if needed // in order to avoid flickering focusToNull(); } void Workspace::gotFocusIn( const Client* c ) { if( should_get_focus.contains( const_cast< Client* >( c ))) { // remove also all sooner elements that should have got FocusIn, // but didn't for some reason (and also won't anymore, because they were sooner) while( should_get_focus.first() != c ) should_get_focus.pop_front(); } } // focus_in -> the window got FocusIn event // session_active -> the window was active when saving session bool Workspace::allowClientActivation( const Client* c, Time time, bool focus_in, bool session_active ) { // options->focusStealingPreventionLevel : // 0 - none - old KWin behaviour, new windows always get focus // 1 - low - focus stealing prevention is applied normally, when unsure, activation is allowed // 2 - normal - focus stealing prevention is applied normally, when unsure, activation is not allowed, // this is the default // 3 - high - new window gets focus only if it belongs to the active application, // or when no window is currently active // 4 - extreme - no window gets focus without user intervention if( session_saving && options->focusStealingPreventionLevel <= 3 ) // <= normal { return true; } - Client* ac = activeClient(); + Client* ac = mostRecentlyActivatedClient(); if( focus_in ) { if( should_get_focus.contains( const_cast< Client* >( c ))) return true; // FocusIn was result of KWin's action // Before getting FocusIn, the active Client already // got FocusOut, and therefore got deactivated. ac = last_active_client; } if( options->focusStealingPreventionLevel == 0 ) // none return true; if( options->focusStealingPreventionLevel == 5 ) // extreme return false; if( ac == NULL || ac->isDesktop()) { kdDebug( 1212 ) << "Activation: No client active, allowing" << endl; return true; // no active client -> always allow } if( time == 0 ) // explicitly asked not to get focus return false; // TODO window urgency -> return true? if( Client::belongToSameApplication( c, ac, true )) { kdDebug( 1212 ) << "Activation: Belongs to active application" << endl; return true; } if( options->focusStealingPreventionLevel == 4 ) // high return false; if( time == -1U ) // no time known if( session_active ) return !was_user_interaction; // see Client::readUserTimeMapTimestamp() else { kdDebug() << "Activation: No timestamp at all" << endl; if( options->focusStealingPreventionLevel == 1 ) // low return true; // no timestamp at all, don't activate - because there's also creation timestamp // done on CreateNotify, this case should happen only in case application // maps again already used window, i.e. this won't happen after app startup return false; } // options->focusStealingPreventionLevel == 2 // normal Time user_time = ac->userTime(); kdDebug( 1212 ) << "Activation, compared:" << time << ":" << user_time << ":" << ( timestampCompare( time, user_time ) >= 0 ) << endl; return timestampCompare( time, user_time ) >= 0; // time >= user_time } // basically the same like allowClientActivation(), this time allowing // a window to be fully raised upon its own request (XRaiseWindow), // if refused, it will be raised only on top of windows belonging // to the same application bool Workspace::allowFullClientRaising( const Client* c ) { if( session_saving && options->focusStealingPreventionLevel <= 3 ) // <= normal { return true; } - Client* ac = activeClient(); + Client* ac = mostRecentlyActivatedClient(); if( options->focusStealingPreventionLevel == 0 ) // none return true; if( options->focusStealingPreventionLevel == 5 ) // extreme return false; if( ac == NULL || ac->isDesktop()) { kdDebug( 1212 ) << "Raising: No client active, allowing" << endl; return true; // no active client -> always allow } // TODO window urgency -> return true? if( Client::belongToSameApplication( c, ac, true )) { kdDebug( 1212 ) << "Raising: Belongs to active application" << endl; return true; } if( options->focusStealingPreventionLevel == 4 ) // high return false; if( !c->hasUserTimeSupport()) { kdDebug() << "Raising: No support" << endl; if( options->focusStealingPreventionLevel == 1 ) // low return true; } // options->focusStealingPreventionLevel == 2 // normal kdDebug() << "Raising: Refusing" << endl; return false; } // called from Client after FocusIn that wasn't initiated by KWin and the client // wasn't allowed to activate void Workspace::restoreFocus() { // this updateXTime() is necessary - as FocusIn events don't have // a timestamp *sigh*, kwin's timestamp would be older than the timestamp // that was used by whoever caused the focus change, and therefore // the attempt to restore the focus would fail due to old timestamp updateXTime(); if( should_get_focus.count() > 0 ) requestFocus( should_get_focus.last()); else if( last_active_client ) requestFocus( last_active_client ); } void Workspace::clientAttentionChanged( Client* c, bool set ) { if( set ) { attention_chain.remove( c ); attention_chain.prepend( c ); } else attention_chain.remove( c ); } // This is used when a client should be shown active immediately after requestFocus(), // without waiting for the matching FocusIn that will really make the window the active one. // Used only in special cases, e.g. for MouseActivateRaiseandMove with transparent windows, bool Workspace::fakeRequestedActivity( Client* c ) { if( should_get_focus.count() > 0 && should_get_focus.last() == c ) { if( c->isActive()) return false; c->setActive( true ); return true; } return false; } void Workspace::unfakeActivity( Client* c ) { if( should_get_focus.count() > 0 && should_get_focus.last() == c ) { // TODO this will cause flicker, and probably is not needed if( last_active_client != NULL ) last_active_client->setActive( true ); else c->setActive( false ); } } //******************************************** // Client //******************************************** /*! Updates the user time (time of last action in the active window). This is called inside kwin for every action with the window that qualifies for user interaction (clicking on it, activate it externally, etc.). */ void Client::updateUserTime( Time time ) { if( time == CurrentTime ) time = qt_x_time; if( time != -1U && ( user_time == CurrentTime || timestampCompare( time, user_time ) > 0 )) // time > user_time user_time = time; } Time Client::readUserCreationTime() const { long result = -1; // Time == -1 means none Atom type; int format, status; unsigned long nitems = 0; unsigned long extra = 0; unsigned char *data = 0; KXErrorHandler handler; // ignore errors? status = XGetWindowProperty( qt_xdisplay(), window(), atoms->kde_net_wm_user_creation_time, 0, 10000, FALSE, XA_CARDINAL, &type, &format, &nitems, &extra, &data ); if (status == Success ) { if (data && nitems > 0) result = *((long*) data); XFree(data); } return result; } void Client::demandAttention( bool set ) { if( isActive()) set = false; info->setState( set ? NET::DemandsAttention : 0, NET::DemandsAttention ); workspace()->clientAttentionChanged( this, set ); } // TODO I probably shouldn't be lazy here and do it without the macro, so that people can read it KWIN_COMPARE_PREDICATE( SameApplicationActiveHackPredicate, const Client*, // ignore already existing splashes, toolbars, utilities, menus and topmenus, // as the app may show those before the main window !cl->isSplash() && !cl->isToolbar() && !cl->isTopMenu() && !cl->isUtility() && !cl->isMenu() && Client::belongToSameApplication( cl, value, true ) && cl != value); Time Client::readUserTimeMapTimestamp( const KStartupInfoData* asn_data, const SessionInfo* session ) const { Time time = info->userTime(); kdDebug( 1212 ) << "User timestamp, initial:" << time << endl; // newer ASN timestamp always replaces user timestamp, unless user timestamp is 0 // helps e.g. with konqy reusing if( asn_data != NULL && time != 0 && ( time == -1U || ( asn_data->timestamp() != -1U && timestampCompare( asn_data->timestamp(), time ) > 0 ))) time = asn_data->timestamp(); kdDebug( 1212 ) << "User timestamp, ASN:" << time << endl; if( time == -1U ) { // The window doesn't have any timestamp. // If it's the first window for its application // (i.e. there's no other window from the same app), // use the _KDE_NET_WM_USER_CREATION_TIME trick. // Otherwise, refuse activation of a window // from already running application if this application // is not the active one. - if( workspace()->activeClient() != NULL - && !belongToSameApplication( workspace()->activeClient(), this, true )) + Client* act = workspace()->mostRecentlyActivatedClient(); + if( act != NULL && !belongToSameApplication( act, this, true )) { bool first_window = true; if( isTransient()) { - if( workspace()->activeClient()->hasTransient( this, true )) + if( act->hasTransient( this, true )) ; // is transient for currently active window, even though it's not // the same app (e.g. kcookiejar dialog) -> allow activation else if( groupTransient() && mainClients().isEmpty()) ; // standalone transient else first_window = false; } else { if( workspace()->findClient( SameApplicationActiveHackPredicate( this ))) first_window = false; } if( !first_window ) { kdDebug( 1212 ) << "User timestamp, already exists:" << 0 << endl; return 0; // refuse activation } } // Creation time would just mess things up during session startup, // as possibly many apps are started up at the same time. // If there's no active window yet, no timestamp will be needed, // as plain Workspace::allowClientActivation() will return true // in such case. And if there's already active window, // it's better not to activate the new one. // Unless it was the active window at the time // of session saving and there was no user interaction yet, // this check will be done in Workspace::allowClientActiovationTimestamp(). if( session && !session->fake ) return -1U; time = readUserCreationTime(); } kdDebug( 1212 ) << "User timestamp, final:" << time << endl; return time; } /*! Sets the client's active state to \a act. This function does only change the visual appearance of the client, it does not change the focus setting. Use Workspace::activateClient() or Workspace::requestFocus() instead. If a client receives or looses the focus, it calls setActive() on its own. */ void Client::setActive( bool act) { if ( active == act ) return; active = act; workspace()->setActiveClient( act ? this : NULL, Allowed ); if ( active ) Notify::raise( Notify::Activate ); if ( !active && autoRaiseTimer ) { delete autoRaiseTimer; autoRaiseTimer = 0; } if( !active && shade_mode == ShadeActivated ) setShade( ShadeNormal ); StackingUpdatesBlocker blocker( workspace()); workspace()->updateClientLayer( this ); // active windows may get different layer // TODO optimize? mainClients() may be a bit expensive ClientList mainclients = mainClients(); for( ClientList::ConstIterator it = mainclients.begin(); it != mainclients.end(); ++it ) if( (*it)->isFullScreen()) // fullscreens go high even if their transient is active workspace()->updateClientLayer( *it ); if( decoration != NULL ) decoration->activeChange(); updateMouseGrab(); updateUrgency(); // demand attention again if it's still urgent } void Client::startupIdChanged() { KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification( this, asn_data ); if( !asn_valid ) return; if( asn_data.desktop() != 0 ) workspace()->sendClientToDesktop( this, asn_data.desktop(), true ); if( asn_data.timestamp() != -1U ) { bool activate = workspace()->allowClientActivation( this, asn_data.timestamp()); if( asn_data.desktop() != 0 && !isOnCurrentDesktop()) activate = false; // it was started on different desktop than current one if( activate ) workspace()->activateClient( this ); else demandAttention(); } } void Client::updateUrgency() { if( urgency ) demandAttention(); } } // namespace diff --git a/kwin/workspace.h b/kwin/workspace.h index 544860c8f3..528d915cd3 100644 --- a/kwin/workspace.h +++ b/kwin/workspace.h @@ -1,663 +1,672 @@ /***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ #ifndef KWIN_WORKSPACE_H #define KWIN_WORKSPACE_H #include #include #include #include #include "KWinInterface.h" #include "utils.h" #include "kdecoration.h" #include "sm.h" #include class QPopupMenu; class KConfig; class KGlobalAccel; class KStartupInfo; class KStartupInfoData; namespace KWinInternal { class Client; class TabBox; class PopupInfo; class RootInfo; class PluginMgr; class Placement; class SystemTrayWindow { public: SystemTrayWindow() : win(0),winFor(0) {} SystemTrayWindow( WId w ) : win(w),winFor(0) {} SystemTrayWindow( WId w, WId wf ) : win(w),winFor(wf) {} bool operator==( const SystemTrayWindow& other ) { return win == other.win; } WId win; WId winFor; }; typedef QValueList SystemTrayWindowList; class Workspace : public QObject, public KWinInterface, public KDecorationDefines { Q_OBJECT public: Workspace( bool restore = FALSE ); virtual ~Workspace(); static Workspace * self() { return _self; } bool workspaceEvent( XEvent * ); KDecoration* createDecoration( KDecorationBridge* bridge ); bool hasClient( const Client * ); template< typename T > Client* findClient( T predicate ); template< typename T1, typename T2 > void forEachClient( T1 procedure, T2 predicate ); template< typename T > void forEachClient( T procedure ); Group* findGroup( Window leader ); void addGroup( Group* group, allowed_t ); void removeGroup( Group* group, allowed_t ); QRect clientArea( clientAreaOption, const QPoint& p, int desktop ) const; QRect clientArea( clientAreaOption, const Client* c ) const; /** * @internal */ void killWindowId( Window window); void killWindow() { slotKillWindow(); } WId rootWin() const; bool initializing() const; /** * Returns the active client, i.e. the client that has the focus (or None * if no client has the focus) */ Client* activeClient() const; + // Client that was activated, but it's not yet really activeClient(), because + // we didn't process yet the matching FocusIn event. Used mostly in focus + // stealing prevention code. + Client* mostRecentlyActivatedClient() const; void setActiveClient( Client*, allowed_t ); void activateClient( Client*, bool force = FALSE ); void requestFocus( Client* c, bool force = FALSE ); bool allowClientActivation( const Client* c, Time time = -1U, bool focus_in = false, bool session_active = false ); void restoreFocus(); void gotFocusIn( const Client* ); bool fakeRequestedActivity( Client* c ); void unfakeActivity( Client* c ); void updateColormap(); void setFocusChangeEnabled(bool b) { focus_change = b; } bool focusChangeEnabled() { return focus_change; } /** * Indicates that the client c is being moved around by the user. */ void setClientIsMoving( Client *c ); void place(Client *c); void placeSmart( Client* c ); QPoint adjustClientPosition( Client* c, QPoint pos ); void raiseClient( Client* c ); void lowerClient( Client* c ); void raiseClientRequest( Client* c ); void lowerClientRequest( Client* c ); void restackClientUnderActive( Client* ); void updateClientLayer( Client* c ); void raiseOrLowerClient( Client * ); void reconfigure(); void clientHidden( Client* ); void clientAttentionChanged( Client* c, bool set ); void clientMoved(const QPoint &pos, Time time); /** * Returns the current virtual desktop of this workspace */ int currentDesktop() const; /** * Returns the number of virtual desktops of this workspace */ int numberOfDesktops() const; void setNumberOfDesktops( int n ); QWidget* desktopWidget(); // for TabBox Client* nextFocusChainClient(Client*) const; Client* previousFocusChainClient(Client*) const; Client* nextStaticClient(Client*) const; Client* previousStaticClient(Client*) const; int nextDesktopFocusChain( int iDesktop ) const; int previousDesktopFocusChain( int iDesktop ) const; /** * Returns the list of clients sorted in stacking order, with topmost client * at the last position */ const ClientList& stackingOrder() const; ClientList ensureStackingOrder( const ClientList& clients ) const; Client* topClientOnDesktop( int desktop ) const; Client* findDesktop( bool topmost, int desktop ) const; void sendClientToDesktop( Client* c, int desktop, bool dont_activate ); // KDE4 remove me - and it's also in the DCOP interface :( void showWindowMenuAt( unsigned long id, int x, int y ); /** * Shows the menu operations menu for the client * and makes it active if it's not already. */ void showWindowMenu( int x, int y, Client* cl ); void showWindowMenu( QPoint pos, Client* cl ); void updateMinimizedOfTransients( Client* ); void updateOnAllDesktopsOfTransients( Client* ); void checkTransients( Window w ); void performWindowOperation( Client* c, WindowOperation op ); void storeSession( KConfig* config, SMSavePhase phase ); SessionInfo* takeSessionInfo( Client* ); // dcop interface void cascadeDesktop(); void unclutterDesktop(); void doNotManage(QString); bool setCurrentDesktop( int new_desktop ); void nextDesktop(); void previousDesktop(); void circulateDesktopApplications(); QString desktopName( int desk ) const; void setDesktopLayout(int o, int x, int y); bool isNotManaged( const QString& title ); // ### setter or getter ? void sendPingToWindow( Window w, Time timestamp ); // called from Client::pingWindow() // only called from Client::destroyClient() or Client::releaseWindow() void removeClient( Client*, allowed_t ); bool checkStartupNotification( const Client* c, KStartupInfoData& data ); void focusToNull(); // SELI public? void sessionSaveStarted(); void sessionSaveDone(); void setWasUserInteraction(); bool sessionSaving() const; bool managingTopMenus() const; int topMenuHeight() const; int packPositionLeft( const Client* cl, int oldx, bool left_edge ) const; int packPositionRight( const Client* cl, int oldx, bool right_edge ) const; int packPositionUp( const Client* cl, int oldy, bool top_edge ) const; int packPositionDown( const Client* cl, int oldy, bool bottom_edge ) const; public slots: void refresh(); // keybindings void slotSwitchDesktopNext(); void slotSwitchDesktopPrevious(); void slotSwitchDesktopRight(); void slotSwitchDesktopLeft(); void slotSwitchDesktopUp(); void slotSwitchDesktopDown(); void slotSwitchToDesktop( int ); //void slotSwitchToWindow( int ); void slotWindowToDesktop( int ); //void slotWindowToListPosition( int ); void slotWindowMaximize(); void slotWindowMaximizeVertical(); void slotWindowMaximizeHorizontal(); void slotWindowMinimize(); void slotWindowShade(); void slotWindowRaise(); void slotWindowLower(); void slotWindowRaiseOrLower(); void slotActivateAttentionWindow(); void slotWindowPackLeft(); void slotWindowPackRight(); void slotWindowPackUp(); void slotWindowPackDown(); void slotWindowGrowHorizontal(); void slotWindowGrowVertical(); void slotWindowShrinkHorizontal(); void slotWindowShrinkVertical(); void slotWalkThroughDesktops(); void slotWalkBackThroughDesktops(); void slotWalkThroughDesktopList(); void slotWalkBackThroughDesktopList(); void slotWalkThroughWindows(); void slotWalkBackThroughWindows(); void slotWindowOperations(); void slotWindowClose(); void slotWindowMove(); void slotWindowResize(); void slotWindowAbove(); void slotWindowBelow(); void slotWindowOnAllDesktops(); void slotWindowFullScreen(); void slotWindowNoBorder(); void slotWindowToNextDesktop(); void slotWindowToPreviousDesktop(); void slotMouseEmulation(); void slotSettingsChanged( int category ); void slotReconfigure(); void slotKillWindow(); void slotGrabWindow(); void slotGrabDesktop(); void updateClientArea(); private slots: void desktopPopupAboutToShow(); void clientPopupAboutToShow(); void sendToDesktop( int ); void clientPopupActivated( int ); void configureWM(); void desktopResized(); void slotUpdateToolWindows(); void lostTopMenuSelection(); void lostTopMenuOwner(); protected: bool keyPressMouseEmulation( XKeyEvent& ev ); bool netCheck( XEvent* e ); private: void init(); void initShortcuts(); void readShortcuts(); void initDesktopPopup(); bool startKDEWalkThroughWindows(); bool startWalkThroughDesktops( int mode ); // TabBox::Mode::DesktopMode | DesktopListMode bool startWalkThroughDesktops(); bool startWalkThroughDesktopList(); void KDEWalkThroughWindows( bool forward ); void CDEWalkThroughWindows( bool forward ); void walkThroughDesktops( bool forward ); void KDEOneStepThroughWindows( bool forward ); void oneStepThroughDesktops( bool forward, int mode ); // TabBox::Mode::DesktopMode | DesktopListMode void oneStepThroughDesktops( bool forward ); void oneStepThroughDesktopList( bool forward ); void updateStackingOrder( bool propagate_new_clients = false ); void propagateClients( bool propagate_new_clients ); // called only from updateStackingOrder ClientList constrainedStackingOrder(); void raiseClientWithinApplication( Client* c ); void lowerClientWithinApplication( Client* c ); bool allowFullClientRaising( const Client* c ); bool keepTransientAbove( const Client* mainwindow, const Client* transient ); void blockStackingUpdates( bool block ); void updateCurrentTopMenu(); void addTopMenu( Client* c ); void removeTopMenu( Client* c ); void setupTopMenuHandling(); void updateTopMenuGeometry( Client* c = NULL ); void updateToolWindows( bool also_hide ); // this is the right way to create a new client Client* createClient( Window w, bool is_mapped ); void addClient( Client* c, allowed_t ); Window findSpecialEventWindow( XEvent* e ); void randomPlacement(Client* c); void smartPlacement(Client* c); void cascadePlacement(Client* c, bool re_init = false); bool addSystemTrayWin( WId w ); bool removeSystemTrayWin( WId w, bool check ); void propagateSystemTrayWins(); SystemTrayWindow findSystemTrayWin( WId w ); // desktop names and number of desktops void loadDesktopSettings(); void saveDesktopSettings(); // mouse emulation WId getMouseEmulationWindow(); enum MouseEmulation { EmuPress, EmuRelease, EmuMove }; unsigned int sendFakedMouseEvent( QPoint pos, WId win, MouseEmulation type, int button, unsigned int state ); // returns the new state void tabBoxKeyPress( const KKeyNative& keyX ); void tabBoxKeyRelease( const XKeyEvent& ev ); // electric borders void createBorderWindows(); void destroyBorderWindows(); void electricBorder(XEvent * e); void raiseElectricBorders(); // ------------------ void helperDialog( const QString& message, const Client* c ); void calcDesktopLayout(int &x, int &y); QPopupMenu* clientPopup(); void updateClientArea( bool force ); SystemTrayWindowList systemTrayWins; int current_desktop; int number_of_desktops; QMemArray desktop_focus_chain; Client* popup_client; void loadSessionInfo(); QWidget* desktop_widget; QPtrList session; QPtrList fakeSession; void loadFakeSessionInfo(); void storeFakeSessionInfo( Client* c ); void writeFakeSessionInfo(); static const char* windowTypeToTxt( NET::WindowType type ); static NET::WindowType txtToWindowType( const char* txt ); static bool sessionInfoWindowTypeMatch( Client* c, SessionInfo* info ); Client* active_client; Client* last_active_client; Client* most_recently_raised; // used _only_ by raiseOrLowerClient() Client* movingClient; ClientList clients; ClientList desktops; ClientList unconstrained_stacking_order; ClientList stacking_order; ClientList focus_chain; ClientList should_get_focus; // last is most recent ClientList attention_chain; GroupList groups; bool was_user_interaction; bool session_saving; int session_active_client; int session_desktop; bool control_grab; bool tab_grab; //KKeyNative walkThroughDesktopsKeycode, walkBackThroughDesktopsKeycode; //KKeyNative walkThroughDesktopListKeycode, walkBackThroughDesktopListKeycode; //KKeyNative walkThroughWindowsKeycode, walkBackThroughWindowsKeycode; KShortcut cutWalkThroughDesktops, cutWalkThroughDesktopsReverse; KShortcut cutWalkThroughDesktopList, cutWalkThroughDesktopListReverse; KShortcut cutWalkThroughWindows, cutWalkThroughWindowsReverse; bool mouse_emulation; unsigned int mouse_emulation_state; WId mouse_emulation_window; bool focus_change; TabBox* tab_box; PopupInfo* popupinfo; QPopupMenu *popup; QPopupMenu *options_popup; QPopupMenu *desk_popup; int desk_popup_index; KGlobalAccel *keys; WId root; PluginMgr *mgr; RootInfo *rootInfo; QWidget* supportWindow; // swallowing QStringList doNotManageList; // colormap handling Colormap default_colormap; Colormap installed_colormap; // Timer to collect requests for 'reconfigure' QTimer reconfigureTimer; QTimer updateToolWindowsTimer; static Workspace *_self; bool workspaceInit; KStartupInfo* startup; bool electric_have_borders; int electric_current_border; WId electric_top_border; WId electric_bottom_border; WId electric_left_border; WId electric_right_border; int electricLeft; int electricRight; int electricTop; int electricBottom; Time electric_time_first; Time electric_time_last; QPoint electric_push_point; Qt::Orientation layoutOrientation; int layoutX; int layoutY; Placement *initPositioning; QRect* workarea; // array of workareas for virtual desktops bool managing_topmenus; KSelectionOwner* topmenu_selection; KSelectionWatcher* topmenu_watcher; ClientList topmenus; // doesn't own them mutable int topmenu_height; QWidget* topmenu_space; int set_active_client_recursion; int block_stacking_updates; // when >0, stacking updates are temporarily disabled bool blocked_propagating_new_clients; // propagate also new clients after enabling stacking updates? friend class StackingUpdatesBlocker; }; // helper for Workspace::blockStackingUpdates() being called in pairs (true/false) class StackingUpdatesBlocker { public: StackingUpdatesBlocker( Workspace* w ) : ws( w ) { ws->blockStackingUpdates( true ); } ~StackingUpdatesBlocker() { ws->blockStackingUpdates( false ); } private: Workspace* ws; }; // NET WM Protocol handler class class RootInfo : public NETRootInfo2 { private: typedef KWinInternal::Client Client; // because of NET::Client public: RootInfo( Workspace* ws, Display *dpy, Window w, const char *name, unsigned long pr[], int pr_num, int scr= -1); protected: virtual void changeNumberOfDesktops(int n); virtual void changeCurrentDesktop(int d); // virtual void changeActiveWindow(Window w); the extended version is used virtual void changeActiveWindow(Window w,NET::RequestSource src, Time timestamp); virtual void closeWindow(Window w); virtual void moveResize(Window w, int x_root, int y_root, unsigned long direction); virtual void moveResizeWindow(Window w, int flags, int x, int y, int width, int height ); virtual void gotPing(Window w, Time timestamp); virtual void restackWindow(Window w, Window above, int detail); private: Workspace* workspace; }; inline WId Workspace::rootWin() const { return root; } inline bool Workspace::initializing() const { return workspaceInit; } inline Client* Workspace::activeClient() const { return active_client; } +inline Client* Workspace::mostRecentlyActivatedClient() const + { + return should_get_focus.count() > 0 ? should_get_focus.last() : active_client; + } + inline int Workspace::currentDesktop() const { return current_desktop; } inline int Workspace::numberOfDesktops() const { return number_of_desktops; } inline void Workspace::addGroup( Group* group, allowed_t ) { groups.append( group ); } inline void Workspace::removeGroup( Group* group, allowed_t ) { groups.remove( group ); } inline const ClientList& Workspace::stackingOrder() const { // TODO Q_ASSERT( block_stacking_updates == 0 ); return stacking_order; } inline void Workspace::showWindowMenu(QPoint pos, Client* cl) { showWindowMenu(pos.x(), pos.y(), cl); } inline void Workspace::setWasUserInteraction() { was_user_interaction = true; } inline bool Workspace::managingTopMenus() const { return managing_topmenus; } inline void Workspace::sessionSaveStarted() { session_saving = true; } inline void Workspace::sessionSaveDone() { session_saving = false; } inline bool Workspace::sessionSaving() const { return session_saving; } template< typename T > inline Client* Workspace::findClient( T predicate ) { for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) { if ( predicate( const_cast< const Client* >( *it))) return *it; } for ( ClientList::ConstIterator it = desktops.begin(); it != desktops.end(); ++it) { if ( predicate( const_cast< const Client* >( *it))) return *it; } return NULL; } template< typename T1, typename T2 > inline void Workspace::forEachClient( T1 procedure, T2 predicate ) { for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) if ( predicate( const_cast< const Client* >( *it))) procedure( *it ); for ( ClientList::ConstIterator it = desktops.begin(); it != desktops.end(); ++it) if ( predicate( const_cast< const Client* >( *it))) procedure( *it ); } template< typename T > inline void Workspace::forEachClient( T procedure ) { return forEachClient( procedure, TruePredicate()); } KWIN_COMPARE_PREDICATE( ClientMatchPredicate, const Client*, cl == value ); inline bool Workspace::hasClient( const Client* c ) { return findClient( ClientMatchPredicate( c )); } } // namespace #endif