/*****************************************************************
 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>

#ifdef Q_OS_SOLARIS
#include <procfs.h>
#include <libgen.h>
#endif /* SunOS */

#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 )),
        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_( Opacity::Opaque ),
        demandAttentionKNotifyTimer( NULL ),
        activeMaximizing(false),
        activeTiled(false)
// 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 );
        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)
    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<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, 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<TQRgb> 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)
{
    TQRgb pixel;
    double opacity;
    int red, green, blue, pixelRed, pixelGreen, pixelBlue;
    int subW, subH, w, 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();

    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<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;
    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() );
        }
    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 == Opacity::Opaque) && !custom_opacity)
        { // Note: if it is custom_opacity we want to keep the properties in case of WM restart
        opacity_ = Opacity::Opaque;
        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);
    }

uint Client::defaultOpacity()
    {
    return defaultOpacity(isActive() || (keepAbove() && options->keepAboveAsActive));
    }

uint Client::defaultOpacity(bool active)
    {
    if (active)
        {
        if( ruleOpacityActive() )
            return rule_opacity_active;
        else
            return options->translucentActiveWindows ? options->activeWindowOpacity : Opacity::Opaque;
        }
    else
        {
        if( ruleOpacityInactive() )
            return rule_opacity_inactive;
        else
            return options->translucentInactiveWindows ? options->inactiveWindowOpacity : Opacity::Opaque;
        }
    }

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;
    uint opacity = defaultOpacity();
    setOpacity(true, opacity);

    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 groupMembers;
        groupMembers.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");
                    (*it)->setOpacity(true, opacity);
//                         tqWarning("(de)activated, search restarted (1)");
                    (*it)->setShadowSize(options->activeWindowShadowSize);
                    groupMembers.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 = groupMembers.begin(); it2 != groupMembers.end(); it2++ )
                        {
                        if ((*it2) != this && (*it2) != (*it) && (*it)->touches(*it2))
                            {
//                                 tqWarning("found client touches other active client");
                            (*it)->setOpacity(true, opacity);
                            (*it)->setShadowSize(isActive() ? options->activeWindowShadowSize : options->inactiveWindowShadowSize);
                            groupMembers.append(*it);
                            tmpGroupMembers.remove(it);
                            it = tmpGroupMembers.begin(); // reset potential client search
                            found = true;
//                                 tqWarning("(de)activated, search restarted (2)");
                            break; // skip this loop
                            }
                        }
                    if (found) continue;
                    }
                }
                it++;
            }
        }
    else if (isNormalWindow())
    // activate/deactivate dependend minor windows as well
        {
        for( ClientList::ConstIterator it = group()->members().begin(); it != group()->members().end(); it++ )
            if ((*it)->isUtility() || ((*it)->isDialog() && isActive() )) // note: don't deactivate dialogs...
                (*it)->setOpacity(true, opacity);
        }
    }

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 );
        // Don't set custom_opacity flag during initialization if the opacity looks like what it
        // supposed to be for the given type of window. As in a such case it is likely set by us
        // and the WM is just restarting
        if ( !(Workspace::self()->initializing()
               && (   opacity_ == defaultOpacity(/*active*/ false)
                   || opacity_ == defaultOpacity(/*active*/ true) ) ) )
            {
            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 opacity_ / ( 0xffffffff / 100 );
    }

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;
    }

#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"
