Index: branches/kwin_iii/kdebase/kwin/client.cpp =================================================================== --- branches/kwin_iii/kdebase/kwin/client.cpp (revision 240956) +++ branches/kwin_iii/kdebase/kwin/client.cpp (revision 240957) @@ -1,4291 +1,4290 @@ /***************************************************************** 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "workspace.h" #include "client.h" #include "events.h" #include "atoms.h" #include "windowwrapper.h" #include "geometrytip.h" #include "group.h" #include #include #include #include #include #include #include #include #include #include #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 { WinInfo::WinInfo( Client * c, Display * display, Window window, Window rwin, const unsigned long pr[], int pr_size ) : NETWinInfo( display, window, rwin, pr, pr_size, NET::WindowManager ), m_client( c ) { } void WinInfo::changeDesktop(int desktop) { m_client->workspace()->sendClientToDesktop( m_client, desktop, true ); } void WinInfo::changeState( unsigned long state, unsigned long mask ) { mask &= ~NET::Sticky; // KWin doesn't support large desktops, ignore mask &= ~NET::Hidden; // clients are not allowed to change this directly state &= mask; // for safety, clear all other bits if ( (mask & NET::Max) == NET::Max ) m_client->setMaximize( state & NET::MaxVert, state & NET::MaxHoriz ); else if ( mask & NET::MaxVert ) m_client->setMaximize( state & NET::MaxVert, m_client->maximizeMode() & Client::MaximizeHorizontal ); else if ( mask & NET::MaxHoriz ) m_client->setMaximize( m_client->maximizeMode() & Client::MaximizeVertical, state & NET::MaxHoriz ); if ( mask & NET::Shaded ) m_client->setShade( state & NET::Shaded ? Client::ShadeNormal : Client::ShadeNone ); if ( mask & NET::KeepAbove) m_client->setKeepAbove( (state & NET::KeepAbove) != 0 ); if ( mask & NET::KeepBelow) m_client->setKeepBelow( (state & NET::KeepBelow) != 0 ); if( mask & NET::SkipTaskbar ) m_client->setSkipTaskbar( ( state & NET::SkipTaskbar ) != 0, true ); if( mask & NET::SkipPager ) m_client->setSkipPager( ( state & NET::SkipPager ) != 0 ); if( mask & NET::DemandsAttention ) m_client->demandAttention(( state & NET::DemandsAttention ) != 0 ); if( mask & NET::Modal ) m_client->setModal( ( state & NET::Modal ) != 0 ); } static int nullErrorHandler(Display *, XErrorEvent *) { return 0; } static QCString getStringProperty(WId w, Atom prop, char separator=0); 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); } /* 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, QWidget *parent, const char *name, WFlags f ) : QWidget( parent, name, f | WX11BypassWM ), move_faked_activity( false ), 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 ) // SELI do all as initialization { setMouseTracking( TRUE ); // does XSelectInput() // SELI is it really necessary to have setMouseTracking() ? XSelectInput( qt_xdisplay(), winId(), KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | KeymapStateMask | ButtonMotionMask | EnterWindowMask | LeaveWindowMask | FocusChangeMask | ExposureMask | PropertyChangeMask | StructureNotifyMask | SubstructureRedirectMask | PointerMotionMask | // for setMouseTracking() // add VisibilityChangeMask VisibilityChangeMask ); wspace = ws; win = None; autoRaiseTimer = 0; shadeHoverTimer = 0; // set the initial mapping state mapping_state = WithdrawnState; desk = 0; // no desktop yet mode = Nowhere; buttonDown = FALSE; moveResizeMode = FALSE; info = NULL; wwrap = NULL; shade_mode = ShadeNone; active = FALSE; keep_above = FALSE; keep_below = FALSE; is_shape = FALSE; may_move = TRUE; may_resize = TRUE; may_minimize = TRUE; may_maximize = TRUE; may_close = TRUE; is_fullscreen = FALSE; skip_taskbar = FALSE; original_skip_taskbar = false; minimized = false; hidden = false; modal = false; Pdeletewindow = 0; Ptakefocus = 0; Pcontexthelp = 0; Pping = 0; input = FALSE; store_settings = FALSE; skip_pager = FALSE; max_mode = MaximizeRestore; cmap = None; // SELI initialize xsizehints?? } /*! "Dumb" destructor. */ Client::~Client() { assert(!moveResizeMode); delete info; } // use destroyClient() or releaseWindow(), Client instances cannot be deleted directly void Client::deleteClient( Client* c, allowed_t ) { delete c; } /*! 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; // initial state int init_mapping_state = NormalState; XWMHints * hints = XGetWMHints(qt_xdisplay(), w ); if (hints && (hints->flags & StateHint)) init_mapping_state = hints->initial_state; if (hints) XFree(hints); if( isMapped ) init_mapping_state = NormalState; // if it's already mapped, ignore hint if( init_mapping_state != NormalState && init_mapping_state != IconicState ) { // don't manage windows with strange initial mapping state // it's mapped and unmapped for WindowMaker applets // they usually map with initial_state == Withdrawn, // and don't want to be mapped // mapping it for a while will give the docking panel // a chance to get MapNotify for it, and swallow the matching // window // SELI XMapWindow( qt_xdisplay(), w ); XUnmapWindow( qt_xdisplay(), w ); return false; } // from this place on, manage() mustn't return false win = w; // SELI order all these things in some sane manner bool init_minimize = init_mapping_state == IconicState; unsigned long properties[ 2 ]; properties[ WinInfo::PROTOCOLS ] = NET::WMDesktop | NET::WMState | NET::WMWindowType | NET::WMStrut | NET::WMName | NET::WMIconGeometry | NET::WMIcon | NET::WMPid | 0; properties[ WinInfo::PROTOCOLS2 ] = NET::WM2UserTime | NET::WM2StartupId | 0; info = new WinInfo( this, qt_xdisplay(), win, qt_xrootwin(), properties, 2 ); wwrap = new WindowWrapper( w, this ); wwrap->installEventFilter( this ); if( info->userTime() != -1U ) user_time = info->userTime(); cmap = attr.colormap; bool mresize, mmove, mminimize, mmaximize, mclose; if( Motif::funcFlags( win, mresize, mmove, mminimize, mmaximize, mclose )) { may_resize = mresize; may_move = mmove; may_minimize = mminimize; may_maximize = mmaximize; may_close = mclose; } XClassHint classHint; if ( XGetClassHint( qt_xdisplay(), win, &classHint ) ) { resource_name = classHint.res_name; resource_class = classHint.res_class; XFree( classHint.res_name ); XFree( classHint.res_class ); } fetchName(); getWMHints(); readTransient(); getIcons(); getWindowProtocols(); getWmNormalHints(); // get xSizeHint getWmClientLeader(); 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( this, asn_data ); // SELI maybe move this lower? createLayout() may call e.g. isResizable() createLayout(); if (layout()) layout()->setResizeMode( QLayout::Minimum ); workspace()->updateClientLayer( this ); QRect geom( attr.x, attr.y, attr.width, attr.height ); bool placementDone = FALSE; SessionInfo* session = workspace()->takeSessionInfo( this ); if ( session ) geom = session->geometry; if ( session ) { if ( session->minimized ) init_minimize = true; } // TODO for all noborder clients? if ( geom.size() == workspace()->geometry().size() && inherits( "KWinInternal::NoBorderClient" ) && ( isOverride() || isNormalWindow())) { // if ( !keep_above ) CHECKME is_fullscreen = TRUE; may_move = FALSE; // don't let fullscreen windows be moved around may_resize = FALSE; may_minimize = FALSE; may_maximize = FALSE; - may_close = FALSE; geom.moveTopLeft( QPoint( 0, 0 )); // in case they try to make it fullscreen, but misplace (#55290) placementDone = true; } if ( isDesktop() ) { // desktops are treated slightly special geom = workspace()->geometry(); may_move = FALSE; may_resize = FALSE; may_minimize = FALSE; may_maximize = FALSE; may_close = FALSE; placementDone = 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; for( ClientList::ConstIterator it = mainclients.begin(); it != mainclients.end(); ++it ) if( (*it)->isOnCurrentDesktop()) on_current = true; if( on_current ) desk = workspace()->currentDesktop(); else if( mainclients.count() > 0 ) desk = mainclients.first()->desktop(); } } if ( desk == 0 ) // assume window wants to be visible on the current desktop desk = workspace()->currentDesktop(); info->setDesktop( desk ); workspace()->updateOnAllDesktopsOfTransients( this ); // SELI onAllDesktopsChange( isOnAllDesktops()); QRect area = workspace()->clientArea( geom.center(), desktop()); if ( isMapped || session || ( isTransient() && !isUtility() && !isDialog() && !isSplash())) { // TODO placementDone = TRUE; } else if( isDialog()) { if( options->dialog_placement == Options::ObeyApplication ) placementDone = true; // else force using placement policy } else if( isSplash()) ; // force using placement policy else { bool ignorePPosition = false; XClassHint classHint; if ( XGetClassHint(qt_xdisplay(), win, &classHint) != 0 ) { if ( classHint.res_class ) ignorePPosition = ( options->ignorePositionClasses.find(QString::fromLatin1(classHint.res_class)) != options->ignorePositionClasses.end() ); XFree(classHint.res_name); XFree(classHint.res_class); } if ((xSizeHint.flags & PPosition) && ! ignorePPosition) { int tx = geom.x(); int ty = geom.y(); if (tx < 0) tx = area.right() + tx; if (ty < 0) ty = area.bottom() + ty; geom.moveTopLeft(QPoint(tx, ty)); } if ( ( (xSizeHint.flags & PPosition) && !ignorePPosition ) || (xSizeHint.flags & USPosition) ) { placementDone = TRUE; } 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 ) ) ); windowWrapper()->resize( geom.size() ); // the clever activate() trick is necessary activateLayout(); resize ( sizeForWindowSize( geom.size() ) ); activateLayout(); if( may_move ) { if( geom.x() > area.right() || geom.y() > area.bottom()) placementDone = FALSE; // weird, do not trust. } if ( placementDone ) { move( geom.x(), geom.y() ); // TODO QWidget::move() ? move(gravitate( FALSE ) ); // take the decoration size into account } else { workspace()->place( this ); placementDone = TRUE; } if (( !isSpecialWindow() || isToolbar()) && may_move ) { if ( geometry().right() > area.right() && width() < area.width() ) move( area.right() - width(), y() ); if ( geometry().bottom() > area.bottom() && height() < area.height() ) move( x(), area.bottom() - height() ); if( !area.contains( geometry().topLeft() )) { int tx = x(); int ty = y(); if ( tx >= 0 && tx < area.x() ) tx = area.x(); if ( ty >= 0 && ty < area.y() ) ty = area.y(); move( tx, ty ); } } // inform clients about the frame geometry NETStrut strut; QRect wr = windowWrapper()->geometry(); QRect mr = rect(); strut.left = wr.left(); strut.right = mr.right() - wr.right(); strut.top = wr.top(); strut.bottom = mr.bottom() - wr.bottom(); info->setKDEFrameStrut( strut ); XShapeSelectInput( qt_xdisplay(), win, ShapeNotifyMask ); if ( (is_shape = Shape::hasShape( win )) ) { 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()) { for( ClientList::ConstIterator it = mainClients().begin(); it != mainClients().end(); ++it ) if( (*it)->isShown()) init_minimize = false; // SELI even e.g. for NET::Utility? } if( init_minimize ) minimize(); // 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 ); if( session->maximized != MaximizeRestore ) { maximize( (MaximizeMode) session->maximized ); geom_restore = session->restore; } setShade( session->shaded ? ShadeNormal : ShadeNone ); } else if ( !is_fullscreen ){ 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 ); } updateAllowedActions( true ); if ( isShown() && !doNotShow ) { if( isDialog()) Events::raise( Events::TransNew ); if( isNormalWindow()) Events::raise( Events::New ); if( isOnCurrentDesktop()) { setMappingState( NormalState ); if( isMapped || ( isSpecialWindow() && !isOverride() && !isToolbar())) { workspace()->raiseClient( this ); rawShow(); } else { Time time = readUserTimeMapTimestamp( asn_valid ? &asn_data : NULL, session ); if( workspace()->allowClientActivation( this, time, false, session && session->active )) { workspace()->raiseClient( this ); rawShow(); if ( options->focusPolicyIsReasonable() && wantsTabFocus() ) workspace()->requestFocus( this ); } else { workspace()->restackClientUnderActive( this ); rawShow(); if( !session ) demandAttention(); } } } else { virtualDesktopChange(); workspace()->raiseClient( this ); if( !session && !isMapped ) demandAttention(); } } else if( !doNotShow ) // !isShown() { rawHide(); setMappingState( IconicState ); } else // doNotShow { // SELI HACK !!! rawHide(); setMappingState( IconicState ); } assert( mappingState() != WithdrawnState ); if ( !doNotShow ) { workspace()->updateClientArea(); area = workspace()->clientArea( geometry().center() ); } updateWorkareaDiffs( area ); sendSyntheticConfigureNotify(); delete session; return true; } // updates differences to workarea edges for all directions // area_ is workarea for this Client (optimization - given only if already known) void Client::updateWorkareaDiffs( const QRect& area_ ) { QRect area = area_; if( !area.isValid()) // default arg area = workspace()->clientArea( geometry().center(), desktop()); QRect geom = geometry(); workarea_diff_x = computeWorkareaDiff( geom.left(), geom.right(), area.left(), area.right()); workarea_diff_y = computeWorkareaDiff( geom.top(), geom.bottom(), area.top(), area.bottom()); } // If the client was inside workarea in the x direction, and if it was close to the left/right // edge, return the distance from the left/right edge (negative for left, positive for right) // INT_MIN means 'not inside workarea', INT_MAX means 'not near edge'. // In order to recognize 'at the left workarea edge' from 'at the right workarea edge' // (i.e. negative vs positive zero), the distances are one larger in absolute value than they // really are (i.e. 5 pixels from the left edge is -6, not -5). A bit hacky, but I'm lazy // to rewrite it just to make it nicer. If this will ever get touched again, perhaps then. // the y direction is done the same, just the values will be rotated: top->left, bottom->right int Client::computeWorkareaDiff( int left, int right, int a_left, int a_right ) { int left_diff = left - a_left; int right_diff = a_right - right; if( left_diff < 0 || right_diff < 0 ) return INT_MIN; else // fully inside workarea in this direction direction { int a_third = ( a_right - a_left ) / 3; if( left_diff < right_diff ) return left_diff < a_third ? -left_diff - 1 : INT_MAX; else if( left_diff > right_diff ) return right_diff < a_third ? right_diff + 1 : INT_MAX; return INT_MAX; // not close to workarea edge } } void Client::checkWorkspacePosition() { if( isMaximized()) // TODO update geom_restore? changeMaximize( false, false, true ); // adjust size if( isFullScreen()) return; if( isDock()) return; // TopMenu has this reimplemented if( isOverride()) return; // I wish I knew what to do here :( QRect area = workspace()->clientArea( geometry().center(), desktop()); int old_diff_x = workarea_diff_x; int old_diff_y = workarea_diff_y; updateWorkareaDiffs( area ); // this can be true only if this window was mapped before KWin // was started - in such case, don't adjust position to workarea, // because the window already had its position, and if a window // with a strut altering the workarea would be managed in initialization // after this one, this window would be moved if( workspace()->initializing()) return; QRect new_geom = geometry(); QRect tmp_rect_x( new_geom.left(), 0, new_geom.width(), 0 ); QRect tmp_area_x( area.left(), 0, area.width(), 0 ); checkDirection( workarea_diff_x, old_diff_x, tmp_rect_x, tmp_area_x ); // the x<->y swapping QRect tmp_rect_y( new_geom.top(), 0, new_geom.height(), 0 ); QRect tmp_area_y( area.top(), 0, area.height(), 0 ); checkDirection( workarea_diff_y, old_diff_y, tmp_rect_y, tmp_area_y ); new_geom = QRect( tmp_rect_x.left(), tmp_rect_y.left(), tmp_rect_x.width(), tmp_rect_y.width()); QRect final_geom( new_geom.topLeft(), adjustedSize( new_geom.size())); if( final_geom != new_geom ) // size increments, or size restrictions { // adjusted size differing matters only for right and bottom edge if( old_diff_x != INT_MAX && old_diff_x > 0 ) final_geom.moveRight( area.right() - ( old_diff_x - 1 )); if( old_diff_y != INT_MAX && old_diff_y > 0 ) final_geom.moveBottom( area.bottom() - ( old_diff_y - 1 )); } if( final_geom != geometry() ) setGeometry( final_geom ); updateWorkareaDiffs( area ); } // Try to be smart about keeping the clients visible. // If the client was fully inside the workspace before, try to keep // it still inside the workarea, possibly moving it or making it smaller if possible, // and try to keep the distance from the nearest workarea edge. // On the other hand, it it was partially moved outside of the workspace in some direction, // don't do anything with that direction if it's still at least partially visible. If it's // not visible anymore at all, make sure it's visible at least partially // again (not fully, as that could(?) be potentionally annoying) by // moving it slightly inside the workarea (those '+ 5'). // Again, this is done for the x direction, y direction will be done by x<->y swapping void Client::checkDirection( int new_diff, int old_diff, QRect& rect, const QRect& area ) { if( old_diff != INT_MIN ) // was inside workarea { if( old_diff == INT_MAX ) // was in workarea, but far from edge { if( new_diff == INT_MIN ) // is not anymore fully in workarea { rect.setLeft( area.left()); rect.setRight( area.right()); } return; } if( may_resize ) { if( rect.width() > area.width()) rect.setWidth( area.width()); if( rect.width() >= area.width() / 2 ) { if( old_diff < 0 ) rect.setLeft( area.left() + ( -old_diff - 1 ) ); else // old_diff > 0 rect.setRight( area.right() - ( old_diff - 1 )); } } if( may_move ) { if( old_diff < 0 ) // was in left third, keep distance from left edge rect.moveLeft( area.left() + ( -old_diff - 1 )); else // old_diff > 0 // was in right third, keep distance from right edge rect.moveRight( area.right() - ( old_diff - 1 )); } // this may_resize block is copied from above, the difference is // the condition with 'area.width() / 2' - for windows that are not wide, // moving is preffered to resizing if( may_resize ) { if( old_diff < 0 ) rect.setLeft( area.left() + ( -old_diff - 1 ) ); else // old_diff > 0 rect.setRight( area.right() - ( old_diff - 1 )); } } if( rect.right() < area.left() + 5 || rect.left() > area.right() - 5 ) { // not visible (almost) at all - try to make it at least partially visible if( may_move ) { if( rect.left() < area.left() + 5 ) rect.moveRight( area.left() + 5 ); if( rect.right() > area.right() - 5 ) rect.moveLeft( area.right() - 5 ); } } } /*! Updates the user time (time of last action in the active window). This is called inside kwin for every action with the window that qualifies for user interaction (clicking on it, activate it externally, etc.). */ void Client::updateUserTime( Time time ) { if( time == CurrentTime ) time = qt_x_time; if( time != -1U && ( user_time == CurrentTime || timestampCompare( time, user_time ) > 0 )) // time > user_time user_time = time; } Time Client::readUserCreationTime() const { long result = -1; // Time == -1 means none Atom type; int format, status; unsigned long nitems = 0; unsigned long extra = 0; unsigned char *data = 0; XErrorHandler oldHandler = XSetErrorHandler(nullErrorHandler); // SELI? status = XGetWindowProperty( qt_xdisplay(), window(), atoms->kde_net_wm_user_creation_time, 0, 10000, FALSE, XA_CARDINAL, &type, &format, &nitems, &extra, &data ); XSetErrorHandler(oldHandler); 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 ); } Time Client::readUserTimeMapTimestamp( const KStartupInfoData* asn_data, const SessionInfo* session ) const { Time time = info->userTime(); kdDebug() << "READ1:" << 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() << "READ2:" << time << endl; if( time == -1U ) { // The window doesn't have any timestamp. // If it's the first window for its application // (i.e. there's no other window from the same app), // use the _KDE_NET_WM_USER_CREATION_TIME trick. // Otherwise, refuse activation of a window // from already running application if this application // is not the active one. if( workspace()->activeClient() != NULL && !belongToSameApplication( workspace()->activeClient(), this, true )) { bool first_window = true; if( isTransient()) { if( workspace()->activeClient()->hasTransient( this, true )) ; // is transient for currently active window, even though it's not // the same app (e.g. kcookiejar dialog) -> allow activation else if( groupTransient() && mainClients().isEmpty()) ; // standalone transient else first_window = false; } else { // SELI TODO using stackingOrder() is weird for( ClientList::ConstIterator it = workspace()->stackingOrder().begin(); it != workspace()->stackingOrder().end(); ++it ) { if( belongToSameApplication( *it, this, true )) { first_window = false; break; } } } if( !first_window ) { kdDebug() << "READ3:" << 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 ) return -1U; time = readUserCreationTime(); } kdDebug() << "READ4:" << time << endl; return time; } // hacks for broken apps here bool Client::resourceMatch( const Client* c1, const Client* c2 ) { // xv has "xv" as resource name, and different strings starting with "XV" as resource class if( qstrncmp( c1->resourceClass(), "XV", 2 ) == 0 && c1->resourceName() == "xv" ) return qstrncmp( c2->resourceClass(), "XV", 2 ) == 0 && c2->resourceName() == "xv"; // Mozilla has "Mozilla" as resource name, and different strings as resource class if( c1->resourceName() == "Mozilla" ) return c2->resourceName() == "Mozilla"; return c1->resourceClass() == c2->resourceClass(); } bool Client::belongToSameApplication( const Client* c1, const Client* c2, bool active_hack ) { bool same_app = false; if( c1->isTransient() && c2->hasTransient( c1, true )) same_app = true; // c1 has c2 as mainwindow else if( c2->isTransient() && c1->hasTransient( c2, true )) same_app = true; // c2 has c1 as mainwindow else if( c1->pid() != c2->pid() || c1->wmClientMachine() != c2->wmClientMachine()) ; // different processes else if( c1->wmClientLeader() != c2->wmClientLeader()) ; // different client leader else if( !resourceMatch( c1, c2 )) ; // different apps else if( !sameAppWindowRoleMatch( c1, c2, active_hack )) ; // "different" apps else same_app = true; // looks like it's the same app return same_app; } // Non-transient windows with window role containing '#' are always // considered belonging to different applications (unless // the window role is exactly the same). KMainWindow sets // window role this way by default, and different KMainWindow // usually "are" different application from user's point of view. // This help with no-focus-stealing for e.g. konqy reusing. // On the other hand, if one of the windows is active, they are // considered belonging to the same application. This is for // the cases when opening new mainwindow directly from the application, // e.g. 'Open New Window' in konqy ( active_hack == true ). bool Client::sameAppWindowRoleMatch( const Client* c1, const Client* c2, bool active_hack ) { if( c1->isTransient()) { while( c1->transientFor() != NULL ) c1 = c1->transientFor(); if( c1->groupTransient()) return c1->group() == c2->group() // if a group transient is in its own group, it didn't possibly have a group, // and therefore should be considered belonging to the same app like // all other windows from the same app || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; } if( c2->isTransient()) { while( c2->transientFor() != NULL ) c2 = c2->transientFor(); if( c2->groupTransient()) return c1->group() == c2->group() || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; } int pos1 = c1->windowRole().find( '#' ); int pos2 = c2->windowRole().find( '#' ); if(( pos1 >= 0 && pos2 >= 0 ) || // hacks here // Mozilla has resourceName() and resourceClass() swapped c1->resourceName() == "Mozilla" && c2->resourceName() == "Mozilla" ) { if( !active_hack ) // without the active hack for focus stealing prevention, return c1 == c2; // different mainwindows are always different apps if( !c1->isActive() && !c2->isActive()) return c1 == c2; else return true; } return true; } void Client::startupIdChanged() { KStartupInfoData asn_data; bool asn_valid = workspace()->checkStartupNotification( this, asn_data ); if( !asn_valid ) return; if( asn_data.desktop() != 0 ) workspace()->sendClientToDesktop( this, asn_data.desktop(), true ); if( asn_data.timestamp() != -1U ) { bool activate = workspace()->allowClientActivation( this, asn_data.timestamp()); if( asn_data.desktop() != 0 && !isOnCurrentDesktop()) activate = false; // it was started on different desktop than current one if( activate ) workspace()->activateClient( this ); else demandAttention(); } } /*! Gets the client's normal WM hints and reconfigures itself respectively. */ void Client::getWmNormalHints() { long msize; if (XGetWMNormalHints(qt_xdisplay(), win, &xSizeHint, &msize) == 0 ) xSizeHint.flags = 0; // set defined values for basesize, minsize, maxsize, aspect and resizeinc, // even if they're not in flags // basesize is just like minsize, except for minsize is not used for aspect ratios // keep basesize only for aspect ratios, for size increments, keep the base // value in minsize - see ICCCM 4.1.2.3 if( xSizeHint.flags & PBaseSize ) { if( ! ( xSizeHint.flags & PMinSize )) // PBaseSize and PMinSize are equivalent { xSizeHint.flags |= PMinSize; xSizeHint.min_width = xSizeHint.base_width; xSizeHint.min_height = xSizeHint.base_height; } } else xSizeHint.base_width = xSizeHint.base_height = 0; if( ! ( xSizeHint.flags & PMinSize )) xSizeHint.min_width = xSizeHint.min_height = 0; if( ! ( xSizeHint.flags & PMaxSize )) xSizeHint.max_width = xSizeHint.max_height = INT_MAX; if( xSizeHint.flags & PResizeInc ) { xSizeHint.width_inc = kMax( xSizeHint.width_inc, 1 ); xSizeHint.height_inc = kMax( xSizeHint.height_inc, 1 ); } else { xSizeHint.width_inc = 1; xSizeHint.height_inc = 1; } if( xSizeHint.flags & PAspect ) { // no dividing by zero xSizeHint.min_aspect.y = kMax( xSizeHint.min_aspect.y, 1 ); xSizeHint.max_aspect.y = kMax( xSizeHint.max_aspect.y, 1 ); } else { xSizeHint.min_aspect.x = 1; xSizeHint.min_aspect.y = INT_MAX; xSizeHint.max_aspect.x = INT_MAX; xSizeHint.max_aspect.y = 1; } if( isManaged()) { // update to match restrictions QSize new_size = adjustedSize( size()); if( new_size != size()) resize( new_size ); } updateAllowedActions(); // affects isResizeable() } /*! Fetches the window's caption (WM_NAME property). It will be stored in the client's caption(). */ void Client::fetchName() { QString s; if ( info->name() ) { s = QString::fromUtf8( info->name() ); } else { XTextProperty tp; char **text; int count; if ( XGetTextProperty( qt_xdisplay(), win, &tp, XA_WM_NAME) != 0 && tp.value != NULL ) { if ( tp.encoding == XA_STRING ) s = QString::fromLocal8Bit( (const char*) tp.value ); else if ( XmbTextPropertyToTextList( qt_xdisplay(), &tp, &text, &count) == Success && text != NULL && count > 0 ) { s = QString::fromLocal8Bit( text[0] ); XFreeStringList( text ); } XFree( tp.value ); } } if ( s != caption() ) { setCaption( "" ); if (workspace()->hasCaption( s ) ){ int i = 2; QString s2; do { s2 = s + " <" + QString::number(i) + ">"; i++; } while (workspace()->hasCaption(s2) ); s = s2; } setCaption( s ); info->setVisibleName( s.utf8() ); if( isManaged()) captionChange( caption() ); } } /*! Sets the client window's mapping state. Possible values are WithdrawnState, IconicState, NormalState. */ void Client::setMappingState(int s) { if ( !win) return; if( mapping_state == s ) return; mapping_state = s; if( mapping_state == WithdrawnState ) { XDeleteProperty( qt_xdisplay(), win, 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(), win, qt_wm_state, qt_wm_state, 32, PropModeReplace, (unsigned char *)data, 2); } /*! General handler for XEvents concerning the client window */ bool Client::windowEvent( XEvent * e) { unsigned long dirty[ 2 ]; info->event( e, dirty, 2 ); // pass through the NET stuff if ( ( dirty[ WinInfo::PROTOCOLS ] & NET::WMName ) != 0 ) fetchName(); if ( ( dirty[ WinInfo::PROTOCOLS ] & NET::WMStrut ) != 0 ) workspace()->updateClientArea(); if ( ( dirty[ WinInfo::PROTOCOLS ] & NET::WMIcon) != 0 ) getIcons(); // Note there's a difference between userTime() and info->userTime() // info->userTime() is the value of the property, userTime() also includes // updates of the time done by KWin (ButtonPress on windowrapper etc.). if(( dirty[ WinInfo::PROTOCOLS2 ] & NET::WM2UserTime ) != 0 ) { workspace()->setWasUserInteraction(); updateUserTime( info->userTime()); } if(( dirty[ WinInfo::PROTOCOLS2 ] & NET::WM2StartupId ) != 0 ) startupIdChanged(); switch (e->type) { case UnmapNotify: return unmapNotify( e->xunmap ); case MapRequest: return mapRequest( e->xmaprequest ); case ConfigureRequest: return configureRequest( e->xconfigurerequest ); case PropertyNotify: return propertyNotify( e->xproperty ); case KeyPress: case ButtonPress: updateUserTime(); workspace()->setWasUserInteraction(); break; case KeyRelease: case ButtonRelease: // don't update user time on releases // e.g. if the user presses Alt+F2, the Alt release // would appear as user input to the currently active window break; case FocusIn: { if ( e->xfocus.mode == NotifyUngrab ) break; // we don't care if ( e->xfocus.detail == NotifyPointer ) break; // we don't care // check if this client is in should_get_focus list or if activation is allowed bool activate = workspace()->allowClientActivation( this, -1U, true ); workspace()->gotFocusIn( this ); // remove from should_get_focus list if( activate ) setActive( TRUE ); else { workspace()->restoreFocus(); demandAttention(); } break; } case FocusOut: if ( e->xfocus.mode == NotifyGrab ) break; // we don't care if ( isShade() ) break; // we neither if ( e->xfocus.detail != NotifyNonlinear && e->xfocus.detail != NotifyNonlinearVirtual ) // SELI check all this return TRUE; // hack for motif apps like netscape if ( QApplication::activePopupWidget() ) break; setActive( FALSE ); break; case ReparentNotify: break; case ClientMessage: return clientMessage( e->xclient ); case ColormapChangeMask: cmap = e->xcolormap.colormap; if ( isActive() ) workspace()->updateColormap(); default: if ( e->type == Shape::shapeEvent() ) updateShape(); break; } return TRUE; // we accept everything :-) } /*! Handles map requests of the client window */ bool Client::mapRequest( XMapRequestEvent& /* e */ ) { switch ( mappingState() ) { case WithdrawnState: assert( false ); // WMs are not supposed to manage clients in Withdrawn state, // manage(); // after initial mapping manage() is called from createClient() break; case IconicState: // also copied in clientMessage() if( isMinimized()) unminimize(); else if( isShade()) setShade( ShadeNone ); else // it's on another virtual desktop { if( workspace()->allowClientActivation( this )) workspace()->activateClient( this ); else demandAttention(); } break; case NormalState: Q_ASSERT( !"map request for a window in NormalState!" ); break; } return true; } /*! Handles unmap notify events of the client window */ bool Client::unmapNotify( XUnmapEvent& e ) { if ( e.event != windowWrapper()->winId() && !e.send_event ) return TRUE; switch ( mappingState() ) { case IconicState: // only react on sent events, all others are produced by us if ( e.send_event ) releaseWindow(); break; case NormalState: if ( !windowWrapper()->isVisibleTo( 0 ) && !e.send_event ) return TRUE; // this event was produced by us as well // maybe we will be destroyed soon. Check this first. XEvent ev; if ( XCheckTypedWindowEvent (qt_xdisplay(), windowWrapper()->winId(), DestroyNotify, &ev) ){ destroyClient(); // deletes this return TRUE; } if ( XCheckTypedWindowEvent (qt_xdisplay(), windowWrapper()->winId(), ReparentNotify, &ev) ) { if ( ev.xreparent.window == windowWrapper()->window() && ev.xreparent.parent != windowWrapper()->winId() ) /* SELI what's this? invalidateWindow()*/; } releaseWindow(); break; case WithdrawnState: // however that has been possible.... releaseWindow(); break; } return TRUE; } static bool blockAnimation = FALSE; /*! Handles client messages for the client window */ bool Client::clientMessage( XClientMessageEvent& e ) { // WM_STATE if ( e.message_type == atoms->kde_wm_change_state ) { if( e.data.l[ 1 ] ) blockAnimation = true; if( e.data.l[ 0 ] == IconicState ) minimize(); else if( e.data.l[ 0 ] == NormalState ) { // copied from mapRequest() if( isMinimized()) unminimize(); else if( isShade()) setShade( ShadeNone ); else // it's on another virtual desktop { if( workspace()->allowClientActivation( this )) workspace()->activateClient( this ); else demandAttention(); } } blockAnimation = false; } else if ( e.message_type == atoms->wm_change_state) { if ( e.data.l[0] == IconicState ) minimize(); return TRUE; } return FALSE; } /*! Handles configure requests of the client window */ bool Client::configureRequest( XConfigureRequestEvent& e ) { if ( isResize() ) return TRUE; // we have better things to do right now if ( isShade() ) // SELI SHADE setShade( ShadeNone ); // compress configure requests XEvent otherEvent; while (XCheckTypedWindowEvent (qt_xdisplay(), win, ConfigureRequest, &otherEvent) ) { if (otherEvent.xconfigurerequest.value_mask == e.value_mask) e = otherEvent.xconfigurerequest; else { XPutBackEvent(qt_xdisplay(), &otherEvent); break; } } bool stacking = e.value_mask & CWStackMode; int stack_mode = e.detail; if ( e.value_mask & CWBorderWidth ) { // first, get rid of a window border XWindowChanges wc; unsigned int value_mask = 0; wc.border_width = 0; value_mask = CWBorderWidth; XConfigureWindow( qt_xdisplay(), win, value_mask, & wc ); } if ( e.value_mask & (CWX | CWY ) ) { int ox = 0; int oy = 0; int gravity = NorthWestGravity; if ( xSizeHint.flags & PWinGravity) gravity = xSizeHint.win_gravity; if ( gravity == StaticGravity ) { // only with StaticGravity according to ICCCM 4.1.5 ox = windowWrapper()->x(); oy = windowWrapper()->y(); } int nx = x() + ox; int ny = y() + oy; if ( e.value_mask & CWX ) nx = e.x; if ( e.value_mask & CWY ) ny = e.y; // clever workaround for applications like xv that want to set // the location to the current location but miscalculate the // frame size due to kwin being a double-reparenting window // manager if ( ox == 0 && oy == 0 && nx == x() + windowWrapper()->x() && ny == y() + windowWrapper()->y() ) { nx = x(); ny = y(); } QPoint np( nx-ox, ny-oy); #if 0 if ( windowType() == NET::Normal && may_move ) { // crap for broken netscape QRect area = workspace()->clientArea(); if ( !area.contains( np ) && width() < area.width() && height() < area.height() ) { if ( np.x() < area.x() ) np.rx() = area.x(); if ( np.y() < area.y() ) np.ry() = area.y(); } } #endif if ( !isMaximized() ) move( np ); } if ( e.value_mask & (CWWidth | CWHeight ) ) { int nw = windowWrapper()->width(); int nh = windowWrapper()->height(); if ( e.value_mask & CWWidth ) nw = e.width; if ( e.value_mask & CWHeight ) nh = e.height; QSize ns = sizeForWindowSize( QSize( nw, nh ) ); //QRect area = workspace()->clientArea(); if ( isMaximizable() && isMaximized() ) { //&& ( ns.width() < area.width() || ns.height() < area.height() ) ) { if( ns != size()) { // don't restore if some app sets its own size again maximize( Client::MaximizeRestore ); resize( ns ); } } else if ( !isMaximized() ) { if ( ns == size() ) return TRUE; // broken xemacs stuff (ediff) resize( ns ); } } if ( stacking ){ switch (stack_mode){ case Above: case TopIf: if ( isTopMenu()) break; // always ignore, we handle stacking order if( workspace()->allowClientActivation( this )) // not really activation, workspace()->raiseClient( this ); // but it's the same, showing else // unwanted window on top workspace()->restackClientUnderActive( this ); // would be obtrusive break; case Below: case BottomIf: workspace()->lowerClient( this ); break; case Opposite: default: break; } } kdDebug() << "CONF:" << isVisible() << ":" << wwrap->isVisible() << endl; // TODO sending a synthetic configure notify always is fine, even in cases where // the ICCCM doesn't require this - it can be though of as 'the WM decided to move // the window later'. Perhaps those unnecessary ones could be saved though. sendSyntheticConfigureNotify(); // SELI TODO accept configure requests for isDesktop windows (because kdesktop // may get XRANDR resize event before kwin), but check it's still at the bottom? return TRUE; } /*! Handles property changes of the client window */ bool Client::propertyNotify( XPropertyEvent& e ) { switch ( e.atom ) { case XA_WM_NORMAL_HINTS: getWmNormalHints(); break; case XA_WM_NAME: fetchName(); break; case XA_WM_TRANSIENT_FOR: readTransient(); break; case XA_WM_HINTS: getWMHints(); getIcons(); // because KWin::icon() uses WMHints as fallback break; default: if ( e.atom == atoms->wm_protocols ) getWindowProtocols(); else if (e.atom == atoms->wm_client_leader ) getWmClientLeader(); else if( e.atom == qt_window_role ) window_role = getStringProperty( win, qt_window_role ); break; } return TRUE; } /*! Auxiliary function to inform the client about the current window configuration. */ void Client::sendSyntheticConfigureNotify() { if( !isVisible()) { kapp->sendPostedEvents( this, QEvent::Resize ); kapp->sendPostedEvents( this, QEvent::Move ); } XConfigureEvent c; c.type = ConfigureNotify; c.send_event = True; c.event = win; c.window = win; c.x = x() + windowWrapper()->x(); c.y = y() + windowWrapper()->y(); c.width = windowWrapper()->width(); c.height = windowWrapper()->height(); c.border_width = 0; c.above = None; c.override_redirect = 0; XSendEvent( qt_xdisplay(), c.event, TRUE, StructureNotifyMask, (XEvent*)&c ); } /*! Adjust the frame size \a frame according to he window's size hints. */ QSize Client::adjustedSize( const QSize& frame) const { // first, get the window size for the given frame size s QSize wsize( frame.width() - ( width() - wwrap->width() ), frame.height() - ( height() - wwrap->height() ) ); return sizeForWindowSize( wsize ); } /*! Calculate the appropriate frame size for the given window size \a wsize. \a wsize is adapted according to the window's size hints (minimum, maximum and incremental size changes). */ QSize Client::sizeForWindowSize( const QSize& wsize, bool ignore_height) const { int w = wsize.width(); int h = wsize.height(); if (w<1) w = 1; if (h<1) h = 1; // basesize, minsize, maxsize, paspect and resizeinc have all values defined, // even if they're not set in flags - see getWmNormalHints() QSize min_size( xSizeHint.min_width, xSizeHint.min_height ); QSize max_size( xSizeHint.max_width, xSizeHint.max_height ); // this is in order to match minimum size for the Client (i.e. decorations) // TODO it probably shouldn't be actually honored? (after Client is no longer QWidget) if( minimumWidth() > min_size.width()) min_size.setWidth( minimumWidth()); if( minimumHeight() > min_size.height()) min_size.setHeight( minimumHeight()); w = QMIN( max_size.width(), w ); h = QMIN( max_size.height(), h ); w = QMAX( min_size.width(), w ); h = QMAX( min_size.height(), h ); int width_inc = xSizeHint.width_inc; int height_inc = xSizeHint.height_inc; int basew_inc = xSizeHint.min_width; // see getWmNormalHints() int baseh_inc = xSizeHint.min_height; w = int(( w - basew_inc ) / width_inc ) * width_inc + basew_inc; h = int(( h - baseh_inc ) / height_inc ) * height_inc + baseh_inc; // The algorithm for aspect ratios in based on the one used in Metacity, which says: /* This is partially borrowed from GTK (LGPL), which in turn * partially borrowed from fvwm, * * Copyright 1993, Robert Nation * You may use this code for any purpose, as long as the original * copyright remains in the source code and all documentation * * which in turn borrows parts of the algorithm from uwm */ float min_wh = float( xSizeHint.min_aspect.x ) / xSizeHint.min_aspect.y; float max_wh = float( xSizeHint.max_aspect.x ) / xSizeHint.max_aspect.y; int aspect_w = w - xSizeHint.base_width; int aspect_h = h - xSizeHint.base_height; if( min_wh > float( aspect_w ) / aspect_h ) { int delta = int(( aspect_h - aspect_w / min_wh ) / height_inc ) * height_inc; if( h - delta >= min_size.height()) h -= delta; else { delta = int(( aspect_h * min_wh - aspect_w ) / width_inc ) * width_inc; if( w + delta <= max_size.width()) w -= delta; } } if( max_wh < float( aspect_w ) / aspect_h ) { int delta = int(( aspect_w - aspect_h * max_wh ) / width_inc ) * width_inc; if( w - delta >= min_size.width()) w -= delta; else { delta = int(( aspect_w / max_wh - aspect_h ) / height_inc ) * height_inc; if( h + delta <= max_size.height()) h += delta; } } int ww = wwrap->width(); int wh = 1; if ( !wwrap->isHidden() ) wh = wwrap->height(); if ( ignore_height && wsize.height() == 0 ) h = 0; return QSize( width() - ww + w, height()-wh+h ); } /*! Returns whether the window is resizable or has a fixed size. */ bool Client::isResizable() const { if ( !isMovable() || !may_resize || isSplash()) return FALSE; if ( ( xSizeHint.flags & PMaxSize) == 0 || (xSizeHint.flags & PMinSize ) == 0 ) return TRUE; return ( xSizeHint.min_width < xSizeHint.max_width ) || ( xSizeHint.min_height < xSizeHint.max_height ); } /* Returns whether the window is maximizable or not */ bool Client::isMaximizable() const { if ( isMaximized() ) return TRUE; return isResizable() && !isTool() && may_maximize; // SELI isTool() ? } /* Returns whether the window is minimizable or not */ bool Client::isMinimizable() const { 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()) return false; } return wantsTabFocus() && may_minimize; // SELI co NET::Utility? a proc wantsTabFocus() ? } /* Returns whether the window may be closed (have a close button) */ bool Client::isCloseable() const { return may_close && ( !isSpecialWindow() || isOverride()); // TODO is NET::Override special? } bool Client::isShadeable() const { return !isSpecialWindow(); } static QRect* visible_bound = 0; static GeometryTip* geometryTip = 0; void Client::drawbound( const QRect& geom ) { if ( visible_bound ) *visible_bound = geom; else visible_bound = new QRect( geom ); QPainter p ( workspace()->desktopWidget() ); p.setPen( QPen( Qt::white, 5 ) ); p.setRasterOp( Qt::XorROP ); p.drawRect( geom ); } void Client::clearbound() { if ( !visible_bound ) return; drawbound( *visible_bound ); delete visible_bound; visible_bound = 0; } void Client::positionGeometryTip() { assert( isMove() || isResize()); // Position and Size display if (options->showGeometryTip()) { if( !geometryTip ) { // save under is not necessary with opaque, and seem to make things slower bool save_under = ( isMove() && options->moveMode != Options::Opaque ) || ( isResize() && options->resizeMode != Options::Opaque ); geometryTip = new GeometryTip( &xSizeHint, save_under ); } QRect wgeom( moveResizeGeom ); // position of the frame, size of the window itself wgeom.setWidth( wgeom.width() - ( width() - wwrap->width())); wgeom.setHeight( wgeom.height() - ( height() - wwrap->height())); geometryTip->setGeometry( wgeom ); if( !geometryTip->isVisible()) { geometryTip->show(); geometryTip->raise(); } } } class EatAllPaintEvents : public QObject { protected: virtual bool eventFilter( QObject* o, QEvent* e ) { return e->type() == QEvent::Paint && o != geometryTip; } }; static EatAllPaintEvents* eater = 0; void Client::startMoveResize() { assert( !moveResizeMode ); if ( isMaximized() ) { // in case we were maximized, reset state max_mode = MaximizeRestore; maximizeChange(FALSE ); Events::raise( Events::UnMaximize ); info->setState( 0, NET::Max ); } moveResizeMode = true; workspace()->setClientIsMoving(this); moveResizeGeom = geometry(); grabMouse( cursor() ); grabKeyboard(); if ( ( isMove() && options->moveMode != Options::Opaque ) || ( isResize() && options->resizeMode != Options::Opaque ) ) { XGrabServer( qt_xdisplay() ); kapp->sendPostedEvents(); // we have server grab -> nothing should cause paint events // unfortunately, that's not completely true, Qt may generate // paint events on some widgets due to FocusIn(?) // eat them, otherwise XOR painting will be broken (#58054) // paint events for the geometrytip need to be allowed, though eater = new EatAllPaintEvents; kapp->installEventFilter( eater ); } Events::raise( isResize() ? Events::ResizeStart : Events::MoveStart ); } void Client::stopMoveResize( bool apply ) { clearbound(); if (geometryTip) { geometryTip->hide(); delete geometryTip; geometryTip = NULL; } if ( ( isMove() && options->moveMode != Options::Opaque ) || ( isResize() && options->resizeMode != Options::Opaque ) ) XUngrabServer( qt_xdisplay() ); releaseKeyboard(); releaseMouse(); workspace()->setClientIsMoving(0); if( move_faked_activity ) workspace()->unfakeActivity( this ); move_faked_activity = false; moveResizeMode = false; delete eater; eater = 0; if( apply ) setGeometry( moveResizeGeom ); update(); Events::raise( isResize() ? Events::ResizeEnd : Events::MoveEnd ); } void Client::updateShape() { if ( shape() ) XShapeCombineShape(qt_xdisplay(), winId(), ShapeBounding, windowWrapper()->x(), windowWrapper()->y(), window(), ShapeBounding, ShapeSet); else XShapeCombineMask( qt_xdisplay(), winId(), ShapeBounding, 0, 0, None, ShapeSet); } /*! Reimplemented to provide move/resize */ void Client::mousePressEvent( QMouseEvent * e) { if (buttonDown) return; updateUserTime(); Options::MouseCommand com = Options::MouseNothing; if (e->state() & AltButton) { // TODO this is duped in WindowWrapper, and here it doesn't seem to be ever // executed (no grab), moreover AltButton should be configurable (also Meta) if ( e->button() == LeftButton ) { com = options->commandAll1(); } else if (e->button() == MidButton) { com = options->commandAll2(); } else if (e->button() == RightButton) { com = options->commandAll3(); } } else { bool active = isActive(); if ( !wantsInput() ) // we cannot be active, use it anyway active = TRUE; if ((e->button() == LeftButton && options->commandActiveTitlebar1() != Options::MouseOperationsMenu) || (e->button() == MidButton && options->commandActiveTitlebar2() != Options::MouseOperationsMenu) || (e->button() == RightButton && options->commandActiveTitlebar3() != Options::MouseOperationsMenu) ) { mouseMoveEvent( e ); buttonDown = TRUE; moveOffset = e->pos(); invertedMoveOffset = rect().bottomRight() - e->pos(); QRect desktopArea = workspace()->clientArea( e->globalPos()); unrestrictedMoveResize = false; } if ( e->button() == LeftButton ) { com = active ? options->commandActiveTitlebar1() : options->commandInactiveTitlebar1(); } else if ( e->button() == MidButton ) { com = active ? options->commandActiveTitlebar2() : options->commandInactiveTitlebar2(); } else if ( e->button() == RightButton ) { com = active ? options->commandActiveTitlebar3() : options->commandInactiveTitlebar3(); } } performMouseCommand( com, e->globalPos()); } /*! Reimplemented to provide move/resize */ void Client::mouseReleaseEvent( QMouseEvent * e) { updateUserTime(); if ( (e->stateAfter() & MouseButtonMask) == 0 ) { buttonDown = FALSE; if ( moveResizeMode ) { stopMoveResize( true ); // mouse position is still relative to old Client position, adjust it QPoint mousepos = mapFromGlobal( e->globalPos()); mode = mousePosition( mousepos ); setMouseCursor( mode ); } } } /*! */ void Client::resizeEvent( QResizeEvent * e) { QWidget::resizeEvent( e ); } /*! Reimplemented to provide move/resize */ void Client::mouseMoveEvent( QMouseEvent * e) { if ( !buttonDown ) { MousePosition newmode = mousePosition( e->pos() ); if( newmode != mode ) setMouseCursor( newmode ); mode = newmode; return; } if(( mode == Center && !isMovable()) || ( mode != Center && ( isShade() || !isResizable()))) return; if ( !moveResizeMode ) { QPoint p( e->pos() - moveOffset ); if (p.manhattanLength() >= 6) startMoveResize(); else return; } // ShadeHover or ShadeActive, ShadeNormal was already avoided above if ( mode != Center && shade_mode != ShadeNone ) setShade( ShadeNone ); QPoint globalPos = e->globalPos(); // pos() + geometry().topLeft(); QRect desktopArea = workspace()->clientArea( globalPos ); QPoint p = globalPos + invertedMoveOffset; QPoint pp = globalPos - moveOffset; if( !unrestrictedMoveResize ) { int left_overlap = width() - windowWrapper()->geometry().left() - 100; int right_overlap = width() - windowWrapper()->geometry().right() - 100; int bottom_overlap = - windowWrapper()->geometry().top(); p.setX( QMIN( desktopArea.right() + right_overlap, QMAX( desktopArea.left() - left_overlap, p.x()))); p.setY( QMIN( desktopArea.bottom() + bottom_overlap, QMAX( desktopArea.top(), p.y()))); pp.setX( QMIN( desktopArea.right() + right_overlap, QMAX( desktopArea.left() - left_overlap, pp.x()))); pp.setY( QMIN( desktopArea.bottom() + bottom_overlap, QMAX( desktopArea.top(), pp.y()))); } QSize mpsize( geometry().right() - pp.x() + 1, geometry().bottom() - pp.y() + 1 ); mpsize = adjustedSize( mpsize ); QPoint mp( geometry().right() - mpsize.width() + 1, geometry().bottom() - mpsize.height() + 1 ); moveResizeGeom = geometry(); switch ( mode ) { case TopLeft: moveResizeGeom = QRect( mp, geometry().bottomRight() ) ; break; case BottomRight: moveResizeGeom = QRect( geometry().topLeft(), p ) ; break; case BottomLeft: moveResizeGeom = QRect( QPoint(mp.x(), geometry().y() ), QPoint( geometry().right(), p.y()) ) ; break; case TopRight: moveResizeGeom = QRect( QPoint(geometry().x(), mp.y() ), QPoint( p.x(), geometry().bottom()) ) ; break; case Top: moveResizeGeom = QRect( QPoint( geometry().left(), mp.y() ), geometry().bottomRight() ) ; break; case Bottom: moveResizeGeom = QRect( geometry().topLeft(), QPoint( geometry().right(), p.y() ) ) ; break; case Left: moveResizeGeom = QRect( QPoint( mp.x(), geometry().top() ), geometry().bottomRight() ) ; break; case Right: moveResizeGeom = QRect( geometry().topLeft(), QPoint( p.x(), geometry().bottom() ) ) ; break; case Center: moveResizeGeom.moveTopLeft( pp ); break; default: //fprintf(stderr, "KWin::mouseMoveEvent with mode = %d\n", mode); break; } const int marge = 5; // TODO move whole group when moving its leader or when the leader is not mapped? if ( isResize() && moveResizeGeom.size() != size() ) { if (moveResizeGeom.bottom() < desktopArea.top()+marge) moveResizeGeom.setBottom(desktopArea.top()+marge); if (moveResizeGeom.top() > desktopArea.bottom()-marge) moveResizeGeom.setTop(desktopArea.bottom()-marge); if (moveResizeGeom.right() < desktopArea.left()+marge) moveResizeGeom.setRight(desktopArea.left()+marge); if (moveResizeGeom.left() > desktopArea.right()-marge) moveResizeGeom.setLeft(desktopArea.right()-marge); moveResizeGeom.setSize( adjustedSize( moveResizeGeom.size() ) ); if (options->resizeMode == Options::Opaque ) { setGeometry( moveResizeGeom ); positionGeometryTip(); } else if ( options->resizeMode == Options::Transparent ) { clearbound(); // it's necessary to move the geometry tip when there's no outline positionGeometryTip(); // shown, otherwise it would cause repaint problems in case drawbound( moveResizeGeom ); // they overlap; the paint event will come after this, } // so the geometry tip will be painted above the outline } else if ( isMove() && moveResizeGeom.topLeft() != geometry().topLeft() ) { moveResizeGeom.moveTopLeft( workspace()->adjustClientPosition( this, moveResizeGeom.topLeft() ) ); if (moveResizeGeom.bottom() < desktopArea.top()+marge) moveResizeGeom.moveBottomLeft( QPoint( moveResizeGeom.left(), desktopArea.top()+marge)); if (moveResizeGeom.top() > desktopArea.bottom()-marge) moveResizeGeom.moveTopLeft( QPoint( moveResizeGeom.left(), desktopArea.bottom()-marge)); if (moveResizeGeom.right() < desktopArea.left()+marge) moveResizeGeom.moveTopRight( QPoint( desktopArea.left()+marge, moveResizeGeom.top())); if (moveResizeGeom.left() > desktopArea.right()-marge) moveResizeGeom.moveTopLeft( QPoint( desktopArea.right()-marge, moveResizeGeom.top())); switch ( options->moveMode ) { case Options::Opaque: move( moveResizeGeom.topLeft() ); positionGeometryTip(); break; case Options::Transparent: clearbound(); positionGeometryTip(); drawbound( moveResizeGeom ); break; } } if ( isMove() ) workspace()->clientMoved(globalPos, qt_x_time); // QApplication::syncX(); // process our own configure events synchronously. } // these two aren't called at all ... ?! /*! Reimplemented to provide move/resize */ void Client::enterEvent( QEvent * ) { } /*! Reimplemented to provide move/resize */ void Client::leaveEvent( QEvent * ) { } /*! Reimplemented to inform the client about the new window position. */ void Client::setGeometry( int x, int y, int w, int h ) { QWidget::setGeometry(x, y, w, h); // SELI TODO won't this be too expensive? updateWorkareaDiffs(); if ( !isResize() ) sendSyntheticConfigureNotify(); // TODO make this ICCCM compliant } void Client::resize( int w, int h ) { QWidget::resize( w, h ); updateWorkareaDiffs(); // TODO to be done with synt. notify cleanup // resize() is called from manage(), can't send synt. notify here // if ( !isResize() && isVisible() ) // sendSyntheticConfigureNotify(); } /*! Reimplemented to inform the client about the new window position. */ void Client::move( int x, int y ) { QWidget::move( x, y ); if ( !isResize() ) sendSyntheticConfigureNotify(); // TODO make this ICCCM compliant } 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()) setMappingState( NormalState ); rawShow(); // is either visible or shaded } } } /*! Minimizes this client plus its transients */ void Client::minimize() { if ( !isMinimizable() || isMinimized()) return; minimized = true; Events::raise( Events::Minimize ); // SELI mainClients().isEmpty() ??? - and in unminimize() too if ( mainClients().isEmpty() && isOnCurrentDesktop()) animateMinimizeOrUnminimize( true ); // was visible or shaded setMappingState( IconicState ); info->setState( NET::Hidden, NET::Hidden ); rawHide(); updateAllowedActions(); workspace()->updateMinimizedOfTransients( this ); } void Client::unminimize() { if( !isMinimized()) return; Events::raise( Events::UnMinimize ); minimized = false; info->setState( 0, NET::Hidden ); if( isOnCurrentDesktop()) { if( mainClients().isEmpty()) animateMinimizeOrUnminimize( FALSE ); if( isShown()) setMappingState( NormalState ); rawShow(); // is either visible or shaded } updateAllowedActions(); workspace()->updateMinimizedOfTransients( this ); } 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(); } } /*! Reimplemented to map the managed window in the window wrapper. Proper mapping state should be set before showing the client. */ void Client::rawShow() { QWidget::show(); if( !isShade()) windowWrapper()->show(); } /*! 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() { QWidget::hide(); workspace()->clientHidden( this ); // This is a bad hack. In order for layouts to work, windowwraper mustn't be isHidden(), // otherwise it won't be resized in the layout. The show() belong won't really do // anything (because the parent is still hidden), it will only remove the hidden state. // This is needed e.g. for TopMenu's, as they may have configure requests even when hidden. // Perhaps KWin shouldn't use layouts? windowWrapper()->hide(); windowWrapper()->QWidget::show(); } /*! Late initialialize the client after the window has been managed. Ensure to call the superclasses init() implementation when subclassing. */ void Client::init() { } /*!\fn captionChange( const QString& name ) Indicates that the caption (the window title) changed to \a name. Subclasses shall then repaint the title string in a clever, fast mannor. The default implementation calls repaint( FALSE ); */ void Client::captionChange( const QString& ) { repaint( FALSE ); } /*!\fn activeChange( bool act ) Indicates that the activation state changed to \a act. Subclasses may want to indicate the new state graphically in a clever, fast mannor. The default implementation calls repaint( FALSE ); */ void Client::activeChange( bool ) { repaint( FALSE ); } /*! Indicates that the application's icon changed to \a act. Subclasses may want to indicate the new state graphically in a clever, fast mannor. The default implementation calls repaint( FALSE ); */ void Client::iconChange() { repaint( FALSE ); } /*!\fn maximizeChange( bool max ) Indicates that the window was maximized or demaximized. \a max is set respectively. Subclasses may want to indicate the new state graphically, for example with a different icon. */ void Client::maximizeChange( bool ) { } /*!\fn onAllDesktopsChange( bool on_all_desktops ) Indicates that the window appears or not on all desktops. \a on_all_desktops is set respectively. Subclasses may want to indicate the new state graphically, for example with a different icon. */ void Client::onAllDesktopsChange( bool ) { } /*!\fn shadeChange( bool shaded ) Indicates that the window was shaded or unshaded. \a shaded is set respectively. Subclasses may want to indicate the new state graphically, for example with a different icon. */ void Client::shadeChange( bool ) { } /*! Paints a client window. The default implementation does nothing. To be implemented by subclasses. */ void Client::paintEvent( QPaintEvent * ) { } /*! Releases the window. The client has done its job and the window is still existing. */ void Client::releaseWindow( bool on_shutdown ) { if (moveResizeMode) stopMoveResize( false ); setModal( false ); // otherwise its mainwindow wouldn't get focus if( !on_shutdown ) workspace()->clientHidden( this ); move(gravitate(TRUE)); removeFromMainClients(); for( ClientList::ConstIterator it = transients_list.begin(); it != transients_list.end(); ) if( (*it)->transientFor() == this ) { ClientList::ConstIterator it2 = it++; removeTransient( *it2 ); } else ++it; group()->removeMember( this ); in_group = NULL; setMappingState( WithdrawnState ); windowWrapper()->releaseWindow(); XDeleteProperty( qt_xdisplay(), win, atoms->kde_net_wm_user_creation_time); if( on_shutdown ) { // map the window, so it can be found after another WM is started XMapWindow( qt_xdisplay(), win ); // TODO preserve minimized, shaded etc. state? } 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 } 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) stopMoveResize( false ); setModal( false ); workspace()->clientHidden( this ); removeFromMainClients(); for( ClientList::ConstIterator it = transients_list.begin(); it != transients_list.end(); ) if( (*it)->transientFor() == this ) { ClientList::ConstIterator it2 = it++; removeTransient( *it2 ); } else ++it; group()->removeMember( this ); in_group = NULL; windowWrapper()->invalidateWindow(); workspace()->removeClient( this, Allowed ); deleteClient( this, Allowed ); } /*! Closes the window by either sending a delete_window message or using XKill. */ void Client::closeWindow() { if( !isCloseable()) return; if ( Pdeletewindow ){ Events::raise( Events::Close ); sendClientMessage( win, 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 Events::Kill or not.. until then, use // Events::Close Events::raise( Events::Close ); if( isDialog()) Events::raise( Events::TransDelete ); if( isNormalWindow()) Events::raise( Events::Delete ); killProcess( false ); // always kill this client at the server XKillClient(qt_xdisplay(), win ); // needs to be delayed, because this may be called from the client // popup menu, and there may be possibly code still touching // this instance after returning from killWindow() QTimer::singleShot( 0, this, SLOT( 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( win, 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; } /*! Maximizes the client according to mode \a m. MaximizeRestore always restores. MaximizeFull always fully maximizes, unless the client is already fully maximized. MaximizeVertical and MaximizeHorizontal flip their matching maximalization. This is the slot to connect to from your client subclass. */ void Client::maximize( MaximizeMode m ) { switch( m ) { case MaximizeRestore: // reset back setMaximize( false, false ); break; case MaximizeVertical: case MaximizeHorizontal: changeMaximize( m & MaximizeVertical, m & MaximizeHorizontal, false ); break; case MaximizeFull: if( max_mode == MaximizeFull ) setMaximize( false, false ); // reset else setMaximize( true, true ); } } /*! Sets the maximization according to \a vertically and \a horizontally */ void Client::setMaximize( bool vertically, bool horizontally ) { // changeMaximize() flips the state, so change from set->flip changeMaximize( max_mode & MaximizeVertical ? !vertically : vertically, max_mode & MaximizeHorizontal ? !horizontally : horizontally, false ); } void Client::changeMaximize( bool vertical, bool horizontal, bool adjust ) { if( !isMaximizable()) return; if( isShade()) // SELI SHADE setShade( ShadeNone ); MaximizeMode old_mode = max_mode; // 'adjust == true' means to update the size only, e.g. after changing workspace size if( !adjust ) { if( vertical ) max_mode = MaximizeMode( max_mode ^ MaximizeVertical ); if( horizontal ) max_mode = MaximizeMode( max_mode ^ MaximizeHorizontal ); } // maximing one way and unmaximizing the other way shouldn't happen Q_ASSERT( !( vertical && horizontal ) || (( max_mode & MaximizeVertical != 0 ) == ( max_mode & MaximizeHorizontal != 0 ))); // save sizes for restoring, if maximalizing bool maximalizing = false; if( vertical && !(old_mode & MaximizeVertical )) { geom_restore.setTop( y()); geom_restore.setHeight( height()); maximalizing = true; } if( horizontal && !( old_mode & MaximizeHorizontal )) { geom_restore.setLeft( x()); geom_restore.setWidth( width()); maximalizing = true; } if( !adjust ) { if( maximalizing ) Events::raise( Events::Maximize ); else Events::raise( Events::UnMaximize ); } QRect clientArea = workspace()->clientArea(geometry().center()); switch (max_mode) { case MaximizeVertical: if( old_mode & MaximizeHorizontal ) // actually restoring from MaximizeFull { if( geom_restore.width() == 0 ) { // needs placement resize( adjustedSize(QSize(width(), clientArea.height()))); workspace()->placeSmart( this ); } else setGeometry( QRect(QPoint( geom_restore.x(), clientArea.top()), adjustedSize(QSize( geom_restore.width(), clientArea.height())))); } else setGeometry( QRect(QPoint(x(), clientArea.top()), adjustedSize(QSize(width(), clientArea.height())))); info->setState( NET::MaxVert, NET::Max ); break; case MaximizeHorizontal: if( old_mode & MaximizeVertical ) // actually restoring from MaximizeFull { if( geom_restore.height() == 0 ) { // needs placement resize( adjustedSize(QSize(clientArea.width(), height()))); workspace()->placeSmart( this ); } else setGeometry( QRect( QPoint(clientArea.left(), geom_restore.y()), adjustedSize(QSize(clientArea.width(), geom_restore.height())))); } else setGeometry( QRect( QPoint(clientArea.left(), y()), adjustedSize(QSize(clientArea.width(), height())))); info->setState( NET::MaxHoriz, NET::Max ); break; case MaximizeRestore: { QRect restore = geom_restore; if( !geom_restore.isValid()) { QSize s = QSize( clientArea.width()*2/3, clientArea.height()*2/3 ); if( geom_restore.width() > 0 ) s.setWidth( geom_restore.width()); if( geom_restore.height() > 0 ) s.setHeight( geom_restore.height()); resize( adjustedSize( s )); workspace()->placeSmart( this ); restore = geometry(); if( geom_restore.width() > 0 ) restore.moveLeft( geom_restore.x()); if( geom_restore.height() > 0 ) restore.moveTop( geom_restore.y()); } // when only partially maximized, geom_restore may not have the other dimension remembered if(( old_mode & MaximizeHorizontal ) == 0 && restore.width() <= 0 ) { restore.setLeft( x()); restore.setWidth( width()); } if(( old_mode & MaximizeVertical ) == 0 && restore.height() <= 0 ) { restore.setTop( y()); restore.setHeight( height()); } setGeometry( restore ); info->setState( 0, NET::Max ); break; } case MaximizeFull: { QSize adjSize = adjustedSize(clientArea.size()); QRect r = QRect(clientArea.topLeft(), adjSize); setGeometry( r ); info->setState( NET::Max, NET::Max ); } break; default: break; } updateAllowedActions(); // TODO this shouldn't have any flag, the decoration should call maximizeMode() maximizeChange( max_mode != MaximizeRestore ); } void Client::toggleOnAllDesktops() { setOnAllDesktops( !isOnAllDesktops()); } void Client::toggleShade() { // if the mode is ShadeHover or ShadeActive, cancel shade too setShade( shade_mode == ShadeNone ? ShadeNormal : ShadeNone ); } 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; // TODO fullscreen 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 } /*! Catch events of the WindowWrapper */ bool Client::eventFilter( QObject *o, QEvent * e) { if ( o != wwrap ) return FALSE; switch ( e->type() ) { case QEvent::Show: windowWrapperShowEvent( (QShowEvent*)e ); break; case QEvent::Hide: windowWrapperHideEvent( (QHideEvent*)e ); break; default: break; } return FALSE; } const QPoint Client::gravitate( bool invert ) const { int gravity, dx, dy; dx = dy = 0; gravity = NorthWestGravity; if ( xSizeHint.flags & PWinGravity) gravity = xSizeHint.win_gravity; switch (gravity) { case NorthWestGravity: dx = 0; dy = 0; break; case NorthGravity: dx = windowWrapper()->x(); dy = 0; break; case NorthEastGravity: dx = width() - windowWrapper()->width(); dy = 0; break; case WestGravity: dx = 0; dy = windowWrapper()->y(); break; case CenterGravity: case StaticGravity: dx = windowWrapper()->x(); dy = windowWrapper()->y(); break; case EastGravity: dx = width() - windowWrapper()->width(); dy = windowWrapper()->y(); break; case SouthWestGravity: dx = 0; dy = height() - windowWrapper()->height(); break; case SouthGravity: dx = windowWrapper()->x(); dy = height() - windowWrapper()->height(); break; case SouthEastGravity: dx = width() - windowWrapper()->width(); dy = height() - windowWrapper()->height(); break; } if (invert) return QPoint( x() + dx, y() + dy ); else return QPoint( x() - dx, y() - dy ); } /*! Reimplement to handle crossing events (qt should provide xroot, yroot) Crossing events are necessary for the focus-follows-mouse focus policies, to do proper activation and deactivation. */ bool Client::x11Event( XEvent * e) { if( e->type == VisibilityNotify ) { windowWrapper()->updateVisibility( e->xvisibility.state == VisibilityUnobscured ); return false; } if ( e->type == EnterNotify && ( e->xcrossing.mode == NotifyNormal || ( !options->focusPolicyIsReasonable() && e->xcrossing.mode == NotifyUngrab ) ) ) { if (options->shadeHover && isShade()) { delete shadeHoverTimer; shadeHoverTimer = new QTimer( this ); connect( shadeHoverTimer, SIGNAL( timeout() ), this, SLOT( shadeHover() )); shadeHoverTimer->start( options->shadeHoverInterval, TRUE ); } if ( options->focusPolicy == Options::ClickToFocus ) return false; if ( options->autoRaise && !isDesktop() && !isDock() && !isTopMenu() && workspace()->focusChangeEnabled() && workspace()->topClientOnDesktop( workspace()->currentDesktop()) != this ) { delete autoRaiseTimer; autoRaiseTimer = new QTimer( this ); connect( autoRaiseTimer, SIGNAL( timeout() ), this, SLOT( autoRaise() ) ); autoRaiseTimer->start( options->autoRaiseInterval, TRUE ); } if ( options->focusPolicy != Options::FocusStrictlyUnderMouse && ( isDesktop() || isDock() || isTopMenu() ) ) return false; workspace()->requestFocus( this ); return false; } if ( e->type == LeaveNotify && e->xcrossing.mode == NotifyNormal ) { if ( !buttonDown ) { mode = Nowhere; setCursor( arrowCursor ); } bool lostMouse = !rect().contains( QPoint( e->xcrossing.x, e->xcrossing.y ) ); // 'lostMouse' wouldn't work with e.g. B2 or Keramik, which have non-rectangular decorations // (i.e. the LeaveNotify event comes before leaving the rect and no LeaveNotify event // comes after leaving the rect) - so lets check if the pointer is really outside the window // TODO this still sucks if a window appears above this one - it should lose the mouse // if this window is another client, but not if it's a popup ... maybe after KDE3.1 :( // (repeat after me 'AARGHL!') if ( !lostMouse && e->xcrossing.detail != NotifyInferior ) { int d1, d2, d3, d4; unsigned int d5; Window w, child; if( XQueryPointer( qt_xdisplay(), winId(), &w, &child, &d1, &d2, &d3, &d4, &d5 ) == False || child == None ) lostMouse = true; // really lost the mouse } if ( lostMouse ) { delete autoRaiseTimer; autoRaiseTimer = 0; delete shadeHoverTimer; shadeHoverTimer = 0; if ( shade_mode == ShadeHover && !moveResizeMode && !buttonDown ) setShade( ShadeNormal ); } if ( options->focusPolicy == Options::FocusStrictlyUnderMouse ) if ( isActive() && lostMouse ) workspace()->requestFocus( 0 ) ; return false; } return FALSE; } /*! Returns a logical mouse position for the cursor position \a p. Possible positions are: Nowhere, TopLeft , BottomRight, BottomLeft, TopRight, Top, Bottom, Left, Right, Center */ Client::MousePosition Client::mousePosition( const QPoint& p ) const { const int range = 16; const int border = 4; MousePosition m = Nowhere; if ( ( p.x() > border && p.x() < width() - border ) && ( p.y() > border && p.y() < height() - border ) ) return Center; if ( p.y() <= range && p.x() <= range) m = TopLeft; else if ( p.y() >= height()-range && p.x() >= width()-range) m = BottomRight; else if ( p.y() >= height()-range && p.x() <= range) m = BottomLeft; else if ( p.y() <= range && p.x() >= width()-range) m = TopRight; else if ( p.y() <= border ) m = Top; else if ( p.y() >= height()-border ) m = Bottom; else if ( p.x() <= border ) m = Left; else if ( p.x() >= width()-border ) m = Right; else m = Center; return m; } /*! Sets an appropriate cursor shape for the logical mouse position \a m \sa QWidget::setCursor() */ void Client::setMouseCursor( MousePosition m ) { if ( !isResizable() || isShade() ) { setCursor( arrowCursor ); return; } switch ( m ) { case TopLeft: case BottomRight: setCursor( sizeFDiagCursor ); break; case BottomLeft: case TopRight: setCursor( sizeBDiagCursor ); break; case Top: case Bottom: setCursor( sizeVerCursor ); break; case Left: case Right: setCursor( sizeHorCursor ); break; default: setCursor( arrowCursor ); break; } } void Client::setShade( ShadeMode mode ) { if( !isShadeable()) return; if( shade_mode == mode ) return; bool was_shade = isShade(); shade_mode = mode; if( was_shade == isShade()) return; // no real change in shaded state if( shade_mode == ShadeNormal ) { if ( isShown() && isOnCurrentDesktop()) Events::raise( Events::ShadeUp ); } else if( shade_mode == ShadeNone ) { if( isShown() && isOnCurrentDesktop()) Events::raise( Events::ShadeDown ); } int as = options->animateShade? 10 : 1; if ( isShade()) { // shade_mode == ShadeNormal int h = height(); QSize s( sizeForWindowSize( QSize( windowWrapper()->width(), 0), TRUE ) ); windowWrapper()->hide(); repaint( FALSE ); bool wasStaticContents = testWFlags( WStaticContents ); setWFlags( WStaticContents ); int step = QMAX( 4, QABS( h - s.height() ) / as )+1; do { h -= step; resize ( s.width(), h ); QApplication::syncX(); } while ( h > s.height() + step ); if ( !wasStaticContents ) clearWFlags( WStaticContents ); resize (s ); if( isActive()) workspace()->focusToNull(); } else { int h = height(); QSize s( sizeForWindowSize( windowWrapper()->size(), TRUE ) ); bool wasStaticContents = testWFlags( WStaticContents ); setWFlags( WStaticContents ); int step = QMAX( 4, QABS( h - s.height() ) / as )+1; do { h += step; resize ( s.width(), h ); // assume a border // we do not have time to wait for X to send us paint events repaint( 0, h - step-5, width(), step+5, TRUE); QApplication::syncX(); } while ( h < s.height() - step ); if ( !wasStaticContents ) clearWFlags( WStaticContents ); resize ( s ); if( shade_mode == ShadeHover || shade_mode == ShadeActivated ) setActive( TRUE ); windowWrapper()->show(); activateLayout(); if ( isActive() ) workspace()->requestFocus( this ); } info->setState( isShade() ? NET::Shaded : 0, NET::Shaded ); info->setState( isShown() ? 0 : NET::Hidden, NET::Hidden ); setMappingState( isShown() && isOnCurrentDesktop() ? NormalState : IconicState ); updateAllowedActions(); workspace()->updateMinimizedOfTransients( this ); shadeChange( isShade()); } /*! 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; windowWrapper()->setActive( act ); workspace()->setActiveClient( act ? this : NULL, Allowed ); if ( active ) Events::raise( Events::Activate ); if ( !active && autoRaiseTimer ) { delete autoRaiseTimer; autoRaiseTimer = 0; } if( shade_mode == ShadeActivated ) setShade( ShadeNormal ); StackingUpdatesBlocker blocker( workspace()); workspace()->updateClientLayer( this ); // active windows may get different layer activeChange( active ); } 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 ) { // support modal only for dialogs? if( windowType() != NET::Dialog ) return; 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::setDesktop( int 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()) Events::raise( isOnAllDesktops() ? Events::OnAllDesktops : Events::NotOnAllDesktops ); workspace()->updateOnAllDesktopsOfTransients( this ); onAllDesktopsChange( isOnAllDesktops()); } 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()); } void Client::getWMHints() { XWMHints *hints = XGetWMHints(qt_xdisplay(), win ); input = true; window_group = None; if ( hints ) { if ( hints->flags & InputHint ) input = hints->input; if( hints->flags & WindowGroupHint ) window_group = hints->window_group; XFree((char*)hints); } checkGroup(); 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( win, 32, 32, TRUE, KWin::ClassHint | KWin::XApp ); miniicon_pix = KWin::icon( win, 16, 16, TRUE, KWin::ClassHint | KWin::XApp ); } if( isManaged()) iconChange(); } void Client::getWindowProtocols(){ Atom *p; int i,n; Pdeletewindow = 0; Ptakefocus = 0; Pcontexthelp = 0; Pping = 0; if (XGetWMProtocols(qt_xdisplay(), win, &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); } } /*! 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 if ( input ) { // Qt may delay the mapping which may cause XSetInputFocus to fail, force show window QApplication::sendPostedEvents( windowWrapper(), QEvent::ShowWindowRequest ); XSetInputFocus( qt_xdisplay(), win, RevertToPointerRoot, qt_x_time ); } if ( Ptakefocus ) sendClientMessage(win, atoms->wm_protocols, atoms->wm_take_focus); } /*!\reimp */ void Client::setMask( const QRegion & reg) { mask = reg; QWidget::setMask( reg ); } bool Client::isOnCurrentDesktop() const { return isOnDesktop( workspace()->currentDesktop()); } /* Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3 WM_TRANSIENT_FOR is basically means "this is my mainwindow". For NET::Unknown windows, transient windows are considered to be NET::Dialog windows, for compatibility with non-NETWM clients. KWin may adjust the value of this property in some cases (window pointing to itself or creating a loop, keeping NET::Splash windows above other windows from the same app, etc.). Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after possibly being adjusted by KWin. Client::transient_for points to the Client this Client is transient for, or is NULL. If Client::transient_for_id is poiting to the root window, the window is considered to be transient for the whole window group, as suggested in NETWM 7.3. In the case of group transient window, Client::transient_for is NULL, and Client::groupTransient() returns true. Such window is treated as if it were transient for every window in its window group, with the exception of the windows that are (even indirectly) transient for it (quite nasty, loops must be avoided). Client::original_transient_for_id is the value of the property, which may be different if Client::transient_for_id if e.g. forcing NET::Splash to be kept on top of its window group, or when the mainwindow is not mapped yet, in which case the window is temporarily made group transient, and when the mainwindow is mapped, transiency is re-evaluated. This can get a bit complicated with with e.g. two Konqueror windows created by the same process. They should ideally appear like two independent applications to the user. This should be accomplished by all windows in the same process having the same window group (needs to be changed in Qt at the moment), and using non-group transients poiting to their relevant mainwindow for toolwindows etc. KWin should handle both group and non-group transient dialogs well. In other words: - non-transient windows : isTransient() == false - normal transients : transientFor() != NULL - group transients : groupTransient() == true - list of mainwindows : mainClients() (call once and loop over the result) - list of transients : transients() - every window in the group : group()->members() */ void Client::readTransient() { Window new_transient_for_id; if( XGetTransientForHint( qt_xdisplay(), win, &new_transient_for_id )) { original_transient_for_id = new_transient_for_id; new_transient_for_id = verifyTransientFor( new_transient_for_id, true ); } else { original_transient_for_id = None; new_transient_for_id = verifyTransientFor( None, false ); } setTransient( new_transient_for_id ); } void Client::setTransient( Window new_transient_for_id ) { if( new_transient_for_id != transient_for_id ) { removeFromMainClients(); transient_for = NULL; transient_for_id = new_transient_for_id; if( groupTransient()) { for( ClientList::ConstIterator it = group()->members().begin(); it != group()->members().end(); ++it ) if( !(*it)->groupTransient()) (*it)->addTransient( this ); } else if( transient_for_id != None ) { transient_for = workspace()->findClient( transient_for_id ); assert( transient_for != NULL ); // verifyTransient() had to check this transient_for->addTransient( this ); } checkGroup(); checkGroupTransients(); workspace()->updateClientLayer( this ); } } void Client::removeFromMainClients() { if( transientFor() != NULL ) transientFor()->removeTransient( this ); if( groupTransient()) { for( ClientList::ConstIterator it = group()->members().begin(); it != group()->members().end(); ++it ) (*it)->removeTransient( this ); } } // Make sure that no group transient is considered transient // for a window trat is (directly or indirectly) for it. // Group transients not being transient for each other is already // handled before calling addTransient(). void Client::checkGroupTransients() { for( ClientList::ConstIterator it1 = group()->members().begin(); it1 != group()->members().end(); ++it1 ) { if( !(*it1)->groupTransient()) continue; for( ClientList::Iterator it2 = (*it1)->transients_list.begin(); it2 != (*it1)->transients_list.end(); ) { Client* cl = (*it2)->transientFor(); if( cl != NULL ) cl = cl->transientFor(); if( cl == *it1 ) { it2 = (*it1)->transients_list.remove( it2 ); continue; } ++it2; } } } /*! Check that the window is not transient for itself, and similar nonsense. */ Window Client::verifyTransientFor( Window new_transient_for, bool defined ) { Window new_property_value = new_transient_for; // make sure splashscreens are shown above all their app's windows, even though // they're in Normal layer if( isSplash() && new_transient_for == None ) new_transient_for = workspace()->rootWin(); if( new_transient_for == None ) if( defined ) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window new_property_value = new_transient_for = workspace()->rootWin(); else return None; if( new_transient_for == window()) // pointing to self { // also fix the property itself kdWarning( 1216 ) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." << endl; new_property_value = new_transient_for = workspace()->rootWin(); } // The transient_for window may be embedded in another application, // so kwin cannot see it. Try to find the managed client for the // window and fix the transient_for property if possible. WId before_search = new_transient_for; while( new_transient_for != None && new_transient_for != workspace()->rootWin() && !workspace()->findClient( new_transient_for )) { Window root_return, parent_return; Window* wins = NULL; unsigned int nwins; int r = XQueryTree(qt_xdisplay(), new_transient_for, &root_return, &parent_return, &wins, &nwins); if ( wins ) XFree((void *) wins); if ( r == 0) break; new_transient_for = parent_return; } if( !workspace()->findClient( new_transient_for )) new_transient_for = before_search; // nice try else new_property_value = new_transient_for; // also fix the property // loop detection // group transients cannot cause loops, because they're considered transient only for non-transient // windows in the group int count = 20; Window loop_pos = new_transient_for; while( loop_pos != None && loop_pos != workspace()->rootWin()) { Client* pos = workspace()->findClient( loop_pos ); if( pos == NULL ) break; loop_pos = pos->transient_for_id; if( --count == 0 ) { kdWarning( 1216 ) << "Client " << this << " caused WM_TRANSIENT_FOR loop." << endl; new_transient_for = workspace()->rootWin(); } } if( new_transient_for != workspace()->rootWin() && workspace()->findClient( new_transient_for ) == NULL ) { // it's transient for a specific window, but that window is not mapped new_transient_for = workspace()->rootWin(); } if( new_property_value != original_transient_for_id ) XSetTransientForHint( qt_xdisplay(), win, new_property_value ); return new_transient_for; } void Client::addTransient( Client* cl ) { assert( !transients_list.contains( cl )); assert( cl != this ); transients_list.append( cl ); } void Client::removeTransient( Client* cl ) { transients_list.remove( cl ); if( cl->transientFor() == this ) { cl->transient_for_id = None; cl->transient_for = NULL; // SELI cl->setTransient( workspace()->rootWin()); } } // A new window has been mapped. Check if it's not a mainwindow for this already existing window. void Client::checkTransient( Window w ) { if( original_transient_for_id != w ) return; setTransient( w ); } // returns true if cl is the transient_for window for this client, // or recursively the transient_for window bool Client::hasTransient( const Client* cl, bool indirect ) const { if( cl->transientFor() != NULL ) { if( cl->transientFor() == this ) return true; if( !indirect ) return false; return hasTransient( cl->transientFor(), indirect ); } if( !cl->isTransient()) return false; if( group() != cl->group()) return false; // cl is group transient, search from top if( transients().contains( const_cast< Client* >( cl ))) return true; if( !indirect ) return false; for( ClientList::ConstIterator it = transients().begin(); it != transients().end(); ++it ) if( (*it)->hasTransient( cl, indirect )) return true; return false; } ClientList Client::mainClients() const { if( !isTransient()) return ClientList(); if( transientFor() != NULL ) return ClientList() << const_cast< Client* >( transientFor()); ClientList result; for( ClientList::ConstIterator it = group()->members().begin(); it != group()->members().end(); ++it ) if( !(*it)->groupTransient() && (*it)->hasTransient( this, false )) result.append( *it ); return result; } Client* Client::findModal() { for( ClientList::ConstIterator it = transients().begin(); it != transients().end(); ++it ) if( Client* ret = (*it)->findModal()) return ret; if( isModal()) return this; return NULL; } // Client::window_group only holds the contents of the hint, // but it should be used only to find the group, not for anything else void Client::checkGroup() { if( window_group != None ) { Group* new_group = workspace()->findGroup( window_group ); if( transientFor() != NULL && transientFor()->group() != new_group ) { kdWarning( 1216 ) << this << " is transient for " << transientFor() << ", which is in different group" << endl; new_group = transientFor()->group(); } if( new_group == NULL ) // doesn't exist yet new_group = new Group( window_group, workspace()); if( new_group != in_group ) { if( in_group != NULL ) in_group->removeMember( this ); in_group = new_group; in_group->addMember( this ); } } else { if( transientFor() != NULL ) { // doesn't have window group set, but is transient for something // so make it part of that group Group* new_group = transientFor()->group(); if( new_group != in_group ) { if( in_group != NULL ) in_group->removeMember( this ); in_group = transientFor()->group(); in_group->addMember( this ); } } else // not transient without a group, put it in its own group { // (can be also group transient which actually doesn't have a group :( ) if( in_group != NULL && in_group->leader() != window()) { in_group->removeMember( this ); in_group = NULL; } if( in_group == NULL ) { in_group = new Group( window(), workspace()); in_group->addMember( this ); in_group->gotLeader( this ); } } } } /*! Returns whether the window provides context help or not. If it does, you should show a help menu item or a help button lie '?' 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::contextHelp() { if ( Pcontexthelp ) { sendClientMessage(win, atoms->wm_protocols, atoms->net_wm_context_help); QWhatsThis::enterWhatsThisMode(); } } /*! Performs a mouse command on this client (see options.h) */ bool Client::performMouseCommand( Options::MouseCommand command, QPoint globalPos) { bool replay = FALSE; switch (command) { case Options::MouseRaise: workspace()->raiseClient( this ); break; case Options::MouseLower: workspace()->lowerClient( this ); break; case Options::MouseShade : toggleShade(); break; case Options::MouseOperationsMenu: if ( isActive() & ( options->focusPolicy != Options::ClickToFocus && options->clickRaise ) ) autoRaise(); workspace()->showWindowMenu( globalPos, this ); break; case Options::MouseToggleRaiseAndLower: workspace()->raiseOrLowerClient( this ); break; case Options::MouseActivateAndRaise: replay = isActive(); // for clickraise mode workspace()->requestFocus( this ); workspace()->raiseClient( this ); break; case Options::MouseActivateAndLower: workspace()->requestFocus( this ); workspace()->lowerClient( this ); break; case Options::MouseActivate: replay = isActive(); // for clickraise mode workspace()->requestFocus( this ); break; case Options::MouseActivateRaiseAndPassClick: workspace()->requestFocus( this ); workspace()->raiseClient( this ); replay = TRUE; break; case Options::MouseActivateAndPassClick: workspace()->requestFocus( this ); replay = TRUE; break; case Options::MouseActivateRaiseAndMove: case Options::MouseActivateRaiseAndUnrestrictedMove: workspace()->raiseClient( this ); workspace()->requestFocus( this ); if( options->moveMode == Options::Transparent && isMovable()) move_faked_activity = workspace()->fakeRequestedActivity( this ); // fallthrough case Options::MouseMove: case Options::MouseUnrestrictedMove: { if (!isMovable()) break; if( moveResizeMode ) stopMoveResize( true ); mode = Center; buttonDown = TRUE; moveOffset = mapFromGlobal( globalPos ); invertedMoveOffset = rect().bottomRight() - moveOffset; QRect desktopArea = workspace()->clientArea( globalPos ); unrestrictedMoveResize = ( command == Options::MouseActivateRaiseAndUnrestrictedMove || command == Options::MouseUnrestrictedMove ); // setMouseCursor( mode ); setCursor(sizeAllCursor); startMoveResize(); break; } case Options::MouseResize: case Options::MouseUnrestrictedResize: { if (!isResizable() || isShade()) // SHADE break; if( moveResizeMode ) stopMoveResize( true ); buttonDown = TRUE; moveOffset = mapFromGlobal( globalPos ); int x = moveOffset.x(), y = moveOffset.y(); bool left = x < width() / 3; bool right = x >= 2 * width() / 3; bool top = y < height() / 3; bool bot = y >= 2 * height() / 3; if (top) mode = left ? TopLeft : (right ? TopRight : Top); else if (bot) mode = left ? BottomLeft : (right ? BottomRight : Bottom); else mode = (x < width() / 2) ? Left : Right; invertedMoveOffset = rect().bottomRight() - moveOffset; QRect desktopArea = workspace()->clientArea( globalPos ); unrestrictedMoveResize = ( command == Options::MouseUnrestrictedResize ); setMouseCursor( mode ); startMoveResize(); break; } case Options::MouseMinimize: minimize(); break; case Options::MouseNothing: // fall through default: replay = TRUE; break; } return replay; } // performs _NET_WM_MOVERESIZE void Client::NETMoveResize( int x_root, int y_root, NET::Direction direction ) { if( direction == NET::Move ) performMouseCommand( Options::MouseMove, QPoint( x_root, y_root )); else if( direction >= NET::TopLeft && direction <= NET::Left ) { static const MousePosition convert[] = { TopLeft, Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left }; if(!isResizable() || isShade()) return; if( moveResizeMode ) stopMoveResize( true ); buttonDown = TRUE; moveOffset = mapFromGlobal( QPoint( x_root, y_root )); invertedMoveOffset = rect().bottomRight() - moveOffset; unrestrictedMoveResize = false; mode = convert[ direction ]; setMouseCursor( mode ); startMoveResize(); } else if( direction == NET::KeyboardMove ) { // ignore mouse coordinates given in the message, mouse position is used by the moving algorithm QCursor::setPos( geometry().center() ); performMouseCommand( Options::MouseUnrestrictedMove, geometry().center()); } else if( direction == NET::KeyboardSize ) { // ignore mouse coordinates given in the message, mouse position is used by the resizing algorithm QCursor::setPos( geometry().bottomRight()); performMouseCommand( Options::MouseUnrestrictedResize, geometry().bottomRight()); } } void Client::keyPressEvent( uint key_code ) { updateUserTime(); if ( !isMove() && !isResize() ) return; bool is_control = key_code & Qt::CTRL; key_code = key_code & 0xffff; int delta = is_control?1:8; QPoint pos = QCursor::pos(); switch ( key_code ) { case Key_Left: pos.rx() -= delta; break; case Key_Right: pos.rx() += delta; break; case Key_Up: pos.ry() -= delta; break; case Key_Down: pos.ry() += delta; break; case Key_Space: case Key_Return: case Key_Enter: case Key_Escape: stopMoveResize( true ); buttonDown = FALSE; break; default: return; } QCursor::setPos( pos ); } static QCString getStringProperty(WId w, Atom prop, char separator) { Atom type; int format, status; unsigned long nitems = 0; unsigned long extra = 0; unsigned char *data = 0; QCString result = ""; XErrorHandler oldHandler = XSetErrorHandler(nullErrorHandler); status = XGetWindowProperty( qt_xdisplay(), w, prop, 0, 10000, FALSE, XA_STRING, &type, &format, &nitems, &extra, &data ); XSetErrorHandler(oldHandler); if ( status == Success) { if (data && separator) { for (int i=0; i<(int)nitems; i++) if (!data[i] && i+1<(int)nitems) data[i] = separator; } if (data) result = (const char*) data; XFree(data); } return result; } /*! 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(win); } /*! Returns sessionId for this client, taken either from its window or from the leader window. */ QCString Client::sessionId() { QCString result = staticSessionId(win); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin!=win) 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(win); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin!=win) 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(win); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin!=win) 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 win; } // with the new decorations API, this will be done differently I hope void Client::createLayout() { } void Client::activateLayout() { if ( layout() ) layout()->activate(); } 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 may_move && ( !isSpecialWindow() || isOverride() || isSplash() || isToolbar()) && // allow moving of splashscreens :) ( !isMaximized() || ( options->moveResizeMaximizedWindows || max_mode != MaximizeFull ) ); } bool Client::isDesktop() const { return windowType() == NET::Desktop; } bool Client::isDock() const { return windowType() == NET::Dock; } bool Client::isTopMenu() const { NET::WindowType wt = windowType(); 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()->geometry().width()) < 10 ) wt = NET::TopMenu; } return wt == 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::isTool() const { return isToolbar(); } 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() // SELI is NET::Override special or not? || isToolbar(); // TODO } NET::WindowType Client::windowType( bool strict ) const { NET::WindowType wt = info->windowType( SUPPORTED_WINDOW_TYPES_MASK ); if( !strict ) { if( wt == NET::Menu && isTopMenu()) wt = NET::TopMenu; if( wt == NET::Unknown ) wt = isTransient() ? NET::Dialog : NET::Normal; } return wt; } /*! Returns \a area with the client's strut taken into account. Used from Workspace in updateClientArea. */ QRect Client::adjustedClientArea( const QRect& area ) const { QRect r = area; NETStrut strut = info->strut(); if ( strut.left > 0 ) r.setLeft( r.left() + (int) strut.left ); if ( strut.top > 0 ) r.setTop( r.top() + (int) strut.top ); if ( strut.right > 0 ) r.setRight( r.right() - (int) strut.right ); if ( strut.bottom > 0 ) r.setBottom( r.bottom() - (int) strut.bottom ); return r; } void Client::animateMinimizeOrUnminimize( bool minimize ) { if ( blockAnimation ) return; if ( !options->animateMinimize ) return; // 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; XGrabServer( qt_xdisplay() ); 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(); XUngrabServer( qt_xdisplay() ); } /*! 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::TitleBar, isActive() || isMinimized() ) ); QPainter p( &pm ); p.setPen(options->color(Options::Font, isActive() || isMinimized() )); p.setFont(options->font(isActive())); p.drawText( pm.rect(), AlignLeft|AlignVCenter|SingleLine, caption() ); return pm; } void Client::autoRaise() { workspace()->raiseClient( this ); delete autoRaiseTimer; autoRaiseTimer = 0; } void Client::shadeHover() { setShade( ShadeHover ); delete shadeHoverTimer; shadeHoverTimer = 0; } NETWinInfo * Client::netWinInfo() { return static_cast(info); } /*!\reimp */ QString Client::caption() const { return cap; } /*!\reimp */ void Client::setCaption( const QString & c) { cap = c; } 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() << "\'"; } NoBorderClient::NoBorderClient( Workspace *ws, QWidget *parent, const char *name ) : Client( ws, parent, name ) { } NoBorderClient::~NoBorderClient() { } void NoBorderClient::createLayout() { QHBoxLayout* h = new QHBoxLayout( this ); h->addWidget( windowWrapper() ); } QPixmap * kwin_get_menu_pix_hack() { static QPixmap p; if ( p.isNull() ) p = SmallIcon( "bx2" ); return &p; } } // namespace #include "client.moc"