summaryrefslogtreecommitdiffstats
path: root/kwin/workspace.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kwin/workspace.cpp')
-rw-r--r--kwin/workspace.cpp2732
1 files changed, 2732 insertions, 0 deletions
diff --git a/kwin/workspace.cpp b/kwin/workspace.cpp
new file mode 100644
index 000000000..1335a888c
--- /dev/null
+++ b/kwin/workspace.cpp
@@ -0,0 +1,2732 @@
+/*****************************************************************
+ 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 <qpopupmenu.h>
+#include <klocale.h>
+#include <qregexp.h>
+#include <qpainter.h>
+#include <qbitmap.h>
+#include <qclipboard.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/extensions/shape.h>
+#include <X11/keysym.h>
+#include <X11/keysymdef.h>
+#include <X11/cursorfont.h>
+
+extern Time qt_x_time;
+
+namespace KWinInternal
+{
+
+extern int screen_number;
+
+Workspace *Workspace::_self = 0;
+
+KProcess* kompmgr = 0;
+KSelectionOwner* kompmgr_selection;
+
+bool allowKompmgrRestart = TRUE;
+
+// 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"),
+ QObject (0, "workspace"),
+ current_desktop (0),
+ number_of_desktops(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),
+ 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 = qt_xrootwin();
+ default_colormap = DefaultColormap(qt_xdisplay(), qt_xscreen() );
+ installed_colormap = default_colormap;
+ session.setAutoDelete( TRUE );
+
+ connect( &temporaryRulesMessages, SIGNAL( gotMessage( const QString& )),
+ this, SLOT( gotTemporaryRulesMessage( const QString& )));
+ connect( &rulesUpdatedTimer, SIGNAL( timeout()), this, SLOT( writeWindowRules()));
+
+ updateXTime(); // needed for proper initialization of user_time in Client ctor
+
+ delayFocusTimer = 0;
+
+ electric_time_first = qt_x_time;
+ electric_time_last = qt_x_time;
+
+ if ( restore )
+ loadSessionInfo();
+
+ loadWindowRules();
+
+ (void) QApplication::desktop(); // trigger creation of desktop widget
+
+ desktop_widget =
+ new QWidget(
+ 0,
+ "desktop_widget",
+ Qt::WType_Desktop | Qt::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(qt_xdisplay(), root,
+ KeyPressMask |
+ PropertyChangeMask |
+ ColormapChangeMask |
+ SubstructureRedirectMask |
+ SubstructureNotifyMask |
+ FocusChangeMask // for NotifyDetailNone
+ );
+
+ Shape::init();
+
+ // compatibility
+ long data = 1;
+
+ XChangeProperty(
+ qt_xdisplay(),
+ qt_xrootwin(),
+ atoms->kwin_running,
+ atoms->kwin_running,
+ 32,
+ PropModeAppend,
+ (unsigned char*) &data,
+ 1
+ );
+
+ client_keys = new KGlobalAccel( this );
+ initShortcuts();
+ tab_box = new TabBox( this );
+ popupinfo = new PopupInfo( );
+
+ init();
+
+#if (QT_VERSION-0 >= 0x030200) // XRANDR support
+ connect( kapp->desktop(), SIGNAL( resized( int )), SLOT( desktopResized()));
+#endif
+
+ // 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, SIGNAL(receivedStderr(KProcess*, char*, int)), SLOT(handleKompmgrOutput(KProcess*, char*, int)));
+ *kompmgr << "kompmgr";
+ startKompmgr();
+ }
+ }
+
+
+void Workspace::init()
+ {
+ checkElectricBorders();
+
+// not used yet
+// topDock = 0L;
+// maximizedWindowCounter = 0;
+
+ supportWindow = new QWidget;
+ XLowerWindow( qt_xdisplay(), supportWindow->winId()); // see usage in layers.cpp
+
+ XSetWindowAttributes attr;
+ attr.override_redirect = 1;
+ null_focus_window = XCreateWindow( qt_xdisplay(), qt_xrootwin(), -1,-1, 1, 1, 0, CopyFromParent,
+ InputOnly, CopyFromParent, CWOverrideRedirect, &attr );
+ XMapWindow(qt_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::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, qt_xdisplay(), supportWindow->winId(), "KWin",
+ protocols, 5, qt_xscreen() );
+
+ loadDesktopSettings();
+ updateDesktopLayout();
+ // extra NETRootInfo instance in Client mode is needed to get the values of the properties
+ NETRootInfo client_info( qt_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, SIGNAL(timeout()), this,
+ SLOT(slotReconfigure()));
+ connect( &updateToolWindowsTimer, SIGNAL( timeout()), this, SLOT( slotUpdateToolWindows()));
+
+ connect(kapp, SIGNAL(appearanceChanged()), this,
+ SLOT(slotReconfigure()));
+ connect(kapp, SIGNAL(settingsChanged(int)), this,
+ SLOT(slotSettingsChanged(int)));
+ connect(kapp, SIGNAL( kipcMessage( int, int )), this, 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( qt_xdisplay()));
+ Atom topmenu_atom = XInternAtom( qt_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(qt_xdisplay(), root, &root_return, &parent_return, &wins, &nwins);
+ for (i = 0; i < nwins; i++)
+ {
+ XWindowAttributes attr;
+ XGetWindowAttributes(qt_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 != qt_xrootwin() )
+ { // TODO what is this?
+ // TODO may use QWidget:.create
+ XReparentWindow( qt_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;
+ QRect geom = QApplication::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 kwin --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 == qt_xrootwin() )
+ XDeleteProperty(qt_xdisplay(), qt_xrootwin(), atoms->kwin_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( qt_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 ;)
+// qWarning("%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);
+ }
+ }
+//------------------------------------------------
+ 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( QString::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::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(qt_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
+ QWidget curtain;
+ curtain.setBackgroundMode( NoBackground );
+ curtain.setGeometry( QApplication::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;
+ stopKompmgr();
+ if (tmp)
+ QTimer::singleShot( 200, this, SLOT(startKompmgr()) ); // wait some time to ensure system's ready for restart
+ }
+ }
+
+void Workspace::loadDesktopSettings()
+ {
+ KConfig* c = KGlobal::config();
+ QCString 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 QRect[ 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++)
+ {
+ QString s = c->readEntry(QString("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();
+ QCString 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++)
+ {
+ QString s = desktopName( i );
+ QString defaultvalue = i18n("Desktop %1").arg(i);
+ if ( s.isEmpty() )
+ {
+ s = defaultvalue;
+ rootInfo->setDesktopName( i, s.utf8().data() );
+ }
+
+ if (s != defaultvalue)
+ {
+ c->writeEntry( QString("Name_%1").arg(i), s );
+ }
+ else
+ {
+ QString currentvalue = c->readEntry(QString("Name_%1").arg(i));
+ if (currentvalue != defaultvalue)
+ c->writeEntry( QString("Name_%1").arg(i), "" );
+ }
+ }
+ }
+
+QStringList Workspace::configModules(bool controlCenter)
+ {
+ QStringList args;
+ args << "kde-kwindecoration.desktop";
+ if (controlCenter)
+ args << "kde-kwinoptions.desktop";
+ else if (kapp->authorizeControlModule("kde-kwinoptions.desktop"))
+ args << "kwinactions" << "kwinfocus" << "kwinmoving" << "kwinadvanced" << "kwinrules" << "kwintranslucency";
+ return args;
+ }
+
+void Workspace::configureWM()
+ {
+ KApplication::kdeinitExec( "kcmshell", configModules(false) );
+ }
+
+/*!
+ avoids managing a window with title \a title
+ */
+void Workspace::doNotManage( 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.search(title) != -1)
+ {
+ doNotManageList.remove( it );
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+
+/*!
+ Refreshes all the client windows
+ */
+void Workspace::refresh()
+ {
+ QWidget w;
+ w.setGeometry( QApplication::desktop()->geometry() );
+ w.show();
+ w.hide();
+ QApplication::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:
+ QValueList<Window> obscuring_windows;
+ static QValueList<Window>* cached;
+ static unsigned int max_cache_size;
+ };
+
+QValueList<Window>* ObscuringWindows::cached = 0;
+unsigned int ObscuringWindows::max_cache_size = 0;
+
+void ObscuringWindows::create( Client* c )
+ {
+ if( cached == 0 )
+ cached = new QValueList<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( qt_xdisplay(), qt_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( qt_xdisplay(), obs_win, mask, &chngs );
+ XMapWindow( qt_xdisplay(), obs_win );
+ obscuring_windows.append( obs_win );
+ }
+
+ObscuringWindows::~ObscuringWindows()
+ {
+ max_cache_size = QMAX( max_cache_size, obscuring_windows.count() + 4 ) - 1;
+ for( QValueList<Window>::ConstIterator it = obscuring_windows.begin();
+ it != obscuring_windows.end();
+ ++it )
+ {
+ XUnmapWindow( qt_xdisplay(), *it );
+ if( cached->count() < max_cache_size )
+ cached->prepend( *it );
+ else
+ XDestroyWindow( qt_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)
+
+ 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();
+
+ --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) << QString("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();
+
+// QString s = "desktop_focus_chain[] = { ";
+// for( uint i = 0; i < desktop_focus_chain.size(); i++ )
+// s += QString::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();
+ }
+
+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( qt_xdisplay(), w, root, NET::WMKDESystemTrayWinFor );
+ WId trayWinFor = ni.kdeSystemTrayWinFor();
+ if ( !trayWinFor )
+ return FALSE;
+ systemTrayWins.append( SystemTrayWindow( w, trayWinFor ) );
+ XSelectInput( qt_xdisplay(), w,
+ StructureNotifyMask
+ );
+ XAddToSaveSet( qt_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( qt_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 (qt_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, root;
+ Window* children;
+ unsigned int children_count;
+ XQueryTree( qt_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( client != NULL )
+ client->killWindow();
+ else
+ XKillClient( qt_xdisplay(), 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( Shape::available())
+ {
+ //As the first step, get the mask from XShape.
+ int count, order;
+ XRectangle* rects = XShapeGetRectangles( qt_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 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;
+ QMemArray<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 (uint 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( qt_xrootwin() );
+ QClipboard *cb = QApplication::clipboard();
+ cb->setPixmap( p );
+ }
+
+
+/*!
+ Invokes keyboard mouse emulation
+ */
+void Workspace::slotMouseEmulation()
+ {
+
+ if ( mouse_emulation )
+ {
+ XUngrabKeyboard(qt_xdisplay(), qt_x_time);
+ mouse_emulation = FALSE;
+ return;
+ }
+
+ if ( XGrabKeyboard(qt_xdisplay(),
+ root, FALSE,
+ GrabModeAsync, GrabModeAsync,
+ 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 = qt_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( qt_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( QPoint pos, WId w, MouseEmulation type, int button, unsigned int state )
+ {
+ if ( !w )
+ return state;
+ QWidget* widget = QWidget::find( w );
+ if ( (!widget || widget->inherits("QToolButton") ) && !findClient( WindowMatchPredicate( w )) )
+ {
+ int x, y;
+ Window xw;
+ XTranslateCoordinates( qt_xdisplay(), qt_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 = qt_xrootwin();
+ e.xmotion.subwindow = w;
+ e.xmotion.time = 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( qt_xdisplay(), w, TRUE, ButtonMotionMask, &e );
+ }
+ else
+ {
+ XEvent e;
+ e.type = type == EmuRelease ? ButtonRelease : ButtonPress;
+ e.xbutton.window = w;
+ e.xbutton.root = qt_xrootwin();
+ e.xbutton.subwindow = w;
+ e.xbutton.time = 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( qt_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 != qt_xrootwin() )
+ return FALSE;
+ int kc = XKeycodeToKeysym(qt_xdisplay(), 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 = QCursor::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(qt_xdisplay(), qt_x_time);
+ 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;
+
+ }
+
+/*!
+ 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.
+ */
+QWidget* 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 QTimer( this );
+ connect( delayFocusTimer, SIGNAL( timeout() ), this, 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;
+
+ QRect r = QApplication::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;
+
+ QRect r = QApplication::desktop()->geometry();
+ XSetWindowAttributes attributes;
+ unsigned long valuemask;
+ attributes.override_redirect = True;
+ attributes.event_mask = ( EnterWindowMask | LeaveWindowMask );
+ valuemask= (CWOverrideRedirect | CWEventMask | CWCursor );
+ attributes.cursor = XCreateFontCursor(qt_xdisplay(),
+ XC_sb_up_arrow);
+ electric_top_border = XCreateWindow (qt_xdisplay(), qt_xrootwin(),
+ 0,0,
+ r.width(),1,
+ 0,
+ CopyFromParent, InputOnly,
+ CopyFromParent,
+ valuemask, &attributes);
+ XMapWindow(qt_xdisplay(), electric_top_border);
+
+ attributes.cursor = XCreateFontCursor(qt_xdisplay(),
+ XC_sb_down_arrow);
+ electric_bottom_border = XCreateWindow (qt_xdisplay(), qt_xrootwin(),
+ 0,r.height()-1,
+ r.width(),1,
+ 0,
+ CopyFromParent, InputOnly,
+ CopyFromParent,
+ valuemask, &attributes);
+ XMapWindow(qt_xdisplay(), electric_bottom_border);
+
+ attributes.cursor = XCreateFontCursor(qt_xdisplay(),
+ XC_sb_left_arrow);
+ electric_left_border = XCreateWindow (qt_xdisplay(), qt_xrootwin(),
+ 0,0,
+ 1,r.height(),
+ 0,
+ CopyFromParent, InputOnly,
+ CopyFromParent,
+ valuemask, &attributes);
+ XMapWindow(qt_xdisplay(), electric_left_border);
+
+ attributes.cursor = XCreateFontCursor(qt_xdisplay(),
+ XC_sb_right_arrow);
+ electric_right_border = XCreateWindow (qt_xdisplay(), qt_xrootwin(),
+ r.width()-1,0,
+ 1,r.height(),
+ 0,
+ CopyFromParent, InputOnly,
+ CopyFromParent,
+ valuemask, &attributes);
+ XMapWindow(qt_xdisplay(), electric_right_border);
+ // Set XdndAware on the windows, so that DND enter events are received (#86998)
+ Atom version = 4; // XDND version
+ XChangeProperty( qt_xdisplay(), electric_top_border, atoms->xdnd_aware, XA_ATOM,
+ 32, PropModeReplace, ( unsigned char* )&version, 1 );
+ XChangeProperty( qt_xdisplay(), electric_bottom_border, atoms->xdnd_aware, XA_ATOM,
+ 32, PropModeReplace, ( unsigned char* )&version, 1 );
+ XChangeProperty( qt_xdisplay(), electric_left_border, atoms->xdnd_aware, XA_ATOM,
+ 32, PropModeReplace, ( unsigned char* )&version, 1 );
+ XChangeProperty( qt_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(qt_xdisplay(),electric_top_border);
+ if(electric_bottom_border)
+ XDestroyWindow(qt_xdisplay(),electric_bottom_border);
+ if(electric_left_border)
+ XDestroyWindow(qt_xdisplay(),electric_left_border);
+ if(electric_right_border)
+ XDestroyWindow(qt_xdisplay(),electric_right_border);
+
+ electric_top_border = None;
+ electric_bottom_border = None;
+ electric_left_border = None;
+ electric_right_border = None;
+ }
+
+void Workspace::clientMoved(const QPoint &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;
+
+ QRect r = QApplication::desktop()->geometry();
+ int offset;
+
+ int desk_before = currentDesktop();
+ switch(border)
+ {
+ case 1:
+ slotSwitchDesktopLeft();
+ if (currentDesktop() != desk_before)
+ {
+ offset = r.width() / 5;
+ QCursor::setPos(r.width() - offset, pos.y());
+ }
+ break;
+
+ case 2:
+ slotSwitchDesktopRight();
+ if (currentDesktop() != desk_before)
+ {
+ offset = r.width() / 5;
+ QCursor::setPos(offset, pos.y());
+ }
+ break;
+
+ case 3:
+ slotSwitchDesktopUp();
+ if (currentDesktop() != desk_before)
+ {
+ offset = r.height() / 5;
+ QCursor::setPos(pos.x(), r.height() - offset);
+ }
+ break;
+
+ case 4:
+ slotSwitchDesktopDown();
+ if (currentDesktop() != desk_before)
+ {
+ offset = r.height() / 5;
+ QCursor::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: QCursor::setPos(pos.x()+mouse_warp, pos.y()); break;
+ case 2: QCursor::setPos(pos.x()-mouse_warp, pos.y()); break;
+ case 3: QCursor::setPos(pos.x(), pos.y()+mouse_warp); break;
+ case 4: QCursor::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( 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
+ && ( 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( QPoint( e->xclient.data.l[2]>>16, e->xclient.data.l[2]&0xffff), 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(qt_xdisplay(), electric_top_border);
+ XRaiseWindow(qt_xdisplay(), electric_left_border);
+ XRaiseWindow(qt_xdisplay(), electric_bottom_border);
+ XRaiseWindow(qt_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, 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.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, SIGNAL( lostOwnership()), this, SLOT( lostTopMenuSelection()));
+ disconnect( topmenu_watcher, SIGNAL( lostOwner()), this, SLOT( lostTopMenuOwner()));
+ managing_topmenus = true;
+ topmenu_space = new QWidget;
+ Window stack[ 2 ];
+ stack[ 0 ] = supportWindow->winId();
+ stack[ 1 ] = topmenu_space->winId();
+ XRestackWindows(qt_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 );
+ }
+
+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(qt_xdisplay(), null_focus_window, RevertToPointerRoot, qt_x_time );
+ }
+
+void Workspace::helperDialog( const QString& message, const Client* c )
+ {
+ QStringList args;
+ QString type;
+ if( message == "noborderaltf3" )
+ {
+ QString shortcut = QString( "%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" )
+ {
+ QString shortcut = QString( "%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( "kwin_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" << "kwin_dialogsrc:" + type;
+ }
+ if( c != NULL )
+ proc << "--embed" << QString::number( c->window());
+ proc.start( KProcess::DontCare );
+ }
+
+
+// kompmgr stuff
+
+void Workspace::startKompmgr()
+{
+ 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( qt_xdisplay()));
+ kompmgr_selection = new KSelectionOwner( selection_name );
+ connect( kompmgr_selection, SIGNAL( lostOwnership()), SLOT( stopKompmgr()));
+ kompmgr_selection->claim( true );
+ connect(kompmgr, SIGNAL(processExited(KProcess*)), SLOT(restartKompmgr()));
+ options->useTranslucency = TRUE;
+ allowKompmgrRestart = FALSE;
+ QTimer::singleShot( 60000, this, SLOT(unblockKompmgrRestart()) );
+ QByteArray ba;
+ QDataStream 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, SLOT(restartKompmgr()));
+ options->useTranslucency = FALSE;
+ if (popup){ delete popup; popup = 0L; } // to add/remove opacity slider
+ kompmgr->kill();
+ QByteArray ba;
+ QDataStream 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()
+// this is for inernal purpose (crashhandling) only, usually you want to use workspace->stopKompmgr(); QTimer::singleShot(200, workspace, SLOT(startKompmgr()));
+{
+ if (!allowKompmgrRestart) // uh-ohh
+ {
+ delete kompmgr_selection;
+ kompmgr_selection = NULL;
+ options->useTranslucency = FALSE;
+ 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;
+ QTimer::singleShot( 60000, this, SLOT(unblockKompmgrRestart()) );
+ }
+}
+
+void Workspace::handleKompmgrOutput( KProcess* , char *buffer, int buflen)
+{
+ QString message;
+ QString output = QString::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 &ge; 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 &ge; 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 &ge; 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 &ge; 6.8 for translucency and shadows to work.</qt>");
+ else return; //skip others
+ // kompmgr startup failed or succeeded, release connection
+ kompmgr->closeStderr();
+ disconnect(kompmgr, SIGNAL(receivedStderr(KProcess*, char*, int)), this, 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 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.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 );
+ // kwin will get the kipc message too
+ }
+ }
+
+void Workspace::disableGlobalShortcuts( bool disable )
+ {
+ KIPC::sendMessageAll( KIPC::BlockShortcuts, disable );
+ // kwin 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"