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.
3075 lines
101 KiB
3075 lines
101 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.
|
|
******************************************************************/
|
|
|
|
//#define QT_CLEAN_NAMESPACE
|
|
|
|
#include "workspace.h"
|
|
|
|
#include <kapplication.h>
|
|
#include <kstartupinfo.h>
|
|
#include <fixx11h.h>
|
|
#include <kconfig.h>
|
|
#include <kglobal.h>
|
|
#include <tqpopupmenu.h>
|
|
#include <klocale.h>
|
|
#include <tqregexp.h>
|
|
#include <tqpainter.h>
|
|
#include <tqbitmap.h>
|
|
#include <tqclipboard.h>
|
|
#include <kmenubar.h>
|
|
#include <kprocess.h>
|
|
#include <kglobalaccel.h>
|
|
#include <dcopclient.h>
|
|
#include <kipc.h>
|
|
|
|
#include "plugins.h"
|
|
#include "client.h"
|
|
#include "popupinfo.h"
|
|
#include "tabbox.h"
|
|
#include "atoms.h"
|
|
#include "placement.h"
|
|
#include "notifications.h"
|
|
#include "group.h"
|
|
#include "rules.h"
|
|
|
|
#include <X11/XKBlib.h>
|
|
#include <X11/extensions/shape.h>
|
|
#include <X11/keysym.h>
|
|
#include <X11/keysymdef.h>
|
|
#include <X11/cursorfont.h>
|
|
|
|
#include <pwd.h>
|
|
|
|
namespace KWinInternal
|
|
{
|
|
|
|
extern int screen_number;
|
|
|
|
Workspace *Workspace::_self = 0;
|
|
|
|
KProcess* kompmgr = 0;
|
|
KSelectionOwner* kompmgr_selection;
|
|
|
|
bool allowKompmgrRestart = TRUE;
|
|
|
|
bool supportsCompMgr()
|
|
{
|
|
int i;
|
|
|
|
bool damageExt = XQueryExtension(tqt_xdisplay(), "DAMAGE", &i, &i, &i);
|
|
bool compositeExt = XQueryExtension(tqt_xdisplay(), "Composite", &i, &i, &i);
|
|
bool xfixesExt = XQueryExtension(tqt_xdisplay(), "XFIXES", &i, &i, &i);
|
|
|
|
return damageExt && compositeExt && xfixesExt;
|
|
}
|
|
|
|
// Rikkus: This class is too complex. It needs splitting further.
|
|
// It's a nightmare to understand, especially with so few comments :(
|
|
|
|
// Matthias: Feel free to ask me questions about it. Feel free to add
|
|
// comments. I disagree that further splittings makes it easier. 2500
|
|
// lines are not too much. It's the task that is complex, not the
|
|
// code.
|
|
Workspace::Workspace( bool restore )
|
|
: DCOPObject ("KWinInterface"),
|
|
TQObject (0, "workspace"),
|
|
current_desktop (0),
|
|
number_of_desktops(0),
|
|
active_screen (0),
|
|
active_popup( NULL ),
|
|
active_popup_client( NULL ),
|
|
desktop_widget (0),
|
|
temporaryRulesMessages( "_KDE_NET_WM_TEMPORARY_RULES", NULL, false ),
|
|
rules_updates_disabled( false ),
|
|
active_client (0),
|
|
last_active_client (0),
|
|
next_active_client (0),
|
|
most_recently_raised (0),
|
|
movingClient(0),
|
|
pending_take_activity ( NULL ),
|
|
delayfocus_client (0),
|
|
showing_desktop( false ),
|
|
block_showing_desktop( 0 ),
|
|
was_user_interaction (false),
|
|
session_saving (false),
|
|
control_grab (false),
|
|
tab_grab (false),
|
|
mouse_emulation (false),
|
|
block_focus (0),
|
|
tab_box (0),
|
|
popupinfo (0),
|
|
popup (0),
|
|
advanced_popup (0),
|
|
desk_popup (0),
|
|
desk_popup_index (0),
|
|
keys (0),
|
|
client_keys ( NULL ),
|
|
client_keys_dialog ( NULL ),
|
|
client_keys_client ( NULL ),
|
|
disable_shortcuts_keys ( NULL ),
|
|
global_shortcuts_disabled( false ),
|
|
global_shortcuts_disabled_for_client( false ),
|
|
root (0),
|
|
workspaceInit (true),
|
|
startup(0), electric_have_borders(false),
|
|
electric_current_border(0),
|
|
electric_top_border(None),
|
|
electric_bottom_border(None),
|
|
electric_left_border(None),
|
|
electric_right_border(None),
|
|
layoutOrientation(Qt::Vertical),
|
|
layoutX(-1),
|
|
layoutY(2),
|
|
workarea(NULL),
|
|
screenarea(NULL),
|
|
managing_topmenus( false ),
|
|
topmenu_selection( NULL ),
|
|
topmenu_watcher( NULL ),
|
|
topmenu_height( 0 ),
|
|
topmenu_space( NULL ),
|
|
set_active_client_recursion( 0 ),
|
|
block_stacking_updates( 0 ),
|
|
forced_global_mouse_grab( false )
|
|
{
|
|
_self = this;
|
|
mgr = new PluginMgr;
|
|
root = tqt_xrootwin();
|
|
default_colormap = DefaultColormap(tqt_xdisplay(), tqt_xscreen() );
|
|
installed_colormap = default_colormap;
|
|
session.setAutoDelete( TRUE );
|
|
|
|
connect( &temporaryRulesMessages, TQT_SIGNAL( gotMessage( const TQString& )),
|
|
this, TQT_SLOT( gotTemporaryRulesMessage( const TQString& )));
|
|
connect( &rulesUpdatedTimer, TQT_SIGNAL( timeout()), this, TQT_SLOT( writeWindowRules()));
|
|
|
|
updateXTime(); // needed for proper initialization of user_time in Client ctor
|
|
|
|
delayFocusTimer = 0;
|
|
|
|
electric_time_first = GET_QT_X_TIME();
|
|
electric_time_last = GET_QT_X_TIME();
|
|
|
|
if ( restore )
|
|
loadSessionInfo();
|
|
|
|
loadWindowRules();
|
|
|
|
(void) TQApplication::desktop(); // trigger creation of desktop widget
|
|
|
|
desktop_widget =
|
|
new TQWidget(
|
|
0,
|
|
"desktop_widget",
|
|
(WFlags)(TQt::WType_Desktop | TQt::WPaintUnclipped)
|
|
);
|
|
|
|
kapp->setGlobalMouseTracking( true ); // so that this doesn't mess eventmask on root window later
|
|
// call this before XSelectInput() on the root window
|
|
startup = new KStartupInfo(
|
|
KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this );
|
|
|
|
// select windowmanager privileges
|
|
XSelectInput(tqt_xdisplay(), root,
|
|
KeyPressMask |
|
|
PropertyChangeMask |
|
|
ColormapChangeMask |
|
|
SubstructureRedirectMask |
|
|
SubstructureNotifyMask |
|
|
FocusChangeMask // for NotifyDetailNone
|
|
);
|
|
|
|
Shape::init();
|
|
|
|
// compatibility
|
|
long data = 1;
|
|
|
|
XChangeProperty(
|
|
tqt_xdisplay(),
|
|
tqt_xrootwin(),
|
|
atoms->twin_running,
|
|
atoms->twin_running,
|
|
32,
|
|
PropModeAppend,
|
|
(unsigned char*) &data,
|
|
1
|
|
);
|
|
|
|
client_keys = new KGlobalAccel( this );
|
|
initShortcuts();
|
|
tab_box = new TabBox( this );
|
|
popupinfo = new PopupInfo( this );
|
|
|
|
init();
|
|
|
|
#if (TQT_VERSION-0 >= 0x030200) // XRANDR support
|
|
connect( kapp->desktop(), TQT_SIGNAL( resized( int )), TQT_SLOT( desktopResized()));
|
|
#endif
|
|
|
|
if (!supportsCompMgr())
|
|
options->useTranslucency = false;
|
|
|
|
// start kompmgr - i wanted to put this into main.cpp, but that would prevent dcop support, as long as Application was no dcop_object
|
|
if (options->useTranslucency)
|
|
{
|
|
kompmgr = new KProcess;
|
|
connect(kompmgr, TQT_SIGNAL(receivedStderr(KProcess*, char*, int)), TQT_SLOT(handleKompmgrOutput(KProcess*, char*, int)));
|
|
*kompmgr << "kompmgr";
|
|
startKompmgr();
|
|
}
|
|
else
|
|
{
|
|
// If kompmgr is already running, send it SIGTERM
|
|
// Attempt to load the kompmgr pid file
|
|
const char *home;
|
|
struct passwd *p;
|
|
p = getpwuid(getuid());
|
|
if (p)
|
|
home = p->pw_dir;
|
|
else
|
|
home = getenv("HOME");
|
|
char *filename;
|
|
const char *configfile = "/.kompmgr.pid";
|
|
int n = strlen(home)+strlen(configfile)+1;
|
|
filename = (char*)malloc(n*sizeof(char));
|
|
memset(filename,0,n);
|
|
strcat(filename, home);
|
|
strcat(filename, configfile);
|
|
|
|
printf("[twin-workspace] reading '%s' as kompmgr pidfile\n\n", filename);
|
|
|
|
// Now that we did all that by way of introduction...read the file!
|
|
FILE *pFile;
|
|
char buffer[255];
|
|
pFile = fopen(filename, "r");
|
|
int kompmgrpid = 0;
|
|
if (pFile)
|
|
{
|
|
// obtain file size
|
|
fseek (pFile , 0 , SEEK_END);
|
|
unsigned long lSize = ftell (pFile);
|
|
if (lSize > 254)
|
|
lSize = 254;
|
|
rewind (pFile);
|
|
size_t result = fread (buffer, 1, lSize, pFile);
|
|
fclose(pFile);
|
|
kompmgrpid = atoi(buffer);
|
|
}
|
|
|
|
free(filename);
|
|
filename = NULL;
|
|
|
|
if (kompmgrpid)
|
|
{
|
|
kill(kompmgrpid, SIGTERM);
|
|
}
|
|
else
|
|
{
|
|
stopKompmgr();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Workspace::init()
|
|
{
|
|
checkElectricBorders();
|
|
|
|
// not used yet
|
|
// topDock = 0L;
|
|
// maximizedWindowCounter = 0;
|
|
|
|
supportWindow = new TQWidget;
|
|
XLowerWindow( tqt_xdisplay(), supportWindow->winId()); // see usage in layers.cpp
|
|
|
|
XSetWindowAttributes attr;
|
|
attr.override_redirect = 1;
|
|
null_focus_window = XCreateWindow( tqt_xdisplay(), tqt_xrootwin(), -1,-1, 1, 1, 0, CopyFromParent,
|
|
InputOnly, CopyFromParent, CWOverrideRedirect, &attr );
|
|
XMapWindow(tqt_xdisplay(), null_focus_window);
|
|
|
|
unsigned long protocols[ 5 ] =
|
|
{
|
|
NET::Supported |
|
|
NET::SupportingWMCheck |
|
|
NET::ClientList |
|
|
NET::ClientListStacking |
|
|
NET::DesktopGeometry |
|
|
NET::NumberOfDesktops |
|
|
NET::CurrentDesktop |
|
|
NET::ActiveWindow |
|
|
NET::WorkArea |
|
|
NET::CloseWindow |
|
|
NET::DesktopNames |
|
|
NET::KDESystemTrayWindows |
|
|
NET::WMName |
|
|
NET::WMVisibleName |
|
|
NET::WMDesktop |
|
|
NET::WMWindowType |
|
|
NET::WMState |
|
|
NET::WMStrut |
|
|
NET::WMIconGeometry |
|
|
NET::WMIcon |
|
|
NET::WMPid |
|
|
NET::WMMoveResize |
|
|
NET::WMKDESystemTrayWinFor |
|
|
NET::WMFrameExtents |
|
|
NET::WMPing
|
|
,
|
|
NET::NormalMask |
|
|
NET::DesktopMask |
|
|
NET::DockMask |
|
|
NET::ToolbarMask |
|
|
NET::MenuMask |
|
|
NET::DialogMask |
|
|
NET::OverrideMask |
|
|
NET::TopMenuMask |
|
|
NET::UtilityMask |
|
|
NET::SplashMask |
|
|
0
|
|
,
|
|
NET::Modal |
|
|
// NET::Sticky | // large desktops not supported (and probably never will be)
|
|
NET::MaxVert |
|
|
NET::MaxHoriz |
|
|
NET::Shaded |
|
|
NET::SkipTaskbar |
|
|
NET::KeepAbove |
|
|
// NET::StaysOnTop | the same like KeepAbove
|
|
NET::SkipPager |
|
|
NET::Hidden |
|
|
NET::FullScreen |
|
|
NET::KeepBelow |
|
|
NET::DemandsAttention |
|
|
0
|
|
,
|
|
NET::WM2UserTime |
|
|
NET::WM2StartupId |
|
|
NET::WM2AllowedActions |
|
|
NET::WM2RestackWindow |
|
|
NET::WM2MoveResizeWindow |
|
|
NET::WM2ExtendedStrut |
|
|
NET::WM2KDETemporaryRules |
|
|
NET::WM2ShowingDesktop |
|
|
NET::WM2FullPlacement |
|
|
NET::WM2DesktopLayout |
|
|
0
|
|
,
|
|
NET::ActionMove |
|
|
NET::ActionResize |
|
|
NET::ActionMinimize |
|
|
NET::ActionShade |
|
|
// NET::ActionStick | // Sticky state is not supported
|
|
NET::ActionMaxVert |
|
|
NET::ActionMaxHoriz |
|
|
NET::ActionFullScreen |
|
|
NET::ActionChangeDesktop |
|
|
NET::ActionClose |
|
|
0
|
|
,
|
|
};
|
|
|
|
rootInfo = new RootInfo( this, tqt_xdisplay(), supportWindow->winId(), "KWin",
|
|
protocols, 5, tqt_xscreen() );
|
|
|
|
loadDesktopSettings();
|
|
updateDesktopLayout();
|
|
// extra NETRootInfo instance in Client mode is needed to get the values of the properties
|
|
NETRootInfo client_info( tqt_xdisplay(), NET::ActiveWindow | NET::CurrentDesktop );
|
|
int initial_desktop;
|
|
if( !kapp->isSessionRestored())
|
|
initial_desktop = client_info.currentDesktop();
|
|
else
|
|
{
|
|
KConfigGroupSaver saver( kapp->sessionConfig(), "Session" );
|
|
initial_desktop = kapp->sessionConfig()->readNumEntry( "desktop", 1 );
|
|
}
|
|
if( !setCurrentDesktop( initial_desktop ))
|
|
setCurrentDesktop( 1 );
|
|
|
|
// now we know how many desktops we'll, thus, we initialise the positioning object
|
|
initPositioning = new Placement(this);
|
|
|
|
connect(&reconfigureTimer, TQT_SIGNAL(timeout()), this,
|
|
TQT_SLOT(slotReconfigure()));
|
|
connect( &updateToolWindowsTimer, TQT_SIGNAL( timeout()), this, TQT_SLOT( slotUpdateToolWindows()));
|
|
|
|
connect(kapp, TQT_SIGNAL(appearanceChanged()), this,
|
|
TQT_SLOT(slotReconfigure()));
|
|
connect(kapp, TQT_SIGNAL(settingsChanged(int)), this,
|
|
TQT_SLOT(slotSettingsChanged(int)));
|
|
connect(kapp, TQT_SIGNAL( kipcMessage( int, int )), this, TQT_SLOT( kipcMessage( int, int )));
|
|
|
|
active_client = NULL;
|
|
rootInfo->setActiveWindow( None );
|
|
focusToNull();
|
|
if( !kapp->isSessionRestored())
|
|
++block_focus; // because it will be set below
|
|
|
|
char nm[ 100 ];
|
|
sprintf( nm, "_KDE_TOPMENU_OWNER_S%d", DefaultScreen( tqt_xdisplay()));
|
|
Atom topmenu_atom = XInternAtom( tqt_xdisplay(), nm, False );
|
|
topmenu_selection = new KSelectionOwner( topmenu_atom );
|
|
topmenu_watcher = new KSelectionWatcher( topmenu_atom );
|
|
// TODO grabXServer(); - where exactly put this? topmenu selection claiming down belong must be before
|
|
|
|
{ // begin updates blocker block
|
|
StackingUpdatesBlocker blocker( this );
|
|
|
|
if( options->topMenuEnabled() && topmenu_selection->claim( false ))
|
|
setupTopMenuHandling(); // this can call updateStackingOrder()
|
|
else
|
|
lostTopMenuSelection();
|
|
|
|
unsigned int i, nwins;
|
|
Window root_return, parent_return, *wins;
|
|
XQueryTree(tqt_xdisplay(), root, &root_return, &parent_return, &wins, &nwins);
|
|
for (i = 0; i < nwins; i++)
|
|
{
|
|
XWindowAttributes attr;
|
|
XGetWindowAttributes(tqt_xdisplay(), wins[i], &attr);
|
|
if (attr.override_redirect )
|
|
continue;
|
|
if( topmenu_space && topmenu_space->winId() == wins[ i ] )
|
|
continue;
|
|
if (attr.map_state != IsUnmapped)
|
|
{
|
|
if ( addSystemTrayWin( wins[i] ) )
|
|
continue;
|
|
Client* c = createClient( wins[i], true );
|
|
if ( c != NULL && root != tqt_xrootwin() )
|
|
{ // TODO what is this?
|
|
// TODO may use TQWidget:.create
|
|
XReparentWindow( tqt_xdisplay(), c->frameId(), root, 0, 0 );
|
|
c->move(0,0);
|
|
}
|
|
}
|
|
}
|
|
if ( wins )
|
|
XFree((void *) wins);
|
|
// propagate clients, will really happen at the end of the updates blocker block
|
|
updateStackingOrder( true );
|
|
|
|
updateClientArea();
|
|
raiseElectricBorders();
|
|
|
|
// NETWM spec says we have to set it to (0,0) if we don't support it
|
|
NETPoint* viewports = new NETPoint[ number_of_desktops ];
|
|
rootInfo->setDesktopViewport( number_of_desktops, *viewports );
|
|
delete[] viewports;
|
|
TQRect geom = TQApplication::desktop()->geometry();
|
|
NETSize desktop_geometry;
|
|
desktop_geometry.width = geom.width();
|
|
desktop_geometry.height = geom.height();
|
|
rootInfo->setDesktopGeometry( -1, desktop_geometry );
|
|
setShowingDesktop( false );
|
|
|
|
} // end updates blocker block
|
|
|
|
Client* new_active_client = NULL;
|
|
if( !kapp->isSessionRestored())
|
|
{
|
|
--block_focus;
|
|
new_active_client = findClient( WindowMatchPredicate( client_info.activeWindow()));
|
|
}
|
|
if( new_active_client == NULL
|
|
&& activeClient() == NULL && should_get_focus.count() == 0 ) // no client activated in manage()
|
|
{
|
|
if( new_active_client == NULL )
|
|
new_active_client = topClientOnDesktop( currentDesktop());
|
|
if( new_active_client == NULL && !desktops.isEmpty() )
|
|
new_active_client = findDesktop( true, currentDesktop());
|
|
}
|
|
if( new_active_client != NULL )
|
|
activateClient( new_active_client );
|
|
// SELI TODO this won't work with unreasonable focus policies,
|
|
// and maybe in rare cases also if the selected client doesn't
|
|
// want focus
|
|
workspaceInit = false;
|
|
// TODO ungrabXServer()
|
|
}
|
|
|
|
Workspace::~Workspace()
|
|
{
|
|
if (kompmgr)
|
|
delete kompmgr;
|
|
blockStackingUpdates( true );
|
|
// TODO grabXServer();
|
|
// use stacking_order, so that twin --replace keeps stacking order
|
|
for( ClientList::ConstIterator it = stacking_order.begin();
|
|
it != stacking_order.end();
|
|
++it )
|
|
{
|
|
// only release the window
|
|
(*it)->releaseWindow( true );
|
|
// No removeClient() is called, it does more than just removing.
|
|
// However, remove from some lists to e.g. prevent performTransiencyCheck()
|
|
// from crashing.
|
|
clients.remove( *it );
|
|
desktops.remove( *it );
|
|
}
|
|
delete desktop_widget;
|
|
delete tab_box;
|
|
delete popupinfo;
|
|
delete popup;
|
|
if ( root == tqt_xrootwin() )
|
|
XDeleteProperty(tqt_xdisplay(), tqt_xrootwin(), atoms->twin_running);
|
|
|
|
writeWindowRules();
|
|
KGlobal::config()->sync();
|
|
|
|
delete rootInfo;
|
|
delete supportWindow;
|
|
delete mgr;
|
|
delete[] workarea;
|
|
delete[] screenarea;
|
|
delete startup;
|
|
delete initPositioning;
|
|
delete topmenu_watcher;
|
|
delete topmenu_selection;
|
|
delete topmenu_space;
|
|
delete client_keys_dialog;
|
|
while( !rules.isEmpty())
|
|
{
|
|
delete rules.front();
|
|
rules.pop_front();
|
|
}
|
|
XDestroyWindow( tqt_xdisplay(), null_focus_window );
|
|
// TODO ungrabXServer();
|
|
_self = 0;
|
|
}
|
|
|
|
Client* Workspace::createClient( Window w, bool is_mapped )
|
|
{
|
|
StackingUpdatesBlocker blocker( this );
|
|
Client* c = new Client( this );
|
|
if( !c->manage( w, is_mapped ))
|
|
{
|
|
Client::deleteClient( c, Allowed );
|
|
return NULL;
|
|
}
|
|
addClient( c, Allowed );
|
|
return c;
|
|
}
|
|
|
|
void Workspace::addClient( Client* c, allowed_t )
|
|
{
|
|
// waited with trans settings until window figured out if active or not ;)
|
|
// tqWarning("%s", (const char*)(c->resourceClass()));
|
|
c->setBMP(c->resourceName() == "beep-media-player" || c->decorationId() == None);
|
|
// first check if the window has it's own opinion of it's translucency ;)
|
|
c->getWindowOpacity();
|
|
if (c->isDock())
|
|
{
|
|
// if (c->x() == 0 && c->y() == 0 && c->width() > c->height()) topDock = c;
|
|
if (!c->hasCustomOpacity()) // this xould be done slightly more efficient, but we want to support the topDock in future
|
|
{
|
|
c->setShadowSize(options->dockShadowSize);
|
|
c->setOpacity(options->translucentDocks, options->dockOpacity);
|
|
}
|
|
}
|
|
|
|
if (c->isMenu() || c->isTopMenu())
|
|
{
|
|
c->setShadowSize(options->menuShadowSize);
|
|
}
|
|
//------------------------------------------------
|
|
Group* grp = findGroup( c->window());
|
|
if( grp != NULL )
|
|
grp->gotLeader( c );
|
|
|
|
if ( c->isDesktop() )
|
|
{
|
|
desktops.append( c );
|
|
if( active_client == NULL && should_get_focus.isEmpty() && c->isOnCurrentDesktop())
|
|
requestFocus( c ); // CHECKME? make sure desktop is active after startup if there's no other window active
|
|
}
|
|
else
|
|
{
|
|
updateFocusChains( c, FocusChainUpdate ); // add to focus chain if not already there
|
|
clients.append( c );
|
|
}
|
|
if( !unconstrained_stacking_order.contains( c ))
|
|
unconstrained_stacking_order.append( c );
|
|
if( !stacking_order.contains( c )) // it'll be updated later, and updateToolWindows() requires
|
|
stacking_order.append( c ); // c to be in stacking_order
|
|
if( c->isTopMenu())
|
|
addTopMenu( c );
|
|
updateClientArea(); // this cannot be in manage(), because the client got added only now
|
|
updateClientLayer( c );
|
|
if( c->isDesktop())
|
|
{
|
|
raiseClient( c );
|
|
// if there's no active client, make this desktop the active one
|
|
if( activeClient() == NULL && should_get_focus.count() == 0 )
|
|
activateClient( findDesktop( true, currentDesktop()));
|
|
}
|
|
c->checkActiveModal();
|
|
checkTransients( c->window()); // SELI does this really belong here?
|
|
updateStackingOrder( true ); // propagate new client
|
|
if( c->isUtility() || c->isMenu() || c->isToolbar())
|
|
updateToolWindows( true );
|
|
checkNonExistentClients();
|
|
}
|
|
|
|
/*
|
|
Destroys the client \a c
|
|
*/
|
|
void Workspace::removeClient( Client* c, allowed_t )
|
|
{
|
|
if (c == active_popup_client)
|
|
closeActivePopup();
|
|
|
|
if( client_keys_client == c )
|
|
setupWindowShortcutDone( false );
|
|
if( !c->shortcut().isNull())
|
|
c->setShortcut( TQString::null ); // remove from client_keys
|
|
|
|
if( c->isDialog())
|
|
Notify::raise( Notify::TransDelete );
|
|
if( c->isNormalWindow())
|
|
Notify::raise( Notify::Delete );
|
|
|
|
Q_ASSERT( clients.contains( c ) || desktops.contains( c ));
|
|
clients.remove( c );
|
|
desktops.remove( c );
|
|
unconstrained_stacking_order.remove( c );
|
|
stacking_order.remove( c );
|
|
for( int i = 1;
|
|
i <= numberOfDesktops();
|
|
++i )
|
|
focus_chain[ i ].remove( c );
|
|
global_focus_chain.remove( c );
|
|
attention_chain.remove( c );
|
|
showing_desktop_clients.remove( c );
|
|
if( c->isTopMenu())
|
|
removeTopMenu( c );
|
|
Group* group = findGroup( c->window());
|
|
if( group != NULL )
|
|
group->lostLeader();
|
|
|
|
if ( c == most_recently_raised )
|
|
most_recently_raised = 0;
|
|
should_get_focus.remove( c );
|
|
Q_ASSERT( c != active_client );
|
|
if ( c == last_active_client )
|
|
last_active_client = 0;
|
|
if( c == pending_take_activity )
|
|
pending_take_activity = NULL;
|
|
if( c == delayfocus_client )
|
|
cancelDelayFocus();
|
|
|
|
updateStackingOrder( true );
|
|
|
|
if (tab_grab)
|
|
tab_box->repaint();
|
|
|
|
updateClientArea();
|
|
}
|
|
|
|
void Workspace::updateFocusChains( Client* c, FocusChainChange change )
|
|
{
|
|
if( !c->wantsTabFocus()) // doesn't want tab focus, remove
|
|
{
|
|
for( int i=1;
|
|
i<= numberOfDesktops();
|
|
++i )
|
|
focus_chain[i].remove(c);
|
|
global_focus_chain.remove( c );
|
|
return;
|
|
}
|
|
if(c->desktop() == NET::OnAllDesktops)
|
|
{ //now on all desktops, add it to focus_chains it is not already in
|
|
for( int i=1; i<= numberOfDesktops(); i++)
|
|
{ // making first/last works only on current desktop, don't affect all desktops
|
|
if( i == currentDesktop()
|
|
&& ( change == FocusChainMakeFirst || change == FocusChainMakeLast ))
|
|
{
|
|
focus_chain[ i ].remove( c );
|
|
if( change == FocusChainMakeFirst )
|
|
focus_chain[ i ].append( c );
|
|
else
|
|
focus_chain[ i ].prepend( c );
|
|
}
|
|
else if( !focus_chain[ i ].contains( c ))
|
|
{ // add it after the active one
|
|
if( active_client != NULL && active_client != c
|
|
&& !focus_chain[ i ].isEmpty() && focus_chain[ i ].last() == active_client )
|
|
focus_chain[ i ].insert( focus_chain[ i ].fromLast(), c );
|
|
else
|
|
focus_chain[ i ].append( c ); // otherwise add as the first one
|
|
}
|
|
}
|
|
}
|
|
else //now only on desktop, remove it anywhere else
|
|
{
|
|
for( int i=1; i<= numberOfDesktops(); i++)
|
|
{
|
|
if( i == c->desktop())
|
|
{
|
|
if( change == FocusChainMakeFirst )
|
|
{
|
|
focus_chain[ i ].remove( c );
|
|
focus_chain[ i ].append( c );
|
|
}
|
|
else if( change == FocusChainMakeLast )
|
|
{
|
|
focus_chain[ i ].remove( c );
|
|
focus_chain[ i ].prepend( c );
|
|
}
|
|
else if( !focus_chain[ i ].contains( c ))
|
|
{
|
|
if( active_client != NULL && active_client != c
|
|
&& !focus_chain[ i ].isEmpty() && focus_chain[ i ].last() == active_client )
|
|
focus_chain[ i ].insert( focus_chain[ i ].fromLast(), c );
|
|
else
|
|
focus_chain[ i ].append( c ); // otherwise add as the first one
|
|
}
|
|
}
|
|
else
|
|
focus_chain[ i ].remove( c );
|
|
}
|
|
}
|
|
if( change == FocusChainMakeFirst )
|
|
{
|
|
global_focus_chain.remove( c );
|
|
global_focus_chain.append( c );
|
|
}
|
|
else if( change == FocusChainMakeLast )
|
|
{
|
|
global_focus_chain.remove( c );
|
|
global_focus_chain.prepend( c );
|
|
}
|
|
else if( !global_focus_chain.contains( c ))
|
|
{
|
|
if( active_client != NULL && active_client != c
|
|
&& !global_focus_chain.isEmpty() && global_focus_chain.last() == active_client )
|
|
global_focus_chain.insert( global_focus_chain.fromLast(), c );
|
|
else
|
|
global_focus_chain.append( c ); // otherwise add as the first one
|
|
}
|
|
}
|
|
|
|
void Workspace::updateOverlappingShadows(unsigned long window)
|
|
{
|
|
Client *client;
|
|
|
|
if ((client = findClient(WindowMatchPredicate((WId)window))))
|
|
// Redraw overlapping shadows without waiting for the specified window
|
|
// to redraw its own shadow
|
|
client->drawOverlappingShadows(false);
|
|
}
|
|
|
|
void Workspace::setShadowed(unsigned long window, bool shadowed)
|
|
{
|
|
Client *client;
|
|
|
|
if ((client = findClient(WindowMatchPredicate((WId)window))))
|
|
client->setShadowed(shadowed);
|
|
}
|
|
|
|
void Workspace::updateCurrentTopMenu()
|
|
{
|
|
if( !managingTopMenus())
|
|
return;
|
|
// toplevel menubar handling
|
|
Client* menubar = 0;
|
|
bool block_desktop_menubar = false;
|
|
if( active_client )
|
|
{
|
|
// show the new menu bar first...
|
|
Client* menu_client = active_client;
|
|
for(;;)
|
|
{
|
|
if( menu_client->isFullScreen())
|
|
block_desktop_menubar = true;
|
|
for( ClientList::ConstIterator it = menu_client->transients().begin();
|
|
it != menu_client->transients().end();
|
|
++it )
|
|
if( (*it)->isTopMenu())
|
|
{
|
|
menubar = *it;
|
|
break;
|
|
}
|
|
if( menubar != NULL || !menu_client->isTransient())
|
|
break;
|
|
if( menu_client->isModal() || menu_client->transientFor() == NULL )
|
|
break; // don't use mainwindow's menu if this is modal or group transient
|
|
menu_client = menu_client->transientFor();
|
|
}
|
|
if( !menubar )
|
|
{ // try to find any topmenu from the application (#72113)
|
|
for( ClientList::ConstIterator it = active_client->group()->members().begin();
|
|
it != active_client->group()->members().end();
|
|
++it )
|
|
if( (*it)->isTopMenu())
|
|
{
|
|
menubar = *it;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if( !menubar && !block_desktop_menubar && options->desktopTopMenu())
|
|
{
|
|
// Find the menubar of the desktop
|
|
Client* desktop = findDesktop( true, currentDesktop());
|
|
if( desktop != NULL )
|
|
{
|
|
for( ClientList::ConstIterator it = desktop->transients().begin();
|
|
it != desktop->transients().end();
|
|
++it )
|
|
if( (*it)->isTopMenu())
|
|
{
|
|
menubar = *it;
|
|
break;
|
|
}
|
|
}
|
|
// TODO to be cleaned app with window grouping
|
|
// Without qt-copy patch #0009, the topmenu and desktop are not in the same group,
|
|
// thus the topmenu is not transient for it :-/.
|
|
if( menubar == NULL )
|
|
{
|
|
for( ClientList::ConstIterator it = topmenus.begin();
|
|
it != topmenus.end();
|
|
++it )
|
|
if( (*it)->wasOriginallyGroupTransient()) // kdesktop's topmenu has WM_TRANSIENT_FOR
|
|
{ // set pointing to the root window
|
|
menubar = *it; // to recognize it here
|
|
break; // Also, with the xroot hack in kdesktop,
|
|
} // there's no NET::Desktop window to be transient for
|
|
}
|
|
}
|
|
|
|
// kdDebug() << "CURRENT TOPMENU:" << menubar << ":" << active_client << endl;
|
|
if ( menubar )
|
|
{
|
|
if( active_client && !menubar->isOnDesktop( active_client->desktop()))
|
|
menubar->setDesktop( active_client->desktop());
|
|
menubar->hideClient( false );
|
|
topmenu_space->hide();
|
|
// make it appear like it's been raised manually - it's in the Dock layer anyway,
|
|
// and not raising it could mess up stacking order of topmenus within one application,
|
|
// and thus break raising of mainclients in raiseClient()
|
|
unconstrained_stacking_order.remove( menubar );
|
|
unconstrained_stacking_order.append( menubar );
|
|
}
|
|
else if( !block_desktop_menubar )
|
|
{ // no topmenu active - show the space window, so that there's not empty space
|
|
topmenu_space->show();
|
|
}
|
|
|
|
// ... then hide the other ones. Avoids flickers.
|
|
for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it)
|
|
{
|
|
if( (*it)->isTopMenu() && (*it) != menubar )
|
|
(*it)->hideClient( true );
|
|
}
|
|
}
|
|
|
|
|
|
void Workspace::updateToolWindows( bool also_hide )
|
|
{
|
|
// TODO what if Client's transiency/group changes? should this be called too? (I'm paranoid, am I not?)
|
|
if( !options->hideUtilityWindowsForInactive )
|
|
{
|
|
for( ClientList::ConstIterator it = clients.begin();
|
|
it != clients.end();
|
|
++it )
|
|
(*it)->hideClient( false );
|
|
return;
|
|
}
|
|
const Group* group = NULL;
|
|
const Client* client = active_client;
|
|
// Go up in transiency hiearchy, if the top is found, only tool transients for the top mainwindow
|
|
// will be shown; if a group transient is group, all tools in the group will be shown
|
|
while( client != NULL )
|
|
{
|
|
if( !client->isTransient())
|
|
break;
|
|
if( client->groupTransient())
|
|
{
|
|
group = client->group();
|
|
break;
|
|
}
|
|
client = client->transientFor();
|
|
}
|
|
// use stacking order only to reduce flicker, it doesn't matter if block_stacking_updates == 0,
|
|
// i.e. if it's not up to date
|
|
|
|
// SELI but maybe it should - what if a new client has been added that's not in stacking order yet?
|
|
ClientList to_show, to_hide;
|
|
for( ClientList::ConstIterator it = stacking_order.begin();
|
|
it != stacking_order.end();
|
|
++it )
|
|
{
|
|
if( (*it)->isUtility() || (*it)->isMenu() || (*it)->isToolbar())
|
|
{
|
|
bool show = true;
|
|
if( !(*it)->isTransient())
|
|
{
|
|
if( (*it)->group()->members().count() == 1 ) // has its own group, keep always visible
|
|
show = true;
|
|
else if( client != NULL && (*it)->group() == client->group())
|
|
show = true;
|
|
else
|
|
show = false;
|
|
}
|
|
else
|
|
{
|
|
if( group != NULL && (*it)->group() == group )
|
|
show = true;
|
|
else if( client != NULL && client->hasTransient( (*it), true ))
|
|
show = true;
|
|
else
|
|
show = false;
|
|
}
|
|
if( !show && also_hide )
|
|
{
|
|
const ClientList mainclients = (*it)->mainClients();
|
|
// don't hide utility windows which are standalone(?) or
|
|
// have e.g. kicker as mainwindow
|
|
if( mainclients.isEmpty())
|
|
show = true;
|
|
for( ClientList::ConstIterator it2 = mainclients.begin();
|
|
it2 != mainclients.end();
|
|
++it2 )
|
|
{
|
|
if( (*it2)->isSpecialWindow())
|
|
show = true;
|
|
}
|
|
if( !show )
|
|
to_hide.append( *it );
|
|
}
|
|
if( show )
|
|
to_show.append( *it );
|
|
}
|
|
} // first show new ones, then hide
|
|
for( ClientList::ConstIterator it = to_show.fromLast();
|
|
it != to_show.end();
|
|
--it ) // from topmost
|
|
// TODO since this is in stacking order, the order of taskbar entries changes :(
|
|
(*it)->hideClient( false );
|
|
if( also_hide )
|
|
{
|
|
for( ClientList::ConstIterator it = to_hide.begin();
|
|
it != to_hide.end();
|
|
++it ) // from bottommost
|
|
(*it)->hideClient( true );
|
|
updateToolWindowsTimer.stop();
|
|
}
|
|
else // setActiveClient() is after called with NULL client, quickly followed
|
|
{ // by setting a new client, which would result in flickering
|
|
updateToolWindowsTimer.start( 50, true );
|
|
}
|
|
}
|
|
|
|
void Workspace::slotUpdateToolWindows()
|
|
{
|
|
updateToolWindows( true );
|
|
}
|
|
|
|
/*!
|
|
Updates the current colormap according to the currently active client
|
|
*/
|
|
void Workspace::updateColormap()
|
|
{
|
|
Colormap cmap = default_colormap;
|
|
if ( activeClient() && activeClient()->colormap() != None )
|
|
cmap = activeClient()->colormap();
|
|
if ( cmap != installed_colormap )
|
|
{
|
|
XInstallColormap(tqt_xdisplay(), cmap );
|
|
installed_colormap = cmap;
|
|
}
|
|
}
|
|
|
|
void Workspace::reconfigure()
|
|
{
|
|
reconfigureTimer.start(200, true);
|
|
}
|
|
|
|
|
|
void Workspace::slotSettingsChanged(int category)
|
|
{
|
|
kdDebug(1212) << "Workspace::slotSettingsChanged()" << endl;
|
|
if( category == (int) KApplication::SETTINGS_SHORTCUTS )
|
|
readShortcuts();
|
|
}
|
|
|
|
/*!
|
|
Reread settings
|
|
*/
|
|
KWIN_PROCEDURE( CheckBorderSizesProcedure, cl->checkBorderSizes() );
|
|
|
|
void Workspace::slotReconfigure()
|
|
{
|
|
kdDebug(1212) << "Workspace::slotReconfigure()" << endl;
|
|
reconfigureTimer.stop();
|
|
|
|
KGlobal::config()->reparseConfiguration();
|
|
unsigned long changed = options->updateSettings();
|
|
tab_box->reconfigure();
|
|
popupinfo->reconfigure();
|
|
initPositioning->reinitCascading( 0 );
|
|
readShortcuts();
|
|
forEachClient( CheckIgnoreFocusStealingProcedure());
|
|
updateToolWindows( true );
|
|
|
|
if( mgr->reset( changed ))
|
|
{ // decorations need to be recreated
|
|
#if 0 // This actually seems to make things worse now
|
|
TQWidget curtain;
|
|
curtain.setBackgroundMode( NoBackground );
|
|
curtain.setGeometry( TQApplication::desktop()->geometry() );
|
|
curtain.show();
|
|
#endif
|
|
for( ClientList::ConstIterator it = clients.begin();
|
|
it != clients.end();
|
|
++it )
|
|
{
|
|
(*it)->updateDecoration( true, true );
|
|
}
|
|
mgr->destroyPreviousPlugin();
|
|
}
|
|
else
|
|
{
|
|
forEachClient( CheckBorderSizesProcedure());
|
|
}
|
|
|
|
checkElectricBorders();
|
|
|
|
if( options->topMenuEnabled() && !managingTopMenus())
|
|
{
|
|
if( topmenu_selection->claim( false ))
|
|
setupTopMenuHandling();
|
|
else
|
|
lostTopMenuSelection();
|
|
}
|
|
else if( !options->topMenuEnabled() && managingTopMenus())
|
|
{
|
|
topmenu_selection->release();
|
|
lostTopMenuSelection();
|
|
}
|
|
topmenu_height = 0; // invalidate used menu height
|
|
if( managingTopMenus())
|
|
{
|
|
updateTopMenuGeometry();
|
|
updateCurrentTopMenu();
|
|
}
|
|
|
|
loadWindowRules();
|
|
for( ClientList::Iterator it = clients.begin();
|
|
it != clients.end();
|
|
++it )
|
|
{
|
|
(*it)->setupWindowRules( true );
|
|
(*it)->applyWindowRules();
|
|
discardUsedWindowRules( *it, false );
|
|
}
|
|
|
|
if (options->resetKompmgr) // need restart
|
|
{
|
|
bool tmp = options->useTranslucency;
|
|
|
|
// If kompmgr is already running, sending SIGUSR2 will force a reload of its settings
|
|
// Attempt to load the kompmgr pid file
|
|
const char *home;
|
|
struct passwd *p;
|
|
p = getpwuid(getuid());
|
|
if (p)
|
|
home = p->pw_dir;
|
|
else
|
|
home = getenv("HOME");
|
|
char *filename;
|
|
const char *configfile = "/.kompmgr.pid";
|
|
int n = strlen(home)+strlen(configfile)+1;
|
|
filename = (char*)malloc(n*sizeof(char));
|
|
memset(filename,0,n);
|
|
strcat(filename, home);
|
|
strcat(filename, configfile);
|
|
|
|
printf("[twin-workspace] reading '%s' as kompmgr pidfile\n\n", filename);
|
|
|
|
// Now that we did all that by way of introduction...read the file!
|
|
FILE *pFile;
|
|
char buffer[255];
|
|
pFile = fopen(filename, "r");
|
|
int kompmgrpid = 0;
|
|
if (pFile)
|
|
{
|
|
// obtain file size
|
|
fseek (pFile , 0 , SEEK_END);
|
|
unsigned long lSize = ftell (pFile);
|
|
if (lSize > 254)
|
|
lSize = 254;
|
|
rewind (pFile);
|
|
size_t result = fread (buffer, 1, lSize, pFile);
|
|
fclose(pFile);
|
|
kompmgrpid = atoi(buffer);
|
|
}
|
|
|
|
free(filename);
|
|
filename = NULL;
|
|
|
|
if (tmp)
|
|
{
|
|
if (kompmgrpid)
|
|
{
|
|
kill(kompmgrpid, SIGUSR2);
|
|
}
|
|
else
|
|
{
|
|
stopKompmgr();
|
|
TQTimer::singleShot( 200, this, TQT_SLOT(startKompmgr()) ); // wait some time to ensure system's ready for restart
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (kompmgrpid)
|
|
{
|
|
kill(kompmgrpid, SIGTERM);
|
|
}
|
|
else
|
|
{
|
|
stopKompmgr();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Workspace::loadDesktopSettings()
|
|
{
|
|
KConfig* c = KGlobal::config();
|
|
TQCString groupname;
|
|
if (screen_number == 0)
|
|
groupname = "Desktops";
|
|
else
|
|
groupname.sprintf("Desktops-screen-%d", screen_number);
|
|
KConfigGroupSaver saver(c,groupname);
|
|
|
|
int n = c->readNumEntry("Number", 4);
|
|
number_of_desktops = n;
|
|
delete workarea;
|
|
workarea = new TQRect[ n + 1 ];
|
|
delete screenarea;
|
|
screenarea = NULL;
|
|
rootInfo->setNumberOfDesktops( number_of_desktops );
|
|
desktop_focus_chain.resize( n );
|
|
// make it +1, so that it can be accessed as [1..numberofdesktops]
|
|
focus_chain.resize( n + 1 );
|
|
for(int i = 1; i <= n; i++)
|
|
{
|
|
TQString s = c->readEntry(TQString("Name_%1").arg(i),
|
|
i18n("Desktop %1").arg(i));
|
|
rootInfo->setDesktopName( i, s.utf8().data() );
|
|
desktop_focus_chain[i-1] = i;
|
|
}
|
|
}
|
|
|
|
void Workspace::saveDesktopSettings()
|
|
{
|
|
KConfig* c = KGlobal::config();
|
|
TQCString groupname;
|
|
if (screen_number == 0)
|
|
groupname = "Desktops";
|
|
else
|
|
groupname.sprintf("Desktops-screen-%d", screen_number);
|
|
KConfigGroupSaver saver(c,groupname);
|
|
|
|
c->writeEntry("Number", number_of_desktops );
|
|
for(int i = 1; i <= number_of_desktops; i++)
|
|
{
|
|
TQString s = desktopName( i );
|
|
TQString defaultvalue = i18n("Desktop %1").arg(i);
|
|
if ( s.isEmpty() )
|
|
{
|
|
s = defaultvalue;
|
|
rootInfo->setDesktopName( i, s.utf8().data() );
|
|
}
|
|
|
|
if (s != defaultvalue)
|
|
{
|
|
c->writeEntry( TQString("Name_%1").arg(i), s );
|
|
}
|
|
else
|
|
{
|
|
TQString currentvalue = c->readEntry(TQString("Name_%1").arg(i));
|
|
if (currentvalue != defaultvalue)
|
|
c->writeEntry( TQString("Name_%1").arg(i), "" );
|
|
}
|
|
}
|
|
}
|
|
|
|
TQStringList Workspace::configModules(bool controlCenter)
|
|
{
|
|
TQStringList args;
|
|
args << "tde-twindecoration.desktop";
|
|
if (controlCenter)
|
|
args << "tde-twinoptions.desktop";
|
|
else if (kapp->authorizeControlModule("tde-twinoptions.desktop"))
|
|
args << "twinactions" << "twinfocus" << "twinmoving" << "twinadvanced" << "twinrules" << "twintranslucency";
|
|
return args;
|
|
}
|
|
|
|
void Workspace::configureWM()
|
|
{
|
|
KApplication::tdeinitExec( "kcmshell", configModules(false) );
|
|
}
|
|
|
|
/*!
|
|
avoids managing a window with title \a title
|
|
*/
|
|
void Workspace::doNotManage( TQString title )
|
|
{
|
|
doNotManageList.append( title );
|
|
}
|
|
|
|
/*!
|
|
Hack for java applets
|
|
*/
|
|
bool Workspace::isNotManaged( const TQString& title )
|
|
{
|
|
for ( TQStringList::Iterator it = doNotManageList.begin(); it != doNotManageList.end(); ++it )
|
|
{
|
|
TQRegExp r( (*it) );
|
|
if (r.search(title) != -1)
|
|
{
|
|
doNotManageList.remove( it );
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*!
|
|
Refreshes all the client windows
|
|
*/
|
|
void Workspace::refresh()
|
|
{
|
|
TQWidget w;
|
|
w.setGeometry( TQApplication::desktop()->geometry() );
|
|
w.show();
|
|
w.hide();
|
|
TQApplication::flushX();
|
|
}
|
|
|
|
/*!
|
|
During virt. desktop switching, desktop areas covered by windows that are
|
|
going to be hidden are first obscured by new windows with no background
|
|
( i.e. transparent ) placed right below the windows. These invisible windows
|
|
are removed after the switch is complete.
|
|
Reduces desktop ( wallpaper ) repaints during desktop switching
|
|
*/
|
|
class ObscuringWindows
|
|
{
|
|
public:
|
|
~ObscuringWindows();
|
|
void create( Client* c );
|
|
private:
|
|
TQValueList<Window> obscuring_windows;
|
|
static TQValueList<Window>* cached;
|
|
static unsigned int max_cache_size;
|
|
};
|
|
|
|
TQValueList<Window>* ObscuringWindows::cached = 0;
|
|
unsigned int ObscuringWindows::max_cache_size = 0;
|
|
|
|
void ObscuringWindows::create( Client* c )
|
|
{
|
|
if( cached == 0 )
|
|
cached = new TQValueList<Window>;
|
|
Window obs_win;
|
|
XWindowChanges chngs;
|
|
int mask = CWSibling | CWStackMode;
|
|
if( cached->count() > 0 )
|
|
{
|
|
cached->remove( obs_win = cached->first());
|
|
chngs.x = c->x();
|
|
chngs.y = c->y();
|
|
chngs.width = c->width();
|
|
chngs.height = c->height();
|
|
mask |= CWX | CWY | CWWidth | CWHeight;
|
|
}
|
|
else
|
|
{
|
|
XSetWindowAttributes a;
|
|
a.background_pixmap = None;
|
|
a.override_redirect = True;
|
|
obs_win = XCreateWindow( tqt_xdisplay(), tqt_xrootwin(), c->x(), c->y(),
|
|
c->width(), c->height(), 0, CopyFromParent, InputOutput,
|
|
CopyFromParent, CWBackPixmap | CWOverrideRedirect, &a );
|
|
}
|
|
chngs.sibling = c->frameId();
|
|
chngs.stack_mode = Below;
|
|
XConfigureWindow( tqt_xdisplay(), obs_win, mask, &chngs );
|
|
XMapWindow( tqt_xdisplay(), obs_win );
|
|
obscuring_windows.append( obs_win );
|
|
}
|
|
|
|
ObscuringWindows::~ObscuringWindows()
|
|
{
|
|
max_cache_size = TQMAX( max_cache_size, obscuring_windows.count() + 4 ) - 1;
|
|
for( TQValueList<Window>::ConstIterator it = obscuring_windows.begin();
|
|
it != obscuring_windows.end();
|
|
++it )
|
|
{
|
|
XUnmapWindow( tqt_xdisplay(), *it );
|
|
if( cached->count() < max_cache_size )
|
|
cached->prepend( *it );
|
|
else
|
|
XDestroyWindow( tqt_xdisplay(), *it );
|
|
}
|
|
}
|
|
|
|
|
|
/*!
|
|
Sets the current desktop to \a new_desktop
|
|
|
|
Shows/Hides windows according to the stacking order and finally
|
|
propages the new desktop to the world
|
|
*/
|
|
bool Workspace::setCurrentDesktop( int new_desktop )
|
|
{
|
|
if (new_desktop < 1 || new_desktop > number_of_desktops )
|
|
return false;
|
|
|
|
closeActivePopup();
|
|
++block_focus;
|
|
// TODO Q_ASSERT( block_stacking_updates == 0 ); // make sure stacking_order is up to date
|
|
StackingUpdatesBlocker blocker( this );
|
|
|
|
int old_desktop = current_desktop;
|
|
if (new_desktop != current_desktop)
|
|
{
|
|
++block_showing_desktop;
|
|
/*
|
|
optimized Desktop switching: unmapping done from back to front
|
|
mapping done from front to back => less exposure events
|
|
*/
|
|
Notify::raise((Notify::Event) (Notify::DesktopChange+new_desktop));
|
|
|
|
ObscuringWindows obs_wins;
|
|
|
|
current_desktop = new_desktop; // change the desktop (so that Client::updateVisibility() works)
|
|
|
|
bool desktopHasCompositing = kapp->isCompositionManagerAvailable(); // Technically I should call isX11CompositionAvailable(), but it isn't initialized via my kapp constructir, and in this case it doesn't really matter anyway....
|
|
if (!desktopHasCompositing) {
|
|
// If composition is not in use then we can hide the old windows before showing the new ones
|
|
for ( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it) {
|
|
if ( !(*it)->isOnDesktop( new_desktop ) && (*it) != movingClient )
|
|
{
|
|
if( (*it)->isShown( true ) && (*it)->isOnDesktop( old_desktop )) {
|
|
obs_wins.create( *it );
|
|
}
|
|
(*it)->updateVisibility();
|
|
}
|
|
}
|
|
}
|
|
|
|
rootInfo->setCurrentDesktop( current_desktop ); // now propagate the change, after hiding, before showing
|
|
|
|
if( movingClient && !movingClient->isOnDesktop( new_desktop ))
|
|
movingClient->setDesktop( new_desktop );
|
|
|
|
for ( ClientList::ConstIterator it = stacking_order.fromLast(); it != stacking_order.end(); --it) {
|
|
if ( (*it)->isOnDesktop( new_desktop ) ) {
|
|
(*it)->updateVisibility();
|
|
}
|
|
}
|
|
|
|
if (desktopHasCompositing) {
|
|
// If composition is in use then we cannot hide the old windows before showing the new ones,
|
|
// unless you happen to like the "flicker annoyingly to desktop" effect... :-P
|
|
XSync( tqt_xdisplay(), false); // Make absolutely certain all new windows are shown before hiding the old ones
|
|
for ( ClientList::ConstIterator it = stacking_order.begin(); it != stacking_order.end(); ++it) {
|
|
if ( !(*it)->isOnDesktop( new_desktop ) && (*it) != movingClient )
|
|
{
|
|
if( (*it)->isShown( true ) && (*it)->isOnDesktop( old_desktop )) {
|
|
obs_wins.create( *it );
|
|
}
|
|
(*it)->updateVisibility();
|
|
}
|
|
}
|
|
}
|
|
|
|
--block_showing_desktop;
|
|
if( showingDesktop()) // do this only after desktop change to avoid flicker
|
|
resetShowingDesktop( false );
|
|
}
|
|
|
|
// restore the focus on this desktop
|
|
--block_focus;
|
|
Client* c = 0;
|
|
|
|
if ( options->focusPolicyIsReasonable())
|
|
{
|
|
// Search in focus chain
|
|
if ( movingClient != NULL && active_client == movingClient
|
|
&& focus_chain[currentDesktop()].contains( active_client )
|
|
&& active_client->isShown( true ) && active_client->isOnCurrentDesktop())
|
|
{
|
|
c = active_client; // the requestFocus below will fail, as the client is already active
|
|
}
|
|
if ( !c )
|
|
{
|
|
for( ClientList::ConstIterator it = focus_chain[currentDesktop()].fromLast();
|
|
it != focus_chain[currentDesktop()].end();
|
|
--it )
|
|
{
|
|
if ( (*it)->isShown( false ) && (*it)->isOnCurrentDesktop())
|
|
{
|
|
c = *it;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//if "unreasonable focus policy"
|
|
// and active_client is on_all_desktops and under mouse (hence == old_active_client),
|
|
// conserve focus (thanks to Volker Schatz <V.Schatz at thphys.uni-heidelberg.de>)
|
|
else if( active_client && active_client->isShown( true ) && active_client->isOnCurrentDesktop())
|
|
c = active_client;
|
|
|
|
if( c == NULL && !desktops.isEmpty())
|
|
c = findDesktop( true, currentDesktop());
|
|
|
|
if( c != active_client )
|
|
setActiveClient( NULL, Allowed );
|
|
|
|
if ( c )
|
|
requestFocus( c );
|
|
else
|
|
focusToNull();
|
|
|
|
updateCurrentTopMenu();
|
|
|
|
// Update focus chain:
|
|
// If input: chain = { 1, 2, 3, 4 } and current_desktop = 3,
|
|
// Output: chain = { 3, 1, 2, 4 }.
|
|
// kdDebug(1212) << TQString("Switching to desktop #%1, at focus_chain[currentDesktop()] index %2\n")
|
|
// .arg(currentDesktop()).arg(desktop_focus_chain.find( currentDesktop() ));
|
|
for( int i = desktop_focus_chain.find( currentDesktop() ); i > 0; i-- )
|
|
desktop_focus_chain[i] = desktop_focus_chain[i-1];
|
|
desktop_focus_chain[0] = currentDesktop();
|
|
|
|
// TQString s = "desktop_focus_chain[] = { ";
|
|
// for( uint i = 0; i < desktop_focus_chain.size(); i++ )
|
|
// s += TQString::number(desktop_focus_chain[i]) + ", ";
|
|
// kdDebug(1212) << s << "}\n";
|
|
|
|
if( old_desktop != 0 ) // not for the very first time
|
|
popupinfo->showInfo( desktopName(currentDesktop()) );
|
|
return true;
|
|
}
|
|
|
|
// called only from DCOP
|
|
void Workspace::nextDesktop()
|
|
{
|
|
int desktop = currentDesktop() + 1;
|
|
setCurrentDesktop(desktop > numberOfDesktops() ? 1 : desktop);
|
|
}
|
|
|
|
// called only from DCOP
|
|
void Workspace::previousDesktop()
|
|
{
|
|
int desktop = currentDesktop() - 1;
|
|
setCurrentDesktop(desktop > 0 ? desktop : numberOfDesktops());
|
|
}
|
|
|
|
int Workspace::desktopToRight( int desktop ) const
|
|
{
|
|
int x,y;
|
|
calcDesktopLayout(x,y);
|
|
int dt = desktop-1;
|
|
if (layoutOrientation == Qt::Vertical)
|
|
{
|
|
dt += y;
|
|
if ( dt >= numberOfDesktops() )
|
|
{
|
|
if ( options->rollOverDesktops )
|
|
dt -= numberOfDesktops();
|
|
else
|
|
return desktop;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int d = (dt % x) + 1;
|
|
if ( d >= x )
|
|
{
|
|
if ( options->rollOverDesktops )
|
|
d -= x;
|
|
else
|
|
return desktop;
|
|
}
|
|
dt = dt - (dt % x) + d;
|
|
}
|
|
return dt+1;
|
|
}
|
|
|
|
int Workspace::desktopToLeft( int desktop ) const
|
|
{
|
|
int x,y;
|
|
calcDesktopLayout(x,y);
|
|
int dt = desktop-1;
|
|
if (layoutOrientation == Qt::Vertical)
|
|
{
|
|
dt -= y;
|
|
if ( dt < 0 )
|
|
{
|
|
if ( options->rollOverDesktops )
|
|
dt += numberOfDesktops();
|
|
else
|
|
return desktop;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int d = (dt % x) - 1;
|
|
if ( d < 0 )
|
|
{
|
|
if ( options->rollOverDesktops )
|
|
d += x;
|
|
else
|
|
return desktop;
|
|
}
|
|
dt = dt - (dt % x) + d;
|
|
}
|
|
return dt+1;
|
|
}
|
|
|
|
int Workspace::desktopUp( int desktop ) const
|
|
{
|
|
int x,y;
|
|
calcDesktopLayout(x,y);
|
|
int dt = desktop-1;
|
|
if (layoutOrientation == Qt::Horizontal)
|
|
{
|
|
dt -= x;
|
|
if ( dt < 0 )
|
|
{
|
|
if ( options->rollOverDesktops )
|
|
dt += numberOfDesktops();
|
|
else
|
|
return desktop;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int d = (dt % y) - 1;
|
|
if ( d < 0 )
|
|
{
|
|
if ( options->rollOverDesktops )
|
|
d += y;
|
|
else
|
|
return desktop;
|
|
}
|
|
dt = dt - (dt % y) + d;
|
|
}
|
|
return dt+1;
|
|
}
|
|
|
|
int Workspace::desktopDown( int desktop ) const
|
|
{
|
|
int x,y;
|
|
calcDesktopLayout(x,y);
|
|
int dt = desktop-1;
|
|
if (layoutOrientation == Qt::Horizontal)
|
|
{
|
|
dt += x;
|
|
if ( dt >= numberOfDesktops() )
|
|
{
|
|
if ( options->rollOverDesktops )
|
|
dt -= numberOfDesktops();
|
|
else
|
|
return desktop;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int d = (dt % y) + 1;
|
|
if ( d >= y )
|
|
{
|
|
if ( options->rollOverDesktops )
|
|
d -= y;
|
|
else
|
|
return desktop;
|
|
}
|
|
dt = dt - (dt % y) + d;
|
|
}
|
|
return dt+1;
|
|
}
|
|
|
|
|
|
/*!
|
|
Sets the number of virtual desktops to \a n
|
|
*/
|
|
void Workspace::setNumberOfDesktops( int n )
|
|
{
|
|
if ( n == number_of_desktops )
|
|
return;
|
|
int old_number_of_desktops = number_of_desktops;
|
|
number_of_desktops = n;
|
|
|
|
if( currentDesktop() > numberOfDesktops())
|
|
setCurrentDesktop( numberOfDesktops());
|
|
|
|
// if increasing the number, do the resizing now,
|
|
// otherwise after the moving of windows to still existing desktops
|
|
if( old_number_of_desktops < number_of_desktops )
|
|
{
|
|
rootInfo->setNumberOfDesktops( number_of_desktops );
|
|
NETPoint* viewports = new NETPoint[ number_of_desktops ];
|
|
rootInfo->setDesktopViewport( number_of_desktops, *viewports );
|
|
delete[] viewports;
|
|
updateClientArea( true );
|
|
focus_chain.resize( number_of_desktops + 1 );
|
|
}
|
|
|
|
// if the number of desktops decreased, move all
|
|
// windows that would be hidden to the last visible desktop
|
|
if( old_number_of_desktops > number_of_desktops )
|
|
{
|
|
for( ClientList::ConstIterator it = clients.begin();
|
|
it != clients.end();
|
|
++it)
|
|
{
|
|
if( !(*it)->isOnAllDesktops() && (*it)->desktop() > numberOfDesktops())
|
|
sendClientToDesktop( *it, numberOfDesktops(), true );
|
|
}
|
|
}
|
|
if( old_number_of_desktops > number_of_desktops )
|
|
{
|
|
rootInfo->setNumberOfDesktops( number_of_desktops );
|
|
NETPoint* viewports = new NETPoint[ number_of_desktops ];
|
|
rootInfo->setDesktopViewport( number_of_desktops, *viewports );
|
|
delete[] viewports;
|
|
updateClientArea( true );
|
|
focus_chain.resize( number_of_desktops + 1 );
|
|
}
|
|
|
|
saveDesktopSettings();
|
|
|
|
// Resize and reset the desktop focus chain.
|
|
desktop_focus_chain.resize( n );
|
|
for( int i = 0; i < (int)desktop_focus_chain.size(); i++ )
|
|
desktop_focus_chain[i] = i+1;
|
|
}
|
|
|
|
/*!
|
|
Sends client \a c to desktop \a desk.
|
|
|
|
Takes care of transients as well.
|
|
*/
|
|
void Workspace::sendClientToDesktop( Client* c, int desk, bool dont_activate )
|
|
{
|
|
bool was_on_desktop = c->isOnDesktop( desk ) || c->isOnAllDesktops();
|
|
c->setDesktop( desk );
|
|
if ( c->desktop() != desk ) // no change or desktop forced
|
|
return;
|
|
desk = c->desktop(); // Client did range checking
|
|
|
|
if ( c->isOnDesktop( currentDesktop() ) )
|
|
{
|
|
if ( c->wantsTabFocus() && options->focusPolicyIsReasonable()
|
|
&& !was_on_desktop // for stickyness changes
|
|
&& !dont_activate )
|
|
requestFocus( c );
|
|
else
|
|
restackClientUnderActive( c );
|
|
}
|
|
else
|
|
{
|
|
raiseClient( c );
|
|
}
|
|
|
|
ClientList transients_stacking_order = ensureStackingOrder( c->transients());
|
|
for( ClientList::ConstIterator it = transients_stacking_order.begin();
|
|
it != transients_stacking_order.end();
|
|
++it )
|
|
sendClientToDesktop( *it, desk, dont_activate );
|
|
updateClientArea();
|
|
}
|
|
|
|
int Workspace::numScreens() const
|
|
{
|
|
if( !options->xineramaEnabled )
|
|
return 0;
|
|
return tqApp->desktop()->numScreens();
|
|
}
|
|
|
|
int Workspace::activeScreen() const
|
|
{
|
|
if( !options->xineramaEnabled )
|
|
return 0;
|
|
if( !options->activeMouseScreen )
|
|
{
|
|
if( activeClient() != NULL && !activeClient()->isOnScreen( active_screen ))
|
|
return tqApp->desktop()->screenNumber( activeClient()->geometry().center());
|
|
return active_screen;
|
|
}
|
|
return tqApp->desktop()->screenNumber( TQCursor::pos());
|
|
}
|
|
|
|
// check whether a client moved completely out of what's considered the active screen,
|
|
// if yes, set a new active screen
|
|
void Workspace::checkActiveScreen( const Client* c )
|
|
{
|
|
if( !options->xineramaEnabled )
|
|
return;
|
|
if( !c->isActive())
|
|
return;
|
|
if( !c->isOnScreen( active_screen ))
|
|
active_screen = c->screen();
|
|
}
|
|
|
|
// called e.g. when a user clicks on a window, set active screen to be the screen
|
|
// where the click occured
|
|
void Workspace::setActiveScreenMouse( TQPoint mousepos )
|
|
{
|
|
if( !options->xineramaEnabled )
|
|
return;
|
|
active_screen = tqApp->desktop()->screenNumber( mousepos );
|
|
}
|
|
|
|
TQRect Workspace::screenGeometry( int screen ) const
|
|
{
|
|
if (( !options->xineramaEnabled ) || (kapp->desktop()->numScreens() < 2))
|
|
return tqApp->desktop()->geometry();
|
|
return tqApp->desktop()->screenGeometry( screen );
|
|
}
|
|
|
|
int Workspace::screenNumber( TQPoint pos ) const
|
|
{
|
|
if( !options->xineramaEnabled )
|
|
return 0;
|
|
return tqApp->desktop()->screenNumber( pos );
|
|
}
|
|
|
|
void Workspace::sendClientToScreen( Client* c, int screen )
|
|
{
|
|
if( c->screen() == screen ) // don't use isOnScreen(), that's true even when only partially
|
|
return;
|
|
GeometryUpdatesPostponer blocker( c );
|
|
TQRect old_sarea = clientArea( MaximizeArea, c );
|
|
TQRect sarea = clientArea( MaximizeArea, screen, c->desktop());
|
|
c->setGeometry( sarea.x() - old_sarea.x() + c->x(), sarea.y() - old_sarea.y() + c->y(),
|
|
c->size().width(), c->size().height());
|
|
c->checkWorkspacePosition();
|
|
ClientList transients_stacking_order = ensureStackingOrder( c->transients());
|
|
for( ClientList::ConstIterator it = transients_stacking_order.begin();
|
|
it != transients_stacking_order.end();
|
|
++it )
|
|
sendClientToScreen( *it, screen );
|
|
if( c->isActive())
|
|
active_screen = screen;
|
|
}
|
|
|
|
|
|
void Workspace::setDesktopLayout( int, int, int )
|
|
{ // DCOP-only, unused
|
|
}
|
|
|
|
void Workspace::updateDesktopLayout()
|
|
{
|
|
// rootInfo->desktopLayoutCorner(); // I don't find this worth bothering, feel free to
|
|
layoutOrientation = ( rootInfo->desktopLayoutOrientation() == NET::OrientationHorizontal
|
|
? Qt::Horizontal : Qt::Vertical );
|
|
layoutX = rootInfo->desktopLayoutColumnsRows().width();
|
|
layoutY = rootInfo->desktopLayoutColumnsRows().height();
|
|
if( layoutX == 0 && layoutY == 0 ) // not given, set default layout
|
|
layoutY = 2;
|
|
}
|
|
|
|
void Workspace::calcDesktopLayout(int &x, int &y) const
|
|
{
|
|
x = layoutX; // <= 0 means compute it from the other and total number of desktops
|
|
y = layoutY;
|
|
if((x <= 0) && (y > 0))
|
|
x = (numberOfDesktops()+y-1) / y;
|
|
else if((y <=0) && (x > 0))
|
|
y = (numberOfDesktops()+x-1) / x;
|
|
|
|
if(x <=0)
|
|
x = 1;
|
|
if (y <= 0)
|
|
y = 1;
|
|
}
|
|
|
|
/*!
|
|
Check whether \a w is a system tray window. If so, add it to the respective
|
|
datastructures and propagate it to the world.
|
|
*/
|
|
bool Workspace::addSystemTrayWin( WId w )
|
|
{
|
|
if ( systemTrayWins.contains( w ) )
|
|
return TRUE;
|
|
|
|
NETWinInfo ni( tqt_xdisplay(), w, root, NET::WMKDESystemTrayWinFor );
|
|
WId trayWinFor = ni.kdeSystemTrayWinFor();
|
|
if ( !trayWinFor )
|
|
return FALSE;
|
|
systemTrayWins.append( SystemTrayWindow( w, trayWinFor ) );
|
|
XSelectInput( tqt_xdisplay(), w,
|
|
StructureNotifyMask
|
|
);
|
|
XAddToSaveSet( tqt_xdisplay(), w );
|
|
propagateSystemTrayWins();
|
|
return TRUE;
|
|
}
|
|
|
|
/*!
|
|
Check whether \a w is a system tray window. If so, remove it from
|
|
the respective datastructures and propagate this to the world.
|
|
*/
|
|
bool Workspace::removeSystemTrayWin( WId w, bool check )
|
|
{
|
|
if ( !systemTrayWins.contains( w ) )
|
|
return FALSE;
|
|
if( check )
|
|
{
|
|
// When getting UnmapNotify, it's not clear if it's the systray
|
|
// reparenting the window into itself, or if it's the window
|
|
// going away. This is obviously a flaw in the design, and we were
|
|
// just lucky it worked for so long. Kicker's systray temporarily
|
|
// sets _KDE_SYSTEM_TRAY_EMBEDDING property on the window while
|
|
// embedding it, allowing KWin to figure out. Kicker just mustn't
|
|
// crash before removing it again ... *shrug* .
|
|
int num_props;
|
|
Atom* props = XListProperties( tqt_xdisplay(), w, &num_props );
|
|
if( props != NULL )
|
|
{
|
|
for( int i = 0;
|
|
i < num_props;
|
|
++i )
|
|
if( props[ i ] == atoms->kde_system_tray_embedding )
|
|
{
|
|
XFree( props );
|
|
return false;
|
|
}
|
|
XFree( props );
|
|
}
|
|
}
|
|
systemTrayWins.remove( w );
|
|
XRemoveFromSaveSet (tqt_xdisplay (), w);
|
|
propagateSystemTrayWins();
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*!
|
|
Propagates the systemTrayWins to the world
|
|
*/
|
|
void Workspace::propagateSystemTrayWins()
|
|
{
|
|
Window *cl = new Window[ systemTrayWins.count()];
|
|
|
|
int i = 0;
|
|
for ( SystemTrayWindowList::ConstIterator it = systemTrayWins.begin(); it != systemTrayWins.end(); ++it )
|
|
{
|
|
cl[i++] = (*it).win;
|
|
}
|
|
|
|
rootInfo->setKDESystemTrayWindows( cl, i );
|
|
delete [] cl;
|
|
}
|
|
|
|
|
|
void Workspace::killWindowId( Window window_to_kill )
|
|
{
|
|
if( window_to_kill == None )
|
|
return;
|
|
Window window = window_to_kill;
|
|
Client* client = NULL;
|
|
for(;;)
|
|
{
|
|
client = findClient( FrameIdMatchPredicate( window ));
|
|
if( client != NULL ) // found the client
|
|
break;
|
|
Window parent = NULL;
|
|
Window root = NULL;
|
|
Window* children = NULL;
|
|
unsigned int children_count;
|
|
XQueryTree( tqt_xdisplay(), window, &root, &parent, &children, &children_count );
|
|
if( children != NULL )
|
|
XFree( children );
|
|
if( window == root ) // we didn't find the client, probably an override-redirect window
|
|
break;
|
|
window = parent; // go up
|
|
if( window == NULL )
|
|
break;
|
|
}
|
|
if( client != NULL )
|
|
client->killWindow();
|
|
else
|
|
XKillClient( tqt_xdisplay(), window_to_kill );
|
|
}
|
|
|
|
void Workspace::suspendWindowId( Window window_to_suspend )
|
|
{
|
|
if( window_to_suspend == None )
|
|
return;
|
|
Window window = window_to_suspend;
|
|
Client* client = NULL;
|
|
for(;;)
|
|
{
|
|
client = findClient( FrameIdMatchPredicate( window ));
|
|
if( client != NULL ) // found the client
|
|
break;
|
|
Window parent = NULL;
|
|
Window root = NULL;
|
|
Window* children = NULL;
|
|
unsigned int children_count;
|
|
XQueryTree( tqt_xdisplay(), window, &root, &parent, &children, &children_count );
|
|
if( children != NULL )
|
|
XFree( children );
|
|
if( window == root ) // we didn't find the client, probably an override-redirect window
|
|
break;
|
|
window = parent; // go up
|
|
if( window == NULL )
|
|
break;
|
|
}
|
|
if( client != NULL )
|
|
client->suspendWindow();
|
|
else
|
|
return;
|
|
}
|
|
|
|
void Workspace::resumeWindowId( Window window_to_resume )
|
|
{
|
|
if( window_to_resume == None )
|
|
return;
|
|
Window window = window_to_resume;
|
|
Client* client = NULL;
|
|
for(;;)
|
|
{
|
|
client = findClient( FrameIdMatchPredicate( window ));
|
|
if( client != NULL ) // found the client
|
|
break;
|
|
Window parent = NULL;
|
|
Window root = NULL;
|
|
Window* children = NULL;
|
|
unsigned int children_count;
|
|
XQueryTree( tqt_xdisplay(), window, &root, &parent, &children, &children_count );
|
|
if( children != NULL )
|
|
XFree( children );
|
|
if( window == root ) // we didn't find the client, probably an override-redirect window
|
|
break;
|
|
window = parent; // go up
|
|
if( window == NULL )
|
|
break;
|
|
}
|
|
if( client != NULL )
|
|
client->resumeWindow();
|
|
else
|
|
return;
|
|
}
|
|
|
|
|
|
void Workspace::sendPingToWindow( Window window, Time timestamp )
|
|
{
|
|
rootInfo->sendPing( window, timestamp );
|
|
}
|
|
|
|
void Workspace::sendTakeActivity( Client* c, Time timestamp, long flags )
|
|
{
|
|
rootInfo->takeActivity( c->window(), timestamp, flags );
|
|
pending_take_activity = c;
|
|
}
|
|
|
|
|
|
/*!
|
|
Takes a screenshot of the current window and puts it in the clipboard.
|
|
*/
|
|
void Workspace::slotGrabWindow()
|
|
{
|
|
if ( active_client )
|
|
{
|
|
TQPixmap snapshot = TQPixmap::grabWindow( active_client->frameId() );
|
|
|
|
//No XShape - no work.
|
|
if( Shape::available())
|
|
{
|
|
//As the first step, get the mask from XShape.
|
|
int count, order;
|
|
XRectangle* rects = XShapeGetRectangles( tqt_xdisplay(), active_client->frameId(),
|
|
ShapeBounding, &count, &order);
|
|
//The ShapeBounding region is the outermost shape of the window;
|
|
//ShapeBounding - ShapeClipping is defined to be the border.
|
|
//Since the border area is part of the window, we use bounding
|
|
// to limit our work region
|
|
if (rects)
|
|
{
|
|
//Create a TQRegion from the rectangles describing the bounding mask.
|
|
TQRegion contents;
|
|
for (int pos = 0; pos < count; pos++)
|
|
contents += TQRegion(rects[pos].x, rects[pos].y,
|
|
rects[pos].width, rects[pos].height);
|
|
XFree(rects);
|
|
|
|
//Create the bounding box.
|
|
TQRegion bbox(0, 0, snapshot.width(), snapshot.height());
|
|
|
|
//Get the masked away area.
|
|
TQRegion maskedAway = bbox - contents;
|
|
TQMemArray<TQRect> maskedAwayRects = maskedAway.rects();
|
|
|
|
//Construct a bitmap mask from the rectangles
|
|
TQBitmap mask( snapshot.width(), snapshot.height());
|
|
TQPainter p(&mask);
|
|
p.fillRect(0, 0, mask.width(), mask.height(), Qt::color1);
|
|
for (uint pos = 0; pos < maskedAwayRects.count(); pos++)
|
|
p.fillRect(maskedAwayRects[pos], Qt::color0);
|
|
p.end();
|
|
snapshot.setMask(mask);
|
|
}
|
|
}
|
|
|
|
TQClipboard *cb = TQApplication::clipboard();
|
|
cb->setPixmap( snapshot );
|
|
}
|
|
else
|
|
slotGrabDesktop();
|
|
}
|
|
|
|
/*!
|
|
Takes a screenshot of the whole desktop and puts it in the clipboard.
|
|
*/
|
|
void Workspace::slotGrabDesktop()
|
|
{
|
|
TQPixmap p = TQPixmap::grabWindow( tqt_xrootwin() );
|
|
TQClipboard *cb = TQApplication::clipboard();
|
|
cb->setPixmap( p );
|
|
}
|
|
|
|
|
|
/*!
|
|
Invokes keyboard mouse emulation
|
|
*/
|
|
void Workspace::slotMouseEmulation()
|
|
{
|
|
|
|
if ( mouse_emulation )
|
|
{
|
|
XUngrabKeyboard(tqt_xdisplay(), GET_QT_X_TIME());
|
|
mouse_emulation = FALSE;
|
|
return;
|
|
}
|
|
|
|
if ( XGrabKeyboard(tqt_xdisplay(),
|
|
root, FALSE,
|
|
GrabModeAsync, GrabModeAsync,
|
|
GET_QT_X_TIME()) == GrabSuccess )
|
|
{
|
|
mouse_emulation = TRUE;
|
|
mouse_emulation_state = 0;
|
|
mouse_emulation_window = 0;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Returns the child window under the mouse and activates the
|
|
respective client if necessary.
|
|
|
|
Auxiliary function for the mouse emulation system.
|
|
*/
|
|
WId Workspace::getMouseEmulationWindow()
|
|
{
|
|
Window root;
|
|
Window child = tqt_xrootwin();
|
|
int root_x, root_y, lx, ly;
|
|
uint state;
|
|
Window w;
|
|
Client * c = 0;
|
|
do
|
|
{
|
|
w = child;
|
|
if (!c)
|
|
c = findClient( FrameIdMatchPredicate( w ));
|
|
XQueryPointer( tqt_xdisplay(), w, &root, &child,
|
|
&root_x, &root_y, &lx, &ly, &state );
|
|
} while ( child != None && child != w );
|
|
|
|
if ( c && !c->isActive() )
|
|
activateClient( c );
|
|
return (WId) w;
|
|
}
|
|
|
|
/*!
|
|
Sends a faked mouse event to the specified window. Returns the new button state.
|
|
*/
|
|
unsigned int Workspace::sendFakedMouseEvent( TQPoint pos, WId w, MouseEmulation type, int button, unsigned int state )
|
|
{
|
|
if ( !w )
|
|
return state;
|
|
TQWidget* widget = TQWidget::find( w );
|
|
if ( (!widget || widget->inherits(TQTOOLBUTTON_OBJECT_NAME_STRING) ) && !findClient( WindowMatchPredicate( w )) )
|
|
{
|
|
int x, y;
|
|
Window xw;
|
|
XTranslateCoordinates( tqt_xdisplay(), tqt_xrootwin(), w, pos.x(), pos.y(), &x, &y, &xw );
|
|
if ( type == EmuMove )
|
|
{ // motion notify events
|
|
XEvent e;
|
|
e.type = MotionNotify;
|
|
e.xmotion.window = w;
|
|
e.xmotion.root = tqt_xrootwin();
|
|
e.xmotion.subwindow = w;
|
|
e.xmotion.time = GET_QT_X_TIME();
|
|
e.xmotion.x = x;
|
|
e.xmotion.y = y;
|
|
e.xmotion.x_root = pos.x();
|
|
e.xmotion.y_root = pos.y();
|
|
e.xmotion.state = state;
|
|
e.xmotion.is_hint = NotifyNormal;
|
|
XSendEvent( tqt_xdisplay(), w, TRUE, ButtonMotionMask, &e );
|
|
}
|
|
else
|
|
{
|
|
XEvent e;
|
|
e.type = type == EmuRelease ? ButtonRelease : ButtonPress;
|
|
e.xbutton.window = w;
|
|
e.xbutton.root = tqt_xrootwin();
|
|
e.xbutton.subwindow = w;
|
|
e.xbutton.time = GET_QT_X_TIME();
|
|
e.xbutton.x = x;
|
|
e.xbutton.y = y;
|
|
e.xbutton.x_root = pos.x();
|
|
e.xbutton.y_root = pos.y();
|
|
e.xbutton.state = state;
|
|
e.xbutton.button = button;
|
|
XSendEvent( tqt_xdisplay(), w, TRUE, ButtonPressMask, &e );
|
|
|
|
if ( type == EmuPress )
|
|
{
|
|
switch ( button )
|
|
{
|
|
case 2:
|
|
state |= Button2Mask;
|
|
break;
|
|
case 3:
|
|
state |= Button3Mask;
|
|
break;
|
|
default: // 1
|
|
state |= Button1Mask;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch ( button )
|
|
{
|
|
case 2:
|
|
state &= ~Button2Mask;
|
|
break;
|
|
case 3:
|
|
state &= ~Button3Mask;
|
|
break;
|
|
default: // 1
|
|
state &= ~Button1Mask;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return state;
|
|
}
|
|
|
|
/*!
|
|
Handles keypress event during mouse emulation
|
|
*/
|
|
bool Workspace::keyPressMouseEmulation( XKeyEvent& ev )
|
|
{
|
|
if ( root != tqt_xrootwin() )
|
|
return FALSE;
|
|
int kc = XkbKeycodeToKeysym(tqt_xdisplay(), ev.keycode, 0, 0);
|
|
int km = ev.state & (ControlMask | Mod1Mask | ShiftMask);
|
|
|
|
bool is_control = km & ControlMask;
|
|
bool is_alt = km & Mod1Mask;
|
|
bool is_shift = km & ShiftMask;
|
|
int delta = is_control?1:is_alt?32:8;
|
|
TQPoint pos = TQCursor::pos();
|
|
|
|
switch ( kc )
|
|
{
|
|
case XK_Left:
|
|
case XK_KP_Left:
|
|
pos.rx() -= delta;
|
|
break;
|
|
case XK_Right:
|
|
case XK_KP_Right:
|
|
pos.rx() += delta;
|
|
break;
|
|
case XK_Up:
|
|
case XK_KP_Up:
|
|
pos.ry() -= delta;
|
|
break;
|
|
case XK_Down:
|
|
case XK_KP_Down:
|
|
pos.ry() += delta;
|
|
break;
|
|
case XK_F1:
|
|
if ( !mouse_emulation_state )
|
|
mouse_emulation_window = getMouseEmulationWindow();
|
|
if ( (mouse_emulation_state & Button1Mask) == 0 )
|
|
mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuPress, Button1, mouse_emulation_state );
|
|
if ( !is_shift )
|
|
mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button1, mouse_emulation_state );
|
|
break;
|
|
case XK_F2:
|
|
if ( !mouse_emulation_state )
|
|
mouse_emulation_window = getMouseEmulationWindow();
|
|
if ( (mouse_emulation_state & Button2Mask) == 0 )
|
|
mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuPress, Button2, mouse_emulation_state );
|
|
if ( !is_shift )
|
|
mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button2, mouse_emulation_state );
|
|
break;
|
|
case XK_F3:
|
|
if ( !mouse_emulation_state )
|
|
mouse_emulation_window = getMouseEmulationWindow();
|
|
if ( (mouse_emulation_state & Button3Mask) == 0 )
|
|
mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuPress, Button3, mouse_emulation_state );
|
|
if ( !is_shift )
|
|
mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button3, mouse_emulation_state );
|
|
break;
|
|
case XK_Return:
|
|
case XK_space:
|
|
case XK_KP_Enter:
|
|
case XK_KP_Space:
|
|
{
|
|
if ( !mouse_emulation_state )
|
|
{
|
|
// nothing was pressed, fake a LMB click
|
|
mouse_emulation_window = getMouseEmulationWindow();
|
|
mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuPress, Button1, mouse_emulation_state );
|
|
mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button1, mouse_emulation_state );
|
|
}
|
|
else
|
|
{ // release all
|
|
if ( mouse_emulation_state & Button1Mask )
|
|
mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button1, mouse_emulation_state );
|
|
if ( mouse_emulation_state & Button2Mask )
|
|
mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button2, mouse_emulation_state );
|
|
if ( mouse_emulation_state & Button3Mask )
|
|
mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuRelease, Button3, mouse_emulation_state );
|
|
}
|
|
}
|
|
// fall through
|
|
case XK_Escape:
|
|
XUngrabKeyboard(tqt_xdisplay(), GET_QT_X_TIME());
|
|
mouse_emulation = FALSE;
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
TQCursor::setPos( pos );
|
|
if ( mouse_emulation_state )
|
|
mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window, EmuMove, 0, mouse_emulation_state );
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
/*!
|
|
Returns the workspace's desktop widget. The desktop widget is
|
|
sometimes required by clients to draw on it, for example outlines on
|
|
moving or resizing.
|
|
*/
|
|
TQWidget* Workspace::desktopWidget()
|
|
{
|
|
return desktop_widget;
|
|
}
|
|
|
|
//Delayed focus functions
|
|
void Workspace::delayFocus()
|
|
{
|
|
requestFocus( delayfocus_client );
|
|
cancelDelayFocus();
|
|
}
|
|
|
|
void Workspace::requestDelayFocus( Client* c )
|
|
{
|
|
delayfocus_client = c;
|
|
delete delayFocusTimer;
|
|
delayFocusTimer = new TQTimer( this );
|
|
connect( delayFocusTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( delayFocus() ) );
|
|
delayFocusTimer->start( options->delayFocusInterval, TRUE );
|
|
}
|
|
|
|
void Workspace::cancelDelayFocus()
|
|
{
|
|
delete delayFocusTimer;
|
|
delayFocusTimer = 0;
|
|
}
|
|
|
|
// Electric Borders
|
|
//========================================================================//
|
|
// Electric Border Window management. Electric borders allow a user
|
|
// to change the virtual desktop by moving the mouse pointer to the
|
|
// borders. Technically this is done with input only windows. Since
|
|
// electric borders can be switched on and off, we have these two
|
|
// functions to create and destroy them.
|
|
void Workspace::checkElectricBorders( bool force )
|
|
{
|
|
if( force )
|
|
destroyBorderWindows();
|
|
|
|
electric_current_border = 0;
|
|
|
|
TQRect r = TQApplication::desktop()->geometry();
|
|
electricTop = r.top();
|
|
electricBottom = r.bottom();
|
|
electricLeft = r.left();
|
|
electricRight = r.right();
|
|
|
|
if (options->electricBorders() == Options::ElectricAlways)
|
|
createBorderWindows();
|
|
else
|
|
destroyBorderWindows();
|
|
}
|
|
|
|
void Workspace::createBorderWindows()
|
|
{
|
|
if ( electric_have_borders )
|
|
return;
|
|
|
|
electric_have_borders = true;
|
|
|
|
TQRect r = TQApplication::desktop()->geometry();
|
|
XSetWindowAttributes attributes;
|
|
unsigned long valuemask;
|
|
attributes.override_redirect = True;
|
|
attributes.event_mask = ( EnterWindowMask | LeaveWindowMask );
|
|
valuemask= (CWOverrideRedirect | CWEventMask | CWCursor );
|
|
attributes.cursor = XCreateFontCursor(tqt_xdisplay(),
|
|
XC_sb_up_arrow);
|
|
electric_top_border = XCreateWindow (tqt_xdisplay(), tqt_xrootwin(),
|
|
0,0,
|
|
r.width(),1,
|
|
0,
|
|
CopyFromParent, InputOnly,
|
|
CopyFromParent,
|
|
valuemask, &attributes);
|
|
XMapWindow(tqt_xdisplay(), electric_top_border);
|
|
|
|
attributes.cursor = XCreateFontCursor(tqt_xdisplay(),
|
|
XC_sb_down_arrow);
|
|
electric_bottom_border = XCreateWindow (tqt_xdisplay(), tqt_xrootwin(),
|
|
0,r.height()-1,
|
|
r.width(),1,
|
|
0,
|
|
CopyFromParent, InputOnly,
|
|
CopyFromParent,
|
|
valuemask, &attributes);
|
|
XMapWindow(tqt_xdisplay(), electric_bottom_border);
|
|
|
|
attributes.cursor = XCreateFontCursor(tqt_xdisplay(),
|
|
XC_sb_left_arrow);
|
|
electric_left_border = XCreateWindow (tqt_xdisplay(), tqt_xrootwin(),
|
|
0,0,
|
|
1,r.height(),
|
|
0,
|
|
CopyFromParent, InputOnly,
|
|
CopyFromParent,
|
|
valuemask, &attributes);
|
|
XMapWindow(tqt_xdisplay(), electric_left_border);
|
|
|
|
attributes.cursor = XCreateFontCursor(tqt_xdisplay(),
|
|
XC_sb_right_arrow);
|
|
electric_right_border = XCreateWindow (tqt_xdisplay(), tqt_xrootwin(),
|
|
r.width()-1,0,
|
|
1,r.height(),
|
|
0,
|
|
CopyFromParent, InputOnly,
|
|
CopyFromParent,
|
|
valuemask, &attributes);
|
|
XMapWindow(tqt_xdisplay(), electric_right_border);
|
|
// Set XdndAware on the windows, so that DND enter events are received (#86998)
|
|
Atom version = 4; // XDND version
|
|
XChangeProperty( tqt_xdisplay(), electric_top_border, atoms->xdnd_aware, XA_ATOM,
|
|
32, PropModeReplace, ( unsigned char* )&version, 1 );
|
|
XChangeProperty( tqt_xdisplay(), electric_bottom_border, atoms->xdnd_aware, XA_ATOM,
|
|
32, PropModeReplace, ( unsigned char* )&version, 1 );
|
|
XChangeProperty( tqt_xdisplay(), electric_left_border, atoms->xdnd_aware, XA_ATOM,
|
|
32, PropModeReplace, ( unsigned char* )&version, 1 );
|
|
XChangeProperty( tqt_xdisplay(), electric_right_border, atoms->xdnd_aware, XA_ATOM,
|
|
32, PropModeReplace, ( unsigned char* )&version, 1 );
|
|
}
|
|
|
|
|
|
// Electric Border Window management. Electric borders allow a user
|
|
// to change the virtual desktop by moving the mouse pointer to the
|
|
// borders. Technically this is done with input only windows. Since
|
|
// electric borders can be switched on and off, we have these two
|
|
// functions to create and destroy them.
|
|
void Workspace::destroyBorderWindows()
|
|
{
|
|
if( !electric_have_borders)
|
|
return;
|
|
|
|
electric_have_borders = false;
|
|
|
|
if(electric_top_border)
|
|
XDestroyWindow(tqt_xdisplay(),electric_top_border);
|
|
if(electric_bottom_border)
|
|
XDestroyWindow(tqt_xdisplay(),electric_bottom_border);
|
|
if(electric_left_border)
|
|
XDestroyWindow(tqt_xdisplay(),electric_left_border);
|
|
if(electric_right_border)
|
|
XDestroyWindow(tqt_xdisplay(),electric_right_border);
|
|
|
|
electric_top_border = None;
|
|
electric_bottom_border = None;
|
|
electric_left_border = None;
|
|
electric_right_border = None;
|
|
}
|
|
|
|
void Workspace::clientMoved(const TQPoint &pos, Time now)
|
|
{
|
|
if (options->electricBorders() == Options::ElectricDisabled)
|
|
return;
|
|
|
|
if ((pos.x() != electricLeft) &&
|
|
(pos.x() != electricRight) &&
|
|
(pos.y() != electricTop) &&
|
|
(pos.y() != electricBottom))
|
|
return;
|
|
|
|
Time treshold_set = options->electricBorderDelay(); // set timeout
|
|
Time treshold_reset = 250; // reset timeout
|
|
int distance_reset = 30; // Mouse should not move more than this many pixels
|
|
|
|
int border = 0;
|
|
if (pos.x() == electricLeft)
|
|
border = 1;
|
|
else if (pos.x() == electricRight)
|
|
border = 2;
|
|
else if (pos.y() == electricTop)
|
|
border = 3;
|
|
else if (pos.y() == electricBottom)
|
|
border = 4;
|
|
|
|
if ((electric_current_border == border) &&
|
|
(timestampDiff(electric_time_last, now) < treshold_reset) &&
|
|
((pos-electric_push_point).manhattanLength() < distance_reset))
|
|
{
|
|
electric_time_last = now;
|
|
|
|
if (timestampDiff(electric_time_first, now) > treshold_set)
|
|
{
|
|
electric_current_border = 0;
|
|
|
|
TQRect r = TQApplication::desktop()->geometry();
|
|
int offset;
|
|
|
|
int desk_before = currentDesktop();
|
|
switch(border)
|
|
{
|
|
case 1:
|
|
slotSwitchDesktopLeft();
|
|
if (currentDesktop() != desk_before)
|
|
{
|
|
offset = r.width() / 5;
|
|
TQCursor::setPos(r.width() - offset, pos.y());
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
slotSwitchDesktopRight();
|
|
if (currentDesktop() != desk_before)
|
|
{
|
|
offset = r.width() / 5;
|
|
TQCursor::setPos(offset, pos.y());
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
slotSwitchDesktopUp();
|
|
if (currentDesktop() != desk_before)
|
|
{
|
|
offset = r.height() / 5;
|
|
TQCursor::setPos(pos.x(), r.height() - offset);
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
slotSwitchDesktopDown();
|
|
if (currentDesktop() != desk_before)
|
|
{
|
|
offset = r.height() / 5;
|
|
TQCursor::setPos(pos.x(), offset);
|
|
}
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
electric_current_border = border;
|
|
electric_time_first = now;
|
|
electric_time_last = now;
|
|
electric_push_point = pos;
|
|
}
|
|
|
|
int mouse_warp = 1;
|
|
|
|
// reset the pointer to find out wether the user is really pushing
|
|
switch( border)
|
|
{
|
|
case 1: TQCursor::setPos(pos.x()+mouse_warp, pos.y()); break;
|
|
case 2: TQCursor::setPos(pos.x()-mouse_warp, pos.y()); break;
|
|
case 3: TQCursor::setPos(pos.x(), pos.y()+mouse_warp); break;
|
|
case 4: TQCursor::setPos(pos.x(), pos.y()-mouse_warp); break;
|
|
}
|
|
}
|
|
|
|
// this function is called when the user entered an electric border
|
|
// with the mouse. It may switch to another virtual desktop
|
|
bool Workspace::electricBorder(XEvent *e)
|
|
{
|
|
if( !electric_have_borders )
|
|
return false;
|
|
if( e->type == EnterNotify )
|
|
{
|
|
if( e->xcrossing.window == electric_top_border ||
|
|
e->xcrossing.window == electric_left_border ||
|
|
e->xcrossing.window == electric_bottom_border ||
|
|
e->xcrossing.window == electric_right_border)
|
|
// the user entered an electric border
|
|
{
|
|
clientMoved( TQPoint( e->xcrossing.x_root, e->xcrossing.y_root ), e->xcrossing.time );
|
|
return true;
|
|
}
|
|
}
|
|
if( e->type == ClientMessage )
|
|
{
|
|
if( e->xclient.message_type == atoms->xdnd_position
|
|
&& ( e->xclient.window == electric_top_border
|
|
|| e->xclient.window == electric_bottom_border
|
|
|| e->xclient.window == electric_left_border
|
|
|| e->xclient.window == electric_right_border ))
|
|
{
|
|
updateXTime();
|
|
clientMoved( TQPoint( e->xclient.data.l[2]>>16, e->xclient.data.l[2]&0xffff), GET_QT_X_TIME() );
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// electric borders (input only windows) have to be always on the
|
|
// top. For that reason kwm calls this function always after some
|
|
// windows have been raised.
|
|
void Workspace::raiseElectricBorders()
|
|
{
|
|
|
|
if(electric_have_borders)
|
|
{
|
|
XRaiseWindow(tqt_xdisplay(), electric_top_border);
|
|
XRaiseWindow(tqt_xdisplay(), electric_left_border);
|
|
XRaiseWindow(tqt_xdisplay(), electric_bottom_border);
|
|
XRaiseWindow(tqt_xdisplay(), electric_right_border);
|
|
}
|
|
}
|
|
|
|
void Workspace::addTopMenu( Client* c )
|
|
{
|
|
assert( c->isTopMenu());
|
|
assert( !topmenus.contains( c ));
|
|
topmenus.append( c );
|
|
if( managingTopMenus())
|
|
{
|
|
int minsize = c->minSize().height();
|
|
if( minsize > topMenuHeight())
|
|
{
|
|
topmenu_height = minsize;
|
|
updateTopMenuGeometry();
|
|
}
|
|
updateTopMenuGeometry( c );
|
|
updateCurrentTopMenu();
|
|
}
|
|
// kdDebug() << "NEW TOPMENU:" << c << endl;
|
|
}
|
|
|
|
void Workspace::removeTopMenu( Client* c )
|
|
{
|
|
// if( c->isTopMenu())
|
|
// kdDebug() << "REMOVE TOPMENU:" << c << endl;
|
|
assert( c->isTopMenu());
|
|
assert( topmenus.contains( c ));
|
|
topmenus.remove( c );
|
|
updateCurrentTopMenu();
|
|
// TODO reduce topMenuHeight() if possible?
|
|
}
|
|
|
|
void Workspace::lostTopMenuSelection()
|
|
{
|
|
// kdDebug() << "lost TopMenu selection" << endl;
|
|
// make sure this signal is always set when not owning the selection
|
|
disconnect( topmenu_watcher, TQT_SIGNAL( lostOwner()), this, TQT_SLOT( lostTopMenuOwner()));
|
|
connect( topmenu_watcher, TQT_SIGNAL( lostOwner()), this, TQT_SLOT( lostTopMenuOwner()));
|
|
if( !managing_topmenus )
|
|
return;
|
|
connect( topmenu_watcher, TQT_SIGNAL( lostOwner()), this, TQT_SLOT( lostTopMenuOwner()));
|
|
disconnect( topmenu_selection, TQT_SIGNAL( lostOwnership()), this, TQT_SLOT( lostTopMenuSelection()));
|
|
managing_topmenus = false;
|
|
delete topmenu_space;
|
|
topmenu_space = NULL;
|
|
updateClientArea();
|
|
for( ClientList::ConstIterator it = topmenus.begin();
|
|
it != topmenus.end();
|
|
++it )
|
|
(*it)->checkWorkspacePosition();
|
|
}
|
|
|
|
void Workspace::lostTopMenuOwner()
|
|
{
|
|
if( !options->topMenuEnabled())
|
|
return;
|
|
// kdDebug() << "TopMenu selection lost owner" << endl;
|
|
if( !topmenu_selection->claim( false ))
|
|
{
|
|
// kdDebug() << "Failed to claim TopMenu selection" << endl;
|
|
return;
|
|
}
|
|
// kdDebug() << "claimed TopMenu selection" << endl;
|
|
setupTopMenuHandling();
|
|
}
|
|
|
|
void Workspace::setupTopMenuHandling()
|
|
{
|
|
if( managing_topmenus )
|
|
return;
|
|
connect( topmenu_selection, TQT_SIGNAL( lostOwnership()), this, TQT_SLOT( lostTopMenuSelection()));
|
|
disconnect( topmenu_watcher, TQT_SIGNAL( lostOwner()), this, TQT_SLOT( lostTopMenuOwner()));
|
|
managing_topmenus = true;
|
|
topmenu_space = new TQWidget;
|
|
Window stack[ 2 ];
|
|
stack[ 0 ] = supportWindow->winId();
|
|
stack[ 1 ] = topmenu_space->winId();
|
|
XRestackWindows(tqt_xdisplay(), stack, 2);
|
|
updateTopMenuGeometry();
|
|
topmenu_space->show();
|
|
updateClientArea();
|
|
updateCurrentTopMenu();
|
|
}
|
|
|
|
int Workspace::topMenuHeight() const
|
|
{
|
|
if( topmenu_height == 0 )
|
|
{ // simply create a dummy menubar and use its preffered height as the menu height
|
|
KMenuBar tmpmenu;
|
|
tmpmenu.insertItem( "dummy" );
|
|
topmenu_height = tmpmenu.sizeHint().height();
|
|
}
|
|
return topmenu_height;
|
|
}
|
|
|
|
KDecoration* Workspace::createDecoration( KDecorationBridge* bridge )
|
|
{
|
|
return mgr->createDecoration( bridge );
|
|
}
|
|
|
|
TQString Workspace::desktopName( int desk ) const
|
|
{
|
|
return TQString::fromUtf8( rootInfo->desktopName( desk ) );
|
|
}
|
|
|
|
bool Workspace::checkStartupNotification( Window w, KStartupInfoId& id, KStartupInfoData& data )
|
|
{
|
|
return startup->checkStartup( w, id, data ) == KStartupInfo::Match;
|
|
}
|
|
|
|
/*!
|
|
Puts the focus on a dummy window
|
|
Just using XSetInputFocus() with None would block keyboard input
|
|
*/
|
|
void Workspace::focusToNull()
|
|
{
|
|
XSetInputFocus(tqt_xdisplay(), null_focus_window, RevertToPointerRoot, GET_QT_X_TIME() );
|
|
}
|
|
|
|
void Workspace::helperDialog( const TQString& message, const Client* c )
|
|
{
|
|
TQStringList args;
|
|
TQString type;
|
|
if( message == "noborderaltf3" )
|
|
{
|
|
TQString shortcut = TQString( "%1 (%2)" ).arg( keys->label( "Window Operations Menu" ))
|
|
.arg( keys->shortcut( "Window Operations Menu" ).seq( 0 ).toString());
|
|
args << "--msgbox" <<
|
|
i18n( "You have selected to show a window without its border.\n"
|
|
"Without the border, you will not be able to enable the border "
|
|
"again using the mouse: use the window operations menu instead, "
|
|
"activated using the %1 keyboard shortcut." )
|
|
.arg( shortcut );
|
|
type = "altf3warning";
|
|
}
|
|
else if( message == "fullscreenaltf3" )
|
|
{
|
|
TQString shortcut = TQString( "%1 (%2)" ).arg( keys->label( "Window Operations Menu" ))
|
|
.arg( keys->shortcut( "Window Operations Menu" ).seq( 0 ).toString());
|
|
args << "--msgbox" <<
|
|
i18n( "You have selected to show a window in fullscreen mode.\n"
|
|
"If the application itself does not have an option to turn the fullscreen "
|
|
"mode off you will not be able to disable it "
|
|
"again using the mouse: use the window operations menu instead, "
|
|
"activated using the %1 keyboard shortcut." )
|
|
.arg( shortcut );
|
|
type = "altf3warning";
|
|
}
|
|
else
|
|
assert( false );
|
|
KProcess proc;
|
|
proc << "kdialog" << args;
|
|
if( !type.isEmpty())
|
|
{
|
|
KConfig cfg( "twin_dialogsrc" );
|
|
cfg.setGroup( "Notification Messages" ); // this depends on KMessageBox
|
|
if( !cfg.readBoolEntry( type, true )) // has don't show again checked
|
|
return; // save launching kdialog
|
|
proc << "--dontagain" << "twin_dialogsrc:" + type;
|
|
}
|
|
if( c != NULL )
|
|
proc << "--embed" << TQString::number( c->window());
|
|
proc.start( KProcess::DontCare );
|
|
}
|
|
|
|
|
|
// kompmgr stuff
|
|
|
|
void Workspace::startKompmgr()
|
|
{
|
|
// See if the desktop is loaded yet
|
|
Atom type;
|
|
int format;
|
|
unsigned long length, after;
|
|
unsigned char* data_root;
|
|
Atom prop_root;
|
|
prop_root = XInternAtom(tqt_xdisplay(), "_XROOTPMAP_ID", False);
|
|
if( XGetWindowProperty( tqt_xdisplay(), tqt_xrootwin(), prop_root, 0L, 1L, False, AnyPropertyType, &type, &format, &length, &after, &data_root) == Success && data_root != NULL ) {
|
|
// Root pixmap is available; OK to load...
|
|
}
|
|
else {
|
|
// Try again a bit later!
|
|
TQTimer::singleShot( 200, this, TQT_SLOT(startKompmgr()) );
|
|
return;
|
|
}
|
|
if (!kompmgr || kompmgr->isRunning())
|
|
return;
|
|
if (!kompmgr->start(KProcess::OwnGroup, KProcess::Stderr))
|
|
{
|
|
options->useTranslucency = FALSE;
|
|
KProcess proc;
|
|
proc << "kdialog" << "--error"
|
|
<< i18n("The Composite Manager could not be started.\\nMake sure you have \"kompmgr\" in a $PATH directory.")
|
|
<< "--title" << "Composite Manager Failure";
|
|
proc.start(KProcess::DontCare);
|
|
}
|
|
else
|
|
{
|
|
delete kompmgr_selection;
|
|
char selection_name[ 100 ];
|
|
sprintf( selection_name, "_NET_WM_CM_S%d", DefaultScreen( tqt_xdisplay()));
|
|
kompmgr_selection = new KSelectionOwner( selection_name );
|
|
connect( kompmgr_selection, TQT_SIGNAL( lostOwnership()), TQT_SLOT( stopKompmgr()));
|
|
kompmgr_selection->claim( true );
|
|
connect(kompmgr, TQT_SIGNAL(processExited(KProcess*)), TQT_SLOT(restartKompmgr(KProcess*)));
|
|
options->useTranslucency = TRUE;
|
|
//allowKompmgrRestart = FALSE;
|
|
//TQTimer::singleShot( 60000, this, TQT_SLOT(unblockKompmgrRestart()) );
|
|
TQByteArray ba;
|
|
TQDataStream arg(ba, IO_WriteOnly);
|
|
arg << "";
|
|
kapp->dcopClient()->emitDCOPSignal("default", "kompmgrStarted()", ba);
|
|
}
|
|
if (popup){ delete popup; popup = 0L; } // to add/remove opacity slider
|
|
}
|
|
|
|
void Workspace::stopKompmgr()
|
|
{
|
|
if (!kompmgr || !kompmgr->isRunning()) {
|
|
return;
|
|
}
|
|
delete kompmgr_selection;
|
|
kompmgr_selection = NULL;
|
|
kompmgr->disconnect(this, TQT_SLOT(restartKompmgr(KProcess*)));
|
|
options->useTranslucency = FALSE;
|
|
if (popup){ delete popup; popup = 0L; } // to add/remove opacity slider
|
|
kompmgr->kill();
|
|
TQByteArray ba;
|
|
TQDataStream arg(ba, IO_WriteOnly);
|
|
arg << "";
|
|
kapp->dcopClient()->emitDCOPSignal("default", "kompmgrStopped()", ba);
|
|
}
|
|
|
|
bool Workspace::kompmgrIsRunning()
|
|
{
|
|
return kompmgr && kompmgr->isRunning();
|
|
}
|
|
|
|
void Workspace::unblockKompmgrRestart()
|
|
{
|
|
allowKompmgrRestart = TRUE;
|
|
}
|
|
|
|
void Workspace::restartKompmgr( KProcess *proc )
|
|
// this is for inernal purpose (crashhandling) only, usually you want to use workspace->stopKompmgr(); TQTimer::singleShot(200, workspace, TQT_SLOT(startKompmgr()));
|
|
{
|
|
bool crashed;
|
|
if (proc->signalled()) { // looks like kompmgr may have crashed
|
|
int exit_signal_number = proc->exitSignal();
|
|
if ( (exit_signal_number == SIGILL) || (exit_signal_number == SIGTRAP) || (exit_signal_number == SIGABRT) || (exit_signal_number == SIGSYS) || (exit_signal_number == SIGFPE) || (exit_signal_number == SIGBUS) || (exit_signal_number == SIGSEGV) ) {
|
|
crashed = true;
|
|
}
|
|
else {
|
|
crashed = false;
|
|
}
|
|
if (!allowKompmgrRestart) // uh oh, it exited recently already
|
|
{
|
|
delete kompmgr_selection;
|
|
kompmgr_selection = NULL;
|
|
options->useTranslucency = FALSE;
|
|
if (crashed) {
|
|
KProcess proc;
|
|
proc << "kdialog" << "--error"
|
|
<< i18n( "The Composite Manager crashed twice within a minute and is therefore disabled for this session.")
|
|
<< "--title" << i18n("Composite Manager Failure");
|
|
proc.start(KProcess::DontCare);
|
|
}
|
|
return;
|
|
}
|
|
if (!kompmgr)
|
|
return;
|
|
// this should be useless, i keep it for maybe future need
|
|
// if (!kcompmgr)
|
|
// {
|
|
// kompmgr = new KProcess;
|
|
// kompmgr->clearArguments();
|
|
// *kompmgr << "kompmgr";
|
|
// }
|
|
// -------------------
|
|
if (!kompmgr->start(KProcess::NotifyOnExit, KProcess::Stderr))
|
|
{
|
|
delete kompmgr_selection;
|
|
kompmgr_selection = NULL;
|
|
options->useTranslucency = FALSE;
|
|
KProcess proc;
|
|
proc << "kdialog" << "--error"
|
|
<< i18n("The Composite Manager could not be started.\\nMake sure you have \"kompmgr\" in a $PATH directory.")
|
|
<< "--title" << i18n("Composite Manager Failure");
|
|
proc.start(KProcess::DontCare);
|
|
}
|
|
else
|
|
{
|
|
allowKompmgrRestart = FALSE;
|
|
TQTimer::singleShot( 60000, this, TQT_SLOT(unblockKompmgrRestart()) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void Workspace::handleKompmgrOutput( KProcess* , char *buffer, int buflen)
|
|
{
|
|
TQString message;
|
|
TQString output = TQString::fromLocal8Bit( buffer, buflen );
|
|
if (output.contains("Started",false))
|
|
; // don't do anything, just pass to the connection release
|
|
else if (output.contains("Can't open display",false))
|
|
message = i18n("<qt><b>kompmgr failed to open the display</b><br>There is probably an invalid display entry in your ~/.xcompmgrrc.</qt>");
|
|
else if (output.contains("No render extension",false))
|
|
message = i18n("<qt><b>kompmgr cannot find the Xrender extension</b><br>You are using either an outdated or a crippled version of XOrg.<br>Get XOrg ≥ 6.8 from www.freedesktop.org.<br></qt>");
|
|
else if (output.contains("No composite extension",false))
|
|
message = i18n("<qt><b>Composite extension not found</b><br>You <i>must</i> use XOrg ≥ 6.8 for translucency and shadows to work.<br>Additionally, you need to add a new section to your X config file:<br>"
|
|
"<i>Section \"Extensions\"<br>"
|
|
"Option \"Composite\" \"Enable\"<br>"
|
|
"EndSection</i></qt>");
|
|
else if (output.contains("No damage extension",false))
|
|
message = i18n("<qt><b>Damage extension not found</b><br>You <i>must</i> use XOrg ≥ 6.8 for translucency and shadows to work.</qt>");
|
|
else if (output.contains("No XFixes extension",false))
|
|
message = i18n("<qt><b>XFixes extension not found</b><br>You <i>must</i> use XOrg ≥ 6.8 for translucency and shadows to work.</qt>");
|
|
else return; //skip others
|
|
// kompmgr startup failed or succeeded, release connection
|
|
kompmgr->closeStderr();
|
|
disconnect(kompmgr, TQT_SIGNAL(receivedStderr(KProcess*, char*, int)), this, TQT_SLOT(handleKompmgrOutput(KProcess*, char*, int)));
|
|
if( !message.isEmpty())
|
|
{
|
|
KProcess proc;
|
|
proc << "kdialog" << "--error"
|
|
<< message
|
|
<< "--title" << i18n("Composite Manager Failure");
|
|
proc.start(KProcess::DontCare);
|
|
}
|
|
}
|
|
|
|
|
|
void Workspace::setOpacity(unsigned long winId, unsigned int opacityPercent)
|
|
{
|
|
if (opacityPercent > 100) opacityPercent = 100;
|
|
for( ClientList::ConstIterator it = stackingOrder().begin(); it != stackingOrder().end(); it++ )
|
|
if (winId == (*it)->window())
|
|
{
|
|
(*it)->setOpacity(opacityPercent < 100, (unsigned int)((opacityPercent/100.0)*0xFFFFFFFF));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Workspace::setShadowSize(unsigned long winId, unsigned int shadowSizePercent)
|
|
{
|
|
//this is open to the user by dcop - to avoid stupid trials, we limit the max shadow size to 400%
|
|
if (shadowSizePercent > 400) shadowSizePercent = 400;
|
|
for( ClientList::ConstIterator it = stackingOrder().begin(); it != stackingOrder().end(); it++ )
|
|
if (winId == (*it)->window())
|
|
{
|
|
(*it)->setShadowSize(shadowSizePercent);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Workspace::setUnshadowed(unsigned long winId)
|
|
{
|
|
for( ClientList::ConstIterator it = stackingOrder().begin(); it != stackingOrder().end(); it++ )
|
|
if (winId == (*it)->window())
|
|
{
|
|
(*it)->setShadowSize(0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Workspace::setShowingDesktop( bool showing )
|
|
{
|
|
rootInfo->setShowingDesktop( showing );
|
|
showing_desktop = showing;
|
|
++block_showing_desktop;
|
|
if( showing_desktop )
|
|
{
|
|
showing_desktop_clients.clear();
|
|
++block_focus;
|
|
ClientList cls = stackingOrder();
|
|
// find them first, then minimize, otherwise transients may get minimized with the window
|
|
// they're transient for
|
|
for( ClientList::ConstIterator it = cls.begin();
|
|
it != cls.end();
|
|
++it )
|
|
{
|
|
if( (*it)->isOnCurrentDesktop() && (*it)->isShown( true ) && !(*it)->isSpecialWindow())
|
|
showing_desktop_clients.prepend( *it ); // topmost first to reduce flicker
|
|
}
|
|
for( ClientList::ConstIterator it = showing_desktop_clients.begin();
|
|
it != showing_desktop_clients.end();
|
|
++it )
|
|
(*it)->minimize(true);
|
|
--block_focus;
|
|
if( Client* desk = findDesktop( true, currentDesktop()))
|
|
requestFocus( desk );
|
|
}
|
|
else
|
|
{
|
|
for( ClientList::ConstIterator it = showing_desktop_clients.begin();
|
|
it != showing_desktop_clients.end();
|
|
++it )
|
|
(*it)->unminimize(true);
|
|
if( showing_desktop_clients.count() > 0 )
|
|
requestFocus( showing_desktop_clients.first());
|
|
showing_desktop_clients.clear();
|
|
}
|
|
--block_showing_desktop;
|
|
}
|
|
|
|
// Following Kicker's behavior:
|
|
// Changing a virtual desktop resets the state and shows the windows again.
|
|
// Unminimizing a window resets the state but keeps the windows hidden (except
|
|
// the one that was unminimized).
|
|
// A new window resets the state and shows the windows again, with the new window
|
|
// being active. Due to popular demand (#67406) by people who apparently
|
|
// don't see a difference between "show desktop" and "minimize all", this is not
|
|
// true if "showDesktopIsMinimizeAll" is set in twinrc. In such case showing
|
|
// a new window resets the state but doesn't show windows.
|
|
void Workspace::resetShowingDesktop( bool keep_hidden )
|
|
{
|
|
if( block_showing_desktop > 0 )
|
|
return;
|
|
rootInfo->setShowingDesktop( false );
|
|
showing_desktop = false;
|
|
++block_showing_desktop;
|
|
if( !keep_hidden )
|
|
{
|
|
for( ClientList::ConstIterator it = showing_desktop_clients.begin();
|
|
it != showing_desktop_clients.end();
|
|
++it )
|
|
(*it)->unminimize(true);
|
|
}
|
|
showing_desktop_clients.clear();
|
|
--block_showing_desktop;
|
|
}
|
|
|
|
// Activating/deactivating this feature works like this:
|
|
// When nothing is active, and the shortcut is pressed, global shortcuts are disabled
|
|
// (using global_shortcuts_disabled)
|
|
// When a window that has disabling forced is activated, global shortcuts are disabled.
|
|
// (using global_shortcuts_disabled_for_client)
|
|
// When a shortcut is pressed and global shortcuts are disabled (either by a shortcut
|
|
// or for a client), they are enabled again.
|
|
void Workspace::slotDisableGlobalShortcuts()
|
|
{
|
|
if( global_shortcuts_disabled || global_shortcuts_disabled_for_client )
|
|
disableGlobalShortcuts( false );
|
|
else
|
|
disableGlobalShortcuts( true );
|
|
}
|
|
|
|
static bool pending_dfc = false;
|
|
|
|
void Workspace::disableGlobalShortcutsForClient( bool disable )
|
|
{
|
|
if( global_shortcuts_disabled_for_client == disable )
|
|
return;
|
|
if( !global_shortcuts_disabled )
|
|
{
|
|
if( disable )
|
|
pending_dfc = true;
|
|
KIPC::sendMessageAll( KIPC::BlockShortcuts, disable );
|
|
// twin will get the kipc message too
|
|
}
|
|
}
|
|
|
|
void Workspace::disableGlobalShortcuts( bool disable )
|
|
{
|
|
KIPC::sendMessageAll( KIPC::BlockShortcuts, disable );
|
|
// twin will get the kipc message too
|
|
}
|
|
|
|
void Workspace::kipcMessage( int id, int data )
|
|
{
|
|
if( id != KIPC::BlockShortcuts )
|
|
return;
|
|
if( pending_dfc && data )
|
|
{
|
|
global_shortcuts_disabled_for_client = true;
|
|
pending_dfc = false;
|
|
}
|
|
else
|
|
{
|
|
global_shortcuts_disabled = data;
|
|
global_shortcuts_disabled_for_client = false;
|
|
}
|
|
// update also Alt+LMB actions etc.
|
|
for( ClientList::ConstIterator it = clients.begin();
|
|
it != clients.end();
|
|
++it )
|
|
(*it)->updateMouseGrab();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
#include "workspace.moc"
|