diff --git a/kwin/activation.cpp b/kwin/activation.cpp index 49167b77f4..70fe5e9db9 100644 --- a/kwin/activation.cpp +++ b/kwin/activation.cpp @@ -1,796 +1,798 @@ /***************************************************************** 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" #include "group.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()) ) { ++block_focus; setCurrentDesktop( c->desktop() ); --block_focus; // popupinfo->showInfo( desktopName(currentDesktop()) ); // AK - not sure } if( c->isMinimized()) c->unminimize(); if( options->focusPolicyIsReasonable()) requestFocus( c, force ); 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())) // move the modal to client's desktop modal->setDesktop( c->isOnAllDesktops() ? currentDesktop() : 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 */ void Workspace::clientHidden( Client* c ) { assert( !c->isShown( true ) || !c->isOnCurrentDesktop()); activateNextClient( c ); } // deactivates 'c' and activates next client void Workspace::activateNextClient( Client* c ) { // if 'c' is not the active or the to-become active one, do nothing 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( focusChangeEnabled()) { 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( time == -1U ) + time = c->userTime(); if( session_saving && options->focusStealingPreventionLevel <= 3 ) // <= normal { return true; } 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( options->ignoreFocusStealingClasses.contains(QString::fromLatin1(c->resourceClass()))) return true; 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( 1212 ) << "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 = 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 } if( options->ignoreFocusStealingClasses.contains(QString::fromLatin1(c->resourceClass()))) return true; // 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( 1212 ) << "Raising: No support" << endl; if( options->focusStealingPreventionLevel == 1 ) // low return true; } // options->focusStealingPreventionLevel == 2 // normal kdDebug( 1212 ) << "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 ) { // copied in Group::updateUserTime 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. Client* act = workspace()->mostRecentlyActivatedClient(); if( act != NULL && !belongToSameApplication( act, this, true )) { bool first_window = true; if( isTransient()) { 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() && findClientInList( mainClients(), SameApplicationActiveHackPredicate( this )) == NULL ) ; // 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; } Time Client::userTime() const { Time time = user_time; assert( group() != NULL ); if( time == -1U || ( group()->userTime() != -1U && timestampCompare( group()->userTime(), time ) > 0 )) time = group()->userTime(); 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 ) cancelAutoRaise(); 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( window(), 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(); } //**************************************** // Group //**************************************** void Group::startupIdChanged() { KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification( leader_wid, asn_data ); if( !asn_valid ) return; if( asn_data.timestamp() != -1U && user_time != -1U &×tampCompare( asn_data.timestamp(), user_time ) > 0 ) user_time = asn_data.timestamp(); } void Group::updateUserTime( Time time ) { // copy of Client::updateUserTime if( time == CurrentTime ) time = qt_x_time; if( time != -1U && ( user_time == CurrentTime || timestampCompare( time, user_time ) > 0 )) // time > user_time user_time = time; } } // namespace diff --git a/kwin/events.cpp b/kwin/events.cpp index 4245487bc2..ec4af720e9 100644 --- a/kwin/events.cpp +++ b/kwin/events.cpp @@ -1,1483 +1,1486 @@ /***************************************************************** 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 handling incoming events. */ #include "client.h" #include "workspace.h" #include "atoms.h" #include "tabbox.h" #include "group.h" #include #include #include #include #include extern Time qt_x_time; extern Atom qt_window_role; namespace KWinInternal { // **************************************** // WinInfo // **************************************** WinInfo::WinInfo( Client * c, Display * display, Window window, Window rwin, const unsigned long pr[], int pr_size ) : NETWinInfo( display, window, rwin, pr, pr_size, NET::WindowManager ), m_client( c ) { } void WinInfo::changeDesktop(int desktop) { m_client->workspace()->sendClientToDesktop( m_client, desktop, true ); } void WinInfo::changeState( unsigned long state, unsigned long mask ) { mask &= ~NET::Sticky; // KWin doesn't support large desktops, ignore mask &= ~NET::Hidden; // clients are not allowed to change this directly state &= mask; // for safety, clear all other bits if(( mask & NET::FullScreen ) != 0 && ( state & NET::FullScreen ) == 0 ) m_client->setFullScreen( false, false ); if ( (mask & NET::Max) == NET::Max ) m_client->setMaximize( state & NET::MaxVert, state & NET::MaxHoriz ); else if ( mask & NET::MaxVert ) m_client->setMaximize( state & NET::MaxVert, m_client->maximizeMode() & Client::MaximizeHorizontal ); else if ( mask & NET::MaxHoriz ) m_client->setMaximize( m_client->maximizeMode() & Client::MaximizeVertical, state & NET::MaxHoriz ); if ( mask & NET::Shaded ) m_client->setShade( state & NET::Shaded ? Client::ShadeNormal : Client::ShadeNone ); if ( mask & NET::KeepAbove) m_client->setKeepAbove( (state & NET::KeepAbove) != 0 ); if ( mask & NET::KeepBelow) m_client->setKeepBelow( (state & NET::KeepBelow) != 0 ); if( mask & NET::SkipTaskbar ) m_client->setSkipTaskbar( ( state & NET::SkipTaskbar ) != 0, true ); if( mask & NET::SkipPager ) m_client->setSkipPager( ( state & NET::SkipPager ) != 0 ); if( mask & NET::DemandsAttention ) m_client->demandAttention(( state & NET::DemandsAttention ) != 0 ); if( mask & NET::Modal ) m_client->setModal( ( state & NET::Modal ) != 0 ); // unsetting fullscreen first, setting it last (because e.g. maximize works only for !isFullScreen() ) if(( mask & NET::FullScreen ) != 0 && ( state & NET::FullScreen ) != 0 ) m_client->setFullScreen( true, false ); } // **************************************** // RootInfo // **************************************** RootInfo::RootInfo( Workspace* ws, Display *dpy, Window w, const char *name, unsigned long pr[], int pr_num, int scr ) : NETRootInfo2( dpy, w, name, pr, pr_num, scr ) { workspace = ws; } void RootInfo::changeNumberOfDesktops(int n) { workspace->setNumberOfDesktops( n ); } void RootInfo::changeCurrentDesktop(int d) { workspace->setCurrentDesktop( d ); } void RootInfo::changeActiveWindow( Window w, NET::RequestSource src, Time timestamp, Window active_window ) { if( Client* c = workspace->findClient( WindowMatchPredicate( w ))) { + if( timestamp == CurrentTime ) + timestamp = c->userTime(); if( src == NET::FromUnknown ) src = NET::FromTool; // KWIN_FOCUS, use qt_x_time as timestamp? if( src == NET::FromTool ) workspace->activateClient( c ); else // NET::FromApplication { Client* c2; if( workspace->allowClientActivation( c, timestamp )) workspace->activateClient( c ); // if activation of the requestor's window would be allowed, allow activation too else if( active_window != None && ( c2 = workspace->findClient( WindowMatchPredicate( active_window ))) != NULL - && workspace->allowClientActivation( c2, timestamp )) + && workspace->allowClientActivation( c2, + timestampCompare( timestamp, c2->userTime() > 0 ? timestamp : c2->userTime()))) workspace->activateClient( c ); else c->demandAttention(); } } } void RootInfo::closeWindow(Window w) { Client* c = workspace->findClient( WindowMatchPredicate( w )); if ( c ) c->closeWindow(); } void RootInfo::moveResize(Window w, int x_root, int y_root, unsigned long direction) { Client* c = workspace->findClient( WindowMatchPredicate( w )); if ( c ) { updateXTime(); // otherwise grabbing may have old timestamp - this message should include timestamp c->NETMoveResize( x_root, y_root, (Direction)direction); } } void RootInfo::moveResizeWindow(Window w, int flags, int x, int y, int width, int height ) { Client* c = workspace->findClient( WindowMatchPredicate( w )); if ( c ) c->NETMoveResizeWindow( flags, x, y, width, height ); } void RootInfo::gotPing( Window w, Time timestamp ) { if( Client* c = workspace->findClient( WindowMatchPredicate( w ))) c->gotPing( timestamp ); } void RootInfo::restackWindow( Window w, Window above, int detail ) { if( Client* c = workspace->findClient( WindowMatchPredicate( w ))) c->restackWindow( above, detail, NET::FromTool, true ); } // **************************************** // Workspace // **************************************** /*! Handles workspace specific XEvents */ bool Workspace::workspaceEvent( XEvent * e ) { if ( mouse_emulation && (e->type == ButtonPress || e->type == ButtonRelease ) ) { mouse_emulation = FALSE; XUngrabKeyboard( qt_xdisplay(), qt_x_time ); } if ( e->type == PropertyNotify || e->type == ClientMessage ) { if ( netCheck( e ) ) return TRUE; } // events that should be handled before Clients can get them switch (e->type) { case ButtonPress: case ButtonRelease: was_user_interaction = true; // fallthrough case MotionNotify: if ( tab_grab || control_grab ) { tab_box->handleMouseEvent( e ); return TRUE; } break; case KeyPress: { was_user_interaction = true; KKeyNative keyX( (XEvent*)e ); uint keyQt = keyX.keyCodeQt(); kdDebug(125) << "Workspace::keyPress( " << keyX.key().toString() << " )" << endl; if (movingClient) { movingClient->keyPressEvent(keyQt); return true; } if( tab_grab || control_grab ) { tabBoxKeyPress( keyX ); return true; } break; } case KeyRelease: was_user_interaction = true; if( tab_grab || control_grab ) { tabBoxKeyRelease( e->xkey ); return true; } break; }; if( Client* c = findClient( WindowMatchPredicate( e->xany.window ))) { if( c->windowEvent( e )) return true; } else if( Client* c = findClient( WrapperIdMatchPredicate( e->xany.window ))) { if( c->windowEvent( e )) return true; } else if( Client* c = findClient( FrameIdMatchPredicate( e->xany.window ))) { if( c->windowEvent( e )) return true; } else { Window special = findSpecialEventWindow( e ); if( special != None ) if( Client* c = findClient( WindowMatchPredicate( special ))) { if( c->windowEvent( e )) return true; } } switch (e->type) { case CreateNotify: if ( e->xcreatewindow.parent == root && !QWidget::find( e->xcreatewindow.window) && !e->xcreatewindow.override_redirect ) { // see comments for allowClientActivation() XChangeProperty(qt_xdisplay(), e->xcreatewindow.window, atoms->kde_net_wm_user_creation_time, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&qt_x_time, 1); } break; case UnmapNotify: { // check for system tray windows if ( removeSystemTrayWin( e->xunmap.window, true ) ) { // If the system tray gets destroyed, the system tray // icons automatically get unmapped, reparented and mapped // again to the closest non-client ancestor due to // QXEmbed's SaveSet feature. Unfortunatly with kicker // this closest ancestor is not the root window, but our // decoration, so we reparent explicitely back to the root // window. XEvent ev; WId w = e->xunmap.window; if ( XCheckTypedWindowEvent (qt_xdisplay(), w, ReparentNotify, &ev) ) { if ( ev.xreparent.parent != root ) { XReparentWindow( qt_xdisplay(), w, root, 0, 0 ); addSystemTrayWin( w ); } } return TRUE; } return ( e->xunmap.event != e->xunmap.window ); // hide wm typical event from Qt } case MapNotify: return ( e->xmap.event != e->xmap.window ); // hide wm typical event from Qt case ReparentNotify: { //do not confuse Qt with these events. After all, _we_ are the //window manager who does the reparenting. return TRUE; } case DestroyNotify: { if ( removeSystemTrayWin( e->xdestroywindow.window, false ) ) return TRUE; return false; } case MapRequest: { updateXTime(); // e->xmaprequest.window is different from e->xany.window // TODO this shouldn't be necessary now Client* c = findClient( WindowMatchPredicate( e->xmaprequest.window )); if ( !c ) { // don't check for the parent being the root window, this breaks when some app unmaps // a window, changes something and immediately maps it back, without giving KWin // a chance to reparent it back to root // since KWin can get MapRequest only for root window children and // children of WindowWrapper (=clients), the check is AFAIK useless anyway // Note: Now the save-set support in Client::mapRequestEvent() actually requires that // this code doesn't check the parent to be root. // if ( e->xmaprequest.parent == root ) { //###TODO store previously destroyed client ids if ( addSystemTrayWin( e->xmaprequest.window ) ) return TRUE; c = createClient( e->xmaprequest.window, false ); if ( c != NULL && root != qt_xrootwin() ) { // TODO what is this? // TODO may use QWidget::create XReparentWindow( qt_xdisplay(), c->frameId(), root, 0, 0 ); } if( c == NULL ) // refused to manage, simply map it (most probably override redirect) XMapRaised( qt_xdisplay(), e->xmaprequest.window ); return true; } if ( c ) { c->windowEvent( e ); if ( !c->wantsTabFocus()) focus_chain.remove( c ); // TODO move focus_chain changes to functions return true; } break; } case EnterNotify: if ( QWhatsThis::inWhatsThisMode() ) { QWidget* w = QWidget::find( e->xcrossing.window ); if ( w ) QWhatsThis::leaveWhatsThisMode(); } if (electric_have_borders && (e->xcrossing.window == electric_top_border || e->xcrossing.window == electric_left_border || e->xcrossing.window == electric_bottom_border || e->xcrossing.window == electric_right_border)) { // the user entered an electric border electricBorder(e); } break; case LeaveNotify: { if ( !QWhatsThis::inWhatsThisMode() ) break; // TODO is this cliente ever found, given that client events are searched above? Client* c = findClient( FrameIdMatchPredicate( e->xcrossing.window )); if ( c && e->xcrossing.detail != NotifyInferior ) QWhatsThis::leaveWhatsThisMode(); break; } case ConfigureRequest: { if ( e->xconfigurerequest.parent == root ) { XWindowChanges wc; unsigned int value_mask = 0; wc.border_width = 0; wc.x = e->xconfigurerequest.x; wc.y = e->xconfigurerequest.y; wc.width = e->xconfigurerequest.width; wc.height = e->xconfigurerequest.height; wc.sibling = None; wc.stack_mode = Above; value_mask = e->xconfigurerequest.value_mask | CWBorderWidth; XConfigureWindow( qt_xdisplay(), e->xconfigurerequest.window, value_mask, &wc ); return true; } break; } case KeyPress: if ( mouse_emulation ) return keyPressMouseEmulation( e->xkey ); break; case KeyRelease: if ( mouse_emulation ) return FALSE; break; case FocusIn: if( e->xfocus.window == rootWin() && e->xfocus.detail == NotifyDetailNone ) { updateXTime(); // focusToNull() uses qt_x_time, which is old now (FocusIn has no timestamp) Window focus; int revert; XGetInputFocus( qt_xdisplay(), &focus, &revert ); if( focus == None ) { kdWarning( 1212 ) << "X focus set to None, reseting focus" << endl; focusToNull(); } } // fall through case FocusOut: return true; // always eat these, they would tell Qt that KWin is the active app default: break; } return FALSE; } // Some events don't have the actual window which caused the event // as e->xany.window (e.g. ConfigureRequest), but as some other // field in the XEvent structure. Window Workspace::findSpecialEventWindow( XEvent* e ) { switch( e->type ) { case CreateNotify: return e->xcreatewindow.window; case DestroyNotify: return e->xdestroywindow.window; case UnmapNotify: return e->xunmap.window; case MapNotify: return e->xmap.window; case MapRequest: return e->xmaprequest.window; case ReparentNotify: return e->xreparent.window; case ConfigureNotify: return e->xconfigure.window; case GravityNotify: return e->xgravity.window; case ConfigureRequest: return e->xconfigurerequest.window; case CirculateNotify: return e->xcirculate.window; case CirculateRequest: return e->xcirculaterequest.window; default: return None; }; } /*! Handles client messages sent to the workspace */ bool Workspace::netCheck( XEvent* e ) { unsigned int dirty = rootInfo->event( e ); if ( dirty & NET::DesktopNames ) saveDesktopSettings(); return dirty != 0; } // **************************************** // Client // **************************************** /*! General handler for XEvents concerning the client window */ bool Client::windowEvent( XEvent* e ) { if( e->xany.window == window()) // avoid doing stuff on frame or wrapper { unsigned long dirty[ 2 ]; info->event( e, dirty, 2 ); // pass through the NET stuff if ( ( dirty[ WinInfo::PROTOCOLS ] & NET::WMName ) != 0 ) fetchName(); if ( ( dirty[ WinInfo::PROTOCOLS ] & NET::WMIconName ) != 0 ) fetchIconicName(); if ( ( dirty[ WinInfo::PROTOCOLS ] & NET::WMStrut ) != 0 || ( dirty[ WinInfo::PROTOCOLS2 ] & NET::WM2ExtendedStrut ) != 0 ) { if( isTopMenu()) // the fallback mode of KMenuBar may alter the strut checkWorkspacePosition(); // restore it workspace()->updateClientArea(); } if ( ( dirty[ WinInfo::PROTOCOLS ] & NET::WMIcon) != 0 ) getIcons(); // Note there's a difference between userTime() and info->userTime() // info->userTime() is the value of the property, userTime() also includes // updates of the time done by KWin (ButtonPress on windowrapper etc.). if(( dirty[ WinInfo::PROTOCOLS2 ] & NET::WM2UserTime ) != 0 ) { workspace()->setWasUserInteraction(); updateUserTime( info->userTime()); } if(( dirty[ WinInfo::PROTOCOLS2 ] & NET::WM2StartupId ) != 0 ) startupIdChanged(); } // TODO move all focus handling stuff to separate file? switch (e->type) { case UnmapNotify: unmapNotifyEvent( &e->xunmap ); break; case DestroyNotify: destroyNotifyEvent( &e->xdestroywindow ); break; case MapRequest: // this one may pass the event to workspace return mapRequestEvent( &e->xmaprequest ); case ConfigureRequest: configureRequestEvent( &e->xconfigurerequest ); break; case PropertyNotify: propertyNotifyEvent( &e->xproperty ); break; case KeyPress: updateUserTime(); workspace()->setWasUserInteraction(); break; case ButtonPress: updateUserTime(); workspace()->setWasUserInteraction(); buttonPressEvent( e->xbutton.window, e->xbutton.button, e->xbutton.state, e->xbutton.x, e->xbutton.y, e->xbutton.x_root, e->xbutton.y_root ); break;; case KeyRelease: // don't update user time on releases // e.g. if the user presses Alt+F2, the Alt release // would appear as user input to the currently active window break; case ButtonRelease: // don't update user time on releases // e.g. if the user presses Alt+F2, the Alt release // would appear as user input to the currently active window buttonReleaseEvent( e->xbutton.window, e->xbutton.button, e->xbutton.state, e->xbutton.x, e->xbutton.y, e->xbutton.x_root, e->xbutton.y_root ); break; case MotionNotify: motionNotifyEvent( e->xmotion.window, e->xmotion.state, e->xmotion.x, e->xmotion.y, e->xmotion.x_root, e->xmotion.y_root ); break; case EnterNotify: enterNotifyEvent( &e->xcrossing ); // MotionNotify is guaranteed to be generated only if the mouse // move start and ends in the window; for cases when it only // starts or only ends there, Enter/LeaveNotify are generated. // Fake a MotionEvent in such cases to make handle of mouse // events simpler (Qt does that too). motionNotifyEvent( e->xcrossing.window, e->xcrossing.state, e->xcrossing.x, e->xcrossing.y, e->xcrossing.x_root, e->xcrossing.y_root ); break; case LeaveNotify: motionNotifyEvent( e->xcrossing.window, e->xcrossing.state, e->xcrossing.x, e->xcrossing.y, e->xcrossing.x_root, e->xcrossing.y_root ); leaveNotifyEvent( &e->xcrossing ); break; case FocusIn: focusInEvent( &e->xfocus ); break; case FocusOut: focusOutEvent( &e->xfocus ); break; case ReparentNotify: break; case ClientMessage: clientMessageEvent( &e->xclient ); break; case ColormapChangeMask: if( e->xany.window == window()) { cmap = e->xcolormap.colormap; if ( isActive() ) workspace()->updateColormap(); } break; case VisibilityNotify: visibilityNotifyEvent( &e->xvisibility ); break; default: if( e->xany.window == window()) { if( e->type == Shape::shapeEvent() ) { is_shape = Shape::hasShape( window()); // workaround for #19644 updateShape(); } } break; } return true; // eat all events } /*! Handles map requests of the client window */ bool Client::mapRequestEvent( XMapRequestEvent* e ) { if( e->window != window()) { // Special support for the save-set feature, which is a bit broken. // If there's a window from one client embedded in another one, // e.g. using XEMBED, and the embedder suddenly looses its X connection, // save-set will reparent the embedded window to its closest ancestor // that will remains. Unfortunately, with reparenting window managers, // this is not the root window, but the frame (or in KWin's case, // it's the wrapper for the client window). In this case, // the wrapper will get ReparentNotify for a window it won't know, // which will be ignored, and then it gets MapRequest, as save-set // always maps. Returning true here means that Workspace::workspaceEvent() // will handle this MapRequest and manage this window (i.e. act as if // it was reparented to root window). if( e->parent == wrapperId()) return false; return true; // no messing with frame etc. } if( isTopMenu() && workspace()->managingTopMenus()) return true; // kwin controls these switch ( mappingState() ) { case WithdrawnState: assert( false ); // WMs are not supposed to manage clients in Withdrawn state, // manage(); // after initial mapping manage() is called from createClient() break; case IconicState: // also copied in clientMessage() if( isMinimized()) unminimize(); if( isShade()) setShade( ShadeNone ); if( !isOnCurrentDesktop()) { if( workspace()->allowClientActivation( this )) workspace()->activateClient( this ); else demandAttention(); } break; case NormalState: // TODO fake MapNotify? break; } return true; } /*! Handles unmap notify events of the client window */ void Client::unmapNotifyEvent( XUnmapEvent* e ) { if( e->window != window()) return; if( e->event != wrapperId()) { // most probably event from root window when initially reparenting bool ignore = true; if( e->event == workspace()->rootWin() && e->send_event ) ignore = false; // XWithdrawWindow() if( ignore ) return; } switch( mappingState()) { case IconicState: releaseWindow(); return; case NormalState: // maybe we will be destroyed soon. Check this first. XEvent ev; if( XCheckTypedWindowEvent (qt_xdisplay(), window(), DestroyNotify, &ev) ) // TODO I don't like this much { destroyClient(); // deletes this return; } releaseWindow(); break; default: assert( false ); } } void Client::destroyNotifyEvent( XDestroyWindowEvent* e ) { if( e->window != window()) return; destroyClient(); } bool blockAnimation = FALSE; /*! Handles client messages for the client window */ void Client::clientMessageEvent( XClientMessageEvent* e ) { if( e->window != window()) return; // ignore frame/wrapper // WM_STATE if ( e->message_type == atoms->kde_wm_change_state ) { if( isTopMenu() && workspace()->managingTopMenus()) return; // kwin controls these if( e->data.l[ 1 ] ) blockAnimation = true; if( e->data.l[ 0 ] == IconicState ) minimize(); else if( e->data.l[ 0 ] == NormalState ) { // copied from mapRequest() if( isMinimized()) unminimize(); if( isShade()) setShade( ShadeNone ); if( !isOnCurrentDesktop()) { if( workspace()->allowClientActivation( this )) workspace()->activateClient( this ); else demandAttention(); } } blockAnimation = false; } else if ( e->message_type == atoms->wm_change_state) { if( isTopMenu() && workspace()->managingTopMenus()) return; // kwin controls these if ( e->data.l[0] == IconicState ) minimize(); return; } } /*! Handles configure requests of the client window */ void Client::configureRequestEvent( XConfigureRequestEvent* e ) { if( e->window != window()) return; // ignore frame/wrapper if ( isResize() || isMove()) return; // we have better things to do right now if( isFullScreen() // refuse resizing of fullscreen windows || isSplash() // no manipulations with splashscreens either || isTopMenu()) // topmenus neither { sendSyntheticConfigureNotify(); return; } if ( e->value_mask & CWBorderWidth ) { // first, get rid of a window border XWindowChanges wc; unsigned int value_mask = 0; wc.border_width = 0; value_mask = CWBorderWidth; XConfigureWindow( qt_xdisplay(), window(), value_mask, & wc ); } if( e->value_mask & ( CWX | CWY | CWHeight | CWWidth )) configureRequest( e->value_mask, e->x, e->y, e->width, e->height ); if ( e->value_mask & CWStackMode ) restackWindow( e->above, e->detail, NET::FromApplication ); // TODO sending a synthetic configure notify always is fine, even in cases where // the ICCCM doesn't require this - it can be though of as 'the WM decided to move // the window later'. The client should not cause that many configure request, // so this should not have any significant impact. With user moving/resizing // the it should be optimized though (see also Client::setGeometry()/plainResize()/move()). sendSyntheticConfigureNotify(); // SELI TODO accept configure requests for isDesktop windows (because kdesktop // may get XRANDR resize event before kwin), but check it's still at the bottom? } /*! Handles property changes of the client window */ void Client::propertyNotifyEvent( XPropertyEvent* e ) { if( e->window != window()) return; // ignore frame/wrapper switch ( e->atom ) { case XA_WM_NORMAL_HINTS: getWmNormalHints(); break; case XA_WM_NAME: fetchName(); break; case XA_WM_ICON_NAME: fetchIconicName(); break; case XA_WM_TRANSIENT_FOR: readTransient(); break; case XA_WM_HINTS: getWMHints(); getIcons(); // because KWin::icon() uses WMHints as fallback break; default: if ( e->atom == atoms->wm_protocols ) getWindowProtocols(); else if (e->atom == atoms->wm_client_leader ) getWmClientLeader(); else if( e->atom == qt_window_role ) window_role = getStringProperty( window(), qt_window_role ); break; } } void Client::enterNotifyEvent( XCrossingEvent* e ) { if( e->window != frameId()) return; // care only about entering the whole frame if( e->mode == NotifyNormal || ( !options->focusPolicyIsReasonable() && e->mode == NotifyUngrab ) ) { if (options->shadeHover && isShade()) { delete shadeHoverTimer; shadeHoverTimer = new QTimer( this ); connect( shadeHoverTimer, SIGNAL( timeout() ), this, SLOT( shadeHover() )); shadeHoverTimer->start( options->shadeHoverInterval, TRUE ); } if ( options->focusPolicy == Options::ClickToFocus ) return; if ( options->autoRaise && !isDesktop() && !isDock() && !isTopMenu() && workspace()->focusChangeEnabled() && workspace()->topClientOnDesktop( workspace()->currentDesktop()) != this ) { delete autoRaiseTimer; autoRaiseTimer = new QTimer( this ); connect( autoRaiseTimer, SIGNAL( timeout() ), this, SLOT( autoRaise() ) ); autoRaiseTimer->start( options->autoRaiseInterval, TRUE ); } if ( options->focusPolicy != Options::FocusStrictlyUnderMouse && ( isDesktop() || isDock() || isTopMenu() ) ) return; workspace()->requestFocus( this ); return; } } void Client::leaveNotifyEvent( XCrossingEvent* e ) { if( e->window != frameId()) return; // care only about leaving the whole frame if ( e->mode == NotifyNormal ) { if ( !buttonDown ) { mode = PositionCenter; setCursor( arrowCursor ); } bool lostMouse = !rect().contains( QPoint( e->x, e->y ) ); // 'lostMouse' wouldn't work with e.g. B2 or Keramik, which have non-rectangular decorations // (i.e. the LeaveNotify event comes before leaving the rect and no LeaveNotify event // comes after leaving the rect) - so lets check if the pointer is really outside the window // TODO this still sucks if a window appears above this one - it should lose the mouse // if this window is another client, but not if it's a popup ... maybe after KDE3.1 :( // (repeat after me 'AARGHL!') if ( !lostMouse && e->detail != NotifyInferior ) { int d1, d2, d3, d4; unsigned int d5; Window w, child; if( XQueryPointer( qt_xdisplay(), frameId(), &w, &child, &d1, &d2, &d3, &d4, &d5 ) == False || child == None ) lostMouse = true; // really lost the mouse } if ( lostMouse ) { cancelAutoRaise(); delete shadeHoverTimer; shadeHoverTimer = 0; if ( shade_mode == ShadeHover && !moveResizeMode && !buttonDown ) setShade( ShadeNormal ); } if ( options->focusPolicy == Options::FocusStrictlyUnderMouse ) if ( isActive() && lostMouse ) workspace()->requestFocus( 0 ) ; return; } } #define XCapL KKeyNative::modXLock() #define XNumL KKeyNative::modXNumLock() #define XScrL KKeyNative::modXScrollLock() void Client::grabButton( int modifier ) { unsigned int mods[ 8 ] = { 0, XCapL, XNumL, XNumL | XCapL, XScrL, XScrL | XCapL, XScrL | XNumL, XScrL | XNumL | XCapL }; for( int i = 0; i < 8; ++i ) XGrabButton( qt_xdisplay(), AnyButton, modifier | mods[ i ], wrapperId(), FALSE, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None ); } void Client::ungrabButton( int modifier ) { unsigned int mods[ 8 ] = { 0, XCapL, XNumL, XNumL | XCapL, XScrL, XScrL | XCapL, XScrL | XNumL, XScrL | XNumL | XCapL }; for( int i = 0; i < 8; ++i ) XUngrabButton( qt_xdisplay(), AnyButton, modifier | mods[ i ], wrapperId()); } #undef XCapL #undef XNumL #undef XScrL /* Releases the passive grab for some modifier combinations when a window becomes active. This helps broken X programs that missinterpret LeaveNotify events in grab mode to work properly (Motif, AWT, Tk, ...) */ void Client::updateMouseGrab() { // see Workspace::establishTabBoxGrab() if( isActive() && !workspace()->forcedGlobalMouseGrab()) { // remove the grab for no modifiers only if the window // is unobscured or if the user doesn't want click raise if( !options->clickRaise || not_obscured ) ungrabButton( None ); else grabButton( None ); ungrabButton( ShiftMask ); ungrabButton( ControlMask ); ungrabButton( ControlMask | ShiftMask ); } else { XUngrabButton( qt_xdisplay(), AnyButton, AnyModifier, wrapperId()); // simply grab all modifier combinations XGrabButton(qt_xdisplay(), AnyButton, AnyModifier, wrapperId(), FALSE, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None ); } } int qtToX11Button( Qt::ButtonState button ) { if( button == Qt::LeftButton ) return Button1; else if( button == Qt::MidButton ) return Button2; else if( button == Qt::RightButton ) return Button3; return AnyButton; } int qtToX11State( Qt::ButtonState state ) { int ret = 0; if( state & Qt::LeftButton ) ret |= Button1Mask; if( state & Qt::MidButton ) ret |= Button2Mask; if( state & Qt::RightButton ) ret |= Button3Mask; if( state & Qt::ShiftButton ) ret |= ShiftMask; if( state & Qt::ControlButton ) ret |= ControlMask; if( state & Qt::AltButton ) ret |= KKeyNative::modX(KKey::ALT); if( state & Qt::MetaButton ) ret |= KKeyNative::modX(KKey::WIN); return ret; } // Qt propagates mouse events up the widget hierachy, which means events // for the decoration window cannot be (easily) intercepted as X11 events bool Client::eventFilter( QObject* o, QEvent* e ) { if( decoration == NULL || o != decoration->widget()) return false; if( e->type() == QEvent::MouseButtonPress ) { QMouseEvent* ev = static_cast< QMouseEvent* >( e ); return buttonPressEvent( decorationId(), qtToX11Button( ev->button()), qtToX11State( ev->state()), ev->x(), ev->y(), ev->globalX(), ev->globalY() ); } if( e->type() == QEvent::MouseButtonRelease ) { QMouseEvent* ev = static_cast< QMouseEvent* >( e ); return buttonReleaseEvent( decorationId(), qtToX11Button( ev->button()), qtToX11State( ev->state()), ev->x(), ev->y(), ev->globalX(), ev->globalY() ); } if( e->type() == QEvent::MouseMove ) // FRAME i fake z enter/leave? { QMouseEvent* ev = static_cast< QMouseEvent* >( e ); return motionNotifyEvent( decorationId(), qtToX11State( ev->state()), ev->x(), ev->y(), ev->globalX(), ev->globalY() ); } if( e->type() == QEvent::Resize ) { QResizeEvent* ev = static_cast< QResizeEvent* >( e ); // Filter out resize events that inform about size different than frame size. // This will ensure that decoration->width() etc. and decoration->widget()->width() will be in sync. // These events only seem to be delayed events from initial resizing before show() was called // on the decoration widget. if( ev->size() != size()) return true; } return false; } // return value matters only when filtering events before decoration gets them bool Client::buttonPressEvent( Window w, int button, int state, int x, int y, int x_root, int y_root ) { if (buttonDown) { if( w == wrapperId()) XAllowEvents(qt_xdisplay(), SyncPointer, CurrentTime ); //qt_x_time); return true; } if( w == wrapperId() || w == frameId() || w == decorationId()) { // FRAME neco s tohohle by se melo zpracovat, nez to dostane dekorace updateUserTime(); workspace()->setWasUserInteraction(); uint keyModX = (options->keyCmdAllModKey() == Qt::Key_Meta) ? KKeyNative::modX(KKey::WIN) : KKeyNative::modX(KKey::ALT); bool bModKeyHeld = ( state & KKeyNative::accelModMaskX()) == keyModX; if( isSplash() && button == Button1 && !bModKeyHeld ) { // hide splashwindow if the user clicks on it hideClient( true ); if( w == wrapperId()) XAllowEvents(qt_xdisplay(), SyncPointer, CurrentTime ); //qt_x_time); return true; } if ( isActive() && w == wrapperId() && ( options->clickRaise && !bModKeyHeld ) ) { if ( button < 4 ) // exclude wheel autoRaise(); } Options::MouseCommand com = Options::MouseNothing; bool was_action = false; if ( bModKeyHeld ) { was_action = true; switch (button) { case Button1: com = options->commandAll1(); break; case Button2: com = options->commandAll2(); break; case Button3: com = options->commandAll3(); break; } } else { // inactive inner window if( !isActive() && w == wrapperId()) { was_action = true; switch (button) { case Button1: com = options->commandWindow1(); break; case Button2: com = options->commandWindow2(); break; case Button3: com = options->commandWindow3(); break; default: com = Options::MouseActivateAndPassClick; } } } if( was_action ) { bool replay = performMouseCommand( com, QPoint( x_root, y_root) ); if ( isSpecialWindow() && !isOverride()) replay = TRUE; if( w == wrapperId()) // these can come only from a grab XAllowEvents(qt_xdisplay(), replay? ReplayPointer : SyncPointer, CurrentTime ); //qt_x_time); return true; } } if( w == wrapperId()) // these can come only from a grab { XAllowEvents(qt_xdisplay(), ReplayPointer, CurrentTime ); //qt_x_time); return true; } if( w == decorationId()) return false; // don't eat decoration events if( w == frameId()) processDecorationButtonPress( button, state, x, y, x_root, y_root ); return true; } // this function processes button press events only after decoration decides not to handle them, // unlike buttonPressEvent(), which (when the window is decoration) filters events before decoration gets them void Client::processDecorationButtonPress( int button, int /*state*/, int x, int y, int x_root, int y_root ) { Options::MouseCommand com = Options::MouseNothing; bool active = isActive(); if ( !wantsInput() ) // we cannot be active, use it anyway active = TRUE; if ( button == Button1 ) com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1(); else if ( button == Button2 ) com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2(); else if ( button == Button3 ) com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3(); if( com != Options::MouseOperationsMenu // actions where it's not possible to get the matching && com != Options::MouseMinimize ) // mouse release event { // FRAME mouseMoveEvent( e ); shouldn't be necessary buttonDown = TRUE; moveOffset = QPoint( x, y ); invertedMoveOffset = rect().bottomRight() - moveOffset; unrestrictedMoveResize = false; } performMouseCommand( com, QPoint( x_root, y_root )); } // called from decoration void Client::processMousePressEvent( QMouseEvent* e ) { if( e->type() != QEvent::MouseButtonPress ) { kdWarning() << "processMousePressEvent()" << endl; return; } int button; switch( e->button()) { case LeftButton: button = Button1; break; case MidButton: button = Button2; break; case RightButton: button = Button3; break; default: return; } processDecorationButtonPress( button, e->state(), e->x(), e->y(), e->globalX(), e->globalY()); } // return value matters only when filtering events before decoration gets them bool Client::buttonReleaseEvent( Window w, int /*button*/, int state, int x, int y, int x_root, int y_root ) { if( w == decorationId() && !buttonDown) return false; if( w == wrapperId()) { XAllowEvents(qt_xdisplay(), SyncPointer, CurrentTime ); //qt_x_time); return true; } if( w != frameId() && w != decorationId()) return true; if ( (state & ( Button1Mask & Button2Mask & Button3Mask )) == 0 ) { buttonDown = FALSE; if ( moveResizeMode ) { finishMoveResize( false ); // mouse position is still relative to old Client position, adjust it QPoint mousepos( x_root - x, y_root - y ); mode = mousePosition( mousepos ); setCursor( mode ); } } return true; } static bool was_motion = false; static Time next_motion_time = CurrentTime; // Check whole incoming X queue for MotionNotify events // checking whole queue is done by always returning False in the predicate. // If there are more MotionNotify events in the queue, all until the last // one may be safely discarded (if a ButtonRelease event comes, a MotionNotify // will be faked from it, so there's no need to check other events). // This helps avoiding being overloaded by being flooded from many events // from the XServer. static Bool motion_predicate( Display*, XEvent* ev, XPointer ) { if( ev->type == MotionNotify ) { was_motion = true; next_motion_time = ev->xmotion.time; // for setting time } return False; } static bool waitingMotionEvent() { // The queue doesn't need to be checked until the X timestamp // of processes events reaches the timestamp of the last suitable // MotionNotify event in the queue. if( next_motion_time != CurrentTime && timestampCompare( qt_x_time, next_motion_time ) < 0 ) return true; was_motion = false; XSync( qt_xdisplay(), False ); // this helps to discard more MotionNotify events XEvent dummy; XCheckIfEvent( qt_xdisplay(), &dummy, motion_predicate, NULL ); return was_motion; } // return value matters only when filtering events before decoration gets them bool Client::motionNotifyEvent( Window w, int /*state*/, int x, int y, int x_root, int y_root ) { if( w != frameId() && w != decorationId()) return true; // care only about the whole frame if ( !buttonDown ) { Position newmode = mousePosition( QPoint( x, y )); if( newmode != mode ) setCursor( newmode ); mode = newmode; return false; } if( !waitingMotionEvent()) handleMoveResize( x, y, x_root, y_root ); return true; } void Client::focusInEvent( XFocusInEvent* e ) { if( e->window != window()) return; // only window gets focus if ( e->mode == NotifyUngrab ) return; // we don't care if ( e->detail == NotifyPointer ) return; // we don't care // check if this client is in should_get_focus list or if activation is allowed bool activate = workspace()->allowClientActivation( this, -1U, true ); workspace()->gotFocusIn( this ); // remove from should_get_focus list if( activate ) setActive( TRUE ); else { workspace()->restoreFocus(); demandAttention(); } } // When a client loses focus, FocusOut events are usually immediatelly // followed by FocusIn events for another client that gains the focus // (unless the focus goes to another screen, or to the nofocus widget). // Without this check, the former focused client would have to be // deactivated, and after that, the new one would be activated, with // a short time when there would be no active client. This can cause // flicker sometimes, e.g. when a fullscreen is shown, and focus is transferred // from it to its transient, the fullscreen would be kept in the Active layer // at the beginning and at the end, but not in the middle, when the active // client would be temporarily none (see Client::belongToLayer() ). // Therefore, the events queue is checked, whether it contains the matching // FocusIn event, and if yes, deactivation of the previous client will // be skipped, as activation of the new one will automatically deactivate // previously active client. static bool follows_focusin = false; static bool follows_focusin_failed = false; static Bool predicate_follows_focusin( Display*, XEvent* e, XPointer arg ) { if( follows_focusin || follows_focusin_failed ) return False; Client* c = ( Client* ) arg; if( e->type == FocusIn && c->workspace()->findClient( WindowMatchPredicate( e->xfocus.window ))) { // found FocusIn follows_focusin = true; return False; } // events that may be in the queue before the FocusIn event that's being // searched for if( e->type == FocusIn || e->type == FocusOut || e->type == KeymapNotify ) return False; follows_focusin_failed = true; // a different event - stop search return False; } static bool check_follows_focusin( Client* c ) { follows_focusin = follows_focusin_failed = false; XEvent dummy; // XCheckIfEvent() is used to make the search non-blocking, the predicate // always returns False, so nothing is removed from the events queue. // XPeekIfEvent() would block. XCheckIfEvent( qt_xdisplay(), &dummy, predicate_follows_focusin, (XPointer)c ); return follows_focusin; } void Client::focusOutEvent( XFocusOutEvent* e ) { if( e->window != window()) return; // only window gets focus if ( e->mode == NotifyGrab ) return; // we don't care if ( isShade() ) return; // here neither if ( e->detail != NotifyNonlinear && e->detail != NotifyNonlinearVirtual ) // SELI check all this return; // hack for motif apps like netscape if ( QApplication::activePopupWidget() ) return; if( !check_follows_focusin( this )) setActive( FALSE ); } void Client::visibilityNotifyEvent( XVisibilityEvent * e) { if( e->window != frameId()) return; // care only about the whole frame bool new_not_obscured = e->state == VisibilityUnobscured; if( not_obscured == new_not_obscured ) return; not_obscured = new_not_obscured; updateMouseGrab(); } // performs _NET_WM_MOVERESIZE void Client::NETMoveResize( int x_root, int y_root, NET::Direction direction ) { if( direction == NET::Move ) performMouseCommand( Options::MouseMove, QPoint( x_root, y_root )); else if( direction >= NET::TopLeft && direction <= NET::Left ) { static const Position convert[] = { PositionTopLeft, PositionTop, PositionTopRight, PositionRight, PositionBottomRight, PositionBottom, PositionBottomLeft, PositionLeft }; if(!isResizable() || isShade()) return; if( moveResizeMode ) finishMoveResize( false ); buttonDown = TRUE; moveOffset = QPoint( x_root - x(), y_root - y()); // map from global invertedMoveOffset = rect().bottomRight() - moveOffset; unrestrictedMoveResize = false; mode = convert[ direction ]; setCursor( mode ); if( !startMoveResize()) buttonDown = false; } else if( direction == NET::KeyboardMove ) { // ignore mouse coordinates given in the message, mouse position is used by the moving algorithm QCursor::setPos( geometry().center() ); performMouseCommand( Options::MouseUnrestrictedMove, geometry().center()); } else if( direction == NET::KeyboardSize ) { // ignore mouse coordinates given in the message, mouse position is used by the resizing algorithm QCursor::setPos( geometry().bottomRight()); performMouseCommand( Options::MouseUnrestrictedResize, geometry().bottomRight()); } } void Client::keyPressEvent( uint key_code ) { updateUserTime(); if ( !isMove() && !isResize() ) return; bool is_control = key_code & Qt::CTRL; bool is_alt = key_code & Qt::ALT; key_code = key_code & 0xffff; int delta = is_control?1:is_alt?32:8; QPoint pos = QCursor::pos(); switch ( key_code ) { case Key_Left: pos.rx() -= delta; break; case Key_Right: pos.rx() += delta; break; case Key_Up: pos.ry() -= delta; break; case Key_Down: pos.ry() += delta; break; case Key_Space: case Key_Return: case Key_Enter: finishMoveResize( false ); buttonDown = FALSE; break; case Key_Escape: finishMoveResize( true ); buttonDown = FALSE; break; default: return; } QCursor::setPos( pos ); } // **************************************** // Group // **************************************** bool Group::groupEvent( XEvent* e ) { unsigned long dirty[ 2 ]; leader_info->event( e, dirty, 2 ); // pass through the NET stuff if ( ( dirty[ WinInfo::PROTOCOLS ] & NET::WMIcon) != 0 ) getIcons(); if(( dirty[ WinInfo::PROTOCOLS2 ] & NET::WM2StartupId ) != 0 ) startupIdChanged(); return false; } } // namespace