/***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ #include "client.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_SOLARIS #include #include #endif /* SunOS */ #include "bridge.h" #include "group.h" #include "workspace.h" #include "atoms.h" #include "notifications.h" #include "rules.h" #include // put all externs before the namespace statement to allow the linker // to resolve them properly extern Atom tqt_wm_state; extern Atom tqt_window_role; extern Atom tqt_sm_client_id; // wait 200 ms before drawing shadow after move/resize static const int SHADOW_DELAY = 200; namespace KWinInternal { /* TODO: Remove this once X has real translucency. * * A list of the regions covered by all shadows and the Clients to which they * belong. Used to redraw shadows when a window overlapping or underlying a * shadow is moved, resized, or hidden. */ struct ShadowRegion { TQRegion region; Client *client; }; static TQValueList shadowRegions; /* 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 ) : TQObject( NULL ), client( None ), wrapper( None ), frame( None ), decoration( NULL ), wspace( ws ), bridge( new Bridge( this )), inhibitConfigureRequests(false), move_faked_activity( false ), move_resize_grab_window( None ), transient_for( NULL ), transient_for_id( None ), original_transient_for_id( None ), in_group( NULL ), window_group( None ), in_layer( UnknownLayer ), ping_timer( NULL ), process_killer( NULL ), process_resumer( NULL ), user_time( CurrentTime ), // not known yet allowed_actions( 0 ), postpone_geometry_updates( 0 ), pending_geometry_update( false ), shade_geometry_change( false ), border_left( 0 ), border_right( 0 ), border_top( 0 ), border_bottom( 0 ), opacity_( 0 ), demandAttentionKNotifyTimer( NULL ), activeMaximizing(false), activeTiled(false), gridTilingMode(false), hGridTiles(1), vGridTiles(1) // SELI do all as initialization { autoRaiseTimer = 0; shadeHoverTimer = 0; configureRequestTimer = new TQTimer(this); connect(configureRequestTimer, TQ_SIGNAL(timeout()), TQ_SLOT(configureRequestTimeout())); shadowDelayTimer = new TQTimer(this); opacityCache = &activeOpacityCache; shadowAfterClient = NULL; shadowWidget = NULL; shadowMe = true; connect(shadowDelayTimer, TQ_SIGNAL(timeout()), TQ_SLOT(drawShadow())); // set the initial mapping state mapping_state = WithdrawnState; desk = 0; // no desktop yet mode = PositionCenter; buttonDown = FALSE; moveResizeMode = FALSE; info = NULL; shade_mode = ShadeNone; active = FALSE; deleting = false; keep_above = FALSE; keep_below = FALSE; is_shape = FALSE; motif_noborder = false; motif_may_move = TRUE; motif_may_resize = TRUE; motif_may_close = TRUE; fullscreen_mode = FullScreenNone; skip_taskbar = FALSE; original_skip_taskbar = false; minimized = false; hidden = false; modal = false; noborder = false; user_noborder = false; urgency = false; ignore_focus_stealing = false; demands_attention = false; check_active_modal = false; Pdeletewindow = 0; Ptakefocus = 0; Ptakeactivity = 0; Pcontexthelp = 0; Pping = 0; input = FALSE; skip_pager = FALSE; max_mode = MaximizeRestore; maxmode_restore = MaximizeRestore; cmap = None; frame_geometry = TQRect( 0, 0, 100, 100 ); // so that decorations don't start with size being (0,0) client_size = TQSize( 100, 100 ); custom_opacity = false; rule_opacity_active = 0; //translucency rules rule_opacity_inactive = 0; //dito. // SELI initialize xsizehints?? } /*! "Dumb" destructor. */ Client::~Client() { assert(!moveResizeMode); assert( client == None ); assert( frame == None && wrapper == None ); assert( decoration == NULL ); assert( postpone_geometry_updates == 0 ); assert( !check_active_modal ); delete info; delete bridge; } // use destroyClient() or releaseWindow(), Client instances cannot be deleted directly void Client::deleteClient( Client* c, allowed_t ) { delete c; } /*! Releases the window. The client has done its job and the window is still existing. */ void Client::releaseWindow( bool on_shutdown ) { assert( !deleting ); deleting = true; workspace()->discardUsedWindowRules( this, true ); // remove ForceTemporarily rules StackingUpdatesBlocker blocker( workspace()); if (!custom_opacity) setOpacity(FALSE); if (moveResizeMode) leaveMoveResize(); removeShadow(); drawIntersectingShadows(); finishWindowRules(); ++postpone_geometry_updates; // grab X during the release to make removing of properties, setting to withdrawn state // and repareting to root an atomic operation (http://lists.kde.org/?l=kde-devel&m=116448102901184&w=2) grabXServer(); setMappingState( WithdrawnState ); setModal( false ); // otherwise its mainwindow wouldn't get focus hidden = true; // so that it's not considered visible anymore (can't use hideClient(), it would set flags) if( !on_shutdown ) workspace()->clientHidden( this ); XUnmapWindow( tqt_xdisplay(), frameId()); // destroying decoration would cause ugly visual effect destroyDecoration(); cleanGrouping(); if( !on_shutdown ) { workspace()->removeClient( this, Allowed ); // only when the window is being unmapped, not when closing down KWin // (NETWM sections 5.5,5.7) info->setDesktop( 0 ); desk = 0; info->setState( 0, info->state()); // reset all state flags } XDeleteProperty( tqt_xdisplay(), client, atoms->kde_net_wm_user_creation_time); XDeleteProperty( tqt_xdisplay(), client, atoms->net_frame_extents ); XDeleteProperty( tqt_xdisplay(), client, atoms->kde_net_wm_frame_strut ); XReparentWindow( tqt_xdisplay(), client, workspace()->rootWin(), x(), y()); XRemoveFromSaveSet( tqt_xdisplay(), client ); XSelectInput( tqt_xdisplay(), client, NoEventMask ); if( on_shutdown ) { // map the window, so it can be found after another WM is started XMapWindow( tqt_xdisplay(), client ); // TODO preserve minimized, shaded etc. state? } else { // Make sure it's not mapped if the app unmapped it (#65279). The app // may do map+unmap before we initially map the window by calling rawShow() from manage(). XUnmapWindow( tqt_xdisplay(), client ); } client = None; XDestroyWindow( tqt_xdisplay(), wrapper ); wrapper = None; XDestroyWindow( tqt_xdisplay(), frame ); frame = None; --postpone_geometry_updates; // don't use GeometryUpdatesBlocker, it would now set the geometry checkNonExistentClients(); deleteClient( this, Allowed ); ungrabXServer(); } // like releaseWindow(), but this one is called when the window has been already destroyed // (e.g. the application closed it) void Client::destroyClient() { assert( !deleting ); deleting = true; workspace()->discardUsedWindowRules( this, true ); // remove ForceTemporarily rules StackingUpdatesBlocker blocker( workspace()); if (moveResizeMode) leaveMoveResize(); removeShadow(); drawIntersectingShadows(); finishWindowRules(); ++postpone_geometry_updates; setModal( false ); hidden = true; // so that it's not considered visible anymore workspace()->clientHidden( this ); destroyDecoration(); cleanGrouping(); workspace()->removeClient( this, Allowed ); client = None; // invalidate XDestroyWindow( tqt_xdisplay(), wrapper ); wrapper = None; XDestroyWindow( tqt_xdisplay(), frame ); frame = None; --postpone_geometry_updates; // don't use GeometryUpdatesBlocker, it would now set the geometry checkNonExistentClients(); deleteClient( this, Allowed ); } void Client::updateDecoration( bool check_workspace_pos, bool force ) { if( !force && (( decoration == NULL && noBorder()) || ( decoration != NULL && !noBorder()))) return; bool do_show = false; postponeGeometryUpdates( true ); if( force ) destroyDecoration(); if( !noBorder()) { setMask( TQRegion()); // reset shape mask decoration = workspace()->createDecoration( bridge ); // TODO check decoration's minimum size? decoration->init(); decoration->widget()->installEventFilter( this ); XReparentWindow( tqt_xdisplay(), decoration->widget()->winId(), frameId(), 0, 0 ); decoration->widget()->lower(); decoration->borders( border_left, border_right, border_top, border_bottom ); options->onlyDecoTranslucent ? setDecoHashProperty(border_top, border_right, border_bottom, border_left): unsetDecoHashProperty(); int save_workarea_diff_x = workarea_diff_x; int save_workarea_diff_y = workarea_diff_y; move( calculateGravitation( false )); plainResize( sizeForClientSize( clientSize()), ForceGeometrySet ); workarea_diff_x = save_workarea_diff_x; workarea_diff_y = save_workarea_diff_y; do_show = true; } else destroyDecoration(); if( check_workspace_pos ) checkWorkspacePosition(); postponeGeometryUpdates( false ); if( do_show ) decoration->widget()->show(); updateFrameExtents(); updateOpacityCache(); } void Client::destroyDecoration() { if( decoration != NULL ) { delete decoration; decoration = NULL; TQPoint grav = calculateGravitation( true ); border_left = border_right = border_top = border_bottom = 0; setMask( TQRegion()); // reset shape mask int save_workarea_diff_x = workarea_diff_x; int save_workarea_diff_y = workarea_diff_y; plainResize( sizeForClientSize( clientSize()), ForceGeometrySet ); move( grav ); workarea_diff_x = save_workarea_diff_x; workarea_diff_y = save_workarea_diff_y; } } void Client::checkBorderSizes() { if( decoration == NULL ) return; int new_left, new_right, new_top, new_bottom; decoration->borders( new_left, new_right, new_top, new_bottom ); if( new_left == border_left && new_right == border_right && new_top == border_top && new_bottom == border_bottom ) return; GeometryUpdatesPostponer blocker( this ); move( calculateGravitation( true )); border_left = new_left; border_right = new_right; border_top = new_top; border_bottom = new_bottom; if (border_left != new_left || border_right != new_right || border_top != new_top || border_bottom != new_bottom) options->onlyDecoTranslucent ? setDecoHashProperty(new_top, new_right, new_bottom, new_left): unsetDecoHashProperty(); move( calculateGravitation( false )); plainResize( sizeForClientSize( clientSize()), ForceGeometrySet ); checkWorkspacePosition(); } void Client::detectNoBorder() { if( Shape::hasShape( window())) { noborder = true; return; } switch( windowType()) { case NET::Desktop : case NET::Dock : case NET::TopMenu : case NET::Splash : noborder = true; break; case NET::Unknown : case NET::Normal : case NET::Toolbar : case NET::Menu : case NET::Dialog : case NET::Utility : noborder = false; break; default: assert( false ); } // NET::Override is some strange beast without clear definition, usually // just meaning "noborder", so let's treat it only as such flag, and ignore it as // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it) if( info->windowType( SUPPORTED_WINDOW_TYPES_MASK | NET::OverrideMask ) == NET::Override ) noborder = true; } void Client::detectShapable() { if( Shape::hasShape( window())) return; switch( windowType()) { case NET::Desktop : case NET::Dock : case NET::TopMenu : case NET::Splash : break; case NET::Unknown : case NET::Normal : case NET::Toolbar : case NET::Menu : case NET::Dialog : case NET::Utility : setShapable(FALSE); break; default: assert( false ); } } void Client::updateFrameExtents() { NETStrut strut; strut.left = border_left; strut.right = border_right; strut.top = border_top; strut.bottom = border_bottom; info->setFrameExtents( strut ); } // Resizes the decoration, and makes sure the decoration widget gets resize event // even if the size hasn't changed. This is needed to make sure the decoration // re-layouts (e.g. when options()->moveResizeMaximizedWindows() changes, // the decoration may turn on/off some borders, but the actual size // of the decoration stays the same). void Client::resizeDecoration( const TQSize& s ) { if( decoration == NULL ) return; TQSize oldsize = decoration->widget()->size(); decoration->resize( s ); if( oldsize == s ) { TQResizeEvent e( s, oldsize ); TQApplication::sendEvent( decoration->widget(), &e ); } if (!moveResizeMode && options->shadowEnabled(isActive())) { // If the user is manually resizing, let Client::leaveMoveResize() // decide when to redraw the shadow updateOpacityCache(); } } bool Client::noBorder() const { return noborder || isFullScreen() || user_noborder || motif_noborder; } bool Client::userCanSetNoBorder() const { return !noborder && !isFullScreen() && !isShade(); } bool Client::isUserNoBorder() const { return user_noborder; } void Client::setUserNoBorder( bool set ) { if( !userCanSetNoBorder()) return; set = rules()->checkNoBorder( set ); if( user_noborder == set ) return; user_noborder = set; updateDecoration( true, false ); updateWindowRules(); } bool Client::isModalSystemNotification() const { unsigned char *data = 0; Atom actual; int format, result; unsigned long n, left; result = XGetWindowProperty(tqt_xdisplay(), window(), atoms->net_wm_system_modal_notification, 0L, 1L, False, XA_CARDINAL, &actual, &format, &n, &left, /*(unsigned char **)*/ &data); if (result == Success && data && format == 32 ) { return TRUE; } return FALSE; } void Client::updateShape() { // workaround for #19644 - shaped windows shouldn't have decoration if( shape() && !noBorder()) { noborder = true; updateDecoration( true ); } updateOpacityCache(); if ( shape() ) { XShapeCombineShape(tqt_xdisplay(), frameId(), ShapeBounding, clientPos().x(), clientPos().y(), window(), ShapeBounding, ShapeSet); setShapable(TRUE); } // !shape() mask setting is done in setMask() when the decoration // calls it or when the decoration is created/destroyed if( Shape::version() >= 0x11 ) // 1.1, has input shape support { // There appears to be no way to find out if a window has input // shape set or not, so always propagate the input shape // (it's the same like the bounding shape by default). // Also, build the shape using a helper window, not directly // in the frame window, because the sequence set-shape-to-frame, // remove-shape-of-client, add-input-shape-of-client has the problem // that after the second step there's a hole in the input shape // until the real shape of the client is added and that can make // the window lose focus (which is a problem with mouse focus policies) static Window helper_window = None; if( helper_window == None ) helper_window = XCreateSimpleWindow( tqt_xdisplay(), tqt_xrootwin(), 0, 0, 1, 1, 0, 0, 0 ); XResizeWindow( tqt_xdisplay(), helper_window, width(), height()); XShapeCombineShape( tqt_xdisplay(), helper_window, ShapeInput, 0, 0, frameId(), ShapeBounding, ShapeSet ); XShapeCombineShape( tqt_xdisplay(), helper_window, ShapeInput, clientPos().x(), clientPos().y(), window(), ShapeBounding, ShapeSubtract ); XShapeCombineShape( tqt_xdisplay(), helper_window, ShapeInput, clientPos().x(), clientPos().y(), window(), ShapeInput, ShapeUnion ); XShapeCombineShape( tqt_xdisplay(), frameId(), ShapeInput, 0, 0, helper_window, ShapeInput, ShapeSet ); } } void Client::setMask( const TQRegion& reg, int mode ) { _mask = reg; if( reg.isNull()) XShapeCombineMask( tqt_xdisplay(), frameId(), ShapeBounding, 0, 0, None, ShapeSet ); else if( mode == X::Unsorted ) XShapeCombineRegion( tqt_xdisplay(), frameId(), ShapeBounding, 0, 0, reg.handle(), ShapeSet ); else { TQMemArray< TQRect > rects = reg.rects(); XRectangle* xrects = new XRectangle[ rects.count() ]; for( unsigned int i = 0; i < rects.count(); ++i ) { xrects[ i ].x = rects[ i ].x(); xrects[ i ].y = rects[ i ].y(); xrects[ i ].width = rects[ i ].width(); xrects[ i ].height = rects[ i ].height(); } XShapeCombineRectangles( tqt_xdisplay(), frameId(), ShapeBounding, 0, 0, xrects, rects.count(), ShapeSet, mode ); delete[] xrects; } updateShape(); } TQRegion Client::mask() const { if( _mask.isEmpty()) return TQRegion( 0, 0, width(), height()); return _mask; } void Client::setShapable(bool b) { long tmp = b?1:0; XChangeProperty(tqt_xdisplay(), frameId(), atoms->net_wm_window_shapable, XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &tmp, 1L); } void Client::hideClient( bool hide ) { if( hidden == hide ) return; hidden = hide; updateVisibility(); } /*! Returns whether the window is minimizable or not */ bool Client::isMinimizable() const { if( isSpecialWindow()) return false; if( isModalSystemNotification()) return false; if( isTransient()) { // #66868 - let other xmms windows be minimized when the mainwindow is minimized bool shown_mainwindow = false; ClientList mainclients = mainClients(); for( ClientList::ConstIterator it = mainclients.begin(); it != mainclients.end(); ++it ) { if( (*it)->isShown( true )) shown_mainwindow = true; } if( !shown_mainwindow ) return true; } // this is here because kicker's taskbar doesn't provide separate entries // for windows with an explicitly given parent // TODO perhaps this should be redone if( transientFor() != NULL ) return false; if( !wantsTabFocus()) // SELI - NET::Utility? why wantsTabFocus() - skiptaskbar? ? return false; return true; } /*! Returns whether the window is kept above or not */ bool Client::keepAbove() const { if( isModalSystemNotification()) return true; return keep_above; } /*! Minimizes this client plus its transients */ void Client::minimize( bool avoid_animation ) { if ( !isMinimizable() || isMinimized()) return; if (isShade()) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded info->setState(0, NET::Shaded); Notify::raise( Notify::Minimize ); // SELI mainClients().isEmpty() ??? - and in unminimize() too if ( mainClients().isEmpty() && isOnCurrentDesktop() && isShown( true ) && !avoid_animation ) animateMinimizeOrUnminimize( true ); // was visible or shaded minimized = true; updateVisibility(); updateAllowedActions(); workspace()->updateMinimizedOfTransients( this ); updateWindowRules(); workspace()->updateFocusChains( this, Workspace::FocusChainMakeLast ); } void Client::unminimize( bool avoid_animation ) { if (!queryUserSuspendedResume()) return; if( !isMinimized()) return; if (isShade()) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded info->setState(NET::Shaded, NET::Shaded); Notify::raise( Notify::UnMinimize ); minimized = false; if( isOnCurrentDesktop() && isShown( true )) { if( mainClients().isEmpty() && !avoid_animation ) animateMinimizeOrUnminimize( FALSE ); } updateVisibility(); updateAllowedActions(); workspace()->updateMinimizedOfTransients( this ); updateWindowRules(); } extern bool blockAnimation; void Client::animateMinimizeOrUnminimize( bool minimize ) { if ( blockAnimation ) return; if ( !options->animateMinimize ) return; if( decoration != NULL && decoration->animateMinimize( minimize )) return; // decoration did it // the function is a bit tricky since it will ensure that an // animation action needs always the same time regardless of the // performance of the machine or the X-Server. float lf,rf,tf,bf,step; int speed = options->animateMinimizeSpeed; if ( speed > 10 ) speed = 10; if ( speed < 0 ) speed = 0; step = 40. * (11 - speed ); NETRect r = info->iconGeometry(); TQRect icongeom( r.pos.x, r.pos.y, r.size.width, r.size.height ); if ( !icongeom.isValid() ) return; TQPixmap pm = animationPixmap( minimize ? width() : icongeom.width() ); TQRect before, after; if ( minimize ) { before = TQRect( x(), y(), width(), pm.height() ); after = TQRect( icongeom.x(), icongeom.y(), icongeom.width(), pm.height() ); } else { before = TQRect( icongeom.x(), icongeom.y(), icongeom.width(), pm.height() ); after = TQRect( x(), y(), width(), pm.height() ); } lf = (after.left() - before.left())/step; rf = (after.right() - before.right())/step; tf = (after.top() - before.top())/step; bf = (after.bottom() - before.bottom())/step; grabXServer(); TQRect area = before; TQRect area2; TQPixmap pm2; TQTime t; t.start(); float diff; TQPainter p ( workspace()->desktopWidget() ); bool need_to_clear = FALSE; TQPixmap pm3; do { if (area2 != area) { pm = animationPixmap( area.width() ); pm2 = TQPixmap::grabWindow( tqt_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(tqt_xdisplay()); XSync( tqt_xdisplay(), FALSE ); diff = t.elapsed(); if (diff > step) diff = step; area.setLeft(before.left() + int(diff*lf)); area.setRight(before.right() + int(diff*rf)); area.setTop(before.top() + int(diff*tf)); area.setBottom(before.bottom() + int(diff*bf)); if (area2 != area ) { if ( area2.intersects( area ) ) p.drawPixmap( area2.x(), area2.y(), pm2 ); else { // no overlap, we can clear later to avoid flicker pm3 = pm2; need_to_clear = TRUE; } } } while ( t.elapsed() < step); if (area2 == area || need_to_clear ) p.drawPixmap( area2.x(), area2.y(), pm2 ); p.end(); ungrabXServer(); } /*! The pixmap shown during (un)minimalization animation */ TQPixmap Client::animationPixmap( int w ) { TQFont font = options->font(isActive()); TQFontMetrics fm( font ); TQPixmap pm( w, fm.lineSpacing() ); pm.fill( options->color(Options::ColorTitleBar, isActive() || isMinimized() ) ); TQPainter p( &pm ); p.setPen(options->color(Options::ColorFont, isActive() || isMinimized() )); p.setFont(options->font(isActive())); p.drawText( pm.rect(), AlignLeft|AlignVCenter|SingleLine, caption() ); return pm; } bool Client::isShadeable() const { return !isSpecialWindow() && !noBorder(); } void Client::setShade( ShadeMode mode ) { if( !isShadeable()) return; if( isModalSystemNotification()) return; mode = rules()->checkShade( mode ); if( shade_mode == mode ) return; bool was_shade = isShade(); ShadeMode was_shade_mode = shade_mode; shade_mode = mode; if( was_shade == isShade()) { if( decoration != NULL ) // decoration may want to update after e.g. hover-shade changes decoration->shadeChange(); return; // no real change in shaded state } if( shade_mode == ShadeNormal ) { if ( isShown( true ) && isOnCurrentDesktop()) Notify::raise( Notify::ShadeUp ); } else if( shade_mode == ShadeNone ) { if( isShown( true ) && isOnCurrentDesktop()) Notify::raise( Notify::ShadeDown ); } assert( decoration != NULL ); // noborder windows can't be shaded GeometryUpdatesPostponer blocker( this ); // decorations may turn off some borders when shaded decoration->borders( border_left, border_right, border_top, border_bottom ); int as = options->animateShade? 10 : 1; // TODO all this unmapping, resizing etc. feels too much duplicated from elsewhere if ( isShade()) { // shade_mode == ShadeNormal // we're about to shade, texx xcompmgr to prepare long _shade = 1; XChangeProperty(tqt_xdisplay(), frameId(), atoms->net_wm_window_shade, XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &_shade, 1L); // shade int h = height(); shade_geometry_change = true; TQSize s( sizeForClientSize( TQSize( clientSize()))); s.setHeight( border_top + border_bottom ); XSelectInput( tqt_xdisplay(), wrapper, ClientWinMask ); // avoid getting UnmapNotify XUnmapWindow( tqt_xdisplay(), wrapper ); XUnmapWindow( tqt_xdisplay(), client ); XSelectInput( tqt_xdisplay(), wrapper, ClientWinMask | SubstructureNotifyMask ); //as we hid the unmap event, xcompmgr didn't recognize the client wid has vanished, so we'll extra inform it //done xcompmgr workaround // FRAME repaint( FALSE ); // bool wasStaticContents = testWFlags( WStaticContents ); // setWFlags( WStaticContents ); int step = TQMAX( 4, TQABS( h - s.height() ) / as )+1; do { h -= step; XResizeWindow( tqt_xdisplay(), frameId(), s.width(), h ); resizeDecoration( TQSize( s.width(), h )); TQApplication::syncX(); } while ( h > s.height() + step ); // if ( !wasStaticContents ) // clearWFlags( WStaticContents ); plainResize( s ); shade_geometry_change = false; if( isActive()) { if( was_shade_mode == ShadeHover ) workspace()->activateNextClient( this ); else workspace()->focusToNull(); } // tell xcompmgr shade's done _shade = 2; XChangeProperty(tqt_xdisplay(), frameId(), atoms->net_wm_window_shade, XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &_shade, 1L); } else { int h = height(); shade_geometry_change = true; TQSize s( sizeForClientSize( clientSize())); // FRAME bool wasStaticContents = testWFlags( WStaticContents ); // setWFlags( WStaticContents ); int step = TQMAX( 4, TQABS( h - s.height() ) / as )+1; do { h += step; XResizeWindow( tqt_xdisplay(), frameId(), s.width(), h ); resizeDecoration( TQSize( s.width(), h )); // assume a border // we do not have time to wait for X to send us paint events // FRAME repaint( 0, h - step-5, width(), step+5, TRUE); TQApplication::syncX(); } while ( h < s.height() - step ); // if ( !wasStaticContents ) // clearWFlags( WStaticContents ); shade_geometry_change = false; plainResize( s ); if( shade_mode == ShadeHover || shade_mode == ShadeActivated ) setActive( TRUE ); XMapWindow( tqt_xdisplay(), wrapperId()); XMapWindow( tqt_xdisplay(), window()); XDeleteProperty (tqt_xdisplay(), client, atoms->net_wm_window_shade); if (options->shadowEnabled(false)) { for (ClientList::ConstIterator it = transients().begin(); it != transients().end(); ++it) { (*it)->removeShadow(); (*it)->drawDelayedShadow(); } } if ( isActive() ) workspace()->requestFocus( this ); } checkMaximizeGeometry(); info->setState( (isShade() && !isMinimized()) ? NET::Shaded : 0, NET::Shaded ); info->setState( isShown( false ) ? 0 : NET::Hidden, NET::Hidden ); updateVisibility(); updateAllowedActions(); workspace()->updateMinimizedOfTransients( this ); decoration->shadeChange(); updateWindowRules(); } void Client::configureRequestTimeout() { inhibitConfigureRequests = false; sendSyntheticConfigureNotify(); } void Client::shadeHover() { setShade( ShadeHover ); cancelShadeHover(); } void Client::cancelShadeHover() { delete shadeHoverTimer; shadeHoverTimer = 0; } void Client::toggleShade() { // if the mode is ShadeHover or ShadeActive, cancel shade too setShade( shade_mode == ShadeNone ? ShadeNormal : ShadeNone ); } void Client::updateVisibility() { if( deleting ) return; bool show = true; if( hidden ) { setMappingState( IconicState ); info->setState( NET::Hidden, NET::Hidden ); setSkipTaskbar( true, false ); // also hide from taskbar rawHide(); show = false; } else { setSkipTaskbar( original_skip_taskbar, false ); } if( minimized ) { setMappingState( IconicState ); info->setState( NET::Hidden, NET::Hidden ); rawHide(); show = false; } if( show ) info->setState( 0, NET::Hidden ); if( !isOnCurrentDesktop()) { setMappingState( IconicState ); rawHide(); show = false; } if( show ) { bool belongs_to_desktop = false; for( ClientList::ConstIterator it = group()->members().begin(); it != group()->members().end(); ++it ) if( (*it)->isDesktop()) { belongs_to_desktop = true; break; } if( !belongs_to_desktop && workspace()->showingDesktop()) workspace()->resetShowingDesktop( true ); if( isShade()) setMappingState( IconicState ); else setMappingState( NormalState ); rawShow(); } } void Client::setShadowed(bool shadowed) { bool wasShadowed; wasShadowed = isShadowed(); shadowMe = options->shadowEnabled(isActive()) ? shadowed : false; if (shadowMe) { if (!wasShadowed) drawShadow(); } else { if (wasShadowed) { removeShadow(); if (!activeOpacityCache.isNull()) activeOpacityCache.resize(0); if (!inactiveOpacityCache.isNull()) inactiveOpacityCache.resize(0); } } } void Client::updateOpacityCache() { if (!activeOpacityCache.isNull()) activeOpacityCache.resize(0); if (!inactiveOpacityCache.isNull()) inactiveOpacityCache.resize(0); if (!moveResizeMode) { // If the user is manually resizing, let Client::finishMoveResize() // decide when to redraw the shadow removeShadow(); drawIntersectingShadows(); if (options->shadowEnabled(isActive())) drawDelayedShadow(); } } /*! Redraw shadows that were previously occluding or occluded by this window, to avoid visual glitches. */ void Client::drawIntersectingShadows() { //Client *reshadowClient; TQRegion region; //TQPtrList reshadowClients; TQValueList reshadowClients; TQValueListIterator it; TQValueListIterator it2; if (!options->shadowEnabled(false)) // No point in redrawing overlapping/overlapped shadows if only the // active window has a shadow. return; region = shapeBoundingRegion; // Generate list of Clients whose shadows need to be redrawn. That is, // those that are currently intersecting or intersected by other windows or // shadows. for (it = shadowRegions.begin(); it != shadowRegions.end(); ++it) if ((isOnAllDesktops() || (*it).client->isOnCurrentDesktop()) && !(*it).region.intersect(region).isEmpty()) reshadowClients.append((*it).client); // Redraw shadows for each of the Clients in the list generated above for (it2 = reshadowClients.begin(); it2 != reshadowClients.end(); ++it2) { (*it2)->removeShadow(); (*it2)->drawDelayedShadow(); } } /*! Redraw shadows that are above the current window in the stacking order. Furthermore, redraw them in the same order as they come in the stacking order from bottom to top. */ void Client::drawOverlappingShadows(bool waitForMe) { Client *aClient; TQRegion region; TQValueList reshadowClients; ClientList stacking_order; ClientList::ConstIterator it; TQValueListIterator it2; TQValueListIterator it3; if (!options->shadowEnabled(false)) // No point in redrawing overlapping/overlapped shadows if only the // active window has a shadow. return; region = shapeBoundingRegion; stacking_order = workspace()->stackingOrder(); for (it = stacking_order.fromLast(); it != stacking_order.end(); --it) { // Find the position of this window in the stacking order. if ((*it) == this) break; } ++it; while (it != stacking_order.end()) { if ((*it)->windowType() == NET::Dock) { // This function is only interested in windows whose shadows don't // have weird stacking rules. ++it; continue; } // Generate list of Clients whose shadows need to be redrawn. That is, // those that are currently overlapping or overlapped by other windows // or shadows. The list should be in order from bottom to top in the // stacking order. for (it2 = shadowRegions.begin(); it2 != shadowRegions.end(); ++it2) { if ((*it2).client == (*it)) { if ((isOnAllDesktops() || (*it2).client->isOnCurrentDesktop()) && !(*it2).region.intersect(region).isEmpty()) reshadowClients.append((*it2).client); } } ++it; } // Redraw shadows for each of the Clients in the list generated above for (it3 = reshadowClients.begin(); it3 != reshadowClients.end(); ++it3) { (*it3)->removeShadow(); if (it3 == reshadowClients.begin()) { if (waitForMe) (*it3)->drawShadowAfter(this); else (*it3)->drawDelayedShadow(); } else { --it3; aClient = (*it3); ++it3; (*it3)->drawShadowAfter(aClient); } } } /*! Draw shadow after some time has elapsed, to give recently exposed windows a chance to repaint before a shadow gradient is drawn over them. */ void Client::drawDelayedShadow() { shadowDelayTimer->stop(); shadowDelayTimer->start(SHADOW_DELAY, true); } /*! Draw shadow immediately after the specified Client's shadow finishes drawing. */ void Client::drawShadowAfter(Client *after) { shadowAfterClient = after; connect(after, TQ_SIGNAL(shadowDrawn()), TQ_SLOT(drawShadow())); } /*! Draw a shadow under this window and XShape the shadow accordingly. */ void Client::drawShadow() { Window shadows[2]; XRectangle *shapes; int i, count, ordering; // If we are waiting for another Client's shadow to be drawn, stop waiting now if (shadowAfterClient != NULL) { disconnect(shadowAfterClient, TQ_SIGNAL(shadowDrawn()), this, TQ_SLOT(drawShadow())); shadowAfterClient = NULL; } if (!isOnCurrentDesktop()) return; /* Store this window's ShapeBoundingRegion even if shadows aren't drawn for * this type of window. Otherwise, drawIntersectingShadows() won't update * properly when this window is moved/resized/hidden/closed. */ shapes = XShapeGetRectangles(tqt_xdisplay(), frameId(), ShapeBounding, &count, &ordering); if (!shapes) // XShape extension not supported shapeBoundingRegion = TQRegion(x(), y(), width(), height()); else { shapeBoundingRegion = TQRegion(); for (i = 0; i < count; i++) { // Translate XShaped window into a TQRegion TQRegion shapeRectangle(shapes[i].x, shapes[i].y, shapes[i].width, shapes[i].height); shapeBoundingRegion += shapeRectangle; } if (isShade()) // Since XResize() doesn't change a window's XShape regions, ensure that // shapeBoundingRegion is not taller than the window's shaded height, // or the bottom shadow will appear to be missing shapeBoundingRegion &= TQRegion(0, 0, width(), height()); shapeBoundingRegion.translate(x(), y()); } if (!isShadowed() || hidden || isMinimized() || maximizeMode() == MaximizeFull || !options->shadowWindowType(windowType())) { XFree(shapes); // Tell whatever Clients are listening that this Client's shadow has been drawn. // It hasn't, but there's no sense waiting for something that won't happen. emit shadowDrawn(); return; } removeShadow(); TQMemArray pixelData; TQPixmap shadowPixmap; TQRect shadow; TQRegion exposedRegion; ShadowRegion shadowRegion; int thickness, xOffset, yOffset; thickness = options->shadowThickness(isActive()); xOffset = options->shadowXOffset(isActive()); yOffset = options->shadowYOffset(isActive()); opacityCache = active? &activeOpacityCache : &inactiveOpacityCache; shadow.setRect(x() - thickness + xOffset, y() - thickness + yOffset, width() + thickness * 2, height() + thickness * 2); shadowPixmap.resize(shadow.size()); // Create a fake drop-down shadow effect via blended Xwindows shadowWidget = new TQWidget(0, 0, (WFlags)(WStyle_Customize | WX11BypassWM)); shadowWidget->setGeometry(shadow); XSelectInput(tqt_xdisplay(), shadowWidget->winId(), ButtonPressMask | ButtonReleaseMask | StructureNotifyMask); shadowWidget->installEventFilter(this); if (!shapes) { // XShape extension not supported exposedRegion = getExposedRegion(shapeBoundingRegion, shadow.x(), shadow.y(), shadow.width(), shadow.height(), thickness, xOffset, yOffset); shadowRegion.region = exposedRegion; shadowRegion.client = this; shadowRegions.append(shadowRegion); if (opacityCache->isNull()) imposeRegionShadow(shadowPixmap, shapeBoundingRegion, exposedRegion, thickness, options->shadowOpacity(isActive())); else imposeCachedShadow(shadowPixmap, exposedRegion); } else { TQMemArray exposedRects; TQMemArray::Iterator it, itEnd; XRectangle *shadowShapes; exposedRegion = getExposedRegion(shapeBoundingRegion, shadow.x(), shadow.y(), shadow.width(), shadow.height(), thickness, xOffset, yOffset); shadowRegion.region = exposedRegion; shadowRegion.client = this; shadowRegions.append(shadowRegion); // XShape the shadow exposedRects = exposedRegion.rects(); i = 0; itEnd = exposedRects.end(); shadowShapes = new XRectangle[exposedRects.count()]; for (it = exposedRects.begin(); it != itEnd; ++it) { shadowShapes[i].x = (*it).x(); shadowShapes[i].y = (*it).y(); shadowShapes[i].width = (*it).width(); shadowShapes[i].height = (*it).height(); i++; } XShapeCombineRectangles(tqt_xdisplay(), shadowWidget->winId(), ShapeBounding, -x() + thickness - xOffset, -y() + thickness - yOffset, shadowShapes, i, ShapeSet, Unsorted); delete [] shadowShapes; if (opacityCache->isNull()) imposeRegionShadow(shadowPixmap, shapeBoundingRegion, exposedRegion, thickness, options->shadowOpacity(isActive())); else imposeCachedShadow(shadowPixmap, exposedRegion); } XFree(shapes); // Set the background pixmap //shadowPixmap.convertFromImage(shadowImage); shadowWidget->setErasePixmap(shadowPixmap); // Restack shadows under this window so that shadows drawn for a newly // focused (but not raised) window don't overlap any windows above it. if (isDock()) { ClientList stacking_order = workspace()->stackingOrder(); for (ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it) if ((*it)->isDesktop()) { ++it; shadows[0] = (*it)->frameId(); shadows[1] = shadowWidget->winId(); } } else { shadows[0] = frameId(); if (shadowWidget != NULL) shadows[1] = shadowWidget->winId(); } XRestackWindows(tqt_xdisplay(), shadows, 2); // Don't use TQWidget::show() so we don't confuse QEffects, thus causing // broken focus. XMapWindow(tqt_xdisplay(), shadowWidget->winId()); // Tell whatever Clients are listening that this Client's shadow has been drawn. emit shadowDrawn(); } /*! Remove shadow under this window. */ void Client::removeShadow() { TQValueList::Iterator it; shadowDelayTimer->stop(); if (shadowWidget != NULL) { for (it = shadowRegions.begin(); it != shadowRegions.end(); ++it) if ((*it).client == this) { shadowRegions.remove(it); break; } delete shadowWidget; shadowWidget = NULL; } } /*! Calculate regions in which the shadow will be visible given the window's origin, height and width and the shadow's thickness, and X- and Y-offsets. */ TQRegion Client::getExposedRegion(TQRegion occludedRegion, int x, int y, int w, int h, int thickness, int xOffset, int yOffset) { TQRegion exposedRegion; exposedRegion = TQRegion(x, y, w, h); exposedRegion -= occludedRegion; if (thickness > 0) { // Limit exposedRegion to include only where a shadow of the specified // thickness will be drawn TQMemArray occludedRects; TQMemArray::Iterator it, itEnd; TQRegion shadowRegion; occludedRects = occludedRegion.rects(); itEnd = occludedRects.end(); for (it = occludedRects.begin(); it != itEnd; ++it) { // Expand each of the occluded region's shape rectangles to contain // where a shadow of the specified thickness will be drawn. Create // a new TQRegion that contains the expanded occluded region it->setTop(it->top() - thickness + yOffset); it->setLeft(it->left() - thickness + xOffset); it->setRight(it->right() + thickness + xOffset); it->setBottom(it->bottom() + thickness + yOffset); shadowRegion += TQRegion(*it); } exposedRegion -= exposedRegion - shadowRegion; } return exposedRegion; } /*! Draw shadow gradient around this window using cached opacity values. */ void Client::imposeCachedShadow(TQPixmap &pixmap, TQRegion exposed) { TQRgb pixel; double opacity; int red, green, blue, pixelRed, pixelGreen, pixelBlue; int subW, subH, w, x, y, zeroX, zeroY; TQImage image; TQMemArray::Iterator it, itEnd; TQMemArray rectangles; TQPixmap subPixmap; Window rootWindow; int thickness, windowX, windowY, xOffset, yOffset; rectangles = exposed.rects(); rootWindow = tqt_xrootwin(); thickness = options->shadowThickness(isActive()); windowX = this->x(); windowY = this->y(); xOffset = options->shadowXOffset(isActive()); yOffset = options->shadowYOffset(isActive()); options->shadowColour(isActive()).rgb(&red, &green, &blue); w = pixmap.width(); itEnd = rectangles.end(); for (it = rectangles.begin(); it != itEnd; ++it) { subW = (*it).width(); subH = (*it).height(); subPixmap = TQPixmap::grabWindow(rootWindow, (*it).x(), (*it).y(), subW, subH); zeroX = (*it).x() - windowX + thickness - xOffset; zeroY = (*it).y() - windowY + thickness - yOffset; image = subPixmap.convertToImage(); for (x = 0; x < subW; x++) { for (y = 0; y < subH; y++) { opacity = (*(opacityCache))[(zeroY + y) * w + zeroX + x]; pixel = image.pixel(x, y); pixelRed = tqRed(pixel); pixelGreen = tqGreen(pixel); pixelBlue = tqBlue(pixel); image.setPixel(x, y, tqRgb((int)(pixelRed + (red - pixelRed) * opacity), (int)(pixelGreen + (green - pixelGreen) * opacity), (int)(pixelBlue + (blue - pixelBlue) * opacity))); } } subPixmap.convertFromImage(image); bitBlt(&pixmap, zeroX, zeroY, &subPixmap); } } /*! Draw shadow around this window using calculated opacity values. */ void Client::imposeRegionShadow(TQPixmap &pixmap, TQRegion occluded, TQRegion exposed, int thickness, double maxOpacity) { int distance, intersectCount, i, j, x, y; TQRgb pixel; double decay, factor, opacity; int red, green, blue, pixelRed, pixelGreen, pixelBlue; int lineIntersects, maxIntersects, maxY; int irBottom, irLeft, irRight, irTop, yIncrement; int subW, subH, w, h, zeroX, zeroY; TQImage image; TQMemArray::Iterator it, itEnd; TQMemArray rectangles; TQPixmap subPixmap; Window rootWindow; int windowX, windowY, xOffset, yOffset; rectangles = exposed.rects(); rootWindow = tqt_xrootwin(); windowX = this->x(); windowY = this->y(); xOffset = options->shadowXOffset(isActive()); yOffset = options->shadowYOffset(isActive()); options->shadowColour(isActive()).rgb(&red, &green, &blue); maxIntersects = thickness * thickness * 4 + (thickness * 4) + 1; lineIntersects = thickness * 2 + 1; factor = maxIntersects / maxOpacity; decay = (lineIntersects / 0.0125 - factor) / pow((double)maxIntersects, 3.0); w = pixmap.width(); h = pixmap.height(); xOffset = options->shadowXOffset(isActive()); yOffset = options->shadowYOffset(isActive()); opacityCache->resize(0); opacityCache->resize(w * h); occluded.translate(-windowX + thickness, -windowY + thickness); itEnd = rectangles.end(); for (it = rectangles.begin(); it != itEnd; ++it) { subW = (*it).width(); subH = (*it).height(); subPixmap = TQPixmap::grabWindow(rootWindow, (*it).x(), (*it).y(), subW, subH); maxY = subH; zeroX = (*it).x() - windowX + thickness - xOffset; zeroY = (*it).y() - windowY + thickness - yOffset; image = subPixmap.convertToImage(); intersectCount = 0; opacity = -1; y = 0; yIncrement = 1; for (x = 0; x < subW; x++) { irLeft = zeroX + x - thickness; irRight = zeroX + x + thickness; while (y != maxY) { // horizontal row about to leave the intersect region, not // necessarily the top row irTop = zeroY + y - thickness * yIncrement; // horizontal row that just came into the intersect region, // not necessarily the bottom row irBottom = zeroY + y + thickness * yIncrement; if (opacity == -1) { // If occluded pixels caused an intersect count to be // skipped, recount it intersectCount = 0; for (j = irTop; j != irBottom; j += yIncrement) { // irTop is not necessarily larger than irBottom and // yIncrement isn't necessarily positive for (i = irLeft; i <= irRight; i++) { if (occluded.contains(TQPoint(i, j))) intersectCount++; } } } else { if (intersectCount < 0) intersectCount = 0; for (i = irLeft; i <= irRight; i++) { if (occluded.contains(TQPoint(i, irBottom))) intersectCount++; } } distance = maxIntersects - intersectCount; opacity = intersectCount / (factor + pow((double)distance, 3.0) * decay); (*(opacityCache))[(zeroY + y) * w + zeroX + x] = opacity; pixel = image.pixel(x, y); pixelRed = tqRed(pixel); pixelGreen = tqGreen(pixel); pixelBlue = tqBlue(pixel); image.setPixel(x, y, tqRgb((int)(pixelRed + (red - pixelRed) * opacity), (int)(pixelGreen + (green - pixelGreen) * opacity), (int)(pixelBlue + (blue - pixelBlue) * opacity))); for (i = irLeft; i <= irRight; i++) { if (occluded.contains(TQPoint(i, irTop))) intersectCount--; } y += yIncrement; } y -= yIncrement; irTop += yIncrement; for (j = irTop; j != irBottom; j += yIncrement) { if (occluded.contains(TQPoint(irLeft, j))) intersectCount--; } irRight++; for (j = irTop; j != irBottom; j += yIncrement) { if (occluded.contains(TQPoint(irRight, j))) intersectCount++; } yIncrement *= -1; if (yIncrement < 0) // Scan Y-axis bottom-up for next X-coordinate iteration maxY = -1; else // Scan Y-axis top-down for next X-coordinate iteration maxY = subH; } subPixmap.convertFromImage(image); bitBlt(&pixmap, zeroX, zeroY, &subPixmap); } } /*! Sets the client window's mapping state. Possible values are WithdrawnState, IconicState, NormalState. */ void Client::setMappingState(int s) { assert( client != None ); assert( !deleting || s == WithdrawnState ); if( mapping_state == s ) return; bool was_unmanaged = ( mapping_state == WithdrawnState ); mapping_state = s; if( mapping_state == WithdrawnState ) { XDeleteProperty( tqt_xdisplay(), window(), tqt_wm_state ); return; } assert( s == NormalState || s == IconicState ); unsigned long data[2]; data[0] = (unsigned long) s; data[1] = (unsigned long) None; XChangeProperty(tqt_xdisplay(), window(), tqt_wm_state, tqt_wm_state, 32, PropModeReplace, (unsigned char *)data, 2); if( was_unmanaged ) // manage() did postpone_geometry_updates = 1, now it's ok to finally set the geometry postponeGeometryUpdates( false ); } /*! Reimplemented to map the managed window in the window wrapper. Proper mapping state should be set before showing the client. */ void Client::rawShow() { if( decoration != NULL ) decoration->widget()->show(); // not really necessary, but let it know the state XMapWindow( tqt_xdisplay(), frame ); if( !isShade()) { XMapWindow( tqt_xdisplay(), wrapper ); XMapWindow( tqt_xdisplay(), client ); } if (options->shadowEnabled(isActive())) drawDelayedShadow(); } /*! Reimplemented to unmap the managed window in the window wrapper. Also informs the workspace. Proper mapping state should be set before hiding the client. */ void Client::rawHide() { // Here it may look like a race condition, as some other client might try to unmap // the window between these two XSelectInput() calls. However, they're supposed to // use XWithdrawWindow(), which also sends a synthetic event to the root window, // which won't be missed, so this shouldn't be a problem. The chance the real UnmapNotify // will be missed is also very minimal, so I don't think it's needed to grab the server // here. removeShadow(); drawIntersectingShadows(); XSelectInput( tqt_xdisplay(), wrapper, ClientWinMask ); // avoid getting UnmapNotify XUnmapWindow( tqt_xdisplay(), frame ); XUnmapWindow( tqt_xdisplay(), wrapper ); XUnmapWindow( tqt_xdisplay(), client ); XSelectInput( tqt_xdisplay(), wrapper, ClientWinMask | SubstructureNotifyMask ); if( decoration != NULL ) decoration->widget()->hide(); // not really necessary, but let it know the state workspace()->clientHidden( this ); } void Client::sendClientMessage(Window w, Atom a, Atom protocol, long data1, long data2, long data3) { 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] = protocol; ev.xclient.data.l[1] = get_tqt_x_time(); ev.xclient.data.l[2] = data1; ev.xclient.data.l[3] = data2; ev.xclient.data.l[4] = data3; mask = 0L; if (w == tqt_xrootwin()) mask = SubstructureRedirectMask; /* magic! */ XSendEvent(tqt_xdisplay(), w, False, mask, &ev); } /* Returns whether the window may be closed (have a close button) */ bool Client::isCloseable() const { if( isModalSystemNotification()) return false; return rules()->checkCloseable( motif_may_close && !isSpecialWindow()); } /*! Closes the window by either sending a delete_window message or using XKill. */ void Client::closeWindow() { if( !isCloseable()) return; // Update user time, because the window may create a confirming dialog. updateUserTime(); if ( Pdeletewindow ) { Notify::raise( Notify::Close ); sendClientMessage( window(), atoms->wm_protocols, atoms->wm_delete_window); pingWindow(); } else { // client will not react on wm_delete_window. We have not choice // but destroy his connection to the XServer. killWindow(); } } /*! Kills the window via XKill */ void Client::killWindow() { kdDebug( 1212 ) << "Client::killWindow():" << caption() << endl; // not sure if we need an Notify::Kill or not.. until then, use // Notify::Close Notify::raise( Notify::Close ); if( isDialog()) Notify::raise( Notify::TransDelete ); if( isNormalWindow()) Notify::raise( Notify::Delete ); killProcess( false ); // always kill this client at the server XKillClient(tqt_xdisplay(), window() ); destroyClient(); } // send a ping to the window using _NET_WM_PING if possible // if it doesn't respond within a reasonable time, it will be // killed void Client::pingWindow() { if( !Pping ) return; // can't ping :( if( options->killPingTimeout == 0 ) return; // turned off if( ping_timer != NULL ) return; // pinging already ping_timer = new TQTimer( this ); connect( ping_timer, TQ_SIGNAL( timeout()), TQ_SLOT( pingTimeout())); ping_timer->start( options->killPingTimeout, true ); ping_timestamp = get_tqt_x_time(); workspace()->sendPingToWindow( window(), ping_timestamp ); } void Client::gotPing( Time timestamp ) { // just plain compare is not good enough because of 64bit and truncating and whatnot if( NET::timestampCompare( timestamp, ping_timestamp ) != 0 ) 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 ); TQCString machine = wmClientMachine( true ); 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" ) { TDEProcess proc; proc << "xon" << machine << "kill" << pid; proc.start( TDEProcess::DontCare ); } else ::kill( pid, SIGTERM ); } else { // SELI TODO handle the window created by handler specially (on top,urgent?) process_killer = new TDEProcess( this ); *process_killer << TDEStandardDirs::findExe( "twin_killer_helper" ) << "--pid" << TQCString().setNum( pid ) << "--hostname" << machine << "--windowname" << caption().utf8() << "--applicationname" << resourceClass() << "--wid" << TQCString().setNum( window()) << "--timestamp" << TQCString().setNum( timestamp ); connect( process_killer, TQ_SIGNAL( processExited( TDEProcess* )), TQ_SLOT( processKillerExited())); if( !process_killer->start( TDEProcess::NotifyOnExit )) { delete process_killer; process_killer = NULL; return; } } } bool Client::isSuspendable() const { bool cansuspend = true; if( skipTaskbar() || skipPager() ) return false; TQCString machine = wmClientMachine( true ); pid_t pid = info->pid(); if( pid <= 0 || machine.isEmpty()) // needed properties missing return false; kdDebug( 1212 ) << "Check suspendable process:" << pid << "(" << machine << ")" << endl; if( machine != "localhost" ) { return false; } else { #ifdef Q_OS_SOLARIS TQFile procStatFile(TQString("/proc/%1/lwp/1/lwpsinfo").arg(pid)); #else /* default */ TQFile procStatFile(TQString("/proc/%1/stat").arg(pid)); #endif if (procStatFile.open(IO_ReadOnly)) { TQByteArray statRaw = procStatFile.readAll(); procStatFile.close(); #ifdef Q_OS_SOLARIS lwpsinfo_t *inf = (lwpsinfo_t *)statRaw.data(); char tbuf[PATH_MAX]; TQString tcomm; TQString state(TQChar(inf->pr_sname)); readlink(TQString("/proc/%1/path/a.out").arg(pid).latin1(), tbuf, sizeof(tbuf)); tcomm = basename(tbuf); #else /* default */ TQString statString(statRaw); TQStringList statFields = TQStringList::split(" ", statString, TRUE); TQString tcomm = statFields[1]; TQString state = statFields[2]; #endif /* default */ if( state != "T" ) { // Make sure no windows of this process are special for ( ClientList::ConstIterator it = workspace()->clients.begin(); it != workspace()->clients.end(); ++it) { Client* nextclient = *it; pid_t nextpid = nextclient->info->pid(); TQCString nextmachine = nextclient->wmClientMachine( true ); if( nextpid > 0 && (!nextmachine.isEmpty())) { if( ( nextmachine == "localhost" ) && ( pid == nextpid ) ) { if( nextclient->skipTaskbar() || nextclient->skipPager() ) cansuspend = false; } } } // Process exception list TQString execname(tcomm); execname.truncate(execname.length()-1); execname = execname.remove(0,1); // FIXME This list should not be hardcoded if( (execname == "kdesktop") || (execname == "kicker") ) return false; else return cansuspend; } else { return false; } } else { return false; } } } bool Client::isResumeable() const { TQCString machine = wmClientMachine( true ); pid_t pid = info->pid(); if( pid <= 0 || machine.isEmpty()) // needed properties missing return false; kdDebug( 1212 ) << "Check resumeable process:" << pid << "(" << machine << ")" << endl; if( machine != "localhost" ) { return false; } else { #ifdef Q_OS_SOLARIS TQFile procStatFile(TQString("/proc/%1/lwp/1/lwpsinfo").arg(pid)); #else /* default */ TQFile procStatFile(TQString("/proc/%1/stat").arg(pid)); #endif if (procStatFile.open(IO_ReadOnly)) { TQByteArray statRaw = procStatFile.readAll(); procStatFile.close(); #ifdef Q_OS_SOLARIS lwpsinfo_t *inf = (lwpsinfo_t *)statRaw.data(); TQString state(TQChar(inf->pr_sname)); #else /* default */ TQString statString(statRaw); TQStringList statFields = TQStringList::split(" ", statString, TRUE); TQString tcomm = statFields[1]; TQString state = statFields[2]; #endif /* default */ if( state == "T" ) { return true; } else { return false; } } else { return false; } } } bool Client::queryUserSuspendedResume() { if (isResumeable()) { if (process_resumer != NULL) { return false; } // FIXME We should display a busy cursor until twin_resumer_helper loads process_resumer = new TDEProcess( this ); *process_resumer << TDEStandardDirs::findExe( "twin_resumer_helper" ) << "--pid" << TQCString().setNum( info->pid() ) << "--hostname" << wmClientMachine( true ) << "--windowname" << caption().utf8() << "--applicationname" << resourceClass() << "--wid" << TQCString().setNum( window()); connect( process_resumer, TQ_SIGNAL( processExited( TDEProcess* )), TQ_SLOT( processResumerExited())); if( !process_resumer->start( TDEProcess::NotifyOnExit )) { delete process_resumer; process_resumer = NULL; return true; } return false; } else { return true; } } void Client::suspendWindow() { TQCString machine = wmClientMachine( true ); pid_t pid = info->pid(); if( pid <= 0 || machine.isEmpty()) // needed properties missing return; kdDebug( 1212 ) << "Suspend process:" << pid << "(" << machine << ")" << endl; if( machine != "localhost" ) { return; } else { for ( ClientList::ConstIterator it = workspace()->clients.begin(); it != workspace()->clients.end(); ++it) { Client* nextclient = *it; pid_t nextpid = nextclient->info->pid(); TQCString nextmachine = nextclient->wmClientMachine( true ); if( nextpid > 0 && (!nextmachine.isEmpty())) { if( ( nextmachine == "localhost" ) && ( pid == nextpid ) ) { TQString newCaption = TQString(readName()).append(" <").append(i18n("Suspended")).append(">"); nextclient->info->setVisibleName(newCaption.utf8()); nextclient->info->setVisibleIconName(newCaption.utf8()); nextclient->minimized_before_suspend = nextclient->isMinimized(); nextclient->minimize(true); } } } ::kill( pid, SIGSTOP ); } } void Client::resumeWindow() { TQCString machine = wmClientMachine( true ); pid_t pid = info->pid(); if( pid <= 0 || machine.isEmpty()) // needed properties missing return; kdDebug( 1212 ) << "Resume process:" << pid << "(" << machine << ")" << endl; if( machine != "localhost" ) { return; } else { ::kill( pid, SIGCONT ); for ( ClientList::ConstIterator it = workspace()->clients.begin(); it != workspace()->clients.end(); ++it) { Client* nextclient = *it; pid_t nextpid = nextclient->info->pid(); TQCString nextmachine = nextclient->wmClientMachine( true ); if( nextpid > 0 && (!nextmachine.isEmpty())) { if( ( nextmachine == "localhost" ) && ( pid == nextpid ) ) { if (!nextclient->minimized_before_suspend) { nextclient->unminimize(true); } nextclient->updateCaption(); } } } } } void Client::processKillerExited() { kdDebug( 1212 ) << "Killer exited" << endl; delete process_killer; process_killer = NULL; } void Client::processResumerExited() { kdDebug( 1212 ) << "Resumer exited" << endl; // 0 means the user clicked Resume; 2 means that the resumer dialog failed to launch somehow if ((process_resumer->exitStatus() == 0) || (process_resumer->exitStatus() == 2)) { resumeWindow(); takeFocus( Allowed ); } delete process_resumer; process_resumer = NULL; } void Client::setSkipTaskbar( bool b, bool from_outside ) { int was_wants_tab_focus = wantsTabFocus(); if( from_outside ) { b = rules()->checkSkipTaskbar( b ); original_skip_taskbar = b; } if ( b == skipTaskbar() ) return; skip_taskbar = b; info->setState( b?NET::SkipTaskbar:0, NET::SkipTaskbar ); updateWindowRules(); if( was_wants_tab_focus != wantsTabFocus()) workspace()->updateFocusChains( this, isActive() ? Workspace::FocusChainMakeFirst : Workspace::FocusChainUpdate ); } void Client::setSkipPager( bool b ) { b = rules()->checkSkipPager( b ); if ( b == skipPager() ) return; skip_pager = b; info->setState( b?NET::SkipPager:0, NET::SkipPager ); updateWindowRules(); } void Client::setModal( bool m ) { // Qt-3.2 can have even modal normal windows :( if( modal == m ) return; modal = m; if( !modal ) return; // changing modality for a mapped window is weird (?) // _NET_WM_STATE_MODAL should possibly rather be _NET_WM_WINDOW_TYPE_MODAL_DIALOG } void Client::setDesktop( int desktop ) { if( desktop != NET::OnAllDesktops ) // do range check desktop = KMAX( 1, KMIN( workspace()->numberOfDesktops(), desktop )); desktop = rules()->checkDesktop( desktop ); if( desk == desktop ) return; int was_desk = desk; desk = desktop; info->setDesktop( desktop ); if(( was_desk == NET::OnAllDesktops ) != ( desktop == NET::OnAllDesktops )) { // onAllDesktops changed if ( isShown( true )) Notify::raise( isOnAllDesktops() ? Notify::OnAllDesktops : Notify::NotOnAllDesktops ); workspace()->updateOnAllDesktopsOfTransients( this ); } if( decoration != NULL ) decoration->desktopChange(); workspace()->updateFocusChains( this, Workspace::FocusChainMakeFirst ); updateVisibility(); updateWindowRules(); } void Client::setOnAllDesktops( bool b ) { if(( b && isOnAllDesktops()) || ( !b && !isOnAllDesktops())) return; if( b ) setDesktop( NET::OnAllDesktops ); else setDesktop( workspace()->currentDesktop()); } bool Client::isOnCurrentDesktop() const { return isOnDesktop( workspace()->currentDesktop()); } int Client::screen() const { if( !options->xineramaEnabled ) return 0; return workspace()->screenNumber( geometry().center()); } bool Client::isOnScreen( int screen ) const { if( !options->xineramaEnabled ) return screen == 0; return workspace()->screenGeometry( screen ).intersects( geometry()); } // performs activation and/or raising of the window void Client::takeActivity( int flags, bool handled, allowed_t ) { if( !handled || !Ptakeactivity ) { if( flags & ActivityFocus ) takeFocus( Allowed ); if( flags & ActivityRaise ) workspace()->raiseClient( this ); return; } #ifndef NDEBUG static Time previous_activity_timestamp; static Client* previous_client; if( previous_activity_timestamp == get_tqt_x_time() && previous_client != this ) { kdDebug( 1212 ) << "Repeated use of the same X timestamp for activity" << endl; kdDebug( 1212 ) << kdBacktrace() << endl; } previous_activity_timestamp = get_tqt_x_time(); previous_client = this; #endif workspace()->sendTakeActivity( this, get_tqt_x_time(), flags ); } // performs the actual focusing of the window using XSetInputFocus and WM_TAKE_FOCUS void Client::takeFocus( allowed_t ) { #ifndef NDEBUG static Time previous_focus_timestamp; static Client* previous_client; if( previous_focus_timestamp == get_tqt_x_time() && previous_client != this ) { kdDebug( 1212 ) << "Repeated use of the same X timestamp for focus" << endl; kdDebug( 1212 ) << kdBacktrace() << endl; } previous_focus_timestamp = get_tqt_x_time(); previous_client = this; #endif if ( rules()->checkAcceptFocus( input )) { XSetInputFocus( tqt_xdisplay(), window(), RevertToPointerRoot, get_tqt_x_time() ); // Work around opacity bug bool activePrev = active; active = true; updateOpacity(); active = activePrev; } if ( Ptakefocus ) { sendClientMessage(window(), atoms->wm_protocols, atoms->wm_take_focus); } workspace()->setShouldGetFocus( this ); } /*! Returns whether the window provides context help or not. If it does, you should show a help menu item or a help button like '?' and call contextHelp() if this is invoked. \sa contextHelp() */ bool Client::providesContextHelp() const { if (isModalSystemNotification()) return false; return Pcontexthelp; } /*! Invokes context help on the window. Only works if the window actually provides context help. \sa providesContextHelp() */ void Client::showContextHelp() { if ( Pcontexthelp ) { sendClientMessage(window(), atoms->wm_protocols, atoms->net_wm_context_help); TQWhatsThis::enterWhatsThisMode(); // SELI? } } /*! Fetches the window's caption (WM_NAME property). It will be stored in the client's caption(). */ void Client::fetchName() { setCaption( readName()); } TQString Client::readName() const { if ( info->name() && info->name()[ 0 ] != '\0' ) return TQString::fromUtf8( info->name() ); else return KWin::readNameProperty( window(), XA_WM_NAME ); } KWIN_COMPARE_PREDICATE( FetchNameInternalPredicate, const Client*, (!cl->isSpecialWindow() || cl->isToolbar()) && cl != value && cl->caption() == value->caption()); void Client::setCaption( const TQString& s, bool force ) { if ( s != cap_normal || force ) { bool reset_name = force; for( unsigned int i = 0; i < s.length(); ++i ) if( !s[ i ].isPrint()) s[ i ] = ' '; cap_normal = s; bool was_suffix = ( !cap_suffix.isEmpty()); TQString machine_suffix; if( wmClientMachine( false ) != "localhost" && !isLocalMachine( wmClientMachine( false ))) machine_suffix = " <@" + wmClientMachine( true ) + ">"; TQString shortcut_suffix = !shortcut().isNull() ? ( " {" + shortcut().toString() + "}" ) : ""; cap_suffix = machine_suffix + shortcut_suffix; if ( ( !isSpecialWindow() || isToolbar()) && workspace()->findClient( FetchNameInternalPredicate( this ))) { int i = 2; do { cap_suffix = machine_suffix + " <" + TQString::number(i) + ">" + shortcut_suffix; i++; } while ( workspace()->findClient( FetchNameInternalPredicate( this ))); info->setVisibleName( caption().utf8() ); reset_name = false; } if(( (was_suffix && cap_suffix.isEmpty()) || reset_name )) // if it was new window, it may have old value still set, if the window is reused { info->setVisibleName( "" ); // remove info->setVisibleIconName( "" ); // remove } else if( !cap_suffix.isEmpty() && !cap_iconic.isEmpty()) // keep the same suffix in iconic name if it's set info->setVisibleIconName( ( cap_iconic + cap_suffix ).utf8() ); if( isManaged() && decoration != NULL ) decoration->captionChange(); } } void Client::updateCaption() { setCaption( cap_normal, true ); } void Client::fetchIconicName() { TQString s; if ( info->iconName() && info->iconName()[ 0 ] != '\0' ) s = TQString::fromUtf8( info->iconName() ); else s = KWin::readNameProperty( window(), XA_WM_ICON_NAME ); if ( s != cap_iconic ) { bool was_set = !cap_iconic.isEmpty(); cap_iconic = s; if( !cap_suffix.isEmpty()) { if( !cap_iconic.isEmpty()) // keep the same suffix in iconic name if it's set info->setVisibleIconName( ( s + cap_suffix ).utf8() ); else if( was_set ) info->setVisibleIconName( "" ); //remove } } } /*!\reimp */ TQString Client::caption( bool full ) const { return full ? cap_normal + cap_suffix : cap_normal; } void Client::getWMHints() { XWMHints *hints = XGetWMHints(tqt_xdisplay(), window() ); input = true; window_group = None; urgency = false; if ( hints ) { if( hints->flags & InputHint ) input = hints->input; if( hints->flags & WindowGroupHint ) window_group = hints->window_group; urgency = ( hints->flags & UrgencyHint ) ? true : false; // true/false needed, it's uint bitfield XFree( (char*)hints ); } checkGroup(); updateUrgency(); updateAllowedActions(); // group affects isMinimizable() } void Client::getMotifHints() { bool mnoborder, mresize, mmove, mminimize, mmaximize, mclose; Motif::readFlags( client, mnoborder, mresize, mmove, mminimize, mmaximize, mclose ); motif_noborder = mnoborder; if( !hasNETSupport()) // NETWM apps should set type and size constraints { motif_may_resize = mresize; // this should be set using minsize==maxsize, but oh well motif_may_move = mmove; } else motif_may_resize = motif_may_move = true; // mminimize; - ignore, bogus - e.g. shading or sending to another desktop is "minimizing" too // mmaximize; - ignore, bogus - maximizing is basically just resizing motif_may_close = mclose; // motif apps like to crash when they set this hint and WM closes them anyway if( isManaged()) updateDecoration( true ); // check if noborder state has changed } void Client::readIcons( Window win, TQPixmap* icon, TQPixmap* 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 = TQPixmap(); } } void Client::getIcons() { // first read icons from the window itself readIcons( window(), &icon_pix, &miniicon_pix ); if( icon_pix.isNull()) { // then try window group icon_pix = group()->icon(); miniicon_pix = group()->miniIcon(); } if( icon_pix.isNull() && isTransient()) { // then mainclients ClientList mainclients = mainClients(); for( ClientList::ConstIterator it = mainclients.begin(); it != mainclients.end() && icon_pix.isNull(); ++it ) { icon_pix = (*it)->icon(); miniicon_pix = (*it)->miniIcon(); } } if( icon_pix.isNull()) { // and if nothing else, load icon from classhint or xapp icon icon_pix = KWin::icon( window(), 32, 32, TRUE, KWin::ClassHint | KWin::XApp ); miniicon_pix = KWin::icon( window(), 16, 16, TRUE, KWin::ClassHint | KWin::XApp ); } if( isManaged() && decoration != NULL ) decoration->iconChange(); } void Client::getWindowProtocols() { Atom *p; int i,n; Pdeletewindow = 0; Ptakefocus = 0; Ptakeactivity = 0; Pcontexthelp = 0; Pping = 0; if (XGetWMProtocols(tqt_xdisplay(), window(), &p, &n)) { for (i = 0; i < n; i++) if (p[i] == atoms->wm_delete_window) Pdeletewindow = 1; else if (p[i] == atoms->wm_take_focus) Ptakefocus = 1; else if (p[i] == atoms->net_wm_take_activity) Ptakeactivity = 1; else if (p[i] == atoms->net_wm_context_help) Pcontexthelp = 1; else if (p[i] == atoms->net_wm_ping) Pping = 1; if (n>0) XFree(p); } } static int nullErrorHandler(Display *, XErrorEvent *) { return 0; } /*! Returns WM_WINDOW_ROLE property for a given window. */ TQCString Client::staticWindowRole(WId w) { return getStringProperty(w, tqt_window_role).lower(); } /*! Returns SM_CLIENT_ID property for a given window. */ TQCString Client::staticSessionId(WId w) { return getStringProperty(w, tqt_sm_client_id); } /*! Returns WM_COMMAND property for a given window. */ TQCString Client::staticWmCommand(WId w) { return getStringProperty(w, XA_WM_COMMAND, ' '); } /*! 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( tqt_xdisplay(), w, atoms->wm_client_leader, 0, 10000, FALSE, XA_WINDOW, &type, &format, &nitems, &extra, &data ); XSetErrorHandler(oldHandler); if (status == Success ) { if (data && nitems > 0) result = *((Window*) data); XFree(data); } return result; } void Client::getWmClientLeader() { wmClientLeaderWin = staticWmClientLeader(window()); } /*! Returns sessionId for this client, taken either from its window or from the leader window. */ TQCString Client::sessionId() { TQCString result = staticSessionId(window()); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin!=window()) result = staticSessionId(wmClientLeaderWin); return result; } /*! Returns command property for this client, taken either from its window or from the leader window. */ TQCString Client::wmCommand() { TQCString result = staticWmCommand(window()); if (result.isEmpty() && wmClientLeaderWin && wmClientLeaderWin!=window()) result = staticWmCommand(wmClientLeaderWin); return result; } void Client::getWmClientMachine() { client_machine = getStringProperty(window(), XA_WM_CLIENT_MACHINE); if( client_machine.isEmpty() && wmClientLeaderWin && wmClientLeaderWin!=window()) client_machine = getStringProperty(wmClientLeaderWin, XA_WM_CLIENT_MACHINE); if( client_machine.isEmpty()) client_machine = "localhost"; } /*! Returns client machine for this client, taken either from its window or from the leader window. */ TQCString Client::wmClientMachine( bool use_localhost ) const { TQCString result = client_machine; if( use_localhost ) { // special name for the local machine (localhost) if( result != "localhost" && isLocalMachine( result )) result = "localhost"; } return result; } /*! Returns client leader window for this client. Returns the client window itself if no leader window is defined. */ Window Client::wmClientLeader() const { if (wmClientLeaderWin) return wmClientLeaderWin; return window(); } bool Client::wantsTabFocus() const { return ( isNormalWindow() || isDialog()) && wantsInput() && !skip_taskbar; } bool Client::wantsInput() const { return rules()->checkAcceptFocus( input || Ptakefocus ); } bool Client::isDesktop() const { return windowType() == NET::Desktop; } bool Client::isDock() const { return windowType() == NET::Dock; } bool Client::isTopMenu() const { return windowType() == NET::TopMenu; } bool Client::isMenu() const { return windowType() == NET::Menu && !isTopMenu(); // because of backwards comp. } bool Client::isToolbar() const { return windowType() == NET::Toolbar; } bool Client::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() || isToolbar(); // TODO } NET::WindowType Client::windowType( bool direct, int supported_types ) const { NET::WindowType wt = info->windowType( supported_types ); if( direct ) return wt; NET::WindowType wt2 = rules()->checkType( wt ); if( wt != wt2 ) { wt = wt2; info->setWindowType( wt ); // force hint change } // hacks here if( wt == NET::Menu ) { // ugly hack to support the times when NET::Menu meant NET::TopMenu // if it's as wide as the screen, not very high and has its upper-left // corner a bit above the screen's upper-left cornet, it's a topmenu if( x() == 0 && y() < 0 && y() > -10 && height() < 100 && abs( width() - workspace()->clientArea( FullArea, this ).width()) < 10 ) wt = NET::TopMenu; } // TODO change this to rule const char* const oo_prefix = "openoffice.org"; // TQCString has no startsWith() // oo_prefix is lowercase, because resourceClass() is forced to be lowercase if( tqstrncmp( resourceClass(), oo_prefix, strlen( oo_prefix )) == 0 && wt == NET::Dialog ) wt = NET::Normal; // see bug #66065 if( wt == NET::Unknown ) // this is more or less suggested in NETWM spec wt = isTransient() ? NET::Dialog : NET::Normal; return wt; } /*! Sets an appropriate cursor shape for the logical mouse position \a m */ void Client::setCursor( Position m ) { if( !isResizable() || isShade()) { m = PositionCenter; } switch ( m ) { case PositionTopLeft: case PositionBottomRight: setCursor( TQt::sizeFDiagCursor ); break; case PositionBottomLeft: case PositionTopRight: setCursor( TQt::sizeBDiagCursor ); break; case PositionTop: case PositionBottom: setCursor( TQt::sizeVerCursor ); break; case PositionLeft: case PositionRight: setCursor( TQt::sizeHorCursor ); break; default: if( buttonDown && isMovable()) setCursor( TQt::sizeAllCursor ); else setCursor( TQt::arrowCursor ); break; } } // TODO mit nejake checkCursor(), ktere se zavola v manage() a pri vecech, kdy by se kurzor mohl zmenit? // TRANSLATION: TODO: have a checkCursor() function, which is called both in manage() and in cases where the cursor might change void Client::setCursor( const TQCursor& c ) { if( c.handle() == cursor.handle()) return; cursor = c; if( decoration != NULL ) decoration->widget()->setCursor( cursor ); XDefineCursor( tqt_xdisplay(), frameId(), cursor.handle()); } Client::Position Client::mousePosition( const TQPoint& p ) const { if( decoration != NULL ) return decoration->mousePosition( p ); return PositionCenter; } void Client::updateAllowedActions( bool force ) { if( !isManaged() && !force ) return; unsigned long old_allowed_actions = allowed_actions; allowed_actions = 0; if( isMovable()) allowed_actions |= NET::ActionMove; if( isResizable()) allowed_actions |= NET::ActionResize; if( isMinimizable()) allowed_actions |= NET::ActionMinimize; if( isShadeable()) allowed_actions |= NET::ActionShade; // sticky state not supported if( isMaximizable()) allowed_actions |= NET::ActionMax; if( userCanSetFullScreen()) allowed_actions |= NET::ActionFullScreen; allowed_actions |= NET::ActionChangeDesktop; // always (pagers shouldn't show Docks etc.) if( isCloseable()) allowed_actions |= NET::ActionClose; if( old_allowed_actions == allowed_actions ) return; // TODO this could be delayed and compressed - it's only for pagers etc. anyway info->setAllowedActions( allowed_actions ); // TODO this should also tell the decoration, so that it can update the buttons } void Client::autoRaise() { workspace()->raiseClient( this ); cancelAutoRaise(); } void Client::cancelAutoRaise() { delete autoRaiseTimer; autoRaiseTimer = 0; } void Client::setOpacity(bool translucent, uint opacity) { if (isDesktop()) return; // xcompmgr does not like non solid desktops and the user could set it accidently by mouse scrolling // tqWarning("setting opacity for %d",tqt_xdisplay()); //rule out activated translulcency with 100% opacity if (!translucent || opacity == 0xFFFFFFFF) { opacity_ = 0xFFFFFFFF; XDeleteProperty (tqt_xdisplay(), frameId(), atoms->net_wm_window_opacity); XDeleteProperty (tqt_xdisplay(), window(), atoms->net_wm_window_opacity); // ??? frameId() is necessary for visible changes, window() is the winId() that would be set by apps - we set both to be sure the app knows what's currently displayd } else{ if(opacity == opacity_) return; opacity_ = opacity; long data = opacity; // 32bit XChangeProperty needs long XChangeProperty(tqt_xdisplay(), frameId(), atoms->net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &data, 1L); XChangeProperty(tqt_xdisplay(), window(), atoms->net_wm_window_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &data, 1L); } } void Client::setShadowSize(uint shadowSize) { // ignoring all individual settings - if we control a window, we control it's shadow // TODO somehow handle individual settings for docks (besides custom sizes) long data = shadowSize; XChangeProperty(tqt_xdisplay(), frameId(), atoms->net_wm_window_shadow, XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &data, 1L); } void Client::updateOpacity() // extra syncscreen flag allows to avoid double syncs when active state changes (as it will usually change for two windows) { if (!(isNormalWindow() || isDialog() || isUtility() )|| custom_opacity) return; if (isActive()) { if( ruleOpacityActive() ) setOpacity(rule_opacity_active < 0xFFFFFFFF, rule_opacity_active); else setOpacity(options->translucentActiveWindows, options->activeWindowOpacity); if (isBMP()) // beep-media-player, only undecorated windows (gtk2 xmms, xmms doesn't work with compmgr at all - s.e.p. :P ) { ClientList tmpGroupMembers = group()->members(); ClientList activeGroupMembers; activeGroupMembers.append(this); tmpGroupMembers.remove(this); ClientList::Iterator it = tmpGroupMembers.begin(); while (it != tmpGroupMembers.end()) // search for next attached and not activated client and repeat if found { if ((*it) != this && (*it)->isBMP()) // potential "to activate" client found { // tqWarning("client found"); if ((*it)->touches(this)) // first test, if the new client touches the just activated one { // tqWarning("found client touches me"); if( ruleOpacityActive() ) (*it)->setOpacity(rule_opacity_active < 0xFFFFFFFF, rule_opacity_active); else (*it)->setOpacity(options->translucentActiveWindows, options->activeWindowOpacity); // tqWarning("activated, search restarted (1)"); (*it)->setShadowSize(options->activeWindowShadowSize); activeGroupMembers.append(*it); tmpGroupMembers.remove(it); it = tmpGroupMembers.begin(); // restart, search next client continue; } else { // pot. client does not touch c, so we have to search if it touches some other activated client bool found = false; for( ClientList::ConstIterator it2 = activeGroupMembers.begin(); it2 != activeGroupMembers.end(); it2++ ) { if ((*it2) != this && (*it2) != (*it) && (*it)->touches(*it2)) { // tqWarning("found client touches other active client"); if( ruleOpacityActive() ) (*it)->setOpacity(rule_opacity_active < 0xFFFFFFFF, rule_opacity_active); else (*it)->setOpacity(options->translucentActiveWindows, options->activeWindowOpacity); (*it)->setShadowSize(options->activeWindowShadowSize); activeGroupMembers.append(*it); tmpGroupMembers.remove(it); it = tmpGroupMembers.begin(); // reset potential client search found = true; // tqWarning("activated, search restarted (2)"); break; // skip this loop } } if (found) continue; } } it++; } } else if (isNormalWindow()) // activate dependend minor windows as well { for( ClientList::ConstIterator it = group()->members().begin(); it != group()->members().end(); it++ ) if ((*it)->isDialog() || (*it)->isUtility()) { if( (*it)->ruleOpacityActive() ) (*it)->setOpacity((*it)->ruleOpacityActive() < 0xFFFFFFFF, (*it)->ruleOpacityActive()); else (*it)->setOpacity(options->translucentActiveWindows, options->activeWindowOpacity); } } } else { if( ruleOpacityInactive() ) setOpacity(rule_opacity_inactive < 0xFFFFFFFF, rule_opacity_inactive); else setOpacity(options->translucentInactiveWindows && !(keepAbove() && options->keepAboveAsActive), options->inactiveWindowOpacity); // deactivate dependend minor windows as well if (isBMP()) // beep-media-player, only undecorated windows (gtk2 xmms, xmms doesn't work with compmgr at all - s.e.p. :P ) { ClientList tmpGroupMembers = group()->members(); ClientList inactiveGroupMembers; inactiveGroupMembers.append(this); tmpGroupMembers.remove(this); ClientList::Iterator it = tmpGroupMembers.begin(); while ( it != tmpGroupMembers.end() ) // search for next attached and not activated client and repeat if found { if ((*it) != this && (*it)->isBMP()) // potential "to activate" client found { // tqWarning("client found"); if ((*it)->touches(this)) // first test, if the new client touches the just activated one { // tqWarning("found client touches me"); if( (*it)->ruleOpacityInactive() ) (*it)->setOpacity((*it)->ruleOpacityInactive() < 0xFFFFFFFF, (*it)->ruleOpacityInactive()); else (*it)->setOpacity(options->translucentInactiveWindows && !((*it)->keepAbove() && options->keepAboveAsActive), options->inactiveWindowOpacity); (*it)->setShadowSize(options->inactiveWindowShadowSize); // tqWarning("deactivated, search restarted (1)"); inactiveGroupMembers.append(*it); tmpGroupMembers.remove(it); it = tmpGroupMembers.begin(); // restart, search next client continue; } else // pot. client does not touch c, so we have to search if it touches some other activated client { bool found = false; for( ClientList::ConstIterator it2 = inactiveGroupMembers.begin(); it2 != inactiveGroupMembers.end(); it2++ ) { if ((*it2) != this && (*it2) != (*it) && (*it)->touches(*it2)) { // tqWarning("found client touches other inactive client"); if( (*it)->ruleOpacityInactive() ) (*it)->setOpacity((*it)->ruleOpacityInactive() < 0xFFFFFFFF, (*it)->ruleOpacityInactive()); else (*it)->setOpacity(options->translucentInactiveWindows && !((*it)->keepAbove() && options->keepAboveAsActive), options->inactiveWindowOpacity); (*it)->setShadowSize(options->inactiveWindowShadowSize); // tqWarning("deactivated, search restarted (2)"); inactiveGroupMembers.append(*it); tmpGroupMembers.remove(it); it = tmpGroupMembers.begin(); // reset potential client search found = true; break; // skip this loop } } if (found) continue; } } it++; } } else if (isNormalWindow()) { for( ClientList::ConstIterator it = group()->members().begin(); it != group()->members().end(); it++ ) if ((*it)->isUtility()) //don't deactivate dialogs... { if( (*it)->ruleOpacityInactive() ) (*it)->setOpacity((*it)->ruleOpacityInactive() < 0xFFFFFFFF, (*it)->ruleOpacityInactive()); else (*it)->setOpacity(options->translucentInactiveWindows && !((*it)->keepAbove() && options->keepAboveAsActive), options->inactiveWindowOpacity); } } } } void Client::updateShadowSize() // extra syncscreen flag allows to avoid double syncs when active state changes (as it will usually change for two windows) { if (!(isNormalWindow() || isDialog() || isUtility() )) return; if (isActive()) setShadowSize(options->activeWindowShadowSize); else setShadowSize(options->inactiveWindowShadowSize); } uint Client::ruleOpacityInactive() { return rule_opacity_inactive;// != 0 ; } uint Client::ruleOpacityActive() { return rule_opacity_active;// != 0; } bool Client::getWindowOpacity() //query translucency settings from X, returns true if window opacity is set { unsigned char *data = 0; Atom actual; int format, result; unsigned long n, left; result = XGetWindowProperty(tqt_xdisplay(), window(), atoms->net_wm_window_opacity, 0L, 1L, False, XA_CARDINAL, &actual, &format, &n, &left, /*(unsigned char **)*/ &data); if (result == Success && data && format == 32 ) { opacity_ = *reinterpret_cast< long* >( data ); custom_opacity = true; // setOpacity(opacity_ < 0xFFFFFFFF, opacity_); XFree ((char*)data); return TRUE; } return FALSE; } void Client::setCustomOpacityFlag(bool custom) { custom_opacity = custom; } uint Client::opacity() { return opacity_; } int Client::opacityPercentage() { return int(100*((double)opacity_/0xffffffff)); } bool Client::touches(const Client* c) // checks if this client borders c, needed to test beep media player window state { if (y() == c->y() + c->height()) // this bottom to c return TRUE; if (y() + height() == c->y()) // this top to c return TRUE; if (x() == c->x() + c->width()) // this right to c return TRUE; if (x() + width() == c->x()) // this left to c return TRUE; return FALSE; } void Client::setDecoHashProperty(uint topHeight, uint rightWidth, uint bottomHeight, uint leftWidth) { long data = (topHeight < 255 ? topHeight : 255) << 24 | (rightWidth < 255 ? rightWidth : 255) << 16 | (bottomHeight < 255 ? bottomHeight : 255) << 8 | (leftWidth < 255 ? leftWidth : 255); XChangeProperty(tqt_xdisplay(), frameId(), atoms->net_wm_window_decohash, XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &data, 1L); } void Client::unsetDecoHashProperty() { XDeleteProperty( tqt_xdisplay(), frameId(), atoms->net_wm_window_decohash); } #ifndef NDEBUG kdbgstream& operator<<( kdbgstream& stream, const Client* cl ) { if( cl == NULL ) return stream << "\'NULL_CLIENT\'"; return stream << "\'ID:" << cl->window() << ";WMCLASS:" << cl->resourceClass() << ":" << cl->resourceName() << ";Caption:" << cl->caption() << "\'"; } kdbgstream& operator<<( kdbgstream& stream, const ClientList& list ) { stream << "LIST:("; bool first = true; for( ClientList::ConstIterator it = list.begin(); it != list.end(); ++it ) { if( !first ) stream << ":"; first = false; stream << *it; } stream << ")"; return stream; } kdbgstream& operator<<( kdbgstream& stream, const ConstClientList& list ) { stream << "LIST:("; bool first = true; for( ConstClientList::ConstIterator it = list.begin(); it != list.end(); ++it ) { if( !first ) stream << ":"; first = false; stream << *it; } stream << ")"; return stream; } #endif TQPixmap * twin_get_menu_pix_hack() { static TQPixmap p; if ( p.isNull() ) p = SmallIcon( "bx2" ); return &p; } } // namespace #include "client.moc"