You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3115 lines
102 KiB
3115 lines
102 KiB
/*****************************************************************
|
|
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 )),
|
|
move_faked_activity( false ),
|
|
move_resize_grab_window( None ),
|
|
transient_for( NULL ),
|
|
transient_for_id( None ),
|
|
original_transient_for_id( None ),
|
|
in_group( NULL ),
|
|
window_group( None ),
|
|
in_layer( UnknownLayer ),
|
|
ping_timer( NULL ),
|
|
process_killer( NULL ),
|
|
process_resumer( NULL ),
|
|
user_time( CurrentTime ), // not known yet
|
|
allowed_actions( 0 ),
|
|
postpone_geometry_updates( 0 ),
|
|
pending_geometry_update( false ),
|
|
shade_geometry_change( false ),
|
|
border_left( 0 ),
|
|
border_right( 0 ),
|
|
border_top( 0 ),
|
|
border_bottom( 0 ),
|
|
opacity_( 0 ),
|
|
demandAttentionKNotifyTimer( NULL ),
|
|
activeMaximizing(false),
|
|
activeTiled(false)
|
|
// 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 && 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<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_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
|
|
{
|
|
#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, 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 && 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"
|