You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1581 lines
55 KiB
1581 lines
55 KiB
/***************************************************************** |
|
KWin - the KDE window manager |
|
This file is part of the KDE project. |
|
|
|
Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org> |
|
Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org> |
|
|
|
You can Freely distribute this program under the GNU General Public |
|
License. See the file "COPYING" for the exact licensing terms. |
|
******************************************************************/ |
|
|
|
/* |
|
|
|
This file contains things relevant to handling incoming events. |
|
|
|
*/ |
|
|
|
#include "client.h" |
|
#include "workspace.h" |
|
#include "atoms.h" |
|
#include "tabbox.h" |
|
#include "group.h" |
|
#include "rules.h" |
|
|
|
#include <QWhatsThis> |
|
#include <QApplication> |
|
|
|
#include <kkeyserver.h> |
|
|
|
#include <X11/extensions/shape.h> |
|
#include <X11/Xatom.h> |
|
#include <QX11Info> |
|
|
|
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 ? ShadeNormal : 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 ) |
|
: NETRootInfo4( 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::FromApplication && src != FromTool ) |
|
src = NET::FromTool; |
|
if( src == NET::FromTool ) |
|
workspace->activateClient( c, true ); // force |
|
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, |
|
timestampCompare( timestamp, c2->userTime() > 0 ? timestamp : c2->userTime()))) |
|
workspace->activateClient( c ); |
|
else |
|
c->demandAttention(); |
|
} |
|
} |
|
} |
|
|
|
void RootInfo::restackWindow( Window w, RequestSource src, Window above, int detail, Time timestamp ) |
|
{ |
|
if( Client* c = workspace->findClient( WindowMatchPredicate( w ))) |
|
{ |
|
if( timestamp == CurrentTime ) |
|
timestamp = c->userTime(); |
|
if( src != NET::FromApplication && src != FromTool ) |
|
src = NET::FromTool; |
|
c->restackWindow( above, detail, src, timestamp, true ); |
|
} |
|
} |
|
|
|
void RootInfo::gotTakeActivity( Window w, Time timestamp, long flags ) |
|
{ |
|
if( Client* c = workspace->findClient( WindowMatchPredicate( w ))) |
|
workspace->handleTakeActivity( c, timestamp, flags ); |
|
} |
|
|
|
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::changeShowingDesktop( bool showing ) |
|
{ |
|
workspace->setShowingDesktop( showing ); |
|
} |
|
|
|
// **************************************** |
|
// Workspace |
|
// **************************************** |
|
|
|
/*! |
|
Handles workspace specific XEvents |
|
*/ |
|
bool Workspace::workspaceEvent( XEvent * e ) |
|
{ |
|
if ( mouse_emulation && (e->type == ButtonPress || e->type == ButtonRelease ) ) |
|
{ |
|
mouse_emulation = false; |
|
XUngrabKeyboard( QX11Info::display(), QX11Info::appTime() ); |
|
} |
|
|
|
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; |
|
int keyQt; |
|
KKeyServer::xEventToQt(e, keyQt); |
|
kDebug(125) << "Workspace::keyPress( " << keyQt << " )" << endl; |
|
if (movingClient) |
|
{ |
|
movingClient->keyPressEvent(keyQt); |
|
return true; |
|
} |
|
if( tab_grab || control_grab ) |
|
{ |
|
tabBoxKeyPress( keyQt ); |
|
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; |
|
} |
|
} |
|
if( movingClient != NULL && movingClient->moveResizeGrabWindow() == e->xany.window |
|
&& ( e->type == MotionNotify || e->type == ButtonPress || e->type == ButtonRelease )) |
|
{ |
|
if( movingClient->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() |
|
Time t = QX11Info::appTime(); |
|
XChangeProperty(QX11Info::display(), e->xcreatewindow.window, |
|
atoms->kde_net_wm_user_creation_time, XA_CARDINAL, |
|
32, PropModeReplace, (unsigned char *)&t, 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. Unfortunately with kicker |
|
// this closest ancestor is not the root window, but our |
|
// decoration, so we reparent explicitly back to the root |
|
// window. |
|
XEvent ev; |
|
WId w = e->xunmap.window; |
|
if ( XCheckTypedWindowEvent (QX11Info::display(), w, |
|
ReparentNotify, &ev) ) |
|
{ |
|
if ( ev.xreparent.parent != root ) |
|
{ |
|
XReparentWindow( QX11Info::display(), 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 != QX11Info::appRootWindow() ) |
|
{ // TODO what is this? |
|
// TODO may use QWidget::create |
|
XReparentWindow( QX11Info::display(), c->frameId(), root, 0, 0 ); |
|
} |
|
if( c == NULL ) // refused to manage, simply map it (most probably override redirect) |
|
XMapRaised( QX11Info::display(), e->xmaprequest.window ); |
|
return true; |
|
} |
|
if( c ) |
|
{ |
|
c->windowEvent( e ); |
|
updateFocusChains( c, FocusChainUpdate ); |
|
return true; |
|
} |
|
break; |
|
} |
|
case EnterNotify: |
|
{ |
|
if ( QWhatsThis::inWhatsThisMode() ) |
|
{ |
|
QWidget* w = QWidget::find( e->xcrossing.window ); |
|
if ( w ) |
|
QWhatsThis::leaveWhatsThisMode(); |
|
} |
|
if( electricBorder(e)) |
|
return true; |
|
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; |
|
wc.border_width = e->xconfigurerequest.border_width; |
|
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; |
|
unsigned int value_mask = e->xconfigurerequest.value_mask |
|
& ( CWX | CWY | CWWidth | CWHeight | CWBorderWidth ); |
|
XConfigureWindow( QX11Info::display(), 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 || e->xfocus.detail == NotifyPointerRoot )) |
|
{ |
|
updateXTime(); // focusToNull() uses QX11Info::appTime(), which is old now (FocusIn has no timestamp) |
|
Window focus; |
|
int revert; |
|
XGetInputFocus( QX11Info::display(), &focus, &revert ); |
|
if( focus == None || focus == PointerRoot ) |
|
{ |
|
//kWarning( 1212 ) << "X focus set to None/PointerRoot, reseting focus" << endl; |
|
Client *c = mostRecentlyActivatedClient(); |
|
if( c != NULL ) |
|
requestFocus( c, true ); |
|
else if( activateNextClient( NULL )) |
|
; // ok, activated |
|
else |
|
focusToNull(); |
|
} |
|
} |
|
// fall through |
|
case FocusOut: |
|
return true; // always eat these, they would tell Qt that KWin is the active app |
|
case ClientMessage: |
|
if( electricBorder( e )) |
|
return true; |
|
break; |
|
case MappingNotify: |
|
XRefreshKeyboardMapping( &e->xmapping ); |
|
tab_box->updateKeyMapping(); |
|
break; |
|
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(); |
|
if( dirty[ WinInfo::PROTOCOLS ] & NET::WMIconGeometry ) |
|
{ |
|
if( demandAttentionKNotifyTimer != NULL ) |
|
demandAttentionKNotify(); |
|
} |
|
} |
|
|
|
// 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 (QX11Info::display(), 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( fullscreen_mode == FullScreenNormal ) // refuse resizing of fullscreen windows |
|
{ // but allow resizing fullscreen hacks in order to let them cancel fullscreen mode |
|
sendSyntheticConfigureNotify(); |
|
return; |
|
} |
|
if( 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( QX11Info::display(), window(), value_mask, & wc ); |
|
} |
|
|
|
if( e->value_mask & ( CWX | CWY | CWHeight | CWWidth )) |
|
configureRequest( e->value_mask, e->x, e->y, e->width, e->height, 0, false ); |
|
|
|
if ( e->value_mask & CWStackMode ) |
|
restackWindow( e->above, e->detail, NET::FromApplication, userTime(), false ); |
|
|
|
// 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 == atoms->wm_window_role ) |
|
window_role = staticWindowRole( window()); |
|
else if( e->atom == atoms->motif_wm_hints ) |
|
getMotifHints(); |
|
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->setSingleShot( true ); |
|
shadeHoverTimer->start( options->shadeHoverInterval ); |
|
} |
|
|
|
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->setSingleShot( true ); |
|
autoRaiseTimer->start( options->autoRaiseInterval ); |
|
} |
|
|
|
if ( options->focusPolicy != Options::FocusStrictlyUnderMouse && ( isDesktop() || isDock() || isTopMenu() ) ) |
|
return; |
|
if ( options->delayFocus ) |
|
workspace()->requestDelayFocus( this ); |
|
else |
|
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( Qt::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( QX11Info::display(), frameId(), &w, &child, &d1, &d2, &d3, &d4, &d5 ) == False |
|
|| child == None ) |
|
lostMouse = true; // really lost the mouse |
|
} |
|
if ( lostMouse ) |
|
{ |
|
cancelAutoRaise(); |
|
workspace()->cancelDelayFocus(); |
|
cancelShadeHover(); |
|
if ( shade_mode == ShadeHover && !moveResizeMode && !buttonDown ) |
|
setShade( ShadeNormal ); |
|
} |
|
if ( options->focusPolicy == Options::FocusStrictlyUnderMouse ) |
|
if ( isActive() && lostMouse ) |
|
workspace()->requestFocus( 0 ) ; |
|
return; |
|
} |
|
} |
|
|
|
#define XCapL KKeyServer::modXLock() |
|
#define XNumL KKeyServer::modXNumLock() |
|
#define XScrL KKeyServer::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( QX11Info::display(), 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( QX11Info::display(), 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() |
|
{ |
|
if( workspace()->globalShortcutsDisabled()) |
|
{ |
|
XUngrabButton( QX11Info::display(), AnyButton, AnyModifier, wrapperId()); |
|
// keep grab for the simple click without modifiers if needed |
|
if( !( !options->clickRaise || not_obscured )) |
|
grabButton( None ); |
|
return; |
|
} |
|
if( isActive() && !workspace()->forcedGlobalMouseGrab()) // see Workspace::establishTabBoxGrab() |
|
{ |
|
// 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( QX11Info::display(), AnyButton, AnyModifier, wrapperId()); |
|
// simply grab all modifier combinations |
|
XGrabButton(QX11Info::display(), 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 buttons, Qt::KeyboardModifiers modifiers ) |
|
{ |
|
int ret = 0; |
|
if( buttons & Qt::LeftButton ) |
|
ret |= Button1Mask; |
|
if( buttons & Qt::MidButton ) |
|
ret |= Button2Mask; |
|
if( buttons & Qt::RightButton ) |
|
ret |= Button3Mask; |
|
if( modifiers & Qt::ShiftModifier ) |
|
ret |= ShiftMask; |
|
if( modifiers & Qt::ControlModifier ) |
|
ret |= ControlMask; |
|
if( modifiers & Qt::AltModifier ) |
|
ret |= KKeyServer::modXAlt(); |
|
if( modifiers & Qt::MetaModifier ) |
|
ret |= KKeyServer::modXMeta(); |
|
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->buttons(), ev->modifiers() ), |
|
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->buttons(), ev->modifiers() ), |
|
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->buttons(), ev->modifiers() ), |
|
ev->x(), ev->y(), ev->globalX(), ev->globalY() ); |
|
} |
|
if( e->type() == QEvent::Wheel ) |
|
{ |
|
QWheelEvent* ev = static_cast< QWheelEvent* >( e ); |
|
bool r = buttonPressEvent( decorationId(), ev->delta() > 0 ? Button4 : Button5, qtToX11State( ev->buttons(), ev->modifiers() ), |
|
ev->x(), ev->y(), ev->globalX(), ev->globalY() ); |
|
r = r || buttonReleaseEvent( decorationId(), ev->delta() > 0 ? Button4 : Button5, qtToX11State( ev->buttons(), ev->modifiers() ), |
|
ev->x(), ev->y(), ev->globalX(), ev->globalY() ); |
|
return r; |
|
} |
|
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(QX11Info::display(), SyncPointer, CurrentTime ); //QX11Info::appTime()); |
|
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) ? |
|
KKeyServer::modXMeta() : |
|
KKeyServer::modXAlt(); |
|
bool bModKeyHeld = keyModX != 0 && ( state & KKeyServer::accelModMaskX()) == keyModX; |
|
|
|
if( isSplash() |
|
&& button == Button1 && !bModKeyHeld ) |
|
{ // hide splashwindow if the user clicks on it |
|
hideClient( true ); |
|
if( w == wrapperId()) |
|
XAllowEvents(QX11Info::display(), SyncPointer, CurrentTime ); //QX11Info::appTime()); |
|
return true; |
|
} |
|
|
|
Options::MouseCommand com = Options::MouseNothing; |
|
bool was_action = false; |
|
bool perform_handled = 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; |
|
case Button4: |
|
case Button5: |
|
com = options->operationWindowMouseWheel( button == Button4 ? 120 : -120 ); |
|
break; |
|
} |
|
} |
|
else |
|
{ // inactive inner window |
|
if( !isActive() && w == wrapperId()) |
|
{ |
|
was_action = true; |
|
perform_handled = 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; |
|
} |
|
} |
|
// active inner window |
|
if( isActive() && w == wrapperId() |
|
&& options->clickRaise && button < 4 ) // exclude wheel |
|
{ |
|
com = Options::MouseActivateRaiseAndPassClick; |
|
was_action = true; |
|
perform_handled = true; |
|
} |
|
} |
|
if( was_action ) |
|
{ |
|
bool replay = performMouseCommand( com, QPoint( x_root, y_root), perform_handled ); |
|
|
|
if ( isSpecialWindow()) |
|
replay = true; |
|
|
|
if( w == wrapperId()) // these can come only from a grab |
|
XAllowEvents(QX11Info::display(), replay? ReplayPointer : SyncPointer, CurrentTime ); //QX11Info::appTime()); |
|
return true; |
|
} |
|
} |
|
|
|
if( w == wrapperId()) // these can come only from a grab |
|
{ |
|
XAllowEvents(QX11Info::display(), ReplayPointer, CurrentTime ); //QX11Info::appTime()); |
|
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( button == Button1 |
|
&& com != Options::MouseOperationsMenu // actions where it's not possible to get the matching |
|
&& com != Options::MouseMinimize ) // mouse release event |
|
{ |
|
mode = mousePosition( QPoint( x, y )); |
|
buttonDown = true; |
|
moveOffset = QPoint( x, y ); |
|
invertedMoveOffset = rect().bottomRight() - moveOffset; |
|
unrestrictedMoveResize = false; |
|
setCursor( mode ); // update to sizeAllCursor if about to move |
|
} |
|
performMouseCommand( com, QPoint( x_root, y_root )); |
|
} |
|
|
|
// called from decoration |
|
void Client::processMousePressEvent( QMouseEvent* e ) |
|
{ |
|
if( e->type() != QEvent::MouseButtonPress ) |
|
{ |
|
kWarning() << "processMousePressEvent()" << endl; |
|
return; |
|
} |
|
int button; |
|
switch( e->button()) |
|
{ |
|
case Qt::LeftButton: |
|
button = Button1; |
|
break; |
|
case Qt::MidButton: |
|
button = Button2; |
|
break; |
|
case Qt::RightButton: |
|
button = Button3; |
|
break; |
|
default: |
|
return; |
|
} |
|
processDecorationButtonPress( button, e->buttons(), 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(QX11Info::display(), SyncPointer, CurrentTime ); //QX11Info::appTime()); |
|
return true; |
|
} |
|
if( w != frameId() && w != decorationId() && w != moveResizeGrabWindow()) |
|
return true; |
|
x = this->x(); // translate from grab window to local coords |
|
y = this->y(); |
|
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( QX11Info::appTime(), next_motion_time ) < 0 ) |
|
return true; |
|
was_motion = false; |
|
XSync( QX11Info::display(), False ); // this helps to discard more MotionNotify events |
|
XEvent dummy; |
|
XCheckIfEvent( QX11Info::display(), &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() && w != moveResizeGrabWindow()) |
|
return true; // care only about the whole frame |
|
if ( !buttonDown ) |
|
{ |
|
Position newmode = mousePosition( QPoint( x, y )); |
|
if( newmode != mode ) |
|
setCursor( newmode ); |
|
mode = newmode; |
|
// reset the timestamp for the optimization, otherwise with long passivity |
|
// the option in waitingMotionEvent() may be always true |
|
next_motion_time = CurrentTime; |
|
return false; |
|
} |
|
if( w == moveResizeGrabWindow()) |
|
{ |
|
x = this->x(); // translate from grab window to local coords |
|
y = this->y(); |
|
} |
|
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 |
|
if( !isShown( false ) || !isOnCurrentDesktop()) // we unmapped it, but it got focus meanwhile -> |
|
return; // activateNextClient() already transferred focus elsewhere |
|
// 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( QX11Info::display(), &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( moveResizeMode && direction == NET::MoveResizeCancel) |
|
{ |
|
finishMoveResize( true ); |
|
buttonDown = false; |
|
setCursor( mode ); |
|
} |
|
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; |
|
setCursor( mode ); |
|
} |
|
} |
|
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 Qt::Key_Left: |
|
pos.rx() -= delta; |
|
break; |
|
case Qt::Key_Right: |
|
pos.rx() += delta; |
|
break; |
|
case Qt::Key_Up: |
|
pos.ry() -= delta; |
|
break; |
|
case Qt::Key_Down: |
|
pos.ry() += delta; |
|
break; |
|
case Qt::Key_Space: |
|
case Qt::Key_Return: |
|
case Qt::Key_Enter: |
|
finishMoveResize( false ); |
|
buttonDown = false; |
|
setCursor( mode ); |
|
break; |
|
case Qt::Key_Escape: |
|
finishMoveResize( true ); |
|
buttonDown = false; |
|
setCursor( mode ); |
|
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
|
|
|