kwin/workspace.cpp
Lucas Murray e4ab19a936 Allow automatic grouping of unrelated windows by matching them with a
window rule ID.

svn path=/trunk/KDE/kdebase/workspace/; revision=1050040
2009-11-16 13:31:02 +00:00

2939 lines
101 KiB
C++

/********************************************************************
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>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
//#define QT_CLEAN_NAMESPACE
#include "workspace.h"
#include <kapplication.h>
#include <kstartupinfo.h>
#include <fixx11h.h>
#include <kconfig.h>
#include <kglobal.h>
#include <klocale.h>
#include <QRegExp>
#include <QPainter>
#include <QBitmap>
#include <QClipboard>
#include <kmenubar.h>
#include <kprocess.h>
#include <kglobalaccel.h>
#include <QToolButton>
#include <kactioncollection.h>
#include <kaction.h>
#include <kconfiggroup.h>
#include <kcmdlineargs.h>
#include <QtDBus/QtDBus>
#include "client.h"
#include "tabbox.h"
#include "desktopchangeosd.h"
#include "atoms.h"
#include "placement.h"
#include "notifications.h"
#include "group.h"
#include "rules.h"
#include "kwinadaptor.h"
#include "unmanaged.h"
#include "scene.h"
#include "deleted.h"
#include "effects.h"
#include <X11/extensions/shape.h>
#include <X11/keysym.h>
#include <X11/keysymdef.h>
#include <X11/cursorfont.h>
#include <QX11Info>
#include <stdio.h>
#include <kauthorized.h>
#include <ktoolinvocation.h>
#include <kglobalsettings.h>
#include <kephal/screens.h>
namespace KWin
{
extern int screen_number;
Workspace* Workspace::_self = 0;
//-----------------------------------------------------------------------------
// 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 dissagree 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 )
: QObject( 0 )
// Desktop layout
, desktopCount_( 0 ) // This is an invalid state
, desktopGridSize_( 1, 2 ) // Default to two rows
, desktopGrid_( new int[2] )
, currentDesktop_( 0 )
, desktopLayoutDynamicity_( false )
// Unsorted
, active_popup( NULL )
, active_popup_client( NULL )
, temporaryRulesMessages( "_KDE_NET_WM_TEMPORARY_RULES", NULL, false )
, rules_updates_disabled( false )
, active_client( 0 )
, last_active_client( 0 )
, most_recently_raised( 0 )
, movingClient( 0 )
, pending_take_activity( NULL )
, active_screen( 0 )
, delayfocus_client( 0 )
, force_restacking( false )
, x_stacking_dirty( true )
, 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 )
, desktop_change_osd( 0 )
, popup( 0 )
, advanced_popup( 0 )
, trans_popup( 0 )
, desk_popup( 0 )
, add_tabs_popup( 0 )
, switch_to_tab_popup( 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 )
, workspaceInit( true )
, startup( 0 )
, 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 )
, cm_selection( NULL )
, compositingSuspended( false )
, compositeRate( 0 )
, xrrRefreshRate( 0 )
, overlay( None )
, overlay_visible( true )
, overlay_shown( false )
, transSlider( NULL )
, transButton( NULL )
, forceUnredirectCheck( true )
{
(void) new KWinAdaptor( this );
QDBusConnection dbus = QDBusConnection::sessionBus();
dbus.registerObject( "/KWin", this );
dbus.connect( QString(), "/KWin", "org.kde.KWin", "reloadConfig",
this, SLOT( slotReloadConfig() ));
dbus.connect( QString(), "/KWin", "org.kde.KWin", "reinitCompositing",
this, SLOT( slotReinitCompositing() ));
// Initialize desktop grid array
desktopGrid_[0] = 0;
desktopGrid_[1] = 0;
_self = this;
mgr = new PluginMgr;
QX11Info info;
default_colormap = DefaultColormap( display(), info.screen() );
installed_colormap = default_colormap;
for( int i = 0; i < ELECTRIC_COUNT; ++i )
{
electric_reserved[i] = 0;
electric_windows[i] = None;
}
connect( &temporaryRulesMessages, SIGNAL( gotMessage(const QString&) ),
this, SLOT( gotTemporaryRulesMessage(const QString&) ));
connect( &rulesUpdatedTimer, SIGNAL( timeout() ), this, SLOT( writeWindowRules() ));
connect( &unredirectTimer, SIGNAL( timeout() ), this, SLOT( delayedCheckUnredirect() ));
connect( &compositeResetTimer, SIGNAL( timeout()), this, SLOT( resetCompositing()));
unredirectTimer.setSingleShot( true );
compositeResetTimer.setSingleShot( true );
updateXTime(); // Needed for proper initialization of user_time in Client ctor
delayFocusTimer = 0;
if( restore )
loadSessionInfo();
loadWindowRules();
// Call this before XSelectInput() on the root window
startup = new KStartupInfo(
KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this );
// Select windowmanager privileges
XSelectInput( display(), rootWindow(),
KeyPressMask |
PropertyChangeMask |
ColormapChangeMask |
SubstructureRedirectMask |
SubstructureNotifyMask |
FocusChangeMask | // For NotifyDetailNone
ExposureMask
);
Extensions::init();
setupCompositing();
// Compatibility
long data = 1;
XChangeProperty(
display(),
rootWindow(),
atoms->kwin_running,
atoms->kwin_running,
32,
PropModeAppend,
(unsigned char*)( &data ),
1
);
client_keys = new KActionCollection( this );
initShortcuts();
tab_box = new TabBox::TabBox( this );
desktop_change_osd = new DesktopChangeOSD( this );
init();
connect( Kephal::Screens::self(), SIGNAL( screenAdded(Kephal::Screen*) ), SLOT( desktopResized() ));
connect( Kephal::Screens::self(), SIGNAL( screenRemoved(int) ), SLOT( desktopResized() ));
connect( Kephal::Screens::self(), SIGNAL( screenResized(Kephal::Screen*, QSize, QSize) ), SLOT( desktopResized() ));
connect( Kephal::Screens::self(), SIGNAL( screenMoved(Kephal::Screen*, QPoint, QPoint) ), SLOT( desktopResized() ));
}
void Workspace::init()
{
reserveElectricBorderActions( true );
if( options->electricBorders() == Options::ElectricAlways )
reserveElectricBorderSwitching( true );
updateElectricBorders();
// Not used yet
//topDock = 0L;
//maximizedWindowCounter = 0;
supportWindow = new QWidget( NULL, Qt::X11BypassWindowManagerHint );
XLowerWindow( display(), supportWindow->winId() ); // See usage in layers.cpp
XSetWindowAttributes attr;
attr.override_redirect = 1;
null_focus_window = XCreateWindow( display(), rootWindow(), -1,-1, 1, 1, 0, CopyFromParent,
InputOnly, CopyFromParent, CWOverrideRedirect, &attr );
XMapWindow( display(), 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::WMName |
NET::WMVisibleName |
NET::WMDesktop |
NET::WMWindowType |
NET::WMState |
NET::WMStrut |
NET::WMIconGeometry |
NET::WMIcon |
NET::WMPid |
NET::WMMoveResize |
NET::WMFrameExtents |
NET::WMPing
,
NET::NormalMask |
NET::DesktopMask |
NET::DockMask |
NET::ToolbarMask |
NET::MenuMask |
NET::DialogMask |
NET::OverrideMask |
NET::TopMenuMask |
NET::UtilityMask |
NET::SplashMask |
// No compositing window types here unless we support them also as managed window types
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::WM2DesktopLayout |
NET::WM2FullPlacement |
NET::WM2FullscreenMonitors |
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
,
};
QX11Info info;
rootInfo = new RootInfo( this, display(), supportWindow->winId(), "KWin", protocols, 5, info.screen() );
loadDesktopSettings();
updateDesktopLayout();
desktop_change_osd->numberDesktopsChanged();
// Extra NETRootInfo instance in Client mode is needed to get the values of the properties
NETRootInfo client_info( display(), NET::ActiveWindow | NET::CurrentDesktop );
int initial_desktop;
if( !kapp->isSessionRestored() )
initial_desktop = client_info.currentDesktop();
else
{
KConfigGroup group( kapp->sessionConfig(), "Session" );
initial_desktop = group.readEntry( "desktop", 1 );
}
if( !setCurrentDesktop( initial_desktop ))
setCurrentDesktop( 1 );
// Now we know how many desktops we'll have, thus we initialize the positioning object
initPositioning = new Placement( this );
reconfigureTimer.setSingleShot( true );
updateToolWindowsTimer.setSingleShot( true );
connect( &reconfigureTimer, SIGNAL( timeout() ), this, SLOT( slotReconfigure() ));
connect( &updateToolWindowsTimer, SIGNAL( timeout() ), this, SLOT( slotUpdateToolWindows() ));
connect( &compositeTimer, SIGNAL( timeout() ), SLOT( performCompositing() ));
connect( &mousePollingTimer, SIGNAL( timeout() ), SLOT( performMousePoll() ));
connect( KGlobalSettings::self(), SIGNAL( appearanceChanged() ), this, SLOT( reconfigure() ));
connect( KGlobalSettings::self(), SIGNAL( settingsChanged(int) ), this, SLOT( slotSettingsChanged(int) ));
connect( KGlobalSettings::self(), SIGNAL( blockShortcuts(int) ), this, SLOT( slotBlockShortcuts(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( display()));
Atom topmenu_atom = XInternAtom( display(), 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;
Window* wins;
XQueryTree( display(), rootWindow(), &root_return, &parent_return, &wins, &nwins );
bool fixoffset = KCmdLineArgs::parsedArgs()->getOption( "crashes" ).toInt() > 0;
for( i = 0; i < nwins; i++ )
{
XWindowAttributes attr;
XGetWindowAttributes( display(), wins[i], &attr );
if( attr.override_redirect )
{
createUnmanaged( wins[i] );
continue;
}
if( topmenu_space && topmenu_space->winId() == wins[i] )
continue;
if( attr.map_state != IsUnmapped )
{
if( fixoffset )
fixPositionAfterCrash( wins[ i ], attr );
createClient( wins[i], true );
}
}
if( wins )
XFree( (void*)( wins ));
// Propagate clients, will really happen at the end of the updates blocker block
updateStackingOrder( true );
updateClientArea();
// NETWM spec says we have to set it to (0,0) if we don't support it
NETPoint* viewports = new NETPoint[numberOfDesktops()];
rootInfo->setDesktopViewport( numberOfDesktops(), *viewports );
delete[] viewports;
QRect geom = Kephal::ScreenUtils::desktopGeometry();
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(), -1 );
if( new_active_client == NULL && !desktops.isEmpty() )
new_active_client = findDesktop( true, currentDesktop() );
}
if( new_active_client != NULL )
activateClient( new_active_client );
// outline windows for electric border maximize window mode
outline_left = XCreateWindow( QX11Info::display(), QX11Info::appRootWindow(), 0, 0, 1, 1, 0,
CopyFromParent, CopyFromParent, CopyFromParent, CWOverrideRedirect, &attr );
outline_right = XCreateWindow( QX11Info::display(), QX11Info::appRootWindow(), 0, 0, 1, 1, 0,
CopyFromParent, CopyFromParent, CopyFromParent, CWOverrideRedirect, &attr );
outline_top = XCreateWindow( QX11Info::display(), QX11Info::appRootWindow(), 0, 0, 1, 1, 0,
CopyFromParent, CopyFromParent, CopyFromParent, CWOverrideRedirect, &attr );
outline_bottom = XCreateWindow( QX11Info::display(), QX11Info::appRootWindow(), 0, 0, 1, 1, 0,
CopyFromParent, CopyFromParent, CopyFromParent, CWOverrideRedirect, &attr );
// 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()
{
finishCompositing();
blockStackingUpdates( true );
// TODO: grabXServer();
// Use stacking_order, so that kwin --replace keeps stacking order
for( ClientList::ConstIterator it = stacking_order.constBegin();
it != stacking_order.constEnd();
++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.removeAll( *it );
desktops.removeAll( *it );
}
for( UnmanagedList::ConstIterator it = unmanaged.constBegin();
it != unmanaged.constEnd();
++it )
(*it)->release();
delete tab_box;
delete desktop_change_osd;
discardPopup();
XDeleteProperty( display(), rootWindow(), atoms->kwin_running );
writeWindowRules();
KGlobal::config()->sync();
// destroy outline windows for electric border maximize window mode
XDestroyWindow( QX11Info::display(), outline_left );
XDestroyWindow( QX11Info::display(), outline_right );
XDestroyWindow( QX11Info::display(), outline_top );
XDestroyWindow( QX11Info::display(), outline_bottom );
delete rootInfo;
delete supportWindow;
delete mgr;
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();
}
foreach( SessionInfo* s, session )
delete s;
XDestroyWindow( display(), null_focus_window );
// TODO: ungrabXServer();
delete[] desktopGrid_;
_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 );
if( scene )
scene->windowAdded( c );
if( effects )
static_cast<EffectsHandlerImpl*>( effects )->windowAdded( c->effectWindow() );
return c;
}
Unmanaged* Workspace::createUnmanaged( Window w )
{
if( w == overlay )
return NULL;
Unmanaged* c = new Unmanaged( this );
if( !c->track( w ))
{
Unmanaged::deleteUnmanaged( c, Allowed );
return NULL;
}
addUnmanaged( c, Allowed );
if( scene )
scene->windowAdded( c );
if( effects )
static_cast<EffectsHandlerImpl*>( effects )->windowAdded( c->effectWindow() );
return c;
}
void Workspace::addClient( Client* c, allowed_t )
{
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 ); // TODO: 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 ); // Raise if it hasn't got any stacking position yet
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 );
x_stacking_dirty = true;
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 TODO: Does this really belong here?
updateStackingOrder( true ); // Propagate new client
if( c->isUtility() || c->isMenu() || c->isToolbar() )
updateToolWindows( true );
checkNonExistentClients();
if( tab_grab )
tab_box->reset( true );
}
void Workspace::addUnmanaged( Unmanaged* c, allowed_t )
{
unmanaged.append( c );
x_stacking_dirty = true;
}
/**
* 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().isEmpty() )
{
c->setShortcut( QString() ); // Remove from client_keys
clientShortcutUpdated( c ); // Needed, since this is otherwise delayed by setShortcut() and wouldn't run
}
if( c->isDialog())
Notify::raise( Notify::TransDelete );
if( c->isNormalWindow())
Notify::raise( Notify::Delete );
if( tab_grab && tab_box->currentClient() == c )
tab_box->nextPrev( true );
Q_ASSERT( clients.contains( c ) || desktops.contains( c ));
clients.removeAll( c );
desktops.removeAll( c );
unconstrained_stacking_order.removeAll( c );
stacking_order.removeAll( c );
x_stacking_dirty = true;
for( int i = 1; i <= numberOfDesktops(); ++i )
focus_chain[i].removeAll( c );
global_focus_chain.removeAll( c );
attention_chain.removeAll( c );
showing_desktop_clients.removeAll( 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.removeAll( 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->reset( true );
updateClientArea();
}
void Workspace::removeUnmanaged( Unmanaged* c, allowed_t )
{
assert( unmanaged.contains( c ));
unmanaged.removeAll( c );
x_stacking_dirty = true;
}
void Workspace::addDeleted( Deleted* c, allowed_t )
{
assert( !deleted.contains( c ));
deleted.append( c );
x_stacking_dirty = true;
}
void Workspace::removeDeleted( Deleted* c, allowed_t )
{
assert( deleted.contains( c ));
if( scene )
scene->windowDeleted( c );
if( effects )
static_cast<EffectsHandlerImpl*>( effects )->windowDeleted( c->effectWindow() );
deleted.removeAll( c );
x_stacking_dirty = true;
}
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].removeAll( c );
global_focus_chain.removeAll( 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].removeAll( 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].size() - 1, 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].removeAll( c );
focus_chain[i].append( c );
}
else if( change == FocusChainMakeLast )
{
focus_chain[i].removeAll( c );
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].size() - 1, c );
else
focus_chain[i].append( c ); // Otherwise add as the first one
}
}
else
focus_chain[i].removeAll( c );
}
}
if( change == FocusChainMakeFirst )
{
global_focus_chain.removeAll( c );
global_focus_chain.append( c );
}
else if( change == FocusChainMakeLast )
{
global_focus_chain.removeAll( c );
global_focus_chain.prepend( c );
}
else if( !global_focus_chain.contains( c ))
{ // Add it after the active one
if( active_client != NULL && active_client != c &&
!global_focus_chain.isEmpty() && global_focus_chain.last() == active_client )
global_focus_chain.insert( global_focus_chain.size() - 1, c );
else
global_focus_chain.append( c ); // Otherwise add as the first one
}
}
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().constBegin();
it != menu_client->transients().constEnd();
++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().constBegin();
it != active_client->group()->members().constEnd();
++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().constBegin();
it != desktop->transients().constEnd();
++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.constBegin();
it != topmenus.constEnd();
++it )
// kdesktop's topmenu has WM_TRANSIENT_FOR set pointing to the root window
// to recognize it here. Also, with the xroot hack in kdesktop, there's
// no NET::Desktop window to be transient for.
if( (*it)->wasOriginallyGroupTransient() )
{
menubar = *it;
break;
}
}
}
//kDebug( 1212 ) << "CURRENT TOPMENU:" << menubar << ":" << active_client;
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.removeAll( 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.constBegin(); it != clients.constEnd(); ++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.constBegin();
it != clients.constEnd();
++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 TODO: 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.constBegin();
it != stacking_order.constEnd();
++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.constBegin();
it2 != mainclients.constEnd();
++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( int i = to_show.size() - 1;
i >= 0;
--i ) // From topmost
// TODO: Since this is in stacking order, the order of taskbar entries changes :(
to_show.at( i )->hideClient( false );
if( also_hide )
{
for( ClientList::ConstIterator it = to_hide.constBegin();
it != to_hide.constEnd();
++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 );
}
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( display(), cmap );
installed_colormap = cmap;
}
}
void Workspace::slotReloadConfig()
{
reconfigure();
}
void Workspace::reconfigure()
{
reconfigureTimer.start( 200 );
}
/**
* This D-Bus call is used by the compositing kcm. Since the reconfigure()
* D-Bus call delays the actual reconfiguring, it is not possible to immediately
* call compositingActive(). Therefore the kcm will instead call this to ensure
* the reconfiguring has already happened.
*/
bool Workspace::waitForCompositingSetup()
{
if( reconfigureTimer.isActive() )
{
reconfigureTimer.stop();
slotReconfigure();
}
return compositingActive();
}
void Workspace::slotSettingsChanged( int category )
{
kDebug( 1212 ) << "Workspace::slotSettingsChanged()";
if( category == KGlobalSettings::SETTINGS_SHORTCUTS )
readShortcuts();
}
/**
* Reread settings
*/
KWIN_PROCEDURE( CheckBorderSizesProcedure, Client, cl->checkBorderSizes( true ));
void Workspace::slotReconfigure()
{
kDebug( 1212 ) << "Workspace::slotReconfigure()";
reconfigureTimer.stop();
reserveElectricBorderActions( false );
if( options->electricBorders() == Options::ElectricAlways )
reserveElectricBorderSwitching( false );
KGlobal::config()->reparseConfiguration();
unsigned long changed = options->updateSettings();
tab_box->reconfigure();
desktop_change_osd->reconfigure();
initPositioning->reinitCascading( 0 );
readShortcuts();
forEachClient( CheckIgnoreFocusStealingProcedure());
updateToolWindows( true );
if( mgr->reset( changed ))
{ // Decorations need to be recreated
// This actually seems to make things worse now
//QWidget curtain;
//curtain.setBackgroundMode( NoBackground );
//curtain.setGeometry( Kephal::ScreenUtils::desktopGeometry() );
//curtain.show();
for( ClientList::ConstIterator it = clients.constBegin();
it != clients.constEnd();
++it )
(*it)->updateDecoration( true, true );
// If the new decoration doesn't supports tabs then ungroup clients
if( !decorationSupportsClientGrouping() )
{
QList<ClientGroup*> tmpGroups = clientGroups; // Prevent crashing
for( QList<ClientGroup*>::const_iterator i = tmpGroups.constBegin(); i != tmpGroups.constEnd(); i++ )
(*i)->removeAll();
}
mgr->destroyPreviousPlugin();
}
else
{
forEachClient( CheckBorderSizesProcedure() );
foreach( Client* c, clients )
c->triggerDecorationRepaint();
}
reserveElectricBorderActions( true );
if( options->electricBorders() == Options::ElectricAlways )
reserveElectricBorderSwitching( true );
updateElectricBorders();
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();
}
if( options->useCompositing && !compositingSuspended )
{
setupCompositing();
if( effects ) // setupCompositing() may fail
effects->reconfigure();
addRepaintFull();
}
else
finishCompositing();
loadWindowRules();
for( ClientList::Iterator it = clients.begin();
it != clients.end();
++it )
{
(*it)->setupWindowRules( true );
(*it)->applyWindowRules();
discardUsedWindowRules( *it, false );
}
}
void Workspace::slotReinitCompositing()
{
// Reparse config. Config options will be reloaded by setupCompositing()
KGlobal::config()->reparseConfiguration();
options->updateSettings();
// Update any settings that can be set in the compositing kcm.
updateElectricBorders();
// Restart compositing
finishCompositing();
// resume compositing if suspended
compositingSuspended = false;
setupCompositing();
KDecorationFactory* factory = mgr->factory();
factory->reset(SettingCompositing);
if( effects ) // setupCompositing() may fail
{
effects->reconfigure();
emit compositingToggled( true );
}
}
void Workspace::loadDesktopSettings()
{
KSharedConfig::Ptr c = KGlobal::config();
QString groupname;
if( screen_number == 0 )
groupname = "Desktops";
else
groupname.sprintf( "Desktops-screen-%d", screen_number );
KConfigGroup group( c, groupname );
int n = group.readEntry( "Number", 4 );
desktopCount_ = n;
workarea.clear();
workarea.resize( n + 1 );
restrictedmovearea.clear();
restrictedmovearea.resize( n + 1 );
oldrestrictedmovearea.clear();
oldrestrictedmovearea.resize( n + 1 );
screenarea.clear();
rootInfo->setNumberOfDesktops( n );
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++ )
{
QString s = group.readEntry( QString( "Name_%1" ).arg( i ), i18n( "Desktop %1", i ));
rootInfo->setDesktopName( i, s.toUtf8().data() );
desktop_focus_chain[i-1] = i;
}
}
void Workspace::saveDesktopSettings()
{
KSharedConfig::Ptr c = KGlobal::config();
QString groupname;
if (screen_number == 0)
groupname = "Desktops";
else
groupname.sprintf( "Desktops-screen-%d", screen_number );
KConfigGroup group( c, groupname );
group.writeEntry( "Number", numberOfDesktops() );
for( int i = 1; i <= numberOfDesktops(); i++ )
{
QString s = desktopName( i );
QString defaultvalue = i18n( "Desktop %1", i );
if( s.isEmpty() )
{
s = defaultvalue;
rootInfo->setDesktopName( i, s.toUtf8().data() );
}
if( s != defaultvalue )
{
group.writeEntry( QString( "Name_%1" ).arg( i ), s );
}
else
{
QString currentvalue = group.readEntry( QString( "Name_%1" ).arg( i ), QString() );
if( currentvalue != defaultvalue )
group.writeEntry( QString( "Name_%1" ).arg( i ), "" );
}
}
// Save to disk
group.sync();
}
QStringList Workspace::configModules( bool controlCenter )
{
QStringList args;
args << "kwindecoration";
if( controlCenter )
args << "kwinoptions";
else if( KAuthorized::authorizeControlModule( "kde-kwinoptions.desktop" ))
args << "kwinactions" << "kwinfocus" << "kwinmoving" << "kwinadvanced"
<< "kwinrules" << "kwincompositing" << "kwintabbox";
return args;
}
void Workspace::configureWM()
{
QStringList args;
args << "--icon" << "preferences-system-windows" << configModules( false );
KToolInvocation::kdeinitExec( "kcmshell4", args );
}
/**
* Avoids managing a window with title \a title
*/
void Workspace::doNotManage( const QString& title )
{
doNotManageList.append( title );
}
/**
* Hack for java applets
*/
bool Workspace::isNotManaged( const QString& title )
{
for( QStringList::Iterator it = doNotManageList.begin(); it != doNotManageList.end(); ++it )
{
QRegExp r( (*it) );
if( r.indexIn(title) != -1 )
{
doNotManageList.erase( it );
return true;
}
}
return false;
}
/**
* Refreshes all the client windows
*/
void Workspace::refresh()
{
QWidget w( NULL, Qt::X11BypassWindowManagerHint );
w.setGeometry( Kephal::ScreenUtils::desktopGeometry() );
w.show();
w.hide();
QApplication::flush();
}
/**
* 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:
QList<Window> obscuring_windows;
static QList<Window>* cached;
static unsigned int max_cache_size;
};
QList<Window>* ObscuringWindows::cached = 0;
unsigned int ObscuringWindows::max_cache_size = 0;
void ObscuringWindows::create( Client* c )
{
if( compositing() )
return; // Not needed with compositing
if( cached == 0 )
cached = new QList<Window>;
Window obs_win;
XWindowChanges chngs;
int mask = CWSibling | CWStackMode;
if( cached->count() > 0 )
{
cached->removeAll( 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( display(), rootWindow(), c->x(), c->y(),
c->width(), c->height(), 0, CopyFromParent, InputOutput,
CopyFromParent, CWBackPixmap | CWOverrideRedirect, &a );
}
chngs.sibling = c->frameId();
chngs.stack_mode = Below;
XConfigureWindow( display(), obs_win, mask, &chngs );
XMapWindow( display(), obs_win );
obscuring_windows.append( obs_win );
}
ObscuringWindows::~ObscuringWindows()
{
max_cache_size = qMax( int( max_cache_size ), obscuring_windows.count() + 4 ) - 1;
for( QList<Window>::ConstIterator it = obscuring_windows.constBegin();
it != obscuring_windows.constEnd();
++it )
{
XUnmapWindow( display(), *it );
if( cached->count() < int( max_cache_size ))
cached->prepend( *it );
else
XDestroyWindow( display(), *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 > numberOfDesktops() )
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 = currentDesktop();
if (new_desktop != currentDesktop() )
{
++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;
currentDesktop_ = new_desktop; // Change the desktop (so that Client::updateVisibility() works)
for( ClientList::ConstIterator it = stacking_order.constBegin();
it != stacking_order.constEnd();
++it )
if( !(*it)->isOnDesktop( new_desktop ) && (*it) != movingClient )
{
if( (*it)->isShown( true ) && (*it)->isOnDesktop( old_desktop ))
obs_wins.create( *it );
(*it)->updateVisibility();
}
// Now propagate the change, after hiding, before showing
rootInfo->setCurrentDesktop( currentDesktop() );
if( movingClient && !movingClient->isOnDesktop( new_desktop ))
movingClient->setDesktop( new_desktop );
for( int i = stacking_order.size() - 1; i >= 0 ; --i )
if( stacking_order.at( i )->isOnDesktop( new_desktop ))
stacking_order.at( i )->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( int i = focus_chain[currentDesktop()].size() - 1; i >= 0; --i )
{
if( focus_chain[currentDesktop()].at( i )->isShown( false ) &&
focus_chain[currentDesktop()].at( i )->isOnCurrentDesktop() )
{
c = focus_chain[currentDesktop()].at( i );
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 if( !desktops.isEmpty() )
requestFocus( findDesktop( true, currentDesktop() ));
else
focusToNull();
updateCurrentTopMenu();
// Update focus chain:
// If input: chain = { 1, 2, 3, 4 } and currentDesktop() = 3,
// Output: chain = { 3, 1, 2, 4 }.
//kDebug(1212) << QString("Switching to desktop #%1, at focus_chain index %2\n")
// .arg(currentDesktop()).arg(desktop_focus_chain.find( currentDesktop() ));
for( int i = desktop_focus_chain.indexOf( currentDesktop() ); i > 0; i-- )
desktop_focus_chain[i] = desktop_focus_chain[i-1];
desktop_focus_chain[0] = currentDesktop();
//QString s = "desktop_focus_chain[] = { ";
//for( uint i = 0; i < desktop_focus_chain.size(); i++ )
// s += QString::number( desktop_focus_chain[i] ) + ", ";
//kDebug( 1212 ) << s << "}\n";
// Not for the very first time, only if something changed and there are more than 1 desktops
if( old_desktop != 0 && old_desktop != new_desktop && numberOfDesktops() > 1 )
desktop_change_osd->desktopChanged( old_desktop );
if( effects != NULL && old_desktop != 0 && old_desktop != new_desktop )
static_cast<EffectsHandlerImpl*>( effects )->desktopChanged( old_desktop );
if( compositing())
addRepaintFull();
return true;
}
/**
* Called only from D-Bus
*/
void Workspace::nextDesktop()
{
int desktop = currentDesktop() + 1;
setCurrentDesktop( desktop > numberOfDesktops() ? 1 : desktop );
}
/**
* Called only from D-Bus
*/
void Workspace::previousDesktop()
{
int desktop = currentDesktop() - 1;
setCurrentDesktop( desktop > 0 ? desktop : numberOfDesktops() );
}
/**
* Sets the number of virtual desktops to \a n
*/
void Workspace::setNumberOfDesktops( int n )
{
if( n == numberOfDesktops() )
return;
int old_number_of_desktops = numberOfDesktops();
desktopCount_ = n;
updateDesktopLayout(); // Make sure the layout is still valid
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 < numberOfDesktops() )
{
rootInfo->setNumberOfDesktops( numberOfDesktops() );
NETPoint* viewports = new NETPoint[numberOfDesktops()];
rootInfo->setDesktopViewport( numberOfDesktops(), *viewports );
delete[] viewports;
updateClientArea( true );
focus_chain.resize( numberOfDesktops() + 1 );
}
// If the number of desktops decreased, move all windows
// that would be hidden to the last visible desktop
if( old_number_of_desktops > numberOfDesktops() )
{
for( ClientList::ConstIterator it = clients.constBegin();
it != clients.constEnd();
++it)
if( !(*it)->isOnAllDesktops() && (*it)->desktop() > numberOfDesktops() )
sendClientToDesktop( *it, numberOfDesktops(), true );
}
if( old_number_of_desktops > numberOfDesktops() )
{
rootInfo->setNumberOfDesktops( numberOfDesktops() );
NETPoint* viewports = new NETPoint[numberOfDesktops()];
rootInfo->setDesktopViewport( numberOfDesktops(), *viewports );
delete[] viewports;
updateClientArea( true );
focus_chain.resize( numberOfDesktops() + 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;
// reset the desktop change osd
desktop_change_osd->numberDesktopsChanged();
}
/**
* 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.constBegin();
it != transients_stacking_order.constEnd();
++it )
sendClientToDesktop( *it, desk, dont_activate );
updateClientArea();
}
int Workspace::numScreens() const
{
if( !options->xineramaEnabled )
return 1;
return Kephal::ScreenUtils::numScreens();
}
int Workspace::activeScreen() const
{
if( !options->xineramaEnabled )
return 0;
if( !options->activeMouseScreen )
{
if( activeClient() != NULL && !activeClient()->isOnScreen( active_screen ))
return activeClient()->screen();
return active_screen;
}
return Kephal::ScreenUtils::screenId( cursorPos());
}
/**
* 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 occurred
*/
void Workspace::setActiveScreenMouse( const QPoint& mousepos )
{
if( !options->xineramaEnabled )
return;
active_screen = Kephal::ScreenUtils::screenId( mousepos );
}
QRect Workspace::screenGeometry( int screen ) const
{
if( !options->xineramaEnabled )
return Kephal::ScreenUtils::desktopGeometry();
return Kephal::ScreenUtils::screenGeometry( screen );
}
int Workspace::screenNumber( const QPoint& pos ) const
{
if( !options->xineramaEnabled )
return 0;
return Kephal::ScreenUtils::screenId( pos );
}
void Workspace::sendClientToScreen( Client* c, int screen )
{
if( c->screen() == screen ) // Don't use isOnScreen(), that's true even when only partially
return;
GeometryUpdatesBlocker blocker( c );
QRect old_sarea = clientArea( MaximizeArea, c );
QRect 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.constBegin();
it != transients_stacking_order.constEnd();
++it )
sendClientToScreen( *it, screen );
if( c->isActive() )
active_screen = screen;
}
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 )
break; // Found the client
Window parent, root;
Window* children;
unsigned int children_count;
XQueryTree( display(), 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( client != NULL )
client->killWindow();
else
XKillClient( display(), window_to_kill );
}
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 )
{
QPixmap snapshot = QPixmap::grabWindow( active_client->frameId() );
// No XShape - no work.
if( Extensions::shapeAvailable() )
{
// As the first step, get the mask from XShape.
int count, order;
XRectangle* rects = XShapeGetRectangles( display(), 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 QRegion from the rectangles describing the bounding mask.
QRegion contents;
for( int pos = 0; pos < count; pos++ )
contents += QRegion( rects[pos].x, rects[pos].y,
rects[pos].width, rects[pos].height);
XFree( rects );
// Create the bounding box.
QRegion bbox( 0, 0, snapshot.width(), snapshot.height() );
// Get the masked away area.
QRegion maskedAway = bbox - contents;
QVector<QRect> maskedAwayRects = maskedAway.rects();
// Construct a bitmap mask from the rectangles
QBitmap mask( snapshot.width(), snapshot.height() );
QPainter p( &mask );
p.fillRect( 0, 0, mask.width(), mask.height(), Qt::color1 );
for( int pos = 0; pos < maskedAwayRects.count(); pos++ )
p.fillRect( maskedAwayRects[pos], Qt::color0 );
p.end();
snapshot.setMask( mask );
}
}
QClipboard* cb = QApplication::clipboard();
cb->setPixmap( snapshot );
}
else
slotGrabDesktop();
}
/**
* Takes a screenshot of the whole desktop and puts it in the clipboard.
*/
void Workspace::slotGrabDesktop()
{
QPixmap p = QPixmap::grabWindow( rootWindow() );
QClipboard* cb = QApplication::clipboard();
cb->setPixmap( p );
}
/**
* Invokes keyboard mouse emulation
*/
void Workspace::slotMouseEmulation()
{
if( mouse_emulation )
{
ungrabXKeyboard();
mouse_emulation = false;
return;
}
if( grabXKeyboard() )
{
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 = rootWindow();
int root_x, root_y, lx, ly;
uint state;
Window w;
Client * c = 0;
do
{
w = child;
if( !c )
c = findClient( FrameIdMatchPredicate( w ));
XQueryPointer( display(), 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( const QPoint& pos, WId w, MouseEmulation type,
int button, unsigned int state )
{
if ( !w )
return state;
QWidget* widget = QWidget::find( w );
if(( !widget || qobject_cast<QToolButton*>( widget )) && !findClient( WindowMatchPredicate( w )))
{
int x, y;
Window xw;
XTranslateCoordinates( display(), rootWindow(), 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 = rootWindow();
e.xmotion.subwindow = w;
e.xmotion.time = xTime();
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( display(), w, true, ButtonMotionMask, &e );
}
else
{
XEvent e;
e.type = type == EmuRelease ? ButtonRelease : ButtonPress;
e.xbutton.window = w;
e.xbutton.root = rootWindow();
e.xbutton.subwindow = w;
e.xbutton.time = xTime();
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( display(), 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 )
{
int kc = XKeycodeToKeysym( display(), ev.keycode, 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 );
QPoint pos = cursorPos();
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:
ungrabXKeyboard();
mouse_emulation = false;
return true;
default:
return false;
}
QCursor::setPos( pos );
if( mouse_emulation_state )
mouse_emulation_state = sendFakedMouseEvent( pos, mouse_emulation_window,
EmuMove, 0, mouse_emulation_state );
return true;
}
/**
* Delayed focus functions
*/
void Workspace::delayFocus()
{
requestFocus( delayfocus_client );
cancelDelayFocus();
}
void Workspace::requestDelayFocus( Client* c )
{
delayfocus_client = c;
delete delayFocusTimer;
delayFocusTimer = new QTimer( this );
connect( delayFocusTimer, SIGNAL( timeout() ), this, SLOT( delayFocus() ) );
delayFocusTimer->setSingleShot( true );
delayFocusTimer->start( options->delayFocusInterval );
}
void Workspace::cancelDelayFocus()
{
delete delayFocusTimer;
delayFocusTimer = 0;
}
//-----------------------------------------------------------------------------
// Electric Borders
//-----------------------------------------------------------------------------
// Electric Border Window management. Electric borders allow a user to change
// the virtual desktop or activate another features by moving the mouse pointer
// to the borders or corners. Technically this is done with input only windows.
//-----------------------------------------------------------------------------
void Workspace::updateElectricBorders()
{
electric_time_first = xTime();
electric_time_last = xTime();
electric_time_last_trigger = xTime();
electric_current_border = ElectricNone;
QRect r = Kephal::ScreenUtils::desktopGeometry();
electricTop = r.top();
electricBottom = r.bottom();
electricLeft = r.left();
electricRight = r.right();
for( int pos = 0; pos < ELECTRIC_COUNT; ++pos )
{
if( electric_reserved[pos] == 0 )
{
if( electric_windows[pos] != None )
XDestroyWindow( display(), electric_windows[pos] );
electric_windows[pos] = None;
continue;
}
if( electric_windows[pos] != None )
continue;
XSetWindowAttributes attributes;
attributes.override_redirect = True;
attributes.event_mask = EnterWindowMask | LeaveWindowMask;
unsigned long valuemask = CWOverrideRedirect | CWEventMask;
int xywh[ELECTRIC_COUNT][4] =
{
{ r.left() + 1, r.top(), r.width() - 2, 1 }, // Top
{ r.right(), r.top(), 1, 1 }, // Top-right
{ r.right(), r.top() + 1, 1, r.height() - 2 }, // Etc.
{ r.right(), r.bottom(), 1, 1 },
{ r.left() + 1, r.bottom(), r.width() - 2, 1 },
{ r.left(), r.bottom(), 1, 1 },
{ r.left(), r.top() + 1, 1, r.height() - 2 },
{ r.left(), r.top(), 1, 1 }
};
electric_windows[pos] = XCreateWindow( display(), rootWindow(),
xywh[pos][0], xywh[pos][1], xywh[pos][2], xywh[pos][3],
0, CopyFromParent, InputOnly, CopyFromParent, valuemask, &attributes );
XMapWindow( display(), electric_windows[pos] );
// Set XdndAware on the windows, so that DND enter events are received (#86998)
Atom version = 4; // XDND version
XChangeProperty( display(), electric_windows[pos], atoms->xdnd_aware, XA_ATOM,
32, PropModeReplace, (unsigned char*)( &version ), 1 );
}
}
void Workspace::destroyElectricBorders()
{
for( int pos = 0; pos < ELECTRIC_COUNT; ++pos )
{
if( electric_windows[pos] != None )
XDestroyWindow( display(), electric_windows[pos] );
electric_windows[pos] = None;
}
}
void Workspace::restoreElectricBorderSize( ElectricBorder border )
{
if( electric_windows[border] == None )
return;
QRect r = Kephal::ScreenUtils::desktopGeometry();
int xywh[ELECTRIC_COUNT][4] =
{
{ r.left() + 1, r.top(), r.width() - 2, 1 }, // Top
{ r.right(), r.top(), 1, 1 }, // Top-right
{ r.right(), r.top() + 1, 1, r.height() - 2 }, // Etc.
{ r.right(), r.bottom(), 1, 1 },
{ r.left() + 1, r.bottom(), r.width() - 2, 1 },
{ r.left(), r.bottom(), 1, 1 },
{ r.left(), r.top() + 1, 1, r.height() - 2 },
{ r.left(), r.top(), 1, 1 }
};
XMoveResizeWindow( display(), electric_windows[border],
xywh[border][0], xywh[border][1], xywh[border][2], xywh[border][3] );
}
void Workspace::reserveElectricBorderActions( bool reserve )
{
for( int pos = 0; pos < ELECTRIC_COUNT; ++pos )
if( options->electricBorderAction( static_cast<ElectricBorder>( pos )))
{
if( reserve )
reserveElectricBorder( static_cast<ElectricBorder>( pos ));
else
unreserveElectricBorder( static_cast<ElectricBorder>( pos ));
}
}
void Workspace::reserveElectricBorderSwitching( bool reserve )
{
for( int pos = 0; pos < ELECTRIC_COUNT; ++pos )
if( reserve )
reserveElectricBorder( static_cast<ElectricBorder>( pos ));
else
unreserveElectricBorder( static_cast<ElectricBorder>( pos ));
}
void Workspace::reserveElectricBorder( ElectricBorder border )
{
if( border == ElectricNone )
return;
if( electric_reserved[border]++ == 0 )
QTimer::singleShot( 0, this, SLOT( updateElectricBorders() ));
}
void Workspace::unreserveElectricBorder( ElectricBorder border )
{
if( border == ElectricNone )
return;
assert( electric_reserved[border] > 0 );
if( --electric_reserved[border] == 0 )
QTimer::singleShot( 0, this, SLOT( updateElectricBorders() ));
}
void Workspace::checkElectricBorder(const QPoint& pos, Time now)
{
if(( pos.x() != electricLeft ) &&
( pos.x() != electricRight ) &&
( pos.y() != electricTop ) &&
( pos.y() != electricBottom ))
return;
bool have_borders = false;
for( int i = 0; i < ELECTRIC_COUNT; ++i )
if( electric_windows[i] != None )
have_borders = true;
if( !have_borders )
return;
Time treshold_set = options->electricBorderDelay(); // Set timeout
Time treshold_reset = 250; // Reset timeout
Time treshold_trigger = options->electricBorderCooldown(); // Minimum time between triggers
int distance_reset = 30; // Mouse should not move more than this many pixels
int pushback_pixels = options->electricBorderPushbackPixels();
ElectricBorder border;
if( pos.x() == electricLeft && pos.y() == electricTop )
border = ElectricTopLeft;
else if( pos.x() == electricRight && pos.y() == electricTop )
border = ElectricTopRight;
else if( pos.x() == electricLeft && pos.y() == electricBottom )
border = ElectricBottomLeft;
else if( pos.x() == electricRight && pos.y() == electricBottom )
border = ElectricBottomRight;
else if( pos.x() == electricLeft )
border = ElectricLeft;
else if( pos.x() == electricRight )
border = ElectricRight;
else if( pos.y() == electricTop )
border = ElectricTop;
else if( pos.y() == electricBottom )
border = ElectricBottom;
else
abort();
if( electric_windows[border] == None )
return;
if( pushback_pixels == 0 )
{
// no pushback so we have to activate at once
electric_time_last = now;
}
if(( electric_current_border == border ) &&
( timestampDiff( electric_time_last, now ) < treshold_reset ) &&
( timestampDiff( electric_time_last_trigger, now ) > treshold_trigger ) &&
(( pos-electric_push_point ).manhattanLength() < distance_reset ))
{
electric_time_last = now;
if( timestampDiff( electric_time_first, now ) > treshold_set )
{
electric_current_border = ElectricNone;
electric_time_last_trigger = now;
if( movingClient )
{
// If moving a client or have force doing the desktop switch
if( options->electricBorders() != Options::ElectricDisabled )
{
electricBorderSwitchDesktop( border, pos );
return; // Don't reset cursor position
}
// maximize only when not using for switch
if( options->electricBorderMaximize() && border == ElectricTop &&
movingClient->isMaximizable() )
{
movingClient->setElectricBorderMode( ElectricMaximizeMode );
movingClient->setElectricBorderMaximizing( true );
// Make electric windows thicker so we can detect when the user wants to cancel
QRect r = Kephal::ScreenUtils::desktopGeometry();
XRaiseWindow( display(), electric_windows[ElectricTop] );
XResizeWindow( display(), electric_windows[ElectricTop],
r.width() - 2, 20 );
return; // Don't reset cursor position
}
if( options->electricBorderTiling() )
{
bool activate = false;
if( border == ElectricLeft )
{
movingClient->setElectricBorderMode( ElectricLeftMode );
activate = true;
// Make electric windows thicker so we can detect when the user wants to cancel
QRect r = Kephal::ScreenUtils::desktopGeometry();
XRaiseWindow( display(), electric_windows[ElectricLeft] );
XResizeWindow( display(), electric_windows[ElectricLeft],
20, r.height() - 2 );
}
else if( border == ElectricRight )
{
movingClient->setElectricBorderMode( ElectricRightMode );
activate = true;
// Make electric windows thicker so we can detect when the user wants to cancel
QRect r = Kephal::ScreenUtils::desktopGeometry();
XRaiseWindow( display(), electric_windows[ElectricRight] );
XMoveResizeWindow( display(), electric_windows[ElectricRight],
r.right() - 19, r.top() + 1, 20, r.height() - 2 );
}
if( activate )
{
movingClient->setElectricBorderMaximizing( true );
return; // Don't reset cursor position
}
}
else
return; // Don't reset cursor position
}
else
{
if( options->electricBorders() == Options::ElectricAlways &&
( border == ElectricTop || border == ElectricRight ||
border == ElectricBottom || border == ElectricLeft ))
{ // If desktop switching is always enabled don't apply it to the corners if
// an effect is applied to it (We will check that later).
electricBorderSwitchDesktop( border, pos );
return; // Don't reset cursor position
}
switch( options->electricBorderAction( border ))
{
case ElectricActionDashboard: // Display Plasma dashboard
{
QDBusInterface plasmaApp( "org.kde.plasma-desktop", "/App" );
plasmaApp.call( "toggleDashboard" );
}
break;
case ElectricActionShowDesktop:
{
setShowingDesktop( !showingDesktop() );
break;
}
case ElectricActionNone: // Either desktop switching or an effect
default:
{
if( effects && static_cast<EffectsHandlerImpl*>( effects )->borderActivated( border ))
{} // Handled by effects
else
{
electricBorderSwitchDesktop( border, pos );
return; // Don't reset cursor position
}
}
}
}
}
}
else
{
electric_current_border = border;
electric_time_first = now;
electric_time_last = now;
electric_push_point = pos;
}
// Reset the pointer to find out whether the user is really pushing
// (the direction back from which it came, starting from top clockwise)
const int xdiff[ELECTRIC_COUNT] = { 0,
-pushback_pixels,
-pushback_pixels,
-pushback_pixels,
0,
pushback_pixels,
pushback_pixels,
pushback_pixels };
const int ydiff[ELECTRIC_COUNT] = { pushback_pixels,
pushback_pixels,
0,
-pushback_pixels,
-pushback_pixels,
-pushback_pixels,
0,
pushback_pixels };
QCursor::setPos( pos.x() + xdiff[border], pos.y() + ydiff[border] );
}
void Workspace::electricBorderSwitchDesktop( ElectricBorder border, const QPoint& _pos )
{
QPoint pos = _pos;
int desk = currentDesktop();
const int OFFSET = 2;
if( border == ElectricLeft || border == ElectricTopLeft || border == ElectricBottomLeft )
{
desk = desktopToLeft( desk, options->rollOverDesktops );
pos.setX( displayWidth() - 1 - OFFSET );
}
if( border == ElectricRight || border == ElectricTopRight || border == ElectricBottomRight )
{
desk = desktopToRight( desk, options->rollOverDesktops );
pos.setX( OFFSET );
}
if( border == ElectricTop || border == ElectricTopLeft || border == ElectricTopRight )
{
desk = desktopAbove( desk, options->rollOverDesktops );
pos.setY( displayHeight() - 1 - OFFSET );
}
if( border == ElectricBottom || border == ElectricBottomLeft || border == ElectricBottomRight )
{
desk = desktopBelow( desk, options->rollOverDesktops );
pos.setY( OFFSET );
}
int desk_before = currentDesktop();
setCurrentDesktop( desk );
if( currentDesktop() != desk_before )
QCursor::setPos( pos );
}
/**
* Called when the user entered an electric border with the mouse.
* It may switch to another virtual desktop.
*/
bool Workspace::electricBorderEvent( XEvent* e )
{
if( e->type == EnterNotify )
{
if( movingClient && movingClient->isElectricBorderMaximizing() &&
e->xcrossing.window == movingClient->moveResizeGrabWindow() )
{ // Cancel the quick tiling/maximization action when the user moves away from the edge.
// This event isn't from the border window; it came from the grab window.
movingClient->setElectricBorderMaximizing( false );
// Restore electric windows back to their normal size
switch( movingClient->electricBorderMode() )
{
case ElectricMaximizeMode:
restoreElectricBorderSize( ElectricTop );
break;
case ElectricLeftMode:
restoreElectricBorderSize( ElectricLeft );
break;
case ElectricRightMode:
restoreElectricBorderSize( ElectricRight );
break;
}
}
for( int i = 0; i < ELECTRIC_COUNT; ++i )
if( electric_windows[i] != None && e->xcrossing.window == electric_windows[i] )
{ // The user entered an electric border
checkElectricBorder( QPoint( 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 )
{
for( int i = 0; i < ELECTRIC_COUNT; ++i )
if( electric_windows[i] != None && e->xclient.window == electric_windows[i] )
{
updateXTime();
checkElectricBorder( QPoint(
e->xclient.data.l[2]>>16, e->xclient.data.l[2]&0xffff), xTime() );
return true;
}
}
}
return false;
}
void Workspace::showElectricBorderWindowOutline()
{
if( !movingClient )
return;
// code copied from TabBox::updateOutline() in tabbox.cpp
QRect c = movingClient->electricBorderMaximizeGeometry();
// left/right parts are between top/bottom, they don't reach as far as the corners
XMoveResizeWindow( QX11Info::display(), outline_left, c.x(), c.y() + 5, 5, c.height() - 10 );
XMoveResizeWindow( QX11Info::display(), outline_right, c.x() + c.width() - 5, c.y() + 5, 5, c.height() - 10 );
XMoveResizeWindow( QX11Info::display(), outline_top, c.x(), c.y(), c.width(), 5 );
XMoveResizeWindow( QX11Info::display(), outline_bottom, c.x(), c.y() + c.height() - 5, c.width(), 5 );
{
QPixmap pix( 5, c.height() - 10 );
QPainter p( &pix );
p.setPen( Qt::white );
p.drawLine( 0, 0, 0, pix.height() - 1 );
p.drawLine( 4, 0, 4, pix.height() - 1 );
p.setPen( Qt::gray );
p.drawLine( 1, 0, 1, pix.height() - 1 );
p.drawLine( 3, 0, 3, pix.height() - 1 );
p.setPen( Qt::black );
p.drawLine( 2, 0, 2, pix.height() - 1 );
p.end();
XSetWindowBackgroundPixmap( QX11Info::display(), outline_left, pix.handle());
XSetWindowBackgroundPixmap( QX11Info::display(), outline_right, pix.handle());
}
{
QPixmap pix( c.width(), 5 );
QPainter p( &pix );
p.setPen( Qt::white );
p.drawLine( 0, 0, pix.width() - 1 - 0, 0 );
p.drawLine( 4, 4, pix.width() - 1 - 4, 4 );
p.drawLine( 0, 0, 0, 4 );
p.drawLine( pix.width() - 1 - 0, 0, pix.width() - 1 - 0, 4 );
p.setPen( Qt::gray );
p.drawLine( 1, 1, pix.width() - 1 - 1, 1 );
p.drawLine( 3, 3, pix.width() - 1 - 3, 3 );
p.drawLine( 1, 1, 1, 4 );
p.drawLine( 3, 3, 3, 4 );
p.drawLine( pix.width() - 1 - 1, 1, pix.width() - 1 - 1, 4 );
p.drawLine( pix.width() - 1 - 3, 3, pix.width() - 1 - 3, 4 );
p.setPen( Qt::black );
p.drawLine( 2, 2, pix.width() - 1 - 2, 2 );
p.drawLine( 2, 2, 2, 4 );
p.drawLine( pix.width() - 1 - 2, 2, pix.width() - 1 - 2, 4 );
p.end();
XSetWindowBackgroundPixmap( QX11Info::display(), outline_top, pix.handle());
}
{
QPixmap pix( c.width(), 5 );
QPainter p( &pix );
p.setPen( Qt::white );
p.drawLine( 4, 0, pix.width() - 1 - 4, 0 );
p.drawLine( 0, 4, pix.width() - 1 - 0, 4 );
p.drawLine( 0, 4, 0, 0 );
p.drawLine( pix.width() - 1 - 0, 4, pix.width() - 1 - 0, 0 );
p.setPen( Qt::gray );
p.drawLine( 3, 1, pix.width() - 1 - 3, 1 );
p.drawLine( 1, 3, pix.width() - 1 - 1, 3 );
p.drawLine( 3, 1, 3, 0 );
p.drawLine( 1, 3, 1, 0 );
p.drawLine( pix.width() - 1 - 3, 1, pix.width() - 1 - 3, 0 );
p.drawLine( pix.width() - 1 - 1, 3, pix.width() - 1 - 1, 0 );
p.setPen( Qt::black );
p.drawLine( 2, 2, pix.width() - 1 - 2, 2 );
p.drawLine( 2, 0, 2, 2 );
p.drawLine( pix.width() - 1 - 2, 0, pix.width() - 1 - 2, 2 );
p.end();
XSetWindowBackgroundPixmap( QX11Info::display(), outline_bottom, pix.handle());
}
XClearWindow( QX11Info::display(), outline_left );
XClearWindow( QX11Info::display(), outline_right );
XClearWindow( QX11Info::display(), outline_top );
XClearWindow( QX11Info::display(), outline_bottom );
XMapWindow( QX11Info::display(), outline_left );
XMapWindow( QX11Info::display(), outline_right );
XMapWindow( QX11Info::display(), outline_top );
XMapWindow( QX11Info::display(), outline_bottom );
}
void Workspace::hideElectricBorderWindowOutline()
{
XUnmapWindow( QX11Info::display(), outline_left );
XUnmapWindow( QX11Info::display(), outline_right );
XUnmapWindow( QX11Info::display(), outline_top );
XUnmapWindow( QX11Info::display(), outline_bottom );
}
//-----------------------------------------------------------------------------
// Top menu
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();
}
//kDebug( 1212 ) << "NEW TOPMENU:" << c;
}
void Workspace::removeTopMenu( Client* c )
{
//if( c->isTopMenu() )
// kDebug( 1212 ) << "REMOVE TOPMENU:" << c;
assert( c->isTopMenu() );
assert( topmenus.contains( c ));
topmenus.removeAll( c );
updateCurrentTopMenu();
// TODO: Reduce topMenuHeight() if possible?
}
void Workspace::lostTopMenuSelection()
{
//kDebug( 1212 ) << "lost TopMenu selection";
// Make sure this signal is always set when not owning the selection
disconnect( topmenu_watcher, SIGNAL( lostOwner() ), this, SLOT( lostTopMenuOwner() ));
connect( topmenu_watcher, SIGNAL( lostOwner() ), this, SLOT( lostTopMenuOwner() ));
if( !managing_topmenus )
return;
connect( topmenu_watcher, SIGNAL( lostOwner() ), this, SLOT( lostTopMenuOwner() ));
disconnect( topmenu_selection, SIGNAL( lostOwnership() ), this, SLOT( lostTopMenuSelection() ));
managing_topmenus = false;
delete topmenu_space;
topmenu_space = NULL;
updateClientArea();
for( ClientList::ConstIterator it = topmenus.constBegin();
it != topmenus.constEnd();
++it )
(*it)->checkWorkspacePosition();
}
void Workspace::lostTopMenuOwner()
{
if( !options->topMenuEnabled())
return;
//kDebug( 1212 ) << "TopMenu selection lost owner";
if( !topmenu_selection->claim( false ))
{
//kDebug( 1212 ) << "Failed to claim TopMenu selection";
return;
}
//kDebug( 1212 ) << "Claimed TopMenu selection";
setupTopMenuHandling();
}
void Workspace::setupTopMenuHandling()
{
if( managing_topmenus )
return;
connect( topmenu_selection, SIGNAL( lostOwnership() ), this, SLOT( lostTopMenuSelection() ));
disconnect( topmenu_watcher, SIGNAL( lostOwner() ), this, SLOT( lostTopMenuOwner() ));
managing_topmenus = true;
topmenu_space = new QWidget( NULL, Qt::X11BypassWindowManagerHint );
Window stack[2];
stack[0] = supportWindow->winId();
stack[1] = topmenu_space->winId();
XRestackWindows( display(), stack, 2 );
updateTopMenuGeometry();
topmenu_space->show();
updateClientArea();
updateCurrentTopMenu();
}
int Workspace::topMenuHeight() const
{
if( topmenu_height == 0 )
{ // Simply create a dummy menubar and use its preferred height as the menu height
KMenuBar tmpmenu;
tmpmenu.addAction( "dummy" );
topmenu_height = tmpmenu.sizeHint().height();
}
return topmenu_height;
}
KDecoration* Workspace::createDecoration( KDecorationBridge* bridge )
{
return mgr->createDecoration( bridge );
}
/**
* Returns a list of all colors (KDecorationDefines::ColorType) the current
* decoration supports
*/
QList<int> Workspace::decorationSupportedColors() const
{
KDecorationFactory* factory = mgr->factory();
QList<int> ret;
for( Ability ab = ABILITYCOLOR_FIRST;
ab < ABILITYCOLOR_END;
ab = static_cast<Ability>( ab + 1 ))
if( factory->supports( ab ))
ret << ab;
return ret;
}
QString Workspace::desktopName( int desk ) const
{
return QString::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( display(), null_focus_window, RevertToPointerRoot, xTime() );
}
void Workspace::helperDialog( const QString& message, const Client* c )
{
QStringList args;
QString type;
if( message == "noborderaltf3" )
{
KAction* action = qobject_cast<KAction*>( keys->action( "Window Operations Menu" ));
assert( action != NULL );
QString shortcut = QString( "%1 (%2)" ).arg( action->text() )
.arg( action->globalShortcut().primary().toString( QKeySequence::NativeText ));
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.",
shortcut );
type = "altf3warning";
}
else if( message == "fullscreenaltf3" )
{
KAction* action = qobject_cast<KAction*>( keys->action( "Window Operations Menu" ));
assert( action != NULL );
QString shortcut = QString( "%1 (%2)" ).arg( action->text() )
.arg( action->globalShortcut().primary().toString( QKeySequence::NativeText ));
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.",
shortcut );
type = "altf3warning";
}
else
abort();
if( !type.isEmpty() )
{
KConfig cfg( "kwin_dialogsrc" );
KConfigGroup cg(&cfg, "Notification Messages" ); // Depends on KMessageBox
if( !cg.readEntry( type, true ))
return;
args << "--dontagain" << "kwin_dialogsrc:" + type;
}
if( c != NULL )
args << "--embed" << QString::number( c->window() );
KProcess::startDetached( "kdialog",args );
}
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.constBegin();
it != cls.constEnd();
++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.constBegin();
it != showing_desktop_clients.constEnd();
++it )
(*it)->minimize();
--block_focus;
if( Client* desk = findDesktop( true, currentDesktop() ))
requestFocus( desk );
}
else
{
for( ClientList::ConstIterator it = showing_desktop_clients.constBegin();
it != showing_desktop_clients.constEnd();
++it )
(*it)->unminimize();
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 kwinrc. 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.constBegin();
it != showing_desktop_clients.constEnd();
++it )
(*it)->unminimize();
}
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;
KGlobalSettings::self()->emitChange( KGlobalSettings::BlockShortcuts, disable );
// KWin will get the kipc message too
}
}
void Workspace::disableGlobalShortcuts( bool disable )
{
KGlobalSettings::self()->emitChange( KGlobalSettings::BlockShortcuts, disable );
// KWin will get the kipc message too
}
void Workspace::slotBlockShortcuts( int data )
{
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.constBegin();
it != clients.constEnd();
++it )
(*it)->updateMouseGrab();
}
// Optimized version of QCursor::pos() that tries to avoid X roundtrips
// by updating the value only when the X timestamp changes.
static QPoint last_cursor_pos;
static int last_buttons = 0;
static Time last_cursor_timestamp = CurrentTime;
static QTimer* last_cursor_timer;
QPoint Workspace::cursorPos() const
{
if( last_cursor_timestamp == CurrentTime ||
last_cursor_timestamp != QX11Info::appTime() )
{
last_cursor_timestamp = QX11Info::appTime();
Window root;
Window child;
int root_x, root_y, win_x, win_y;
uint state;
XQueryPointer( display(), rootWindow(), &root, &child,
&root_x, &root_y, &win_x, &win_y, &state );
last_cursor_pos = QPoint( root_x, root_y );
last_buttons = state;
if( last_cursor_timer == NULL )
{
Workspace* ws = const_cast<Workspace*>( this );
last_cursor_timer = new QTimer( ws );
last_cursor_timer->setSingleShot( true );
connect( last_cursor_timer, SIGNAL( timeout() ), ws, SLOT( resetCursorPosTime() ));
}
last_cursor_timer->start( 0 );
}
return last_cursor_pos;
}
/**
* Because of QTimer's and the impossibility to get events for all mouse
* movements (at least I haven't figured out how) the position needs
* to be also refetched after each return to the event loop.
*/
void Workspace::resetCursorPosTime()
{
last_cursor_timestamp = CurrentTime;
}
void Workspace::checkCursorPos()
{
QPoint last = last_cursor_pos;
int lastb = last_buttons;
cursorPos(); // Update if needed
if( last != last_cursor_pos || lastb != last_buttons )
static_cast<EffectsHandlerImpl*>( effects )->mouseChanged( cursorPos(), last,
x11ToQtMouseButtons( last_buttons ), x11ToQtMouseButtons( lastb ),
x11ToQtKeyboardModifiers( last_buttons ), x11ToQtKeyboardModifiers( lastb ));
}
int Workspace::indexOfClientGroup( ClientGroup* group )
{
return clientGroups.indexOf( group );
}
void Workspace::moveItemToClientGroup( ClientGroup* oldGroup, int oldIndex,
ClientGroup* group, int index )
{
Client* c = oldGroup->clients().at( oldIndex );
oldGroup->remove( c );
group->add( c, index, true );
}
// To accept "mainwindow#1" to "mainwindow#2"
static QByteArray truncatedWindowRole( QByteArray a )
{
int i = a.indexOf('#');
if( i == -1 )
return a;
QByteArray b( a );
b.truncate( i );
return b;
}
Client* Workspace::findSimilarClient( Client* c )
{ // Attempt to find a similar window to the input. If we find multiple possibilities that are in
// different groups then ignore all of them. This function is for automatic window grouping.
Client* found = NULL;
// See if the window has a group ID to match with
QString wGId = c->rules()->checkAutogroupById( QString() );
if( !wGId.isEmpty() )
{
foreach( Client* cl, clients )
{
if( wGId == cl->rules()->checkAutogroupById( QString() ))
{
if( found && found->clientGroup() != cl->clientGroup() ) // We've found two, ignore both
{
found = NULL;
break; // Continue to the next test
}
found = cl;
}
}
if( found )
return found;
}
// If we don't have an ID take a guess
if( c->rules()->checkAutogrouping( options->autogroupSimilarWindows ))
{
QByteArray wRole = truncatedWindowRole( c->windowRole() );
foreach( Client* cl, clients )
{
QByteArray wRoleB = truncatedWindowRole( cl->windowRole() );
if( c->resourceClass() == cl->resourceClass() && // Same resource class
wRole == wRoleB && // Same window role
cl->isNormalWindow() ) // Normal window TODO: Can modal windows be "normal"?
{
if( found && found->clientGroup() != cl->clientGroup() ) // We've found two, ignore both
return NULL;
found = cl;
}
}
}
return found;
}
} // namespace
#include "workspace.moc"