/***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org> Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org> You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ #include "client.h" #include <math.h> #include <tqapplication.h> #include <tqpainter.h> #include <tqdatetime.h> #include <tqimage.h> #include <tqfile.h> #include <kprocess.h> #include <unistd.h> #include <kstandarddirs.h> #include <tqwhatsthis.h> #include <twin.h> #include <kiconloader.h> #include <tdelocale.h> #include <stdlib.h> #include "bridge.h" #include "group.h" #include "workspace.h" #include "atoms.h" #include "notifications.h" #include "rules.h" #include <X11/extensions/shape.h> // 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<ShadowRegion> 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 )), 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 ) // SELI do all as initialization { autoRaiseTimer = 0; shadeHoverTimer = 0; shadowDelayTimer = new TQTimer(this); opacityCache = &activeOpacityCache; shadowAfterClient = NULL; shadowWidget = NULL; shadowMe = true; connect(shadowDelayTimer, TQT_SIGNAL(timeout()), TQT_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 != None && 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, QABS( 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, QABS( 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::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<Client> reshadowClients; TQValueList<Client *> reshadowClients; TQValueListIterator<ShadowRegion> it; TQValueListIterator<Client *> 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<Client *> reshadowClients; ClientList stacking_order; ClientList::ConstIterator it; TQValueListIterator<ShadowRegion> it2; TQValueListIterator<Client *> 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, TQT_SIGNAL(shadowDrawn()), TQT_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, TQT_SIGNAL(shadowDrawn()), this, TQT_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<QRgb> 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<TQRect> exposedRects; TQMemArray<TQRect>::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<ShadowRegion>::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<TQRect> occludedRects; TQMemArray<TQRect>::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) { QRgb pixel; double opacity; int red, green, blue, pixelRed, pixelGreen, pixelBlue; int subW, subH, w, h, x, y, zeroX, zeroY; TQImage image; TQMemArray<TQRect>::Iterator it, itEnd; TQMemArray<TQRect> 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(); h = pixmap.height(); 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) { register int distance, intersectCount, i, j, x, y; QRgb pixel; double decay, factor, opacity; int red, green, blue, pixelRed, pixelGreen, pixelBlue; int halfMaxIntersects, lineIntersects, maxIntersects, maxY; int irBottom, irLeft, irRight, irTop, yIncrement; int subW, subH, w, h, zeroX, zeroY; TQImage image; TQMemArray<TQRect>::Iterator it, itEnd; TQMemArray<TQRect> 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; halfMaxIntersects = maxIntersects / 2; 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_QT_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, TQT_SIGNAL( timeout()), TQT_SLOT( pingTimeout())); ping_timer->start( options->killPingTimeout, true ); ping_timestamp = GET_QT_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, TQT_SIGNAL( processExited( TDEProcess* )), TQT_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 { TQFile procStatFile(TQString("/proc/%1/stat").arg(pid)); if (procStatFile.open(IO_ReadOnly)) { TQByteArray statRaw = procStatFile.readAll(); procStatFile.close(); TQString statString(statRaw); TQStringList statFields = TQStringList::split(" ", statString, TRUE); TQString tcomm = statFields[1]; TQString state = statFields[2]; 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 { TQFile procStatFile(TQString("/proc/%1/stat").arg(pid)); if (procStatFile.open(IO_ReadOnly)) { TQByteArray statRaw = procStatFile.readAll(); procStatFile.close(); TQString statString(statRaw); TQStringList statFields = TQStringList::split(" ", statString, TRUE); TQString tcomm = statFields[1]; TQString state = statFields[2]; 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, TQT_SIGNAL( processExited( TDEProcess* )), TQT_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_QT_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_QT_X_TIME(); previous_client = this; #endif workspace()->sendTakeActivity( this, GET_QT_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_QT_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_QT_X_TIME(); previous_client = this; #endif if ( rules()->checkAcceptFocus( input )) { XSetInputFocus( tqt_xdisplay(), window(), RevertToPointerRoot, GET_QT_X_TIME() ); } 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( tqsizeFDiagCursor ); break; case PositionBottomLeft: case PositionTopRight: setCursor( tqsizeBDiagCursor ); break; case PositionTop: case PositionBottom: setCursor( tqsizeVerCursor ); break; case PositionLeft: case PositionRight: setCursor( tqsizeHorCursor ); break; default: if( buttonDown && isMovable()) setCursor( tqsizeAllCursor ); else setCursor( tqarrowCursor ); 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 != None && 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"