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