Index: trunk/kdebase/kwin/options.h =================================================================== --- trunk/kdebase/kwin/options.h (revision 298356) +++ trunk/kdebase/kwin/options.h (revision 298357) @@ -1,274 +1,278 @@ /***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ #ifndef KWIN_OPTIONS_H #define KWIN_OPTIONS_H #include #include #include #include #include namespace KWinInternal { +class Client; + class Options : public KDecorationOptions { public: Options(); ~Options(); virtual unsigned long updateSettings(); /*! Different focus policies:
  • ClickToFocus - Clicking into a window activates it. This is also the default.
  • FocusFollowsMouse - Moving the mouse pointer actively onto a normal window activates it. For convenience, the desktop and windows on the dock are excluded. They require clicking.
  • FocusUnderMouse - The window that happens to be under the mouse pointer becomes active. The invariant is: no window can have focus that is not under the mouse. This also means that Alt-Tab won't work properly and popup dialogs are usually unsable with the keyboard. Note that the desktop and windows on the dock are excluded for convenience. They get focus only when clicking on it.
  • FocusStrictlyUnderMouse - this is even worse than FocusUnderMouse. Only the window under the mouse pointer is active. If the mouse points nowhere, nothing has the focus. If the mouse points onto the desktop, the desktop has focus. The same holds for windows on the dock. Note that FocusUnderMouse and FocusStrictlyUnderMouse are not particulary useful. They are only provided for old-fashined die-hard UNIX people ;-)
*/ enum FocusPolicy { ClickToFocus, FocusFollowsMouse, FocusUnderMouse, FocusStrictlyUnderMouse }; FocusPolicy focusPolicy; /** Whether clicking on a window raises it in FocusFollowsMouse mode or not. */ bool clickRaise; /** whether autoraise is enabled FocusFollowsMouse mode or not. */ bool autoRaise; /** autoraise interval */ int autoRaiseInterval; /** Whether shade hover is enabled or not */ bool shadeHover; /** shade hover interval */ int shadeHoverInterval; /** Different Alt-Tab-Styles:
  • KDE - the recommended KDE style. Alt-Tab opens a nice icon box that makes it easy to select the window you want to tab to. The order automatically adjusts to the most recently used windows. Note that KDE style does not work with the FocusUnderMouse and FocusStrictlyUnderMouse focus policies. Choose ClickToFocus or FocusFollowsMouse instead.
  • CDE - the old-fashion CDE style. Alt-Tab cycles between the windows in static order. The current window gets raised, the previous window gets lowered.
*/ enum AltTabStyle { KDE, CDE }; AltTabStyle altTabStyle; /** * Xinerama options */ bool xineramaEnabled; bool xineramaPlacementEnabled; bool xineramaMovementEnabled; bool xineramaMaximizeEnabled; /** MoveResizeMode, either Tranparent or Opaque. */ enum MoveResizeMode { Transparent, Opaque }; MoveResizeMode resizeMode; MoveResizeMode moveMode; /** * Placement policies. How workspace decides the way windows get positioned * on the screen. The better the policy, the heavier the resource use. * Normally you don't have to worry. What the WM adds to the startup time * is nil compared to the creation of the window itself in the memory */ enum PlacementPolicy { Random, Smart, Cascade, Centered, ZeroCornered }; PlacementPolicy placement; bool focusPolicyIsReasonable() { return focusPolicy == ClickToFocus || focusPolicy == FocusFollowsMouse; } /** * whether we animate the shading of windows to titlebar or not */ bool animateShade; /** * the size of the zone that triggers snapping on desktop borders */ int borderSnapZone; /** * the number of animation steps (would this be general?) */ int windowSnapZone; /** * snap only when windows will overlap */ bool snapOnlyWhenOverlapping; /** * whether we animate the minimization of windows or not */ bool animateMinimize; /** * Animation speed (0 .. 10 ) */ int animateMinimizeSpeed; /** * whether or not we roll over to the other edge when switching desktops past the edge */ bool rollOverDesktops; // 0 - 4 , see Workspace::allowClientActivation() int focusStealingPreventionLevel; /** * List of window classes to ignore PPosition size hint */ QStringList ignorePositionClasses; - // List of window classes for which not to use focus stealing prevention - QStringList ignoreFocusStealingClasses; + + bool checkIgnoreFocusStealing( const Client* c ); WindowOperation operationTitlebarDblClick() { return OpTitlebarDblClick; } enum MouseCommand { MouseRaise, MouseLower, MouseOperationsMenu, MouseToggleRaiseAndLower, MouseActivateAndRaise, MouseActivateAndLower, MouseActivate, MouseActivateRaiseAndPassClick, MouseActivateAndPassClick, MouseMove, MouseUnrestrictedMove, MouseActivateRaiseAndMove, MouseActivateRaiseAndUnrestrictedMove, MouseResize, MouseUnrestrictedResize, MouseShade, MouseMinimize, MouseNothing }; MouseCommand commandActiveTitlebar1() { return CmdActiveTitlebar1; } MouseCommand commandActiveTitlebar2() { return CmdActiveTitlebar2; } MouseCommand commandActiveTitlebar3() { return CmdActiveTitlebar3; } MouseCommand commandInactiveTitlebar1() { return CmdInactiveTitlebar1; } MouseCommand commandInactiveTitlebar2() { return CmdInactiveTitlebar2; } MouseCommand commandInactiveTitlebar3() { return CmdInactiveTitlebar3; } MouseCommand commandWindow1() { return CmdWindow1; } MouseCommand commandWindow2() { return CmdWindow2; } MouseCommand commandWindow3() { return CmdWindow3; } MouseCommand commandAll1() { return CmdAll1; } MouseCommand commandAll2() { return CmdAll2; } MouseCommand commandAll3() { return CmdAll3; } uint keyCmdAllModKey() { return CmdAllModKey; } static WindowOperation windowOperation(const QString &name, bool restricted ); static MouseCommand mouseCommand(const QString &name, bool restricted ); /** * @returns true if the Geometry Tip should be shown during a window move/resize. * @since 3.2 */ bool showGeometryTip(); enum { ElectricDisabled = 0, ElectricMoveOnly = 1, ElectricAlways = 2 }; /** * @returns true if electric borders are enabled. With electric borders * you can change desktop by moving the mouse pointer towards the edge * of the screen */ int electricBorders(); /** * @returns the activation delay for electric borders in milliseconds. */ int electricBorderDelay(); bool topMenuEnabled() const { return topmenus; } bool desktopTopMenu() const { return desktop_topmenu; } private: WindowOperation OpTitlebarDblClick; // mouse bindings MouseCommand CmdActiveTitlebar1; MouseCommand CmdActiveTitlebar2; MouseCommand CmdActiveTitlebar3; MouseCommand CmdInactiveTitlebar1; MouseCommand CmdInactiveTitlebar2; MouseCommand CmdInactiveTitlebar3; MouseCommand CmdWindow1; MouseCommand CmdWindow2; MouseCommand CmdWindow3; MouseCommand CmdAll1; MouseCommand CmdAll2; MouseCommand CmdAll3; uint CmdAllModKey; int electric_borders; int electric_border_delay; bool show_geometry_tip; bool topmenus; bool desktop_topmenu; + // List of window classes for which not to use focus stealing prevention + QStringList ignoreFocusStealingClasses; }; extern Options* options; } // namespace #endif Index: trunk/kdebase/kwin/options.cpp =================================================================== --- trunk/kdebase/kwin/options.cpp (revision 298356) +++ trunk/kdebase/kwin/options.cpp (revision 298357) @@ -1,245 +1,253 @@ /***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ #include "options.h" + #include #include #include #include #include #include #include +#include "client.h" + namespace KWinInternal { Options::Options() : electric_borders( 0 ), electric_border_delay(0) { d = new KDecorationOptionsPrivate; d->defaultKWinSettings(); updateSettings(); } Options::~Options() { delete d; } unsigned long Options::updateSettings() { KConfig *config = KGlobal::config(); unsigned long changed = 0; changed |= d->updateKWinSettings( config ); // read decoration settings config->setGroup( "Windows" ); moveMode = config->readEntry("MoveMode", "Opaque" ) == "Opaque"?Opaque:Transparent; resizeMode = config->readEntry("ResizeMode", "Opaque" ) == "Opaque"?Opaque:Transparent; show_geometry_tip = config->readBoolEntry("GeometryTip", false); QString val; val = config->readEntry ("FocusPolicy", "ClickToFocus"); focusPolicy = ClickToFocus; // what a default :-) if ( val == "FocusFollowsMouse" ) focusPolicy = FocusFollowsMouse; else if ( val == "FocusUnderMouse" ) focusPolicy = FocusUnderMouse; else if ( val == "FocusStrictlyUnderMouse" ) focusPolicy = FocusStrictlyUnderMouse; val = config->readEntry ("AltTabStyle", "KDE"); altTabStyle = KDE; // what a default :-) if ( val == "CDE" ) altTabStyle = CDE; rollOverDesktops = config->readBoolEntry("RollOverDesktops", TRUE); // focusStealingPreventionLevel = config->readNumEntry( "FocusStealingPreventionLevel", 2 ); // TODO use low level for now focusStealingPreventionLevel = config->readNumEntry( "FocusStealingPreventionLevel", 1 ); focusStealingPreventionLevel = KMAX( 0, KMIN( 4, focusStealingPreventionLevel )); if( !focusPolicyIsReasonable()) // #48786, comments #7 and later focusStealingPreventionLevel = 0; KConfig *gc = new KConfig("kdeglobals", false, false); bool isVirtual = KApplication::desktop()->isVirtualDesktop(); gc->setGroup("Windows"); xineramaEnabled = gc->readBoolEntry ("XineramaEnabled", isVirtual ) && isVirtual; if (xineramaEnabled) { xineramaPlacementEnabled = gc->readBoolEntry ("XineramaPlacementEnabled", true); xineramaMovementEnabled = gc->readBoolEntry ("XineramaMovementEnabled", true); xineramaMaximizeEnabled = gc->readBoolEntry ("XineramaMaximizeEnabled", true); } else { xineramaPlacementEnabled = xineramaMovementEnabled = xineramaMaximizeEnabled = false; } delete gc; val = config->readEntry("Placement","Smart"); if (val == "Random") placement = Random; else if (val == "Cascade") placement = Cascade; else if (val == "Centered") placement = Centered; else if (val == "ZeroCornered") placement = ZeroCornered; else placement = Smart; animateShade = config->readBoolEntry("AnimateShade", TRUE ); animateMinimize = config->readBoolEntry("AnimateMinimize", TRUE ); animateMinimizeSpeed = config->readNumEntry("AnimateMinimizeSpeed", 5 ); if( focusPolicy == ClickToFocus ) { autoRaise = false; autoRaiseInterval = 0; } else { autoRaise = config->readBoolEntry("AutoRaise", FALSE ); autoRaiseInterval = config->readNumEntry("AutoRaiseInterval", 0 ); } shadeHover = config->readBoolEntry("ShadeHover", FALSE ); shadeHoverInterval = config->readNumEntry("ShadeHoverInterval", 250 ); // important: autoRaise implies ClickRaise clickRaise = autoRaise || config->readBoolEntry("ClickRaise", TRUE ); borderSnapZone = config->readNumEntry("BorderSnapZone", 10); windowSnapZone = config->readNumEntry("WindowSnapZone", 10); snapOnlyWhenOverlapping=config->readBoolEntry("SnapOnlyWhenOverlapping",FALSE); electric_borders = config->readNumEntry("ElectricBorders", 0); electric_border_delay = config->readNumEntry("ElectricBorderDelay", 150); OpTitlebarDblClick = windowOperation( config->readEntry("TitlebarDoubleClickCommand", "Shade"), true ); ignorePositionClasses = config->readListEntry("IgnorePositionClasses"); ignoreFocusStealingClasses = config->readListEntry("IgnoreFocusStealingClasses"); // Qt3.2 and older had resource class all lowercase, but Qt3.3 has it capitalized // therefore Client::resourceClass() forces lowercase, force here lowercase as well for( QStringList::Iterator it = ignorePositionClasses.begin(); it != ignorePositionClasses.end(); ++it ) (*it) = (*it).lower(); for( QStringList::Iterator it = ignoreFocusStealingClasses.begin(); it != ignoreFocusStealingClasses.end(); ++it ) (*it) = (*it).lower(); // Mouse bindings config->setGroup( "MouseBindings"); CmdActiveTitlebar1 = mouseCommand(config->readEntry("CommandActiveTitlebar1","Raise"), true ); CmdActiveTitlebar2 = mouseCommand(config->readEntry("CommandActiveTitlebar2","Lower"), true ); CmdActiveTitlebar3 = mouseCommand(config->readEntry("CommandActiveTitlebar3","Operations menu"), true ); CmdInactiveTitlebar1 = mouseCommand(config->readEntry("CommandInactiveTitlebar1","Activate and raise"), true ); CmdInactiveTitlebar2 = mouseCommand(config->readEntry("CommandInactiveTitlebar2","Activate and lower"), true ); CmdInactiveTitlebar3 = mouseCommand(config->readEntry("CommandInactiveTitlebar3","Operations menu"), true ); CmdWindow1 = mouseCommand(config->readEntry("CommandWindow1","Activate, raise and pass click"), false ); CmdWindow2 = mouseCommand(config->readEntry("CommandWindow2","Activate and pass click"), false ); CmdWindow3 = mouseCommand(config->readEntry("CommandWindow3","Activate and pass click"), false ); CmdAllModKey = (config->readEntry("CommandAllKey","Alt") == "Meta") ? Qt::Key_Meta : Qt::Key_Alt; CmdAll1 = mouseCommand(config->readEntry("CommandAll1","Move"), false ); CmdAll2 = mouseCommand(config->readEntry("CommandAll2","Toggle raise and lower"), false ); CmdAll3 = mouseCommand(config->readEntry("CommandAll3","Resize"), false ); // Read button tooltip animation effect from kdeglobals // Since we want to allow users to enable window decoration tooltips // and not kstyle tooltips and vise-versa, we don't read the // "EffectNoTooltip" setting from kdeglobals. KConfig globalConfig("kdeglobals"); globalConfig.setGroup("KDE"); topmenus = globalConfig.readBoolEntry( "macStyle", false ); KConfig kdesktopcfg( "kdesktoprc", true ); kdesktopcfg.setGroup( "Menubar" ); desktop_topmenu = kdesktopcfg.readBoolEntry( "ShowMenubar", false ); if( desktop_topmenu ) topmenus = true; QToolTip::setGloballyEnabled( d->show_tooltips ); return changed; } // restricted should be true for operations that the user may not be able to repeat // if the window is moved out of the workspace (e.g. if the user moves a window // by the titlebar, and moves it too high beneath Kicker at the top edge, they // may not be able to move it back, unless they know about Alt+LMB) Options::WindowOperation Options::windowOperation(const QString &name, bool restricted ) { if (name == "Move") return restricted ? MoveOp : UnrestrictedMoveOp; else if (name == "Resize") return restricted ? ResizeOp : UnrestrictedResizeOp; else if (name == "Maximize") return MaximizeOp; else if (name == "Minimize") return MinimizeOp; else if (name == "Close") return CloseOp; else if (name == "OnAllDesktops") return OnAllDesktopsOp; else if (name == "Shade") return ShadeOp; else if (name == "Operations") return OperationsOp; else if (name == "Maximize (vertical only)") return VMaximizeOp; else if (name == "Maximize (horizontal only)") return HMaximizeOp; else if (name == "Lower") return LowerOp; return NoOp; } Options::MouseCommand Options::mouseCommand(const QString &name, bool restricted ) { QString lowerName = name.lower(); if (lowerName == "raise") return MouseRaise; if (lowerName == "lower") return MouseLower; if (lowerName == "operations menu") return MouseOperationsMenu; if (lowerName == "toggle raise and lower") return MouseToggleRaiseAndLower; if (lowerName == "activate and raise") return MouseActivateAndRaise; if (lowerName == "activate and lower") return MouseActivateAndLower; if (lowerName == "activate") return MouseActivate; if (lowerName == "activate, raise and pass click") return MouseActivateRaiseAndPassClick; if (lowerName == "activate and pass click") return MouseActivateAndPassClick; if (lowerName == "activate, raise and move") return restricted ? MouseActivateRaiseAndMove : MouseActivateRaiseAndUnrestrictedMove; if (lowerName == "move") return restricted ? MouseMove : MouseUnrestrictedMove; if (lowerName == "resize") return restricted ? MouseResize : MouseUnrestrictedResize; if (lowerName == "shade") return MouseShade; if (lowerName == "minimize") return MouseMinimize; if (lowerName == "nothing") return MouseNothing; return MouseNothing; } bool Options::showGeometryTip() { return show_geometry_tip; } int Options::electricBorders() { return electric_borders; } int Options::electricBorderDelay() { return electric_border_delay; } +bool Options::checkIgnoreFocusStealing( const Client* c ) + { + return ignoreFocusStealingClasses.contains(QString::fromLatin1(c->resourceClass())); + } + } // namespace Index: trunk/kdebase/kwin/client.cpp =================================================================== --- trunk/kdebase/kwin/client.cpp (revision 298356) +++ trunk/kdebase/kwin/client.cpp (revision 298357) @@ -1,1714 +1,1715 @@ /***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ #include "client.h" #include #include #include #include #include #include #include #include #include #include #include "bridge.h" #include "group.h" #include "workspace.h" #include "atoms.h" #include "notifications.h" #include // put all externs before the namespace statement to allow the linker // to resolve them properly extern Atom qt_wm_state; extern Time qt_x_time; extern Atom qt_window_role; extern Atom qt_sm_client_id; namespace KWinInternal { /* Creating a client: - only by calling Workspace::createClient() - it creates a new client and calls manage() for it Destroying a client: - destroyClient() - only when the window itself has been destroyed - releaseWindow() - the window is kept, only the client itself is destroyed */ /*! \class Client client.h \brief The Client class encapsulates a window decoration frame. */ /*! This ctor is "dumb" - it only initializes data. All the real initialization is done in manage(). */ Client::Client( Workspace *ws ) : QObject( NULL ), client( None ), wrapper( None ), frame( None ), decoration( NULL ), wspace( ws ), bridge( new Bridge( this )), move_faked_activity( false ), move_resize_grab_window( None ), transient_for( NULL ), transient_for_id( None ), original_transient_for_id( None ), in_group( NULL ), window_group( None ), in_layer( UnknownLayer ), ping_timer( NULL ), process_killer( NULL ), user_time( CurrentTime ), // not known yet allowed_actions( 0 ), block_geometry( 0 ), shade_geometry_change( false ), border_left( 0 ), border_right( 0 ), border_top( 0 ), border_bottom( 0 ) // SELI do all as initialization { autoRaiseTimer = 0; shadeHoverTimer = 0; // set the initial mapping state mapping_state = WithdrawnState; desk = 0; // no desktop yet mode = PositionCenter; buttonDown = FALSE; moveResizeMode = FALSE; info = NULL; shade_mode = ShadeNone; active = FALSE; keep_above = FALSE; keep_below = FALSE; is_shape = FALSE; motif_may_move = TRUE; motif_may_resize = TRUE; motif_may_close = TRUE; fullscreen_mode = FullScreenNone; skip_taskbar = FALSE; original_skip_taskbar = false; minimized = false; hidden = false; modal = false; noborder = false; user_noborder = false; not_obscured = false; urgency = false; + ignore_focus_stealing = false; Pdeletewindow = 0; Ptakefocus = 0; Pcontexthelp = 0; Pping = 0; input = FALSE; store_settings = FALSE; skip_pager = FALSE; max_mode = MaximizeRestore; cmap = None; frame_geometry = QRect( 0, 0, 100, 100 ); // so that decorations don't start with size being (0,0) client_size = QSize( 100, 100 ); // SELI initialize xsizehints?? } /*! "Dumb" destructor. */ Client::~Client() { assert(!moveResizeMode); assert( client == None ); assert( frame == None && wrapper == None ); assert( decoration == NULL ); assert( block_geometry == 0 ); delete info; delete bridge; } // use destroyClient() or releaseWindow(), Client instances cannot be deleted directly void Client::deleteClient( Client* c, allowed_t ) { delete c; } /*! Releases the window. The client has done its job and the window is still existing. */ void Client::releaseWindow( bool on_shutdown ) { if (moveResizeMode) leaveMoveResize(); setModal( false ); // otherwise its mainwindow wouldn't get focus hidden = true; // so that it's not considered visible anymore (can't use hideClient(), it would set flags) if( !on_shutdown ) workspace()->clientHidden( this ); XUnmapWindow( qt_xdisplay(), frameId()); // destroying decoration would cause ugly visual effect destroyDecoration(); cleanGrouping(); if( !on_shutdown ) { workspace()->removeClient( this, Allowed ); // only when the window is being unmapped, not when closing down KWin // (NETWM sections 5.5,5.7) info->setDesktop( 0 ); desk = 0; info->setState( 0, info->state()); // reset all state flags } XDeleteProperty( qt_xdisplay(), client, atoms->kde_net_wm_user_creation_time); // TODO remove KDEFrameStrut property XReparentWindow( qt_xdisplay(), client, workspace()->rootWin(), x(), y()); XRemoveFromSaveSet( qt_xdisplay(), client ); XSelectInput( qt_xdisplay(), client, NoEventMask ); if( on_shutdown ) { // map the window, so it can be found after another WM is started XMapWindow( qt_xdisplay(), client ); // TODO preserve minimized, shaded etc. state? } else { // Make sure it's not mapped if the app unmapped it (#65279). The app // may do map+unmap before we initially map the window by calling rawShow() from manage(). XUnmapWindow( qt_xdisplay(), client ); } setMappingState( WithdrawnState ); // after all is done, tell the app client = None; XDestroyWindow( qt_xdisplay(), wrapper ); wrapper = None; XDestroyWindow( qt_xdisplay(), frame ); frame = None; deleteClient( this, Allowed ); } // like releaseWindow(), but this one is called when the window has been already destroyed // (e.g. the application closed it) void Client::destroyClient() { if (moveResizeMode) leaveMoveResize(); ++block_geometry; setModal( false ); hidden = true; // so that it's not considered visible anymore workspace()->clientHidden( this ); destroyDecoration(); cleanGrouping(); workspace()->removeClient( this, Allowed ); client = None; // invalidate XDestroyWindow( qt_xdisplay(), wrapper ); wrapper = None; XDestroyWindow( qt_xdisplay(), frame ); frame = None; --block_geometry; deleteClient( this, Allowed ); } void Client::updateDecoration( bool check_workspace_pos, bool force ) { if( !force && (( decoration == NULL && noBorder()) || ( decoration != NULL && !noBorder()))) return; bool do_show = false; ++block_geometry; if( force ) destroyDecoration(); if( !noBorder()) { decoration = workspace()->createDecoration( bridge ); // TODO check decoration's minimum size? decoration->init(); decoration->widget()->installEventFilter( this ); XReparentWindow( qt_xdisplay(), decoration->widget()->winId(), frameId(), 0, 0 ); decoration->widget()->lower(); decoration->borders( border_left, border_right, border_top, border_bottom ); int save_workarea_diff_x = workarea_diff_x; int save_workarea_diff_y = workarea_diff_y; move( calculateGravitation( false )); if( !isShade()) plainResize( sizeForClientSize( clientSize()), ForceGeometrySet ); else plainResize( sizeForClientSize( QSize( clientSize().width(), 0 ), SizemodeShaded ), ForceGeometrySet ); workarea_diff_x = save_workarea_diff_x; workarea_diff_y = save_workarea_diff_y; do_show = true; } else destroyDecoration(); if( check_workspace_pos ) checkWorkspacePosition(); --block_geometry; setGeometry( geometry(), ForceGeometrySet ); if( do_show ) decoration->widget()->show(); updateFrameStrut(); } void Client::destroyDecoration() { if( decoration != NULL ) { delete decoration; decoration = NULL; QPoint grav = calculateGravitation( true ); border_left = border_right = border_top = border_bottom = 0; setMask( QRegion()); // reset shape mask int save_workarea_diff_x = workarea_diff_x; int save_workarea_diff_y = workarea_diff_y; if( !isShade()) plainResize( clientSize(), ForceGeometrySet ); else plainResize( QSize( clientSize().width(), 0 ), ForceGeometrySet ); move( grav ); workarea_diff_x = save_workarea_diff_x; workarea_diff_y = save_workarea_diff_y; } } void Client::checkBorderSizes() { if( decoration == NULL ) return; int new_left, new_right, new_top, new_bottom; decoration->borders( new_left, new_right, new_top, new_bottom ); if( new_left == border_left && new_right == border_right && new_top == border_top && new_bottom == border_bottom ) return; ++block_geometry; move( calculateGravitation( true )); border_left = new_left; border_right = new_right; border_top = new_top; border_bottom = new_bottom; move( calculateGravitation( false )); plainResize( sizeForClientSize( clientSize()), ForceGeometrySet ); checkWorkspacePosition(); --block_geometry; setGeometry( geometry(), ForceGeometrySet ); } void Client::detectNoBorder() { if( Shape::hasShape( window()) || Motif::noBorder( window())) { noborder = true; // TODO for all window types? return; } switch( windowType()) { case NET::Desktop : case NET::Dock : case NET::TopMenu : case NET::Override : case NET::Splash : noborder = true; break; case NET::Unknown : case NET::Normal : case NET::Toolbar : case NET::Menu : case NET::Dialog : case NET::Utility : noborder = false; break; default: assert( false ); } } void Client::updateFrameStrut() { // TODO KDEFrameStrut je ale pitome jmeno NETStrut strut; strut.left = border_left; strut.right = border_right; strut.top = border_top; strut.bottom = border_bottom; info->setKDEFrameStrut( strut ); } // Resizes the decoration, and makes sure the decoration widget gets resize event // even if the size hasn't changed. This is needed to make sure the decoration // re-layouts (e.g. when options()->moveResizeMaximizedWindows() changes, // the decoration may turn on/off some borders, but the actual size // of the decoration stays the same). void Client::resizeDecoration( const QSize& s ) { if( decoration == NULL ) return; QSize oldsize = decoration->widget()->size(); decoration->resize( s ); if( oldsize == s ) { QResizeEvent e( s, oldsize ); QApplication::sendEvent( decoration->widget(), &e ); } } bool Client::noBorder() const { return noborder || isFullScreen() || user_noborder; } bool Client::userCanSetNoBorder() const { return !noborder && !isFullScreen() && !isShade(); } bool Client::isUserNoBorder() const { return user_noborder; } void Client::setUserNoBorder( bool set ) { if( !userCanSetNoBorder()) return; if( user_noborder == set ) return; user_noborder = set; updateDecoration( true, false ); } void Client::updateShape() { if ( shape() ) XShapeCombineShape(qt_xdisplay(), frameId(), ShapeBounding, clientPos().x(), clientPos().y(), window(), ShapeBounding, ShapeSet); else XShapeCombineMask( qt_xdisplay(), frameId(), ShapeBounding, 0, 0, None, ShapeSet); // workaround for #19644 - shaped windows shouldn't have decoration if( shape() && !noBorder()) { noborder = true; updateDecoration( true ); } } void Client::setMask( const QRegion& reg, int mode ) { _mask = reg; if( reg.isNull()) XShapeCombineMask( qt_xdisplay(), frameId(), ShapeBounding, 0, 0, None, ShapeSet ); else if( mode == X::Unsorted ) XShapeCombineRegion( qt_xdisplay(), frameId(), ShapeBounding, 0, 0, reg.handle(), ShapeSet ); else { QMemArray< QRect > rects = reg.rects(); XRectangle* xrects = new XRectangle[ rects.count() ]; for( unsigned int i = 0; i < rects.count(); ++i ) { xrects[ i ].x = rects[ i ].x(); xrects[ i ].y = rects[ i ].y(); xrects[ i ].width = rects[ i ].width(); xrects[ i ].height = rects[ i ].height(); } XShapeCombineRectangles( qt_xdisplay(), frameId(), ShapeBounding, 0, 0, xrects, rects.count(), ShapeSet, mode ); delete[] xrects; } } QRegion Client::mask() const { if( _mask.isEmpty()) return QRegion( 0, 0, width(), height()); return _mask; } void Client::hideClient( bool hide ) { if( hidden == hide ) return; hidden = hide; info->setState( hidden ? NET::Hidden : 0, NET::Hidden ); if( hidden ) { setMappingState( IconicState ); rawHide(); setSkipTaskbar( true, false ); // also hide from taskbar } else // !hidden { setSkipTaskbar( original_skip_taskbar, false ); if( isOnCurrentDesktop()) { if( isShown( false )) setMappingState( NormalState ); rawShow(); // is either visible or shaded } } } /* Returns whether the window is minimizable or not */ bool Client::isMinimizable() const { if( !wantsTabFocus() // SELI co NET::Utility? a proc wantsTabFocus() - skiptaskbar? ? || ( isSpecialWindow() && !isOverride())) return false; if( isTransient()) { // transients may be minimized only if mainwindow is not shown ClientList mainclients = mainClients(); for( ClientList::ConstIterator it = mainclients.begin(); it != mainclients.end(); ++it ) if( (*it)->isShown( true )) return false; } return true; } /*! Minimizes this client plus its transients */ void Client::minimize( bool avoid_animation ) { if ( !isMinimizable() || isMinimized()) return; minimized = true; Notify::raise( Notify::Minimize ); // SELI mainClients().isEmpty() ??? - and in unminimize() too if ( mainClients().isEmpty() && isOnCurrentDesktop() && !avoid_animation ) animateMinimizeOrUnminimize( true ); // was visible or shaded setMappingState( IconicState ); info->setState( NET::Hidden, NET::Hidden ); rawHide(); updateAllowedActions(); workspace()->updateMinimizedOfTransients( this ); } void Client::unminimize( bool avoid_animation ) { if( !isMinimized()) return; Notify::raise( Notify::UnMinimize ); minimized = false; info->setState( 0, NET::Hidden ); if( isOnCurrentDesktop()) { if( mainClients().isEmpty() && !avoid_animation ) animateMinimizeOrUnminimize( FALSE ); if( isShown( false )) setMappingState( NormalState ); rawShow(); // is either visible or shaded } updateAllowedActions(); workspace()->updateMinimizedOfTransients( this ); } extern bool blockAnimation; void Client::animateMinimizeOrUnminimize( bool minimize ) { if ( blockAnimation ) return; if ( !options->animateMinimize ) return; if( decoration != NULL && decoration->animateMinimize( minimize )) return; // decoration did it // the function is a bit tricky since it will ensure that an // animation action needs always the same time regardless of the // performance of the machine or the X-Server. float lf,rf,tf,bf,step; int speed = options->animateMinimizeSpeed; if ( speed > 10 ) speed = 10; if ( speed < 0 ) speed = 0; step = 40. * (11 - speed ); NETRect r = info->iconGeometry(); QRect icongeom( r.pos.x, r.pos.y, r.size.width, r.size.height ); if ( !icongeom.isValid() ) return; QPixmap pm = animationPixmap( minimize ? width() : icongeom.width() ); QRect before, after; if ( minimize ) { before = QRect( x(), y(), width(), pm.height() ); after = QRect( icongeom.x(), icongeom.y(), icongeom.width(), pm.height() ); } else { before = QRect( icongeom.x(), icongeom.y(), icongeom.width(), pm.height() ); after = QRect( x(), y(), width(), pm.height() ); } lf = (after.left() - before.left())/step; rf = (after.right() - before.right())/step; tf = (after.top() - before.top())/step; bf = (after.bottom() - before.bottom())/step; grabXServer(); QRect area = before; QRect area2; QPixmap pm2; QTime t; t.start(); float diff; QPainter p ( workspace()->desktopWidget() ); bool need_to_clear = FALSE; QPixmap pm3; do { if (area2 != area) { pm = animationPixmap( area.width() ); pm2 = QPixmap::grabWindow( qt_xrootwin(), area.x(), area.y(), area.width(), area.height() ); p.drawPixmap( area.x(), area.y(), pm ); if ( need_to_clear ) { p.drawPixmap( area2.x(), area2.y(), pm3 ); need_to_clear = FALSE; } area2 = area; } XFlush(qt_xdisplay()); XSync( qt_xdisplay(), FALSE ); diff = t.elapsed(); if (diff > step) diff = step; area.setLeft(before.left() + int(diff*lf)); area.setRight(before.right() + int(diff*rf)); area.setTop(before.top() + int(diff*tf)); area.setBottom(before.bottom() + int(diff*bf)); if (area2 != area ) { if ( area2.intersects( area ) ) p.drawPixmap( area2.x(), area2.y(), pm2 ); else { // no overlap, we can clear later to avoid flicker pm3 = pm2; need_to_clear = TRUE; } } } while ( t.elapsed() < step); if (area2 == area || need_to_clear ) p.drawPixmap( area2.x(), area2.y(), pm2 ); p.end(); ungrabXServer(); } /*! The pixmap shown during (un)minimalization animation */ QPixmap Client::animationPixmap( int w ) { QFont font = options->font(isActive()); QFontMetrics fm( font ); QPixmap pm( w, fm.lineSpacing() ); pm.fill( options->color(Options::ColorTitleBar, isActive() || isMinimized() ) ); QPainter p( &pm ); p.setPen(options->color(Options::ColorFont, isActive() || isMinimized() )); p.setFont(options->font(isActive())); p.drawText( pm.rect(), AlignLeft|AlignVCenter|SingleLine, caption() ); return pm; } bool Client::isShadeable() const { return !isSpecialWindow() && !noBorder(); } void Client::setShade( ShadeMode mode ) { if( !isShadeable()) return; if( shade_mode == mode ) return; bool was_shade = isShade(); ShadeMode was_shade_mode = shade_mode; shade_mode = mode; if( was_shade == isShade()) return; // no real change in shaded state if( shade_mode == ShadeNormal ) { if ( isShown( true ) && isOnCurrentDesktop()) Notify::raise( Notify::ShadeUp ); } else if( shade_mode == ShadeNone ) { if( isShown( true ) && isOnCurrentDesktop()) Notify::raise( Notify::ShadeDown ); } assert( decoration != NULL ); // noborder windows can't be shaded ++block_geometry; // decorations may turn off some borders when shaded decoration->borders( border_left, border_right, border_top, border_bottom ); int as = options->animateShade? 10 : 1; // TODO all this unmapping, resizing etc. feels too much duplicated from elsewhere if ( isShade()) { // shade_mode == ShadeNormal int h = height(); shade_geometry_change = true; QSize s( sizeForClientSize( QSize( clientSize().width(), 0), SizemodeShaded ) ); XSelectInput( qt_xdisplay(), wrapper, ClientWinMask ); // avoid getting UnmapNotify XUnmapWindow( qt_xdisplay(), wrapper ); XUnmapWindow( qt_xdisplay(), client ); XSelectInput( qt_xdisplay(), wrapper, ClientWinMask | SubstructureNotifyMask ); // FRAME repaint( FALSE ); // bool wasStaticContents = testWFlags( WStaticContents ); // setWFlags( WStaticContents ); int step = QMAX( 4, QABS( h - s.height() ) / as )+1; do { h -= step; XResizeWindow( qt_xdisplay(), frameId(), s.width(), h ); resizeDecoration( QSize( s.width(), h )); QApplication::syncX(); } while ( h > s.height() + step ); // if ( !wasStaticContents ) // clearWFlags( WStaticContents ); shade_geometry_change = false; plainResize( s ); if( isActive()) { if( was_shade_mode == ShadeHover ) workspace()->activateNextClient( this ); else workspace()->focusToNull(); } } else { int h = height(); shade_geometry_change = true; QSize s( sizeForClientSize( clientSize(), SizemodeShaded )); // FRAME bool wasStaticContents = testWFlags( WStaticContents ); // setWFlags( WStaticContents ); int step = QMAX( 4, QABS( h - s.height() ) / as )+1; do { h += step; XResizeWindow( qt_xdisplay(), frameId(), s.width(), h ); resizeDecoration( QSize( s.width(), h )); // assume a border // we do not have time to wait for X to send us paint events // FRAME repaint( 0, h - step-5, width(), step+5, TRUE); QApplication::syncX(); } while ( h < s.height() - step ); // if ( !wasStaticContents ) // clearWFlags( WStaticContents ); shade_geometry_change = false; plainResize( s ); if( shade_mode == ShadeHover || shade_mode == ShadeActivated ) setActive( TRUE ); XMapWindow( qt_xdisplay(), wrapperId()); XMapWindow( qt_xdisplay(), window()); if ( isActive() ) workspace()->requestFocus( this ); } --block_geometry; setGeometry( geometry(), ForceGeometrySet ); info->setState( isShade() ? NET::Shaded : 0, NET::Shaded ); info->setState( isShown( false ) ? 0 : NET::Hidden, NET::Hidden ); setMappingState( isShown( false ) && isOnCurrentDesktop() ? NormalState : IconicState ); updateAllowedActions(); workspace()->updateMinimizedOfTransients( this ); decoration->shadeChange(); } void Client::shadeHover() { setShade( ShadeHover ); delete shadeHoverTimer; shadeHoverTimer = 0; } void Client::toggleShade() { // if the mode is ShadeHover or ShadeActive, cancel shade too setShade( shade_mode == ShadeNone ? ShadeNormal : ShadeNone ); } void Client::virtualDesktopChange() { if( hidden || minimized ) return; // no visibility change // from here it can be only shaded or normally shown if( isOnCurrentDesktop()) { if( !isShade()) setMappingState( NormalState ); rawShow(); } else { if( !isShade()) setMappingState( IconicState ); rawHide(); } } /*! Sets the client window's mapping state. Possible values are WithdrawnState, IconicState, NormalState. */ void Client::setMappingState(int s) { assert( client != None ); if( mapping_state == s ) return; bool was_unmanaged = ( mapping_state == WithdrawnState ); mapping_state = s; if( mapping_state == WithdrawnState ) { XDeleteProperty( qt_xdisplay(), window(), qt_wm_state ); return; } assert( s == NormalState || s == IconicState ); unsigned long data[2]; data[0] = (unsigned long) s; data[1] = (unsigned long) None; XChangeProperty(qt_xdisplay(), window(), qt_wm_state, qt_wm_state, 32, PropModeReplace, (unsigned char *)data, 2); if( was_unmanaged ) // force setting the geometry, manage() did block_geometry = 1 { assert( block_geometry == 1 ); --block_geometry; setGeometry( frame_geometry, ForceGeometrySet ); } } /*! Reimplemented to map the managed window in the window wrapper. Proper mapping state should be set before showing the client. */ void Client::rawShow() { if( decoration != NULL ) decoration->widget()->show(); // not really necessary, but let it know the state XMapWindow( qt_xdisplay(), frame ); if( !isShade()) { XMapWindow( qt_xdisplay(), wrapper ); XMapWindow( qt_xdisplay(), client ); } } /*! Reimplemented to unmap the managed window in the window wrapper. Also informs the workspace. Proper mapping state should be set before hiding the client. */ void Client::rawHide() { // Here it may look like a race condition, as some other client might try to unmap // the window between these two XSelectInput() calls. However, they're supposed to // use XWithdrawWindow(), which also sends a synthetic event to the root window, // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify // will be missed is also very minimal, so I don't think it's needed to grab the server // here. XSelectInput( qt_xdisplay(), wrapper, ClientWinMask ); // avoid getting UnmapNotify XUnmapWindow( qt_xdisplay(), frame ); XUnmapWindow( qt_xdisplay(), wrapper ); XUnmapWindow( qt_xdisplay(), client ); XSelectInput( qt_xdisplay(), wrapper, ClientWinMask | SubstructureNotifyMask ); if( decoration != NULL ) decoration->widget()->hide(); // not really necessary, but let it know the state workspace()->clientHidden( this ); } static void sendClientMessage(Window w, Atom a, long x) { XEvent ev; long mask; memset(&ev, 0, sizeof(ev)); ev.xclient.type = ClientMessage; ev.xclient.window = w; ev.xclient.message_type = a; ev.xclient.format = 32; ev.xclient.data.l[0] = x; ev.xclient.data.l[1] = qt_x_time; mask = 0L; if (w == qt_xrootwin()) mask = SubstructureRedirectMask; /* magic! */ XSendEvent(qt_xdisplay(), w, False, mask, &ev); } /* Returns whether the window may be closed (have a close button) */ bool Client::isCloseable() const { return motif_may_close && ( !isSpecialWindow() || isOverride()); // TODO is NET::Override special? } /*! Closes the window by either sending a delete_window message or using XKill. */ void Client::closeWindow() { if( !isCloseable()) return; // Update user time, needed for whole group, because the window may create a confirming dialog, // and this window's user time wouldn't apply to it // This is needed even for apps without support for user timestamp (e.g. nedit), so updating // user timestamp in apps on WM_DELETE_WINDOW is not an option (and I'm not sure if it would be right) group()->updateUserTime(); if ( Pdeletewindow ) { Notify::raise( Notify::Close ); sendClientMessage( window(), atoms->wm_protocols, atoms->wm_delete_window); pingWindow(); } else { // client will not react on wm_delete_window. We have not choice // but destroy his connection to the XServer. killWindow(); } } /*! Kills the window via XKill */ void Client::killWindow() { kdDebug( 1212 ) << "Client::killWindow():" << caption() << endl; // not sure if we need an Notify::Kill or not.. until then, use // Notify::Close Notify::raise( Notify::Close ); if( isDialog()) Notify::raise( Notify::TransDelete ); if( isNormalWindow()) Notify::raise( Notify::Delete ); killProcess( false ); // always kill this client at the server XKillClient(qt_xdisplay(), window() ); destroyClient(); } // send a ping to the window using _NET_WM_PING if possible // if it doesn't respond within a reasonable time, it will be // killed void Client::pingWindow() { if( !Pping ) return; // can't ping :( if( ping_timer != NULL ) return; // pinging already ping_timer = new QTimer( this ); connect( ping_timer, SIGNAL( timeout()), SLOT( pingTimeout())); ping_timer->start( 5000, true ); // give it 5 seconds ping_timestamp = qt_x_time; workspace()->sendPingToWindow( window(), ping_timestamp ); } void Client::gotPing( Time timestamp ) { if( timestamp != ping_timestamp ) return; delete ping_timer; ping_timer = NULL; if( process_killer != NULL ) { process_killer->kill(); delete process_killer; process_killer = NULL; } } void Client::pingTimeout() { kdDebug( 1212 ) << "Ping timeout:" << caption() << endl; delete ping_timer; ping_timer = NULL; killProcess( true, ping_timestamp ); } void Client::killProcess( bool ask, Time timestamp ) { if( process_killer != NULL ) return; Q_ASSERT( !ask || timestamp != CurrentTime ); QCString machine = wmClientMachine(); pid_t pid = info->pid(); if( pid <= 0 || machine.isEmpty()) // needed properties missing return; kdDebug( 1212 ) << "Kill process:" << pid << "(" << machine << ")" << endl; if( !ask ) { if( machine != "localhost" ) { KProcess proc; proc << "xon" << machine << "kill" << pid; proc.start( KProcess::DontCare ); } else ::kill( pid, SIGTERM ); } else { // SELI TODO handle the window created by handler specially (on top,urgent?) process_killer = new KProcess( this ); *process_killer << KStandardDirs::findExe( "kwin_killer_helper" ) << "--pid" << QCString().setNum( pid ) << "--hostname" << machine << "--windowname" << caption().utf8() << "--applicationname" << resourceClass() << "--wid" << QCString().setNum( window()) << "--timestamp" << QCString().setNum( timestamp ); connect( process_killer, SIGNAL( processExited( KProcess* )), SLOT( processKillerExited())); if( !process_killer->start( KProcess::NotifyOnExit )) { delete process_killer; process_killer = NULL; return; } } } void Client::processKillerExited() { kdDebug( 1212 ) << "Killer exited" << endl; delete process_killer; process_killer = NULL; } void Client::setSkipTaskbar( bool b, bool from_outside ) { if( from_outside ) original_skip_taskbar = b; if ( b == skipTaskbar() ) return; skip_taskbar = b; info->setState( b?NET::SkipTaskbar:0, NET::SkipTaskbar ); } void Client::setSkipPager( bool b ) { if ( b == skipPager() ) return; skip_pager = b; info->setState( b?NET::SkipPager:0, NET::SkipPager ); } void Client::setModal( bool m ) { // Qt-3.2 can have even modal normal windows :( if( modal == m ) return; modal = m; if( !modal ) return; // changing modality for a mapped window is weird (?) // _NET_WM_STATE_MODAL should possibly rather be _NET_WM_WINDOW_TYPE_MODAL_DIALOG } void Client::toggleOnAllDesktops() { setOnAllDesktops( !isOnAllDesktops()); } void Client::setDesktop( int desktop ) { if( desktop != NET::OnAllDesktops ) // do range check desktop = KMAX( 1, KMIN( workspace()->numberOfDesktops(), desktop )); if( desk == desktop ) return; int was_desk = desk; desk = desktop; info->setDesktop( desktop ); if(( was_desk == NET::OnAllDesktops ) != ( desktop == NET::OnAllDesktops )) { // onAllDesktops changed if ( isShown( true )) Notify::raise( isOnAllDesktops() ? Notify::OnAllDesktops : Notify::NotOnAllDesktops ); workspace()->updateOnAllDesktopsOfTransients( this ); } if( decoration != NULL ) decoration->desktopChange(); virtualDesktopChange(); // hide/show if needed } void Client::setOnAllDesktops( bool b ) { if(( b && isOnAllDesktops()) || ( !b && !isOnAllDesktops())) return; if( b ) setDesktop( NET::OnAllDesktops ); else setDesktop( workspace()->currentDesktop()); } bool Client::isOnCurrentDesktop() const { return isOnDesktop( workspace()->currentDesktop()); } /*! Puts the focus on this window. Clients should never calls this themselves, instead they should use Workspace::requestFocus(). */ void Client::takeFocus( bool force, allowed_t ) { if ( !force && ( isTopMenu() || isDock() || isSplash()) ) return; // toplevel menus and dock windows don't take focus if not forced #ifndef NDEBUG static Time previous_focus_timestamp; static Client* previous_client; if( previous_focus_timestamp == qt_x_time && previous_client != this ) { kdWarning( 1212 ) << "Repeated use of the same X timestamp for focus" << endl; kdDebug( 1212 ) << kdBacktrace() << endl; } previous_focus_timestamp = qt_x_time; previous_client = this; #endif if ( input ) { XSetInputFocus( qt_xdisplay(), window(), RevertToPointerRoot, qt_x_time ); } if ( Ptakefocus ) sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus); } /*! Returns whether the window provides context help or not. If it does, you should show a help menu item or a help button like '?' and call contextHelp() if this is invoked. \sa contextHelp() */ bool Client::providesContextHelp() const { return Pcontexthelp; } /*! Invokes context help on the window. Only works if the window actually provides context help. \sa providesContextHelp() */ void Client::showContextHelp() { if ( Pcontexthelp ) { sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help); QWhatsThis::enterWhatsThisMode(); // SELI? } } /*! Fetches the window's caption (WM_NAME property). It will be stored in the client's caption(). */ KWIN_COMPARE_PREDICATE( FetchNameInternalPredicate, const Client*, (!cl->isSpecialWindow() || cl->isToolbar()) && cl != value && cl->caption() == value->caption()); void Client::fetchName() { QString s; if ( info->name() && info->name()[ 0 ] != '\0' ) s = QString::fromUtf8( info->name() ); else s = KWin::readNameProperty( window(), XA_WM_NAME ); if ( s != cap_normal ) { bool reset_name = cap_normal.isEmpty(); for( unsigned int i = 0; i < s.length(); ++i ) if( !s[ i ].isPrint()) s[ i ] = ' '; cap_normal = s; bool was_suffix = ( !cap_suffix.isEmpty()); cap_suffix = QString::null; if ( ( !isSpecialWindow() || isToolbar()) && workspace()->findClient( FetchNameInternalPredicate( this ))) { int i = 2; do { cap_suffix = " <" + QString::number(i) + ">"; i++; } while ( workspace()->findClient( FetchNameInternalPredicate( this ))); info->setVisibleName( caption().utf8() ); reset_name = false; } if(( was_suffix && cap_suffix.isEmpty() || reset_name )) // if it was new window, it may have old value still set, if the window is reused { info->setVisibleName( "" ); // remove info->setVisibleIconName( "" ); // remove } else if( !cap_suffix.isEmpty() && !cap_iconic.isEmpty()) // keep the same suffix in iconic name if it's set info->setVisibleIconName( ( cap_iconic + cap_suffix ).utf8() ); if( isManaged() && decoration != NULL ) decoration->captionChange(); } } void Client::fetchIconicName() { QString s; if ( info->iconName() && info->iconName()[ 0 ] != '\0' ) s = QString::fromUtf8( info->iconName() ); else s = KWin::readNameProperty( window(), XA_WM_ICON_NAME ); if ( s != cap_iconic ) { cap_iconic = s; if( !cap_suffix.isEmpty() && !cap_iconic.isEmpty()) // keep the same suffix in iconic name if it's set info->setVisibleIconName( ( s + cap_suffix ).utf8() ); } } /*!\reimp */ QString Client::caption() const { return cap_normal + cap_suffix; } void Client::getWMHints() { XWMHints *hints = XGetWMHints(qt_xdisplay(), window() ); input = true; window_group = None; urgency = false; if ( hints ) { if( hints->flags & InputHint ) input = hints->input; if( hints->flags & WindowGroupHint ) window_group = hints->window_group; urgency = ( hints->flags & UrgencyHint ) ? true : false; // true/false needed, it's uint bitfield XFree( (char*)hints ); } checkGroup(); updateUrgency(); updateAllowedActions(); // group affects isMinimizable() } void Client::readIcons( Window win, QPixmap* icon, QPixmap* miniicon ) { // get the icons, allow scaling if( icon != NULL ) *icon = KWin::icon( win, 32, 32, TRUE, KWin::NETWM | KWin::WMHints ); if( miniicon != NULL ) if( icon == NULL || !icon->isNull()) *miniicon = KWin::icon( win, 16, 16, TRUE, KWin::NETWM | KWin::WMHints ); else *miniicon = QPixmap(); } void Client::getIcons() { // first read icons from the window itself readIcons( window(), &icon_pix, &miniicon_pix ); if( icon_pix.isNull()) { // then try window group icon_pix = group()->icon(); miniicon_pix = group()->miniIcon(); } if( icon_pix.isNull() && isTransient()) { // then mainclients ClientList mainclients = mainClients(); for( ClientList::ConstIterator it = mainclients.begin(); it != mainclients.end() && icon_pix.isNull(); ++it ) { icon_pix = (*it)->icon(); miniicon_pix = (*it)->miniIcon(); } } if( icon_pix.isNull()) { // and if nothing else, load icon from classhint or xapp icon icon_pix = KWin::icon( window(), 32, 32, TRUE, KWin::ClassHint | KWin::XApp ); miniicon_pix = KWin::icon( window(), 16, 16, TRUE, KWin::ClassHint | KWin::XApp ); } if( isManaged() && decoration != NULL ) decoration->iconChange(); } void Client::getWindowProtocols() { Atom *p; int i,n; Pdeletewindow = 0; Ptakefocus = 0; Pcontexthelp = 0; Pping = 0; if (XGetWMProtocols(qt_xdisplay(), window(), &p, &n)) { for (i = 0; i < n; i++) if (p[i] == atoms->wm_delete_window) Pdeletewindow = 1; else if (p[i] == atoms->wm_take_focus) Ptakefocus = 1; else if (p[i] == atoms->net_wm_context_help) Pcontexthelp = 1; else if (p[i] == atoms->net_wm_ping) Pping = 1; if (n>0) XFree(p); } } static int nullErrorHandler(Display *, XErrorEvent *) { return 0; } /*! Returns WM_WINDOW_ROLE property for a given window. */ QCString Client::staticWindowRole(WId w) { return getStringProperty(w, qt_window_role); } /*! Returns SM_CLIENT_ID property for a given window. */ QCString Client::staticSessionId(WId w) { return getStringProperty(w, qt_sm_client_id); } /*! Returns WM_COMMAND property for a given window. */ QCString Client::staticWmCommand(WId w) { return getStringProperty(w, XA_WM_COMMAND, ' '); } /*! Returns WM_CLIENT_MACHINE property for a given window. Local machine is always returned as "localhost". */ QCString Client::staticWmClientMachine(WId w) { QCString result = getStringProperty(w, XA_WM_CLIENT_MACHINE); if (result.isEmpty()) { result = "localhost"; } else { // special name for the local machine (localhost) char hostnamebuf[80]; if (gethostname (hostnamebuf, sizeof hostnamebuf) >= 0) { hostnamebuf[sizeof(hostnamebuf)-1] = 0; if (result == hostnamebuf) result = "localhost"; char *dot = strchr(hostnamebuf, '.'); if (dot && !(*dot = 0) && result == hostnamebuf) result = "localhost"; } } return result; } /*! Returns WM_CLIENT_LEADER property for a given window. */ Window Client::staticWmClientLeader(WId w) { Atom type; int format, status; unsigned long nitems = 0; unsigned long extra = 0; unsigned char *data = 0; Window result = w; XErrorHandler oldHandler = XSetErrorHandler(nullErrorHandler); status = XGetWindowProperty( qt_xdisplay(), w, atoms->wm_client_leader, 0, 10000, FALSE, XA_WINDOW, &type, &format, &nitems, &extra, &data ); XSetErrorHandler(oldHandler); if (status == Success ) { if (data && nitems > 0) result = *((Window*) data); XFree(data); } return result; } void Client::getWmClientLeader() { wmClientLeaderWin = staticWmClientLeader(window()); } /*! Returns sessionId for this client, taken either from its window or from the leader window. */ QCString Client::sessionId() { QCString result = staticSessionId(window()); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin!=window()) result = staticSessionId(wmClientLeaderWin); return result; } /*! Returns command property for this client, taken either from its window or from the leader window. */ QCString Client::wmCommand() { QCString result = staticWmCommand(window()); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin!=window()) result = staticWmCommand(wmClientLeaderWin); return result; } /*! Returns client machine for this client, taken either from its window or from the leader window. */ QCString Client::wmClientMachine() const { QCString result = staticWmClientMachine(window()); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin!=window()) result = staticWmClientMachine(wmClientLeaderWin); return result; } /*! Returns client leader window for this client. Returns the client window itself if no leader window is defined. */ Window Client::wmClientLeader() const { if (wmClientLeaderWin) return wmClientLeaderWin; return window(); } bool Client::wantsTabFocus() const { return ( isNormalWindow() || isDialog() || isOverride()) && ( input || Ptakefocus ) && !skip_taskbar; } bool Client::wantsInput() const { return input; } /*! Returns whether the window is moveable or has a fixed position. !isMovable implies !isResizable. */ bool Client::isMovable() const { return motif_may_move && !isFullScreen() && ( !isSpecialWindow() || isOverride() || isSplash() || isToolbar()) && // allow moving of splashscreens :) ( maximizeMode() != MaximizeFull || options->moveResizeMaximizedWindows() ); } bool Client::isDesktop() const { return windowType() == NET::Desktop; } bool Client::isDock() const { return windowType() == NET::Dock; } bool Client::isTopMenu() const { return windowType() == NET::TopMenu; } bool Client::isMenu() const { return windowType() == NET::Menu && !isTopMenu(); // because of backwards comp. } bool Client::isToolbar() const { return windowType() == NET::Toolbar; } bool Client::isOverride() const { return windowType() == NET::Override; } bool Client::isSplash() const { return windowType() == NET::Splash; } bool Client::isUtility() const { return windowType() == NET::Utility; } bool Client::isDialog() const { return windowType() == NET::Dialog; } bool Client::isNormalWindow() const { return windowType() == NET::Normal; } bool Client::isSpecialWindow() const { return isDesktop() || isDock() || isSplash() || isTopMenu() || ( isOverride() && !isFullScreen())// SELI is NET::Override special or not? || isToolbar(); // TODO } NET::WindowType Client::windowType( bool strict, int supported_types ) const { NET::WindowType wt = info->windowType( supported_types ); // TODO is this 'strict' really needed (and used?) if( !strict ) { // hacks here if( wt == NET::Menu ) { // ugly hack to support the times when NET::Menu meant NET::TopMenu // if it's as wide as the screen, not very high and has its upper-left // corner a bit above the screen's upper-left cornet, it's a topmenu if( x() == 0 && y() < 0 && y() > -10 && height() < 100 && abs( width() - workspace()->clientArea( FullArea, this ).width()) < 10 ) wt = NET::TopMenu; } const char* const oo_prefix = "openoffice.org"; // QCString has no startsWith() // oo_prefix is lowercase, because resourceClass() is forced to be lowercase if( qstrncmp( resourceClass(), oo_prefix, strlen( oo_prefix )) == 0 && wt == NET::Dialog ) wt = NET::Normal; // see bug #66065 if( wt == NET::Unknown ) // this is more or less suggested in NETWM spec wt = isTransient() ? NET::Dialog : NET::Normal; } return wt; } /*! Sets an appropriate cursor shape for the logical mouse position \a m */ void Client::setCursor( Position m ) { if ( !isResizable() || isShade() || noBorder()) { setCursor( arrowCursor ); return; } switch ( m ) { case PositionTopLeft: case PositionBottomRight: setCursor( sizeFDiagCursor ); break; case PositionBottomLeft: case PositionTopRight: setCursor( sizeBDiagCursor ); break; case PositionTop: case PositionBottom: setCursor( sizeVerCursor ); break; case PositionLeft: case PositionRight: setCursor( sizeHorCursor ); break; default: if( buttonDown ) setCursor( sizeAllCursor ); else setCursor( arrowCursor ); break; } } // TODO mit nejake checkCursor(), ktere se zavola v manage() a pri vecech, kdy by se kurzor mohl zmenit? void Client::setCursor( const QCursor& c ) { if( c.handle() == cursor.handle()) return; cursor = c; if( decoration != NULL ) decoration->widget()->setCursor( cursor ); XDefineCursor( qt_xdisplay(), frameId(), cursor.handle()); } Client::Position Client::mousePosition( const QPoint& p ) const { if( decoration != NULL ) return decoration->mousePosition( p ); return PositionCenter; } void Client::updateAllowedActions( bool force ) { if( !isManaged() && !force ) return; unsigned long old_allowed_actions = allowed_actions; allowed_actions = 0; if( isMovable()) allowed_actions |= NET::ActionMove; if( isResizable()) allowed_actions |= NET::ActionResize; if( isMinimizable()) allowed_actions |= NET::ActionMinimize; if( isShadeable()) allowed_actions |= NET::ActionShade; // sticky state not supported if( isMaximizable()) allowed_actions |= NET::ActionMax; if( userCanSetFullScreen()) allowed_actions |= NET::ActionFullScreen; allowed_actions |= NET::ActionChangeDesktop; // always (pagers shouldn't show Docks etc.) if( isCloseable()) allowed_actions |= NET::ActionClose; if( old_allowed_actions == allowed_actions ) return; // TODO this could be delayed and compressed - it's only for pagers etc. anyway info->setAllowedActions( allowed_actions ); // TODO this should also tell the decoration, so that it can update the buttons } void Client::autoRaise() { workspace()->raiseClient( this ); delete autoRaiseTimer; autoRaiseTimer = 0; } void Client::cancelAutoRaise() { delete autoRaiseTimer; autoRaiseTimer = 0; } #ifdef NDEBUG kndbgstream& operator<<( kndbgstream& stream, const Client* ) { return stream; } kndbgstream& operator<<( kndbgstream& stream, const ClientList& ) { return stream; } kndbgstream& operator<<( kndbgstream& stream, const ConstClientList& ) { return stream; } #else kdbgstream& operator<<( kdbgstream& stream, const Client* cl ) { if( cl == NULL ) return stream << "\'NULL_CLIENT\'"; return stream << "\'ID:" << cl->window() << ";WMCLASS:" << cl->resourceClass() << ":" << cl->resourceName() << ";Caption:" << cl->caption() << "\'"; } kdbgstream& operator<<( kdbgstream& stream, const ClientList& list ) { stream << "LIST:("; bool first = true; for( ClientList::ConstIterator it = list.begin(); it != list.end(); ++it ) { if( !first ) stream << ":"; first = false; stream << *it; } stream << ")"; return stream; } kdbgstream& operator<<( kdbgstream& stream, const ConstClientList& list ) { stream << "LIST:("; bool first = true; for( ConstClientList::ConstIterator it = list.begin(); it != list.end(); ++it ) { if( !first ) stream << ":"; first = false; stream << *it; } stream << ")"; return stream; } #endif QPixmap * kwin_get_menu_pix_hack() { static QPixmap p; if ( p.isNull() ) p = SmallIcon( "bx2" ); return &p; } } // namespace #include "client.moc" Index: trunk/kdebase/kwin/client.h =================================================================== --- trunk/kdebase/kwin/client.h (revision 298356) +++ trunk/kdebase/kwin/client.h (revision 298357) @@ -1,848 +1,858 @@ /***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ #ifndef KWIN_CLIENT_H #define KWIN_CLIENT_H #include "utils.h" #include "options.h" #include "workspace.h" #include "kdecoration.h" #include #include #include #include #include #include #include #include #include #include class QTimer; class KProcess; class KStartupInfoData; namespace KWinInternal { class Workspace; class Client; class WinInfo; class SessionInfo; class Bridge; class Client : public QObject, public KDecorationDefines { Q_OBJECT public: Client( Workspace *ws ); Window window() const; Window frameId() const; Window wrapperId() const; Window decorationId() const; Workspace* workspace() 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 bool hasTransient( const Client* c, bool indirect ) const; const ClientList& transients() const; // is not indirect void checkTransient( Window w ); Client* findModal(); const Group* group() const; Group* group(); // prefer isXXX() instead NET::WindowType windowType( bool strict = false, int supported_types = SUPPORTED_WINDOW_TYPES_MASK ) const; QRect geometry() const; QSize size() const; QSize minSize() const; QPoint pos() const; QRect rect() const; int x() const; int y() const; int width() const; int height() const; QPoint clientPos() const; // inside of geometry() QSize clientSize() const; bool windowEvent( XEvent* e ); virtual bool eventFilter( QObject* o, QEvent* e ); bool manage( Window w, bool isMapped ); void releaseWindow( bool on_shutdown = false ); 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 SizemodeShaded // shaded - height == 0 }; QSize adjustedSize( const QSize&, Sizemode mode = SizemodeAny ) const; QPixmap icon() const; QPixmap miniIcon() const; bool isActive() const; void setActive( bool ); int desktop() const; void setDesktop( int ); bool isOnDesktop( int d ) const; bool isOnCurrentDesktop() const; bool isOnAllDesktops() const; void setOnAllDesktops( bool set ); // !isMinimized() && not hidden, i.e. normally visible on some virtual desktop bool isShown( bool shaded_is_shown ) const; enum ShadeMode { ShadeNone, // not shaded ShadeNormal, // normally shaded - isShade() is true only here ShadeHover, // "shaded", but visible due to hover unshade ShadeActivated // "shaded", but visible due to alt+tab to the window }; 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 maximizeMode() const; bool isMinimizable() const; void setMaximize( bool vertically, bool horizontally ); void setFullScreen( bool set, bool user ); bool isFullScreen() const; bool isFullScreenable( bool fullscreen_hack = false ) 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 isUserNoBorder() const; void setUserNoBorder( bool set ); bool userCanSetNoBorder() const; bool noBorder() 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(); void setModal( bool modal ); bool isModal() const; bool storeSettings() const; void setStoreSettings( bool ); // auxiliary functions, depend on the windowType bool wantsTabFocus() const; bool wantsInput() const; bool hasNETSupport() const; bool isMovable() const; bool isDesktop() const; bool isDock() const; bool isToolbar() const; bool isTopMenu() const; bool isMenu() const; bool isNormalWindow() const; // normal as in 'NET::Normal or NET::Unknown non-transient' bool isDialog() const; bool isSplash() const; bool isUtility() const; bool isOverride() const; // not override redirect, but NET::Override // 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 isResizable() const; bool isCloseable() const; // may be closed by the user (may have a close button) void takeFocus( bool force, 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 ); void checkBorderSizes(); // shape extensions bool shape() const; 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 ); void keepInArea( const QRect& area ); void growHorizontal(); void shrinkHorizontal(); void growVertical(); void shrinkVertical(); bool providesContextHelp() const; bool performMouseCommand( Options::MouseCommand, QPoint globalPos ); QCString windowRole() const; QCString sessionId(); QCString resourceName() const; QCString resourceClass() const; QCString wmCommand(); QCString wmClientMachine() const; Window wmClientLeader() const; pid_t pid() const; QRect adjustedClientArea( const QRect& desktop, const QRect& area ) const; Colormap colormap() const; // hides a client - basically like minimize, but without effects, it's simply hidden void hideClient( bool hide ); // updates visibility depending on whether it's on the current desktop void virtualDesktopChange(); QString caption() const; 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, bool send_event = false ); void gotPing( Time timestamp ); static QCString staticWindowRole(WId); static QCString staticSessionId(WId); static QCString staticWmCommand(WId); static QCString staticWmClientMachine(WId); static Window staticWmClientLeader(WId); 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 resourceMatch( const Client* c1, const Client* c2 ); static bool belongToSameApplication( const Client* c1, const Client* c2, bool active_hack = false ); 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 toggleOnAllDesktops(); void toggleShade(); void showContextHelp(); void cancelAutoRaise(); void destroyClient(); private slots: void autoRaise(); void shadeHover(); 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 setCursor( Position m ); void setCursor( const QCursor& c ); void animateMinimizeOrUnminimize( bool minimize ); QPixmap animationPixmap( int w ); // 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 ); void propertyNotifyEvent( XPropertyEvent* e ); void clientMessageEvent( XClientMessageEvent* e ); void enterNotifyEvent( XCrossingEvent* e ); void leaveNotifyEvent( XCrossingEvent* e ); void visibilityNotifyEvent( XVisibilityEvent* e ); void focusInEvent( XFocusInEvent* e ); void focusOutEvent( XFocusOutEvent* e ); 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 ); private slots: void pingTimeout(); void processKillerExited(); private: // ICCCM 4.1.3.1, 4.1.4 , NETWM 2.5.1 void setMappingState( int s ); int mappingState() const; bool isIconicState() const; bool isNormalState() const; bool isManaged() const; // returns false if this client is not yet managed void updateAllowedActions( bool force = false ); QSize sizeForClientSize( const QSize&, Sizemode mode = SizemodeAny ) const; void changeMaximize( bool horizontal, bool vertical, bool adjust ); void getWmNormalHints(); void getIcons(); void getWmClientLeader(); void fetchName(); void fetchIconicName(); bool hasTransientInternal( const Client* c, bool indirect, ConstClientList& set ) const; 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 = 0 ); // 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 ); NETExtendedStrut strut() const; bool hasStrut() const; bool startMoveResize(); void finishMoveResize( bool cancel ); void leaveMoveResize(); void checkUnrestrictedMoveResize(); void handleMoveResize( int x, int y, int x_root, int y_root ); void positionGeometryTip(); void grabButton( int mod ); void ungrabButton( int mod ); void resetMaximize(); void resizeDecoration( const QSize& s ); void pingWindow(); void killProcess( bool ask, Time timestamp = CurrentTime ); void updateUrgency(); void embedClient( Window w, const XWindowAttributes &attr ); void detectNoBorder(); void destroyDecoration(); void updateFrameStrut(); void rawShow(); // just shows it void rawHide(); // just hides it Time readUserTimeMapTimestamp( const KStartupInfoData* asn_data, const SessionInfo* session ) const; Time readUserCreationTime() const; static bool sameAppWindowRoleMatch( const Client* c1, const Client* c2, bool active_hack ); void startupIdChanged(); Window client; Window wrapper; Window frame; KDecoration* decoration; Workspace* wspace; Bridge* bridge; int desk; bool buttonDown; bool moveResizeMode; bool move_faked_activity; Window move_resize_grab_window; bool unrestrictedMoveResize; bool isMove() const { return moveResizeMode && mode == PositionCenter; } bool isResize() const { return moveResizeMode && mode != PositionCenter; } Position mode; QPoint moveOffset; QPoint invertedMoveOffset; QRect moveResizeGeom; QRect initialMoveResizeGeom; XSizeHints xSizeHint; void sendSyntheticConfigureNotify(); int 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 keep_above : 1; // NET::KeepAbove (was stays_on_top) uint is_shape :1; 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 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 store_settings : 1; 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 user_noborder : 1; uint not_obscured : 1; uint urgency : 1; // XWMHints, UrgencyHint + uint ignore_focus_stealing : 1; // don't apply focus stealing prevention to this client 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; int workarea_diff_x, workarea_diff_y; WinInfo* info; QTimer* autoRaiseTimer; QTimer* shadeHoverTimer; Colormap cmap; QCString resource_name; QCString resource_class; QString cap_normal, cap_iconic, cap_suffix; WId wmClientLeaderWin; QCString window_role; void checkGroup(); Group* in_group; Window window_group; Layer in_layer; QTimer* ping_timer; KProcess* process_killer; Time ping_timestamp; Time user_time; unsigned long allowed_actions; QRect frame_geometry; QSize client_size; int block_geometry; // >0 - new geometry is remembered, but not actually set bool shade_geometry_change; int border_left, border_right, border_top, border_bottom; QRegion _mask; friend struct FetchNameInternalPredicate; + friend struct CheckIgnoreFocusStealingProcedure; void show() { assert( false ); } // SELI remove after Client is no longer QWidget void hide() { assert( false ); } }; // NET WM Protocol handler class class WinInfo : public NETWinInfo { private: typedef KWinInternal::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 ); private: Client * m_client; }; inline Window Client::window() const { return client; } inline Window Client::frameId() const { return frame; } inline Window Client::wrapperId() const { return wrapper; } inline Window Client::decorationId() const { return decoration != NULL ? decoration->widget()->winId() : None; } inline Workspace* Client::workspace() const { return wspace; } 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 == workspace()->rootWin(); } // 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 == workspace()->rootWin(); } 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 int Client::mappingState() const { return mapping_state; } inline QCString Client::resourceName() const { return resource_name; // it is always lowercase } inline QCString Client::resourceClass() const { return resource_class; // it is always lowercase } inline bool Client::isMinimized() const { return minimized; } inline bool Client::isActive() const { return active; } /*! Returns the virtual desktop within the workspace() the client window is located in, 0 if it isn't located on any special desktop (not mapped yet), or NET::OnAllDesktops. Do not use desktop() directly, use isOnDesktop() instead. */ inline int Client::desktop() const { return desk; } inline bool Client::isOnAllDesktops() const { return desk == NET::OnAllDesktops; } /*! Returns whether the client is on the virtual desktop \a d. This is always TRUE for onAllDesktops clients. */ inline bool Client::isOnDesktop( int d ) const { return desk == d || /*desk == 0 ||*/ isOnAllDesktops(); } inline bool Client::isShown( bool shaded_is_shown ) const { return !isMinimized() && ( !isShade() || shaded_is_shown ) && !hidden; } inline bool Client::isShade() const { return shade_mode == ShadeNormal; } inline Client::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::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::storeSettings() const { return store_settings; } inline void Client::setStoreSettings( bool b ) { store_settings = b; } inline bool Client::shape() const { return is_shape; } 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 pid_t Client::pid() const { return info->pid(); } inline void Client::invalidateLayer() { in_layer = UnknownLayer; } inline bool Client::isIconicState() const { return mapping_state == IconicState; } inline bool Client::isNormalState() const { return mapping_state == NormalState; } inline bool Client::isManaged() const { return mapping_state != WithdrawnState; } inline QCString Client::windowRole() const { return window_role; } inline QRect Client::geometry() const { return frame_geometry; } inline QSize Client::size() const { return frame_geometry.size(); } inline QSize Client::minSize() const { return QSize( xSizeHint.min_width, xSizeHint.min_height ); } inline QPoint Client::pos() const { return frame_geometry.topLeft(); } inline int Client::x() const { return frame_geometry.x(); } inline int Client::y() const { return frame_geometry.y(); } inline int Client::width() const { return frame_geometry.width(); } inline int Client::height() const { return frame_geometry.height(); } inline QRect Client::rect() const { return QRect( 0, 0, width(), height()); } 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; + } + +KWIN_PROCEDURE( CheckIgnoreFocusStealingProcedure, cl->ignore_focus_stealing = options->checkIgnoreFocusStealing( cl )); inline Window Client::moveResizeGrabWindow() const { return move_resize_grab_window; } #ifdef NDEBUG kndbgstream& operator<<( kndbgstream& stream, const Client* ); kndbgstream& operator<<( kndbgstream& stream, const ClientList& ); kndbgstream& operator<<( kndbgstream& stream, const ConstClientList& ); #else kdbgstream& operator<<( kdbgstream& stream, const Client* ); kdbgstream& operator<<( kdbgstream& stream, const ClientList& ); kdbgstream& operator<<( kdbgstream& stream, const ConstClientList& ); #endif KWIN_COMPARE_PREDICATE( WindowMatchPredicate, Window, cl->window() == value ); KWIN_COMPARE_PREDICATE( FrameIdMatchPredicate, Window, cl->frameId() == value ); KWIN_COMPARE_PREDICATE( WrapperIdMatchPredicate, Window, cl->wrapperId() == value ); } // namespace #endif Index: trunk/kdebase/kwin/manage.cpp =================================================================== --- trunk/kdebase/kwin/manage.cpp (revision 298356) +++ trunk/kdebase/kwin/manage.cpp (revision 298357) @@ -1,531 +1,532 @@ /***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ /* This file contains things relevant to handling incoming events. */ #include "client.h" #include #include #include #include "notifications.h" extern Time qt_x_time; extern Atom qt_window_role; namespace KWinInternal { /*! Manages the clients. This means handling the very first maprequest: reparenting, initial geometry, initial state, placement, etc. Returns false if KWin is not going to manage this window. */ bool Client::manage( Window w, bool isMapped ) { XWindowAttributes attr; if( !XGetWindowAttributes(qt_xdisplay(), w, &attr)) return false; grabXServer(); // from this place on, manage() mustn't return false block_geometry = 1; embedClient( w, attr ); // SELI order all these things in some sane manner bool init_minimize = false; XWMHints * hints = XGetWMHints(qt_xdisplay(), w ); if (hints && (hints->flags & StateHint) && hints->initial_state == IconicState) init_minimize = true; if (hints) XFree(hints); if( isMapped ) init_minimize = false; // if it's already mapped, ignore hint unsigned long properties[ 2 ]; properties[ WinInfo::PROTOCOLS ] = NET::WMDesktop | NET::WMState | NET::WMWindowType | NET::WMStrut | NET::WMName | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMIconName | 0; properties[ WinInfo::PROTOCOLS2 ] = NET::WM2UserTime | NET::WM2StartupId | NET::WM2ExtendedStrut | 0; info = new WinInfo( this, qt_xdisplay(), client, qt_xrootwin(), properties, 2 ); cmap = attr.colormap; bool mresize, mmove, mminimize, mmaximize, mclose; if( Motif::funcFlags( client, mresize, mmove, mminimize, mmaximize, mclose )) { if( !hasNETSupport()) // NETWM apps should set type and size constraints { motif_may_resize = mresize; // this should be set using minsize==maxsize, but oh well motif_may_move = mmove; } // mminimize; - ignore, bogus - e.g. shading or sending to another desktop is "minimizing" too // mmaximize; - ignore, bogus - maximizing is basically just resizing motif_may_close = mclose; // motif apps like to crash when they set this hint and WM closes them anyway } XClassHint classHint; if ( XGetClassHint( qt_xdisplay(), client, &classHint ) ) { // Qt3.2 and older had this all lowercase, Qt3.3 capitalized resource class // force lowercase, so that workarounds listing resource classes still work resource_name = QCString( classHint.res_name ).lower(); resource_class = QCString( classHint.res_class ).lower(); XFree( classHint.res_name ); XFree( classHint.res_class ); } + ignore_focus_stealing = options->checkIgnoreFocusStealing( this ); detectNoBorder(); fetchName(); fetchIconicName(); getWMHints(); // needs to be done before readTransient() because of reading the group getWmClientLeader(); // needs to be done before readTransient() because of same app comparing readTransient(); getIcons(); getWindowProtocols(); getWmNormalHints(); // get xSizeHint window_role = getStringProperty( w, qt_window_role ); // TODO try to obey all state information from info->state() original_skip_taskbar = skip_taskbar = ( info->state() & NET::SkipTaskbar) != 0; skip_pager = ( info->state() & NET::SkipPager) != 0; modal = ( info->state() & NET::Modal ) != 0; // window wants to stay on top? keep_above = ( info->state() & NET::KeepAbove ) != 0; // window wants to stay on bottom? keep_below = ( info->state() & NET::KeepBelow ) != 0; if( keep_above && keep_below ) keep_above = keep_below = false; KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification( window(), asn_data ); workspace()->updateClientLayer( this ); SessionInfo* session = workspace()->takeSessionInfo( this ); if ( session ) { if ( session->minimized ) init_minimize = true; if( session->userNoBorder ) setUserNoBorder( true ); } // initial desktop placement if ( info->desktop() ) desk = info->desktop(); // window had the initial desktop property! else if( asn_valid && asn_data.desktop() != 0 ) desk = asn_data.desktop(); if ( session ) { desk = session->desktop; if( session->onAllDesktops ) desk = NET::OnAllDesktops; } else if ( desk == 0 ) { // if this window is transient, ensure that it is opened on the // same window as its parent. this is necessary when an application // starts up on a different desktop than is currently displayed if( isTransient()) { ClientList mainclients = mainClients(); bool on_current = false; Client* maincl = NULL; // this is slightly duplicated from Placement::placeOnMainWindow() for( ClientList::ConstIterator it = mainclients.begin(); it != mainclients.end(); ++it ) { if( (*it)->isSpecialWindow() && !(*it)->isOverride()) continue; // don't consider toolbars etc when placing maincl = *it; if( (*it)->isOnCurrentDesktop()) on_current = true; } if( on_current ) desk = workspace()->currentDesktop(); else if( maincl != NULL ) desk = maincl->desktop(); } } if ( desk == 0 ) // assume window wants to be visible on the current desktop desk = workspace()->currentDesktop(); if( desk != NET::OnAllDesktops ) // do range check desk = KMAX( 1, KMIN( workspace()->numberOfDesktops(), desk )); info->setDesktop( desk ); workspace()->updateOnAllDesktopsOfTransients( this ); // SELI // onAllDesktopsChange(); decoration doesn't exist here yet QRect geom( attr.x, attr.y, attr.width, attr.height ); bool placementDone = FALSE; if ( session ) geom = session->geometry; QRect area; if( isMapped || session ) area = workspace()->clientArea( WorkArea, geom.center(), desktop()); else if( options->xineramaPlacementEnabled ) area = workspace()->clientArea( PlacementArea, QCursor::pos(), desktop()); else area = workspace()->clientArea( PlacementArea, geom.center(), desktop()); // if it's noborder window, and has size of one screen or the whole desktop geometry, it's fullscreen hack if( ( geom.size() == workspace()->clientArea( FullArea, geom.center(), desktop()).size() || geom.size() == workspace()->clientArea( ScreenArea, geom.center(), desktop()).size()) && noBorder() && !isUserNoBorder() && isFullScreenable( true )) { fullscreen_mode = FullScreenHack; geom = workspace()->clientArea( MaximizeFullArea, geom.center(), desktop()); placementDone = true; } if ( isDesktop() ) { // desktops are treated slightly special geom = workspace()->clientArea( FullArea, geom.center(), desktop()); placementDone = true; } if ( isMapped || session || placementDone || ( isTransient() && !isUtility() && !isDialog() && !isSplash())) { // TODO placementDone = TRUE; } else if( isTransient() && !hasNETSupport()) placementDone = true; else if( isDialog() && hasNETSupport()) // see Placement::placeDialog() ; // force using placement policy else if( isSplash()) ; // force using placement policy else { bool ignorePPosition = ( options->ignorePositionClasses.contains(QString::fromLatin1(resourceClass()))); if ( ( (xSizeHint.flags & PPosition) && !ignorePPosition ) || (xSizeHint.flags & USPosition) ) { placementDone = TRUE; // disobey xinerama placement option for now (#70943) area = workspace()->clientArea( PlacementArea, geom.center(), desktop()); } if ( (xSizeHint.flags & USSize) || (xSizeHint.flags & PSize) ) { // keep in mind that we now actually have a size :-) } } if (xSizeHint.flags & PMaxSize) geom.setSize( geom.size().boundedTo( QSize(xSizeHint.max_width, xSizeHint.max_height ) ) ); if (xSizeHint.flags & PMinSize) geom.setSize( geom.size().expandedTo( QSize(xSizeHint.min_width, xSizeHint.min_height ) ) ); if( isMovable()) { if( geom.x() > area.right() || geom.y() > area.bottom()) placementDone = FALSE; // weird, do not trust. } if ( placementDone ) move( geom.x(), geom.y() ); // before gravitating updateDecoration( false ); // also gravitates // TODO is CentralGravity right here, when resizing is done after gravitating? plainResize( sizeForClientSize( geom.size())); if( !placementDone ) { // placement needs to be after setting size workspace()->place( this, area ); placementDone = TRUE; } if( !isMapped && !session // trust position from session or if already mapped && ( !isSpecialWindow() || isToolbar()) && isMovable()) keepInArea( area ); XShapeSelectInput( qt_xdisplay(), window(), ShapeNotifyMask ); if ( (is_shape = Shape::hasShape( window())) ) { updateShape(); } //CT extra check for stupid jdk 1.3.1. But should make sense in general // if client has initial state set to Iconic and is transient with a parent // window that is not Iconic, set init_state to Normal if( init_minimize && isTransient()) { ClientList mainclients = mainClients(); for( ClientList::ConstIterator it = mainclients.begin(); it != mainclients.end(); ++it ) if( (*it)->isShown( true )) init_minimize = false; // SELI even e.g. for NET::Utility? } if( init_minimize ) minimize( true ); // no animation // SELI this seems to be mainly for kstart and ksystraycmd // probably should be replaced by something better bool doNotShow = false; if ( workspace()->isNotManaged( caption() ) ) doNotShow = TRUE; // other settings from the previous session if ( session ) { setKeepAbove( session->keepAbove ); setKeepBelow( session->keepBelow ); setSkipTaskbar( session->skipTaskbar, true ); setSkipPager( session->skipPager ); setShade( session->shaded ? ShadeNormal : ShadeNone ); if( session->maximized != MaximizeRestore ) { maximize( (MaximizeMode) session->maximized ); geom_restore = session->restore; } if( session->fullscreen == FullScreenHack ) ; // nothing, this should be already set again above else if( session->fullscreen != FullScreenNone ) { setFullScreen( true, false ); geom_fs_restore = session->fsrestore; } } else { geom_restore = geometry(); // remember restore geometry if ( isMaximizable() && ( width() >= area.width() || height() >= area.height() ) ) { // window is too large for the screen, maximize in the // directions necessary if ( width() >= area.width() && height() >= area.height() ) { maximize( Client::MaximizeFull ); geom_restore = QRect(); // use placement when unmaximizing } else if ( width() >= area.width() ) { maximize( Client::MaximizeHorizontal ); geom_restore = QRect(); // use placement when unmaximizing geom_restore.setY( y()); // but only for horizontal direction geom_restore.setHeight( height()); } else if ( height() >= area.height() ) { maximize( Client::MaximizeVertical ); geom_restore = QRect(); // use placement when unmaximizing geom_restore.setX( x()); // but only for vertical direction geom_restore.setWidth( width()); } } // window may want to be maximized // done after checking that the window isn't larger than the workarea, so that // the restore geometry from the checks above takes precedence, and window // isn't restored larger than the workarea if ( (info->state() & NET::Max) == NET::Max ) maximize( Client::MaximizeFull ); else if ( info->state() & NET::MaxVert ) maximize( Client::MaximizeVertical ); else if ( info->state() & NET::MaxHoriz ) maximize( Client::MaximizeHorizontal ); // read other initial states if( info->state() & NET::Shaded ) setShade( ShadeNormal ); if( info->state() & NET::KeepAbove ) setKeepAbove( true ); if( info->state() & NET::KeepBelow ) setKeepBelow( true ); if( info->state() & NET::SkipTaskbar ) setSkipTaskbar( true, true ); if( info->state() & NET::SkipPager ) setSkipPager( true ); if( info->state() & NET::DemandsAttention ) demandAttention(); if( info->state() & NET::Modal ) setModal( true ); if( fullscreen_mode != FullScreenHack ) { if(( info->state() & NET::FullScreen ) != 0 && isFullScreenable()) setFullScreen( true, false ); } } updateAllowedActions( true ); // TODO this should avoid flicker, because real restacking is done // only after manage() finishes, but the window is shown sooner // - keep it? XLowerWindow( qt_xdisplay(), frameId()); user_time = readUserTimeMapTimestamp( asn_valid ? &asn_data : NULL, session ); if( isTopMenu()) // they're shown in Workspace::addClient() if their mainwindow hideClient( true ); // is the active one if ( isShown( true ) && !doNotShow ) { if( isDialog()) Notify::raise( Notify::TransNew ); if( isNormalWindow()) Notify::raise( Notify::New ); // if session saving, force showing new windows (i.e. "save file?" dialogs etc.) if( workspace()->sessionSaving() && !isOnCurrentDesktop()) workspace()->setCurrentDesktop( desktop()); if( isOnCurrentDesktop()) { setMappingState( NormalState ); if( isMapped ) { workspace()->raiseClient( this ); rawShow(); } else { if( workspace()->allowClientActivation( this, userTime(), false, session && session->active )) { workspace()->raiseClient( this ); rawShow(); if( !isSpecialWindow() || isOverride()) if ( options->focusPolicyIsReasonable() && wantsTabFocus() ) workspace()->requestFocus( this ); } else { workspace()->restackClientUnderActive( this ); rawShow(); if( ( !session || session->fake ) && ( !isSpecialWindow() || isOverride())) demandAttention(); } } } else { virtualDesktopChange(); workspace()->raiseClient( this ); if( ( !session || session->fake ) && !isMapped ) demandAttention(); } } else if( !doNotShow ) // !isShown() { rawHide(); setMappingState( IconicState ); } else // doNotShow { // SELI HACK !!! hideClient( true ); setMappingState( IconicState ); } assert( mappingState() != WithdrawnState ); if( user_time == CurrentTime || user_time == -1U ) // no known user time, set something old { user_time = qt_x_time - 1000000; if( user_time == CurrentTime || user_time == -1U ) // let's be paranoid user_time = qt_x_time - 1000000 + 10; } updateWorkareaDiffs(); // sendSyntheticConfigureNotify(); done when setting mapping state delete session; ungrabXServer(); return true; } // called only from manage() void Client::embedClient( Window w, const XWindowAttributes &attr ) { assert( client == None ); assert( frame == None ); assert( wrapper == None ); client = w; // we don't want the window to be destroyed when we are destroyed XAddToSaveSet( qt_xdisplay(), client ); XSelectInput( qt_xdisplay(), client, NoEventMask ); XUnmapWindow( qt_xdisplay(), client ); XWindowChanges wc; // set the border width to 0 wc.border_width = 0; // TODO possibly save this, and also use it for initial configuring of the window XConfigureWindow( qt_xdisplay(), client, CWBorderWidth, &wc ); XSetWindowAttributes swa; swa.colormap = attr.colormap; swa.background_pixmap = None; swa.border_pixel = 0; frame = XCreateWindow( qt_xdisplay(), qt_xrootwin(), 0, 0, 1, 1, 0, attr.depth, InputOutput, attr.visual, CWColormap | CWBackPixmap | CWBorderPixel, &swa ); wrapper = XCreateWindow( qt_xdisplay(), frame, 0, 0, 1, 1, 0, attr.depth, InputOutput, attr.visual, CWColormap | CWBackPixmap | CWBorderPixel, &swa ); XDefineCursor( qt_xdisplay(), frame, arrowCursor.handle()); // some apps are stupid and don't define their own cursor - set the arrow one for them XDefineCursor( qt_xdisplay(), wrapper, arrowCursor.handle()); XReparentWindow( qt_xdisplay(), client, wrapper, 0, 0 ); XSelectInput( qt_xdisplay(), frame, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | KeymapStateMask | ButtonMotionMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask | FocusChangeMask | ExposureMask | PropertyChangeMask | StructureNotifyMask | SubstructureRedirectMask | VisibilityChangeMask ); XSelectInput( qt_xdisplay(), wrapper, ClientWinMask | SubstructureNotifyMask ); XSelectInput( qt_xdisplay(), client, FocusChangeMask | PropertyChangeMask | ColormapChangeMask | EnterWindowMask | LeaveWindowMask | KeyPressMask | KeyReleaseMask ); updateMouseGrab(); } } // namespace Index: trunk/kdebase/kwin/workspace.cpp =================================================================== --- trunk/kdebase/kwin/workspace.cpp (revision 298356) +++ trunk/kdebase/kwin/workspace.cpp (revision 298357) @@ -1,2026 +1,2027 @@ /***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ //#define QT_CLEAN_NAMESPACE #include "workspace.h" #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 #include #include #include extern Time qt_x_time; namespace KWinInternal { 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 ) : DCOPObject ("KWinInterface"), QObject (0, "workspace"), current_desktop (0), number_of_desktops(0), popup_client (0), desktop_widget (0), active_client (0), last_active_client (0), most_recently_raised (0), movingClient(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), desk_popup (0), desk_popup_index (0), keys (0), root (0), workspaceInit (true), startup(0), electric_have_borders(false), electric_current_border(0), electric_top_border(None), electric_bottom_border(None), electric_left_border(None), electric_right_border(None), layoutOrientation(Qt::Vertical), layoutX(-1), layoutY(2), workarea(NULL), screenarea(NULL), set_active_client_recursion( 0 ), block_stacking_updates( 0 ), forced_global_mouse_grab( false ) { _self = this; mgr = new PluginMgr; root = qt_xrootwin(); default_colormap = DefaultColormap(qt_xdisplay(), qt_xscreen() ); installed_colormap = default_colormap; session.setAutoDelete( TRUE ); updateXTime(); // needed for proper initialization of user_time in Client ctor electric_time_first = qt_x_time; electric_time_last = qt_x_time; if ( restore ) loadSessionInfo(); loadFakeSessionInfo(); (void) QApplication::desktop(); // trigger creation of desktop widget desktop_widget = new QWidget( 0, "desktop_widget", Qt::WType_Desktop | Qt::WPaintUnclipped ); kapp->setGlobalMouseTracking( true ); // so that this doesn't mess eventmask on root window later // call this before XSelectInput() on the root window startup = new KStartupInfo( KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this ); // select windowmanager privileges XSelectInput(qt_xdisplay(), root, KeyPressMask | PropertyChangeMask | ColormapChangeMask | SubstructureRedirectMask | SubstructureNotifyMask | FocusChangeMask // for NotifyDetailNone ); Shape::init(); // compatibility long data = 1; XChangeProperty( qt_xdisplay(), qt_xrootwin(), atoms->kwin_running, atoms->kwin_running, 32, PropModeAppend, (unsigned char*) &data, 1 ); initShortcuts(); tab_box = new TabBox( this ); popupinfo = new PopupInfo( ); init(); #if (QT_VERSION-0 >= 0x030200) // XRANDR support connect( kapp->desktop(), SIGNAL( resized( int )), SLOT( desktopResized())); #endif } void Workspace::init() { if (options->electricBorders() == Options::ElectricAlways) createBorderWindows(); supportWindow = new QWidget; XLowerWindow( qt_xdisplay(), supportWindow->winId()); // see usage in layers.cpp XSetWindowAttributes attr; attr.override_redirect = 1; null_focus_window = XCreateWindow( qt_xdisplay(), qt_xrootwin(), -1,-1, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWOverrideRedirect, &attr ); XMapWindow(qt_xdisplay(), 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::KDESystemTrayWindows | NET::WMName | NET::WMVisibleName | NET::WMDesktop | NET::WMWindowType | NET::WMState | NET::WMStrut | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | NET::WMMoveResize | NET::WMKDESystemTrayWinFor | NET::WMKDEFrameStrut | NET::WMPing , NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask | 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 | 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 , }; rootInfo = new RootInfo( this, qt_xdisplay(), supportWindow->winId(), "KWin", protocols, 5, qt_xscreen() ); loadDesktopSettings(); // extra NETRootInfo instance in Client mode is needed to get the values of the properties NETRootInfo client_info( qt_xdisplay(), NET::ActiveWindow | NET::CurrentDesktop ); int initial_desktop; if( !kapp->isSessionRestored()) initial_desktop = client_info.currentDesktop(); else { KConfigGroupSaver saver( kapp->sessionConfig(), "Session" ); initial_desktop = kapp->sessionConfig()->readNumEntry( "desktop", 1 ); } if( !setCurrentDesktop( initial_desktop )) setCurrentDesktop( 1 ); // now we know how many desktops we'll, thus, we initialise the positioning object initPositioning = new Placement(this); connect(&reconfigureTimer, SIGNAL(timeout()), this, SLOT(slotReconfigure())); connect( &updateToolWindowsTimer, SIGNAL( timeout()), this, SLOT( slotUpdateToolWindows())); connect(kapp, SIGNAL(appearanceChanged()), this, SLOT(slotReconfigure())); connect(kapp, SIGNAL(settingsChanged(int)), this, SLOT(slotSettingsChanged(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( qt_xdisplay())); Atom topmenu_atom = XInternAtom( qt_xdisplay(), nm, False ); topmenu_selection = new KSelectionOwner( topmenu_atom ); topmenu_watcher = new KSelectionWatcher( topmenu_atom ); topmenu_height = 0; managing_topmenus = false; topmenu_space = NULL; // 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(qt_xdisplay(), root, &root_return, &parent_return, &wins, &nwins); for (i = 0; i < nwins; i++) { XWindowAttributes attr; XGetWindowAttributes(qt_xdisplay(), wins[i], &attr); if (attr.override_redirect ) continue; if( topmenu_space && topmenu_space->winId() == wins[ i ] ) continue; if (attr.map_state != IsUnmapped) { if ( addSystemTrayWin( wins[i] ) ) continue; Client* c = createClient( wins[i], true ); if ( c != NULL && root != qt_xrootwin() ) { // TODO what is this? // TODO may use QWidget:.create XReparentWindow( qt_xdisplay(), c->frameId(), root, 0, 0 ); c->move(0,0); } } } if ( wins ) XFree((void *) wins); // propagate clients, will really happen at the end of the updates blocker block updateStackingOrder( true ); updateClientArea(); raiseElectricBorders(); // 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(); // TODO update also after gaining XRANDR support rootInfo->setDesktopGeometry( -1, desktop_geometry ); } // 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()); 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() { 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 if( !(*it)->isDesktop()) // TODO ? storeFakeSessionInfo( *it ); (*it)->releaseWindow( true ); } delete desktop_widget; delete tab_box; delete popupinfo; delete popup; if ( root == qt_xrootwin() ) XDeleteProperty(qt_xdisplay(), qt_xrootwin(), atoms->kwin_running); writeFakeSessionInfo(); KGlobal::config()->sync(); delete rootInfo; delete supportWindow; delete mgr; delete[] workarea; delete[] screenarea; delete startup; delete initPositioning; delete topmenu_watcher; delete topmenu_selection; delete topmenu_space; XDestroyWindow( qt_xdisplay(), 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 ); 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 { if ( c->wantsTabFocus() && !focus_chain.contains( c )) focus_chain.append( c ); clients.append( c ); } if( !unconstrained_stacking_order.contains( c )) unconstrained_stacking_order.append( c ); 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())); } if( c->isUtility() || c->isMenu() || c->isToolbar()) updateToolWindows( true ); checkTransients( c->window()); // SELI does this really belong here? updateStackingOrder( true ); // propagate new client } /* Destroys the client \a c */ void Workspace::removeClient( Client* c, allowed_t ) { if (c == active_client && popup) popup->close(); if( c == popup_client ) popup_client = 0; if( c->isDialog()) Notify::raise( Notify::TransDelete ); if( c->isNormalWindow()) Notify::raise( Notify::Delete ); storeFakeSessionInfo( c ); Q_ASSERT( clients.contains( c ) || desktops.contains( c )); clients.remove( c ); desktops.remove( c ); unconstrained_stacking_order.remove( c ); stacking_order.remove( c ); focus_chain.remove( c ); attention_chain.remove( 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.remove( c ); Q_ASSERT( c != active_client ); if ( c == last_active_client ) last_active_client = 0; updateStackingOrder( true ); if (tab_grab) tab_box->repaint(); updateClientArea(); } 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 } } // kdDebug() << "CURRENT TOPMENU:" << menubar << ":" << active_client << endl; 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.remove( 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?) 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 ) to_show.append( *it ); else if( also_hide ) to_hide.append( *it ); } } // first show new ones, then hide for( ClientList::ConstIterator it = to_show.fromLast(); it != to_show.end(); --it ) // from topmost // TODO since this is in stacking order, the order of taskbar entries changes :( (*it)->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, true ); } } 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(qt_xdisplay(), cmap ); installed_colormap = cmap; } } void Workspace::reconfigure() { reconfigureTimer.start(200, true); } void Workspace::slotSettingsChanged(int category) { kdDebug(1212) << "Workspace::slotSettingsChanged()" << endl; if( category == (int) KApplication::SETTINGS_SHORTCUTS ) readShortcuts(); } /*! Reread settings */ KWIN_PROCEDURE( CheckBorderSizesProcedure, cl->checkBorderSizes() ); void Workspace::slotReconfigure() { kdDebug(1212) << "Workspace::slotReconfigure()" << endl; reconfigureTimer.stop(); KGlobal::config()->reparseConfiguration(); unsigned long changed = options->updateSettings(); tab_box->reconfigure(); popupinfo->reconfigure(); readShortcuts(); + forEachClient( CheckIgnoreFocusStealingProcedure()); 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()); } if (options->electricBorders() == Options::ElectricAlways) createBorderWindows(); else destroyBorderWindows(); 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(); } } void Workspace::loadDesktopSettings() { KConfig c("kwinrc"); QCString groupname; if (screen_number == 0) groupname = "Desktops"; else groupname.sprintf("Desktops-screen-%d", screen_number); c.setGroup(groupname); int n = c.readNumEntry("Number", 4); number_of_desktops = n; delete workarea; workarea = new QRect[ n + 1 ]; delete screenarea; screenarea = NULL; rootInfo->setNumberOfDesktops( number_of_desktops ); desktop_focus_chain.resize( n ); for(int i = 1; i <= n; i++) { QString s = c.readEntry(QString("Name_%1").arg(i), i18n("Desktop %1").arg(i)); rootInfo->setDesktopName( i, s.utf8().data() ); desktop_focus_chain[i-1] = i; } } void Workspace::saveDesktopSettings() { KConfig c("kwinrc"); QCString groupname; if (screen_number == 0) groupname = "Desktops"; else groupname.sprintf("Desktops-screen-%d", screen_number); c.setGroup(groupname); c.writeEntry("Number", number_of_desktops ); for(int i = 1; i <= number_of_desktops; i++) { QString s = desktopName( i ); QString defaultvalue = i18n("Desktop %1").arg(i); if ( s.isEmpty() ) { s = defaultvalue; rootInfo->setDesktopName( i, s.utf8().data() ); } if (s != defaultvalue) { c.writeEntry( QString("Name_%1").arg(i), s ); } else { QString currentvalue = c.readEntry(QString("Name_%1").arg(i)); if (currentvalue != defaultvalue) c.writeEntry( QString("Name_%1").arg(i), "" ); } } } QStringList Workspace::configModules(bool controlCenter) { QStringList args; args << "kde-kwindecoration.desktop"; if (controlCenter) args << "kde-kwinoptions.desktop"; else if (kapp->authorizeControlModule("kde-kwinoptions.desktop")) args << "kwinactions" << "kwinfocus" << "kwinmoving" << "kwinadvanced"; return args; } void Workspace::configureWM() { KApplication::kdeinitExec( "kcmshell", configModules(false) ); } /*! avoids managing a window with title \a title */ void Workspace::doNotManage( 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.search(title) != -1) { doNotManageList.remove( it ); return TRUE; } } return FALSE; } /*! Refreshes all the client windows */ void Workspace::refresh() { QWidget w; w.setGeometry( QApplication::desktop()->geometry() ); w.show(); w.hide(); QApplication::flushX(); } /*! 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: QValueList obscuring_windows; static QValueList* cached; static unsigned int max_cache_size; }; QValueList* ObscuringWindows::cached = 0; unsigned int ObscuringWindows::max_cache_size = 0; void ObscuringWindows::create( Client* c ) { if( cached == 0 ) cached = new QValueList; Window obs_win; XWindowChanges chngs; int mask = CWSibling | CWStackMode; if( cached->count() > 0 ) { cached->remove( 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( qt_xdisplay(), qt_xrootwin(), c->x(), c->y(), c->width(), c->height(), 0, CopyFromParent, InputOutput, CopyFromParent, CWBackPixmap | CWOverrideRedirect, &a ); } chngs.sibling = c->frameId(); chngs.stack_mode = Below; XConfigureWindow( qt_xdisplay(), obs_win, mask, &chngs ); XMapWindow( qt_xdisplay(), obs_win ); obscuring_windows.append( obs_win ); } ObscuringWindows::~ObscuringWindows() { max_cache_size = QMAX( max_cache_size, obscuring_windows.count() + 4 ) - 1; for( QValueList::ConstIterator it = obscuring_windows.begin(); it != obscuring_windows.end(); ++it ) { XUnmapWindow( qt_xdisplay(), *it ); if( cached->count() < max_cache_size ) cached->prepend( *it ); else XDestroyWindow( qt_xdisplay(), *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; if( popup ) popup->close(); ++block_focus; // TODO Q_ASSERT( block_stacking_updates == 0 ); // make sure stacking_order is up to date StackingUpdatesBlocker blocker( this ); if (new_desktop != current_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; int old_desktop = current_desktop; current_desktop = new_desktop; // change the desktop (so that Client::virtualDesktopChange() 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)->virtualDesktopChange(); } rootInfo->setCurrentDesktop( current_desktop ); // now propagate the change, after hiding, before showing if( movingClient && !movingClient->isOnDesktop( new_desktop )) movingClient->setDesktop( new_desktop ); for ( ClientList::ConstIterator it = stacking_order.fromLast(); it != stacking_order.end(); --it) if ( (*it)->isOnDesktop( new_desktop ) ) (*it)->virtualDesktopChange(); } // restore the focus on this desktop --block_focus; Client* c = 0; if ( options->focusPolicyIsReasonable()) { // Search in focus chain if ( focus_chain.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( ClientList::ConstIterator it = focus_chain.fromLast(); it != focus_chain.end(); --it) { if ( (*it)->isShown( false ) && !(*it)->isOnAllDesktops() && (*it)->isOnCurrentDesktop()) { c = *it; break; } } } if ( !c ) { for( ClientList::ConstIterator it = focus_chain.fromLast(); it != focus_chain.end(); --it) { if ( (*it)->isShown( false ) && (*it)->isOnCurrentDesktop()) { c = *it; 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 != active_client ) setActiveClient( NULL, Allowed ); if ( c ) requestFocus( c ); else focusToNull(); if( !desktops.isEmpty() ) { Window w_tmp; int i_tmp; XGetInputFocus( qt_xdisplay(), &w_tmp, &i_tmp ); if( w_tmp == null_focus_window ) // CHECKME? requestFocus( findDesktop( true, currentDesktop())); } // Update focus chain: // If input: chain = { 1, 2, 3, 4 } and current_desktop = 3, // Output: chain = { 3, 1, 2, 4 }. // kdDebug(1212) << QString("Switching to desktop #%1, at focus_chain index %2\n") // .arg(current_desktop).arg(desktop_focus_chain.find( current_desktop )); for( int i = desktop_focus_chain.find( current_desktop ); i > 0; i-- ) desktop_focus_chain[i] = desktop_focus_chain[i-1]; desktop_focus_chain[0] = current_desktop; // QString s = "desktop_focus_chain[] = { "; // for( uint i = 0; i < desktop_focus_chain.size(); i++ ) // s += QString::number(desktop_focus_chain[i]) + ", "; // kdDebug(1212) << s << "}\n"; return true; } void Workspace::nextDesktop() { int desktop = currentDesktop() + 1; setCurrentDesktop(desktop > numberOfDesktops() ? 1 : desktop); popupinfo->showInfo( desktopName(currentDesktop()) ); } void Workspace::previousDesktop() { int desktop = currentDesktop() - 1; setCurrentDesktop(desktop > 0 ? desktop : numberOfDesktops()); popupinfo->showInfo( desktopName(currentDesktop()) ); } /*! 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 ); } // 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 ); } 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 ) { if ( c->desktop() == desk ) return; bool was_on_desktop = c->isOnDesktop( desk ) || c->isOnAllDesktops(); c->setDesktop( desk ); 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 ); focus_chain.remove( c ); if ( c->wantsTabFocus() ) focus_chain.append( 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(); } void Workspace::setDesktopLayout(int o, int x, int y) { layoutOrientation = (Qt::Orientation) o; layoutX = x; layoutY = y; } void Workspace::calcDesktopLayout(int &x, int &y) { x = layoutX; y = layoutY; if ((x == -1) && (y > 0)) x = (numberOfDesktops()+y-1) / y; else if ((y == -1) && (x > 0)) y = (numberOfDesktops()+x-1) / x; if (x == -1) x = 1; if (y == -1) y = 1; } /*! Check whether \a w is a system tray window. If so, add it to the respective datastructures and propagate it to the world. */ bool Workspace::addSystemTrayWin( WId w ) { if ( systemTrayWins.contains( w ) ) return TRUE; NETWinInfo ni( qt_xdisplay(), w, root, NET::WMKDESystemTrayWinFor ); WId trayWinFor = ni.kdeSystemTrayWinFor(); if ( !trayWinFor ) return FALSE; systemTrayWins.append( SystemTrayWindow( w, trayWinFor ) ); XSelectInput( qt_xdisplay(), w, StructureNotifyMask ); XAddToSaveSet( qt_xdisplay(), w ); propagateSystemTrayWins(); return TRUE; } /*! Check whether \a w is a system tray window. If so, remove it from the respective datastructures and propagate this to the world. */ bool Workspace::removeSystemTrayWin( WId w, bool check ) { if ( !systemTrayWins.contains( w ) ) return FALSE; if( check ) { // When getting UnmapNotify, it's not clear if it's the systray // reparenting the window into itself, or if it's the window // going away. This is obviously a flaw in the design, and we were // just lucky it worked for so long. Kicker's systray temporarily // sets _KDE_SYSTEM_TRAY_EMBEDDING property on the window while // embedding it, allowing KWin to figure out. Kicker just mustn't // crash before removing it again ... *shrug* . int num_props; Atom* props = XListProperties( qt_xdisplay(), w, &num_props ); if( props != NULL ) { for( int i = 0; i < num_props; ++i ) if( props[ i ] == atoms->kde_system_tray_embedding ) { XFree( props ); return false; } XFree( props ); } } systemTrayWins.remove( w ); propagateSystemTrayWins(); return TRUE; } /*! Propagates the systemTrayWins to the world */ void Workspace::propagateSystemTrayWins() { Window *cl = new Window[ systemTrayWins.count()]; int i = 0; for ( SystemTrayWindowList::ConstIterator it = systemTrayWins.begin(); it != systemTrayWins.end(); ++it ) { cl[i++] = (*it).win; } rootInfo->setKDESystemTrayWindows( cl, i ); delete [] cl; } 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( qt_xdisplay(), 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( qt_xdisplay(), window_to_kill ); } void Workspace::sendPingToWindow( Window window, Time timestamp ) { rootInfo->sendPing( window, timestamp ); } /*! 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( Shape::available()) { //As the first step, get the mask from XShape. int count, order; XRectangle* rects = XShapeGetRectangles( qt_xdisplay(), 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; QMemArray 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 (uint 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( qt_xrootwin() ); QClipboard *cb = QApplication::clipboard(); cb->setPixmap( p ); } /*! Invokes keyboard mouse emulation */ void Workspace::slotMouseEmulation() { if ( mouse_emulation ) { XUngrabKeyboard(qt_xdisplay(), qt_x_time); mouse_emulation = FALSE; return; } if ( XGrabKeyboard(qt_xdisplay(), root, FALSE, GrabModeAsync, GrabModeAsync, qt_x_time) == GrabSuccess ) { 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 = qt_xrootwin(); int root_x, root_y, lx, ly; uint state; Window w; Client * c = 0; do { w = child; if (!c) c = findClient( FrameIdMatchPredicate( w )); XQueryPointer( qt_xdisplay(), 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( QPoint pos, WId w, MouseEmulation type, int button, unsigned int state ) { if ( !w ) return state; QWidget* widget = QWidget::find( w ); if ( (!widget || widget->inherits("QToolButton") ) && !findClient( WindowMatchPredicate( w )) ) { int x, y; Window xw; XTranslateCoordinates( qt_xdisplay(), qt_xrootwin(), w, pos.x(), pos.y(), &x, &y, &xw ); if ( type == EmuMove ) { // motion notify events XMotionEvent e; e.type = MotionNotify; e.window = w; e.root = qt_xrootwin(); e.subwindow = w; e.time = qt_x_time; e.x = x; e.y = y; e.x_root = pos.x(); e.y_root = pos.y(); e.state = state; e.is_hint = NotifyNormal; XSendEvent( qt_xdisplay(), w, TRUE, ButtonMotionMask, (XEvent*)&e ); } else { XButtonEvent e; e.type = type == EmuRelease ? ButtonRelease : ButtonPress; e.window = w; e.root = qt_xrootwin(); e.subwindow = w; e.time = qt_x_time; e.x = x; e.y = y; e.x_root = pos.x(); e.y_root = pos.y(); e.state = state; e.button = button; XSendEvent( qt_xdisplay(), w, TRUE, ButtonPressMask, (XEvent*)&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 ) { if ( root != qt_xrootwin() ) return FALSE; int kc = XKeycodeToKeysym(qt_xdisplay(), 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 = QCursor::pos(); 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: XUngrabKeyboard(qt_xdisplay(), qt_x_time); 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; } /*! Returns the workspace's desktop widget. The desktop widget is sometimes required by clients to draw on it, for example outlines on moving or resizing. */ QWidget* Workspace::desktopWidget() { return desktop_widget; } // Electric Borders //========================================================================// // Electric Border Window management. Electric borders allow a user // to change the virtual desktop by moving the mouse pointer to the // borders. Technically this is done with input only windows. Since // electric borders can be switched on and off, we have these two // functions to create and destroy them. void Workspace::createBorderWindows() { if ( electric_have_borders ) return; electric_have_borders = true; electric_current_border = 0; QRect r = QApplication::desktop()->geometry(); electricTop = r.top(); electricBottom = r.bottom(); electricLeft = r.left(); electricRight = r.right(); XSetWindowAttributes attributes; unsigned long valuemask; attributes.override_redirect = True; attributes.event_mask = (EnterWindowMask | LeaveWindowMask | VisibilityChangeMask); valuemask= (CWOverrideRedirect | CWEventMask | CWCursor ); attributes.cursor = XCreateFontCursor(qt_xdisplay(), XC_sb_up_arrow); electric_top_border = XCreateWindow (qt_xdisplay(), qt_xrootwin(), 0,0, r.width(),1, 0, CopyFromParent, InputOnly, CopyFromParent, valuemask, &attributes); XMapWindow(qt_xdisplay(), electric_top_border); attributes.cursor = XCreateFontCursor(qt_xdisplay(), XC_sb_down_arrow); electric_bottom_border = XCreateWindow (qt_xdisplay(), qt_xrootwin(), 0,r.height()-1, r.width(),1, 0, CopyFromParent, InputOnly, CopyFromParent, valuemask, &attributes); XMapWindow(qt_xdisplay(), electric_bottom_border); attributes.cursor = XCreateFontCursor(qt_xdisplay(), XC_sb_left_arrow); electric_left_border = XCreateWindow (qt_xdisplay(), qt_xrootwin(), 0,0, 1,r.height(), 0, CopyFromParent, InputOnly, CopyFromParent, valuemask, &attributes); XMapWindow(qt_xdisplay(), electric_left_border); attributes.cursor = XCreateFontCursor(qt_xdisplay(), XC_sb_right_arrow); electric_right_border = XCreateWindow (qt_xdisplay(), qt_xrootwin(), r.width()-1,0, 1,r.height(), 0, CopyFromParent, InputOnly, CopyFromParent, valuemask, &attributes); XMapWindow(qt_xdisplay(), electric_right_border); } // Electric Border Window management. Electric borders allow a user // to change the virtual desktop by moving the mouse pointer to the // borders. Technically this is done with input only windows. Since // electric borders can be switched on and off, we have these two // functions to create and destroy them. void Workspace::destroyBorderWindows() { if( !electric_have_borders) return; electric_have_borders = false; if(electric_top_border) XDestroyWindow(qt_xdisplay(),electric_top_border); if(electric_bottom_border) XDestroyWindow(qt_xdisplay(),electric_bottom_border); if(electric_left_border) XDestroyWindow(qt_xdisplay(),electric_left_border); if(electric_right_border) XDestroyWindow(qt_xdisplay(),electric_right_border); electric_top_border = None; electric_bottom_border = None; electric_left_border = None; electric_right_border = None; } void Workspace::clientMoved(const QPoint &pos, Time now) { if (options->electricBorders() == Options::ElectricDisabled) return; if ((pos.x() != electricLeft) && (pos.x() != electricRight) && (pos.y() != electricTop) && (pos.y() != electricBottom)) return; Time treshold_set = options->electricBorderDelay(); // set timeout Time treshold_reset = 250; // reset timeout int distance_reset = 10; // Mouse should not move more than this many pixels int border = 0; if (pos.x() == electricLeft) border = 1; else if (pos.x() == electricRight) border = 2; else if (pos.y() == electricTop) border = 3; else if (pos.y() == electricBottom) border = 4; 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 = 0; QRect r = QApplication::desktop()->geometry(); int offset; int desk_before = currentDesktop(); switch(border) { case 1: slotSwitchDesktopLeft(); if (currentDesktop() != desk_before) { offset = r.width() / 5; QCursor::setPos(r.width() - offset, pos.y()); } break; case 2: slotSwitchDesktopRight(); if (currentDesktop() != desk_before) { offset = r.width() / 5; QCursor::setPos(offset, pos.y()); } break; case 3: slotSwitchDesktopUp(); if (currentDesktop() != desk_before) { offset = r.height() / 5; QCursor::setPos(pos.x(), r.height() - offset); } break; case 4: slotSwitchDesktopDown(); if (currentDesktop() != desk_before) { offset = r.height() / 5; QCursor::setPos(pos.x(), offset); } break; } return; } } else { electric_current_border = border; electric_time_first = now; electric_time_last = now; electric_push_point = pos; } int mouse_warp = 1; // reset the pointer to find out wether the user is really pushing switch( border) { case 1: QCursor::setPos(pos.x()+mouse_warp, pos.y()); break; case 2: QCursor::setPos(pos.x()-mouse_warp, pos.y()); break; case 3: QCursor::setPos(pos.x(), pos.y()+mouse_warp); break; case 4: QCursor::setPos(pos.x(), pos.y()-mouse_warp); break; } } // this function is called when the user entered an electric border // with the mouse. It may switch to another virtual desktop void Workspace::electricBorder(XEvent *e) { Time now = e->xcrossing.time; QPoint p(e->xcrossing.x_root, e->xcrossing.y_root); clientMoved(p, now); } // electric borders (input only windows) have to be always on the // top. For that reason kwm calls this function always after some // windows have been raised. void Workspace::raiseElectricBorders() { if(electric_have_borders) { XRaiseWindow(qt_xdisplay(), electric_top_border); XRaiseWindow(qt_xdisplay(), electric_left_border); XRaiseWindow(qt_xdisplay(), electric_bottom_border); XRaiseWindow(qt_xdisplay(), electric_right_border); } } 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(); } // kdDebug() << "NEW TOPMENU:" << c << endl; } void Workspace::removeTopMenu( Client* c ) { // if( c->isTopMenu()) // kdDebug() << "REMOVE TOPMENU:" << c << endl; assert( c->isTopMenu()); assert( topmenus.contains( c )); topmenus.remove( c ); updateCurrentTopMenu(); // TODO reduce topMenuHeight() if possible? } void Workspace::lostTopMenuSelection() { // kdDebug() << "lost TopMenu selection" << endl; // 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; // kdDebug() << "TopMenu selection lost owner" << endl; if( !topmenu_selection->claim( false )) { // kdDebug() << "Failed to claim TopMenu selection" << endl; return; } // kdDebug() << "claimed TopMenu selection" << endl; 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; 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.insertItem( "dummy" ); topmenu_height = tmpmenu.sizeHint().height(); } return topmenu_height; } KDecoration* Workspace::createDecoration( KDecorationBridge* bridge ) { return mgr->createDecoration( bridge ); } QString Workspace::desktopName( int desk ) const { return QString::fromUtf8( rootInfo->desktopName( desk ) ); } bool Workspace::checkStartupNotification( Window w, KStartupInfoData& data ) { return startup->checkStartup( w, data ) == KStartupInfo::Match; } /*! Puts the focus on a dummy window Just using XSetInputFocus() with None would block keyboard input */ void Workspace::focusToNull() { XSetInputFocus(qt_xdisplay(), null_focus_window, RevertToPointerRoot, qt_x_time ); } void Workspace::helperDialog( const QString& message, const Client* c ) { QStringList args; QString type; if( message == "noborderaltf3" ) { QString shortcut = QString( "%1 (%2)" ).arg( keys->label( "Window Operations Menu" )) .arg( keys->shortcut( "Window Operations Menu" ).seq( 0 ).toString()); args << "--msgbox" << i18n( "You have selected to show a window without its border.\n" "Without the border, you won't be able to enable the border " "again using the mouse. Use the window operations menu instead, " "activated using the %1 keyboard shortcut." ) .arg( shortcut ); type = "altf3warning"; } else if( message == "fullscreenaltf3" ) { QString shortcut = QString( "%1 (%2)" ).arg( keys->label( "Window Operations Menu" )) .arg( keys->shortcut( "Window Operations Menu" ).seq( 0 ).toString()); args << "--msgbox" << i18n( "You have selected to show a window in fullscreen mode.\n" "If the application itself doesn't have an option to turn the fullscreen " "mode off, you won't be able to disable it " "again using the mouse. Use the window operations menu instead, " "activated using the %1 keyboard shortcut." ) .arg( shortcut ); type = "altf3warning"; } else assert( false ); KProcess proc; proc << "kdialog" << args; if( !type.isEmpty()) { KConfig cfg( "kwin_dialogsrc" ); cfg.setGroup( "Notification Messages" ); // this depends on KMessageBox if( !cfg.readBoolEntry( type, true )) // has don't show again checked return; // save launching kdialog proc << "--dontagain" << "kwin_dialogsrc:" + type; } if( c != NULL ) proc << "--embed" << QString::number( c->window()); proc.start( KProcess::DontCare ); } } // namespace #include "workspace.moc" Index: trunk/kdebase/kwin/activation.cpp =================================================================== --- trunk/kdebase/kwin/activation.cpp (revision 298356) +++ trunk/kdebase/kwin/activation.cpp (revision 298357) @@ -1,799 +1,810 @@ /***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ /* This file contains things relevant to window activation and focus stealing prevention. */ #include "client.h" #include "workspace.h" #include #include #include #include "notifications.h" #include "atoms.h" #include "group.h" extern Time qt_x_time; namespace KWinInternal { /* Prevention of focus stealing: KWin tries to prevent unwanted changes of focus, that would result from mapping a new window. Also, some nasty applications may try to force focus change even in cases when ICCCM 4.2.7 doesn't allow it (e.g. they may try to activate their main window because the user definitely "needs" to see something happened - misusing of QWidget::setActiveWindow() may be such case). There are 4 ways how a window may become active: - the user changes the active window (e.g. focus follows mouse, clicking on some window's titlebar) - the change of focus will be done by KWin, so there's nothing to solve in this case - the change of active window will be requested using the _NET_ACTIVE_WINDOW message (handled in RootInfo::changeActiveWindow()) - such requests will be obeyed, because this request is meant mainly for e.g. taskbar asking the WM to change the active window as a result of some user action. Normal applications should use this request only rarely in special cases. See also below the discussion of _NET_ACTIVE_WINDOW_TRANSFER. - the change of active window will be done by performing XSetInputFocus() on a window that's not currently active. ICCCM 4.2.7 describes when the application may perform change of input focus. In order to handle misbehaving applications, KWin will try to detect focus changes to windows that don't belong to currently active application, and restore focus back to the currently active window, instead of activating the window that got focus (unfortunately there's no way to FocusChangeRedirect similar to e.g. SubstructureRedirect, so there will be short time when the focus will be changed). The check itself that's done is Workspace::allowClientActivation() (see below). - a new window will be mapped - this is the most complicated case. If the new window belongs to the currently active application, it may be safely mapped on top and activated. The same if there's no active window, or the active window is the desktop. These checks are done by Workspace::allowClientActivation(). Following checks need to compare times. One time is the timestamp of last user action in the currently active window, the other time is the timestamp of the action that originally caused mapping of the new window (e.g. when the application was started). If the first time is newer than the second one, the window will not be activated, as that indicates futher user actions took place after the action leading to this new mapped window. This check is done by Workspace::allowClientActivation(). There are several ways how to get the timestamp of action that caused the new mapped window (done in Client::readUserTimeMapTimestamp()) : - the window may have the _NET_WM_USER_TIME property. This way the application may either explicitly request that the window is not activated (by using 0 timestamp), or the property contains the time of last user action in the application. - KWin itself tries to detect time of last user action in every window, by watching KeyPress and ButtonPress events on windows. This way some events may be missed (if they don't propagate to the toplevel window), but it's good as a fallback for applications that don't provide _NET_WM_USER_TIME, and missing some events may at most lead to unwanted focus stealing. - the timestamp may come from application startup notification. Application startup notification, if it exists for the new mapped window, should include time of the user action that caused it. - if there's no timestamp available, it's checked whether the new window belongs to some already running application - if yes, the timestamp will be 0 (i.e. refuse activation) - if the window is from session restored window, the timestamp will be 0 too, unless this application was the active one at the time when the session was saved, in which case the window will be activated if there wasn't any user interaction since the time KWin was started. - as the last resort, the _KDE_NET_USER_CREATION_TIME timestamp is used. For every toplevel window that is created (see CreateNotify handling), this property is set to the at that time current time. Since at this time it's known that the new window doesn't belong to any existing application (better said, the application doesn't have any other window mapped), it is either the very first window of the application, or its the only window of the application that was hidden before. The latter case is handled by removing the property from windows before withdrawing them, making the timestamp empty for next mapping of the window. In the sooner case, the timestamp will be used. This helps in case when an application is launched without application startup notification, it creates its mainwindow, and starts its initialization (that may possibly take long time). The timestamp used will be older than any user action done after launching this application. - if no timestamp is found at all, the window is activated. The check whether two windows belong to the same application (same process) is done in Client::belongToSameApplication(). Not 100% reliable, but hopefully 99,99% reliable. As a somewhat special case, window activation is always enabled when session saving is in progress. When session saving, the session manager allows only one application to interact with the user. Not allowing window activation in such case would result in e.g. dialogs not becoming active, so focus stealing prevention would cause here more harm than good. Windows that attempted to become active but KWin prevented this will be marked as demanding user attention. They'll get the _NET_WM_STATE_DEMANDS_ATTENTION state, and the taskbar should mark them specially (blink, etc.). The state will be reset when the window eventually really becomes active. There are one more ways how a window can become obstrusive, window stealing focus: By showing above the active window, by either raising itself, or by moving itself on the active desktop. - KWin will refuse raising non-active window above the active one, unless they belong to the same application. Applications shouldn't raise their windows anyway (unless the app wants to raise one of its windows above another of its windows). - KWin activates windows moved to the current desktop (as that seems logical from the user's point of view, after sending the window there directly from KWin, or e.g. using pager). This means applications shouldn't send their windows to another desktop (SELI TODO - but what if they do?) Special cases I can think of: - konqueror reusing, i.e. kfmclient tells running Konqueror instance to open new window - without focus stealing prevention - no problem - with ASN (application startup notification) - ASN is forwarded, and because it's newer than the instance's user timestamp, it takes precedence - without ASN - user timestamp needs to be reset, otherwise it would be used, and it's old; moreover this new window mustn't be detected as window belonging to already running application, or it wouldn't be activated - see Client::sameAppWindowRoleMatch() for the (rather ugly) hack - konqueror preloading, i.e. window is created in advance, and kfmclient tells this Konqueror instance to show it later - without focus stealing prevention - no problem - with ASN - ASN is forwarded, and because it's newer than the instance's user timestamp, it takes precedence - without ASN - user timestamp needs to be reset, otherwise it would be used, and it's old; also, creation timestamp is changed to the time the instance starts (re-)initializing the window, this ensures creation timestamp will still work somewhat even in this case - KUniqueApplication - when the window is already visible, and the new instance wants it to activate - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem - with ASN - ASN is forwarded, and set on the already visible window, KWin treats the window as new with that ASN - without ASN - _NET_ACTIVE_WINDOW as application request is used, and there's no really usable timestamp, only timestamp from the time the (new) application instance was started, so KWin will activate the window *sigh* - the bad thing here is that there's absolutely no chance to recognize the case of starting this KUniqueApp from Konsole (and thus wanting the already visible window to become active) from the case when something started this KUniqueApp without ASN (in which case the already visible window shouldn't become active) - the only solution is using ASN for starting applications, at least silent (i.e. without feedback) - when one application wants to activate another application's window (e.g. KMail activating already running KAddressBook window ?) - without focus stealing prevention - _NET_ACTIVE_WINDOW - no problem - with ASN - can't be here, it's the KUniqueApp case then - without ASN - _NET_ACTIVE_WINDOW as application request should be used, KWin will activate the new window depending on the timestamp and whether it belongs to the currently active application _NET_ACTIVE_WINDOW usage: data.l[0]= 1 ->app request = 2 ->pager request = 0 - backwards compatibility data.l[1]= timestamp */ //**************************************** // Workspace //**************************************** /*! Informs the workspace about the active client, i.e. the client that has the focus (or None if no client has the focus). This functions is called by the client itself that gets focus. It has no other effect than fixing the focus chain and the return value of activeClient(). And of course, to propagate the active client to the world. */ void Workspace::setActiveClient( Client* c, allowed_t ) { if ( active_client == c ) return; if( popup && popup_client != c && set_active_client_recursion == 0 ) { popup->close(); popup_client = 0; } StackingUpdatesBlocker blocker( this ); ++set_active_client_recursion; if( active_client != NULL ) { // note that this may call setActiveClient( NULL ), therefore the recursion counter active_client->setActive( false ); } active_client = c; Q_ASSERT( c == NULL || c->isActive()); if( active_client != NULL ) last_active_client = active_client; if ( active_client ) { focus_chain.remove( c ); if ( c->wantsTabFocus() ) focus_chain.append( c ); active_client->demandAttention( false ); } updateCurrentTopMenu(); updateToolWindows( false ); updateStackingOrder(); // e.g. fullscreens have different layer when active/not-active rootInfo->setActiveWindow( active_client? active_client->window() : 0 ); updateColormap(); --set_active_client_recursion; } /*! Tries to activate the client \a c. This function performs what you expect when clicking the respective entry in a taskbar: showing and raising the client (this may imply switching to the another virtual desktop) and putting the focus onto it. Once X really gave focus to the client window as requested, the client itself will call setActiveClient() and the operation is complete. This may not happen with certain focus policies, though. \sa stActiveClient(), requestFocus() */ void Workspace::activateClient( Client* c, bool force ) { if( c == NULL ) { setActiveClient( NULL, Allowed ); return; } raiseClient( c ); if (!c->isOnDesktop(currentDesktop()) ) { ++block_focus; setCurrentDesktop( c->desktop() ); --block_focus; // popupinfo->showInfo( desktopName(currentDesktop()) ); // AK - not sure } if( c->isMinimized()) c->unminimize(); if( options->focusPolicyIsReasonable()) requestFocus( c, force ); - c->updateUserTime(); + // Don't update user time for clients that have focus stealing workaround. + // As they usually belong to the current active window but fail to provide + // this information, updating their user time would make the user time + // of the currently active window old, and reject further activation for it. + // E.g. typing URL in minicli which will show kio_uiserver dialog (with workaround), + // and then kdesktop shows dialog about SSL certificate. + // This needs also avoiding user creation time in Client::readUserTimeMapTimestamp(). + if( !c->ignoreFocusStealing()) + c->updateUserTime(); } /*! Tries to activate the client by asking X for the input focus. This function does not perform any show, raise or desktop switching. See Workspace::activateClient() instead. \sa Workspace::activateClient() */ void Workspace::requestFocus( Client* c, bool force ) { // the 'if( c == active_client ) return;' optimization mustn't be done here if (!focusChangeEnabled() && ( c != active_client) ) return; //TODO will be different for non-root clients. (subclassing?) if ( !c ) { focusToNull(); return; } if( !c->isOnCurrentDesktop()) // shouldn't happen, call activateClient() if needed { kdWarning( 1212 ) << "requestFocus: not on current desktop" << endl; return; } Client* modal = c->findModal(); if( modal != NULL && modal != c ) { if( !modal->isOnDesktop( c->desktop())) // move the modal to client's desktop modal->setDesktop( c->isOnAllDesktops() ? currentDesktop() : c->desktop()); requestFocus( modal, force ); return; } if ( c->isShown( false ) ) { c->takeFocus( force, Allowed ); should_get_focus.append( c ); focus_chain.remove( c ); if ( c->wantsTabFocus() ) focus_chain.append( c ); } else if ( c->isShade() && c->wantsInput()) { // client cannot accept focus, but at least the window should be active (window menu, et. al. ) c->setActive( true ); focusToNull(); } } /*! Informs the workspace that the client \a c has been hidden. If it was the active client (or to-become the active client), the workspace activates another one. \a c may already be destroyed */ void Workspace::clientHidden( Client* c ) { assert( !c->isShown( true ) || !c->isOnCurrentDesktop()); activateNextClient( c ); } // deactivates 'c' and activates next client void Workspace::activateNextClient( Client* c ) { // if 'c' is not the active or the to-become active one, do nothing if( !( c == active_client || ( should_get_focus.count() > 0 && c == should_get_focus.last()))) return; if( popup ) popup->close(); if( c == active_client ) setActiveClient( NULL, Allowed ); should_get_focus.remove( c ); if( focusChangeEnabled()) { if ( c->wantsTabFocus() && focus_chain.contains( c ) ) { focus_chain.remove( c ); focus_chain.prepend( c ); } if ( options->focusPolicyIsReasonable()) { // search the focus_chain for a client to transfer focus to // if 'c' is transient, transfer focus to the first suitable mainwindow Client* get_focus = NULL; const ClientList mainwindows = c->mainClients(); for( ClientList::ConstIterator it = focus_chain.fromLast(); it != focus_chain.end(); --it ) { if( !(*it)->isShown( false ) || !(*it)->isOnCurrentDesktop()) continue; if( mainwindows.contains( *it )) { get_focus = *it; break; } if( get_focus == NULL ) get_focus = *it; } if( get_focus == NULL ) get_focus = findDesktop( true, currentDesktop()); if( get_focus != NULL ) requestFocus( get_focus ); else focusToNull(); } } else // if blocking focus, move focus to the desktop later if needed // in order to avoid flickering focusToNull(); } void Workspace::gotFocusIn( const Client* c ) { if( should_get_focus.contains( const_cast< Client* >( c ))) { // remove also all sooner elements that should have got FocusIn, // but didn't for some reason (and also won't anymore, because they were sooner) while( should_get_focus.first() != c ) should_get_focus.pop_front(); should_get_focus.pop_front(); // remove 'c' } } // focus_in -> the window got FocusIn event // session_active -> the window was active when saving session bool Workspace::allowClientActivation( const Client* c, Time time, bool focus_in, bool session_active ) { // options->focusStealingPreventionLevel : // 0 - none - old KWin behaviour, new windows always get focus // 1 - low - focus stealing prevention is applied normally, when unsure, activation is allowed // 2 - normal - focus stealing prevention is applied normally, when unsure, activation is not allowed, // this is the default // 3 - high - new window gets focus only if it belongs to the active application, // or when no window is currently active // 4 - extreme - no window gets focus without user intervention if( time == -1U ) time = c->userTime(); if( session_saving && options->focusStealingPreventionLevel <= 2 ) // <= normal { return true; } Client* ac = mostRecentlyActivatedClient(); if( focus_in ) { if( should_get_focus.contains( const_cast< Client* >( c ))) return true; // FocusIn was result of KWin's action // Before getting FocusIn, the active Client already // got FocusOut, and therefore got deactivated. ac = last_active_client; } if( options->focusStealingPreventionLevel == 0 ) // none return true; if( options->focusStealingPreventionLevel == 4 ) // extreme return false; if( ac == NULL || ac->isDesktop()) { kdDebug( 1212 ) << "Activation: No client active, allowing" << endl; return true; // no active client -> always allow } - if( options->ignoreFocusStealingClasses.contains(QString::fromLatin1(c->resourceClass()))) + if( c->ignoreFocusStealing()) return true; if( time == 0 ) // explicitly asked not to get focus return false; // TODO window urgency -> return true? if( Client::belongToSameApplication( c, ac, true )) { kdDebug( 1212 ) << "Activation: Belongs to active application" << endl; return true; } if( options->focusStealingPreventionLevel == 3 ) // high return false; if( time == -1U ) // no time known if( session_active ) return !was_user_interaction; // see Client::readUserTimeMapTimestamp() else { kdDebug( 1212 ) << "Activation: No timestamp at all" << endl; if( options->focusStealingPreventionLevel == 1 ) // low return true; // no timestamp at all, don't activate - because there's also creation timestamp // done on CreateNotify, this case should happen only in case application // maps again already used window, i.e. this won't happen after app startup return false; } // options->focusStealingPreventionLevel == 2 // normal Time user_time = ac->userTime(); - kdDebug( 1212 ) << "Activation, compared:" << time << ":" << user_time + kdDebug( 1212 ) << "Activation, compared:" << c << ":" << time << ":" << user_time << ":" << ( timestampCompare( time, user_time ) >= 0 ) << endl; return timestampCompare( time, user_time ) >= 0; // time >= user_time } // basically the same like allowClientActivation(), this time allowing // a window to be fully raised upon its own request (XRaiseWindow), // if refused, it will be raised only on top of windows belonging // to the same application bool Workspace::allowFullClientRaising( const Client* c ) { if( session_saving && options->focusStealingPreventionLevel <= 2 ) // <= normal { return true; } Client* ac = mostRecentlyActivatedClient(); if( options->focusStealingPreventionLevel == 0 ) // none return true; if( options->focusStealingPreventionLevel == 4 ) // extreme return false; if( ac == NULL || ac->isDesktop()) { kdDebug( 1212 ) << "Raising: No client active, allowing" << endl; return true; // no active client -> always allow } - if( options->ignoreFocusStealingClasses.contains(QString::fromLatin1(c->resourceClass()))) + if( c->ignoreFocusStealing()) return true; // TODO window urgency -> return true? if( Client::belongToSameApplication( c, ac, true )) { kdDebug( 1212 ) << "Raising: Belongs to active application" << endl; return true; } if( options->focusStealingPreventionLevel == 3 ) // high return false; if( !c->hasUserTimeSupport()) { kdDebug( 1212 ) << "Raising: No support" << endl; if( options->focusStealingPreventionLevel == 1 ) // low return true; } // options->focusStealingPreventionLevel == 2 // normal kdDebug( 1212 ) << "Raising: Refusing" << endl; return false; } // called from Client after FocusIn that wasn't initiated by KWin and the client // wasn't allowed to activate void Workspace::restoreFocus() { // this updateXTime() is necessary - as FocusIn events don't have // a timestamp *sigh*, kwin's timestamp would be older than the timestamp // that was used by whoever caused the focus change, and therefore // the attempt to restore the focus would fail due to old timestamp updateXTime(); if( should_get_focus.count() > 0 ) requestFocus( should_get_focus.last()); else if( last_active_client ) requestFocus( last_active_client ); } void Workspace::clientAttentionChanged( Client* c, bool set ) { if( set ) { attention_chain.remove( c ); attention_chain.prepend( c ); } else attention_chain.remove( c ); } // This is used when a client should be shown active immediately after requestFocus(), // without waiting for the matching FocusIn that will really make the window the active one. // Used only in special cases, e.g. for MouseActivateRaiseandMove with transparent windows, bool Workspace::fakeRequestedActivity( Client* c ) { if( should_get_focus.count() > 0 && should_get_focus.last() == c ) { if( c->isActive()) return false; c->setActive( true ); return true; } return false; } void Workspace::unfakeActivity( Client* c ) { if( should_get_focus.count() > 0 && should_get_focus.last() == c ) { // TODO this will cause flicker, and probably is not needed if( last_active_client != NULL ) last_active_client->setActive( true ); else c->setActive( false ); } } //******************************************** // Client //******************************************** /*! Updates the user time (time of last action in the active window). This is called inside kwin for every action with the window that qualifies for user interaction (clicking on it, activate it externally, etc.). */ void Client::updateUserTime( Time time ) { // copied in Group::updateUserTime if( time == CurrentTime ) time = qt_x_time; if( time != -1U && ( user_time == CurrentTime || timestampCompare( time, user_time ) > 0 )) // time > user_time user_time = time; } Time Client::readUserCreationTime() const { long result = -1; // Time == -1 means none Atom type; int format, status; unsigned long nitems = 0; unsigned long extra = 0; unsigned char *data = 0; KXErrorHandler handler; // ignore errors? status = XGetWindowProperty( qt_xdisplay(), window(), atoms->kde_net_wm_user_creation_time, 0, 10000, FALSE, XA_CARDINAL, &type, &format, &nitems, &extra, &data ); if (status == Success ) { if (data && nitems > 0) result = *((long*) data); XFree(data); } return result; } void Client::demandAttention( bool set ) { if( isActive()) set = false; info->setState( set ? NET::DemandsAttention : 0, NET::DemandsAttention ); workspace()->clientAttentionChanged( this, set ); } // TODO I probably shouldn't be lazy here and do it without the macro, so that people can read it KWIN_COMPARE_PREDICATE( SameApplicationActiveHackPredicate, const Client*, // ignore already existing splashes, toolbars, utilities, menus and topmenus, // as the app may show those before the main window !cl->isSplash() && !cl->isToolbar() && !cl->isTopMenu() && !cl->isUtility() && !cl->isMenu() && Client::belongToSameApplication( cl, value, true ) && cl != value); Time Client::readUserTimeMapTimestamp( const KStartupInfoData* asn_data, const SessionInfo* session ) const { Time time = info->userTime(); kdDebug( 1212 ) << "User timestamp, initial:" << time << endl; // newer ASN timestamp always replaces user timestamp, unless user timestamp is 0 // helps e.g. with konqy reusing if( asn_data != NULL && time != 0 && ( time == -1U || ( asn_data->timestamp() != -1U && timestampCompare( asn_data->timestamp(), time ) > 0 ))) time = asn_data->timestamp(); kdDebug( 1212 ) << "User timestamp, ASN:" << time << endl; if( time == -1U ) { // The window doesn't have any timestamp. // If it's the first window for its application // (i.e. there's no other window from the same app), // use the _KDE_NET_WM_USER_CREATION_TIME trick. // Otherwise, refuse activation of a window // from already running application if this application // is not the active one. Client* act = workspace()->mostRecentlyActivatedClient(); if( act != NULL && !belongToSameApplication( act, this, true )) { bool first_window = true; if( isTransient()) { if( act->hasTransient( this, true )) ; // is transient for currently active window, even though it's not // the same app (e.g. kcookiejar dialog) -> allow activation else if( groupTransient() && findClientInList( mainClients(), SameApplicationActiveHackPredicate( this )) == NULL ) ; // standalone transient else first_window = false; } else { if( workspace()->findClient( SameApplicationActiveHackPredicate( this ))) first_window = false; } if( !first_window ) { kdDebug( 1212 ) << "User timestamp, already exists:" << 0 << endl; return 0; // refuse activation } } // Creation time would just mess things up during session startup, // as possibly many apps are started up at the same time. // If there's no active window yet, no timestamp will be needed, // as plain Workspace::allowClientActivation() will return true // in such case. And if there's already active window, // it's better not to activate the new one. // Unless it was the active window at the time // of session saving and there was no user interaction yet, // this check will be done in Workspace::allowClientActiovationTimestamp(). if( session && !session->fake ) return -1U; - time = readUserCreationTime(); + if( ignoreFocusStealing() && act != NULL ) + time = act->userTime(); + else + time = readUserCreationTime(); } - kdDebug( 1212 ) << "User timestamp, final:" << time << endl; + kdDebug( 1212 ) << "User timestamp, final:" << this << ":" << time << endl; return time; } Time Client::userTime() const { Time time = user_time; assert( group() != NULL ); if( time == -1U || ( group()->userTime() != -1U && timestampCompare( group()->userTime(), time ) > 0 )) time = group()->userTime(); return time; } /*! Sets the client's active state to \a act. This function does only change the visual appearance of the client, it does not change the focus setting. Use Workspace::activateClient() or Workspace::requestFocus() instead. If a client receives or looses the focus, it calls setActive() on its own. */ void Client::setActive( bool act) { if ( active == act ) return; active = act; workspace()->setActiveClient( act ? this : NULL, Allowed ); if ( active ) Notify::raise( Notify::Activate ); if( !active ) cancelAutoRaise(); if( !active && shade_mode == ShadeActivated ) setShade( ShadeNormal ); StackingUpdatesBlocker blocker( workspace()); workspace()->updateClientLayer( this ); // active windows may get different layer // TODO optimize? mainClients() may be a bit expensive ClientList mainclients = mainClients(); for( ClientList::ConstIterator it = mainclients.begin(); it != mainclients.end(); ++it ) if( (*it)->isFullScreen()) // fullscreens go high even if their transient is active workspace()->updateClientLayer( *it ); if( decoration != NULL ) decoration->activeChange(); updateMouseGrab(); updateUrgency(); // demand attention again if it's still urgent } void Client::startupIdChanged() { KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification( window(), asn_data ); if( !asn_valid ) return; if( asn_data.desktop() != 0 ) workspace()->sendClientToDesktop( this, asn_data.desktop(), true ); if( asn_data.timestamp() != -1U ) { bool activate = workspace()->allowClientActivation( this, asn_data.timestamp()); if( asn_data.desktop() != 0 && !isOnCurrentDesktop()) activate = false; // it was started on different desktop than current one if( activate ) workspace()->activateClient( this ); else demandAttention(); } } void Client::updateUrgency() { if( urgency ) demandAttention(); } //**************************************** // Group //**************************************** void Group::startupIdChanged() { KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification( leader_wid, asn_data ); if( !asn_valid ) return; if( asn_data.timestamp() != -1U && user_time != -1U &×tampCompare( asn_data.timestamp(), user_time ) > 0 ) user_time = asn_data.timestamp(); } void Group::updateUserTime( Time time ) { // copy of Client::updateUserTime if( time == CurrentTime ) time = qt_x_time; if( time != -1U && ( user_time == CurrentTime || timestampCompare( time, user_time ) > 0 )) // time > user_time user_time = time; } } // namespace