You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1822 lines
62 KiB
1822 lines
62 KiB
/******************************************************************** |
|
KWin - the KDE window manager |
|
This file is part of the KDE project. |
|
|
|
Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org> |
|
Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org> |
|
|
|
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 <QDesktopWidget> |
|
#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 <kdeversion.h> |
|
#include <QtDBus/QtDBus> |
|
#include <QtConcurrentRun> |
|
|
|
#include "client.h" |
|
#include "composite.h" |
|
#include "decorations.h" |
|
#include "focuschain.h" |
|
#ifdef KWIN_BUILD_TABBOX |
|
#include "tabbox.h" |
|
#endif |
|
#include "atoms.h" |
|
#include "cursor.h" |
|
#include "placement.h" |
|
#include "notifications.h" |
|
#include "outline.h" |
|
#include "group.h" |
|
#include "rules.h" |
|
#include "dbusinterface.h" |
|
#include "unmanaged.h" |
|
#include "deleted.h" |
|
#include "effects.h" |
|
#include "overlaywindow.h" |
|
#include "useractions.h" |
|
#include "virtualdesktops.h" |
|
#include "xcbutils.h" |
|
#include <kdecorationfactory.h> |
|
#include <kwinglplatform.h> |
|
#include <kwinglutils.h> |
|
#ifdef KWIN_BUILD_SCREENEDGES |
|
#include "screenedge.h" |
|
#endif |
|
#ifdef KWIN_BUILD_SCRIPTING |
|
#include "scripting/scripting.h" |
|
#endif |
|
#ifdef KWIN_BUILD_ACTIVITIES |
|
#include "activities.h" |
|
#endif |
|
#ifdef KWIN_BUILD_KAPPMENU |
|
#include "appmenu.h" |
|
#endif |
|
|
|
#include <X11/extensions/shape.h> |
|
#include <X11/keysym.h> |
|
#include <X11/keysymdef.h> |
|
#include <X11/cursorfont.h> |
|
#include <stdio.h> |
|
#include <kglobalsettings.h> |
|
#include <kwindowsystem.h> |
|
#include <kwindowinfo.h> |
|
|
|
namespace KWin |
|
{ |
|
|
|
extern int screen_number; |
|
extern bool is_multihead; |
|
|
|
Workspace* Workspace::_self = 0; |
|
|
|
Workspace::Workspace(bool restore) |
|
: QObject(0) |
|
, m_compositor(NULL) |
|
// Unsorted |
|
, active_popup(NULL) |
|
, active_popup_client(NULL) |
|
, temporaryRulesMessages("_KDE_NET_WM_TEMPORARY_RULES", NULL) |
|
, 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) |
|
, block_focus(0) |
|
, m_userActionsMenu(new UserActionsMenu(this)) |
|
, keys(0) |
|
, client_keys(NULL) |
|
, disable_shortcuts_keys(NULL) |
|
, client_keys_dialog(NULL) |
|
, client_keys_client(NULL) |
|
, global_shortcuts_disabled(false) |
|
, global_shortcuts_disabled_for_client(false) |
|
, workspaceInit(true) |
|
, startup(0) |
|
, set_active_client_recursion(0) |
|
, block_stacking_updates(0) |
|
, forced_global_mouse_grab(false) |
|
{ |
|
// If KWin was already running it saved its configuration after loosing the selection -> Reread |
|
QFuture<void> reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration); |
|
|
|
#ifdef KWIN_BUILD_KAPPMENU |
|
ApplicationMenu::create(this); |
|
#endif |
|
|
|
_self = this; |
|
|
|
// first initialize the extensions |
|
Extensions::init(); |
|
Xcb::Extensions::self(); |
|
|
|
// start the cursor support |
|
Cursor::create(this); |
|
|
|
#ifdef KWIN_BUILD_ACTIVITIES |
|
Activities *activities = Activities::create(this); |
|
connect(activities, SIGNAL(currentChanged(QString)), SLOT(updateCurrentActivity(QString))); |
|
#endif |
|
|
|
// PluginMgr needs access to the config file, so we need to wait for it for finishing |
|
reparseConfigFuture.waitForFinished(); |
|
options->loadConfig(); |
|
options->loadCompositingConfig(false); |
|
DecorationPlugin::create(this); |
|
default_colormap = DefaultColormap(display(), screen_number); |
|
installed_colormap = default_colormap; |
|
|
|
connect(&temporaryRulesMessages, SIGNAL(gotMessage(QString)), |
|
this, SLOT(gotTemporaryRulesMessage(QString))); |
|
connect(&rulesUpdatedTimer, SIGNAL(timeout()), this, SLOT(writeWindowRules())); |
|
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 |
|
); |
|
|
|
#ifdef KWIN_BUILD_SCREENEDGES |
|
ScreenEdges::create(this); |
|
#endif |
|
|
|
// VirtualDesktopManager needs to be created prior to init shortcuts |
|
// and prior to TabBox, due to TabBox connecting to signals |
|
// actual initialization happens in init() |
|
VirtualDesktopManager::create(this); |
|
|
|
#ifdef KWIN_BUILD_TABBOX |
|
// need to create the tabbox before compositing scene is setup |
|
TabBox::TabBox::create(this); |
|
#endif |
|
|
|
m_compositor = Compositor::create(this); |
|
connect(this, SIGNAL(currentDesktopChanged(int,KWin::Client*)), m_compositor, SLOT(addRepaintFull())); |
|
connect(m_compositor, SIGNAL(compositingToggled(bool)), decorationPlugin(), SLOT(resetCompositing())); |
|
|
|
new DBusInterface(this); |
|
|
|
// Compatibility |
|
long data = 1; |
|
|
|
XChangeProperty( |
|
display(), |
|
rootWindow(), |
|
atoms->kwin_running, |
|
atoms->kwin_running, |
|
32, |
|
PropModeAppend, |
|
(unsigned char*)(&data), |
|
1 |
|
); |
|
|
|
client_keys = new KActionCollection(this); |
|
|
|
m_outline = new Outline(); |
|
|
|
initShortcuts(); |
|
|
|
init(); |
|
|
|
connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), &screenChangedTimer, SLOT(start())); |
|
connect(QApplication::desktop(), SIGNAL(resized(int)), &screenChangedTimer, SLOT(start())); |
|
|
|
connect(&screenChangedTimer, SIGNAL(timeout()), SLOT(screenChangeTimeout())); |
|
screenChangedTimer.setSingleShot(true); |
|
screenChangedTimer.setInterval(100); |
|
} |
|
|
|
void Workspace::screenChangeTimeout() |
|
{ |
|
kDebug() << "It is time to call desktopResized"; |
|
desktopResized(); |
|
} |
|
|
|
void Workspace::init() |
|
{ |
|
#ifdef KWIN_BUILD_SCREENEDGES |
|
ScreenEdges *screenEdges = ScreenEdges::self(); |
|
screenEdges->setConfig(KGlobal::config()); |
|
screenEdges->init(); |
|
connect(options, SIGNAL(configChanged()), screenEdges, SLOT(reconfigure())); |
|
connect(VirtualDesktopManager::self(), SIGNAL(layoutChanged(int,int)), screenEdges, SLOT(updateLayout())); |
|
connect(this, SIGNAL(clientActivated(KWin::Client*)), screenEdges, SIGNAL(checkBlocking())); |
|
#endif |
|
|
|
FocusChain *focusChain = FocusChain::create(this); |
|
connect(this, SIGNAL(clientRemoved(KWin::Client*)), focusChain, SLOT(remove(KWin::Client*))); |
|
connect(this, SIGNAL(clientActivated(KWin::Client*)), focusChain, SLOT(setActiveClient(KWin::Client*))); |
|
connect(VirtualDesktopManager::self(), SIGNAL(countChanged(uint,uint)), focusChain, SLOT(resize(uint,uint))); |
|
connect(VirtualDesktopManager::self(), SIGNAL(currentChanged(uint,uint)), focusChain, SLOT(setCurrentDesktop(uint,uint))); |
|
connect(options, SIGNAL(separateScreenFocusChanged(bool)), focusChain, SLOT(setSeparateScreenFocus(bool))); |
|
focusChain->setSeparateScreenFocus(options->isSeparateScreenFocus()); |
|
|
|
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::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 | |
|
NET::WM2KDEShadow | |
|
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 |
|
, |
|
}; |
|
|
|
DecorationPlugin *deco = DecorationPlugin::self(); |
|
if (!deco->isDisabled() && deco->factory()->supports(AbilityExtendIntoClientArea)) |
|
protocols[ NETRootInfo::PROTOCOLS2 ] |= NET::WM2FrameOverlap; |
|
|
|
rootInfo = new RootInfo(this, display(), supportWindow->winId(), "KWin", protocols, 5, screen_number); |
|
|
|
// create VirtualDesktopManager and perform dependency injection |
|
VirtualDesktopManager *vds = VirtualDesktopManager::self(); |
|
connect(vds, SIGNAL(desktopsRemoved(uint)), SLOT(moveClientsFromRemovedDesktops())); |
|
connect(vds, SIGNAL(countChanged(uint,uint)), SLOT(slotDesktopCountChanged(uint,uint))); |
|
connect(vds, SIGNAL(currentChanged(uint,uint)), SLOT(slotCurrentDesktopChanged(uint,uint))); |
|
vds->setNavigationWrappingAround(options->isRollOverDesktops()); |
|
connect(options, SIGNAL(rollOverDesktopsChanged(bool)), vds, SLOT(setNavigationWrappingAround(bool))); |
|
vds->setRootInfo(rootInfo); |
|
vds->setConfig(KGlobal::config()); |
|
|
|
// Now we know how many desktops we'll have, thus we initialize the positioning object |
|
Placement::create(this); |
|
|
|
// positioning object needs to be created before the virtual desktops are loaded. |
|
vds->load(); |
|
vds->updateLayout(); |
|
|
|
// 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 (!VirtualDesktopManager::self()->setCurrent(initial_desktop)) |
|
VirtualDesktopManager::self()->setCurrent(1); |
|
#ifdef KWIN_BUILD_ACTIVITIES |
|
Activities::self()->update(false, true); |
|
#endif |
|
|
|
reconfigureTimer.setSingleShot(true); |
|
updateToolWindowsTimer.setSingleShot(true); |
|
|
|
connect(&reconfigureTimer, SIGNAL(timeout()), this, SLOT(slotReconfigure())); |
|
connect(&updateToolWindowsTimer, SIGNAL(timeout()), this, SLOT(slotUpdateToolWindows())); |
|
|
|
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 |
|
|
|
{ |
|
// Begin updates blocker block |
|
StackingUpdatesBlocker blocker(this); |
|
|
|
bool fixoffset = KCmdLineArgs::parsedArgs()->getOption("crashes").toInt() > 0; |
|
|
|
Xcb::Tree tree(rootWindow()); |
|
xcb_window_t *wins = xcb_query_tree_children(tree.data()); |
|
|
|
QVector<Xcb::WindowAttributes> windowAttributes(tree->children_len); |
|
QVector<Xcb::WindowGeometry> windowGeometries(tree->children_len); |
|
|
|
// Request the attributes and geometries of all toplevel windows |
|
for (int i = 0; i < tree->children_len; i++) { |
|
windowAttributes[i] = Xcb::WindowAttributes(wins[i]); |
|
windowGeometries[i] = Xcb::WindowGeometry(wins[i]); |
|
} |
|
|
|
// Get the replies |
|
for (int i = 0; i < tree->children_len; i++) { |
|
Xcb::WindowAttributes attr(windowAttributes.at(i)); |
|
|
|
if (attr.isNull()) { |
|
continue; |
|
} |
|
|
|
if (attr->override_redirect) { |
|
if (attr->map_state == XCB_MAP_STATE_VIEWABLE && |
|
attr->_class != XCB_WINDOW_CLASS_INPUT_ONLY) |
|
// ### This will request the attributes again |
|
createUnmanaged(wins[i]); |
|
} else if (attr->map_state != XCB_MAP_STATE_UNMAPPED) { |
|
if (fixoffset) { |
|
fixPositionAfterCrash(wins[i], windowGeometries[i].data()); |
|
} |
|
|
|
// ### This will request the attributes again |
|
createClient(wins[i], true); |
|
} |
|
} |
|
|
|
// Propagate clients, will really happen at the end of the updates blocker block |
|
updateStackingOrder(true); |
|
|
|
saveOldScreenSizes(); |
|
updateClientArea(); |
|
|
|
// NETWM spec says we have to set it to (0,0) if we don't support it |
|
NETPoint* viewports = new NETPoint[VirtualDesktopManager::self()->count()]; |
|
rootInfo->setDesktopViewport(VirtualDesktopManager::self()->count(), *viewports); |
|
delete[] viewports; |
|
QRect geom; |
|
for (int i = 0; i < QApplication::desktop()->screenCount(); i++) { |
|
geom |= QApplication::desktop()->screenGeometry(i); |
|
} |
|
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(VirtualDesktopManager::self()->current(), -1); |
|
if (new_active_client == NULL && !desktops.isEmpty()) |
|
new_active_client = findDesktop(true, VirtualDesktopManager::self()->current()); |
|
} |
|
if (new_active_client != NULL) |
|
activateClient(new_active_client); |
|
|
|
|
|
#ifdef KWIN_BUILD_SCRIPTING |
|
Scripting::create(this); |
|
#endif |
|
|
|
// 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; |
|
|
|
// broadcast that Workspace is ready, but first process all events. |
|
QMetaObject::invokeMethod(this, "workspaceInitialized", Qt::QueuedConnection); |
|
|
|
// TODO: ungrabXServer() |
|
} |
|
|
|
Workspace::~Workspace() |
|
{ |
|
delete m_compositor; |
|
m_compositor = NULL; |
|
blockStackingUpdates(true); |
|
|
|
// TODO: grabXServer(); |
|
|
|
// Use stacking_order, so that kwin --replace keeps stacking order |
|
for (ToplevelList::iterator it = stacking_order.begin(), end = stacking_order.end(); it != end; ++it) { |
|
Client *c = qobject_cast<Client*>(*it); |
|
if (!c) { |
|
continue; |
|
} |
|
// Only release the window |
|
c->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(c); |
|
desktops.removeAll(c); |
|
} |
|
for (UnmanagedList::iterator it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it) |
|
(*it)->release(true); |
|
delete m_outline; |
|
XDeleteProperty(display(), rootWindow(), atoms->kwin_running); |
|
|
|
writeWindowRules(); |
|
KGlobal::config()->sync(); |
|
|
|
delete rootInfo; |
|
delete supportWindow; |
|
delete startup; |
|
delete Placement::self(); |
|
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(); |
|
|
|
Xcb::Extensions::destroy(); |
|
_self = 0; |
|
} |
|
|
|
Client* Workspace::createClient(Window w, bool is_mapped) |
|
{ |
|
StackingUpdatesBlocker blocker(this); |
|
Client* c = new Client(this); |
|
connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); |
|
connect(c, SIGNAL(activeChanged()), m_compositor, SLOT(checkUnredirect())); |
|
connect(c, SIGNAL(fullScreenChanged()), m_compositor, SLOT(checkUnredirect())); |
|
connect(c, SIGNAL(geometryChanged()), m_compositor, SLOT(checkUnredirect())); |
|
connect(c, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), m_compositor, SLOT(checkUnredirect())); |
|
connect(c, SIGNAL(blockingCompositingChanged(KWin::Client*)), m_compositor, SLOT(updateCompositeBlocking(KWin::Client*))); |
|
#ifdef KWIN_BUILD_SCREENEDGES |
|
connect(c, SIGNAL(clientFullScreenSet(KWin::Client*,bool,bool)), ScreenEdges::self(), SIGNAL(checkBlocking())); |
|
#endif |
|
if (!c->manage(w, is_mapped)) { |
|
Client::deleteClient(c, Allowed); |
|
return NULL; |
|
} |
|
addClient(c, Allowed); |
|
return c; |
|
} |
|
|
|
Unmanaged* Workspace::createUnmanaged(Window w) |
|
{ |
|
if (m_compositor && m_compositor->checkForOverlayWindow(w)) |
|
return NULL; |
|
Unmanaged* c = new Unmanaged(this); |
|
if (!c->track(w)) { |
|
Unmanaged::deleteUnmanaged(c, Allowed); |
|
return NULL; |
|
} |
|
connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); |
|
addUnmanaged(c, Allowed); |
|
emit unmanagedAdded(c); |
|
return c; |
|
} |
|
|
|
void Workspace::addClient(Client* c, allowed_t) |
|
{ |
|
Group* grp = findGroup(c->window()); |
|
|
|
KWindowInfo info = KWindowSystem::windowInfo(c->window(), -1U, NET::WM2WindowClass); |
|
|
|
emit clientAdded(c); |
|
|
|
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 { |
|
FocusChain::self()->update(c, FocusChain::Update); |
|
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 |
|
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, VirtualDesktopManager::self()->current())); |
|
} |
|
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(); |
|
#ifdef KWIN_BUILD_TABBOX |
|
if (TabBox::TabBox::self()->isDisplayed()) |
|
TabBox::TabBox::self()->reset(true); |
|
#endif |
|
#ifdef KWIN_BUILD_KAPPMENU |
|
if (ApplicationMenu::self()->hasMenu(c->window())) |
|
c->setAppMenuAvailable(); |
|
#endif |
|
} |
|
|
|
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) |
|
{ |
|
emit clientRemoved(c); |
|
|
|
if (c == active_popup_client) |
|
closeActivePopup(); |
|
if (m_userActionsMenu->isMenuClient(c)) { |
|
m_userActionsMenu->close(); |
|
} |
|
|
|
c->untab(QRect(), true); |
|
|
|
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); |
|
|
|
#ifdef KWIN_BUILD_TABBOX |
|
TabBox::TabBox *tabBox = TabBox::TabBox::self(); |
|
if (tabBox->isDisplayed() && tabBox->currentClient() == c) |
|
tabBox->nextPrev(true); |
|
#endif |
|
|
|
Q_ASSERT(clients.contains(c) || desktops.contains(c)); |
|
// TODO: if marked client is removed, notify the marked list |
|
clients.removeAll(c); |
|
desktops.removeAll(c); |
|
x_stacking_dirty = true; |
|
attention_chain.removeAll(c); |
|
showing_desktop_clients.removeAll(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 (m_compositor) { |
|
m_compositor->updateCompositeBlocking(); |
|
} |
|
|
|
#ifdef KWIN_BUILD_TABBOX |
|
if (tabBox->isDisplayed()) |
|
tabBox->reset(true); |
|
#endif |
|
|
|
updateClientArea(); |
|
} |
|
|
|
void Workspace::removeUnmanaged(Unmanaged* c, allowed_t) |
|
{ |
|
assert(unmanaged.contains(c)); |
|
unmanaged.removeAll(c); |
|
x_stacking_dirty = true; |
|
} |
|
|
|
void Workspace::addDeleted(Deleted* c, Toplevel *orig, allowed_t) |
|
{ |
|
assert(!deleted.contains(c)); |
|
deleted.append(c); |
|
const int unconstraintedIndex = unconstrained_stacking_order.indexOf(orig); |
|
if (unconstraintedIndex != -1) { |
|
unconstrained_stacking_order.replace(unconstraintedIndex, c); |
|
} else { |
|
unconstrained_stacking_order.append(c); |
|
} |
|
const int index = stacking_order.indexOf(orig); |
|
if (index != -1) { |
|
stacking_order.replace(index, c); |
|
} else { |
|
stacking_order.append(c); |
|
} |
|
x_stacking_dirty = true; |
|
connect(c, SIGNAL(needsRepaint()), m_compositor, SLOT(scheduleRepaint())); |
|
} |
|
|
|
void Workspace::removeDeleted(Deleted* c, allowed_t) |
|
{ |
|
assert(deleted.contains(c)); |
|
emit deletedRemoved(c); |
|
deleted.removeAll(c); |
|
unconstrained_stacking_order.removeAll(c); |
|
stacking_order.removeAll(c); |
|
x_stacking_dirty = 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->isHideUtilityWindowsForInactive()) { |
|
for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) |
|
if (!(*it)->tabGroup() || (*it)->tabGroup()->current() == *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 (ToplevelList::ConstIterator it = stacking_order.constBegin(); |
|
it != stacking_order.constEnd(); |
|
++it) { |
|
Client *c = qobject_cast<Client*>(*it); |
|
if (!c) { |
|
continue; |
|
} |
|
if (c->isUtility() || c->isMenu() || c->isToolbar()) { |
|
bool show = true; |
|
if (!c->isTransient()) { |
|
if (c->group()->members().count() == 1) // Has its own group, keep always visible |
|
show = true; |
|
else if (client != NULL && c->group() == client->group()) |
|
show = true; |
|
else |
|
show = false; |
|
} else { |
|
if (group != NULL && c->group() == group) |
|
show = true; |
|
else if (client != NULL && client->hasTransient(c, true)) |
|
show = true; |
|
else |
|
show = false; |
|
} |
|
if (!show && also_hide) { |
|
const ClientList mainclients = c->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 (c->isSpecialWindow()) |
|
show = true; |
|
} |
|
if (!show) |
|
to_hide.append(c); |
|
} |
|
if (show) |
|
to_show.append(c); |
|
} |
|
} // 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 |
|
resetUpdateToolWindowsTimer(); |
|
} |
|
|
|
|
|
void Workspace::resetUpdateToolWindowsTimer() |
|
{ |
|
updateToolWindowsTimer.start(200); |
|
} |
|
|
|
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(); |
|
} |
|
if (m_compositor) { |
|
return m_compositor->isActive(); |
|
} |
|
return false; |
|
} |
|
|
|
void Workspace::slotSettingsChanged(int category) |
|
{ |
|
kDebug(1212) << "Workspace::slotSettingsChanged()"; |
|
if (category == KGlobalSettings::SETTINGS_SHORTCUTS) |
|
m_userActionsMenu->discard(); |
|
} |
|
|
|
/** |
|
* Reread settings |
|
*/ |
|
KWIN_PROCEDURE(CheckBorderSizesProcedure, Client, cl->checkBorderSizes(true)); |
|
|
|
void Workspace::slotReconfigure() |
|
{ |
|
kDebug(1212) << "Workspace::slotReconfigure()"; |
|
reconfigureTimer.stop(); |
|
|
|
bool borderlessMaximizedWindows = options->borderlessMaximizedWindows(); |
|
|
|
KGlobal::config()->reparseConfiguration(); |
|
unsigned long changed = options->updateSettings(); |
|
|
|
emit configChanged(); |
|
m_userActionsMenu->discard(); |
|
updateToolWindows(true); |
|
|
|
DecorationPlugin *deco = DecorationPlugin::self(); |
|
if (!deco->isDisabled() && deco->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 (!decorationPlugin()->supportsTabbing()) { |
|
foreach (Client * c, clients) |
|
c->untab(); |
|
} |
|
deco->destroyPreviousPlugin(); |
|
} else { |
|
forEachClient(CheckBorderSizesProcedure()); |
|
foreach (Client * c, clients) |
|
c->triggerDecorationRepaint(); |
|
} |
|
|
|
loadWindowRules(); |
|
for (ClientList::Iterator it = clients.begin(); |
|
it != clients.end(); |
|
++it) { |
|
(*it)->setupWindowRules(true); |
|
(*it)->applyWindowRules(); |
|
discardUsedWindowRules(*it, false); |
|
} |
|
|
|
if (borderlessMaximizedWindows != options->borderlessMaximizedWindows() && |
|
!options->borderlessMaximizedWindows()) { |
|
// in case borderless maximized windows option changed and new option |
|
// is to have borders, we need to unset the borders for all maximized windows |
|
for (ClientList::Iterator it = clients.begin(); |
|
it != clients.end(); |
|
++it) { |
|
if ((*it)->maximizeMode() == MaximizeFull) |
|
(*it)->checkNoBorder(); |
|
} |
|
} |
|
|
|
if (!deco->isDisabled()) { |
|
rootInfo->setSupported(NET::WM2FrameOverlap, deco->factory()->supports(AbilityExtendIntoClientArea)); |
|
} else { |
|
rootInfo->setSupported(NET::WM2FrameOverlap, false); |
|
} |
|
} |
|
|
|
/** |
|
* 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 (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); |
|
} |
|
} |
|
|
|
void Workspace::slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop) |
|
{ |
|
closeActivePopup(); |
|
++block_focus; |
|
StackingUpdatesBlocker blocker(this); |
|
updateClientVisibilityOnDesktopChange(oldDesktop, newDesktop); |
|
// Restore the focus on this desktop |
|
--block_focus; |
|
|
|
activateClientOnNewDesktop(newDesktop); |
|
emit currentDesktopChanged(oldDesktop, movingClient); |
|
} |
|
|
|
void Workspace::updateClientVisibilityOnDesktopChange(uint oldDesktop, uint newDesktop) |
|
{ |
|
++block_showing_desktop; |
|
ObscuringWindows obs_wins; |
|
for (ToplevelList::ConstIterator it = stacking_order.constBegin(); |
|
it != stacking_order.constEnd(); |
|
++it) { |
|
Client *c = qobject_cast<Client*>(*it); |
|
if (!c) { |
|
continue; |
|
} |
|
if (!c->isOnDesktop(newDesktop) && c != movingClient && c->isOnCurrentActivity()) { |
|
if (c->isShown(true) && c->isOnDesktop(oldDesktop) && !compositing()) |
|
obs_wins.create(c); |
|
(c)->updateVisibility(); |
|
} |
|
} |
|
// Now propagate the change, after hiding, before showing |
|
rootInfo->setCurrentDesktop(VirtualDesktopManager::self()->current()); |
|
|
|
if (movingClient && !movingClient->isOnDesktop(newDesktop)) { |
|
movingClient->setDesktop(newDesktop); |
|
} |
|
|
|
for (int i = stacking_order.size() - 1; i >= 0 ; --i) { |
|
Client *c = qobject_cast<Client*>(stacking_order.at(i)); |
|
if (!c) { |
|
continue; |
|
} |
|
if (c->isOnDesktop(newDesktop) && c->isOnCurrentActivity()) |
|
c->updateVisibility(); |
|
} |
|
--block_showing_desktop; |
|
if (showingDesktop()) // Do this only after desktop change to avoid flicker |
|
resetShowingDesktop(false); |
|
} |
|
|
|
void Workspace::activateClientOnNewDesktop(uint desktop) |
|
{ |
|
Client* c = NULL; |
|
if (options->focusPolicyIsReasonable()) { |
|
c = findClientToActivateOnDesktop(desktop); |
|
} |
|
// 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, desktop); |
|
|
|
if (c != active_client) |
|
setActiveClient(NULL, Allowed); |
|
|
|
if (c) |
|
requestFocus(c); |
|
else if (!desktops.isEmpty()) |
|
requestFocus(findDesktop(true, desktop)); |
|
else |
|
focusToNull(); |
|
} |
|
|
|
Client *Workspace::findClientToActivateOnDesktop(uint desktop) |
|
{ |
|
if (movingClient != NULL && active_client == movingClient && |
|
FocusChain::self()->contains(active_client, desktop) && |
|
active_client->isShown(true) && active_client->isOnCurrentDesktop()) { |
|
// A requestFocus call will fail, as the client is already active |
|
return active_client; |
|
} |
|
// from actiavtion.cpp |
|
if (options->isNextFocusPrefersMouse()) { |
|
ToplevelList::const_iterator it = stackingOrder().constEnd(); |
|
while (it != stackingOrder().constBegin()) { |
|
Client *client = qobject_cast<Client*>(*(--it)); |
|
if (!client) { |
|
continue; |
|
} |
|
|
|
if (!(client->isShown(false) && client->isOnDesktop(desktop) && |
|
client->isOnCurrentActivity() && client->isOnScreen(activeScreen()))) |
|
continue; |
|
|
|
if (client->geometry().contains(Cursor::pos())) { |
|
if (!client->isDesktop()) |
|
return client; |
|
break; // unconditional break - we do not pass the focus to some client below an unusable one |
|
} |
|
} |
|
} |
|
return FocusChain::self()->getForActivation(desktop); |
|
} |
|
|
|
/** |
|
* Updates the current activity when it changes |
|
* do *not* call this directly; it does not set the activity. |
|
* |
|
* Shows/Hides windows according to the stacking order |
|
*/ |
|
|
|
void Workspace::updateCurrentActivity(const QString &new_activity) |
|
{ |
|
#ifdef KWIN_BUILD_ACTIVITIES |
|
//closeActivePopup(); |
|
++block_focus; |
|
// TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date |
|
StackingUpdatesBlocker blocker(this); |
|
|
|
++block_showing_desktop; //FIXME should I be using that? |
|
// 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; |
|
|
|
const QString &old_activity = Activities::self()->previous(); |
|
|
|
for (ToplevelList::ConstIterator it = stacking_order.constBegin(); |
|
it != stacking_order.constEnd(); |
|
++it) { |
|
Client *c = qobject_cast<Client*>(*it); |
|
if (!c) { |
|
continue; |
|
} |
|
if (!c->isOnActivity(new_activity) && c != movingClient && c->isOnCurrentDesktop()) { |
|
if (c->isShown(true) && c->isOnActivity(old_activity) && !compositing()) |
|
obs_wins.create(c); |
|
c->updateVisibility(); |
|
} |
|
} |
|
|
|
// Now propagate the change, after hiding, before showing |
|
//rootInfo->setCurrentDesktop( currentDesktop() ); |
|
|
|
/* TODO someday enable dragging windows to other activities |
|
if ( movingClient && !movingClient->isOnDesktop( new_desktop )) |
|
{ |
|
movingClient->setDesktop( new_desktop ); |
|
*/ |
|
|
|
for (int i = stacking_order.size() - 1; i >= 0 ; --i) { |
|
Client *c = qobject_cast<Client*>(stacking_order.at(i)); |
|
if (!c) { |
|
continue; |
|
} |
|
if (c->isOnActivity(new_activity)) |
|
c->updateVisibility(); |
|
} |
|
|
|
--block_showing_desktop; |
|
//FIXME not sure if I should do this either |
|
if (showingDesktop()) // Do this only after desktop change to avoid flicker |
|
resetShowingDesktop(false); |
|
|
|
// Restore the focus on this desktop |
|
--block_focus; |
|
Client* c = 0; |
|
|
|
//FIXME below here is a lot of focuschain stuff, probably all wrong now |
|
if (options->focusPolicyIsReasonable()) { |
|
// Search in focus chain |
|
c = FocusChain::self()->getForActivation(VirtualDesktopManager::self()->current()); |
|
} |
|
// 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() && active_client->isOnCurrentActivity()) |
|
c = active_client; |
|
|
|
if (c == NULL && !desktops.isEmpty()) |
|
c = findDesktop(true, VirtualDesktopManager::self()->current()); |
|
|
|
if (c != active_client) |
|
setActiveClient(NULL, Allowed); |
|
|
|
if (c) |
|
requestFocus(c); |
|
else if (!desktops.isEmpty()) |
|
requestFocus(findDesktop(true, VirtualDesktopManager::self()->current())); |
|
else |
|
focusToNull(); |
|
|
|
// Not for the very first time, only if something changed and there are more than 1 desktops |
|
|
|
//if ( effects != NULL && old_desktop != 0 && old_desktop != new_desktop ) |
|
// static_cast<EffectsHandlerImpl*>( effects )->desktopChanged( old_desktop ); |
|
if (compositing() && m_compositor) |
|
m_compositor->addRepaintFull(); |
|
#else |
|
Q_UNUSED(new_activity) |
|
#endif |
|
} |
|
|
|
void Workspace::moveClientsFromRemovedDesktops() |
|
{ |
|
for (ClientList::ConstIterator it = clients.constBegin(); it != clients.constEnd(); ++it) { |
|
if (!(*it)->isOnAllDesktops() && (*it)->desktop() > static_cast<int>(VirtualDesktopManager::self()->count())) |
|
sendClientToDesktop(*it, VirtualDesktopManager::self()->count(), true); |
|
} |
|
} |
|
|
|
void Workspace::slotDesktopCountChanged(uint previousCount, uint newCount) |
|
{ |
|
Q_UNUSED(previousCount) |
|
Placement::self()->reinitCascading(0); |
|
|
|
resetClientAreas(newCount); |
|
} |
|
|
|
void Workspace::resetClientAreas(uint desktopCount) |
|
{ |
|
// Make it +1, so that it can be accessed as [1..numberofdesktops] |
|
workarea.clear(); |
|
workarea.resize(desktopCount + 1); |
|
restrictedmovearea.clear(); |
|
restrictedmovearea.resize(desktopCount + 1); |
|
screenarea.clear(); |
|
|
|
updateClientArea(true); |
|
} |
|
|
|
/** |
|
* Sends client \a c to desktop \a desk. |
|
* |
|
* Takes care of transients as well. |
|
*/ |
|
void Workspace::sendClientToDesktop(Client* c, int desk, bool dont_activate) |
|
{ |
|
if ((desk < 1 && desk != NET::OnAllDesktops) || desk > static_cast<int>(VirtualDesktopManager::self()->count())) |
|
return; |
|
int old_desktop = c->desktop(); |
|
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 |
|
|
|
emit desktopPresenceChanged(c, old_desktop); |
|
|
|
if (c->isOnDesktop(VirtualDesktopManager::self()->current())) { |
|
if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && |
|
!was_on_desktop && // for stickyness changes |
|
!dont_activate) |
|
requestFocus(c); |
|
else |
|
restackClientUnderActive(c); |
|
} else |
|
raiseClient(c); |
|
|
|
c->checkWorkspacePosition( QRect(), old_desktop ); |
|
|
|
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 |
|
{ |
|
return QApplication::desktop()->screenCount(); |
|
} |
|
|
|
int Workspace::activeScreen() const |
|
{ |
|
if (!options->isActiveMouseScreen()) { |
|
if (activeClient() != NULL && !activeClient()->isOnScreen(active_screen)) |
|
return activeClient()->screen(); |
|
return active_screen; |
|
} |
|
return QApplication::desktop()->screenNumber(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 (!c->isActive()) |
|
return; |
|
if (!c->isOnScreen(active_screen)) |
|
active_screen = c->screen(); |
|
} |
|
|
|
/** |
|
* checks whether the X Window with the input focus is on our X11 screen |
|
* if the window cannot be determined or inspected, resturn depends on whether there's actually |
|
* more than one screen |
|
* |
|
* this is NOT in any way related to XRandR multiscreen |
|
* |
|
*/ |
|
extern bool is_multihead; // main.cpp |
|
bool Workspace::isOnCurrentHead() |
|
{ |
|
if (!is_multihead) { |
|
return true; |
|
} |
|
|
|
Xcb::CurrentInput currentInput; |
|
if (currentInput.window() == XCB_WINDOW_NONE) { |
|
return !is_multihead; |
|
} |
|
|
|
Xcb::WindowGeometry geometry(currentInput.window()); |
|
if (geometry.isNull()) { // should not happen |
|
return !is_multihead; |
|
} |
|
|
|
return rootWindow() == geometry->root; |
|
} |
|
|
|
/** |
|
* 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) |
|
{ |
|
active_screen = QApplication::desktop()->screenNumber(mousepos); |
|
} |
|
|
|
QRect Workspace::screenGeometry(int screen) const |
|
{ |
|
return QApplication::desktop()->screenGeometry(screen); |
|
} |
|
|
|
int Workspace::screenNumber(const QPoint& pos) const |
|
{ |
|
return QApplication::desktop()->screenNumber(pos); |
|
} |
|
|
|
void Workspace::sendClientToScreen(Client* c, int screen) |
|
{ |
|
screen = c->rules()->checkScreen(screen); |
|
if (c->isActive()) { |
|
active_screen = screen; |
|
// might impact the layer of a fullscreen window |
|
foreach (Client *cc, clientList()) { |
|
if (cc->isFullScreen() && cc->screen() == screen) { |
|
updateClientLayer(cc); |
|
} |
|
} |
|
} |
|
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()); |
|
QRect oldgeom = c->geometry(); |
|
QRect geom = c->geometry(); |
|
// move the window to have the same relative position to the center of the screen |
|
// (i.e. one near the middle of the right edge will also end up near the middle of the right edge) |
|
geom.moveCenter( |
|
QPoint(( geom.center().x() - old_sarea.center().x()) * sarea.width() / old_sarea.width() + sarea.center().x(), |
|
( geom.center().y() - old_sarea.center().y()) * sarea.height() / old_sarea.height() + sarea.center().y())); |
|
c->setGeometry( geom ); |
|
// If the window was inside the old screen area, explicitly make sure its inside also the new screen area. |
|
// Calling checkWorkspacePosition() should ensure that, but when moving to a small screen the window could |
|
// be big enough to overlap outside of the new screen area, making struts from other screens come into effect, |
|
// which could alter the resulting geometry. |
|
if( old_sarea.contains( oldgeom )) |
|
c->keepInArea( sarea ); |
|
c->checkWorkspacePosition( oldgeom ); |
|
ClientList transients_stacking_order = ensureStackingOrder(c->transients()); |
|
for (ClientList::ConstIterator it = transients_stacking_order.constBegin(); |
|
it != transients_stacking_order.constEnd(); |
|
++it) |
|
sendClientToScreen(*it, 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; |
|
} |
|
|
|
/** |
|
* 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; |
|
} |
|
|
|
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::setShowingDesktop(bool showing) |
|
{ |
|
rootInfo->setShowingDesktop(showing); |
|
showing_desktop = showing; |
|
++block_showing_desktop; |
|
if (showing_desktop) { |
|
showing_desktop_clients.clear(); |
|
++block_focus; |
|
ToplevelList cls = stackingOrder(); |
|
// Find them first, then minimize, otherwise transients may get minimized with the window |
|
// they're transient for |
|
for (ToplevelList::ConstIterator it = cls.constBegin(); |
|
it != cls.constEnd(); |
|
++it) { |
|
Client *c = qobject_cast<Client*>(*it); |
|
if (!c) { |
|
continue; |
|
} |
|
if (c->isOnCurrentActivity() && c->isOnCurrentDesktop() && c->isShown(true) && !c->isSpecialWindow()) |
|
showing_desktop_clients.prepend(c); // 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, VirtualDesktopManager::self()->current())) |
|
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(); |
|
} |
|
|
|
Outline* Workspace::outline() |
|
{ |
|
return m_outline; |
|
} |
|
|
|
QString Workspace::supportInformation() const |
|
{ |
|
QString support; |
|
|
|
support.append(ki18nc("Introductory text shown in the support information.", |
|
"KWin Support Information:\n" |
|
"The following information should be used when requesting support on e.g. http://forum.kde.org.\n" |
|
"It provides information about the currently running instance, which options are used,\n" |
|
"what OpenGL driver and which effects are running.\n" |
|
"Please post the information provided underneath this introductory text to a paste bin service\n" |
|
"like http://paste.kde.org instead of pasting into support threads.\n").toString()); |
|
support.append("\n==========================\n\n"); |
|
// all following strings are intended for support. They need to be pasted to e.g forums.kde.org |
|
// it is expected that the support will happen in English language or that the people providing |
|
// help understand English. Because of that all texts are not translated |
|
support.append("Version\n"); |
|
support.append("=======\n"); |
|
support.append("KWin version: "); |
|
support.append(KWIN_VERSION_STRING); |
|
support.append('\n'); |
|
support.append("KDE SC version (runtime): "); |
|
support.append(KDE::versionString()); |
|
support.append('\n'); |
|
support.append("KDE SC version (compile): "); |
|
support.append(KDE_VERSION_STRING); |
|
support.append('\n'); |
|
support.append("Qt Version: "); |
|
support.append(qVersion()); |
|
support.append("\n\n"); |
|
support.append("Options\n"); |
|
support.append("=======\n"); |
|
const QMetaObject *metaOptions = options->metaObject(); |
|
for (int i=0; i<metaOptions->propertyCount(); ++i) { |
|
const QMetaProperty property = metaOptions->property(i); |
|
if (QLatin1String(property.name()) == "objectName") { |
|
continue; |
|
} |
|
support.append(QLatin1String(property.name()) % ": " % options->property(property.name()).toString() % '\n'); |
|
} |
|
#ifdef KWIN_BUILD_SCREENEDGES |
|
support.append("\nScreen Edges\n"); |
|
support.append( "============\n"); |
|
const QMetaObject *metaScreenEdges = ScreenEdges::self()->metaObject(); |
|
for (int i=0; i<metaScreenEdges->propertyCount(); ++i) { |
|
const QMetaProperty property = metaScreenEdges->property(i); |
|
if (QLatin1String(property.name()) == "objectName") { |
|
continue; |
|
} |
|
support.append(QLatin1String(property.name()) % ": " % ScreenEdges::self()->property(property.name()).toString() % '\n'); |
|
} |
|
#endif |
|
support.append("\nScreens\n"); |
|
support.append( "=======\n"); |
|
support.append("Multi-Head: "); |
|
if (is_multihead) { |
|
support.append("yes\n"); |
|
support.append(QString("Head: %1\n").arg(screen_number)); |
|
} else { |
|
support.append("no\n"); |
|
} |
|
support.append(QString("Number of Screens: %1\n").arg(QApplication::desktop()->screenCount())); |
|
for (int i=0; i<QApplication::desktop()->screenCount(); ++i) { |
|
const QRect geo = QApplication::desktop()->screenGeometry(i); |
|
support.append(QString("Screen %1 Geometry: %2,%3,%4x%5\n") |
|
.arg(i) |
|
.arg(geo.x()) |
|
.arg(geo.y()) |
|
.arg(geo.width()) |
|
.arg(geo.height())); |
|
} |
|
support.append("\nCompositing\n"); |
|
support.append( "===========\n"); |
|
support.append("Qt Graphics System: "); |
|
if (Extensions::nonNativePixmaps()) { |
|
support.append("raster\n"); |
|
} else { |
|
support.append("native\n"); |
|
} |
|
if (effects) { |
|
support.append("Compositing is active\n"); |
|
switch (effects->compositingType()) { |
|
case OpenGL1Compositing: |
|
case OpenGL2Compositing: |
|
case OpenGLCompositing: { |
|
#ifdef KWIN_HAVE_OPENGLES |
|
support.append("Compositing Type: OpenGL ES 2.0\n"); |
|
#else |
|
support.append("Compositing Type: OpenGL\n"); |
|
#endif |
|
|
|
GLPlatform *platform = GLPlatform::instance(); |
|
support.append("OpenGL vendor string: " % platform->glVendorString() % '\n'); |
|
support.append("OpenGL renderer string: " % platform->glRendererString() % '\n'); |
|
support.append("OpenGL version string: " % platform->glVersionString() % '\n'); |
|
|
|
if (platform->supports(LimitedGLSL)) |
|
support.append("OpenGL shading language version string: " % platform->glShadingLanguageVersionString() % '\n'); |
|
|
|
support.append("Driver: " % GLPlatform::driverToString(platform->driver()) % '\n'); |
|
if (!platform->isMesaDriver()) |
|
support.append("Driver version: " % GLPlatform::versionToString(platform->driverVersion()) % '\n'); |
|
|
|
support.append("GPU class: " % GLPlatform::chipClassToString(platform->chipClass()) % '\n'); |
|
|
|
support.append("OpenGL version: " % GLPlatform::versionToString(platform->glVersion()) % '\n'); |
|
|
|
if (platform->supports(LimitedGLSL)) |
|
support.append("GLSL version: " % GLPlatform::versionToString(platform->glslVersion()) % '\n'); |
|
|
|
if (platform->isMesaDriver()) |
|
support.append("Mesa version: " % GLPlatform::versionToString(platform->mesaVersion()) % '\n'); |
|
if (platform->serverVersion() > 0) |
|
support.append("X server version: " % GLPlatform::versionToString(platform->serverVersion()) % '\n'); |
|
if (platform->kernelVersion() > 0) |
|
support.append("Linux kernel version: " % GLPlatform::versionToString(platform->kernelVersion()) % '\n'); |
|
|
|
support.append("Direct rendering: "); |
|
if (platform->isDirectRendering()) { |
|
support.append("yes\n"); |
|
} else { |
|
support.append("no\n"); |
|
} |
|
support.append("Requires strict binding: "); |
|
if (!platform->isLooseBinding()) { |
|
support.append("yes\n"); |
|
} else { |
|
support.append("no\n"); |
|
} |
|
support.append("GLSL shaders: "); |
|
if (platform->supports(GLSL)) { |
|
if (platform->supports(LimitedGLSL)) { |
|
support.append(" limited\n"); |
|
} else { |
|
support.append(" yes\n"); |
|
} |
|
} else { |
|
support.append(" no\n"); |
|
} |
|
support.append("Texture NPOT support: "); |
|
if (platform->supports(TextureNPOT)) { |
|
if (platform->supports(LimitedNPOT)) { |
|
support.append(" limited\n"); |
|
} else { |
|
support.append(" yes\n"); |
|
} |
|
} else { |
|
support.append(" no\n"); |
|
} |
|
support.append("Virtual Machine: "); |
|
if (platform->isVirtualMachine()) { |
|
support.append(" yes\n"); |
|
} else { |
|
support.append(" no\n"); |
|
} |
|
|
|
if (effects->compositingType() == OpenGL2Compositing) { |
|
support.append("OpenGL 2 Shaders are used\n"); |
|
} else { |
|
support.append("OpenGL 2 Shaders are not used. Legacy OpenGL 1.x code path is used.\n"); |
|
} |
|
break; |
|
} |
|
case XRenderCompositing: |
|
support.append("Compositing Type: XRender\n"); |
|
break; |
|
case NoCompositing: |
|
default: |
|
support.append("Something is really broken, neither OpenGL nor XRender is used"); |
|
} |
|
support.append("\nLoaded Effects:\n"); |
|
support.append( "---------------\n"); |
|
foreach (const QString &effect, static_cast<EffectsHandlerImpl*>(effects)->loadedEffects()) { |
|
support.append(effect % '\n'); |
|
} |
|
support.append("\nCurrently Active Effects:\n"); |
|
support.append( "-------------------------\n"); |
|
foreach (const QString &effect, static_cast<EffectsHandlerImpl*>(effects)->activeEffects()) { |
|
support.append(effect % '\n'); |
|
} |
|
support.append("\nEffect Settings:\n"); |
|
support.append( "----------------\n"); |
|
foreach (const QString &effect, static_cast<EffectsHandlerImpl*>(effects)->loadedEffects()) { |
|
support.append(static_cast<EffectsHandlerImpl*>(effects)->supportInformation(effect)); |
|
support.append('\n'); |
|
} |
|
} else { |
|
support.append("Compositing is not active\n"); |
|
} |
|
return support; |
|
} |
|
|
|
void Workspace::slotToggleCompositing() |
|
{ |
|
if (m_compositor) { |
|
m_compositor->slotToggleCompositing(); |
|
} |
|
} |
|
|
|
} // namespace |
|
|
|
#include "workspace.moc"
|
|
|