diff --git a/client.h b/client.h index 4a20f90da..a50c57a41 100644 --- a/client.h +++ b/client.h @@ -1,793 +1,794 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_CLIENT_H #define KWIN_CLIENT_H #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "options.h" #include "workspace.h" #include "kdecoration.h" #include "rules.h" #include "toplevel.h" #ifdef HAVE_XSYNC #include #endif class QProcess; class QTimer; class KStartupInfoData; namespace KWin { class Workspace; class Client; class Bridge; class Client : public Toplevel { Q_OBJECT public: Client( Workspace *ws ); Window wrapperId() const; Window decorationId() const; const Client* transientFor() const; Client* transientFor(); bool isTransient() const; bool groupTransient() const; bool wasOriginallyGroupTransient() const; ClientList mainClients() const; // call once before loop , is not indirect ClientList allMainClients() const; // call once before loop , is indirect bool hasTransient( const Client* c, bool indirect ) const; const ClientList& transients() const; // is not indirect void checkTransient( Window w ); Client* findModal( bool allow_itself = false ); const Group* group() const; Group* group(); void checkGroup( Group* gr = NULL, bool force = false ); void changeClientLeaderGroup( Group* gr ); const WindowRules* rules() const; void removeRule( Rules* r ); void setupWindowRules( bool ignore_temporary ); void applyWindowRules(); void updateWindowRules(); // returns true for "special" windows and false for windows which are "normal" // (normal=window which has a border, can be moved by the user, can be closed, etc.) // true for Desktop, Dock, Splash, Override and TopMenu (and Toolbar??? - for now) // false for Normal, Dialog, Utility and Menu (and Toolbar??? - not yet) TODO bool isSpecialWindow() const; bool hasNETSupport() const; QSize minSize() const; QSize maxSize() const; virtual QPoint clientPos() const; // inside of geometry() virtual QSize clientSize() const; bool windowEvent( XEvent* e ); virtual bool eventFilter( QObject* o, QEvent* e ); #ifdef HAVE_XSYNC void syncEvent( XSyncAlarmNotifyEvent* e ); #endif bool manage( Window w, bool isMapped ); void releaseWindow( bool on_shutdown = false ); void destroyClient(); enum Sizemode // how to resize the window in order to obey constains (mainly aspect ratios) { SizemodeAny, SizemodeFixedW, // try not to affect width SizemodeFixedH, // try not to affect height SizemodeMax // try not to make it larger in either direction }; QSize adjustedSize( const QSize&, Sizemode mode = SizemodeAny ) const; QSize adjustedSize() const; QPixmap icon() const; QPixmap miniIcon() const; bool isActive() const; void setActive( bool ); virtual int desktop() const; void setDesktop( int ); void setOnAllDesktops( bool set ); // !isMinimized() && not hidden, i.e. normally visible on some virtual desktop bool isShown( bool shaded_is_shown ) const; bool isHiddenInternal() const; // for compositing bool isShade() const; // true only for ShadeNormal ShadeMode shadeMode() const; // prefer isShade() void setShade( ShadeMode mode ); bool isShadeable() const; bool isMinimized() const; bool isMaximizable() const; QRect geometryRestore() const; MaximizeMode maximizeModeRestore() const; MaximizeMode maximizeMode() const; bool isMinimizable() const; void setMaximize( bool vertically, bool horizontally ); QRect iconGeometry() const; void setFullScreen( bool set, bool user ); bool isFullScreen() const; bool isFullScreenable( bool fullscreen_hack = false ) const; + bool isActiveFullScreen() const; bool userCanSetFullScreen() const; QRect geometryFSRestore() const { return geom_fs_restore; } // only for session saving int fullScreenMode() const { return fullscreen_mode; } // only for session saving bool noBorder() const; void setNoBorder( bool set ); bool userCanSetNoBorder() const; bool skipTaskbar( bool from_outside = false ) const; void setSkipTaskbar( bool set, bool from_outside ); bool skipPager() const; void setSkipPager( bool ); bool keepAbove() const; void setKeepAbove( bool ); bool keepBelow() const; void setKeepBelow( bool ); Layer layer() const; Layer belongsToLayer() const; void invalidateLayer(); int sessionStackingOrder() const; void setModal( bool modal ); bool isModal() const; // auxiliary functions, depend on the windowType bool wantsTabFocus() const; bool wantsInput() const; bool isResizable() const; bool isMovable() const; bool isMovableAcrossScreens() const; bool isCloseable() const; // may be closed by the user (may have a close button) void takeActivity( int flags, bool handled, allowed_t ); // takes ActivityFlags as arg (in utils.h) void takeFocus( allowed_t ); void demandAttention( bool set = true ); void setMask( const QRegion& r, int mode = X::Unsorted ); QRegion mask() const; void updateDecoration( bool check_workspace_pos, bool force = false ); bool checkBorderSizes( bool also_resize ); void repaintDecoration(); void updateShape(); void setGeometry( int x, int y, int w, int h, ForceGeometry_t force = NormalGeometrySet ); void setGeometry( const QRect& r, ForceGeometry_t force = NormalGeometrySet ); void move( int x, int y, ForceGeometry_t force = NormalGeometrySet ); void move( const QPoint & p, ForceGeometry_t force = NormalGeometrySet ); // plainResize() simply resizes void plainResize( int w, int h, ForceGeometry_t force = NormalGeometrySet ); void plainResize( const QSize& s, ForceGeometry_t force = NormalGeometrySet ); // resizeWithChecks() resizes according to gravity, and checks workarea position void resizeWithChecks( int w, int h, ForceGeometry_t force = NormalGeometrySet ); void resizeWithChecks( const QSize& s, ForceGeometry_t force = NormalGeometrySet ); void keepInArea( QRect area, bool partial = false ); void growHorizontal(); void shrinkHorizontal(); void growVertical(); void shrinkVertical(); bool providesContextHelp() const; KShortcut shortcut() const; void setShortcut( const QString& cut ); bool performMouseCommand( Options::MouseCommand, const QPoint &globalPos, bool handled = false ); QRect adjustedClientArea( const QRect& desktop, const QRect& area ) const; Colormap colormap() const; // updates visibility depending on being shaded, virtual desktop, etc. void updateVisibility(); // hides a client - basically like minimize, but without effects, it's simply hidden void hideClient( bool hide ); bool hiddenPreview() const; // window is mapped in order to get a window pixmap virtual void setupCompositing(); virtual void finishCompositing(); QString caption( bool full = true ) const; void updateCaption(); void keyPressEvent( uint key_code ); // FRAME ?? void updateMouseGrab(); Window moveResizeGrabWindow() const; const QPoint calculateGravitation( bool invert, int gravity = 0 ) const; // FRAME public? void NETMoveResize( int x_root, int y_root, NET::Direction direction ); void NETMoveResizeWindow( int flags, int x, int y, int width, int height ); void restackWindow( Window above, int detail, NET::RequestSource source, Time timestamp, bool send_event = false ); void gotPing( Time timestamp ); void checkWorkspacePosition(); void updateUserTime( Time time = CurrentTime ); Time userTime() const; bool hasUserTimeSupport() const; bool ignoreFocusStealing() const; // does 'delete c;' static void deleteClient( Client* c, allowed_t ); static bool belongToSameApplication( const Client* c1, const Client* c2, bool active_hack = false ); static bool sameAppWindowRoleMatch( const Client* c1, const Client* c2, bool active_hack ); static void readIcons( Window win, QPixmap* icon, QPixmap* miniicon ); void minimize( bool avoid_animation = false ); void unminimize( bool avoid_animation = false ); void closeWindow(); void killWindow(); void maximize( MaximizeMode ); void toggleShade(); void showContextHelp(); void cancelShadeHover(); void cancelAutoRaise(); void checkActiveModal(); bool hasStrut() const; bool isMove() const { return moveResizeMode && mode == PositionCenter; } bool isResize() const { return moveResizeMode && mode != PositionCenter; } private slots: void autoRaise(); void shadeHover(); void shortcutActivated(); void delayedMoveResize(); private: friend class Bridge; // FRAME virtual void processMousePressEvent( QMouseEvent* e ); private: // TODO cleanup the order of things in the .h file // use Workspace::createClient() virtual ~Client(); // use destroyClient() or releaseWindow() Position mousePosition( const QPoint& ) const; void updateCursor(); // transparent stuff void drawbound( const QRect& geom ); void clearbound(); void doDrawbound( const QRect& geom, bool clear ); // handlers for X11 events bool mapRequestEvent( XMapRequestEvent* e ); void unmapNotifyEvent( XUnmapEvent*e ); void destroyNotifyEvent( XDestroyWindowEvent*e ); void configureRequestEvent( XConfigureRequestEvent* e ); virtual void propertyNotifyEvent( XPropertyEvent* e ); void clientMessageEvent( XClientMessageEvent* e ); void enterNotifyEvent( XCrossingEvent* e ); void leaveNotifyEvent( XCrossingEvent* e ); void focusInEvent( XFocusInEvent* e ); void focusOutEvent( XFocusOutEvent* e ); #ifdef HAVE_XDAMAGE virtual void damageNotifyEvent( XDamageNotifyEvent* e ); #endif bool buttonPressEvent( Window w, int button, int state, int x, int y, int x_root, int y_root ); bool buttonReleaseEvent( Window w, int button, int state, int x, int y, int x_root, int y_root ); bool motionNotifyEvent( Window w, int state, int x, int y, int x_root, int y_root ); void processDecorationButtonPress( int button, int state, int x, int y, int x_root, int y_root ); protected: virtual void debug( kdbgstream& stream ) const; private slots: void pingTimeout(); void processKillerExited(); void demandAttentionKNotify(); void syncTimeout(); private: void exportMappingState( int s ); // ICCCM 4.1.3.1, 4.1.4 , NETWM 2.5.1 bool isManaged() const; // returns false if this client is not yet managed void updateAllowedActions( bool force = false ); QSize sizeForClientSize( const QSize&, Sizemode mode = SizemodeAny, bool noframe = false ) const; void changeMaximize( bool horizontal, bool vertical, bool adjust ); void checkMaximizeGeometry(); int checkFullScreenHack( const QRect& geom ) const; // 0 - none, 1 - one xinerama screen, 2 - full area void updateFullScreenHack( const QRect& geom ); void getWmNormalHints(); void getMotifHints(); void getIcons(); void fetchName(); void fetchIconicName(); QString readName() const; void setCaption( const QString& s, bool force = false ); bool hasTransientInternal( const Client* c, bool indirect, ConstClientList& set ) const; void finishWindowRules(); void setShortcutInternal( const KShortcut& cut ); void updateWorkareaDiffs(); void checkDirection( int new_diff, int old_diff, QRect& rect, const QRect& area ); static int computeWorkareaDiff( int left, int right, int a_left, int a_right ); void configureRequest( int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool ); NETExtendedStrut strut() const; int checkShadeGeometry( int w, int h ); void blockGeometryUpdates( bool block ); void getSyncCounter(); void sendSyncRequest(); bool startMoveResize(); void finishMoveResize( bool cancel ); void leaveMoveResize(); void checkUnrestrictedMoveResize(); void handleMoveResize( int x, int y, int x_root, int y_root ); void startDelayedMoveResize(); void stopDelayedMoveResize(); void positionGeometryTip(); void grabButton( int mod ); void ungrabButton( int mod ); void resetMaximize(); void resizeDecoration( const QSize& s ); void performMoveResize(); void pingWindow(); void killProcess( bool ask, Time timestamp = CurrentTime ); void updateUrgency(); static void sendClientMessage( Window w, Atom a, Atom protocol, long data1 = 0, long data2 = 0, long data3 = 0 ); void embedClient( Window w, const XWindowAttributes &attr ); void detectNoBorder(); void destroyDecoration(); void updateFrameExtents(); void internalShow( allowed_t ); void internalHide( allowed_t ); void internalKeep( allowed_t ); void map( allowed_t ); void unmap( allowed_t ); void updateHiddenPreview(); void updateInputShape(); Time readUserTimeMapTimestamp( const KStartupInfoId* asn_id, const KStartupInfoData* asn_data, bool session ) const; Time readUserCreationTime() const; void startupIdChanged(); Window client; Window wrapper; KDecoration* decoration; Bridge* bridge; int desk; bool buttonDown; bool moveResizeMode; bool move_faked_activity; Window move_resize_grab_window; bool move_resize_has_keyboard_grab; bool unrestrictedMoveResize; Position mode; QPoint moveOffset; QPoint invertedMoveOffset; QRect moveResizeGeom; QRect initialMoveResizeGeom; XSizeHints xSizeHint; void sendSyntheticConfigureNotify(); enum MappingState { Withdrawn, // not handled, as per ICCCM WithdrawnState Mapped, // the frame is mapped Unmapped, // the frame is not mapped Kept // the frame should be unmapped, but is kept (for compositing) }; MappingState mapping_state; void readTransient(); Window verifyTransientFor( Window transient_for, bool set ); void addTransient( Client* cl ); void removeTransient( Client* cl ); void removeFromMainClients(); void cleanGrouping(); void checkGroupTransients(); void setTransient( Window new_transient_for_id ); Client* transient_for; Window transient_for_id; Window original_transient_for_id; ClientList transients_list; // SELI make this ordered in stacking order? ShadeMode shade_mode; uint active :1; uint deleting : 1; // true when doing cleanup and destroying the client uint keep_above : 1; // NET::KeepAbove (was stays_on_top) uint skip_taskbar :1; uint original_skip_taskbar :1; // unaffected by KWin uint Pdeletewindow :1; // does the window understand the DeleteWindow protocol? uint Ptakefocus :1;// does the window understand the TakeFocus protocol? uint Ptakeactivity : 1; // does it support _NET_WM_TAKE_ACTIVITY uint Pcontexthelp : 1; // does the window understand the ContextHelp protocol? uint Pping : 1; // does it support _NET_WM_PING? uint input :1; // does the window want input in its wm_hints uint skip_pager : 1; uint motif_may_resize : 1; uint motif_may_move :1; uint motif_may_close : 1; uint keep_below : 1; // NET::KeepBelow uint minimized : 1; uint hidden : 1; // forcibly hidden by calling hide() uint modal : 1; // NET::Modal uint noborder : 1; uint app_noborder : 1; // the app requested no border using something (window type, motif hints) uint urgency : 1; // XWMHints, UrgencyHint uint ignore_focus_stealing : 1; // don't apply focus stealing prevention to this client uint demands_attention : 1; WindowRules client_rules; void getWMHints(); void readIcons(); void getWindowProtocols(); QPixmap icon_pix; QPixmap miniicon_pix; QCursor cursor; // FullScreenHack - non-NETWM fullscreen (noborder,size of desktop) // DON'T reorder - saved to config files !!! enum FullScreenMode { FullScreenNone, FullScreenNormal, FullScreenHack }; FullScreenMode fullscreen_mode; MaximizeMode max_mode; QRect geom_restore; QRect geom_fs_restore; MaximizeMode maxmode_restore; int workarea_diff_x, workarea_diff_y; QTimer* autoRaiseTimer; QTimer* shadeHoverTimer; QTimer* delayedMoveResizeTimer; Colormap cmap; QString cap_normal, cap_iconic, cap_suffix; Group* in_group; Window window_group; Layer in_layer; QTimer* ping_timer; QProcess* process_killer; Time ping_timestamp; Time user_time; unsigned long allowed_actions; QSize client_size; int block_geometry_updates; // >0 - new geometry is remembered, but not actually set enum PendingGeometry_t { PendingGeometryNone, PendingGeometryNormal, PendingGeometryForced }; PendingGeometry_t pending_geometry_update; QRect geom_before_block; bool shade_geometry_change; #ifdef HAVE_XSYNC XSyncCounter sync_counter; XSyncValue sync_counter_value; XSyncAlarm sync_alarm; #endif QTimer* sync_timeout; bool sync_resize_pending; int border_left, border_right, border_top, border_bottom; QRegion _mask; static bool check_active_modal; // see Client::checkActiveModal() KShortcut _shortcut; int sm_stacking_order; friend struct FetchNameInternalPredicate; friend struct CheckIgnoreFocusStealingProcedure; friend struct ResetupRulesProcedure; friend class GeometryUpdatesBlocker; QTimer* demandAttentionKNotifyTimer; friend bool performTransiencyCheck(); }; // helper for Client::blockGeometryUpdates() being called in pairs (true/false) class GeometryUpdatesBlocker { public: GeometryUpdatesBlocker( Client* c ) : cl( c ) { cl->blockGeometryUpdates( true ); } ~GeometryUpdatesBlocker() { cl->blockGeometryUpdates( false ); } private: Client* cl; }; // NET WM Protocol handler class class WinInfo : public NETWinInfo { private: typedef KWin::Client Client; // because of NET::Client public: WinInfo( Client* c, Display * display, Window window, Window rwin, const unsigned long pr[], int pr_size ); virtual void changeDesktop(int desktop); virtual void changeState( unsigned long state, unsigned long mask ); void disable(); private: Client * m_client; }; inline Window Client::wrapperId() const { return wrapper; } inline Window Client::decorationId() const { return decoration != NULL ? decoration->widget()->winId() : None; } inline const Client* Client::transientFor() const { return transient_for; } inline Client* Client::transientFor() { return transient_for; } inline bool Client::groupTransient() const { return transient_for_id == rootWindow(); } // needed because verifyTransientFor() may set transient_for_id to root window, // if the original value has a problem (window doesn't exist, etc.) inline bool Client::wasOriginallyGroupTransient() const { return original_transient_for_id == rootWindow(); } inline bool Client::isTransient() const { return transient_for_id != None; } inline const ClientList& Client::transients() const { return transients_list; } inline const Group* Client::group() const { return in_group; } inline Group* Client::group() { return in_group; } inline bool Client::isMinimized() const { return minimized; } inline bool Client::isActive() const { return active; } inline bool Client::isShown( bool shaded_is_shown ) const { return !isMinimized() && ( !isShade() || shaded_is_shown ) && !hidden; } inline bool Client::isHiddenInternal() const { return hidden; } inline bool Client::isShade() const { return shade_mode == ShadeNormal; } inline ShadeMode Client::shadeMode() const { return shade_mode; } inline QPixmap Client::icon() const { return icon_pix; } inline QPixmap Client::miniIcon() const { return miniicon_pix; } inline QRect Client::geometryRestore() const { return geom_restore; } inline Client::MaximizeMode Client::maximizeModeRestore() const { return maxmode_restore; } inline Client::MaximizeMode Client::maximizeMode() const { return max_mode; } inline bool Client::skipTaskbar( bool from_outside ) const { return from_outside ? original_skip_taskbar : skip_taskbar; } inline bool Client::skipPager() const { return skip_pager; } inline bool Client::keepAbove() const { return keep_above; } inline bool Client::keepBelow() const { return keep_below; } inline bool Client::isFullScreen() const { return fullscreen_mode != FullScreenNone; } inline bool Client::isModal() const { return modal; } inline bool Client::hasNETSupport() const { return info->hasNETSupport(); } inline Colormap Client::colormap() const { return cmap; } inline void Client::invalidateLayer() { in_layer = UnknownLayer; } inline int Client::sessionStackingOrder() const { return sm_stacking_order; } inline bool Client::isManaged() const { return mapping_state != Withdrawn; } inline QPoint Client::clientPos() const { return QPoint( border_left, border_top ); } inline QSize Client::clientSize() const { return client_size; } inline void Client::setGeometry( const QRect& r, ForceGeometry_t force ) { setGeometry( r.x(), r.y(), r.width(), r.height(), force ); } inline void Client::move( const QPoint & p, ForceGeometry_t force ) { move( p.x(), p.y(), force ); } inline void Client::plainResize( const QSize& s, ForceGeometry_t force ) { plainResize( s.width(), s.height(), force ); } inline void Client::resizeWithChecks( const QSize& s, ForceGeometry_t force ) { resizeWithChecks( s.width(), s.height(), force ); } inline bool Client::hasUserTimeSupport() const { return info->userTime() != -1U; } inline bool Client::ignoreFocusStealing() const { return ignore_focus_stealing; } inline const WindowRules* Client::rules() const { return &client_rules; } KWIN_PROCEDURE( CheckIgnoreFocusStealingProcedure, Client, cl->ignore_focus_stealing = options->checkIgnoreFocusStealing( cl )); inline Window Client::moveResizeGrabWindow() const { return move_resize_grab_window; } inline KShortcut Client::shortcut() const { return _shortcut; } inline void Client::removeRule( Rules* rule ) { client_rules.remove( rule ); } inline bool Client::hiddenPreview() const { return mapping_state == Kept; } KWIN_COMPARE_PREDICATE( WrapperIdMatchPredicate, Client, Window, cl->wrapperId() == value ); } // namespace #endif diff --git a/events.cpp b/events.cpp index 4bd6bd7fa..1576f7299 100644 --- a/events.cpp +++ b/events.cpp @@ -1,1724 +1,1725 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* This file contains things relevant to handling incoming events. */ #include #include "client.h" #include "workspace.h" #include "atoms.h" #include "tabbox.h" #include "group.h" #include "rules.h" #include "unmanaged.h" #include "scene.h" #include "effects.h" #include #include #include #include #include #include #ifdef HAVE_XRANDR #include #endif namespace KWin { // **************************************** // 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 ); } void WinInfo::disable() { m_client = NULL; // only used when the object is passed to Deleted } // **************************************** // RootInfo // **************************************** RootInfo::RootInfo( Workspace* ws, Display *dpy, Window w, const char *name, unsigned long pr[], int pr_num, int scr ) : NETRootInfo( 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, false, true )) 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()), false, true )) { 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; ungrabXKeyboard(); } if( effects && static_cast< EffectsHandlerImpl* >( effects )->hasKeyboardGrab() && ( e->type == KeyPress || e->type == KeyRelease )) return false; // let Qt process it, it'll be intercepted again in eventFilter() if( e->type == PropertyNotify || e->type == ClientMessage ) { unsigned long dirty[ NETRootInfo::PROPERTIES_SIZE ]; rootInfo->event( e, dirty, NETRootInfo::PROPERTIES_SIZE ); if( dirty[ NETRootInfo::PROTOCOLS ] & NET::DesktopNames ) saveDesktopSettings(); if( dirty[ NETRootInfo::PROTOCOLS2 ] & NET::WM2DesktopLayout ) updateDesktopLayout(); } // 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; } if( effects && static_cast(effects)->checkInputWindowEvent( e )) return true; break; case KeyPress: { was_user_interaction = true; int keyQt; KKeyServer::xEventToQt(e, &keyQt); // kDebug(125) << "Workspace::keyPress( " << keyQt << " )"; 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 if( Unmanaged* c = findUnmanaged( WindowMatchPredicate( 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 == rootWindow() && !QWidget::find( e->xcreatewindow.window) && !e->xcreatewindow.override_redirect ) { // see comments for allowClientActivation() Time t = xTime(); XChangeProperty(display(), e->xcreatewindow.window, atoms->kde_net_wm_user_creation_time, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&t, 1); } break; case UnmapNotify: { return ( e->xunmap.event != e->xunmap.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: { 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 ) { c = createClient( e->xmaprequest.window, false ); if( c == NULL ) // refused to manage, simply map it (most probably override redirect) XMapRaised( display(), e->xmaprequest.window ); return true; } if( c ) { c->windowEvent( e ); updateFocusChains( c, FocusChainUpdate ); return true; } break; } case MapNotify: { if( e->xmap.override_redirect ) { Unmanaged* c = findUnmanaged( WindowMatchPredicate( e->xmap.window )); if( c == NULL ) c = createUnmanaged( e->xmap.window ); if( c ) return c->windowEvent( e ); } return ( e->xmap.event != e->xmap.window ); // hide wm typical event from Qt } case EnterNotify: { if ( QWhatsThis::inWhatsThisMode() ) { QWidget* w = QWidget::find( e->xcrossing.window ); if ( w ) QWhatsThis::leaveWhatsThisMode(); } if( electricBorderEvent(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 == rootWindow()) { 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( 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 == rootWindow() && ( e->xfocus.detail == NotifyDetailNone || e->xfocus.detail == NotifyPointerRoot )) { updateXTime(); // focusToNull() uses xTime(), which is old now (FocusIn has no timestamp) Window focus; int revert; XGetInputFocus( display(), &focus, &revert ); if( focus == None || focus == PointerRoot ) { //kWarning( 1212 ) << "X focus set to None/PointerRoot, reseting focus" ; 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( electricBorderEvent( e )) return true; break; case Expose: if( compositing() && ( e->xexpose.window == rootWindow() // root window needs repainting || overlay != None && e->xexpose.window == overlay )) // overlay needs repainting { addRepaint( e->xexpose.x, e->xexpose.y, e->xexpose.width, e->xexpose.height ); } break; case VisibilityNotify: if( compositing() && overlay != None && e->xvisibility.window == overlay ) { bool was_visible = overlay_visible; overlay_visible = ( e->xvisibility.state != VisibilityFullyObscured ); if( !was_visible && overlay_visible ) { // hack for #154825 addRepaintFull(); QTimer::singleShot( 2000, this, SLOT( addRepaintFull())); } } break; default: if( e->type == Extensions::randrNotifyEvent() && Extensions::randrAvailable() ) { #ifdef HAVE_XRANDR XRRUpdateConfiguration( e ); #endif if( compositing() ) { // desktopResized() should take care of when the size or // shape of the desktop has changed, but we also want to // catch refresh rate changes finishCompositing(); QTimer::singleShot( 0, this, SLOT( setupCompositing() ) ); } } else if( e->type == Extensions::syncAlarmNotifyEvent() && Extensions::syncAvailable()) { #ifdef HAVE_XSYNC foreach( Client* c, clients ) c->syncEvent( reinterpret_cast< XSyncAlarmNotifyEvent* >( e )); foreach( Client* c, desktops ) c->syncEvent( reinterpret_cast< XSyncAlarmNotifyEvent* >( e )); #endif } break; } return false; } // Used only to filter events that need to be processed by Qt first // (e.g. keyboard input to be composed), otherwise events are // handle by the XEvent filter above bool Workspace::workspaceEvent( QEvent* e ) { if(( e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease || e->type() == QEvent::ShortcutOverride ) && effects && static_cast< EffectsHandlerImpl* >( effects )->hasKeyboardGrab()) { static_cast< EffectsHandlerImpl* >( effects )->grabbedKeyboardEvent( static_cast< QKeyEvent* >( e )); return true; } 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; }; } // **************************************** // 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 ]; double old_opacity = opacity(); 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(); } if( dirty[ WinInfo::PROTOCOLS2 ] & NET::WM2Opacity ) { if( compositing()) { addRepaintFull(); scene->windowOpacityChanged( this ); if( effects ) static_cast(effects)->windowOpacityChanged( effectWindow(), old_opacity ); } else { // forward to the frame if there's possibly another compositing manager running NETWinInfo i( display(), frameId(), rootWindow(), 0 ); i.setOpacity( info->opacity()); } } } 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 ); workspace()->updateFocusMousePosition( QPoint( 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 ); workspace()->updateFocusMousePosition( QPoint( 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 ); // not here, it'd break following enter notify handling // workspace()->updateFocusMousePosition( QPoint( e->xcrossing.x_root, e->xcrossing.y_root )); 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; default: if( e->xany.window == window()) { if( e->type == Extensions::shapeNotifyEvent() ) { detectShape( window()); // workaround for #19644 updateShape(); } } if( e->xany.window == frameId()) { #ifdef HAVE_XDAMAGE if( e->type == Extensions::damageNotifyEvent()) damageNotifyEvent( reinterpret_cast< XDamageNotifyEvent* >( e )); #endif } 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 // also copied in clientMessage() if( isMinimized()) unminimize(); if( isShade()) setShade( ShadeNone ); if( !isOnCurrentDesktop()) { if( workspace()->allowClientActivation( this )) workspace()->activateClient( this ); else demandAttention(); } 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 == rootWindow() && e->send_event ) ignore = false; // XWithdrawWindow() if( ignore ) return; } releaseWindow(); } void Client::destroyNotifyEvent( XDestroyWindowEvent* e ) { if( e->window != window()) return; destroyClient(); } /*! 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 bool avoid_animation = ( e->data.l[ 1 ] ); if( e->data.l[ 0 ] == IconicState ) minimize(); else if( e->data.l[ 0 ] == NormalState ) { // copied from mapRequest() if( isMinimized()) unminimize( avoid_animation ); if( isShade()) setShade( ShadeNone ); if( !isOnCurrentDesktop()) { if( workspace()->allowClientActivation( this )) workspace()->activateClient( this ); else demandAttention(); } } } 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( 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 ); // 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 ) { Toplevel::propertyNotifyEvent( 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->motif_wm_hints ) getMotifHints(); else if( e->atom == atoms->net_wm_sync_request_counter ) getSyncCounter(); 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 ) + workspace()->topClientOnDesktop( workspace()->currentDesktop(), + options->separateScreenFocus ? screen() : -1 ) != this ) { delete autoRaiseTimer; autoRaiseTimer = new QTimer( this ); connect( autoRaiseTimer, SIGNAL( timeout() ), this, SLOT( autoRaise() ) ); autoRaiseTimer->setSingleShot( true ); autoRaiseTimer->start( options->autoRaiseInterval ); } QPoint currentPos( e->x_root, e->y_root ); if ( options->focusPolicy != Options::FocusStrictlyUnderMouse && ( isDesktop() || isDock() || isTopMenu() ) ) return; // for FocusFollowsMouse, change focus only if the mouse has actually been moved, not if the focus // change came because of window changes (e.g. closing a window) - #92290 if( options->focusPolicy != Options::FocusFollowsMouse || currentPos != workspace()->focusMousePosition()) { 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; updateCursor(); } 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( 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( 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( 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( display(), AnyButton, AnyModifier, wrapperId()); // keep grab for the simple click without modifiers if needed (see below) - bool not_obscured = workspace()->topClientOnDesktop( workspace()->currentDesktop(), true, false ) == this; + bool not_obscured = workspace()->topClientOnDesktop( workspace()->currentDesktop(), -1, true, false ) == this; if( !( !options->clickRaise || not_obscured )) grabButton( None ); return; } if( isActive() && !workspace()->forcedGlobalMouseGrab()) // see Workspace::establishTabBoxGrab() { // first grab all modifier combinations XGrabButton( display(), AnyButton, AnyModifier, wrapperId(), false, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None ); // remove the grab for no modifiers only if the window // is unobscured or if the user doesn't want click raise // (it is unobscured if it the topmost in the unconstrained stacking order, i.e. it is // the most recently raised window) - bool not_obscured = workspace()->topClientOnDesktop( workspace()->currentDesktop(), true, false ) == this; + bool not_obscured = workspace()->topClientOnDesktop( workspace()->currentDesktop(), -1, true, false ) == this; if( !options->clickRaise || not_obscured ) ungrabButton( None ); else grabButton( None ); ungrabButton( ShiftMask ); ungrabButton( ControlMask ); ungrabButton( ControlMask | ShiftMask ); } else { XUngrabButton( display(), AnyButton, AnyModifier, wrapperId()); // simply grab all modifier combinations XGrabButton(display(), AnyButton, AnyModifier, wrapperId(), false, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None ); } } // 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; // HACK: Avoid decoration redraw delays. On resize Qt sets WA_WStateConfigPending // which delays all painting until a matching ConfigureNotify event comes. // But this process itself is the window manager, so it's not needed // to wait for that event, the geometry is known. // Note that if Qt in the future changes how this flag is handled and what it // triggers then this may potentionally break things. See mainly QETWidget::translateConfigEvent(). decoration->widget()->setAttribute( Qt::WA_WState_ConfigPending, false ); decoration->widget()->update(); return false; } 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(display(), SyncPointer, CurrentTime ); //xTime()); 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(display(), SyncPointer, CurrentTime ); //xTime()); 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() && button < 4 ) { 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; } } // 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(display(), replay? ReplayPointer : SyncPointer, CurrentTime ); //xTime()); return true; } } if( w == wrapperId()) // these can come only from a grab { XAllowEvents(display(), ReplayPointer, CurrentTime ); //xTime()); 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; startDelayedMoveResize(); updateCursor(); } performMouseCommand( com, QPoint( x_root, y_root )); } // called from decoration void Client::processMousePressEvent( QMouseEvent* e ) { if( e->type() != QEvent::MouseButtonPress ) { kWarning() << "processMousePressEvent()" ; 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(display(), SyncPointer, CurrentTime ); //xTime()); 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; stopDelayedMoveResize(); 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 ); } updateCursor(); } 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( xTime(), next_motion_time ) < 0 ) return true; was_motion = false; XSync( display(), False ); // this helps to discard more MotionNotify events XEvent dummy; XCheckIfEvent( 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 ) { mode = newmode; updateCursor(); } // 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( 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 ); } // 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; updateCursor(); } 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 ]; if( !startMoveResize()) buttonDown = false; updateCursor(); } 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 & ~Qt::KeyboardModifierMask; int delta = is_control?1:is_alt?32:8; QPoint pos = cursorPos(); 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; updateCursor(); break; case Qt::Key_Escape: finishMoveResize( true ); buttonDown = false; updateCursor(); break; default: return; } QCursor::setPos( pos ); } #ifdef HAVE_XSYNC void Client::syncEvent( XSyncAlarmNotifyEvent* e ) { if( e->alarm == sync_alarm && XSyncValueEqual( e->counter_value, sync_counter_value )) { ready_for_painting = true; if( isResize()) { delete sync_timeout; sync_timeout = NULL; if( sync_resize_pending ) performMoveResize(); } } } #endif // **************************************** // Unmanaged // **************************************** bool Unmanaged::windowEvent( XEvent* e ) { double old_opacity = opacity(); unsigned long dirty[ 2 ]; info->event( e, dirty, 2 ); // pass through the NET stuff if( dirty[ NETWinInfo::PROTOCOLS2 ] & NET::WM2Opacity ) { if( compositing()) { addRepaintFull(); scene->windowOpacityChanged( this ); if( effects ) static_cast(effects)->windowOpacityChanged( effectWindow(), old_opacity ); } } switch (e->type) { case UnmapNotify: unmapNotifyEvent( &e->xunmap ); break; case MapNotify: mapNotifyEvent( &e->xmap ); break; case ConfigureNotify: configureNotifyEvent( &e->xconfigure ); break; case PropertyNotify: propertyNotifyEvent( &e->xproperty ); break; default: { if( e->type == Extensions::shapeNotifyEvent() ) { detectShape( window()); addDamageFull(); if( scene != NULL ) scene->windowGeometryShapeChanged( this ); if( effects != NULL ) static_cast(effects)->windowGeometryShapeChanged( effectWindow(), geometry()); } #ifdef HAVE_XDAMAGE if( e->type == Extensions::damageNotifyEvent()) damageNotifyEvent( reinterpret_cast< XDamageNotifyEvent* >( e )); #endif break; } } return false; // don't eat events, even our own unmanaged widgets are tracked } void Unmanaged::mapNotifyEvent( XMapEvent* ) { } void Unmanaged::unmapNotifyEvent( XUnmapEvent* ) { release(); } void Unmanaged::configureNotifyEvent( XConfigureEvent* e ) { if( effects ) static_cast(effects)->checkInputWindowStacking(); // keep them on top QRect newgeom( e->x, e->y, e->width, e->height ); if( newgeom != geom ) { addWorkspaceRepaint( geometry()); // damage old area QRect old = geom; geom = newgeom; if( old.size() != geom.size()) discardWindowPixmap(); if( scene != NULL ) scene->windowGeometryShapeChanged( this ); if( effects != NULL ) static_cast(effects)->windowGeometryShapeChanged( effectWindow(), old ); } } // **************************************** // Toplevel // **************************************** void Toplevel::propertyNotifyEvent( XPropertyEvent* e ) { if( e->window != window()) return; // ignore frame/wrapper switch ( e->atom ) { default: if (e->atom == atoms->wm_client_leader ) getWmClientLeader(); else if( e->atom == atoms->wm_window_role ) getWindowRole(); break; } if( effects ) static_cast< EffectsHandlerImpl* >( effects )->propertyNotify( effectWindow(), e->atom ); } // **************************************** // 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 diff --git a/geometry.cpp b/geometry.cpp index 264c38602..f270e1cf3 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -1,2861 +1,2864 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ /* This file contains things relevant to geometry, i.e. workspace size, window positions and window sizes. */ #include "client.h" #include "workspace.h" #include #include #include #include #include "placement.h" #include "notifications.h" #include "geometrytip.h" #include "rules.h" #include "effects.h" #include #include namespace KWin { //******************************************** // Workspace //******************************************** /*! Resizes the workspace after an XRANDR screen size change */ void Workspace::desktopResized() { QRect geom = QApplication::desktop()->geometry(); NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo->setDesktopGeometry( -1, desktop_geometry ); updateClientArea(); destroyElectricBorders(); updateElectricBorders(); if( compositing() ) { finishCompositing(); QTimer::singleShot( 0, this, SLOT( setupCompositing() ) ); } } /*! Updates the current client areas according to the current clients. If the area changes or force is true, the new areas are propagated to the world. The client area is the area that is available for clients (that which is not taken by windows like panels, the top-of-screen menu etc). \sa clientArea() */ void Workspace::updateClientArea( bool force ) { QDesktopWidget *desktopwidget = KApplication::desktop(); int nscreens = desktopwidget -> numScreens (); // kDebug () << "screens: " << nscreens; QVector< QRect > new_wareas( numberOfDesktops() + 1 ); QVector< QVector< QRect > > new_sareas( numberOfDesktops() + 1 ); QVector< QRect > screens( nscreens ); QRect desktopArea = desktopwidget -> geometry (); for( int iS = 0; iS < nscreens; iS ++ ) { screens [iS] = desktopwidget -> screenGeometry (iS); } for( int i = 1; i <= numberOfDesktops(); ++i ) { new_wareas[ i ] = desktopArea; new_sareas[ i ].resize( nscreens ); for( int iS = 0; iS < nscreens; iS ++ ) new_sareas[ i ][ iS ] = screens[ iS ]; } for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) { if( !(*it)->hasStrut()) continue; QRect r = (*it)->adjustedClientArea( desktopArea, desktopArea ); if( (*it)->isOnAllDesktops()) { for( int i = 1; i <= numberOfDesktops(); ++i ) { new_wareas[ i ] = new_wareas[ i ].intersected( r ); for( int iS = 0; iS < nscreens; iS ++ ) { new_sareas[ i ][ iS ] = new_sareas[ i ][ iS ].intersected( (*it)->adjustedClientArea( desktopArea, screens[ iS ] )); } } } else { new_wareas[ (*it)->desktop() ] = new_wareas[ (*it)->desktop() ].intersected( r ); for( int iS = 0; iS < nscreens; iS ++ ) { // kDebug () << "adjusting new_sarea: " << screens[ iS ]; new_sareas[ (*it)->desktop() ][ iS ] = new_sareas[ (*it)->desktop() ][ iS ].intersected( (*it)->adjustedClientArea( desktopArea, screens[ iS ] )); } } } #if 0 for( int i = 1; i <= numberOfDesktops(); ++i ) { for( int iS = 0; iS < nscreens; iS ++ ) kDebug () << "new_sarea: " << new_sareas[ i ][ iS ]; } #endif // TODO topmenu update for screenarea changes? if( topmenu_space != NULL ) { QRect topmenu_area = desktopArea; topmenu_area.setTop( topMenuHeight()); for( int i = 1; i <= numberOfDesktops(); ++i ) new_wareas[ i ] = new_wareas[ i ].intersected( topmenu_area ); } bool changed = force; if(screenarea.isEmpty()) changed = true; for( int i = 1; !changed && i <= numberOfDesktops(); ++i ) { if( workarea[ i ] != new_wareas[ i ] ) changed = true; if( screenarea[ i ].size() != new_sareas[ i ].size()) changed = true; for( int iS = 0; !changed && iS < nscreens; iS ++ ) if (new_sareas[ i ][ iS ] != screenarea [ i ][ iS ]) changed = true; } if ( changed ) { workarea = new_wareas; screenarea = new_sareas; NETRect r; for( int i = 1; i <= numberOfDesktops(); i++) { r.pos.x = workarea[ i ].x(); r.pos.y = workarea[ i ].y(); r.size.width = workarea[ i ].width(); r.size.height = workarea[ i ].height(); rootInfo->setWorkArea( i, r ); } updateTopMenuGeometry(); for( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) (*it)->checkWorkspacePosition(); for( ClientList::ConstIterator it = desktops.begin(); it != desktops.end(); ++it) (*it)->checkWorkspacePosition(); } } void Workspace::updateClientArea() { updateClientArea( false ); } /*! returns the area available for clients. This is the desktop geometry minus windows on the dock. Placement algorithms should refer to this rather than geometry(). \sa geometry() */ QRect Workspace::clientArea( clientAreaOption opt, int screen, int desktop ) const { if( desktop == NETWinInfo::OnAllDesktops || desktop == 0 ) desktop = currentDesktop(); if( screen == -1 ) screen = activeScreen(); QDesktopWidget *desktopwidget = KApplication::desktop(); QRect sarea = !screenarea.isEmpty() // may be empty during KWin initialization ? screenarea[ desktop ][ screen ] : desktopwidget->screenGeometry( screen ); QRect warea = workarea[ desktop ].isNull() ? QApplication::desktop()->geometry() : workarea[ desktop ]; switch (opt) { case MaximizeArea: if (options->xineramaMaximizeEnabled) return sarea; else return warea; case MaximizeFullArea: if (options->xineramaMaximizeEnabled) return desktopwidget->screenGeometry( screen ); else return desktopwidget->geometry(); case FullScreenArea: if (options->xineramaFullscreenEnabled) return desktopwidget->screenGeometry( screen ); else return desktopwidget->geometry(); case PlacementArea: if (options->xineramaPlacementEnabled) return sarea; else return warea; case MovementArea: if (options->xineramaMovementEnabled) return desktopwidget->screenGeometry( screen ); else return desktopwidget->geometry(); case WorkArea: return warea; case FullArea: return desktopwidget->geometry(); case ScreenArea: return desktopwidget->screenGeometry( screen ); } assert( false ); return QRect(); } QRect Workspace::clientArea( clientAreaOption opt, const QPoint& p, int desktop ) const { QDesktopWidget *desktopwidget = KApplication::desktop(); int screen = desktopwidget->isVirtualDesktop() ? desktopwidget->screenNumber( p ) : desktopwidget->primaryScreen(); if( screen < 0 ) screen = desktopwidget->primaryScreen(); return clientArea( opt, screen, desktop ); } QRect Workspace::clientArea( clientAreaOption opt, const Client* c ) const { return clientArea( opt, c->geometry().center(), c->desktop()); } /*! Client \a c is moved around to position \a pos. This gives the workspace the opportunity to interveniate and to implement snap-to-windows functionality. */ QPoint Workspace::adjustClientPosition( Client* c, QPoint pos, bool unrestricted ) { //CT 16mar98, 27May98 - magics: BorderSnapZone, WindowSnapZone //CT adapted for kwin on 25Nov1999 //aleXXX 02Nov2000 added second snapping mode if (options->windowSnapZone || options->borderSnapZone || options->centerSnapZone ) { const bool sOWO=options->snapOnlyWhenOverlapping; const QRect maxRect = clientArea(MovementArea, pos+c->rect().center(), c->desktop()); const int xmin = maxRect.left(); const int xmax = maxRect.right()+1; //desk size const int ymin = maxRect.top(); const int ymax = maxRect.bottom()+1; const int cx(pos.x()); const int cy(pos.y()); const int cw(c->width()); const int ch(c->height()); const int rx(cx+cw); const int ry(cy+ch); //these don't change int nx(cx), ny(cy); //buffers int deltaX(xmax); int deltaY(ymax); //minimum distance to other clients int lx, ly, lrx, lry; //coords and size for the comparison client, l // border snap int snap = options->borderSnapZone; //snap trigger if (snap) { if ((sOWO?(cxxmax):true) && (qAbs(rx-xmax)ymax):true) && (qAbs(ry-ymax)windowSnapZone; if (snap) { QList::ConstIterator l; for (l = clients.begin();l != clients.end();++l ) { if ((*l)->isOnDesktop(currentDesktop()) && !(*l)->isMinimized() && (*l) != c ) { lx = (*l)->x(); ly = (*l)->y(); lrx = lx + (*l)->width(); lry = ly + (*l)->height(); if ( (( cy <= lry ) && ( cy >= ly )) || (( ry >= ly ) && ( ry <= lry )) || (( cy <= ly ) && ( ry >= lry )) ) { if ((sOWO?(cxlx):true) && (qAbs(rx-lx)= lx )) || (( rx >= lx ) && ( rx <= lrx )) || (( cx <= lx ) && ( rx >= lrx )) ) { if ((sOWO?(cyly):true) && (qAbs(ry-ly)lry):true) && (qAbs(lry-ry)lrx):true) && (qAbs(lrx-rx)centerSnapZone; //snap trigger if (snap) { int diffX = qAbs( (xmin + xmax)/2 - (cx + cw/2) ); int diffY = qAbs( (ymin + ymax)/2 - (cy + ch/2) ); if (diffX < snap && diffY < snap && diffX < deltaX && diffY < deltaY) { // Snap to center of screen deltaX = diffX; deltaY = diffY; nx = (xmin + xmax)/2 - cw/2; ny = (ymin + ymax)/2 - ch/2; } else if ( options->borderSnapZone ) { // Enhance border snap if( ( nx == xmin || nx == xmax - cw ) && diffY < snap && diffY < deltaY) { // Snap to vertical center on screen edge deltaY = diffY; ny = (ymin + ymax)/2 - ch/2; } else if ( (( unrestricted ? ny == ymin : ny <= ymin) || ny == ymax - ch ) && diffX < snap && diffX < deltaX) { // Snap to horizontal center on screen edge deltaX = diffX; nx = (xmin + xmax)/2 - cw/2; } } } pos = QPoint(nx, ny); } return pos; } QRect Workspace::adjustClientSize( Client* c, QRect moveResizeGeom, int mode ) { //adapted from adjustClientPosition on 29May2004 //this function is called when resizing a window and will modify //the new dimensions to snap to other windows/borders if appropriate if ( options->windowSnapZone || options->borderSnapZone ) // || options->centerSnapZone ) { const bool sOWO=options->snapOnlyWhenOverlapping; const QRect maxRect = clientArea(MovementArea, c->rect().center(), c->desktop()); const int xmin = maxRect.left(); const int xmax = maxRect.right(); //desk size const int ymin = maxRect.top(); const int ymax = maxRect.bottom(); const int cx(moveResizeGeom.left()); const int cy(moveResizeGeom.top()); const int rx(moveResizeGeom.right()); const int ry(moveResizeGeom.bottom()); int newcx(cx), newcy(cy); //buffers int newrx(rx), newry(ry); int deltaX(xmax); int deltaY(ymax); //minimum distance to other clients int lx, ly, lrx, lry; //coords and size for the comparison client, l // border snap int snap = options->borderSnapZone; //snap trigger if (snap) { deltaX = int(snap); deltaY = int(snap); #define SNAP_BORDER_TOP \ if ((sOWO?(newcyymax):true) && (qAbs(ymax-newry)xmax):true) && (qAbs(xmax-newrx)windowSnapZone; if (snap) { deltaX = int(snap); deltaY = int(snap); QList::ConstIterator l; for (l = clients.begin();l != clients.end();++l ) { if ((*l)->isOnDesktop(currentDesktop()) && !(*l)->isMinimized() && (*l) != c ) { lx = (*l)->x()-1; ly = (*l)->y()-1; lrx =(*l)->x() + (*l)->width(); lry =(*l)->y() + (*l)->height(); #define WITHIN_HEIGHT ((( newcy <= lry ) && ( newcy >= ly )) || \ (( newry >= ly ) && ( newry <= lry )) || \ (( newcy <= ly ) && ( newry >= lry )) ) #define WITHIN_WIDTH ( (( cx <= lrx ) && ( cx >= lx )) || \ (( rx >= lx ) && ( rx <= lrx )) || \ (( cx <= lx ) && ( rx >= lrx )) ) #define SNAP_WINDOW_TOP if ( (sOWO?(newcyly):true) \ && WITHIN_WIDTH \ && (qAbs( ly - newry ) < deltaY) ) { \ deltaY = qAbs( ly - newry ); \ newry=ly; \ } #define SNAP_WINDOW_LEFT if ( (sOWO?(newcxlx):true) \ && WITHIN_HEIGHT \ && (qAbs( lx - newrx ) < deltaX)) \ { \ deltaX = qAbs( lx - newrx ); \ newrx=lx; \ } #define SNAP_WINDOW_C_TOP if ( (sOWO?(newcylry):true) \ && (newcx == lrx || newrx == lx) \ && qAbs(lry-newry) < deltaY ) { \ deltaY = qAbs( lry - newry - 1 ); \ newry = lry - 1; \ } #define SNAP_WINDOW_C_LEFT if ( (sOWO?(newcxlrx):true) \ && (newcy == lry || newry == ly) \ && qAbs(lrx-newrx) < deltaX ) { \ deltaX = qAbs( lrx - newrx - 1 ); \ newrx = lrx - 1; \ } switch ( mode ) { case PositionBottomRight: SNAP_WINDOW_BOTTOM SNAP_WINDOW_RIGHT SNAP_WINDOW_C_BOTTOM SNAP_WINDOW_C_RIGHT break; case PositionRight: SNAP_WINDOW_RIGHT SNAP_WINDOW_C_RIGHT break; case PositionBottom: SNAP_WINDOW_BOTTOM SNAP_WINDOW_C_BOTTOM break; case PositionTopLeft: SNAP_WINDOW_TOP SNAP_WINDOW_LEFT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_LEFT break; case PositionLeft: SNAP_WINDOW_LEFT SNAP_WINDOW_C_LEFT break; case PositionTop: SNAP_WINDOW_TOP SNAP_WINDOW_C_TOP break; case PositionTopRight: SNAP_WINDOW_TOP SNAP_WINDOW_RIGHT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_RIGHT break; case PositionBottomLeft: SNAP_WINDOW_BOTTOM SNAP_WINDOW_LEFT SNAP_WINDOW_C_BOTTOM SNAP_WINDOW_C_LEFT break; default: assert( false ); break; } } } } // center snap //snap = options->centerSnapZone; //if (snap) // { // // Don't resize snap to center as it interferes too much // // There are two ways of implementing this if wanted: // // 1) Snap only to the same points that the move snap does, and // // 2) Snap to the horizontal and vertical center lines of the screen // } moveResizeGeom = QRect(QPoint(newcx, newcy), QPoint(newrx, newry)); } return moveResizeGeom; } /*! Marks the client as being moved around by the user. */ void Workspace::setClientIsMoving( Client *c ) { Q_ASSERT(!c || !movingClient); // Catch attempts to move a second // window while still moving the first one. movingClient = c; if (movingClient) ++block_focus; else --block_focus; } /*! Cascades all clients on the current desktop */ void Workspace::cascadeDesktop() { // TODO XINERAMA this probably is not right for xinerama Q_ASSERT( block_stacking_updates == 0 ); ClientList::ConstIterator it(stackingOrder().begin()); initPositioning->reinitCascading( currentDesktop()); QRect area = clientArea( PlacementArea, QPoint( 0, 0 ), currentDesktop()); for (; it != stackingOrder().end(); ++it) { if((!(*it)->isOnDesktop(currentDesktop())) || ((*it)->isMinimized()) || ((*it)->isOnAllDesktops()) || (!(*it)->isMovable()) ) continue; initPositioning->placeCascaded(*it, area); } } /*! Unclutters the current desktop by smart-placing all clients again. */ void Workspace::unclutterDesktop() { for ( int i = clients.size() - 1; i>=0; i-- ) { if( ( !clients.at( i )->isOnDesktop( currentDesktop() ) ) || (clients.at( i )->isMinimized()) || (clients.at( i )->isOnAllDesktops()) || (!clients.at( i )->isMovable()) ) continue; initPositioning->placeSmart(clients.at( i ), QRect()); } } void Workspace::updateTopMenuGeometry( Client* c ) { if( !managingTopMenus()) return; if( c != NULL ) { XEvent ev; ev.xclient.display = display(); ev.xclient.type = ClientMessage; ev.xclient.window = c->window(); static Atom msg_type_atom = XInternAtom( display(), "_KDE_TOPMENU_MINSIZE", False ); ev.xclient.message_type = msg_type_atom; ev.xclient.format = 32; ev.xclient.data.l[0] = xTime(); ev.xclient.data.l[1] = topmenu_space->width(); ev.xclient.data.l[2] = topmenu_space->height(); ev.xclient.data.l[3] = 0; ev.xclient.data.l[4] = 0; XSendEvent( display(), c->window(), False, NoEventMask, &ev ); KWindowSystem::setStrut( c->window(), 0, 0, topmenu_height, 0 ); // so that kicker etc. know c->checkWorkspacePosition(); return; } // c == NULL - update all, including topmenu_space QRect area; area = clientArea( MaximizeFullArea, QPoint( 0, 0 ), 1 ); // HACK desktop ? area.setHeight( topMenuHeight()); topmenu_space->setGeometry( area ); for( ClientList::ConstIterator it = topmenus.begin(); it != topmenus.end(); ++it ) updateTopMenuGeometry( *it ); } //******************************************** // Client //******************************************** void Client::keepInArea( QRect area, bool partial ) { if( partial ) { // increase the area so that can have only 100 pixels in the area area.setLeft( qMin( area.left() - width() + 100, area.left())); area.setTop( qMin( area.top() - height() + 100, area.top())); area.setRight( qMax( area.right() + width() - 100, area.right())); area.setBottom( qMax( area.bottom() + height() - 100, area.bottom())); } if( !partial ) { // resize to fit into area if( area.width() < width() || area.height() < height()) resizeWithChecks( qMin( area.width(), width()), qMin( area.height(), height())); } if ( geometry().right() > area.right() && width() < area.width() ) move( area.right() - width(), y() ); if ( geometry().bottom() > area.bottom() && height() < area.height() ) move( x(), area.bottom() - height() ); if( !area.contains( geometry().topLeft() )) { int tx = x(); int ty = y(); if ( tx < area.x() ) tx = area.x(); if ( ty < area.y() ) ty = area.y(); move( tx, ty ); } } /*! Returns \a area with the client's strut taken into account. Used from Workspace in updateClientArea. */ // TODO move to Workspace? QRect Client::adjustedClientArea( const QRect &desktopArea, const QRect& area ) const { QRect r = area; // topmenu area is reserved in updateClientArea() if( isTopMenu()) return r; NETExtendedStrut str = strut(); QRect stareaL = QRect( 0, str . left_start, str . left_width, str . left_end - str . left_start + 1 ); QRect stareaR = QRect ( desktopArea . right () - str . right_width + 1, str . right_start, str . right_width, str . right_end - str . right_start + 1 ); QRect stareaT = QRect ( str . top_start, 0, str . top_end - str . top_start + 1, str . top_width); QRect stareaB = QRect ( str . bottom_start, desktopArea . bottom () - str . bottom_width + 1, str . bottom_end - str . bottom_start + 1, str . bottom_width); QRect screenarea = workspace()->clientArea( ScreenArea, this ); // HACK: workarea handling is not xinerama aware, so if this strut // reserves place at a xinerama edge that's inside the virtual screen, // ignore the strut for workspace setting. if( area == kapp->desktop()->geometry()) { if( stareaL.left() < screenarea.left()) stareaL = QRect(); if( stareaR.right() > screenarea.right()) stareaR = QRect(); if( stareaT.top() < screenarea.top()) stareaT = QRect(); if( stareaB.bottom() < screenarea.bottom()) stareaB = QRect(); } // Handle struts at xinerama edges that are inside the virtual screen. // They're given in virtual screen coordinates, make them affect only // their xinerama screen. stareaL.setLeft( qMax( stareaL.left(), screenarea.left())); stareaR.setRight( qMin( stareaR.right(), screenarea.right())); stareaT.setTop( qMax( stareaT.top(), screenarea.top())); stareaB.setBottom( qMin( stareaB.bottom(), screenarea.bottom())); if (stareaL . intersects (area)) { // kDebug () << "Moving left of: " << r << " to " << stareaL.right() + 1; r . setLeft( stareaL . right() + 1 ); } if (stareaR . intersects (area)) { // kDebug () << "Moving right of: " << r << " to " << stareaR.left() - 1; r . setRight( stareaR . left() - 1 ); } if (stareaT . intersects (area)) { // kDebug () << "Moving top of: " << r << " to " << stareaT.bottom() + 1; r . setTop( stareaT . bottom() + 1 ); } if (stareaB . intersects (area)) { // kDebug () << "Moving bottom of: " << r << " to " << stareaB.top() - 1; r . setBottom( stareaB . top() - 1 ); } return r; } NETExtendedStrut Client::strut() const { NETExtendedStrut ext = info->extendedStrut(); NETStrut str = info->strut(); if( ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 && ( str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0 )) { // build extended from simple if( str.left != 0 ) { ext.left_width = str.left; ext.left_start = 0; ext.left_end = displayHeight(); } if( str.right != 0 ) { ext.right_width = str.right; ext.right_start = 0; ext.right_end = displayHeight(); } if( str.top != 0 ) { ext.top_width = str.top; ext.top_start = 0; ext.top_end = displayWidth(); } if( str.bottom != 0 ) { ext.bottom_width = str.bottom; ext.bottom_start = 0; ext.bottom_end = displayWidth(); } } return ext; } bool Client::hasStrut() const { NETExtendedStrut ext = strut(); if( ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 ) return false; return true; } // updates differences to workarea edges for all directions void Client::updateWorkareaDiffs() { QRect area = workspace()->clientArea( WorkArea, this ); QRect geom = geometry(); workarea_diff_x = computeWorkareaDiff( geom.left(), geom.right(), area.left(), area.right()); workarea_diff_y = computeWorkareaDiff( geom.top(), geom.bottom(), area.top(), area.bottom()); } // If the client was inside workarea in the x direction, and if it was close to the left/right // edge, return the distance from the left/right edge (negative for left, positive for right) // INT_MIN means 'not inside workarea', INT_MAX means 'not near edge'. // In order to recognize 'at the left workarea edge' from 'at the right workarea edge' // (i.e. negative vs positive zero), the distances are one larger in absolute value than they // really are (i.e. 5 pixels from the left edge is -6, not -5). A bit hacky, but I'm lazy // to rewrite it just to make it nicer. If this will ever get touched again, perhaps then. // the y direction is done the same, just the values will be rotated: top->left, bottom->right int Client::computeWorkareaDiff( int left, int right, int a_left, int a_right ) { int left_diff = left - a_left; int right_diff = a_right - right; if( left_diff < 0 || right_diff < 0 ) return INT_MIN; else // fully inside workarea in this direction direction { // max distance from edge where it's still considered to be close and is kept at that distance int max_diff = ( a_right - a_left ) / 10; if( left_diff < right_diff ) return left_diff < max_diff ? -left_diff - 1 : INT_MAX; else if( left_diff > right_diff ) return right_diff < max_diff ? right_diff + 1 : INT_MAX; return INT_MAX; // not close to workarea edge } } void Client::checkWorkspacePosition() { if( isDesktop()) { if (geometry() == workspace()->clientArea( ScreenArea, this )) { return; } QRect area = workspace()->clientArea( FullArea, this ); if( geometry() != area ) setGeometry( area ); return; } if( isFullScreen()) { QRect area = workspace()->clientArea( FullScreenArea, this ); if( geometry() != area ) setGeometry( area ); return; } if( isDock()) return; if( isTopMenu()) { if( workspace()->managingTopMenus()) { QRect area; ClientList mainclients = mainClients(); if( mainclients.count() == 1 ) area = workspace()->clientArea( MaximizeFullArea, mainclients.first()); else area = workspace()->clientArea( MaximizeFullArea, QPoint( 0, 0 ), desktop()); area.setHeight( workspace()->topMenuHeight()); // kDebug() << "TOPMENU size adjust: " << area << ":" << this; setGeometry( area ); } return; } if( maximizeMode() != MaximizeRestore ) // TODO update geom_restore? changeMaximize( false, false, true ); // adjust size if( !isShade()) // TODO { int old_diff_x = workarea_diff_x; int old_diff_y = workarea_diff_y; updateWorkareaDiffs(); // this can be true only if this window was mapped before KWin // was started - in such case, don't adjust position to workarea, // because the window already had its position, and if a window // with a strut altering the workarea would be managed in initialization // after this one, this window would be moved if( workspace()->initializing()) return; QRect area = workspace()->clientArea( WorkArea, this ); QRect new_geom = geometry(); QRect tmp_rect_x( new_geom.left(), 0, new_geom.width(), 0 ); QRect tmp_area_x( area.left(), 0, area.width(), 0 ); checkDirection( workarea_diff_x, old_diff_x, tmp_rect_x, tmp_area_x ); // the x<->y swapping QRect tmp_rect_y( new_geom.top(), 0, new_geom.height(), 0 ); QRect tmp_area_y( area.top(), 0, area.height(), 0 ); checkDirection( workarea_diff_y, old_diff_y, tmp_rect_y, tmp_area_y ); new_geom = QRect( tmp_rect_x.left(), tmp_rect_y.left(), tmp_rect_x.width(), tmp_rect_y.width()); QRect final_geom( new_geom.topLeft(), adjustedSize( new_geom.size())); if( final_geom != new_geom ) // size increments, or size restrictions { // adjusted size differing matters only for right and bottom edge if( old_diff_x != INT_MAX && old_diff_x > 0 ) final_geom.moveRight( area.right() - ( old_diff_x - 1 )); if( old_diff_y != INT_MAX && old_diff_y > 0 ) final_geom.moveBottom( area.bottom() - ( old_diff_y - 1 )); } if( final_geom != geometry() ) setGeometry( final_geom ); // updateWorkareaDiffs(); done already by setGeometry() } } // Try to be smart about keeping the clients visible. // If the client was fully inside the workspace before, try to keep // it still inside the workarea, possibly moving it or making it smaller if possible, // and try to keep the distance from the nearest workarea edge. // On the other hand, it it was partially moved outside of the workspace in some direction, // don't do anything with that direction if it's still at least partially visible. If it's // not visible anymore at all, make sure it's visible at least partially // again (not fully, as that could(?) be potentionally annoying) by // moving it slightly inside the workarea (those '+ 5'). // Again, this is done for the x direction, y direction will be done by x<->y swapping void Client::checkDirection( int new_diff, int old_diff, QRect& rect, const QRect& area ) { if( old_diff != INT_MIN ) // was inside workarea { if( old_diff == INT_MAX ) // was in workarea, but far from edge { if( new_diff == INT_MIN ) // is not anymore fully in workarea { rect.setLeft( area.left()); rect.setRight( area.right()); } return; } if( isMovable()) { if( old_diff < 0 ) // was in left third, keep distance from left edge rect.moveLeft( area.left() + ( -old_diff - 1 )); else // old_diff > 0 // was in right third, keep distance from right edge rect.moveRight( area.right() - ( old_diff - 1 )); } else if( isResizable()) { if( old_diff < 0 ) rect.setLeft( area.left() + ( -old_diff - 1 ) ); else // old_diff > 0 rect.setRight( area.right() - ( old_diff - 1 )); } if( rect.width() > area.width() && isResizable()) rect.setWidth( area.width()); if( isMovable()) { if( rect.left() < area.left()) rect.moveLeft( area.left()); else if( rect.right() > area.right()) rect.moveRight( area.right()); } } if( rect.right() < area.left() + 5 || rect.left() > area.right() - 5 ) { // not visible (almost) at all - try to make it at least partially visible if( isMovable()) { if( rect.left() < area.left() + 5 ) rect.moveRight( area.left() + 5 ); if( rect.right() > area.right() - 5 ) rect.moveLeft( area.right() - 5 ); } } } /*! Adjust the frame size \a frame according to he window's size hints. */ QSize Client::adjustedSize( const QSize& frame, Sizemode mode ) const { // first, get the window size for the given frame size s QSize wsize( frame.width() - ( border_left + border_right ), frame.height() - ( border_top + border_bottom )); if( wsize.isEmpty()) wsize = QSize( 1, 1 ); return sizeForClientSize( wsize, mode, false ); } // this helper returns proper size even if the window is shaded // see also the comment in Client::setGeometry() QSize Client::adjustedSize() const { return sizeForClientSize( clientSize()); } /*! Calculate the appropriate frame size for the given client size \a wsize. \a wsize is adapted according to the window's size hints (minimum, maximum and incremental size changes). */ QSize Client::sizeForClientSize( const QSize& wsize, Sizemode mode, bool noframe ) const { int w = wsize.width(); int h = wsize.height(); if( w < 1 || h < 1 ) { kWarning() << "sizeForClientSize() with empty size!" ; kWarning() << kBacktrace() ; } if (w<1) w = 1; if (h<1) h = 1; // basesize, minsize, maxsize, paspect and resizeinc have all values defined, // even if they're not set in flags - see getWmNormalHints() QSize min_size = minSize(); QSize max_size = maxSize(); if( decoration != NULL ) { QSize decominsize = decoration->minimumSize(); QSize border_size( border_left + border_right, border_top + border_bottom ); if( border_size.width() > decominsize.width()) // just in case decominsize.setWidth( border_size.width()); if( border_size.height() > decominsize.height()) decominsize.setHeight( border_size.height()); if( decominsize.width() > min_size.width()) min_size.setWidth( decominsize.width()); if( decominsize.height() > min_size.height()) min_size.setHeight( decominsize.height()); } w = qMin( max_size.width(), w ); h = qMin( max_size.height(), h ); w = qMax( min_size.width(), w ); h = qMax( min_size.height(), h ); int w1 = w; int h1 = h; int width_inc = xSizeHint.width_inc; int height_inc = xSizeHint.height_inc; int basew_inc = xSizeHint.min_width; // see getWmNormalHints() int baseh_inc = xSizeHint.min_height; w = int(( w - basew_inc ) / width_inc ) * width_inc + basew_inc; h = int(( h - baseh_inc ) / height_inc ) * height_inc + baseh_inc; // code for aspect ratios based on code from FVWM /* * The math looks like this: * * minAspectX dwidth maxAspectX * ---------- <= ------- <= ---------- * minAspectY dheight maxAspectY * * If that is multiplied out, then the width and height are * invalid in the following situations: * * minAspectX * dheight > minAspectY * dwidth * maxAspectX * dheight < maxAspectY * dwidth * */ if( xSizeHint.flags & PAspect ) { double min_aspect_w = xSizeHint.min_aspect.x; // use doubles, because the values can be MAX_INT double min_aspect_h = xSizeHint.min_aspect.y; // and multiplying would go wrong otherwise double max_aspect_w = xSizeHint.max_aspect.x; double max_aspect_h = xSizeHint.max_aspect.y; // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments, // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time, // and I have no idea how it works, let's hope nobody relies on that. w -= xSizeHint.base_width; h -= xSizeHint.base_height; int max_width = max_size.width() - xSizeHint.base_width; int min_width = min_size.width() - xSizeHint.base_width; int max_height = max_size.height() - xSizeHint.base_height; int min_height = min_size.height() - xSizeHint.base_height; #define ASPECT_CHECK_GROW_W \ if( min_aspect_w * h > min_aspect_h * w ) \ { \ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ if( w + delta <= max_width ) \ w += delta; \ } #define ASPECT_CHECK_SHRINK_H_GROW_W \ if( min_aspect_w * h > min_aspect_h * w ) \ { \ int delta = int( h - w * min_aspect_h / min_aspect_w ) / height_inc * height_inc; \ if( h - delta >= min_height ) \ h -= delta; \ else \ { \ int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ if( w + delta <= max_width ) \ w += delta; \ } \ } #define ASPECT_CHECK_GROW_H \ if( max_aspect_w * h < max_aspect_h * w ) \ { \ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ if( h + delta <= max_height ) \ h += delta; \ } #define ASPECT_CHECK_SHRINK_W_GROW_H \ if( max_aspect_w * h < max_aspect_h * w ) \ { \ int delta = int( w - max_aspect_w * h / max_aspect_h ) / width_inc * width_inc; \ if( w - delta >= min_width ) \ w -= delta; \ else \ { \ int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ if( h + delta <= max_height ) \ h += delta; \ } \ } switch( mode ) { case SizemodeAny: #if 0 // make SizemodeAny equal to SizemodeFixedW - prefer keeping fixed width, // so that changing aspect ratio to a different value and back keeps the same size (#87298) { ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_H ASPECT_CHECK_GROW_W break; } #endif case SizemodeFixedW: { // the checks are order so that attempts to modify height are first ASPECT_CHECK_GROW_H ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_W break; } case SizemodeFixedH: { ASPECT_CHECK_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_GROW_H break; } case SizemodeMax: { // first checks that try to shrink ASPECT_CHECK_SHRINK_H_GROW_W ASPECT_CHECK_SHRINK_W_GROW_H ASPECT_CHECK_GROW_W ASPECT_CHECK_GROW_H break; } } #undef ASPECT_CHECK_SHRINK_H_GROW_W #undef ASPECT_CHECK_SHRINK_W_GROW_H #undef ASPECT_CHECK_GROW_W #undef ASPECT_CHECK_GROW_H w += xSizeHint.base_width; h += xSizeHint.base_height; } if( !rules()->checkStrictGeometry( false )) { // disobey increments and aspect when maximized if( maximizeMode() & MaximizeHorizontal ) w = w1; if( maximizeMode() & MaximizeVertical ) h = h1; } if( !noframe ) { w += border_left + border_right; h += border_top + border_bottom; } return rules()->checkSize( QSize( w, h )); } /*! Gets the client's normal WM hints and reconfigures itself respectively. */ void Client::getWmNormalHints() { long msize; if (XGetWMNormalHints(display(), window(), &xSizeHint, &msize) == 0 ) xSizeHint.flags = 0; // set defined values for the fields, even if they're not in flags if( ! ( xSizeHint.flags & PMinSize )) xSizeHint.min_width = xSizeHint.min_height = 0; if( xSizeHint.flags & PBaseSize ) { // PBaseSize is a fallback for PMinSize according to ICCCM 4.1.2.3 // The other way around PMinSize is not a complete fallback for PBaseSize, // so that's not handled here. if( ! ( xSizeHint.flags & PMinSize )) { xSizeHint.min_width = xSizeHint.base_width; xSizeHint.min_height = xSizeHint.base_height; } } else xSizeHint.base_width = xSizeHint.base_height = 0; if( ! ( xSizeHint.flags & PMaxSize )) xSizeHint.max_width = xSizeHint.max_height = INT_MAX; else { xSizeHint.max_width = qMax( xSizeHint.max_width, 1 ); xSizeHint.max_height = qMax( xSizeHint.max_height, 1 ); } if( xSizeHint.flags & PResizeInc ) { xSizeHint.width_inc = qMax( xSizeHint.width_inc, 1 ); xSizeHint.height_inc = qMax( xSizeHint.height_inc, 1 ); } else { xSizeHint.width_inc = 1; xSizeHint.height_inc = 1; } if( xSizeHint.flags & PAspect ) { // no dividing by zero xSizeHint.min_aspect.y = qMax( xSizeHint.min_aspect.y, 1 ); xSizeHint.max_aspect.y = qMax( xSizeHint.max_aspect.y, 1 ); } else { xSizeHint.min_aspect.x = 1; xSizeHint.min_aspect.y = INT_MAX; xSizeHint.max_aspect.x = INT_MAX; xSizeHint.max_aspect.y = 1; } if( ! ( xSizeHint.flags & PWinGravity )) xSizeHint.win_gravity = NorthWestGravity; if( isManaged()) { // update to match restrictions QSize new_size = adjustedSize(); if( new_size != size() && !isFullScreen()) { QRect orig_geometry = geometry(); resizeWithChecks( new_size ); if( ( !isSpecialWindow() || isToolbar()) && !isFullScreen()) { // try to keep the window in its xinerama screen if possible, // if that fails at least keep it visible somewhere QRect area = workspace()->clientArea( MovementArea, this ); if( area.contains( orig_geometry )) keepInArea( area ); area = workspace()->clientArea( WorkArea, this ); if( area.contains( orig_geometry )) keepInArea( area ); } } } updateAllowedActions(); // affects isResizeable() } QSize Client::minSize() const { return rules()->checkMinSize( QSize( xSizeHint.min_width, xSizeHint.min_height )); } QSize Client::maxSize() const { return rules()->checkMaxSize( QSize( xSizeHint.max_width, xSizeHint.max_height )); } /*! Auxiliary function to inform the client about the current window configuration. */ void Client::sendSyntheticConfigureNotify() { XConfigureEvent c; c.type = ConfigureNotify; c.send_event = True; c.event = window(); c.window = window(); c.x = x() + clientPos().x(); c.y = y() + clientPos().y(); c.width = clientSize().width(); c.height = clientSize().height(); c.border_width = 0; c.above = None; c.override_redirect = 0; XSendEvent( display(), c.event, true, StructureNotifyMask, (XEvent*)&c ); } const QPoint Client::calculateGravitation( bool invert, int gravity ) const { int dx, dy; dx = dy = 0; if( gravity == 0 ) // default (nonsense) value for the argument gravity = xSizeHint.win_gravity; // dx, dy specify how the client window moves to make space for the frame switch (gravity) { case NorthWestGravity: // move down right default: dx = border_left; dy = border_top; break; case NorthGravity: // move right dx = 0; dy = border_top; break; case NorthEastGravity: // move down left dx = -border_right; dy = border_top; break; case WestGravity: // move right dx = border_left; dy = 0; break; case CenterGravity: break; // will be handled specially case StaticGravity: // don't move dx = 0; dy = 0; break; case EastGravity: // move left dx = -border_right; dy = 0; break; case SouthWestGravity: // move up right dx = border_left ; dy = -border_bottom; break; case SouthGravity: // move up dx = 0; dy = -border_bottom; break; case SouthEastGravity: // move up left dx = -border_right; dy = -border_bottom; break; } if( gravity != CenterGravity ) { // translate from client movement to frame movement dx -= border_left; dy -= border_top; } else { // center of the frame will be at the same position client center without frame would be dx = - ( border_left + border_right ) / 2; dy = - ( border_top + border_bottom ) / 2; } if( !invert ) return QPoint( x() + dx, y() + dy ); else return QPoint( x() - dx, y() - dy ); } void Client::configureRequest( int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool ) { if( gravity == 0 ) // default (nonsense) value for the argument gravity = xSizeHint.win_gravity; if( value_mask & ( CWX | CWY )) { QPoint new_pos = calculateGravitation( true, gravity ); // undo gravitation if ( value_mask & CWX ) new_pos.setX( rx ); if ( value_mask & CWY ) new_pos.setY( ry ); // clever(?) workaround for applications like xv that want to set // the location to the current location but miscalculate the // frame size due to kwin being a double-reparenting window // manager if ( new_pos.x() == x() + clientPos().x() && new_pos.y() == y() + clientPos().y() && gravity == NorthWestGravity && !from_tool ) { new_pos.setX( x()); new_pos.setY( y()); } int nw = clientSize().width(); int nh = clientSize().height(); if ( value_mask & CWWidth ) nw = rw; if ( value_mask & CWHeight ) nh = rh; QSize ns = sizeForClientSize( QSize( nw, nh ) ); // enforces size if needed new_pos = rules()->checkPosition( new_pos ); // TODO what to do with maximized windows? if ( maximizeMode() != MaximizeFull || ns != size()) { QRect orig_geometry = geometry(); GeometryUpdatesBlocker blocker( this ); move( new_pos ); plainResize( ns ); setGeometry( QRect( calculateGravitation( false, gravity ), size())); updateFullScreenHack( QRect( new_pos, QSize( nw, nh ))); QRect area = workspace()->clientArea( WorkArea, this ); if( !from_tool && ( !isSpecialWindow() || isToolbar()) && !isFullScreen() && area.contains( orig_geometry )) keepInArea( area ); // this is part of the kicker-xinerama-hack... it should be // safe to remove when kicker gets proper ExtendedStrut support; // see Workspace::updateClientArea() and // Client::adjustedClientArea() if (hasStrut ()) workspace() -> updateClientArea (); } } if ( value_mask & (CWWidth | CWHeight ) && ! ( value_mask & ( CWX | CWY )) ) // pure resize { int nw = clientSize().width(); int nh = clientSize().height(); if ( value_mask & CWWidth ) nw = rw; if ( value_mask & CWHeight ) nh = rh; QSize ns = sizeForClientSize( QSize( nw, nh ) ); if( ns != size()) // don't restore if some app sets its own size again { QRect orig_geometry = geometry(); GeometryUpdatesBlocker blocker( this ); int save_gravity = xSizeHint.win_gravity; xSizeHint.win_gravity = gravity; resizeWithChecks( ns ); xSizeHint.win_gravity = save_gravity; updateFullScreenHack( QRect( calculateGravitation( true, xSizeHint.win_gravity ), QSize( nw, nh ))); if( !from_tool && ( !isSpecialWindow() || isToolbar()) && !isFullScreen()) { // try to keep the window in its xinerama screen if possible, // if that fails at least keep it visible somewhere QRect area = workspace()->clientArea( MovementArea, this ); if( area.contains( orig_geometry )) keepInArea( area ); area = workspace()->clientArea( WorkArea, this ); if( area.contains( orig_geometry )) keepInArea( area ); } } } // No need to send synthetic configure notify event here, either it's sent together // with geometry change, or there's no need to send it. // Handling of the real ConfigureRequest event forces sending it, as there it's necessary. } void Client::resizeWithChecks( int w, int h, ForceGeometry_t force ) { if( shade_geometry_change ) assert( false ); else if( isShade()) { if( h == border_top + border_bottom ) { kWarning() << "Shaded geometry passed for size:" ; kWarning() << kBacktrace() ; } } int newx = x(); int newy = y(); QRect area = workspace()->clientArea( WorkArea, this ); // don't allow growing larger than workarea if( w > area.width()) w = area.width(); if( h > area.height()) h = area.height(); QSize tmp = adjustedSize( QSize( w, h )); // checks size constraints, including min/max size w = tmp.width(); h = tmp.height(); switch( xSizeHint.win_gravity ) { case NorthWestGravity: // top left corner doesn't move default: break; case NorthGravity: // middle of top border doesn't move newx = ( newx + width() / 2 ) - ( w / 2 ); break; case NorthEastGravity: // top right corner doesn't move newx = newx + width() - w; break; case WestGravity: // middle of left border doesn't move newy = ( newy + height() / 2 ) - ( h / 2 ); break; case CenterGravity: // middle point doesn't move newx = ( newx + width() / 2 ) - ( w / 2 ); newy = ( newy + height() / 2 ) - ( h / 2 ); break; case StaticGravity: // top left corner of _client_ window doesn't move // since decoration doesn't change, equal to NorthWestGravity break; case EastGravity: // // middle of right border doesn't move newx = newx + width() - w; newy = ( newy + height() / 2 ) - ( h / 2 ); break; case SouthWestGravity: // bottom left corner doesn't move newy = newy + height() - h; break; case SouthGravity: // middle of bottom border doesn't move newx = ( newx + width() / 2 ) - ( w / 2 ); newy = newy + height() - h; break; case SouthEastGravity: // bottom right corner doesn't move newx = newx + width() - w; newy = newy + height() - h; break; } // if it would be moved outside of workarea, keep it inside, // see also Client::computeWorkareaDiff() if( workarea_diff_x != INT_MIN && w <= area.width()) // was inside and can still fit { if( newx < area.left()) newx = area.left(); if( newx + w > area.right() + 1 ) newx = area.right() + 1 - w; assert( newx >= area.left() && newx + w <= area.right() + 1 ); // width was checked above } if( workarea_diff_y != INT_MIN && h <= area.height()) // was inside and can still fit { if( newy < area.top()) newy = area.top(); if( newy + h > area.bottom() + 1 ) newy = area.bottom() + 1 - h; assert( newy >= area.top() && newy + h <= area.bottom() + 1 ); // height was checked above } setGeometry( newx, newy, w, h, force ); } // _NET_MOVERESIZE_WINDOW void Client::NETMoveResizeWindow( int flags, int x, int y, int width, int height ) { int gravity = flags & 0xff; int value_mask = 0; if( flags & ( 1 << 8 )) value_mask |= CWX; if( flags & ( 1 << 9 )) value_mask |= CWY; if( flags & ( 1 << 10 )) value_mask |= CWWidth; if( flags & ( 1 << 11 )) value_mask |= CWHeight; configureRequest( value_mask, x, y, width, height, gravity, true ); } /*! Returns whether the window is moveable or has a fixed position. */ bool Client::isMovable() const { if( !motif_may_move || isFullScreen()) return false; if( isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) return false; if( maximizeMode() == MaximizeFull && !options->moveResizeMaximizedWindows() ) return false; if( rules()->checkPosition( invalidPoint ) != invalidPoint ) // forced position return false; return true; } /*! Returns whether the window is moveable across Xinerama screens */ bool Client::isMovableAcrossScreens() const { if( !motif_may_move ) return false; if( isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) return false; if( rules()->checkPosition( invalidPoint ) != invalidPoint ) // forced position return false; return true; } /*! Returns whether the window is resizable or has a fixed size. */ bool Client::isResizable() const { if( !motif_may_resize || isFullScreen()) return false; if( isSpecialWindow() || isSplash() || isToolbar()) return false; if( maximizeMode() == MaximizeFull && !options->moveResizeMaximizedWindows() ) return false; if( rules()->checkSize( QSize()).isValid()) // forced size return false; QSize min = minSize(); QSize max = maxSize(); return min.width() < max.width() || min.height() < max.height(); } /* Returns whether the window is maximizable or not */ bool Client::isMaximizable() const { { // isMovable() and isResizable() may be false for maximized windows // with moving/resizing maximized windows disabled TemporaryAssign< MaximizeMode > tmp( max_mode, MaximizeRestore ); if( !isMovable() || !isResizable() || isToolbar()) // SELI isToolbar() ? return false; } if ( maximizeMode() != MaximizeRestore ) return true; QSize max = maxSize(); #if 0 if( max.width() < 32767 || max.height() < 32767 ) // sizes are 16bit with X return false; #else // apparently there are enough apps which specify some arbitrary value // for their maximum size just for the fun of it QSize areasize = workspace()->clientArea( MaximizeArea, this ).size(); if( max.width() < areasize.width() || max.height() < areasize.height()) return false; #endif return true; } /*! Reimplemented to inform the client about the new window position. */ void Client::setGeometry( int x, int y, int w, int h, ForceGeometry_t force ) { // this code is also duplicated in Client::plainResize() // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry, // simply because there are too many places dealing with geometry. Those places // ignore shaded state and use normal geometry, which they usually should get // from adjustedSize(). Such geometry comes here, and if the window is shaded, // the geometry is used only for client_size, since that one is not used when // shading. Then the frame geometry is adjusted for the shaded geometry. // This gets more complicated in the case the code does only something like // setGeometry( geometry()) - geometry() will return the shaded frame geometry. // Such code is wrong and should be changed to handle the case when the window is shaded, // for example using Client::clientSize(). if( shade_geometry_change ) ; // nothing else if( isShade()) { if( h == border_top + border_bottom ) { kDebug() << "Shaded geometry passed for size:"; kDebug() << kBacktrace(); } else { client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); h = border_top + border_bottom; } } else { client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); } QRect g( x, y, w, h ); if( block_geometry_updates == 0 && g != rules()->checkGeometry( g )) { kDebug() << "forced geometry fail:" << g << ":" << rules()->checkGeometry( g ); kDebug() << kBacktrace(); } if( force == NormalGeometrySet && geom == g && pending_geometry_update == PendingGeometryNone ) return; geom = g; updateWorkareaDiffs(); if( block_geometry_updates != 0 ) { if( pending_geometry_update == PendingGeometryForced ) {} // maximum, nothing needed else if( force == ForceGeometrySet ) pending_geometry_update = PendingGeometryForced; else pending_geometry_update = PendingGeometryNormal; return; } bool resized = ( geom_before_block.size() != geom.size() || pending_geometry_update == PendingGeometryForced ); if( resized ) { resizeDecoration( QSize( w, h )); XMoveResizeWindow( display(), frameId(), x, y, w, h ); if( !isShade()) { QSize cs = clientSize(); XMoveResizeWindow( display(), wrapperId(), clientPos().x(), clientPos().y(), cs.width(), cs.height()); XMoveResizeWindow( display(), window(), 0, 0, cs.width(), cs.height()); } updateShape(); } else XMoveWindow( display(), frameId(), x, y ); // SELI TODO won't this be too expensive? sendSyntheticConfigureNotify(); updateWindowRules(); checkMaximizeGeometry(); workspace()->checkActiveScreen( this ); + workspace()->updateStackingOrder(); if( resized ) { discardWindowPixmap(); if( scene != NULL ) scene->windowGeometryShapeChanged( this ); if( effects != NULL ) static_cast(effects)->windowGeometryShapeChanged( effectWindow(), geom_before_block ); } addWorkspaceRepaint( geom_before_block ); addWorkspaceRepaint( geom ); geom_before_block = geom; } void Client::plainResize( int w, int h, ForceGeometry_t force ) { // this code is also duplicated in Client::setGeometry(), and it's also commented there if( shade_geometry_change ) ; // nothing else if( isShade()) { if( h == border_top + border_bottom ) { kDebug() << "Shaded geometry passed for size:"; kDebug() << kBacktrace(); } else { client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); h = border_top + border_bottom; } } else { client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); } QSize s( w, h ); if( block_geometry_updates == 0 && s != rules()->checkSize( s )) { kDebug() << "forced size fail:" << s << ":" << rules()->checkSize( s ); kDebug() << kBacktrace(); } // resuming geometry updates is handled only in setGeometry() assert( pending_geometry_update == PendingGeometryNone || block_geometry_updates > 0 ); if( force == NormalGeometrySet && geom.size() == s ) return; geom.setSize( s ); updateWorkareaDiffs(); if( block_geometry_updates != 0 ) { if( pending_geometry_update == PendingGeometryForced ) {} // maximum, nothing needed else if( force == ForceGeometrySet ) pending_geometry_update = PendingGeometryForced; else pending_geometry_update = PendingGeometryNormal; return; } resizeDecoration( s ); XResizeWindow( display(), frameId(), w, h ); // resizeDecoration( s ); if( !isShade()) { QSize cs = clientSize(); XMoveResizeWindow( display(), wrapperId(), clientPos().x(), clientPos().y(), cs.width(), cs.height()); XMoveResizeWindow( display(), window(), 0, 0, cs.width(), cs.height()); } updateShape(); sendSyntheticConfigureNotify(); updateWindowRules(); checkMaximizeGeometry(); workspace()->checkActiveScreen( this ); + workspace()->updateStackingOrder(); discardWindowPixmap(); if( scene != NULL ) scene->windowGeometryShapeChanged( this ); if( effects != NULL ) static_cast(effects)->windowGeometryShapeChanged( effectWindow(), geom_before_block ); addWorkspaceRepaint( geom_before_block ); addWorkspaceRepaint( geom ); geom_before_block = geom; } /*! Reimplemented to inform the client about the new window position. */ void Client::move( int x, int y, ForceGeometry_t force ) { // resuming geometry updates is handled only in setGeometry() assert( pending_geometry_update == PendingGeometryNone || block_geometry_updates > 0 ); QPoint p( x, y ); if( block_geometry_updates == 0 && p != rules()->checkPosition( p )) { kDebug() << "forced position fail:" << p << ":" << rules()->checkPosition( p ); kDebug() << kBacktrace(); } if( force == NormalGeometrySet && geom.topLeft() == p ) return; geom.moveTopLeft( p ); updateWorkareaDiffs(); if( block_geometry_updates != 0 ) { if( pending_geometry_update == PendingGeometryForced ) {} // maximum, nothing needed else if( force == ForceGeometrySet ) pending_geometry_update = PendingGeometryForced; else pending_geometry_update = PendingGeometryNormal; return; } XMoveWindow( display(), frameId(), x, y ); sendSyntheticConfigureNotify(); updateWindowRules(); checkMaximizeGeometry(); workspace()->checkActiveScreen( this ); + workspace()->updateStackingOrder(); // client itself is not damaged addWorkspaceRepaint( geom_before_block ); addWorkspaceRepaint( geom ); // trigger repaint of window's new location geom_before_block = geom; } void Client::blockGeometryUpdates( bool block ) { if( block ) { if( block_geometry_updates == 0 ) pending_geometry_update = PendingGeometryNone; ++block_geometry_updates; } else { if( --block_geometry_updates == 0 ) { if( pending_geometry_update != PendingGeometryNone ) { if( isShade()) setGeometry( QRect( pos(), adjustedSize()), NormalGeometrySet ); else setGeometry( geometry(), NormalGeometrySet ); pending_geometry_update = PendingGeometryNone; } } } } void Client::maximize( MaximizeMode m ) { setMaximize( m & MaximizeVertical, m & MaximizeHorizontal ); } /*! Sets the maximization according to \a vertically and \a horizontally */ void Client::setMaximize( bool vertically, bool horizontally ) { // changeMaximize() flips the state, so change from set->flip changeMaximize( max_mode & MaximizeVertical ? !vertically : vertically, max_mode & MaximizeHorizontal ? !horizontally : horizontally, false ); } void Client::changeMaximize( bool vertical, bool horizontal, bool adjust ) { if( !isMaximizable()) return; MaximizeMode old_mode = max_mode; // 'adjust == true' means to update the size only, e.g. after changing workspace size if( !adjust ) { if( vertical ) max_mode = MaximizeMode( max_mode ^ MaximizeVertical ); if( horizontal ) max_mode = MaximizeMode( max_mode ^ MaximizeHorizontal ); } max_mode = rules()->checkMaximize( max_mode ); if( !adjust && max_mode == old_mode ) return; GeometryUpdatesBlocker blocker( this ); // maximing one way and unmaximizing the other way shouldn't happen, // so restore first and then maximize the other way if( ( old_mode == MaximizeVertical && max_mode == MaximizeHorizontal ) || ( old_mode == MaximizeHorizontal && max_mode == MaximizeVertical )) { changeMaximize( false, false, false ); // restore } QRect clientArea = workspace()->clientArea( MaximizeArea, this ); // save sizes for restoring, if maximalizing if( !adjust && !( y() == clientArea.top() && height() == clientArea.height())) { geom_restore.setTop( y()); geom_restore.setHeight( height()); } if( !adjust && !( x() == clientArea.left() && width() == clientArea.width())) { geom_restore.setLeft( x()); geom_restore.setWidth( width()); } if( !adjust ) { if(( vertical && !(old_mode & MaximizeVertical )) || ( horizontal && !( old_mode & MaximizeHorizontal ))) Notify::raise( Notify::Maximize ); else Notify::raise( Notify::UnMaximize ); } ForceGeometry_t geom_mode = NormalGeometrySet; if( decoration != NULL ) // decorations may turn off some borders when maximized { if( checkBorderSizes( false )) // only query, don't resize geom_mode = ForceGeometrySet; } // restore partial maximizations if ( old_mode==MaximizeFull && max_mode==MaximizeRestore ) { if ( maximizeModeRestore()==MaximizeVertical ) { max_mode = MaximizeVertical; maxmode_restore = MaximizeRestore; } if ( maximizeModeRestore()==MaximizeHorizontal ) { max_mode = MaximizeHorizontal; maxmode_restore = MaximizeRestore; } } switch (max_mode) { case MaximizeVertical: { if( old_mode & MaximizeHorizontal ) // actually restoring from MaximizeFull { if( geom_restore.width() == 0 ) { // needs placement plainResize( adjustedSize( QSize( width() * 2 / 3, clientArea.height()), SizemodeFixedH ), geom_mode ); workspace()->placeSmart( this, clientArea ); } else { setGeometry( QRect(QPoint( geom_restore.x(), clientArea.top()), adjustedSize(QSize( geom_restore.width(), clientArea.height()), SizemodeFixedH )), geom_mode ); } } else { setGeometry( QRect(QPoint(x(), clientArea.top()), adjustedSize(QSize(width(), clientArea.height()), SizemodeFixedH )), geom_mode ); } info->setState( NET::MaxVert, NET::Max ); break; } case MaximizeHorizontal: { if( old_mode & MaximizeVertical ) // actually restoring from MaximizeFull { if( geom_restore.height() == 0 ) { // needs placement plainResize( adjustedSize( QSize( clientArea.width(), height() * 2 / 3 ), SizemodeFixedW ), geom_mode ); workspace()->placeSmart( this, clientArea ); } else { setGeometry( QRect( QPoint(clientArea.left(), geom_restore.y()), adjustedSize(QSize(clientArea.width(), geom_restore.height()), SizemodeFixedW )), geom_mode ); } } else { setGeometry( QRect( QPoint(clientArea.left(), y()), adjustedSize(QSize(clientArea.width(), height()), SizemodeFixedW )), geom_mode ); } info->setState( NET::MaxHoriz, NET::Max ); break; } case MaximizeRestore: { QRect restore = geometry(); // when only partially maximized, geom_restore may not have the other dimension remembered if( old_mode & MaximizeVertical ) { restore.setTop( geom_restore.top()); restore.setBottom( geom_restore.bottom()); } if( old_mode & MaximizeHorizontal ) { restore.setLeft( geom_restore.left()); restore.setRight( geom_restore.right()); } if( !restore.isValid()) { QSize s = QSize( clientArea.width()*2/3, clientArea.height()*2/3 ); if( geom_restore.width() > 0 ) s.setWidth( geom_restore.width()); if( geom_restore.height() > 0 ) s.setHeight( geom_restore.height()); plainResize( adjustedSize( s )); workspace()->placeSmart( this, clientArea ); restore = geometry(); if( geom_restore.width() > 0 ) restore.moveLeft( geom_restore.x()); if( geom_restore.height() > 0 ) restore.moveTop( geom_restore.y()); } setGeometry( restore, geom_mode ); info->setState( 0, NET::Max ); break; } case MaximizeFull: { if( !adjust ) { if( old_mode & MaximizeVertical ) maxmode_restore = MaximizeVertical; if( old_mode & MaximizeHorizontal ) maxmode_restore = MaximizeHorizontal; } QSize adjSize = adjustedSize(clientArea.size(), SizemodeMax ); QRect r = QRect(clientArea.topLeft(), adjSize); setGeometry( r, geom_mode ); info->setState( NET::Max, NET::Max ); break; } default: break; } updateAllowedActions(); if( decoration != NULL ) decoration->maximizeChange(); updateWindowRules(); } void Client::resetMaximize() { if( max_mode == MaximizeRestore ) return; max_mode = MaximizeRestore; Notify::raise( Notify::UnMaximize ); info->setState( 0, NET::Max ); updateAllowedActions(); if( decoration != NULL ) decoration->borders( border_left, border_right, border_top, border_bottom ); if( isShade()) setGeometry( QRect( pos(), sizeForClientSize( clientSize())), ForceGeometrySet ); else setGeometry( geometry(), ForceGeometrySet ); if( decoration != NULL ) decoration->maximizeChange(); } void Client::checkMaximizeGeometry() { // when adding new bail-out conditions here, checkMaximizeGeometry() needs to be called // when after the condition is no longer true if( isShade()) return; if( isMove() || isResize()) // this is because of the option to disallow moving/resizing of max-ed windows return; // Just in case. static int recursion_protection = 0; if( recursion_protection > 3 ) { kWarning( 1212 ) << "Check maximize overflow - you loose!" ; kWarning( 1212 ) << kBacktrace() ; return; } ++recursion_protection; QRect max_area = workspace()->clientArea( MaximizeArea, this ); if( geometry() == max_area ) { if( max_mode != MaximizeFull ) maximize( MaximizeFull ); } else if( x() == max_area.left() && width() == max_area.width()) { if( max_mode != MaximizeHorizontal ) maximize( MaximizeHorizontal ); } else if( y() == max_area.top() && height() == max_area.height()) { if( max_mode != MaximizeVertical ) maximize( MaximizeVertical ); } else if( max_mode != MaximizeRestore ) { resetMaximize(); // not maximize( MaximizeRestore ), that'd change geometry - this is called from setGeometry() } --recursion_protection; } bool Client::isFullScreenable( bool fullscreen_hack ) const { if( !rules()->checkFullScreen( true )) return false; if( fullscreen_hack ) return isNormalWindow(); if( rules()->checkStrictGeometry( false )) { // the app wouldn't fit exactly fullscreen geometry due its strict geometry requirements QRect fsarea = workspace()->clientArea( FullScreenArea, this ); if( sizeForClientSize( fsarea.size(), SizemodeAny, true ) != fsarea.size()) return false; } // don't check size constrains - some apps request fullscreen despite requesting fixed size return !isSpecialWindow(); // also better disallow only weird types to go fullscreen } bool Client::userCanSetFullScreen() const { if( fullscreen_mode == FullScreenHack ) return false; if( !isFullScreenable( false )) return false; // isMaximizable() returns false if fullscreen TemporaryAssign< FullScreenMode > tmp( fullscreen_mode, FullScreenNone ); return isNormalWindow() && isMaximizable(); } void Client::setFullScreen( bool set, bool user ) { if( !isFullScreen() && !set ) return; if( fullscreen_mode == FullScreenHack ) return; if( user && !userCanSetFullScreen()) return; set = rules()->checkFullScreen( set ); setShade( ShadeNone ); bool was_fs = isFullScreen(); if( !was_fs ) geom_fs_restore = geometry(); fullscreen_mode = set ? FullScreenNormal : FullScreenNone; if( was_fs == isFullScreen()) return; StackingUpdatesBlocker blocker1( workspace()); GeometryUpdatesBlocker blocker2( this ); workspace()->updateClientLayer( this ); // active fullscreens get different layer info->setState( isFullScreen() ? NET::FullScreen : 0, NET::FullScreen ); updateDecoration( false, false ); if( isFullScreen()) setGeometry( workspace()->clientArea( FullScreenArea, this )); else { if( !geom_fs_restore.isNull()) setGeometry( QRect( geom_fs_restore.topLeft(), adjustedSize( geom_fs_restore.size()))); // TODO isShaded() ? else { // does this ever happen? setGeometry( workspace()->clientArea( MaximizeArea, this )); } } updateWindowRules(); } int Client::checkFullScreenHack( const QRect& geom ) const { // if it's noborder window, and has size of one screen or the whole desktop geometry, it's fullscreen hack if( noBorder() && app_noborder && isFullScreenable( true )) { if( geom.size() == workspace()->clientArea( FullArea, geom.center(), desktop()).size()) return 2; // full area fullscreen hack if( geom.size() == workspace()->clientArea( ScreenArea, geom.center(), desktop()).size()) return 1; // xinerama-aware fullscreen hack } return 0; } void Client::updateFullScreenHack( const QRect& geom ) { int type = checkFullScreenHack( geom ); if( fullscreen_mode == FullScreenNone && type != 0 ) { fullscreen_mode = FullScreenHack; updateDecoration( false, false ); QRect geom; if( rules()->checkStrictGeometry( false )) { geom = type == 2 // 1 - it's xinerama-aware fullscreen hack, 2 - it's full area ? workspace()->clientArea( FullArea, geom.center(), desktop()) : workspace()->clientArea( ScreenArea, geom.center(), desktop()); } else geom = workspace()->clientArea( FullScreenArea, geom.center(), desktop()); setGeometry( geom ); } else if( fullscreen_mode == FullScreenHack && type == 0 ) { fullscreen_mode = FullScreenNone; updateDecoration( false, false ); // whoever called this must setup correct geometry } StackingUpdatesBlocker blocker( workspace()); workspace()->updateClientLayer( this ); // active fullscreens get different layer } static QRect* visible_bound = 0; static GeometryTip* geometryTip = 0; void Client::drawbound( const QRect& geom ) { assert( visible_bound == NULL ); visible_bound = new QRect( geom ); doDrawbound( *visible_bound, false ); } void Client::clearbound() { if( visible_bound == NULL ) return; doDrawbound( *visible_bound, true ); delete visible_bound; visible_bound = 0; } void Client::doDrawbound( const QRect& geom, bool clear ) { if( decoration != NULL && decoration->drawbound( geom, clear )) return; // done by decoration XGCValues xgc; xgc.function = GXxor; xgc.foreground = WhitePixel( display(), DefaultScreen( display())); xgc.line_width = 5; xgc.subwindow_mode = IncludeInferiors; GC gc = XCreateGC( display(), DefaultRootWindow( display()), GCFunction | GCForeground | GCLineWidth | GCSubwindowMode, &xgc ); // the line is 5 pixel thick, so compensate for the extra two pixels // on outside (#88657) QRect g = geom; if( g.width() > 5 ) { g.setLeft( g.left() + 2 ); g.setRight( g.right() - 2 ); } if( g.height() > 5 ) { g.setTop( g.top() + 2 ); g.setBottom( g.bottom() - 2 ); } XDrawRectangle( display(), DefaultRootWindow( display()), gc, g.x(), g.y(), g.width(), g.height()); XFreeGC( display(), gc ); } void Client::positionGeometryTip() { assert( isMove() || isResize()); // Position and Size display if (options->showGeometryTip()) { if( !geometryTip ) { // save under is not necessary with opaque, and seem to make things slower bool save_under = ( isMove() && rules()->checkMoveResizeMode( options->moveMode ) != Options::Opaque ) || ( isResize() && rules()->checkMoveResizeMode( options->resizeMode ) != Options::Opaque ); geometryTip = new GeometryTip( &xSizeHint, save_under ); } QRect wgeom( moveResizeGeom ); // position of the frame, size of the window itself wgeom.setWidth( wgeom.width() - ( width() - clientSize().width())); wgeom.setHeight( wgeom.height() - ( height() - clientSize().height())); if( isShade()) wgeom.setHeight( 0 ); geometryTip->setGeometry( wgeom ); if( !geometryTip->isVisible()) geometryTip->show(); geometryTip->raise(); } } class EatAllPaintEvents : public QObject { protected: virtual bool eventFilter( QObject* o, QEvent* e ) { return e->type() == QEvent::Paint && o != geometryTip; } }; static EatAllPaintEvents* eater = 0; bool Client::startMoveResize() { assert( !moveResizeMode ); assert( QWidget::keyboardGrabber() == NULL ); assert( QWidget::mouseGrabber() == NULL ); stopDelayedMoveResize(); if( QApplication::activePopupWidget() != NULL ) return false; // popups have grab bool has_grab = false; // This reportedly improves smoothness of the moveresize operation, // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug* // (http://lists.kde.org/?t=107302193400001&r=1&w=2) XSetWindowAttributes attrs; QRect r = workspace()->clientArea( FullArea, this ); move_resize_grab_window = XCreateWindow( display(), rootWindow(), r.x(), r.y(), r.width(), r.height(), 0, CopyFromParent, InputOnly, CopyFromParent, 0, &attrs ); XMapRaised( display(), move_resize_grab_window ); if( XGrabPointer( display(), move_resize_grab_window, False, ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask, GrabModeAsync, GrabModeAsync, move_resize_grab_window, cursor.handle(), xTime() ) == Success ) has_grab = true; if( grabXKeyboard( frameId())) has_grab = move_resize_has_keyboard_grab = true; if( !has_grab ) // at least one grab is necessary in order to be able to finish move/resize { XDestroyWindow( display(), move_resize_grab_window ); move_resize_grab_window = None; return false; } if ( maximizeMode() != MaximizeRestore ) resetMaximize(); moveResizeMode = true; workspace()->setClientIsMoving(this); initialMoveResizeGeom = moveResizeGeom = geometry(); checkUnrestrictedMoveResize(); if ( ( isMove() && rules()->checkMoveResizeMode( options->moveMode ) != Options::Opaque ) || ( isResize() && rules()->checkMoveResizeMode( options->resizeMode ) != Options::Opaque ) ) { grabXServer(); kapp->sendPostedEvents(); // we have server grab -> nothing should cause paint events // unfortunately, that's not completely true, Qt may generate // paint events on some widgets due to FocusIn(?) // eat them, otherwise XOR painting will be broken (#58054) // paint events for the geometrytip need to be allowed, though eater = new EatAllPaintEvents; // not needed anymore? kapp->installEventFilter( eater ); } Notify::raise( isResize() ? Notify::ResizeStart : Notify::MoveStart ); if( effects ) static_cast(effects)->windowUserMovedResized( effectWindow(), true, false ); if( options->electricBorders() == Options::ElectricMoveOnly ) workspace()->reserveElectricBorderSwitching( true ); return true; } void Client::finishMoveResize( bool cancel ) { leaveMoveResize(); if( cancel ) setGeometry( initialMoveResizeGeom ); else setGeometry( moveResizeGeom ); checkMaximizeGeometry(); // FRAME update(); Notify::raise( isResize() ? Notify::ResizeEnd : Notify::MoveEnd ); if( effects ) static_cast(effects)->windowUserMovedResized( effectWindow(), false, true ); } void Client::leaveMoveResize() { clearbound(); if (geometryTip) { geometryTip->hide(); delete geometryTip; geometryTip = NULL; } if ( ( isMove() && rules()->checkMoveResizeMode( options->moveMode ) != Options::Opaque ) || ( isResize() && rules()->checkMoveResizeMode( options->resizeMode ) != Options::Opaque ) ) ungrabXServer(); if( move_resize_has_keyboard_grab ) ungrabXKeyboard(); move_resize_has_keyboard_grab = false; XUngrabPointer( display(), xTime() ); XDestroyWindow( display(), move_resize_grab_window ); move_resize_grab_window = None; workspace()->setClientIsMoving(0); if( move_faked_activity ) workspace()->unfakeActivity( this ); move_faked_activity = false; moveResizeMode = false; delete eater; eater = 0; delete sync_timeout; sync_timeout = NULL; if( options->electricBorders() == Options::ElectricMoveOnly ) workspace()->reserveElectricBorderSwitching( false ); } // This function checks if it actually makes sense to perform a restricted move/resize. // If e.g. the titlebar is already outside of the workarea, there's no point in performing // a restricted move resize, because then e.g. resize would also move the window (#74555). // NOTE: Most of it is duplicated from handleMoveResize(). void Client::checkUnrestrictedMoveResize() { if( unrestrictedMoveResize ) return; QRect desktopArea = workspace()->clientArea( WorkArea, moveResizeGeom.center(), desktop()); int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; // restricted move/resize - keep at least part of the titlebar always visible // how much must remain visible when moved away in that direction left_marge = qMin( 100 + border_right, moveResizeGeom.width()); right_marge = qMin( 100 + border_left, moveResizeGeom.width()); // width/height change with opaque resizing, use the initial ones titlebar_marge = initialMoveResizeGeom.height(); top_marge = border_bottom; bottom_marge = border_top; if( isResize()) { if( moveResizeGeom.bottom() < desktopArea.top() + top_marge ) unrestrictedMoveResize = true; if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) unrestrictedMoveResize = true; if( moveResizeGeom.right() < desktopArea.left() + left_marge ) unrestrictedMoveResize = true; if( moveResizeGeom.left() > desktopArea.right() - right_marge ) unrestrictedMoveResize = true; if( !unrestrictedMoveResize && moveResizeGeom.top() < desktopArea.top() ) // titlebar mustn't go out unrestrictedMoveResize = true; } if( isMove()) { if( moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1 ) // titlebar mustn't go out unrestrictedMoveResize = true; // no need to check top_marge, titlebar_marge already handles it if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) unrestrictedMoveResize = true; if( moveResizeGeom.right() < desktopArea.left() + left_marge ) unrestrictedMoveResize = true; if( moveResizeGeom.left() > desktopArea.right() - right_marge ) unrestrictedMoveResize = true; } } // When the user pressed mouse on the titlebar, don't activate move immediatelly, // since it may be just a click. Activate instead after a delay. Move used to be // activated only after moving by several pixels, but that looks bad. void Client::startDelayedMoveResize() { delete delayedMoveResizeTimer; delayedMoveResizeTimer = new QTimer( this ); connect( delayedMoveResizeTimer, SIGNAL( timeout()), this, SLOT( delayedMoveResize())); delayedMoveResizeTimer->setSingleShot( true ); delayedMoveResizeTimer->start( QApplication::doubleClickInterval()); } void Client::stopDelayedMoveResize() { delete delayedMoveResizeTimer; delayedMoveResizeTimer = NULL; } void Client::delayedMoveResize() { assert( buttonDown ); if( !startMoveResize()) buttonDown = false; updateCursor(); stopDelayedMoveResize(); } void Client::handleMoveResize( int x, int y, int x_root, int y_root ) { if(( mode == PositionCenter && !isMovableAcrossScreens() ) || ( mode != PositionCenter && ( isShade() || !isResizable()))) return; if ( !moveResizeMode ) { QPoint p( QPoint( x, y ) - moveOffset ); if (p.manhattanLength() >= 6) { if( !startMoveResize()) { buttonDown = false; updateCursor(); return; } updateCursor(); } else return; } // ShadeHover or ShadeActive, ShadeNormal was already avoided above if ( mode != PositionCenter && shade_mode != ShadeNone ) setShade( ShadeNone ); QPoint globalPos( x_root, y_root ); // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done, // the bottomleft corner should be at is at (topleft.x(), bottomright().y()) QPoint topleft = globalPos - moveOffset; QPoint bottomright = globalPos + invertedMoveOffset; QRect previousMoveResizeGeom = moveResizeGeom; // TODO move whole group when moving its leader or when the leader is not mapped? // compute bounds // NOTE: This is duped in checkUnrestrictedMoveResize(). QRect desktopArea = workspace()->clientArea( WorkArea, globalPos, desktop()); int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; if( unrestrictedMoveResize ) // unrestricted, just don't let it go out completely left_marge = right_marge = top_marge = bottom_marge = titlebar_marge = 5; else // restricted move/resize - keep at least part of the titlebar always visible { // how much must remain visible when moved away in that direction left_marge = qMin( 100 + border_right, moveResizeGeom.width()); right_marge = qMin( 100 + border_left, moveResizeGeom.width()); // width/height change with opaque resizing, use the initial ones titlebar_marge = initialMoveResizeGeom.height(); top_marge = border_bottom; bottom_marge = border_top; } bool update = false; if( isResize()) { // first resize (without checking constrains), then snap, then check bounds, then check constrains QRect orig = initialMoveResizeGeom; Sizemode sizemode = SizemodeAny; switch ( mode ) { case PositionTopLeft: moveResizeGeom = QRect( topleft, orig.bottomRight() ) ; break; case PositionBottomRight: moveResizeGeom = QRect( orig.topLeft(), bottomright ) ; break; case PositionBottomLeft: moveResizeGeom = QRect( QPoint( topleft.x(), orig.y() ), QPoint( orig.right(), bottomright.y()) ) ; break; case PositionTopRight: moveResizeGeom = QRect( QPoint( orig.x(), topleft.y() ), QPoint( bottomright.x(), orig.bottom()) ) ; break; case PositionTop: moveResizeGeom = QRect( QPoint( orig.left(), topleft.y() ), orig.bottomRight() ) ; sizemode = SizemodeFixedH; // try not to affect height break; case PositionBottom: moveResizeGeom = QRect( orig.topLeft(), QPoint( orig.right(), bottomright.y() ) ) ; sizemode = SizemodeFixedH; break; case PositionLeft: moveResizeGeom = QRect( QPoint( topleft.x(), orig.top() ), orig.bottomRight() ) ; sizemode = SizemodeFixedW; break; case PositionRight: moveResizeGeom = QRect( orig.topLeft(), QPoint( bottomright.x(), orig.bottom() ) ) ; sizemode = SizemodeFixedW; break; case PositionCenter: default: assert( false ); break; } // adjust new size to snap to other windows/borders moveResizeGeom = workspace()->adjustClientSize( this, moveResizeGeom, mode ); // NOTE: This is duped in checkUnrestrictedMoveResize(). if( moveResizeGeom.bottom() < desktopArea.top() + top_marge ) moveResizeGeom.setBottom( desktopArea.top() + top_marge ); if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) moveResizeGeom.setTop( desktopArea.bottom() - bottom_marge ); if( moveResizeGeom.right() < desktopArea.left() + left_marge ) moveResizeGeom.setRight( desktopArea.left() + left_marge ); if( moveResizeGeom.left() > desktopArea.right() - right_marge ) moveResizeGeom.setLeft(desktopArea.right() - right_marge ); if( !unrestrictedMoveResize && moveResizeGeom.top() < desktopArea.top() ) // titlebar mustn't go out moveResizeGeom.setTop( desktopArea.top()); QSize size = adjustedSize( moveResizeGeom.size(), sizemode ); // the new topleft and bottomright corners (after checking size constrains), if they'll be needed topleft = QPoint( moveResizeGeom.right() - size.width() + 1, moveResizeGeom.bottom() - size.height() + 1 ); bottomright = QPoint( moveResizeGeom.left() + size.width() - 1, moveResizeGeom.top() + size.height() - 1 ); orig = moveResizeGeom; switch ( mode ) { // these 4 corners ones are copied from above case PositionTopLeft: moveResizeGeom = QRect( topleft, orig.bottomRight() ) ; break; case PositionBottomRight: moveResizeGeom = QRect( orig.topLeft(), bottomright ) ; break; case PositionBottomLeft: moveResizeGeom = QRect( QPoint( topleft.x(), orig.y() ), QPoint( orig.right(), bottomright.y()) ) ; break; case PositionTopRight: moveResizeGeom = QRect( QPoint( orig.x(), topleft.y() ), QPoint( bottomright.x(), orig.bottom()) ) ; break; // The side ones can't be copied exactly - if aspect ratios are specified, both dimensions may change. // Therefore grow to the right/bottom if needed. // TODO it should probably obey gravity rather than always using right/bottom ? case PositionTop: moveResizeGeom = QRect( QPoint( orig.left(), topleft.y() ), QPoint( bottomright.x(), orig.bottom()) ) ; break; case PositionBottom: moveResizeGeom = QRect( orig.topLeft(), QPoint( bottomright.x(), bottomright.y() ) ) ; break; case PositionLeft: moveResizeGeom = QRect( QPoint( topleft.x(), orig.top() ), QPoint( orig.right(), bottomright.y())); break; case PositionRight: moveResizeGeom = QRect( orig.topLeft(), QPoint( bottomright.x(), bottomright.y() ) ) ; break; case PositionCenter: default: assert( false ); break; } if( moveResizeGeom.size() != previousMoveResizeGeom.size()) update = true; } else if( isMove()) { assert( mode == PositionCenter ); if( !isMovable() ) // isMovableAcrossScreens() must have been true to get here { // Special moving of maximized windows on Xinerama screens int screen = workspace()->screenNumber( globalPos ); moveResizeGeom = workspace()->clientArea( MaximizeArea, screen, 0 ); } else { // first move, then snap, then check bounds moveResizeGeom.moveTopLeft( topleft ); moveResizeGeom.moveTopLeft( workspace()->adjustClientPosition( this, moveResizeGeom.topLeft(), unrestrictedMoveResize )); // NOTE: This is duped in checkUnrestrictedMoveResize(). if( moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1 ) // titlebar mustn't go out moveResizeGeom.moveBottom( desktopArea.top() + titlebar_marge - 1 ); // no need to check top_marge, titlebar_marge already handles it if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) moveResizeGeom.moveTop( desktopArea.bottom() - bottom_marge ); if( moveResizeGeom.right() < desktopArea.left() + left_marge ) moveResizeGeom.moveRight( desktopArea.left() + left_marge ); if( moveResizeGeom.left() > desktopArea.right() - right_marge ) moveResizeGeom.moveLeft(desktopArea.right() - right_marge ); } if( moveResizeGeom.topLeft() != previousMoveResizeGeom.topLeft()) update = true; } else assert( false ); if( isResize()) { if( sync_timeout != NULL ) { sync_resize_pending = true; return; } } if( update ) performMoveResize(); if ( isMove() ) workspace()->checkElectricBorder(globalPos, xTime()); } void Client::performMoveResize() { #ifdef HAVE_XSYNC if( isResize() && sync_counter != None ) { sync_timeout = new QTimer( this ); connect( sync_timeout, SIGNAL( timeout()), SLOT( syncTimeout())); sync_timeout->setSingleShot( true ); sync_timeout->start( 500 ); sendSyncRequest(); } #endif sync_resize_pending = false; if( rules()->checkMoveResizeMode ( isResize() ? options->resizeMode : options->moveMode ) == Options::Opaque ) { setGeometry( moveResizeGeom ); positionGeometryTip(); } else if( rules()->checkMoveResizeMode ( isResize() ? options->resizeMode : options->moveMode ) == Options::Transparent ) { clearbound(); // it's necessary to move the geometry tip when there's no outline positionGeometryTip(); // shown, otherwise it would cause repaint problems in case drawbound( moveResizeGeom ); // they overlap; the paint event will come after this, } // so the geometry tip will be painted above the outline if( effects ) static_cast(effects)->windowUserMovedResized( effectWindow(), false, false ); } void Client::syncTimeout() { sync_timeout->deleteLater(); sync_timeout = NULL; if( sync_resize_pending ) performMoveResize(); } } // namespace diff --git a/layers.cpp b/layers.cpp index adf5aa640..39a8eb3fc 100644 --- a/layers.cpp +++ b/layers.cpp @@ -1,848 +1,856 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // SELI zmenit doc /* This file contains things relevant to stacking order and layers. Design: Normal unconstrained stacking order, as requested by the user (by clicking on windows to raise them, etc.), is in Workspace::unconstrained_stacking_order. That list shouldn't be used at all, except for building Workspace::stacking_order. The building is done in Workspace::constrainedStackingOrder(). Only Workspace::stackingOrder() should be used to get the stacking order, because it also checks the stacking order is up to date. All clients are also stored in Workspace::clients (except for isDesktop() clients, as those are very special, and are stored in Workspace::desktops), in the order the clients were created. Every window has one layer assigned in which it is. There are 6 layers, from bottom : DesktopLayer, BelowLayer, NormalLayer, DockLayer, AboveLayer and ActiveLayer (see also NETWM sect.7.10.). The layer a window is in depends on the window type, and on other things like whether the window is active. NET::Splash clients belong to the Normal layer. NET::TopMenu clients belong to Dock layer. Clients that are both NET::Dock and NET::KeepBelow are in the Normal layer in order to keep the 'allow window to cover the panel' Kicker setting to work as intended (this may look like a slight spec violation, but a) I have no better idea, b) the spec allows adjusting the stacking order if the WM thinks it's a good idea . We put all NET::KeepAbove above all Docks too, even though the spec suggests putting them in the same layer. Most transients are in the same layer as their mainwindow, see Workspace::constrainedStackingOrder(), they may also be in higher layers, but they should never be below their mainwindow. When some client attribute changes (above/below flag, transiency...), Workspace::updateClientLayer() should be called in order to make sure it's moved to the appropriate layer ClientList if needed. Currently the things that affect client in which layer a client belongs: KeepAbove/Keep Below flags, window type, fullscreen state and whether the client is active, mainclient (transiency). Make sure updateStackingOrder() is called in order to make Workspace::stackingOrder() up to date and propagated to the world. Using Workspace::blockStackingUpdates() (or the StackingUpdatesBlocker helper class) it's possible to temporarily disable updates and the stacking order will be updated once after it's allowed again. */ #include #include #include "utils.h" #include "client.h" #include "workspace.h" #include "tabbox.h" #include "group.h" #include "rules.h" #include "unmanaged.h" #include "deleted.h" #include namespace KWin { //******************************* // Workspace //******************************* void Workspace::updateClientLayer( Client* c ) { if( c == NULL ) return; if( c->layer() == c->belongsToLayer()) return; StackingUpdatesBlocker blocker( this ); c->invalidateLayer(); // invalidate, will be updated when doing restacking for( ClientList::ConstIterator it = c->transients().begin(); it != c->transients().end(); ++it ) updateClientLayer( *it ); } void Workspace::updateStackingOrder( bool propagate_new_clients ) { if( block_stacking_updates > 0 ) { if( propagate_new_clients ) blocked_propagating_new_clients = true; return; } ClientList new_stacking_order = constrainedStackingOrder(); bool changed = ( new_stacking_order != stacking_order || force_restacking ); force_restacking = false; stacking_order = new_stacking_order; #if 0 kDebug() << "stacking:" << changed; if( changed || propagate_new_clients ) { for( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it ) kDebug() << (void*)(*it) << *it << ":" << (*it)->layer(); } #endif if( changed || propagate_new_clients ) { propagateClients( propagate_new_clients ); addRepaintFull(); if( active_client ) active_client->updateMouseGrab(); } } /*! Propagates the managed clients to the world. Called ONLY from updateStackingOrder(). */ void Workspace::propagateClients( bool propagate_new_clients ) { Window *cl; // MW we should not assume WId and Window to be compatible // when passig pointers around. // restack the windows according to the stacking order // 1 - supportWindow, 1 - topmenu_space, 8 - electric borders Window* new_stack = new Window[ stacking_order.count() + 1 + 1 + 8 ]; int pos = 0; // Stack all windows under the support window. The support window is // not used for anything (besides the NETWM property), and it's not shown, // but it was lowered after kwin startup. Stacking all clients below // it ensures that no client will be ever shown above override-redirect // windows (e.g. popups). new_stack[ pos++ ] = supportWindow->winId(); for( int i = 0; i < ELECTRIC_COUNT; ++i ) if( electric_windows[ i ] != None ) new_stack[ pos++ ] = electric_windows[ i ]; int topmenu_space_pos = 1; // not 0, that's supportWindow !!! for ( int i = stacking_order.size() - 1; i >= 0; i-- ) { if( stacking_order.at( i )->hiddenPreview()) continue; new_stack[ pos++ ] = stacking_order.at( i )->frameId(); if( stacking_order.at( i )->belongsToLayer() >= DockLayer ) topmenu_space_pos = pos; } if( topmenu_space != NULL ) { // make sure the topmenu space is below all topmenus, fullscreens, etc. for( int i = pos; i > topmenu_space_pos; --i ) new_stack[ i ] = new_stack[ i - 1 ]; new_stack[ topmenu_space_pos ] = topmenu_space->winId(); ++pos; } // when having hidden previews, stack hidden windows below everything else // (as far as pure X stacking order is concerned), in order to avoid having // these windows that should be unmapped to interfere with other windows for ( int i = stacking_order.size() - 1; i >= 0; i-- ) { if( !stacking_order.at( i )->hiddenPreview()) continue; new_stack[ pos++ ] = stacking_order.at( i )->frameId(); if( stacking_order.at( i )->belongsToLayer() >= DockLayer ) topmenu_space_pos = pos; } // TODO isn't it too inefficient to restack always all clients? // TODO don't restack not visible windows? assert( new_stack[ 0 ] == supportWindow->winId()); XRestackWindows(display(), new_stack, pos); delete [] new_stack; if ( propagate_new_clients ) { cl = new Window[ desktops.count() + clients.count()]; pos = 0; // TODO this is still not completely in the map order for ( ClientList::ConstIterator it = desktops.begin(); it != desktops.end(); ++it ) cl[pos++] = (*it)->window(); for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it ) cl[pos++] = (*it)->window(); rootInfo->setClientList( cl, pos ); delete [] cl; } cl = new Window[ stacking_order.count()]; pos = 0; for ( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it) cl[pos++] = (*it)->window(); rootInfo->setClientListStacking( cl, pos ); delete [] cl; } /*! Returns topmost visible client. Windows on the dock, the desktop or of any other special kind are excluded. Also if the window doesn't accept focus it's excluded. */ -// TODO misleading name for this method -Client* Workspace::topClientOnDesktop( int desktop, bool unconstrained, bool only_normal ) const +// TODO misleading name for this method, too many slightly different ways to use it +Client* Workspace::topClientOnDesktop( int desktop, int screen, bool unconstrained, bool only_normal ) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); ClientList list; if( !unconstrained ) list = stacking_order; else list = unconstrained_stacking_order; for( int i = list.size() - 1; i >= 0; --i ) { if( list.at( i )->isOnDesktop( desktop ) && list.at( i )->isShown( false )) { + if( screen != -1 && list.at( i )->screen() != screen ) + continue; if( !only_normal ) return list.at( i ); if( list.at( i )->wantsTabFocus() && !list.at( i )->isSpecialWindow()) return list.at( i ); } } return 0; } Client* Workspace::findDesktop( bool topmost, int desktop ) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); if( topmost ) { for ( int i = stacking_order.size() - 1; i>=0; i-- ) { if ( stacking_order.at( i )->isOnDesktop( desktop ) && stacking_order.at( i )->isDesktop() && stacking_order.at( i )->isShown( true )) return stacking_order.at( i ); } } else // bottom-most { foreach ( Client* c, stacking_order ) { if ( c->isOnDesktop( desktop ) && c->isDesktop() && c->isShown( true )) return c; } } return NULL; } void Workspace::raiseOrLowerClient( Client *c) { if (!c) return; Client* topmost = NULL; // TODO Q_ASSERT( block_stacking_updates == 0 ); if ( most_recently_raised && stacking_order.contains( most_recently_raised ) && most_recently_raised->isShown( true ) && c->isOnCurrentDesktop()) topmost = most_recently_raised; else - topmost = topClientOnDesktop( c->isOnAllDesktops() ? currentDesktop() : c->desktop()); + topmost = topClientOnDesktop( c->isOnAllDesktops() ? currentDesktop() : c->desktop(), + options->separateScreenFocus ? c->screen() : -1 ); if( c == topmost) lowerClient(c); else raiseClient(c); } void Workspace::lowerClient( Client* c, bool nogroup ) { if ( !c ) return; if( c->isTopMenu()) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker( this ); unconstrained_stacking_order.removeAll( c ); unconstrained_stacking_order.prepend( c ); if( !nogroup && c->isTransient() ) { // lower also all windows in the group, in their reversed stacking order ClientList wins = ensureStackingOrder( c->group()->members()); for( int i = wins.size() - 1; i >= 0; --i ) { if( wins[ i ] != c ) lowerClient( wins[ i ], true ); } } if ( c == most_recently_raised ) most_recently_raised = 0; } void Workspace::lowerClientWithinApplication( Client* c ) { if ( !c ) return; if( c->isTopMenu()) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker( this ); unconstrained_stacking_order.removeAll( c ); bool lowered = false; // first try to put it below the bottom-most window of the application for( ClientList::Iterator it = unconstrained_stacking_order.begin(); it != unconstrained_stacking_order.end(); ++it ) if( Client::belongToSameApplication( *it, c )) { unconstrained_stacking_order.insert( it, c ); lowered = true; break; } if( !lowered ) unconstrained_stacking_order.prepend( c ); // ignore mainwindows } void Workspace::raiseClient( Client* c, bool nogroup ) { if ( !c ) return; if( c->isTopMenu()) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker( this ); if( !nogroup && c->isTransient()) { ClientList wins = ensureStackingOrder( c->group()->members()); foreach( Client* c2, wins ) if( c2 != c ) raiseClient( c2, true ); } unconstrained_stacking_order.removeAll( c ); unconstrained_stacking_order.append( c ); if( !c->isSpecialWindow()) { most_recently_raised = c; pending_take_activity = NULL; } } void Workspace::raiseClientWithinApplication( Client* c ) { if ( !c ) return; if( c->isTopMenu()) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker( this ); // ignore mainwindows // first try to put it above the top-most window of the application for ( int i = unconstrained_stacking_order.size() - 1; i>= 0 ; i-- ) { if( unconstrained_stacking_order.at( i ) == c ) // don't lower it just because it asked to be raised return; if( Client::belongToSameApplication( unconstrained_stacking_order.at( i ), c )) { unconstrained_stacking_order.removeAll( c ); unconstrained_stacking_order.insert( ++i, c ); // insert after the found one return; } } } void Workspace::raiseClientRequest( Client* c, NET::RequestSource src, Time timestamp ) { if( src == NET::FromTool || allowFullClientRaising( c, timestamp )) raiseClient( c ); else { raiseClientWithinApplication( c ); c->demandAttention(); } } void Workspace::lowerClientRequest( Client* c, NET::RequestSource src, Time /*timestamp*/ ) { // If the client has support for all this focus stealing prevention stuff, // do only lowering within the application, as that's the more logical // variant of lowering when application requests it. // No demanding of attention here of course. if( src == NET::FromTool || !c->hasUserTimeSupport()) lowerClient( c ); else lowerClientWithinApplication( c ); } void Workspace::restackClientUnderActive( Client* c ) { if( c->isTopMenu()) return; if( !active_client || active_client == c ) { raiseClient( c ); return; } assert( unconstrained_stacking_order.contains( active_client )); if( Client::belongToSameApplication( active_client, c )) { // put it below the active window if it's the same app unconstrained_stacking_order.removeAll( c ); unconstrained_stacking_order.insert( unconstrained_stacking_order.indexOf( active_client ), c ); } else { // put in the stacking order below _all_ windows belonging to the active application for( ClientList::Iterator it = unconstrained_stacking_order.begin(); it != unconstrained_stacking_order.end(); ++it ) { // TODO ignore topmenus? if( Client::belongToSameApplication( active_client, *it )) { if( *it != c ) { unconstrained_stacking_order.removeAll( c ); unconstrained_stacking_order.insert( it, c ); } break; } } } assert( unconstrained_stacking_order.contains( c )); for( int desktop = 1; desktop <= numberOfDesktops(); ++desktop ) { // do for every virtual desktop to handle the case of onalldesktop windows if( c->wantsTabFocus() && c->isOnDesktop( desktop ) && focus_chain[ desktop ].contains( active_client )) { if( Client::belongToSameApplication( active_client, c )) { // put it after the active window if it's the same app focus_chain[ desktop ].removeAll( c ); focus_chain[ desktop ].insert( focus_chain[ desktop ].indexOf( active_client ), c ); } else { // put it in focus_chain[currentDesktop()] after all windows belonging to the active applicationa focus_chain[ desktop ].removeAll( c ); for( int i = focus_chain[ desktop ].size() - 1; i >= 0; --i ) { if( Client::belongToSameApplication( active_client, focus_chain[ desktop ].at( i ))) { focus_chain[ desktop ].insert( i, c ); break; } } } } } // the same for global_focus_chain if( c->wantsTabFocus() && global_focus_chain.contains( active_client )) { if( Client::belongToSameApplication( active_client, c )) { global_focus_chain.removeAll( c ); global_focus_chain.insert( global_focus_chain.indexOf( active_client ), c ); } else { global_focus_chain.removeAll( c ); for ( int i = global_focus_chain.size() - 1; i >= 0; --i ) { if( Client::belongToSameApplication( active_client, global_focus_chain.at( i ) )) { global_focus_chain.insert( i, c ); break; } } } } updateStackingOrder(); } void Workspace::restoreSessionStackingOrder( Client* c ) { if( c->sessionStackingOrder() < 0 ) return; StackingUpdatesBlocker blocker( this ); unconstrained_stacking_order.removeAll( c ); ClientList::Iterator best_pos = unconstrained_stacking_order.end(); for( ClientList::Iterator it = unconstrained_stacking_order.begin(); // from bottom it != unconstrained_stacking_order.end(); ++it ) { if( (*it)->sessionStackingOrder() > c->sessionStackingOrder() ) { unconstrained_stacking_order.insert( it, c ); return; } } unconstrained_stacking_order.append( c ); } void Workspace::circulateDesktopApplications() { if ( desktops.count() > 1 ) { bool change_active = activeClient()->isDesktop(); raiseClient( findDesktop( false, currentDesktop())); if( change_active ) // if the previously topmost Desktop was active, activate this new one activateClient( findDesktop( true, currentDesktop())); } // if there's no active client, make desktop the active one if( desktops.count() > 0 && activeClient() == NULL && should_get_focus.count() == 0 ) activateClient( findDesktop( true, currentDesktop())); } /*! Returns a stacking order based upon \a list that fulfills certain contained. */ ClientList Workspace::constrainedStackingOrder() { ClientList layer[ NumLayers ]; #if 0 kDebug() << "stacking1:"; for( ClientList::ConstIterator it = unconstrained_stacking_order.begin(); it != unconstrained_stacking_order.end(); ++it ) kdDebug() << (void*)(*it) << *it << ":" << (*it)->layer(); #endif // build the order from layers QHash< Group*, Layer > minimum_layer; for( ClientList::ConstIterator it = unconstrained_stacking_order.begin(); it != unconstrained_stacking_order.end(); ++it ) { Layer l = (*it)->layer(); // If a window is raised above some other window in the same window group // which is in the ActiveLayer (i.e. it's fulscreened), make sure it stays // above that window (see #95731). if( minimum_layer.contains( (*it)->group()) && minimum_layer[ (*it)->group() ] == ActiveLayer && ( l == NormalLayer || l == AboveLayer )) { l = minimum_layer[ (*it)->group() ]; } minimum_layer[ (*it)->group() ] = l; layer[ l ].append( *it ); } ClientList stacking; for( Layer lay = FirstLayer; lay < NumLayers; ++lay ) stacking += layer[ lay ]; #if 0 kDebug() << "stacking2:"; for( ClientList::ConstIterator it = stacking.begin(); it != stacking.end(); ++it ) kdDebug() << (void*)(*it) << *it << ":" << (*it)->layer(); #endif // now keep transients above their mainwindows // TODO this could(?) use some optimization for( int i = stacking.size() - 1; i >= 0; ) { if( !stacking[ i ]->isTransient()) { --i; continue; } int i2 = -1; if( stacking[ i ]->groupTransient()) { if( stacking[ i ]->group()->members().count() > 0 ) { // find topmost client this one is transient for for( i2 = stacking.size() - 1; i2 >= 0; --i2 ) { if( stacking[ i2 ] == stacking[ i ] ) { i2 = -1; // don't reorder, already the topmost in the group break; } if( stacking[ i2 ]->hasTransient( stacking[ i ], true ) && keepTransientAbove( stacking[ i2 ], stacking[ i ] )) break; } } // else i2 remains pointing at -1 } else { for( i2 = stacking.size() - 1; i2 >= 0; --i2 ) { if( stacking[ i2 ] == stacking[ i ] ) { i2 = -1; // don't reorder, already on top of its mainwindow break; } if( stacking[ i2 ] == stacking[ i ]->transientFor() && keepTransientAbove( stacking[ i2 ], stacking[ i ] )) break; } } if( i2 == -1 ) { --i; continue; } Client* current = stacking[ i ]; stacking.removeAt( i ); --i; // move onto the next item (for next for() iteration) --i2; // adjust index of the mainwindow after the remove above if( !current->transients().isEmpty()) // this one now can be possibly above its transients, i = i2; // so go again higher in the stack order and possibly move those transients again ++i2; // insert after (on top of) the mainwindow, it's ok if it2 is now stacking.end() stacking.insert( i2, current ); } #if 0 kDebug() << "stacking3:"; for( ClientList::ConstIterator it = stacking.begin(); it != stacking.end(); ++it ) kdDebug() << (void*)(*it) << *it << ":" << (*it)->layer(); kDebug() << "\n\n"; #endif return stacking; } void Workspace::blockStackingUpdates( bool block ) { if( block ) { if( block_stacking_updates == 0 ) blocked_propagating_new_clients = false; ++block_stacking_updates; } else // !block if( --block_stacking_updates == 0 ) updateStackingOrder( blocked_propagating_new_clients ); } // Ensure list is in stacking order ClientList Workspace::ensureStackingOrder( const ClientList& list ) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); if( list.count() < 2 ) return list; // TODO is this worth optimizing? ClientList result = list; for( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it ) if( result.removeAll( *it ) != 0 ) result.append( *it ); return result; } // check whether a transient should be actually kept above its mainwindow // there may be some special cases where this rule shouldn't be enfored bool Workspace::keepTransientAbove( const Client* mainwindow, const Client* transient ) { // When topmenu's mainwindow becomes active, topmenu is raised and shown. // They also belong to the Dock layer. This makes them to be very high. // Therefore don't keep group transients above them, otherwise this would move // group transients way too high. if( mainwindow->isTopMenu() && transient->groupTransient()) return false; // #93832 - don't keep splashscreens above dialogs if( transient->isSplash() && mainwindow->isDialog()) return false; // This is rather a hack for #76026. Don't keep non-modal dialogs above // the mainwindow, but only if they're group transient (since only such dialogs // have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker) // needs to be found. if( transient->isDialog() && !transient->isModal() && transient->groupTransient()) return false; // #63223 - don't keep transients above docks, because the dock is kept high, // and e.g. dialogs for them would be too high too if( mainwindow->isDock()) return false; return true; } // Returns all windows in their stacking order on the root window, used only by compositing. // TODO This possibly should be optimized to avoid the X roundtrip and building it every pass. ToplevelList Workspace::compositingStackingOrder() const { Window dummy; Window* windows = NULL; unsigned int count = 0; XQueryTree( display(), rootWindow(), &dummy, &dummy, &windows, &count ); ToplevelList ret; // use our own stacking order, not the X one, as they may differ foreach( Client* c, stacking_order ) ret.append( c ); for( unsigned int i = 0; i < count; ++i ) { if( Unmanaged* c = findUnmanaged( WindowMatchPredicate( windows[ i ] ))) ret.append( c ); } foreach( Deleted* c, deleted ) ret.append( c ); if( windows != NULL ) XFree( windows ); return ret; } //******************************* // Client //******************************* void Client::restackWindow( Window /*above TODO */, int detail, NET::RequestSource src, Time timestamp, bool send_event ) { switch ( detail ) { case Above: case TopIf: workspace()->raiseClientRequest( this, src, timestamp ); break; case Below: case BottomIf: workspace()->lowerClientRequest( this, src, timestamp ); break; case Opposite: default: break; } if( send_event ) sendSyntheticConfigureNotify(); } void Client::setKeepAbove( bool b ) { b = rules()->checkKeepAbove( b ); if( b && !rules()->checkKeepBelow( false )) setKeepBelow( false ); if ( b == keepAbove()) { // force hint change if different if( bool( info->state() & NET::KeepAbove ) != keepAbove()) info->setState( keepAbove() ? NET::KeepAbove : 0, NET::KeepAbove ); return; } keep_above = b; info->setState( keepAbove() ? NET::KeepAbove : 0, NET::KeepAbove ); if( decoration != NULL ) decoration->emitKeepAboveChanged( keepAbove()); workspace()->updateClientLayer( this ); updateWindowRules(); } void Client::setKeepBelow( bool b ) { b = rules()->checkKeepBelow( b ); if( b && !rules()->checkKeepAbove( false )) setKeepAbove( false ); if ( b == keepBelow()) { // force hint change if different if( bool( info->state() & NET::KeepBelow ) != keepBelow()) info->setState( keepBelow() ? NET::KeepBelow : 0, NET::KeepBelow ); return; } keep_below = b; info->setState( keepBelow() ? NET::KeepBelow : 0, NET::KeepBelow ); if( decoration != NULL ) decoration->emitKeepBelowChanged( keepBelow()); workspace()->updateClientLayer( this ); updateWindowRules(); } Layer Client::layer() const { if( in_layer == UnknownLayer ) const_cast< Client* >( this )->in_layer = belongsToLayer(); return in_layer; } Layer Client::belongsToLayer() const { if( isDesktop()) return DesktopLayer; if( isSplash()) // no damn annoying splashscreens return NormalLayer; // getting in the way of everything else if( isDock() && keepBelow()) // slight hack for the 'allow window to cover panel' Kicker setting // don't move keepbelow docks below normal window, but only to the same // layer, so that both may be raised to cover the other return NormalLayer; if( keepBelow()) return BelowLayer; if( isDock() && !keepBelow()) return DockLayer; if( isTopMenu()) return DockLayer; - // only raise fullscreen above docks if it's the topmost window in unconstrained stacking order, - // i.e. the window set to be topmost by the user (also includes transients of the fullscreen window) - const Client* ac = workspace()->mostRecentlyActivatedClient(); // instead of activeClient() - avoids flicker - const Client* top = workspace()->topClientOnDesktop( desktop(), true, false ); - if( isFullScreen() && ac != NULL && top != NULL - && ( ac == this || this->group() == ac->group()) - && ( top == this || this->group() == top->group())) + if( isActiveFullScreen()) return ActiveLayer; if( keepAbove()) return AboveLayer; return NormalLayer; } +bool Client::isActiveFullScreen() const + { + // only raise fullscreen above docks if it's the topmost window in unconstrained stacking order, + // i.e. the window set to be topmost by the user (also includes transients of the fullscreen window) + const Client* ac = workspace()->mostRecentlyActivatedClient(); // instead of activeClient() - avoids flicker + const Client* top = workspace()->topClientOnDesktop( desktop(), screen(), true, false ); + return( isFullScreen() && ac != NULL && top != NULL +// not needed, for xinerama && ( ac == this || this->group() == ac->group()) + && ( top == this || this->group() == top->group())); + } + } // namespace diff --git a/workspace.cpp b/workspace.cpp index 728575e9a..32fcd50a1 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -1,2676 +1,2676 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ //#define QT_CLEAN_NAMESPACE #include "workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "plugins.h" #include "client.h" #include "popupinfo.h" #include "tabbox.h" #include "atoms.h" #include "placement.h" #include "notifications.h" #include "group.h" #include "rules.h" #include "kwinadaptor.h" #include "unmanaged.h" #include "scene.h" #include "deleted.h" #include "effects.h" #include "kdecorationfactory.h" #include #include #include #include #include #include #include #include #include namespace KWin { extern int screen_number; Workspace *Workspace::_self = 0; // Rikkus: This class is too complex. It needs splitting further. // It's a nightmare to understand, especially with so few comments :( // Matthias: Feel free to ask me questions about it. Feel free to add // comments. I dissagree that further splittings makes it easier. 2500 // lines are not too much. It's the task that is complex, not the // code. Workspace::Workspace( bool restore ) : QObject (0), current_desktop (0), number_of_desktops(0), active_popup( NULL ), active_popup_client( NULL ), temporaryRulesMessages( "_KDE_NET_WM_TEMPORARY_RULES", NULL, false ), rules_updates_disabled( false ), active_client (0), last_active_client (0), most_recently_raised (0), movingClient(0), pending_take_activity ( NULL ), active_screen (0), delayfocus_client (0), force_restacking( false ), showing_desktop( false ), block_showing_desktop( 0 ), was_user_interaction (false), session_saving (false), control_grab (false), tab_grab (false), mouse_emulation (false), block_focus (0), tab_box (0), popupinfo (0), popup (0), advanced_popup (0), trans_popup (0), desk_popup (0), keys (0), client_keys ( NULL ), client_keys_dialog ( NULL ), client_keys_client ( NULL ), disable_shortcuts_keys ( NULL ), global_shortcuts_disabled( false ), global_shortcuts_disabled_for_client( false ), workspaceInit (true), startup(0), layoutOrientation(Qt::Vertical), layoutX(-1), layoutY(2), managing_topmenus( false ), topmenu_selection( NULL ), topmenu_watcher( NULL ), topmenu_height( 0 ), topmenu_space( NULL ), set_active_client_recursion( 0 ), block_stacking_updates( 0 ), forced_global_mouse_grab( false ), cm_selection( NULL ), compositingSuspended( false ), compositeRate( 0 ), overlay( None ), overlay_visible( true ), overlay_shown( false ), transSlider( NULL ), transButton( NULL ) { (void) new KWinAdaptor( this ); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject("/KWin", this); dbus.connect(QString(), "/KWin", "org.kde.KWin", "reloadConfig", this, SLOT(slotReloadConfig())); dbus.connect(QString(), "/KWin", "org.kde.KWin", "reinitCompositing", this, SLOT(slotReinitCompositing())); _self = this; mgr = new PluginMgr; QX11Info info; default_colormap = DefaultColormap(display(), info.screen() ); installed_colormap = default_colormap; for( int i = 0; i < ELECTRIC_COUNT; ++i ) { electric_reserved[ i ] = 0; electric_windows[ i ] = None; } connect( &temporaryRulesMessages, SIGNAL( gotMessage( const QString& )), this, SLOT( gotTemporaryRulesMessage( const QString& ))); connect( &rulesUpdatedTimer, SIGNAL( timeout()), this, SLOT( writeWindowRules())); updateXTime(); // needed for proper initialization of user_time in Client ctor delayFocusTimer = 0; if ( restore ) loadSessionInfo(); loadWindowRules(); (void) QApplication::desktop(); // trigger creation of desktop widget // call this before XSelectInput() on the root window startup = new KStartupInfo( KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this ); // select windowmanager privileges XSelectInput(display(), rootWindow(), KeyPressMask | PropertyChangeMask | ColormapChangeMask | SubstructureRedirectMask | SubstructureNotifyMask | FocusChangeMask | // for NotifyDetailNone ExposureMask ); Extensions::init(); setupCompositing(); // compatibility long data = 1; XChangeProperty( display(), rootWindow(), atoms->kwin_running, atoms->kwin_running, 32, PropModeAppend, (unsigned char*) &data, 1 ); client_keys = new KActionCollection( this ); initShortcuts(); tab_box = new TabBox( this ); popupinfo = new PopupInfo( this ); init(); connect( kapp->desktop(), SIGNAL( resized( int )), SLOT( desktopResized())); } void Workspace::init() { if( options->electricBorders() == Options::ElectricAlways ) reserveElectricBorderSwitching( true ); updateElectricBorders(); // not used yet // topDock = 0L; // maximizedWindowCounter = 0; supportWindow = new QWidget( NULL, Qt::X11BypassWindowManagerHint ); XLowerWindow( display(), supportWindow->winId()); // see usage in layers.cpp XSetWindowAttributes attr; attr.override_redirect = 1; null_focus_window = XCreateWindow( display(), rootWindow(), -1,-1, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWOverrideRedirect, &attr ); XMapWindow(display(), null_focus_window); unsigned long protocols[ 5 ] = { NET::Supported | NET::SupportingWMCheck | NET::ClientList | NET::ClientListStacking | NET::DesktopGeometry | NET::NumberOfDesktops | NET::CurrentDesktop | NET::ActiveWindow | NET::WorkArea | NET::CloseWindow | NET::DesktopNames | NET::WMName | NET::WMVisibleName | NET::WMDesktop | NET::WMWindowType | NET::WMState | NET::WMStrut | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMMoveResize | NET::WMFrameExtents | NET::WMPing , NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask | // No compositing window types here unless we support them also as managed window types 0 , NET::Modal | // NET::Sticky | // large desktops not supported (and probably never will be) NET::MaxVert | NET::MaxHoriz | NET::Shaded | NET::SkipTaskbar | NET::KeepAbove | // NET::StaysOnTop | the same like KeepAbove NET::SkipPager | NET::Hidden | NET::FullScreen | NET::KeepBelow | NET::DemandsAttention | 0 , NET::WM2UserTime | NET::WM2StartupId | NET::WM2AllowedActions | NET::WM2RestackWindow | NET::WM2MoveResizeWindow | NET::WM2ExtendedStrut | NET::WM2KDETemporaryRules | NET::WM2ShowingDesktop | NET::WM2DesktopLayout | NET::WM2FullPlacement | 0 , NET::ActionMove | NET::ActionResize | NET::ActionMinimize | NET::ActionShade | // NET::ActionStick | // Sticky state is not supported NET::ActionMaxVert | NET::ActionMaxHoriz | NET::ActionFullScreen | NET::ActionChangeDesktop | NET::ActionClose | 0 , }; QX11Info info; rootInfo = new RootInfo( this, display(), supportWindow->winId(), "KWin", protocols, 5, info.screen() ); loadDesktopSettings(); updateDesktopLayout(); // extra NETRootInfo instance in Client mode is needed to get the values of the properties NETRootInfo client_info( display(), NET::ActiveWindow | NET::CurrentDesktop ); int initial_desktop; if( !kapp->isSessionRestored()) initial_desktop = client_info.currentDesktop(); else { KConfigGroup group( kapp->sessionConfig(), "Session" ); initial_desktop = group.readEntry( "desktop", 1 ); } if( !setCurrentDesktop( initial_desktop )) setCurrentDesktop( 1 ); // now we know how many desktops we'll, thus, we initialize the positioning object initPositioning = new Placement(this); reconfigureTimer.setSingleShot( true ); updateToolWindowsTimer.setSingleShot( true ); connect(&reconfigureTimer, SIGNAL(timeout()), this, SLOT(slotReconfigure())); connect( &updateToolWindowsTimer, SIGNAL( timeout()), this, SLOT( slotUpdateToolWindows())); connect( &compositeTimer, SIGNAL( timeout()), SLOT( performCompositing())); connect(KGlobalSettings::self(), SIGNAL(appearanceChanged()), this, SLOT(reconfigure())); connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), this, SLOT(slotSettingsChanged(int))); connect(KGlobalSettings::self(), SIGNAL(blockShortcuts(int)), this, SLOT(slotBlockShortcuts(int))); active_client = NULL; rootInfo->setActiveWindow( None ); focusToNull(); if( !kapp->isSessionRestored()) ++block_focus; // because it will be set below char nm[ 100 ]; sprintf( nm, "_KDE_TOPMENU_OWNER_S%d", DefaultScreen( display())); Atom topmenu_atom = XInternAtom( display(), nm, False ); topmenu_selection = new KSelectionOwner( topmenu_atom ); topmenu_watcher = new KSelectionWatcher( topmenu_atom ); // TODO grabXServer(); - where exactly put this? topmenu selection claiming down belong must be before { // begin updates blocker block StackingUpdatesBlocker blocker( this ); if( options->topMenuEnabled() && topmenu_selection->claim( false )) setupTopMenuHandling(); // this can call updateStackingOrder() else lostTopMenuSelection(); unsigned int i, nwins; Window root_return, parent_return, *wins; XQueryTree(display(), rootWindow(), &root_return, &parent_return, &wins, &nwins); for (i = 0; i < nwins; i++) { XWindowAttributes attr; XGetWindowAttributes(display(), wins[i], &attr); if (attr.override_redirect ) { createUnmanaged( wins[ i ] ); continue; } if( topmenu_space && topmenu_space->winId() == wins[ i ] ) continue; if (attr.map_state != IsUnmapped) createClient( wins[i], true ); } if ( wins ) XFree((void *) wins); // propagate clients, will really happen at the end of the updates blocker block updateStackingOrder( true ); updateClientArea(); // NETWM spec says we have to set it to (0,0) if we don't support it NETPoint* viewports = new NETPoint[ number_of_desktops ]; rootInfo->setDesktopViewport( number_of_desktops, *viewports ); delete[] viewports; QRect geom = QApplication::desktop()->geometry(); NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo->setDesktopGeometry( -1, desktop_geometry ); setShowingDesktop( false ); } // end updates blocker block Client* new_active_client = NULL; if( !kapp->isSessionRestored()) { --block_focus; new_active_client = findClient( WindowMatchPredicate( client_info.activeWindow())); } if( new_active_client == NULL && activeClient() == NULL && should_get_focus.count() == 0 ) // no client activated in manage() { if( new_active_client == NULL ) - new_active_client = topClientOnDesktop( currentDesktop()); + new_active_client = topClientOnDesktop( currentDesktop(), -1 ); if( new_active_client == NULL && !desktops.isEmpty() ) new_active_client = findDesktop( true, currentDesktop()); } if( new_active_client != NULL ) activateClient( new_active_client ); // SELI TODO this won't work with unreasonable focus policies, // and maybe in rare cases also if the selected client doesn't // want focus workspaceInit = false; // TODO ungrabXServer() } Workspace::~Workspace() { finishCompositing(); blockStackingUpdates( true ); // TODO grabXServer(); // use stacking_order, so that kwin --replace keeps stacking order for( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it ) { // only release the window (*it)->releaseWindow( true ); // No removeClient() is called, it does more than just removing. // However, remove from some lists to e.g. prevent performTransiencyCheck() // from crashing. clients.removeAll( *it ); desktops.removeAll( *it ); } for( UnmanagedList::ConstIterator it = unmanaged.begin(); it != unmanaged.end(); ++it ) (*it)->release(); delete tab_box; delete popupinfo; discardPopup(); XDeleteProperty(display(), rootWindow(), atoms->kwin_running); writeWindowRules(); KGlobal::config()->sync(); delete rootInfo; delete supportWindow; delete mgr; delete startup; delete initPositioning; delete topmenu_watcher; delete topmenu_selection; delete topmenu_space; delete client_keys_dialog; while( !rules.isEmpty()) { delete rules.front(); rules.pop_front(); } foreach ( SessionInfo* s, session ) delete s; XDestroyWindow( display(), null_focus_window ); // TODO ungrabXServer(); _self = 0; } Client* Workspace::createClient( Window w, bool is_mapped ) { StackingUpdatesBlocker blocker( this ); Client* c = new Client( this ); if( !c->manage( w, is_mapped )) { Client::deleteClient( c, Allowed ); return NULL; } addClient( c, Allowed ); if( scene ) scene->windowAdded( c ); if( effects ) static_cast(effects)->windowAdded( c->effectWindow()); return c; } Unmanaged* Workspace::createUnmanaged( Window w ) { if( w == overlay ) return NULL; Unmanaged* c = new Unmanaged( this ); if( !c->track( w )) { Unmanaged::deleteUnmanaged( c, Allowed ); return NULL; } addUnmanaged( c, Allowed ); if( scene ) scene->windowAdded( c ); if( effects ) static_cast(effects)->windowAdded( c->effectWindow()); return c; } void Workspace::addClient( Client* c, allowed_t ) { Group* grp = findGroup( c->window()); if( grp != NULL ) grp->gotLeader( c ); if ( c->isDesktop() ) { desktops.append( c ); if( active_client == NULL && should_get_focus.isEmpty() && c->isOnCurrentDesktop()) requestFocus( c ); // CHECKME? make sure desktop is active after startup if there's no other window active } else { updateFocusChains( c, FocusChainUpdate ); // add to focus chain if not already there clients.append( c ); } if( !unconstrained_stacking_order.contains( c )) unconstrained_stacking_order.append( c ); // raise if it hasn't got any stacking position yet if( !stacking_order.contains( c )) // it'll be updated later, and updateToolWindows() requires stacking_order.append( c ); // c to be in stacking_order if( c->isTopMenu()) addTopMenu( c ); updateClientArea(); // this cannot be in manage(), because the client got added only now updateClientLayer( c ); if( c->isDesktop()) { raiseClient( c ); // if there's no active client, make this desktop the active one if( activeClient() == NULL && should_get_focus.count() == 0 ) activateClient( findDesktop( true, currentDesktop())); } c->checkActiveModal(); checkTransients( c->window()); // SELI does this really belong here? updateStackingOrder( true ); // propagate new client if( c->isUtility() || c->isMenu() || c->isToolbar()) updateToolWindows( true ); checkNonExistentClients(); if( tab_grab ) tab_box->reset( true ); } void Workspace::addUnmanaged( Unmanaged* c, allowed_t ) { unmanaged.append( c ); } /* Destroys the client \a c */ void Workspace::removeClient( Client* c, allowed_t ) { if (c == active_popup_client) closeActivePopup(); if( client_keys_client == c ) setupWindowShortcutDone( false ); if( !c->shortcut().isEmpty()) c->setShortcut( QString() ); // remove from client_keys if( c->isDialog()) Notify::raise( Notify::TransDelete ); if( c->isNormalWindow()) Notify::raise( Notify::Delete ); if( tab_grab ) { if( tab_box->currentClient() == c ) tab_box->nextPrev( true ); } Q_ASSERT( clients.contains( c ) || desktops.contains( c )); clients.removeAll( c ); desktops.removeAll( c ); unconstrained_stacking_order.removeAll( c ); stacking_order.removeAll( c ); for( int i = 1; i <= numberOfDesktops(); ++i ) focus_chain[ i ].removeAll( c ); global_focus_chain.removeAll( c ); attention_chain.removeAll( c ); showing_desktop_clients.removeAll( c ); if( c->isTopMenu()) removeTopMenu( c ); Group* group = findGroup( c->window()); if( group != NULL ) group->lostLeader(); if ( c == most_recently_raised ) most_recently_raised = 0; should_get_focus.removeAll( c ); Q_ASSERT( c != active_client ); if ( c == last_active_client ) last_active_client = 0; if( c == pending_take_activity ) pending_take_activity = NULL; if( c == delayfocus_client ) cancelDelayFocus(); updateStackingOrder( true ); if( tab_grab ) tab_box->reset( true ); updateClientArea(); } void Workspace::removeUnmanaged( Unmanaged* c, allowed_t ) { assert( unmanaged.contains( c )); unmanaged.removeAll( c ); } void Workspace::addDeleted( Deleted* c, allowed_t ) { assert( !deleted.contains( c )); deleted.append( c ); } void Workspace::removeDeleted( Deleted* c, allowed_t ) { assert( deleted.contains( c )); if( scene ) scene->windowDeleted( c ); if( effects ) static_cast(effects)->windowDeleted( c->effectWindow()); deleted.removeAll( c ); } void Workspace::updateFocusChains( Client* c, FocusChainChange change ) { if( !c->wantsTabFocus()) // doesn't want tab focus, remove { for( int i=1; i<= numberOfDesktops(); ++i ) focus_chain[i].removeAll(c); global_focus_chain.removeAll( c ); return; } if(c->desktop() == NET::OnAllDesktops) { //now on all desktops, add it to focus_chains it is not already in for( int i=1; i<= numberOfDesktops(); i++) { // making first/last works only on current desktop, don't affect all desktops if( i == currentDesktop() && ( change == FocusChainMakeFirst || change == FocusChainMakeLast )) { focus_chain[ i ].removeAll( c ); if( change == FocusChainMakeFirst ) focus_chain[ i ].append( c ); else focus_chain[ i ].prepend( c ); } else if( !focus_chain[ i ].contains( c )) { // add it after the active one if( active_client != NULL && active_client != c && !focus_chain[ i ].isEmpty() && focus_chain[ i ].last() == active_client ) focus_chain[ i ].insert( focus_chain[ i ].size() - 1, c ); else focus_chain[ i ].append( c ); // otherwise add as the first one } } } else //now only on desktop, remove it anywhere else { for( int i=1; i<= numberOfDesktops(); i++) { if( i == c->desktop()) { if( change == FocusChainMakeFirst ) { focus_chain[ i ].removeAll( c ); focus_chain[ i ].append( c ); } else if( change == FocusChainMakeLast ) { focus_chain[ i ].removeAll( c ); focus_chain[ i ].prepend( c ); } else if( !focus_chain[ i ].contains( c )) { // add it after the active one if( active_client != NULL && active_client != c && !focus_chain[ i ].isEmpty() && focus_chain[ i ].last() == active_client ) focus_chain[ i ].insert( focus_chain[ i ].size() - 1, c ); else focus_chain[ i ].append( c ); // otherwise add as the first one } } else focus_chain[ i ].removeAll( c ); } } if( change == FocusChainMakeFirst ) { global_focus_chain.removeAll( c ); global_focus_chain.append( c ); } else if( change == FocusChainMakeLast ) { global_focus_chain.removeAll( c ); global_focus_chain.prepend( c ); } else if( !global_focus_chain.contains( c )) { // add it after the active one if( active_client != NULL && active_client != c && !global_focus_chain.isEmpty() && global_focus_chain.last() == active_client ) global_focus_chain.insert( global_focus_chain.size() - 1, c ); else global_focus_chain.append( c ); // otherwise add as the first one } } void Workspace::updateCurrentTopMenu() { if( !managingTopMenus()) return; // toplevel menubar handling Client* menubar = 0; bool block_desktop_menubar = false; if( active_client ) { // show the new menu bar first... Client* menu_client = active_client; for(;;) { if( menu_client->isFullScreen()) block_desktop_menubar = true; for( ClientList::ConstIterator it = menu_client->transients().begin(); it != menu_client->transients().end(); ++it ) if( (*it)->isTopMenu()) { menubar = *it; break; } if( menubar != NULL || !menu_client->isTransient()) break; if( menu_client->isModal() || menu_client->transientFor() == NULL ) break; // don't use mainwindow's menu if this is modal or group transient menu_client = menu_client->transientFor(); } if( !menubar ) { // try to find any topmenu from the application (#72113) for( ClientList::ConstIterator it = active_client->group()->members().begin(); it != active_client->group()->members().end(); ++it ) if( (*it)->isTopMenu()) { menubar = *it; break; } } } if( !menubar && !block_desktop_menubar && options->desktopTopMenu()) { // Find the menubar of the desktop Client* desktop = findDesktop( true, currentDesktop()); if( desktop != NULL ) { for( ClientList::ConstIterator it = desktop->transients().begin(); it != desktop->transients().end(); ++it ) if( (*it)->isTopMenu()) { menubar = *it; break; } } // TODO to be cleaned app with window grouping // Without qt-copy patch #0009, the topmenu and desktop are not in the same group, // thus the topmenu is not transient for it :-/. if( menubar == NULL ) { for( ClientList::ConstIterator it = topmenus.begin(); it != topmenus.end(); ++it ) if( (*it)->wasOriginallyGroupTransient()) // kdesktop's topmenu has WM_TRANSIENT_FOR { // set pointing to the root window menubar = *it; // to recognize it here break; // Also, with the xroot hack in kdesktop, } // there's no NET::Desktop window to be transient for } } // kDebug() << "CURRENT TOPMENU:" << menubar << ":" << active_client; if ( menubar ) { if( active_client && !menubar->isOnDesktop( active_client->desktop())) menubar->setDesktop( active_client->desktop()); menubar->hideClient( false ); topmenu_space->hide(); // make it appear like it's been raised manually - it's in the Dock layer anyway, // and not raising it could mess up stacking order of topmenus within one application, // and thus break raising of mainclients in raiseClient() unconstrained_stacking_order.removeAll( menubar ); unconstrained_stacking_order.append( menubar ); } else if( !block_desktop_menubar ) { // no topmenu active - show the space window, so that there's not empty space topmenu_space->show(); } // ... then hide the other ones. Avoids flickers. for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) { if( (*it)->isTopMenu() && (*it) != menubar ) (*it)->hideClient( true ); } } void Workspace::updateToolWindows( bool also_hide ) { // TODO what if Client's transiency/group changes? should this be called too? (I'm paranoid, am I not?) if( !options->hideUtilityWindowsForInactive ) { for( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it ) (*it)->hideClient( false ); return; } const Group* group = NULL; const Client* client = active_client; // Go up in transiency hiearchy, if the top is found, only tool transients for the top mainwindow // will be shown; if a group transient is group, all tools in the group will be shown while( client != NULL ) { if( !client->isTransient()) break; if( client->groupTransient()) { group = client->group(); break; } client = client->transientFor(); } // use stacking order only to reduce flicker, it doesn't matter if block_stacking_updates == 0, // i.e. if it's not up to date // SELI but maybe it should - what if a new client has been added that's not in stacking order yet? ClientList to_show, to_hide; for( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it ) { if( (*it)->isUtility() || (*it)->isMenu() || (*it)->isToolbar()) { bool show = true; if( !(*it)->isTransient()) { if( (*it)->group()->members().count() == 1 ) // has its own group, keep always visible show = true; else if( client != NULL && (*it)->group() == client->group()) show = true; else show = false; } else { if( group != NULL && (*it)->group() == group ) show = true; else if( client != NULL && client->hasTransient( (*it), true )) show = true; else show = false; } if( !show && also_hide ) { const ClientList mainclients = (*it)->mainClients(); // don't hide utility windows which are standalone(?) or // have e.g. kicker as mainwindow if( mainclients.isEmpty()) show = true; for( ClientList::ConstIterator it2 = mainclients.begin(); it2 != mainclients.end(); ++it2 ) { if( (*it2)->isSpecialWindow()) show = true; } if( !show ) to_hide.append( *it ); } if( show ) to_show.append( *it ); } } // first show new ones, then hide for( int i = to_show.size() - 1; i >= 0; --i ) //from topmost // TODO since this is in stacking order, the order of taskbar entries changes :( to_show.at( i )->hideClient( false ); if( also_hide ) { for( ClientList::ConstIterator it = to_hide.begin(); it != to_hide.end(); ++it ) // from bottommost (*it)->hideClient( true ); updateToolWindowsTimer.stop(); } else // setActiveClient() is after called with NULL client, quickly followed { // by setting a new client, which would result in flickering updateToolWindowsTimer.start( 50 ); } } void Workspace::slotUpdateToolWindows() { updateToolWindows( true ); } /*! Updates the current colormap according to the currently active client */ void Workspace::updateColormap() { Colormap cmap = default_colormap; if ( activeClient() && activeClient()->colormap() != None ) cmap = activeClient()->colormap(); if ( cmap != installed_colormap ) { XInstallColormap(display(), cmap ); installed_colormap = cmap; } } void Workspace::slotReloadConfig() { reconfigure(); } void Workspace::reconfigure() { reconfigureTimer.start( 200 ); } // This DBUS call is used by the compositing kcm. Since the reconfigure() // DBUS call delays the actual reconfiguring, it is not possible to immediately // call compositingActive(). Therefore the kcm will instead call this to ensure // the reconfiguring has already happened. bool Workspace::waitForCompositingSetup() { if( !reconfigureTimer.isActive()) return false; reconfigureTimer.stop(); slotReconfigure(); return compositingActive(); } void Workspace::slotSettingsChanged(int category) { kDebug(1212) << "Workspace::slotSettingsChanged()"; if( category == KGlobalSettings::SETTINGS_SHORTCUTS ) readShortcuts(); } /*! Reread settings */ KWIN_PROCEDURE( CheckBorderSizesProcedure, Client, cl->checkBorderSizes( true ) ); void Workspace::slotReconfigure() { kDebug(1212) << "Workspace::slotReconfigure()"; reconfigureTimer.stop(); if( options->electricBorders() == Options::ElectricAlways ) reserveElectricBorderSwitching( false ); KGlobal::config()->reparseConfiguration(); unsigned long changed = options->updateSettings(); tab_box->reconfigure(); popupinfo->reconfigure(); initPositioning->reinitCascading( 0 ); readShortcuts(); forEachClient( CheckIgnoreFocusStealingProcedure()); updateToolWindows( true ); if( mgr->reset( changed )) { // decorations need to be recreated #if 0 // This actually seems to make things worse now QWidget curtain; curtain.setBackgroundMode( NoBackground ); curtain.setGeometry( QApplication::desktop()->geometry() ); curtain.show(); #endif for( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it ) { (*it)->updateDecoration( true, true ); } mgr->destroyPreviousPlugin(); } else { forEachClient( CheckBorderSizesProcedure()); foreach( Client* c, clients ) c->repaintDecoration(); } if( options->electricBorders() == Options::ElectricAlways ) reserveElectricBorderSwitching( true ); updateElectricBorders(); if( options->topMenuEnabled() && !managingTopMenus()) { if( topmenu_selection->claim( false )) setupTopMenuHandling(); else lostTopMenuSelection(); } else if( !options->topMenuEnabled() && managingTopMenus()) { topmenu_selection->release(); lostTopMenuSelection(); } topmenu_height = 0; // invalidate used menu height if( managingTopMenus()) { updateTopMenuGeometry(); updateCurrentTopMenu(); } if( options->useCompositing && !compositingSuspended ) { setupCompositing(); if( effects ) // setupCompositing() may fail effects->reconfigure(); addRepaintFull(); } else finishCompositing(); loadWindowRules(); for( ClientList::Iterator it = clients.begin(); it != clients.end(); ++it ) { (*it)->setupWindowRules( true ); (*it)->applyWindowRules(); discardUsedWindowRules( *it, false ); } } void Workspace::slotReinitCompositing() { // Reparse config. Config options will be reloaded by setupCompositing() KGlobal::config()->reparseConfiguration(); options->updateSettings(); // Stop any current compositing finishCompositing(); // And start new one setupCompositing(); if( effects ) // setupCompositing() may fail effects->reconfigure(); } void Workspace::loadDesktopSettings() { KSharedConfig::Ptr c = KGlobal::config(); QString groupname; if (screen_number == 0) groupname = "Desktops"; else groupname.sprintf("Desktops-screen-%d", screen_number); KConfigGroup group(c,groupname); int n = group.readEntry("Number", 4); number_of_desktops = n; workarea.clear(); workarea.resize( n + 1 ); screenarea.clear(); rootInfo->setNumberOfDesktops( number_of_desktops ); desktop_focus_chain.resize( n ); // make it +1, so that it can be accessed as [1..numberofdesktops] focus_chain.resize( n + 1 ); for(int i = 1; i <= n; i++) { QString s = group.readEntry(QString("Name_%1").arg(i), i18n("Desktop %1", i)); rootInfo->setDesktopName( i, s.toUtf8().data() ); desktop_focus_chain[i-1] = i; } } void Workspace::saveDesktopSettings() { KSharedConfig::Ptr c = KGlobal::config(); QString groupname; if (screen_number == 0) groupname = "Desktops"; else groupname.sprintf("Desktops-screen-%d", screen_number); KConfigGroup group(c,groupname); group.writeEntry("Number", number_of_desktops ); for(int i = 1; i <= number_of_desktops; i++) { QString s = desktopName( i ); QString defaultvalue = i18n("Desktop %1", i); if ( s.isEmpty() ) { s = defaultvalue; rootInfo->setDesktopName( i, s.toUtf8().data() ); } if (s != defaultvalue) { group.writeEntry( QString("Name_%1").arg(i), s ); } else { QString currentvalue = group.readEntry(QString("Name_%1").arg(i), QString()); if (currentvalue != defaultvalue) group.writeEntry( QString("Name_%1").arg(i), "" ); } } } QStringList Workspace::configModules(bool controlCenter) { QStringList args; args << "kwindecoration"; if (controlCenter) args << "kwinoptions"; else if (KAuthorized::authorizeControlModule("kde-kwinoptions.desktop")) args << "kwinactions" << "kwinfocus" << "kwinmoving" << "kwinadvanced" << "kwinrules" << "kwincompositing"; return args; } void Workspace::configureWM() { KToolInvocation::kdeinitExec( "kcmshell4", configModules(false) ); } /*! avoids managing a window with title \a title */ void Workspace::doNotManage( const QString &title ) { doNotManageList.append( title ); } /*! Hack for java applets */ bool Workspace::isNotManaged( const QString& title ) { for ( QStringList::Iterator it = doNotManageList.begin(); it != doNotManageList.end(); ++it ) { QRegExp r( (*it) ); if (r.indexIn(title) != -1) { doNotManageList.erase( it ); return true; } } return false; } /*! Refreshes all the client windows */ void Workspace::refresh() { QWidget w( NULL, Qt::X11BypassWindowManagerHint ); w.setGeometry( QApplication::desktop()->geometry() ); w.show(); w.hide(); QApplication::flush(); } /*! During virt. desktop switching, desktop areas covered by windows that are going to be hidden are first obscured by new windows with no background ( i.e. transparent ) placed right below the windows. These invisible windows are removed after the switch is complete. Reduces desktop ( wallpaper ) repaints during desktop switching */ class ObscuringWindows { public: ~ObscuringWindows(); void create( Client* c ); private: QList obscuring_windows; static QList* cached; static unsigned int max_cache_size; }; QList* ObscuringWindows::cached = 0; unsigned int ObscuringWindows::max_cache_size = 0; void ObscuringWindows::create( Client* c ) { if( compositing()) // not needed with compositing return; if( cached == 0 ) cached = new QList; Window obs_win; XWindowChanges chngs; int mask = CWSibling | CWStackMode; if( cached->count() > 0 ) { cached->removeAll( obs_win = cached->first()); chngs.x = c->x(); chngs.y = c->y(); chngs.width = c->width(); chngs.height = c->height(); mask |= CWX | CWY | CWWidth | CWHeight; } else { XSetWindowAttributes a; a.background_pixmap = None; a.override_redirect = True; obs_win = XCreateWindow( display(), rootWindow(), c->x(), c->y(), c->width(), c->height(), 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixmap | CWOverrideRedirect, &a ); } chngs.sibling = c->frameId(); chngs.stack_mode = Below; XConfigureWindow( display(), obs_win, mask, &chngs ); XMapWindow( display(), obs_win ); obscuring_windows.append( obs_win ); } ObscuringWindows::~ObscuringWindows() { max_cache_size = qMax( ( int )max_cache_size, obscuring_windows.count() + 4 ) - 1; for( QList::ConstIterator it = obscuring_windows.begin(); it != obscuring_windows.end(); ++it ) { XUnmapWindow( display(), *it ); if( cached->count() < ( int )max_cache_size ) cached->prepend( *it ); else XDestroyWindow( display(), *it ); } } /*! Sets the current desktop to \a new_desktop Shows/Hides windows according to the stacking order and finally propages the new desktop to the world */ bool Workspace::setCurrentDesktop( int new_desktop ) { if (new_desktop < 1 || new_desktop > number_of_desktops ) return false; closeActivePopup(); ++block_focus; // TODO Q_ASSERT( block_stacking_updates == 0 ); // make sure stacking_order is up to date StackingUpdatesBlocker blocker( this ); int old_desktop = current_desktop; if (new_desktop != current_desktop) { ++block_showing_desktop; /* optimized Desktop switching: unmapping done from back to front mapping done from front to back => less exposure events */ Notify::raise((Notify::Event) (Notify::DesktopChange+new_desktop)); ObscuringWindows obs_wins; current_desktop = new_desktop; // change the desktop (so that Client::updateVisibility() works) for ( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it) if ( !(*it)->isOnDesktop( new_desktop ) && (*it) != movingClient ) { if( (*it)->isShown( true ) && (*it)->isOnDesktop( old_desktop )) obs_wins.create( *it ); (*it)->updateVisibility(); } rootInfo->setCurrentDesktop( current_desktop ); // now propagate the change, after hiding, before showing if( movingClient && !movingClient->isOnDesktop( new_desktop )) movingClient->setDesktop( new_desktop ); for( int i = stacking_order.size() - 1; i >= 0 ; --i ) if ( stacking_order.at( i )->isOnDesktop( new_desktop ) ) stacking_order.at( i )->updateVisibility(); --block_showing_desktop; if( showingDesktop()) // do this only after desktop change to avoid flicker resetShowingDesktop( false ); } // restore the focus on this desktop --block_focus; Client* c = 0; if ( options->focusPolicyIsReasonable()) { // Search in focus chain if ( movingClient != NULL && active_client == movingClient && focus_chain[currentDesktop()].contains( active_client ) && active_client->isShown( true ) && active_client->isOnCurrentDesktop()) { c = active_client; // the requestFocus below will fail, as the client is already active } if( !c ) { for( int i = focus_chain[ currentDesktop() ].size() - 1; i >= 0; --i ) { if( focus_chain[ currentDesktop() ].at( i )->isShown( false ) && focus_chain[ currentDesktop() ].at( i )->isOnCurrentDesktop()) { c = focus_chain[ currentDesktop() ].at( i ); break; } } } } //if "unreasonable focus policy" // and active_client is on_all_desktops and under mouse (hence == old_active_client), // conserve focus (thanks to Volker Schatz ) else if( active_client && active_client->isShown( true ) && active_client->isOnCurrentDesktop()) c = active_client; if( c == NULL && !desktops.isEmpty()) c = findDesktop( true, currentDesktop()); if( c != active_client ) setActiveClient( NULL, Allowed ); if ( c ) requestFocus( c ); else if( !desktops.isEmpty() ) requestFocus( findDesktop( true, currentDesktop())); else focusToNull(); updateCurrentTopMenu(); // Update focus chain: // If input: chain = { 1, 2, 3, 4 } and currentDesktop() = 3, // Output: chain = { 3, 1, 2, 4 }. // kDebug(1212) << QString("Switching to desktop #%1, at focus_chain index %2\n") // .arg(currentDesktop()).arg(desktop_focus_chain.find( currentDesktop() )); for( int i = desktop_focus_chain.indexOf( currentDesktop() ); i > 0; i-- ) desktop_focus_chain[i] = desktop_focus_chain[i-1]; desktop_focus_chain[0] = currentDesktop(); // QString s = "desktop_focus_chain[] = { "; // for( uint i = 0; i < desktop_focus_chain.size(); i++ ) // s += QString::number(desktop_focus_chain[i]) + ", "; // kDebug(1212) << s << "}\n"; if( old_desktop != 0 ) // not for the very first time popupinfo->showInfo( desktopName(currentDesktop()) ); if( effects != NULL && old_desktop != 0 && old_desktop != new_desktop ) static_cast(effects)->desktopChanged( old_desktop ); if( compositing()) addRepaintFull(); return true; } // called only from DCOP void Workspace::nextDesktop() { int desktop = currentDesktop() + 1; setCurrentDesktop(desktop > numberOfDesktops() ? 1 : desktop); } // called only from DCOP void Workspace::previousDesktop() { int desktop = currentDesktop() - 1; setCurrentDesktop(desktop > 0 ? desktop : numberOfDesktops()); } int Workspace::desktopToRight( int desktop, bool wrap ) const { int x,y; Qt::Orientation orientation; calcDesktopLayout( &x, &y, &orientation ); int dt = desktop-1; if (orientation == Qt::Vertical) { dt += y; if ( dt >= numberOfDesktops() ) { if ( wrap ) dt -= numberOfDesktops(); else return desktop; } } else { int d = (dt % x) + 1; if ( d >= x ) { if ( wrap ) d -= x; else return desktop; } dt = dt - (dt % x) + d; } return dt+1; } int Workspace::desktopToLeft( int desktop, bool wrap ) const { int x,y; Qt::Orientation orientation; calcDesktopLayout( &x, &y, &orientation ); int dt = desktop-1; if (orientation == Qt::Vertical) { dt -= y; if ( dt < 0 ) { if ( wrap ) dt += numberOfDesktops(); else return desktop; } } else { int d = (dt % x) - 1; if ( d < 0 ) { if ( wrap ) d += x; else return desktop; } dt = dt - (dt % x) + d; } return dt+1; } int Workspace::desktopUp( int desktop, bool wrap ) const { int x,y; Qt::Orientation orientation; calcDesktopLayout( &x, &y, &orientation); int dt = desktop-1; if (orientation == Qt::Horizontal) { dt -= x; if ( dt < 0 ) { if ( wrap ) dt += numberOfDesktops(); else return desktop; } } else { int d = (dt % y) - 1; if ( d < 0 ) { if ( wrap ) d += y; else return desktop; } dt = dt - (dt % y) + d; } return dt+1; } int Workspace::desktopDown( int desktop, bool wrap ) const { int x,y; Qt::Orientation orientation; calcDesktopLayout( &x, &y, &orientation); int dt = desktop-1; if (orientation == Qt::Horizontal) { dt += x; if ( dt >= numberOfDesktops() ) { if ( wrap ) dt -= numberOfDesktops(); else return desktop; } } else { int d = (dt % y) + 1; if ( d >= y ) { if ( wrap ) d -= y; else return desktop; } dt = dt - (dt % y) + d; } return dt+1; } /*! Sets the number of virtual desktops to \a n */ void Workspace::setNumberOfDesktops( int n ) { if ( n == number_of_desktops ) return; int old_number_of_desktops = number_of_desktops; number_of_desktops = n; if( currentDesktop() > numberOfDesktops()) setCurrentDesktop( numberOfDesktops()); // if increasing the number, do the resizing now, // otherwise after the moving of windows to still existing desktops if( old_number_of_desktops < number_of_desktops ) { rootInfo->setNumberOfDesktops( number_of_desktops ); NETPoint* viewports = new NETPoint[ number_of_desktops ]; rootInfo->setDesktopViewport( number_of_desktops, *viewports ); delete[] viewports; updateClientArea( true ); focus_chain.resize( number_of_desktops + 1 ); } // if the number of desktops decreased, move all // windows that would be hidden to the last visible desktop if( old_number_of_desktops > number_of_desktops ) { for( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) { if( !(*it)->isOnAllDesktops() && (*it)->desktop() > numberOfDesktops()) sendClientToDesktop( *it, numberOfDesktops(), true ); } } if( old_number_of_desktops > number_of_desktops ) { rootInfo->setNumberOfDesktops( number_of_desktops ); NETPoint* viewports = new NETPoint[ number_of_desktops ]; rootInfo->setDesktopViewport( number_of_desktops, *viewports ); delete[] viewports; updateClientArea( true ); focus_chain.resize( number_of_desktops + 1 ); } saveDesktopSettings(); // Resize and reset the desktop focus chain. desktop_focus_chain.resize( n ); for( int i = 0; i < (int)desktop_focus_chain.size(); i++ ) desktop_focus_chain[i] = i+1; } /*! Sends client \a c to desktop \a desk. Takes care of transients as well. */ void Workspace::sendClientToDesktop( Client* c, int desk, bool dont_activate ) { bool was_on_desktop = c->isOnDesktop( desk ) || c->isOnAllDesktops(); c->setDesktop( desk ); if ( c->desktop() != desk ) // no change or desktop forced return; desk = c->desktop(); // Client did range checking if ( c->isOnDesktop( currentDesktop() ) ) { if ( c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_desktop // for stickyness changes && !dont_activate ) requestFocus( c ); else restackClientUnderActive( c ); } else { raiseClient( c ); } ClientList transients_stacking_order = ensureStackingOrder( c->transients()); for( ClientList::ConstIterator it = transients_stacking_order.begin(); it != transients_stacking_order.end(); ++it ) sendClientToDesktop( *it, desk, dont_activate ); updateClientArea(); } int Workspace::numScreens() const { if( !options->xineramaEnabled ) return 1; return qApp->desktop()->numScreens(); } int Workspace::activeScreen() const { if( !options->xineramaEnabled ) return 0; if( !options->activeMouseScreen ) { if( activeClient() != NULL && !activeClient()->isOnScreen( active_screen )) return qApp->desktop()->screenNumber( activeClient()->geometry().center()); return active_screen; } return qApp->desktop()->screenNumber( cursorPos()); } // check whether a client moved completely out of what's considered the active screen, // if yes, set a new active screen void Workspace::checkActiveScreen( const Client* c ) { if( !options->xineramaEnabled ) return; if( !c->isActive()) return; if( !c->isOnScreen( active_screen )) active_screen = c->screen(); } // called e.g. when a user clicks on a window, set active screen to be the screen // where the click occurred void Workspace::setActiveScreenMouse( const QPoint &mousepos ) { if( !options->xineramaEnabled ) return; active_screen = qApp->desktop()->screenNumber( mousepos ); } QRect Workspace::screenGeometry( int screen ) const { if( !options->xineramaEnabled ) return qApp->desktop()->geometry(); return qApp->desktop()->screenGeometry( screen ); } int Workspace::screenNumber( const QPoint &pos ) const { if( !options->xineramaEnabled ) return 0; return qApp->desktop()->screenNumber( pos ); } void Workspace::sendClientToScreen( Client* c, int screen ) { if( c->screen() == screen ) // don't use isOnScreen(), that's true even when only partially return; GeometryUpdatesBlocker blocker( c ); QRect old_sarea = clientArea( MaximizeArea, c ); QRect sarea = clientArea( MaximizeArea, screen, c->desktop()); c->setGeometry( sarea.x() - old_sarea.x() + c->x(), sarea.y() - old_sarea.y() + c->y(), c->size().width(), c->size().height()); c->checkWorkspacePosition(); ClientList transients_stacking_order = ensureStackingOrder( c->transients()); for( ClientList::ConstIterator it = transients_stacking_order.begin(); it != transients_stacking_order.end(); ++it ) sendClientToScreen( *it, screen ); if( c->isActive()) active_screen = screen; } void Workspace::updateDesktopLayout() { // rootInfo->desktopLayoutCorner(); // I don't find this worth bothering, feel free to layoutOrientation = ( rootInfo->desktopLayoutOrientation() == NET::OrientationHorizontal ? Qt::Horizontal : Qt::Vertical ); layoutX = rootInfo->desktopLayoutColumnsRows().width(); layoutY = rootInfo->desktopLayoutColumnsRows().height(); if( layoutX == 0 && layoutY == 0 ) // not given, set default layout layoutY = 2; } void Workspace::calcDesktopLayout(int* xp, int* yp, Qt::Orientation* orientation) const { int x = layoutX; // <= 0 means compute it from the other and total number of desktops int y = layoutY; if((x <= 0) && (y > 0)) x = (numberOfDesktops()+y-1) / y; else if((y <=0) && (x > 0)) y = (numberOfDesktops()+x-1) / x; if(x <=0) x = 1; if (y <= 0) y = 1; *xp = x; *yp = y; *orientation = layoutOrientation; } void Workspace::killWindowId( Window window_to_kill ) { if( window_to_kill == None ) return; Window window = window_to_kill; Client* client = NULL; for(;;) { client = findClient( FrameIdMatchPredicate( window )); if( client != NULL ) // found the client break; Window parent, root; Window* children; unsigned int children_count; XQueryTree( display(), window, &root, &parent, &children, &children_count ); if( children != NULL ) XFree( children ); if( window == root ) // we didn't find the client, probably an override-redirect window break; window = parent; // go up } if( client != NULL ) client->killWindow(); else XKillClient( display(), window_to_kill ); } void Workspace::sendPingToWindow( Window window, Time timestamp ) { rootInfo->sendPing( window, timestamp ); } void Workspace::sendTakeActivity( Client* c, Time timestamp, long flags ) { rootInfo->takeActivity( c->window(), timestamp, flags ); pending_take_activity = c; } /*! Takes a screenshot of the current window and puts it in the clipboard. */ void Workspace::slotGrabWindow() { if ( active_client ) { QPixmap snapshot = QPixmap::grabWindow( active_client->frameId() ); //No XShape - no work. if( Extensions::shapeAvailable()) { //As the first step, get the mask from XShape. int count, order; XRectangle* rects = XShapeGetRectangles( display(), active_client->frameId(), ShapeBounding, &count, &order); //The ShapeBounding region is the outermost shape of the window; //ShapeBounding - ShapeClipping is defined to be the border. //Since the border area is part of the window, we use bounding // to limit our work region if (rects) { //Create a QRegion from the rectangles describing the bounding mask. QRegion contents; for (int pos = 0; pos < count; pos++) contents += QRegion(rects[pos].x, rects[pos].y, rects[pos].width, rects[pos].height); XFree(rects); //Create the bounding box. QRegion bbox(0, 0, snapshot.width(), snapshot.height()); //Get the masked away area. QRegion maskedAway = bbox - contents; QVector maskedAwayRects = maskedAway.rects(); //Construct a bitmap mask from the rectangles QBitmap mask( snapshot.width(), snapshot.height()); QPainter p(&mask); p.fillRect(0, 0, mask.width(), mask.height(), Qt::color1); for (int pos = 0; pos < maskedAwayRects.count(); pos++) p.fillRect(maskedAwayRects[pos], Qt::color0); p.end(); snapshot.setMask(mask); } } QClipboard *cb = QApplication::clipboard(); cb->setPixmap( snapshot ); } else slotGrabDesktop(); } /*! Takes a screenshot of the whole desktop and puts it in the clipboard. */ void Workspace::slotGrabDesktop() { QPixmap p = QPixmap::grabWindow( rootWindow() ); QClipboard *cb = QApplication::clipboard(); cb->setPixmap( p ); } /*! Invokes keyboard mouse emulation */ void Workspace::slotMouseEmulation() { if ( mouse_emulation ) { ungrabXKeyboard(); mouse_emulation = false; return; } if( grabXKeyboard()) { mouse_emulation = true; mouse_emulation_state = 0; mouse_emulation_window = 0; } } /*! Returns the child window under the mouse and activates the respective client if necessary. Auxiliary function for the mouse emulation system. */ WId Workspace::getMouseEmulationWindow() { Window root; Window child = rootWindow(); int root_x, root_y, lx, ly; uint state; Window w; Client * c = 0; do { w = child; if (!c) c = findClient( FrameIdMatchPredicate( w )); XQueryPointer( display(), w, &root, &child, &root_x, &root_y, &lx, &ly, &state ); } while ( child != None && child != w ); if ( c && !c->isActive() ) activateClient( c ); return (WId) w; } /*! Sends a faked mouse event to the specified window. Returns the new button state. */ unsigned int Workspace::sendFakedMouseEvent( const QPoint &pos, WId w, MouseEmulation type, int button, unsigned int state ) { if ( !w ) return state; QWidget* widget = QWidget::find( w ); if ( (!widget || qobject_cast(widget)) && !findClient( WindowMatchPredicate( w )) ) { int x, y; Window xw; XTranslateCoordinates( display(), rootWindow(), w, pos.x(), pos.y(), &x, &y, &xw ); if ( type == EmuMove ) { // motion notify events XEvent e; e.type = MotionNotify; e.xmotion.window = w; e.xmotion.root = rootWindow(); e.xmotion.subwindow = w; e.xmotion.time = xTime(); e.xmotion.x = x; e.xmotion.y = y; e.xmotion.x_root = pos.x(); e.xmotion.y_root = pos.y(); e.xmotion.state = state; e.xmotion.is_hint = NotifyNormal; XSendEvent( display(), w, true, ButtonMotionMask, &e ); } else { XEvent e; e.type = type == EmuRelease ? ButtonRelease : ButtonPress; e.xbutton.window = w; e.xbutton.root = rootWindow(); e.xbutton.subwindow = w; e.xbutton.time = xTime(); e.xbutton.x = x; e.xbutton.y = y; e.xbutton.x_root = pos.x(); e.xbutton.y_root = pos.y(); e.xbutton.state = state; e.xbutton.button = button; XSendEvent( display(), w, true, ButtonPressMask, &e ); if ( type == EmuPress ) { switch ( button ) { case 2: state |= Button2Mask; break; case 3: state |= Button3Mask; break; default: // 1 state |= Button1Mask; break; } } else { switch ( button ) { case 2: state &= ~Button2Mask; break; case 3: state &= ~Button3Mask; break; default: // 1 state &= ~Button1Mask; break; } } } } return state; } /*! Handles keypress event during mouse emulation */ bool Workspace::keyPressMouseEmulation( XKeyEvent& ev ) { int kc = XKeycodeToKeysym(display(), ev.keycode, 0); int km = ev.state & (ControlMask | Mod1Mask | ShiftMask); bool is_control = km & ControlMask; bool is_alt = km & Mod1Mask; bool is_shift = km & ShiftMask; int delta = is_control?1:is_alt?32:8; QPoint pos = cursorPos(); switch ( kc ) { case XK_Left: case XK_KP_Left: pos.rx() -= delta; break; case XK_Right: case XK_KP_Right: pos.rx() += delta; break; case XK_Up: case XK_KP_Up: pos.ry() -= delta; break; case XK_Down: case XK_KP_Down: pos.ry() += delta; break; case XK_F1: if ( !mouse_emulation_state ) mouse_emulation_window = getMouseEmulationWindow(); if ( (mouse_emulation_state & Button1Mask) == 0 ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuPress, Button1, mouse_emulation_state ); if ( !is_shift ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button1, mouse_emulation_state ); break; case XK_F2: if ( !mouse_emulation_state ) mouse_emulation_window = getMouseEmulationWindow(); if ( (mouse_emulation_state & Button2Mask) == 0 ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuPress, Button2, mouse_emulation_state ); if ( !is_shift ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button2, mouse_emulation_state ); break; case XK_F3: if ( !mouse_emulation_state ) mouse_emulation_window = getMouseEmulationWindow(); if ( (mouse_emulation_state & Button3Mask) == 0 ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuPress, Button3, mouse_emulation_state ); if ( !is_shift ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button3, mouse_emulation_state ); break; case XK_Return: case XK_space: case XK_KP_Enter: case XK_KP_Space: { if ( !mouse_emulation_state ) { // nothing was pressed, fake a LMB click mouse_emulation_window = getMouseEmulationWindow(); mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuPress, Button1, mouse_emulation_state ); mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button1, mouse_emulation_state ); } else { // release all if ( mouse_emulation_state & Button1Mask ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button1, mouse_emulation_state ); if ( mouse_emulation_state & Button2Mask ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button2, mouse_emulation_state ); if ( mouse_emulation_state & Button3Mask ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button3, mouse_emulation_state ); } } // fall through case XK_Escape: ungrabXKeyboard(); mouse_emulation = false; return true; default: return false; } QCursor::setPos( pos ); if ( mouse_emulation_state ) mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuMove, 0, mouse_emulation_state ); return true; } //Delayed focus functions void Workspace::delayFocus() { requestFocus( delayfocus_client ); cancelDelayFocus(); } void Workspace::requestDelayFocus( Client* c ) { delayfocus_client = c; delete delayFocusTimer; delayFocusTimer = new QTimer( this ); connect( delayFocusTimer, SIGNAL( timeout() ), this, SLOT( delayFocus() ) ); delayFocusTimer->setSingleShot( true ); delayFocusTimer->start( options->delayFocusInterval ); } void Workspace::cancelDelayFocus() { delete delayFocusTimer; delayFocusTimer = 0; } // Electric Borders //========================================================================// // Electric Border Window management. Electric borders allow a user // to change the virtual desktop or activate another features // by moving the mouse pointer to the borders or corners. // Technically this is done with input only windows. void Workspace::updateElectricBorders() { electric_time_first = xTime(); electric_time_last = xTime(); electric_current_border = ElectricNone; QRect r = QApplication::desktop()->geometry(); electricTop = r.top(); electricBottom = r.bottom(); electricLeft = r.left(); electricRight = r.right(); for( int pos = 0; pos < ELECTRIC_COUNT; ++pos ) { if( electric_reserved[ pos ] == 0 ) { if( electric_windows[ pos ] != None ) XDestroyWindow( display(), electric_windows[ pos ] ); electric_windows[ pos ] = None; continue; } if( electric_windows[ pos ] != None ) continue; XSetWindowAttributes attributes; attributes.override_redirect = True; attributes.event_mask = EnterWindowMask | LeaveWindowMask; unsigned long valuemask = CWOverrideRedirect | CWEventMask; int xywh[ ELECTRIC_COUNT ][ 4 ] = { { r.left() + 1, r.top(), r.width() - 2, 1 }, // top { r.right(), r.top(), 1, 1 }, // topright { r.right(), r.top() + 1, 1, r.height() - 2 }, // etc. { r.right(), r.bottom(), 1, 1 }, { r.left() + 1, r.bottom(), r.width() - 2, 1 }, { r.left(), r.bottom(), 1, 1 }, { r.left(), r.top() + 1, 1, r.height() - 2 }, { r.left(), r.top(), 1, 1 } }; electric_windows[ pos ] = XCreateWindow( display(), rootWindow(), xywh[ pos ][ 0 ], xywh[ pos ][ 1 ], xywh[ pos ][ 2 ], xywh[ pos ][ 3 ], 0, CopyFromParent, InputOnly, CopyFromParent, valuemask, &attributes ); XMapWindow( display(), electric_windows[ pos ]); // Set XdndAware on the windows, so that DND enter events are received (#86998) Atom version = 4; // XDND version XChangeProperty( display(), electric_windows[ pos ], atoms->xdnd_aware, XA_ATOM, 32, PropModeReplace, ( unsigned char* )&version, 1 ); } } void Workspace::destroyElectricBorders() { for( int pos = 0; pos < ELECTRIC_COUNT; ++pos ) { if( electric_windows[ pos ] != None ) XDestroyWindow( display(), electric_windows[ pos ] ); electric_windows[ pos ] = None; } } void Workspace::reserveElectricBorderSwitching( bool reserve ) { for( int pos = 0; pos < ELECTRIC_COUNT; ++pos ) if( reserve ) reserveElectricBorder( static_cast< ElectricBorder >( pos )); else unreserveElectricBorder( static_cast< ElectricBorder >( pos )); } void Workspace::reserveElectricBorder( ElectricBorder border ) { if( border == ElectricNone ) return; if( electric_reserved[ border ]++ == 0 ) QTimer::singleShot( 0, this, SLOT( updateElectricBorders())); } void Workspace::unreserveElectricBorder( ElectricBorder border ) { if( border == ElectricNone ) return; assert( electric_reserved[ border ] > 0 ); if( --electric_reserved[ border ] == 0 ) QTimer::singleShot( 0, this, SLOT( updateElectricBorders())); } void Workspace::checkElectricBorder(const QPoint &pos, Time now) { if ((pos.x() != electricLeft) && (pos.x() != electricRight) && (pos.y() != electricTop) && (pos.y() != electricBottom)) return; bool have_borders = false; for( int i = 0; i < ELECTRIC_COUNT; ++i ) if( electric_windows[ i ] != None ) have_borders = true; if( !have_borders ) return; Time treshold_set = options->electricBorderDelay(); // set timeout Time treshold_reset = 250; // reset timeout int distance_reset = 30; // Mouse should not move more than this many pixels ElectricBorder border; if( pos.x() == electricLeft && pos.y() == electricTop ) border = ElectricTopLeft; else if( pos.x() == electricRight && pos.y() == electricTop ) border = ElectricTopRight; else if( pos.x() == electricLeft && pos.y() == electricBottom ) border = ElectricBottomLeft; else if( pos.x() == electricRight && pos.y() == electricBottom ) border = ElectricBottomRight; else if( pos.x() == electricLeft ) border = ElectricLeft; else if( pos.x() == electricRight ) border = ElectricRight; else if( pos.y() == electricTop ) border = ElectricTop; else if( pos.y() == electricBottom ) border = ElectricBottom; else abort(); if( electric_windows[ border ] == None ) return; if ((electric_current_border == border) && (timestampDiff(electric_time_last, now) < treshold_reset) && ((pos-electric_push_point).manhattanLength() < distance_reset)) { electric_time_last = now; if (timestampDiff(electric_time_first, now) > treshold_set) { electric_current_border = ElectricNone; if( effects && static_cast(effects)->borderActivated( border )) {} // handled by effects else electricBorderSwitchDesktop( border, pos ); return; } } else { electric_current_border = border; electric_time_first = now; electric_time_last = now; electric_push_point = pos; } // reset the pointer to find out wether the user is really pushing // (the direction back from which it came, starting from top clockwise) const int xdiff[ ELECTRIC_COUNT ] = { 0, -1, -1, -1, 0, 1, 1, 1 }; const int ydiff[ ELECTRIC_COUNT ] = { 1, 1, 0, -1, -1, -1, 0, 1 }; QCursor::setPos( pos.x() + xdiff[ border ], pos.y() + ydiff[ border ] ); } void Workspace::electricBorderSwitchDesktop( ElectricBorder border, const QPoint& _pos ) { QPoint pos = _pos; int desk = currentDesktop(); const int OFFSET = 2; if( border == ElectricLeft || border == ElectricTopLeft || border == ElectricBottomLeft ) { desk = desktopToLeft( desk, options->rollOverDesktops ); pos.setX( displayWidth() - 1 - OFFSET ); } if( border == ElectricRight || border == ElectricTopRight || border == ElectricBottomRight ) { desk = desktopToRight( desk, options->rollOverDesktops ); pos.setX( OFFSET ); } if( border == ElectricTop || border == ElectricTopLeft || border == ElectricTopRight ) { desk = desktopUp( desk, options->rollOverDesktops ); pos.setY( displayHeight() - 1 - OFFSET ); } if( border == ElectricBottom || border == ElectricBottomLeft || border == ElectricBottomRight ) { desk = desktopDown( desk, options->rollOverDesktops ); pos.setY( OFFSET ); } int desk_before = currentDesktop(); setCurrentDesktop( desk ); if( currentDesktop() != desk_before ) QCursor::setPos( pos ); } // this function is called when the user entered an electric border // with the mouse. It may switch to another virtual desktop bool Workspace::electricBorderEvent(XEvent *e) { if( e->type == EnterNotify ) { for( int i = 0; i < ELECTRIC_COUNT; ++i ) if( electric_windows[ i ] != None && e->xcrossing.window == electric_windows[ i ] ) { // the user entered an electric border checkElectricBorder( QPoint( e->xcrossing.x_root, e->xcrossing.y_root ), e->xcrossing.time ); return true; } } if( e->type == ClientMessage ) { if( e->xclient.message_type == atoms->xdnd_position ) { for( int i = 0; i < ELECTRIC_COUNT; ++i ) if( electric_windows[ i ] != None && e->xclient.window == electric_windows[ i ] ) { updateXTime(); checkElectricBorder( QPoint( e->xclient.data.l[2]>>16, e->xclient.data.l[2]&0xffff), xTime() ); return true; } } } return false; } void Workspace::addTopMenu( Client* c ) { assert( c->isTopMenu()); assert( !topmenus.contains( c )); topmenus.append( c ); if( managingTopMenus()) { int minsize = c->minSize().height(); if( minsize > topMenuHeight()) { topmenu_height = minsize; updateTopMenuGeometry(); } updateTopMenuGeometry( c ); updateCurrentTopMenu(); } // kDebug() << "NEW TOPMENU:" << c; } void Workspace::removeTopMenu( Client* c ) { // if( c->isTopMenu()) // kDebug() << "REMOVE TOPMENU:" << c; assert( c->isTopMenu()); assert( topmenus.contains( c )); topmenus.removeAll( c ); updateCurrentTopMenu(); // TODO reduce topMenuHeight() if possible? } void Workspace::lostTopMenuSelection() { // kDebug() << "lost TopMenu selection"; // make sure this signal is always set when not owning the selection disconnect( topmenu_watcher, SIGNAL( lostOwner()), this, SLOT( lostTopMenuOwner())); connect( topmenu_watcher, SIGNAL( lostOwner()), this, SLOT( lostTopMenuOwner())); if( !managing_topmenus ) return; connect( topmenu_watcher, SIGNAL( lostOwner()), this, SLOT( lostTopMenuOwner())); disconnect( topmenu_selection, SIGNAL( lostOwnership()), this, SLOT( lostTopMenuSelection())); managing_topmenus = false; delete topmenu_space; topmenu_space = NULL; updateClientArea(); for( ClientList::ConstIterator it = topmenus.begin(); it != topmenus.end(); ++it ) (*it)->checkWorkspacePosition(); } void Workspace::lostTopMenuOwner() { if( !options->topMenuEnabled()) return; // kDebug() << "TopMenu selection lost owner"; if( !topmenu_selection->claim( false )) { // kDebug() << "Failed to claim TopMenu selection"; return; } // kDebug() << "claimed TopMenu selection"; setupTopMenuHandling(); } void Workspace::setupTopMenuHandling() { if( managing_topmenus ) return; connect( topmenu_selection, SIGNAL( lostOwnership()), this, SLOT( lostTopMenuSelection())); disconnect( topmenu_watcher, SIGNAL( lostOwner()), this, SLOT( lostTopMenuOwner())); managing_topmenus = true; topmenu_space = new QWidget( NULL, Qt::X11BypassWindowManagerHint ); Window stack[ 2 ]; stack[ 0 ] = supportWindow->winId(); stack[ 1 ] = topmenu_space->winId(); XRestackWindows(display(), stack, 2); updateTopMenuGeometry(); topmenu_space->show(); updateClientArea(); updateCurrentTopMenu(); } int Workspace::topMenuHeight() const { if( topmenu_height == 0 ) { // simply create a dummy menubar and use its preffered height as the menu height KMenuBar tmpmenu; tmpmenu.addAction( "dummy" ); topmenu_height = tmpmenu.sizeHint().height(); } return topmenu_height; } KDecoration* Workspace::createDecoration( KDecorationBridge* bridge ) { return mgr->createDecoration( bridge ); } // returns a list of all colors (KDecorationDefines::ColorType) the current // decoration supports QList< int > Workspace::decorationSupportedColors() const { KDecorationFactory* factory = mgr->factory(); QList< int > ret; for( Ability ab = ABILITYCOLOR_FIRST; ab < ABILITYCOLOR_END; ab = static_cast< Ability>( ab + 1 )) { if( factory->supports( ab )) ret << ab; } return ret; } QString Workspace::desktopName( int desk ) const { return QString::fromUtf8( rootInfo->desktopName( desk ) ); } bool Workspace::checkStartupNotification( Window w, KStartupInfoId& id, KStartupInfoData& data ) { return startup->checkStartup( w, id, data ) == KStartupInfo::Match; } /*! Puts the focus on a dummy window Just using XSetInputFocus() with None would block keyboard input */ void Workspace::focusToNull() { XSetInputFocus(display(), null_focus_window, RevertToPointerRoot, xTime() ); } void Workspace::helperDialog( const QString& message, const Client* c ) { QStringList args; QString type; if( message == "noborderaltf3" ) { KAction* action = qobject_cast(keys->action( "Window Operations Menu" )); if (action==0) assert( false ); QString shortcut = QString( "%1 (%2)" ).arg( action->text() ) .arg( action->globalShortcut().primary().toString()); args << "--msgbox" << i18n( "You have selected to show a window without its border.\n" "Without the border, you will not be able to enable the border " "again using the mouse: use the window operations menu instead, " "activated using the %1 keyboard shortcut." , shortcut ); type = "altf3warning"; } else if( message == "fullscreenaltf3" ) { KAction* action = qobject_cast(keys->action( "Window Operations Menu" )); if (action==0) assert( false ); QString shortcut = QString( "%1 (%2)" ).arg( action->text() ) .arg( action->globalShortcut().primary().toString()); args << "--msgbox" << i18n( "You have selected to show a window in fullscreen mode.\n" "If the application itself does not have an option to turn the fullscreen " "mode off you will not be able to disable it " "again using the mouse: use the window operations menu instead, " "activated using the %1 keyboard shortcut." , shortcut ); type = "altf3warning"; } else assert( false ); if( !type.isEmpty()) { KConfig cfg( "kwin_dialogsrc" ); KConfigGroup cg(&cfg, "Notification Messages" ); // this depends on KMessageBox if( !cg.readEntry( type, true )) // has don't show again checked return; // save launching kdialog args <<"--dontagain" << "kwin_dialogsrc:" + type; } if( c != NULL ) args <<"--embed" << QString::number( c->window()); KProcess::startDetached("kdialog",args); } void Workspace::setShowingDesktop( bool showing ) { rootInfo->setShowingDesktop( showing ); showing_desktop = showing; ++block_showing_desktop; if( showing_desktop ) { showing_desktop_clients.clear(); ++block_focus; ClientList cls = stackingOrder(); // find them first, then minimize, otherwise transients may get minimized with the window // they're transient for for( ClientList::ConstIterator it = cls.begin(); it != cls.end(); ++it ) { if( (*it)->isOnCurrentDesktop() && (*it)->isShown( true ) && !(*it)->isSpecialWindow()) showing_desktop_clients.prepend( *it ); // topmost first to reduce flicker } for( ClientList::ConstIterator it = showing_desktop_clients.begin(); it != showing_desktop_clients.end(); ++it ) (*it)->minimize(); --block_focus; if( Client* desk = findDesktop( true, currentDesktop())) requestFocus( desk ); } else { for( ClientList::ConstIterator it = showing_desktop_clients.begin(); it != showing_desktop_clients.end(); ++it ) (*it)->unminimize(); if( showing_desktop_clients.count() > 0 ) requestFocus( showing_desktop_clients.first()); showing_desktop_clients.clear(); } --block_showing_desktop; } // Following Kicker's behavior: // Changing a virtual desktop resets the state and shows the windows again. // Unminimizing a window resets the state but keeps the windows hidden (except // the one that was unminimized). // A new window resets the state and shows the windows again, with the new window // being active. Due to popular demand (#67406) by people who apparently // don't see a difference between "show desktop" and "minimize all", this is not // true if "showDesktopIsMinimizeAll" is set in kwinrc. In such case showing // a new window resets the state but doesn't show windows. void Workspace::resetShowingDesktop( bool keep_hidden ) { if( block_showing_desktop > 0 ) return; rootInfo->setShowingDesktop( false ); showing_desktop = false; ++block_showing_desktop; if( !keep_hidden ) { for( ClientList::ConstIterator it = showing_desktop_clients.begin(); it != showing_desktop_clients.end(); ++it ) (*it)->unminimize(); } showing_desktop_clients.clear(); --block_showing_desktop; } // Activating/deactivating this feature works like this: // When nothing is active, and the shortcut is pressed, global shortcuts are disabled // (using global_shortcuts_disabled) // When a window that has disabling forced is activated, global shortcuts are disabled. // (using global_shortcuts_disabled_for_client) // When a shortcut is pressed and global shortcuts are disabled (either by a shortcut // or for a client), they are enabled again. void Workspace::slotDisableGlobalShortcuts() { if( global_shortcuts_disabled || global_shortcuts_disabled_for_client ) disableGlobalShortcuts( false ); else disableGlobalShortcuts( true ); } static bool pending_dfc = false; void Workspace::disableGlobalShortcutsForClient( bool disable ) { if( global_shortcuts_disabled_for_client == disable ) return; if( !global_shortcuts_disabled ) { if( disable ) pending_dfc = true; KGlobalSettings::self()->emitChange( KGlobalSettings::BlockShortcuts, disable ); // kwin will get the kipc message too } } void Workspace::disableGlobalShortcuts( bool disable ) { KGlobalSettings::self()->emitChange( KGlobalSettings::BlockShortcuts, disable ); // kwin will get the kipc message too } void Workspace::slotBlockShortcuts( int data ) { if( pending_dfc && data ) { global_shortcuts_disabled_for_client = true; pending_dfc = false; } else { global_shortcuts_disabled = data; global_shortcuts_disabled_for_client = false; } // update also Alt+LMB actions etc. for( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it ) (*it)->updateMouseGrab(); } // Optimized version of QCursor::pos() that tries to avoid X roundtrips // by updating the value only when the X timestamp changes. static QPoint last_cursor_pos; static int last_buttons = 0; static Time last_cursor_timestamp = CurrentTime; static QTimer* last_cursor_timer; QPoint Workspace::cursorPos() const { if( last_cursor_timestamp == CurrentTime || last_cursor_timestamp != QX11Info::appTime()) { last_cursor_timestamp = QX11Info::appTime(); Window root; Window child; int root_x, root_y, win_x, win_y; uint state; XQueryPointer( display(), rootWindow(), &root, &child, &root_x, &root_y, &win_x, &win_y, &state ); last_cursor_pos = QPoint( root_x, root_y ); last_buttons = state; if( last_cursor_timer == NULL ) { Workspace* ws = const_cast< Workspace* >( this ); last_cursor_timer = new QTimer( ws ); last_cursor_timer->setSingleShot( true ); connect( last_cursor_timer, SIGNAL( timeout()), ws, SLOT( resetCursorPosTime())); } last_cursor_timer->start( 0 ); } return last_cursor_pos; } // Because of QTimer's and the impossibility to get events for all mouse // movements (at least I haven't figured out how) the position needs // to be also refetched after each return to the event loop. void Workspace::resetCursorPosTime() { last_cursor_timestamp = CurrentTime; } void Workspace::checkCursorPos() { QPoint last = last_cursor_pos; int lastb = last_buttons; cursorPos(); // update if needed if( last != last_cursor_pos || lastb != last_buttons ) static_cast< EffectsHandlerImpl* >( effects )->mouseChanged( cursorPos(), last, x11ToQtMouseButtons( last_buttons ), x11ToQtMouseButtons( lastb ), x11ToQtKeyboardModifiers( last_buttons ), x11ToQtKeyboardModifiers( lastb )); } } // namespace #include "workspace.moc" diff --git a/workspace.h b/workspace.h index 272cd3a80..4ef841fdc 100644 --- a/workspace.h +++ b/workspace.h @@ -1,965 +1,965 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_WORKSPACE_H #define KWIN_WORKSPACE_H #include #include #include #include #include #include #include #include #include "utils.h" #include "kdecoration.h" #include "sm.h" #include class QMenu; class KConfig; class KActionCollection; class KStartupInfo; class KStartupInfoId; class KStartupInfoData; class QSlider; class QPushButton; namespace KWin { class Client; class TabBox; class PopupInfo; class RootInfo; class PluginMgr; class Placement; class Rules; class WindowRules; class Workspace : public QObject, public KDecorationDefines { Q_OBJECT public: Workspace( bool restore = false ); virtual ~Workspace(); static Workspace * self() { return _self; } bool workspaceEvent( XEvent * ); bool workspaceEvent( QEvent * ); KDecoration* createDecoration( KDecorationBridge* bridge ); bool hasClient( const Client * ); template< typename T > Client* findClient( T predicate ) const; template< typename T1, typename T2 > void forEachClient( T1 procedure, T2 predicate ); template< typename T > void forEachClient( T procedure ); template< typename T > Unmanaged* findUnmanaged( T predicate ) const; template< typename T1, typename T2 > void forEachUnmanaged( T1 procedure, T2 predicate ); template< typename T > void forEachUnmanaged( T procedure ); QRect clientArea( clientAreaOption, const QPoint& p, int desktop ) const; QRect clientArea( clientAreaOption, const Client* c ) const; QRect clientArea( clientAreaOption, int screen, int desktop ) const; /** * @internal */ void killWindowId( Window window); void killWindow() { slotKillWindow(); } bool initializing() const; /** * Returns the active client, i.e. the client that has the focus (or None * if no client has the focus) */ Client* activeClient() const; // Client that was activated, but it's not yet really activeClient(), because // we didn't process yet the matching FocusIn event. Used mostly in focus // stealing prevention code. Client* mostRecentlyActivatedClient() const; void activateClient( Client*, bool force = false ); void requestFocus( Client* c, bool force = false ); void takeActivity( Client* c, int flags, bool handled ); // flags are ActivityFlags void handleTakeActivity( Client* c, Time timestamp, int flags ); // flags are ActivityFlags bool allowClientActivation( const Client* c, Time time = -1U, bool focus_in = false, bool ignore_desktop = false ); void restoreFocus(); void gotFocusIn( const Client* ); void setShouldGetFocus( Client* ); bool fakeRequestedActivity( Client* c ); void unfakeActivity( Client* c ); bool activateNextClient( Client* c ); bool focusChangeEnabled() { return block_focus == 0; } void updateColormap(); /** * Indicates that the client c is being moved around by the user. */ void setClientIsMoving( Client *c ); void place( Client *c, QRect& area ); void placeSmart( Client* c, const QRect& area ); QPoint adjustClientPosition( Client* c, QPoint pos, bool unrestricted ); QRect adjustClientSize( Client* c, QRect moveResizeGeom, int mode ); void raiseClient( Client* c, bool nogroup = false ); void lowerClient( Client* c, bool nogroup = false ); void raiseClientRequest( Client* c, NET::RequestSource src, Time timestamp ); void lowerClientRequest( Client* c, NET::RequestSource src, Time timestamp ); void restackClientUnderActive( Client* ); void updateClientLayer( Client* c ); void raiseOrLowerClient( Client * ); void restoreSessionStackingOrder( Client* c ); + void updateStackingOrder( bool propagate_new_clients = false ); void forceRestacking(); void clientHidden( Client* ); void clientAttentionChanged( Client* c, bool set ); void checkElectricBorder(const QPoint &pos, Time time); void reserveElectricBorder( ElectricBorder border ); void unreserveElectricBorder( ElectricBorder border ); void reserveElectricBorderSwitching( bool reserve ); /** * Returns the current virtual desktop of this workspace */ int currentDesktop() const; /** * Returns the number of virtual desktops of this workspace */ int numberOfDesktops() const; void setNumberOfDesktops( int n ); void calcDesktopLayout(int* x, int* y, Qt::Orientation* orientation) const; int desktopToRight( int desktop, bool wrap ) const; int desktopToLeft( int desktop, bool wrap ) const; int desktopUp( int desktop, bool wrap ) const; int desktopDown( int desktop, bool wrap ) const; int activeScreen() const; int numScreens() const; void checkActiveScreen( const Client* c ); void setActiveScreenMouse( const QPoint &mousepos ); QRect screenGeometry( int screen ) const; int screenNumber( const QPoint &pos ) const; // for TabBox Client* currentTabBoxClient() const; ClientList currentTabBoxClientList() const; int currentTabBoxDesktop() const; QList< int > currentTabBoxDesktopList() const; void setTabBoxClient(Client*); void setTabBoxDesktop(int); Client* nextClientFocusChain(Client*) const; Client* previousClientFocusChain(Client*) const; Client* nextClientStatic(Client*) const; Client* previousClientStatic(Client*) const; int nextDesktopFocusChain( int iDesktop ) const; int previousDesktopFocusChain( int iDesktop ) const; int nextDesktopStatic( int iDesktop ) const; int previousDesktopStatic( int iDesktop ) const; void refTabBox(); void unrefTabBox(); void closeTabBox(); /** * Returns the list of clients sorted in stacking order, with topmost client * at the last position */ const ClientList& stackingOrder() const; ClientList ensureStackingOrder( const ClientList& clients ) const; - Client* topClientOnDesktop( int desktop, bool unconstrained = false, bool only_normal = true ) const; + Client* topClientOnDesktop( int desktop, int screen, bool unconstrained = false, bool only_normal = true ) const; Client* findDesktop( bool topmost, int desktop ) const; void sendClientToDesktop( Client* c, int desktop, bool dont_activate ); void windowToPreviousDesktop( Client* c ); void windowToNextDesktop( Client* c ); void sendClientToScreen( Client* c, int screen ); // KDE4 remove me - and it's also in the DCOP interface :( void showWindowMenuAt( unsigned long id, int x, int y ); void loadEffect( const QString& name ); void toggleEffect( const QString& name ); void reloadEffect( const QString& name ); void unloadEffect( const QString& name ); QStringList loadedEffects() const; QStringList listOfEffects() const; /** * Shows the menu operations menu for the client and makes it active if * it's not already. */ void showWindowMenu( const QRect &pos, Client* cl ); /** * Backwards compatibility. */ void showWindowMenu( int x, int y, Client* cl ); void showWindowMenu( QPoint pos, Client* cl ); void updateMinimizedOfTransients( Client* ); void updateOnAllDesktopsOfTransients( Client* ); void checkTransients( Window w ); void performWindowOperation( Client* c, WindowOperation op ); void storeSession( KConfig* config, SMSavePhase phase ); SessionInfo* takeSessionInfo( Client* ); WindowRules findWindowRules( const Client*, bool ); void rulesUpdated(); void discardUsedWindowRules( Client* c, bool withdraw ); void disableRulesUpdates( bool disable ); bool rulesUpdatesDisabled() const; // dcop interface void cascadeDesktop(); void unclutterDesktop(); void doNotManage( const QString & ); QList< int > decorationSupportedColors() const; bool setCurrentDesktop( int new_desktop ); void nextDesktop(); void previousDesktop(); void circulateDesktopApplications(); bool compositingActive(); bool waitForCompositingSetup(); void setCurrentScreen( int new_screen ); QString desktopName( int desk ) const; void updateDesktopLayout(); void setShowingDesktop( bool showing ); void resetShowingDesktop( bool keep_hidden ); bool showingDesktop() const; bool isNotManaged( const QString& title ); // ### setter or getter ? void sendPingToWindow( Window w, Time timestamp ); // called from Client::pingWindow() void sendTakeActivity( Client* c, Time timestamp, long flags ); // called from Client::takeActivity() void removeClient( Client*, allowed_t ); // only called from Client::destroyClient() or Client::releaseWindow() void setActiveClient( Client*, allowed_t ); Group* findGroup( Window leader ) const; void addGroup( Group* group, allowed_t ); void removeGroup( Group* group, allowed_t ); Group* findClientLeaderGroup( const Client* c ) const; void removeUnmanaged( Unmanaged*, allowed_t ); // only called from Unmanaged::release() void removeDeleted( Deleted*, allowed_t ); void addDeleted( Deleted*, allowed_t ); bool checkStartupNotification( Window w, KStartupInfoId& id, KStartupInfoData& data ); void focusToNull(); // SELI public? enum FocusChainChange { FocusChainMakeFirst, FocusChainMakeLast, FocusChainUpdate }; void updateFocusChains( Client* c, FocusChainChange change ); bool forcedGlobalMouseGrab() const; void clientShortcutUpdated( Client* c ); bool shortcutAvailable( const KShortcut& cut, Client* ignore = NULL ) const; bool globalShortcutsDisabled() const; void disableGlobalShortcuts( bool disable ); void disableGlobalShortcutsForClient( bool disable ); QPoint cursorPos() const; void sessionSaveStarted(); void sessionSaveDone(); void setWasUserInteraction(); bool wasUserInteraction() const; bool sessionSaving() const; bool managingTopMenus() const; int topMenuHeight() const; void updateCurrentTopMenu(); int packPositionLeft( const Client* cl, int oldx, bool left_edge ) const; int packPositionRight( const Client* cl, int oldx, bool right_edge ) const; int packPositionUp( const Client* cl, int oldy, bool top_edge ) const; int packPositionDown( const Client* cl, int oldy, bool bottom_edge ) const; static QStringList configModules(bool controlCenter); void cancelDelayFocus(); void requestDelayFocus( Client* ); void updateFocusMousePosition( const QPoint& pos ); QPoint focusMousePosition() const; void toggleTopDockShadows(bool on); // when adding repaints caused by a window, you probably want to use // either Toplevel::addRepaint() or Toplevel::addWorkspaceRepaint() void addRepaint( const QRect& r ); void addRepaint( const QRegion& r ); void addRepaint( int x, int y, int w, int h ); // creates XComposite overlay window, call initOverlay() afterwards bool createOverlay(); // init overlay and the destination window in it void setupOverlay( Window window ); void showOverlay(); // destroys XComposite overlay window void destroyOverlay(); Window overlayWindow(); public slots: void addRepaintFull(); void refresh(); // keybindings void slotSwitchDesktopNext(); void slotSwitchDesktopPrevious(); void slotSwitchDesktopRight(); void slotSwitchDesktopLeft(); void slotSwitchDesktopUp(); void slotSwitchDesktopDown(); void slotSwitchToDesktop( int ); void slotSwitchToDesktop1() { return slotSwitchToDesktop( 1 ); } void slotSwitchToDesktop2() { return slotSwitchToDesktop( 2 ); } void slotSwitchToDesktop3() { return slotSwitchToDesktop( 3 ); } void slotSwitchToDesktop4() { return slotSwitchToDesktop( 4 ); } void slotSwitchToDesktop5() { return slotSwitchToDesktop( 5 ); } void slotSwitchToDesktop6() { return slotSwitchToDesktop( 6 ); } void slotSwitchToDesktop7() { return slotSwitchToDesktop( 7 ); } void slotSwitchToDesktop8() { return slotSwitchToDesktop( 8 ); } void slotSwitchToDesktop9() { return slotSwitchToDesktop( 9 ); } void slotSwitchToDesktop10() { return slotSwitchToDesktop( 10 ); } void slotSwitchToDesktop11() { return slotSwitchToDesktop( 11 ); } void slotSwitchToDesktop12() { return slotSwitchToDesktop( 12 ); } void slotSwitchToDesktop13() { return slotSwitchToDesktop( 13 ); } void slotSwitchToDesktop14() { return slotSwitchToDesktop( 14 ); } void slotSwitchToDesktop15() { return slotSwitchToDesktop( 15 ); } void slotSwitchToDesktop16() { return slotSwitchToDesktop( 16 ); } void slotSwitchToDesktop17() { return slotSwitchToDesktop( 17 ); } void slotSwitchToDesktop18() { return slotSwitchToDesktop( 18 ); } void slotSwitchToDesktop19() { return slotSwitchToDesktop( 19 ); } void slotSwitchToDesktop20() { return slotSwitchToDesktop( 20 ); } //void slotSwitchToWindow( int ); void slotWindowToDesktop( int ); void slotWindowToDesktop1() { return slotWindowToDesktop( 1 ); } void slotWindowToDesktop2() { return slotWindowToDesktop( 2 ); } void slotWindowToDesktop3() { return slotWindowToDesktop( 3 ); } void slotWindowToDesktop4() { return slotWindowToDesktop( 4 ); } void slotWindowToDesktop5() { return slotWindowToDesktop( 5 ); } void slotWindowToDesktop6() { return slotWindowToDesktop( 6 ); } void slotWindowToDesktop7() { return slotWindowToDesktop( 7 ); } void slotWindowToDesktop8() { return slotWindowToDesktop( 8 ); } void slotWindowToDesktop9() { return slotWindowToDesktop( 9 ); } void slotWindowToDesktop10() { return slotWindowToDesktop( 10 ); } void slotWindowToDesktop11() { return slotWindowToDesktop( 11 ); } void slotWindowToDesktop12() { return slotWindowToDesktop( 12 ); } void slotWindowToDesktop13() { return slotWindowToDesktop( 13 ); } void slotWindowToDesktop14() { return slotWindowToDesktop( 14 ); } void slotWindowToDesktop15() { return slotWindowToDesktop( 15 ); } void slotWindowToDesktop16() { return slotWindowToDesktop( 16 ); } void slotWindowToDesktop17() { return slotWindowToDesktop( 17 ); } void slotWindowToDesktop18() { return slotWindowToDesktop( 18 ); } void slotWindowToDesktop19() { return slotWindowToDesktop( 19 ); } void slotWindowToDesktop20() { return slotWindowToDesktop( 20 ); } //void slotWindowToListPosition( int ); void slotSwitchToScreen( int ); void slotSwitchToScreen0() { return slotSwitchToScreen( 0 ); } void slotSwitchToScreen1() { return slotSwitchToScreen( 1 ); } void slotSwitchToScreen2() { return slotSwitchToScreen( 2 ); } void slotSwitchToScreen3() { return slotSwitchToScreen( 3 ); } void slotSwitchToScreen4() { return slotSwitchToScreen( 4 ); } void slotSwitchToScreen5() { return slotSwitchToScreen( 5 ); } void slotSwitchToScreen6() { return slotSwitchToScreen( 6 ); } void slotSwitchToScreen7() { return slotSwitchToScreen( 7 ); } void slotWindowToScreen( int ); void slotWindowToScreen0() { return slotWindowToScreen( 0 ); } void slotWindowToScreen1() { return slotWindowToScreen( 1 ); } void slotWindowToScreen2() { return slotWindowToScreen( 2 ); } void slotWindowToScreen3() { return slotWindowToScreen( 3 ); } void slotWindowToScreen4() { return slotWindowToScreen( 4 ); } void slotWindowToScreen5() { return slotWindowToScreen( 5 ); } void slotWindowToScreen6() { return slotWindowToScreen( 6 ); } void slotWindowToScreen7() { return slotWindowToScreen( 7 ); } void slotSwitchToNextScreen(); void slotWindowToNextScreen(); void slotWindowMaximize(); void slotWindowMaximizeVertical(); void slotWindowMaximizeHorizontal(); void slotWindowMinimize(); void slotWindowShade(); void slotWindowRaise(); void slotWindowLower(); void slotWindowRaiseOrLower(); void slotActivateAttentionWindow(); void slotWindowPackLeft(); void slotWindowPackRight(); void slotWindowPackUp(); void slotWindowPackDown(); void slotWindowGrowHorizontal(); void slotWindowGrowVertical(); void slotWindowShrinkHorizontal(); void slotWindowShrinkVertical(); void slotWalkThroughDesktops(); void slotWalkBackThroughDesktops(); void slotWalkThroughDesktopList(); void slotWalkBackThroughDesktopList(); void slotWalkThroughWindows(); void slotWalkBackThroughWindows(); void slotWindowOperations(); void slotWindowClose(); void slotWindowMove(); void slotWindowResize(); void slotWindowAbove(); void slotWindowBelow(); void slotWindowOnAllDesktops(); void slotWindowFullScreen(); void slotWindowNoBorder(); void slotWindowToNextDesktop(); void slotWindowToPreviousDesktop(); void slotWindowToDesktopRight(); void slotWindowToDesktopLeft(); void slotWindowToDesktopUp(); void slotWindowToDesktopDown(); void slotMouseEmulation(); void slotDisableGlobalShortcuts(); void slotSettingsChanged( int category ); void reconfigure(); void slotReconfigure(); void slotReinitCompositing(); void slotKillWindow(); void slotGrabWindow(); void slotGrabDesktop(); void slotSetupWindowShortcut(); void setupWindowShortcutDone( bool ); void slotToggleCompositing(); void updateClientArea(); private slots: void desktopPopupAboutToShow(); void clientPopupAboutToShow(); void slotSendToDesktop( QAction* ); void clientPopupActivated( QAction* ); void configureWM(); void desktopResized(); void slotUpdateToolWindows(); void lostTopMenuSelection(); void lostTopMenuOwner(); void delayFocus(); void gotTemporaryRulesMessage( const QString& ); void cleanupTemporaryRules(); void writeWindowRules(); void slotBlockShortcuts(int data); void slotReloadConfig(); void setPopupClientOpacity( QAction* action ); void setupCompositing(); void performCompositing(); void lostCMSelection(); void updateElectricBorders(); void resetCursorPosTime(); protected: bool keyPressMouseEmulation( XKeyEvent& ev ); private: void init(); void initShortcuts(); void readShortcuts(); void initDesktopPopup(); void discardPopup(); void setupWindowShortcut( Client* c ); void checkCursorPos(); bool startKDEWalkThroughWindows(); bool startWalkThroughDesktops( TabBoxMode mode ); // TabBoxDesktopMode | TabBoxDesktopListMode bool startWalkThroughDesktops(); bool startWalkThroughDesktopList(); void KDEWalkThroughWindows( bool forward ); void CDEWalkThroughWindows( bool forward ); void walkThroughDesktops( bool forward ); void KDEOneStepThroughWindows( bool forward ); void oneStepThroughDesktops( bool forward, TabBoxMode mode ); // TabBoxDesktopMode | TabBoxDesktopListMode void oneStepThroughDesktops( bool forward ); void oneStepThroughDesktopList( bool forward ); bool establishTabBoxGrab(); void removeTabBoxGrab(); - void updateStackingOrder( bool propagate_new_clients = false ); ToplevelList compositingStackingOrder() const; void propagateClients( bool propagate_new_clients ); // called only from updateStackingOrder ClientList constrainedStackingOrder(); void raiseClientWithinApplication( Client* c ); void lowerClientWithinApplication( Client* c ); bool allowFullClientRaising( const Client* c, Time timestamp ); bool keepTransientAbove( const Client* mainwindow, const Client* transient ); void blockStackingUpdates( bool block ); void addTopMenu( Client* c ); void removeTopMenu( Client* c ); void setupTopMenuHandling(); void updateTopMenuGeometry( Client* c = NULL ); void updateToolWindows( bool also_hide ); // this is the right way to create a new client Client* createClient( Window w, bool is_mapped ); void addClient( Client* c, allowed_t ); Unmanaged* createUnmanaged( Window w ); void addUnmanaged( Unmanaged* c, allowed_t ); Window findSpecialEventWindow( XEvent* e ); void randomPlacement(Client* c); void smartPlacement(Client* c); void cascadePlacement(Client* c, bool re_init = false); // desktop names and number of desktops void loadDesktopSettings(); void saveDesktopSettings(); // mouse emulation WId getMouseEmulationWindow(); enum MouseEmulation { EmuPress, EmuRelease, EmuMove }; unsigned int sendFakedMouseEvent( const QPoint &pos, WId win, MouseEmulation type, int button, unsigned int state ); // returns the new state void tabBoxKeyPress( int key ); void tabBoxKeyRelease( const XKeyEvent& ev ); // electric borders void destroyElectricBorders(); bool electricBorderEvent(XEvent * e); void electricBorderSwitchDesktop( ElectricBorder border, const QPoint& pos ); // ------------------ void helperDialog( const QString& message, const Client* c ); QMenu* clientPopup(); void closeActivePopup(); void updateClientArea( bool force ); void finishCompositing(); bool windowRepaintsPending() const; int current_desktop; int number_of_desktops; QVector desktop_focus_chain; QWidget* active_popup; Client* active_popup_client; void loadSessionInfo(); void loadWindowRules(); void editWindowRules( Client* c, bool whole_app ); QList session; QList rules; KXMessages temporaryRulesMessages; QTimer rulesUpdatedTimer; bool rules_updates_disabled; static const char* windowTypeToTxt( NET::WindowType type ); static NET::WindowType txtToWindowType( const char* txt ); static bool sessionInfoWindowTypeMatch( Client* c, SessionInfo* info ); Client* active_client; Client* last_active_client; Client* most_recently_raised; // used _only_ by raiseOrLowerClient() Client* movingClient; Client* pending_take_activity; int active_screen; // delay(ed) window focus timer and client QTimer* delayFocusTimer; Client* delayfocus_client; QPoint focusMousePos; ClientList clients; ClientList desktops; UnmanagedList unmanaged; DeletedList deleted; ClientList unconstrained_stacking_order; // topmost last ClientList stacking_order; // topmost last bool force_restacking; QVector< ClientList > focus_chain; // currently ative last ClientList global_focus_chain; // this one is only for things like tabbox's MRU ClientList should_get_focus; // last is most recent ClientList attention_chain; bool showing_desktop; ClientList showing_desktop_clients; int block_showing_desktop; GroupList groups; bool was_user_interaction; bool session_saving; int session_active_client; int session_desktop; bool control_grab; bool tab_grab; //KKeyNative walkThroughDesktopsKeycode, walkBackThroughDesktopsKeycode; //KKeyNative walkThroughDesktopListKeycode, walkBackThroughDesktopListKeycode; //KKeyNative walkThroughWindowsKeycode, walkBackThroughWindowsKeycode; KShortcut cutWalkThroughDesktops, cutWalkThroughDesktopsReverse; KShortcut cutWalkThroughDesktopList, cutWalkThroughDesktopListReverse; KShortcut cutWalkThroughWindows, cutWalkThroughWindowsReverse; bool mouse_emulation; unsigned int mouse_emulation_state; WId mouse_emulation_window; int block_focus; TabBox* tab_box; PopupInfo* popupinfo; QMenu *popup; QMenu *advanced_popup; QMenu *trans_popup; QMenu *desk_popup; void modalActionsSwitch( bool enabled ); KActionCollection *keys; KActionCollection *client_keys; QAction *mResizeOpAction; QAction *mMoveOpAction; QAction *mMaximizeOpAction; QAction *mShadeOpAction; QAction *mKeepAboveOpAction; QAction *mKeepBelowOpAction; QAction *mFullScreenOpAction; QAction *mNoBorderOpAction; QAction *mMinimizeOpAction; QAction *mCloseOpAction; ShortcutDialog* client_keys_dialog; Client* client_keys_client; KActionCollection *disable_shortcuts_keys; bool global_shortcuts_disabled; bool global_shortcuts_disabled_for_client; PluginMgr *mgr; RootInfo *rootInfo; QWidget* supportWindow; // swallowing QStringList doNotManageList; // colormap handling Colormap default_colormap; Colormap installed_colormap; // Timer to collect requests for 'reconfigure' QTimer reconfigureTimer; QTimer updateToolWindowsTimer; static Workspace *_self; bool workspaceInit; KStartupInfo* startup; ElectricBorder electric_current_border; Window electric_windows[ ELECTRIC_COUNT ]; int electricLeft; int electricRight; int electricTop; int electricBottom; Time electric_time_first; Time electric_time_last; QPoint electric_push_point; int electric_reserved[ ELECTRIC_COUNT ]; // corners/edges used by something Qt::Orientation layoutOrientation; int layoutX; int layoutY; Placement *initPositioning; QVector< QRect > workarea; // array of workareas for virtual desktops QVector< QVector< QRect > > screenarea; // array of workareas per xinerama screen for all virtual desktops bool managing_topmenus; KSelectionOwner* topmenu_selection; KSelectionWatcher* topmenu_watcher; ClientList topmenus; // doesn't own them mutable int topmenu_height; QWidget* topmenu_space; int set_active_client_recursion; int block_stacking_updates; // when >0, stacking updates are temporarily disabled bool blocked_propagating_new_clients; // propagate also new clients after enabling stacking updates? Window null_focus_window; bool forced_global_mouse_grab; friend class StackingUpdatesBlocker; KSelectionOwner* cm_selection; bool compositingSuspended; QTimer compositeTimer; QTime lastCompositePaint; int compositeRate; QRegion repaints_region; Window overlay; // XComposite overlay window bool overlay_visible; bool overlay_shown; // for showOverlay() QSlider *transSlider; QPushButton *transButton; private: friend bool performTransiencyCheck(); }; // helper for Workspace::blockStackingUpdates() being called in pairs (true/false) class StackingUpdatesBlocker { public: StackingUpdatesBlocker( Workspace* w ) : ws( w ) { ws->blockStackingUpdates( true ); } ~StackingUpdatesBlocker() { ws->blockStackingUpdates( false ); } private: Workspace* ws; }; // NET WM Protocol handler class class RootInfo : public NETRootInfo { private: typedef KWin::Client Client; // because of NET::Client public: RootInfo( Workspace* ws, Display *dpy, Window w, const char *name, unsigned long pr[], int pr_num, int scr= -1); protected: virtual void changeNumberOfDesktops(int n); virtual void changeCurrentDesktop(int d); virtual void changeActiveWindow(Window w,NET::RequestSource src, Time timestamp, Window active_window); virtual void closeWindow(Window w); virtual void moveResize(Window w, int x_root, int y_root, unsigned long direction); virtual void moveResizeWindow(Window w, int flags, int x, int y, int width, int height ); virtual void gotPing(Window w, Time timestamp); virtual void restackWindow(Window w, RequestSource source, Window above, int detail, Time timestamp); virtual void gotTakeActivity(Window w, Time timestamp, long flags ); virtual void changeShowingDesktop( bool showing ); private: Workspace* workspace; }; inline bool Workspace::initializing() const { return workspaceInit; } inline Client* Workspace::activeClient() const { return active_client; } inline Client* Workspace::mostRecentlyActivatedClient() const { return should_get_focus.count() > 0 ? should_get_focus.last() : active_client; } inline int Workspace::currentDesktop() const { return current_desktop; } inline int Workspace::numberOfDesktops() const { return number_of_desktops; } inline void Workspace::addGroup( Group* group, allowed_t ) { groups.append( group ); } inline void Workspace::removeGroup( Group* group, allowed_t ) { groups.removeAll( group ); } inline const ClientList& Workspace::stackingOrder() const { // TODO Q_ASSERT( block_stacking_updates == 0 ); return stacking_order; } inline void Workspace::showWindowMenu(QPoint pos, Client* cl) { showWindowMenu(QRect(pos, pos), cl); } inline void Workspace::showWindowMenu(int x, int y, Client* cl) { showWindowMenu(QRect(QPoint(x, y), QPoint(x, y)), cl); } inline void Workspace::setWasUserInteraction() { was_user_interaction = true; } inline bool Workspace::wasUserInteraction() const { return was_user_interaction; } inline bool Workspace::managingTopMenus() const { return managing_topmenus; } inline void Workspace::sessionSaveStarted() { session_saving = true; } inline void Workspace::sessionSaveDone() { session_saving = false; } inline bool Workspace::sessionSaving() const { return session_saving; } inline bool Workspace::forcedGlobalMouseGrab() const { return forced_global_mouse_grab; } inline bool Workspace::showingDesktop() const { return showing_desktop; } inline bool Workspace::globalShortcutsDisabled() const { return global_shortcuts_disabled || global_shortcuts_disabled_for_client; } inline Window Workspace::overlayWindow() { return overlay; } inline bool Workspace::rulesUpdatesDisabled() const { return rules_updates_disabled; } inline void Workspace::forceRestacking() { force_restacking = true; StackingUpdatesBlocker blocker( this ); // do restacking if not blocked } inline void Workspace::updateFocusMousePosition( const QPoint& pos ) { focusMousePos = pos; } inline QPoint Workspace::focusMousePosition() const { return focusMousePos; } template< typename T > inline Client* Workspace::findClient( T predicate ) const { if( Client* ret = findClientInList( clients, predicate )) return ret; if( Client* ret = findClientInList( desktops, predicate )) return ret; return NULL; } template< typename T1, typename T2 > inline void Workspace::forEachClient( T1 procedure, T2 predicate ) { for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) if ( predicate( const_cast< const Client* >( *it))) procedure( *it ); for ( ClientList::ConstIterator it = desktops.begin(); it != desktops.end(); ++it) if ( predicate( const_cast< const Client* >( *it))) procedure( *it ); } template< typename T > inline void Workspace::forEachClient( T procedure ) { return forEachClient( procedure, TruePredicate()); } template< typename T > inline Unmanaged* Workspace::findUnmanaged( T predicate ) const { return findUnmanagedInList( unmanaged, predicate ); } template< typename T1, typename T2 > inline void Workspace::forEachUnmanaged( T1 procedure, T2 predicate ) { for ( UnmanagedList::ConstIterator it = unmanaged.begin(); it != unmanaged.end(); ++it) if ( predicate( const_cast< const Unmanaged* >( *it))) procedure( *it ); } template< typename T > inline void Workspace::forEachUnmanaged( T procedure ) { return forEachUnmanaged( procedure, TruePredicate()); } KWIN_COMPARE_PREDICATE( ClientMatchPredicate, Client, const Client*, cl == value ); inline bool Workspace::hasClient( const Client* c ) { return findClient( ClientMatchPredicate( c )); } } // namespace #endif