kwin/workspace.cpp

1812 lines
64 KiB
C++
Raw Normal View History

/********************************************************************
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/>.
*********************************************************************/
// own
#include "workspace.h"
// kwin libs
#include <kwinglplatform.h>
// kwin
#ifdef KWIN_BUILD_ACTIVITIES
#include "activities.h"
#endif
#include "appmenu.h"
#include "atoms.h"
#include "client.h"
#include "composite.h"
#include "cursor.h"
#include "dbusinterface.h"
#include "deleted.h"
#include "effects.h"
#include "focuschain.h"
#include "group.h"
#include "input.h"
#include "logind.h"
#include "moving_client_x11_filter.h"
#include "killwindow.h"
#include "netinfo.h"
#include "outline.h"
#include "placement.h"
#include "rules.h"
#include "screenedge.h"
#include "screens.h"
#include "platform.h"
#include "scripting/scripting.h"
#ifdef KWIN_BUILD_TABBOX
#include "tabbox.h"
#endif
#include "unmanaged.h"
#include "useractions.h"
#include "virtualdesktops.h"
#include "shell_client.h"
#include "was_user_interaction_x11_filter.h"
#include "wayland_server.h"
#include "xcbutils.h"
#include "main.h"
#include "decorations/decorationbridge.h"
// KDE
#include <KConfig>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KStartupInfo>
// Qt
#include <QtConcurrentRun>
namespace KWin
{
extern int screen_number;
extern bool is_multihead;
ColorMapper::ColorMapper(QObject *parent)
: QObject(parent)
, m_default(defaultScreen()->default_colormap)
, m_installed(defaultScreen()->default_colormap)
{
}
ColorMapper::~ColorMapper()
{
}
void ColorMapper::update()
{
xcb_colormap_t cmap = m_default;
if (Client *c = dynamic_cast<Client*>(Workspace::self()->activeClient())) {
if (c->colormap() != XCB_COLORMAP_NONE) {
cmap = c->colormap();
}
}
if (cmap != m_installed) {
xcb_install_colormap(connection(), cmap);
m_installed = cmap;
}
}
Workspace* Workspace::_self = 0;
Workspace::Workspace(const QString &sessionKey)
2011-01-30 14:34:42 +00:00
: QObject(0)
, m_compositor(NULL)
// Unsorted
2011-01-30 14:34:42 +00:00
, active_popup(NULL)
, active_popup_client(NULL)
, m_initialDesktop(1)
2011-01-30 14:34:42 +00:00
, active_client(0)
, last_active_client(0)
, most_recently_raised(0)
, movingClient(0)
, delayfocus_client(0)
, force_restacking(false)
, showing_desktop(false)
, was_user_interaction(false)
, session_saving(false)
, block_focus(0)
, m_userActionsMenu(new UserActionsMenu(this))
2011-01-30 14:34:42 +00:00
, client_keys_dialog(NULL)
, client_keys_client(NULL)
, global_shortcuts_disabled_for_client(false)
, workspaceInit(true)
, startup(0)
, set_active_client_recursion(0)
, block_stacking_updates(0)
{
// If KWin was already running it saved its configuration after loosing the selection -> Reread
QFuture<void> reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration);
ApplicationMenu::create(this);
_self = this;
#ifdef KWIN_BUILD_ACTIVITIES
Activities *activities = nullptr;
if (kwinApp()->usesKActivities()) {
activities = Activities::create(this);
}
if (activities) {
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);
delayFocusTimer = 0;
if (!sessionKey.isEmpty())
loadSessionInfo(sessionKey);
connect(qApp, &QGuiApplication::commitDataRequest, this, &Workspace::commitData);
connect(qApp, &QGuiApplication::saveStateRequest, this, &Workspace::saveState);
RuleBook::create(this)->load();
ScreenEdges::create(this);
// 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);
//dbus interface
new VirtualDesktopManagerDBusInterface(VirtualDesktopManager::self());
#ifdef KWIN_BUILD_TABBOX
// need to create the tabbox before compositing scene is setup
TabBox::TabBox::create(this);
#endif
if (Compositor::self()) {
m_compositor = Compositor::self();
} else {
Q_ASSERT(kwinApp()->operationMode() == Application::OperationMode::OperationModeX11);
m_compositor = X11Compositor::create(this);
}
connect(this, &Workspace::currentDesktopChanged, m_compositor, &Compositor::addRepaintFull);
connect(m_compositor, &QObject::destroyed, this, [this] { m_compositor = nullptr; });
auto decorationBridge = Decoration::DecorationBridge::create(this);
decorationBridge->init();
connect(this, &Workspace::configChanged, decorationBridge, &Decoration::DecorationBridge::reconfigure);
new DBusInterface(this);
Outline::create(this);
initShortcuts();
init();
}
void Workspace::init()
2011-01-30 14:34:42 +00:00
{
KSharedConfigPtr config = kwinApp()->config();
kwinApp()->createScreens();
Screens *screens = Screens::self();
// get screen support
connect(screens, SIGNAL(changed()), SLOT(desktopResized()));
screens->setConfig(config);
screens->reconfigure();
connect(options, SIGNAL(configChanged()), screens, SLOT(reconfigure()));
ScreenEdges *screenEdges = ScreenEdges::self();
screenEdges->setConfig(config);
screenEdges->init();
connect(options, SIGNAL(configChanged()), screenEdges, SLOT(reconfigure()));
connect(VirtualDesktopManager::self(), SIGNAL(layoutChanged(int,int)), screenEdges, SLOT(updateLayout()));
connect(this, &Workspace::clientActivated, screenEdges, &ScreenEdges::checkBlocking);
FocusChain *focusChain = FocusChain::create(this);
connect(this, &Workspace::clientRemoved, focusChain, &FocusChain::remove);
connect(this, &Workspace::clientActivated, focusChain, &FocusChain::setActiveClient);
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());
// create VirtualDesktopManager and perform dependency injection
VirtualDesktopManager *vds = VirtualDesktopManager::self();
connect(vds, &VirtualDesktopManager::desktopRemoved, this,
[this](KWin::VirtualDesktop *desktop) {
//Wayland
if (kwinApp()->operationMode() == Application::OperationModeWaylandOnly ||
kwinApp()->operationMode() == Application::OperationModeXwayland) {
for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) {
if (!(*it)->desktops().contains(desktop)) {
continue;
}
if ((*it)->desktops().count() > 1) {
(*it)->leaveDesktop(desktop);
} else {
sendClientToDesktop(*it, qMin(desktop->x11DesktopNumber(), VirtualDesktopManager::self()->count()), true);
}
}
//X11
} else {
for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) {
if (!(*it)->isOnAllDesktops() && ((*it)->desktop() > static_cast<int>(VirtualDesktopManager::self()->count()))) {
sendClientToDesktop(*it, VirtualDesktopManager::self()->count(), true);
}
}
}
}
);
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->setConfig(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();
//makes sure any autogenerated id is saved, necessary as in case of xwayland, load will be called 2 times
// load is needed to be called again when starting xwayalnd to sync to RootInfo, see BUG 385260
vds->save();
if (!VirtualDesktopManager::self()->setCurrent(m_initialDesktop))
VirtualDesktopManager::self()->setCurrent(1);
2011-01-30 14:34:42 +00:00
reconfigureTimer.setSingleShot(true);
updateToolWindowsTimer.setSingleShot(true);
2011-01-30 14:34:42 +00:00
connect(&reconfigureTimer, SIGNAL(timeout()), this, SLOT(slotReconfigure()));
connect(&updateToolWindowsTimer, SIGNAL(timeout()), this, SLOT(slotUpdateToolWindows()));
// TODO: do we really need to reconfigure everything when fonts change?
// maybe just reconfigure the decorations? Move this into libkdecoration?
QDBusConnection::sessionBus().connect(QString(),
QStringLiteral("/KDEPlatformTheme"),
QStringLiteral("org.kde.KDEPlatformTheme"),
QStringLiteral("refreshFonts"),
this, SLOT(reconfigure()));
active_client = NULL;
initWithX11();
Scripting::create(this);
if (auto w = waylandServer()) {
connect(w, &WaylandServer::shellClientAdded, this,
[this] (ShellClient *c) {
setupClientConnections(c);
c->updateDecoration(false);
updateClientLayer(c);
if (!c->isInternal()) {
QRect area = clientArea(PlacementArea, Screens::self()->current(), c->desktop());
bool placementDone = false;
if (c->isInitialPositionSet()) {
placementDone = true;
}
if (c->isFullScreen()) {
placementDone = true;
}
if (c->maximizeMode() == MaximizeMode::MaximizeFull) {
placementDone = true;
}
if (c->rules()->checkPosition(invalidPoint, true) != invalidPoint) {
placementDone = true;
}
if (!placementDone) {
c->placeIn(area);
}
m_allClients.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
}
markXStackingOrderAsDirty();
updateStackingOrder(true);
updateClientArea();
if (c->wantsInput() && !c->isMinimized()) {
activateClient(c);
}
updateTabbox();
connect(c, &ShellClient::windowShown, this,
[this, c] {
updateClientLayer(c);
// TODO: when else should we send the client through placement?
if (c->hasTransientPlacementHint()) {
QRect area = clientArea(PlacementArea, Screens::self()->current(), c->desktop());
c->placeIn(area);
}
markXStackingOrderAsDirty();
updateStackingOrder(true);
updateClientArea();
if (c->wantsInput()) {
activateClient(c);
}
}
);
connect(c, &ShellClient::windowHidden, this,
[this] {
// TODO: update tabbox if it's displayed
markXStackingOrderAsDirty();
updateStackingOrder(true);
updateClientArea();
}
);
}
);
connect(w, &WaylandServer::shellClientRemoved, this,
[this] (ShellClient *c) {
m_allClients.removeAll(c);
if (c == most_recently_raised) {
most_recently_raised = nullptr;
}
if (c == delayfocus_client) {
cancelDelayFocus();
}
if (c == last_active_client) {
last_active_client = nullptr;
}
if (client_keys_client == c) {
setupWindowShortcutDone(false);
}
if (!c->shortcut().isEmpty()) {
c->setShortcut(QString()); // Remove from client_keys
}
clientHidden(c);
emit clientRemoved(c);
markXStackingOrderAsDirty();
updateStackingOrder(true);
updateClientArea();
updateTabbox();
}
);
}
// 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()
2011-01-30 14:34:42 +00:00
}
void Workspace::initWithX11()
{
if (!kwinApp()->x11Connection()) {
connect(kwinApp(), &Application::x11ConnectionChanged, this, &Workspace::initWithX11, Qt::UniqueConnection);
return;
}
disconnect(kwinApp(), &Application::x11ConnectionChanged, this, &Workspace::initWithX11);
atoms->retrieveHelpers();
// first initialize the extensions
Xcb::Extensions::self();
ColorMapper *colormaps = new ColorMapper(this);
connect(this, &Workspace::clientActivated, colormaps, &ColorMapper::update);
// Call this before XSelectInput() on the root window
startup = new KStartupInfo(
KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this);
// Select windowmanager privileges
selectWmInputEventMask();
// Compatibility
int32_t data = 1;
xcb_change_property(connection(), XCB_PROP_MODE_APPEND, rootWindow(), atoms->kwin_running,
atoms->kwin_running, 32, 1, &data);
if (kwinApp()->operationMode() == Application::OperationModeX11) {
m_wasUserInteractionFilter.reset(new WasUserInteractionX11Filter);
m_movingClientFilter.reset(new MovingClientX11Filter);
}
updateXTime(); // Needed for proper initialization of user_time in Client ctor
const uint32_t nullFocusValues[] = {true};
m_nullFocus.reset(new Xcb::Window(QRect(-1, -1, 1, 1), XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, nullFocusValues));
m_nullFocus->map();
RootInfo *rootInfo = RootInfo::create();
const auto vds = VirtualDesktopManager::self();
vds->setRootInfo(rootInfo);
// load again to sync to RootInfo, see BUG 385260
vds->load();
vds->updateRootInfo();
rootInfo->setCurrentDesktop(vds->currentDesktop()->x11DesktopNumber());
// TODO: only in X11 mode
// Extra NETRootInfo instance in Client mode is needed to get the values of the properties
NETRootInfo client_info(connection(), NET::ActiveWindow | NET::CurrentDesktop);
if (!qApp->isSessionRestored()) {
m_initialDesktop = client_info.currentDesktop();
vds->setCurrent(m_initialDesktop);
}
// TODO: better value
rootInfo->setActiveWindow(None);
focusToNull();
if (!qApp->isSessionRestored())
++block_focus; // Because it will be set below
{
// Begin updates blocker block
StackingUpdatesBlocker blocker(this);
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 (Application::wasCrash()) {
fixPositionAfterCrash(wins[i], windowGeometries.at(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 < screens()->count(); i++) {
geom |= screens()->geometry(i);
}
NETSize desktop_geometry;
desktop_geometry.width = geom.width();
desktop_geometry.height = geom.height();
rootInfo->setDesktopGeometry(desktop_geometry);
setShowingDesktop(false);
} // End updates blocker block
// TODO: only on X11?
AbstractClient* new_active_client = nullptr;
if (!qApp->isSessionRestored()) {
--block_focus;
new_active_client = findClient(Predicate::WindowMatch, 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);
}
Workspace::~Workspace()
2011-01-30 14:34:42 +00:00
{
blockStackingUpdates(true);
// TODO: grabXServer();
// Use stacking_order, so that kwin --replace keeps stacking order
const ToplevelList stack = stacking_order;
// "mutex" the stackingorder, since anything trying to access it from now on will find
// many dangeling pointers and crash
stacking_order.clear();
for (ToplevelList::const_iterator it = stack.constBegin(), end = stack.constEnd(); it != end; ++it) {
Client *c = qobject_cast<Client*>(const_cast<Toplevel*>(*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);
m_allClients.removeAll(c);
desktops.removeAll(c);
2011-01-30 14:34:42 +00:00
}
Client::cleanupX11();
if (waylandServer()) {
const QList<ShellClient *> shellClients = waylandServer()->clients();
for (ShellClient *shellClient : shellClients) {
shellClient->destroyClient();
}
const QList<ShellClient *> internalClients = waylandServer()->internalClients();
for (ShellClient *internalClient : internalClients) {
internalClient->destroyClient();
}
}
for (UnmanagedList::iterator it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it)
(*it)->release(ReleaseReason::KWinShutsDown);
if (auto c = kwinApp()->x11Connection()) {
xcb_delete_property(c, kwinApp()->x11RootWindow(), atoms->kwin_running);
}
for (auto it = deleted.begin(); it != deleted.end();) {
emit deletedRemoved(*it);
it = deleted.erase(it);
}
delete RuleBook::self();
kwinApp()->config()->sync();
RootInfo::destroy();
delete startup;
delete Placement::self();
delete client_keys_dialog;
2011-01-30 14:34:42 +00:00
foreach (SessionInfo * s, session)
delete s;
// TODO: ungrabXServer();
Xcb::Extensions::destroy();
_self = 0;
2011-01-30 14:34:42 +00:00
}
void Workspace::setupClientConnections(AbstractClient *c)
{
connect(c, &Toplevel::needsRepaint, m_compositor, &Compositor::scheduleRepaint);
connect(c, &AbstractClient::desktopPresenceChanged, this, &Workspace::desktopPresenceChanged);
connect(c, &AbstractClient::minimizedChanged, this, std::bind(&Workspace::clientMinimizedChanged, this, c));
}
2013-04-30 13:41:59 +00:00
Client* Workspace::createClient(xcb_window_t w, bool is_mapped)
2011-01-30 14:34:42 +00:00
{
StackingUpdatesBlocker blocker(this);
Client* c = new Client();
setupClientConnections(c);
[x11] Fix crash during tear down Summary: Any call made to a virtual method in constructor/destructor of a base class won't go to a derived class because the base class may access uninitialized or destroyed resources. For example, let's consider the following two classes class Base { public: Base() { foo()->bar(); } virtual ~Base() { foo()->bar(); } virtual Foo* foo() const { return nullptr; } }; class Derived : public Base { public: Derived() : mFoo(new Foo) {} ~Derived() override { delete mFoo; } Foo* foo() const override { return mFoo; } private: Foo* mFoo; }; When an instance of Derived class is created, constructors will run in the following order: Base() Derived() It's not safe to dispatch foo() method call to Derived class because constructor of Derived hasn't initialized yet mFoo. Same story with destructors, they'll run in the following order: ~Derived() ~Base() It's not safe to dispatch foo() method call in the destructor of Base class to Derived class because mFoo was deleted. So, what does that weird C++ behavior has something to do with KWin? Well, recently Compositor class was split into two classes - WaylandCompositor, and X11Compositor. Some functionality from X11 doesn't make sense on Wayland. Therefore methods that implement that stuff were "purified," i.e. they became pure virtual methods. Unfortunately, when Compositor tears down it may call pure virtual methods on itself. Given that those calls cannot be dispatched to X11Compositor or WaylandCompositor, the only choice that C++ runtime has is to throw an exception. The fix for this very delicate problem is very simple - do not call virtual methods from constructors and the destructor. Avoid doing that if you can! This change moves Compositor::updateClientCompositeBlocking to X11Compositor so it longer has to be a virtual method. Also, it kind of doesn't make sense to keep it in base Compositor class because compositing can be blocked only on X11. BUG: 411049 Test Plan: KWin no longer crashes when running kwin_x11 --replace command. Reviewers: #kwin, romangg Reviewed By: #kwin, romangg Subscribers: anthonyfieroni, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D23098
2019-08-30 16:28:50 +00:00
if (X11Compositor *compositor = X11Compositor::self()) {
connect(c, &Client::blockingCompositingChanged, compositor, &X11Compositor::updateClientCompositeBlocking);
}
connect(c, SIGNAL(clientFullScreenSet(KWin::Client*,bool,bool)), ScreenEdges::self(), SIGNAL(checkBlocking()));
if (!c->manage(w, is_mapped)) {
Client::deleteClient(c);
return NULL;
}
addClient(c);
return c;
2011-01-30 14:34:42 +00:00
}
2013-04-30 13:41:59 +00:00
Unmanaged* Workspace::createUnmanaged(xcb_window_t w)
2011-01-30 14:34:42 +00:00
{
if (X11Compositor *compositor = X11Compositor::self()) {
if (compositor->checkForOverlayWindow(w)) {
return nullptr;
}
}
Unmanaged* c = new Unmanaged();
2011-01-30 14:34:42 +00:00
if (!c->track(w)) {
Unmanaged::deleteUnmanaged(c);
return NULL;
}
connect(c, &Unmanaged::needsRepaint, m_compositor, &Compositor::scheduleRepaint);
addUnmanaged(c);
emit unmanagedAdded(c);
2011-01-30 14:34:42 +00:00
return c;
}
void Workspace::addClient(Client* c)
2011-01-30 14:34:42 +00:00
{
Group* grp = findGroup(c->window());
emit clientAdded(c);
2011-01-30 14:34:42 +00:00
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);
2011-01-30 14:34:42 +00:00
clients.append(c);
m_allClients.append(c);
2011-01-30 14:34:42 +00:00
}
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
markXStackingOrderAsDirty();
updateClientArea(); // This cannot be in manage(), because the client got added only now
2011-01-30 14:34:42 +00:00
updateClientLayer(c);
if (c->isDesktop()) {
raiseClient(c);
// If there's no active client, make this desktop the active one
2011-01-30 14:34:42 +00:00
if (activeClient() == NULL && should_get_focus.count() == 0)
activateClient(findDesktop(true, VirtualDesktopManager::self()->current()));
2011-01-30 14:34:42 +00:00
}
c->checkActiveModal();
2011-01-30 14:34:42 +00:00
checkTransients(c->window()); // SELI TODO: Does this really belong here?
updateStackingOrder(true); // Propagate new client
if (c->isUtility() || c->isMenu() || c->isToolbar())
updateToolWindows(true);
updateTabbox();
2011-01-30 14:34:42 +00:00
}
void Workspace::addUnmanaged(Unmanaged* c)
2011-01-30 14:34:42 +00:00
{
unmanaged.append(c);
markXStackingOrderAsDirty();
2011-01-30 14:34:42 +00:00
}
/**
* Destroys the client \a c
*/
void Workspace::removeClient(Client* c)
2011-01-30 14:34:42 +00:00
{
if (c == active_popup_client)
closeActivePopup();
if (m_userActionsMenu->isMenuClient(c)) {
m_userActionsMenu->close();
}
c->untab(QRect(), true);
2011-01-30 14:34:42 +00:00
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
}
2011-01-30 14:34:42 +00:00
Q_ASSERT(clients.contains(c) || desktops.contains(c));
// TODO: if marked client is removed, notify the marked list
2011-01-30 14:34:42 +00:00
clients.removeAll(c);
m_allClients.removeAll(c);
2011-01-30 14:34:42 +00:00
desktops.removeAll(c);
markXStackingOrderAsDirty();
2011-01-30 14:34:42 +00:00
attention_chain.removeAll(c);
Group* group = findGroup(c->window());
if (group != NULL)
group->lostLeader();
2011-01-30 14:34:42 +00:00
if (c == most_recently_raised)
most_recently_raised = 0;
2011-01-30 14:34:42 +00:00
should_get_focus.removeAll(c);
Q_ASSERT(c != active_client);
if (c == last_active_client)
last_active_client = 0;
2011-01-30 14:34:42 +00:00
if (c == delayfocus_client)
cancelDelayFocus();
emit clientRemoved(c);
2011-01-30 14:34:42 +00:00
updateStackingOrder(true);
updateClientArea();
updateTabbox();
2011-01-30 14:34:42 +00:00
}
void Workspace::removeUnmanaged(Unmanaged* c)
2011-01-30 14:34:42 +00:00
{
Q_ASSERT(unmanaged.contains(c));
2011-01-30 14:34:42 +00:00
unmanaged.removeAll(c);
emit unmanagedRemoved(c);
markXStackingOrderAsDirty();
2011-01-30 14:34:42 +00:00
}
void Workspace::addDeleted(Deleted* c, Toplevel *orig)
2011-01-30 14:34:42 +00:00
{
Q_ASSERT(!deleted.contains(c));
2011-01-30 14:34:42 +00:00
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);
}
markXStackingOrderAsDirty();
connect(c, &Deleted::needsRepaint, m_compositor, &Compositor::scheduleRepaint);
2011-01-30 14:34:42 +00:00
}
void Workspace::removeDeleted(Deleted* c)
2011-01-30 14:34:42 +00:00
{
Q_ASSERT(deleted.contains(c));
emit deletedRemoved(c);
2011-01-30 14:34:42 +00:00
deleted.removeAll(c);
unconstrained_stacking_order.removeAll(c);
stacking_order.removeAll(c);
markXStackingOrderAsDirty();
[x11] Fix crash during tear down Summary: Any call made to a virtual method in constructor/destructor of a base class won't go to a derived class because the base class may access uninitialized or destroyed resources. For example, let's consider the following two classes class Base { public: Base() { foo()->bar(); } virtual ~Base() { foo()->bar(); } virtual Foo* foo() const { return nullptr; } }; class Derived : public Base { public: Derived() : mFoo(new Foo) {} ~Derived() override { delete mFoo; } Foo* foo() const override { return mFoo; } private: Foo* mFoo; }; When an instance of Derived class is created, constructors will run in the following order: Base() Derived() It's not safe to dispatch foo() method call to Derived class because constructor of Derived hasn't initialized yet mFoo. Same story with destructors, they'll run in the following order: ~Derived() ~Base() It's not safe to dispatch foo() method call in the destructor of Base class to Derived class because mFoo was deleted. So, what does that weird C++ behavior has something to do with KWin? Well, recently Compositor class was split into two classes - WaylandCompositor, and X11Compositor. Some functionality from X11 doesn't make sense on Wayland. Therefore methods that implement that stuff were "purified," i.e. they became pure virtual methods. Unfortunately, when Compositor tears down it may call pure virtual methods on itself. Given that those calls cannot be dispatched to X11Compositor or WaylandCompositor, the only choice that C++ runtime has is to throw an exception. The fix for this very delicate problem is very simple - do not call virtual methods from constructors and the destructor. Avoid doing that if you can! This change moves Compositor::updateClientCompositeBlocking to X11Compositor so it longer has to be a virtual method. Also, it kind of doesn't make sense to keep it in base Compositor class because compositing can be blocked only on X11. BUG: 411049 Test Plan: KWin no longer crashes when running kwin_x11 --replace command. Reviewers: #kwin, romangg Reviewed By: #kwin, romangg Subscribers: anthonyfieroni, kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D23098
2019-08-30 16:28:50 +00:00
if (!c->wasClient()) {
return;
}
if (X11Compositor *compositor = X11Compositor::self()) {
compositor->updateClientCompositeBlocking();
}
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
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;
2011-01-30 14:34:42 +00:00
}
const Group* group = nullptr;
auto 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 != nullptr) {
2011-01-30 14:34:42 +00:00
if (!client->isTransient())
break;
2011-01-30 14:34:42 +00:00
if (client->groupTransient()) {
group = client->group();
break;
}
client = client->transientFor();
2011-01-30 14:34:42 +00:00
}
// 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?
QVector<AbstractClient*> to_show, to_hide;
for (ToplevelList::ConstIterator it = stacking_order.constBegin();
2011-01-30 14:34:42 +00:00
it != stacking_order.constEnd();
++it) {
auto c = qobject_cast<AbstractClient*>(*it);
if (!c) {
continue;
}
if (c->isUtility() || c->isMenu() || c->isToolbar()) {
bool show = true;
if (!c->isTransient()) {
if (!c->group() || 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;
2011-01-30 14:34:42 +00:00
} else {
if (group != NULL && c->group() == group)
show = true;
else if (client != NULL && client->hasTransient(c, true))
show = true;
else
show = false;
2011-01-30 14:34:42 +00:00
}
if (!show && also_hide) {
const auto mainclients = c->mainClients();
// Don't hide utility windows which are standalone(?) or
// have e.g. kicker as mainwindow
2011-01-30 14:34:42 +00:00
if (mainclients.isEmpty())
show = true;
for (auto it2 = mainclients.constBegin();
2011-01-30 14:34:42 +00:00
it2 != mainclients.constEnd();
++it2) {
if ((*it2)->isSpecialWindow())
show = true;
}
2011-01-30 14:34:42 +00:00
if (!show)
to_hide.append(c);
}
2011-01-30 14:34:42 +00:00
if (show)
to_show.append(c);
2011-01-30 14:34:42 +00:00
}
} // 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 :(
2011-01-30 14:34:42 +00:00
to_show.at(i)->hideClient(false);
if (also_hide) {
for (auto it = to_hide.constBegin();
2011-01-30 14:34:42 +00:00
it != to_hide.constEnd();
++it) // From bottommost
(*it)->hideClient(true);
updateToolWindowsTimer.stop();
2011-01-30 14:34:42 +00:00
} else // setActiveClient() is after called with NULL client, quickly followed
// by setting a new client, which would result in flickering
resetUpdateToolWindowsTimer();
2011-01-30 14:34:42 +00:00
}
void Workspace::resetUpdateToolWindowsTimer()
2011-01-30 14:34:42 +00:00
{
updateToolWindowsTimer.start(200);
}
void Workspace::slotUpdateToolWindows()
2011-01-30 14:34:42 +00:00
{
updateToolWindows(true);
}
void Workspace::slotReloadConfig()
2011-01-30 14:34:42 +00:00
{
reconfigure();
2011-01-30 14:34:42 +00:00
}
void Workspace::reconfigure()
2011-01-30 14:34:42 +00:00
{
reconfigureTimer.start(200);
}
/**
* Reread settings
*/
void Workspace::slotReconfigure()
2011-01-30 14:34:42 +00:00
{
qCDebug(KWIN_CORE) << "Workspace::slotReconfigure()";
reconfigureTimer.stop();
bool borderlessMaximizedWindows = options->borderlessMaximizedWindows();
kwinApp()->config()->reparseConfiguration();
options->updateSettings();
emit configChanged();
m_userActionsMenu->discard();
2011-01-30 14:34:42 +00:00
updateToolWindows(true);
RuleBook::self()->load();
for (auto it = m_allClients.begin();
it != m_allClients.end();
2011-01-30 14:34:42 +00:00
++it) {
(*it)->setupWindowRules(true);
(*it)->applyWindowRules();
RuleBook::self()->discardUsed(*it, false);
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
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 (auto it = m_allClients.begin();
it != m_allClients.end();
2011-01-30 14:34:42 +00:00
++it) {
if ((*it)->maximizeMode() == MaximizeFull)
(*it)->checkNoBorder();
}
2011-01-30 14:34:42 +00:00
}
}
void Workspace::slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop)
2011-01-30 14:34:42 +00:00
{
closeActivePopup();
++block_focus;
2011-01-30 14:34:42 +00:00
StackingUpdatesBlocker blocker(this);
updateClientVisibilityOnDesktopChange(newDesktop);
// Restore the focus on this desktop
--block_focus;
activateClientOnNewDesktop(newDesktop);
emit currentDesktopChanged(oldDesktop, movingClient);
}
void Workspace::updateClientVisibilityOnDesktopChange(uint newDesktop)
{
for (ToplevelList::ConstIterator it = stacking_order.constBegin();
it != stacking_order.constEnd();
++it) {
Client *c = qobject_cast<Client*>(*it);
if (!c) {
continue;
2011-01-30 14:34:42 +00:00
}
if (!c->isOnDesktop(newDesktop) && c != movingClient && c->isOnCurrentActivity()) {
(c)->updateVisibility();
}
2011-01-30 14:34:42 +00:00
}
// Now propagate the change, after hiding, before showing
if (rootInfo()) {
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));
2011-01-30 14:34:42 +00:00
if (!c) {
continue;
}
if (c->isOnDesktop(newDesktop) && c->isOnCurrentActivity())
c->updateVisibility();
}
if (showingDesktop()) // Do this only after desktop change to avoid flicker
setShowingDesktop(false);
}
void Workspace::activateClientOnNewDesktop(uint desktop)
{
AbstractClient* c = NULL;
if (options->focusPolicyIsReasonable()) {
c = findClientToActivateOnDesktop(desktop);
2011-01-30 14:34:42 +00:00
}
// 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>)
2011-01-30 14:34:42 +00:00
else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop())
c = active_client;
2011-01-30 14:34:42 +00:00
if (c == NULL && !desktops.isEmpty())
c = findDesktop(true, desktop);
2011-01-30 14:34:42 +00:00
if (c != active_client)
setActiveClient(NULL);
2011-01-30 14:34:42 +00:00
if (c)
requestFocus(c);
else if (!desktops.isEmpty())
requestFocus(findDesktop(true, desktop));
else
focusToNull();
}
AbstractClient *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->isOnActiveScreen()))
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);
2011-01-30 14:34:42 +00:00
}
/**
* 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)
2011-01-30 14:34:42 +00:00
{
#ifdef KWIN_BUILD_ACTIVITIES
if (!Activities::self()) {
return;
}
//closeActivePopup();
++block_focus;
// TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date
2011-01-30 14:34:42 +00:00
StackingUpdatesBlocker blocker(this);
// 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));
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()) {
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();
2011-01-30 14:34:42 +00:00
}
//FIXME not sure if I should do this either
if (showingDesktop()) // Do this only after desktop change to avoid flicker
setShowingDesktop(false);
// Restore the focus on this desktop
--block_focus;
AbstractClient* c = 0;
//FIXME below here is a lot of focuschain stuff, probably all wrong now
2011-01-30 14:34:42 +00:00
if (options->focusPolicyIsReasonable()) {
// Search in focus chain
c = FocusChain::self()->getForActivation(VirtualDesktopManager::self()->current());
2011-01-30 14:34:42 +00:00
}
// 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>)
2011-01-30 14:34:42 +00:00
else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop() && active_client->isOnCurrentActivity())
c = active_client;
2011-01-30 14:34:42 +00:00
if (c == NULL && !desktops.isEmpty())
c = findDesktop(true, VirtualDesktopManager::self()->current());
2011-01-30 14:34:42 +00:00
if (c != active_client)
setActiveClient(NULL);
2011-01-30 14:34:42 +00:00
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
2011-01-30 14:34:42 +00:00
//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
2011-01-30 14:34:42 +00:00
}
void Workspace::slotDesktopCountChanged(uint previousCount, uint newCount)
2011-01-30 14:34:42 +00:00
{
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);
2011-01-30 14:34:42 +00:00
}
void Workspace::selectWmInputEventMask()
{
uint32_t presentMask = 0;
Xcb::WindowAttributes attr(rootWindow());
if (!attr.isNull()) {
presentMask = attr->your_event_mask;
}
Xcb::selectInput(rootWindow(),
presentMask |
XCB_EVENT_MASK_KEY_PRESS |
XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_COLOR_MAP_CHANGE |
XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
XCB_EVENT_MASK_FOCUS_CHANGE | // For NotifyDetailNone
XCB_EVENT_MASK_EXPOSURE
);
}
/**
* Sends client \a c to desktop \a desk.
*
* Takes care of transients as well.
*/
void Workspace::sendClientToDesktop(AbstractClient* c, int desk, bool dont_activate)
2011-01-30 14:34:42 +00:00
{
if ((desk < 1 && desk != NET::OnAllDesktops) || desk > static_cast<int>(VirtualDesktopManager::self()->count()))
return;
int old_desktop = c->desktop();
2011-01-30 14:34:42 +00:00
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
2011-01-30 14:34:42 +00:00
if (c->isOnDesktop(VirtualDesktopManager::self()->current())) {
2011-01-30 14:34:42 +00:00
if (c->wantsTabFocus() && options->focusPolicyIsReasonable() &&
!was_on_desktop && // for stickyness changes
!dont_activate)
requestFocus(c);
else
2011-01-30 14:34:42 +00:00
restackClientUnderActive(c);
} else
raiseClient(c);
c->checkWorkspacePosition( QRect(), old_desktop );
auto transients_stacking_order = ensureStackingOrder(c->transients());
for (auto it = transients_stacking_order.constBegin();
it != transients_stacking_order.constEnd();
++it)
sendClientToDesktop(*it, desk, dont_activate);
updateClientArea();
2011-01-30 14:34:42 +00:00
}
/**
* 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;
}
void Workspace::sendClientToScreen(AbstractClient* c, int screen)
2011-01-30 14:34:42 +00:00
{
c->sendToScreen(screen);
2011-01-30 14:34:42 +00:00
}
2013-04-30 13:41:59 +00:00
void Workspace::sendPingToWindow(xcb_window_t window, xcb_timestamp_t timestamp)
2011-01-30 14:34:42 +00:00
{
if (rootInfo()) {
rootInfo()->sendPing(window, timestamp);
}
2011-01-30 14:34:42 +00:00
}
/**
* Delayed focus functions
*/
void Workspace::delayFocus()
2011-01-30 14:34:42 +00:00
{
requestFocus(delayfocus_client);
cancelDelayFocus();
2011-01-30 14:34:42 +00:00
}
void Workspace::requestDelayFocus(AbstractClient* c)
2011-01-30 14:34:42 +00:00
{
delayfocus_client = c;
delete delayFocusTimer;
2011-01-30 14:34:42 +00:00
delayFocusTimer = new QTimer(this);
connect(delayFocusTimer, SIGNAL(timeout()), this, SLOT(delayFocus()));
delayFocusTimer->setSingleShot(true);
delayFocusTimer->start(options->delayFocusInterval());
2011-01-30 14:34:42 +00:00
}
void Workspace::cancelDelayFocus()
2011-01-30 14:34:42 +00:00
{
delete delayFocusTimer;
delayFocusTimer = 0;
2011-01-30 14:34:42 +00:00
}
2013-04-30 13:41:59 +00:00
bool Workspace::checkStartupNotification(xcb_window_t w, KStartupInfoId &id, KStartupInfoData &data)
2011-01-30 14:34:42 +00:00
{
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()
2011-01-30 14:34:42 +00:00
{
if (m_nullFocus) {
m_nullFocus->focus();
}
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
void Workspace::setShowingDesktop(bool showing)
{
const bool changed = showing != showing_desktop;
if (rootInfo() && changed) {
rootInfo()->setShowingDesktop(showing);
}
showing_desktop = showing;
AbstractClient *topDesk = nullptr;
{ // for the blocker RAII
StackingUpdatesBlocker blocker(this); // updateLayer & lowerClient would invalidate stacking_order
for (int i = stacking_order.count() - 1; i > -1; --i) {
AbstractClient *c = qobject_cast<AbstractClient*>(stacking_order.at(i));
if (c && c->isOnCurrentDesktop()) {
if (c->isDock()) {
c->updateLayer();
} else if (c->isDesktop() && c->isShown(true)) {
c->updateLayer();
lowerClient(c);
if (!topDesk)
topDesk = c;
if (auto group = c->group()) {
foreach (Client *cm, group->members()) {
cm->updateLayer();
}
}
}
}
2011-01-30 14:34:42 +00:00
}
} // ~StackingUpdatesBlocker
if (showing_desktop && topDesk) {
requestFocus(topDesk);
} else if (!showing_desktop && changed) {
const auto client = FocusChain::self()->getForActivation(VirtualDesktopManager::self()->current());
if (client) {
activateClient(client);
}
}
if (changed)
emit showingDesktopChanged(showing);
2011-01-30 14:34:42 +00:00
}
2011-01-30 14:34:42 +00:00
void Workspace::disableGlobalShortcutsForClient(bool disable)
{
if (global_shortcuts_disabled_for_client == disable)
return;
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kglobalaccel"),
QStringLiteral("/kglobalaccel"),
QStringLiteral("org.kde.KGlobalAccel"),
QStringLiteral("blockGlobalShortcuts"));
message.setArguments(QList<QVariant>() << disable);
QDBusConnection::sessionBus().asyncCall(message);
global_shortcuts_disabled_for_client = disable;
// Update also Alt+LMB actions etc.
2011-01-30 14:34:42 +00:00
for (ClientList::ConstIterator it = clients.constBegin();
it != clients.constEnd();
++it)
(*it)->updateMouseGrab();
2011-01-30 14:34:42 +00:00
}
QString Workspace::supportInformation() const
{
QString support;
const QString yes = QStringLiteral("yes\n");
const QString no = QStringLiteral("no\n");
support.append(ki18nc("Introductory text shown in the support information.",
"KWin Support Information:\n"
2018-12-11 06:14:55 +00:00
"The following information should be used when requesting support on e.g. https://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 https://paste.kde.org instead of pasting into support threads.\n").toString());
support.append(QStringLiteral("\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(QStringLiteral("Version\n"));
support.append(QStringLiteral("=======\n"));
support.append(QStringLiteral("KWin version: "));
support.append(QStringLiteral(KWIN_VERSION_STRING));
support.append(QStringLiteral("\n"));
support.append(QStringLiteral("Qt Version: "));
support.append(QString::fromUtf8(qVersion()));
support.append(QStringLiteral("\n"));
support.append(QStringLiteral("Qt compile version: %1\n").arg(QStringLiteral(QT_VERSION_STR)));
support.append(QStringLiteral("XCB compile version: %1\n\n").arg(QStringLiteral(XCB_VERSION_STRING)));
support.append(QStringLiteral("Operation Mode: "));
switch (kwinApp()->operationMode()) {
case Application::OperationModeX11:
support.append(QStringLiteral("X11 only"));
break;
case Application::OperationModeWaylandOnly:
support.append(QStringLiteral("Wayland Only"));
break;
case Application::OperationModeXwayland:
support.append(QStringLiteral("Xwayland"));
break;
}
support.append(QStringLiteral("\n\n"));
support.append(QStringLiteral("Build Options\n"));
support.append(QStringLiteral("=============\n"));
support.append(QStringLiteral("KWIN_BUILD_DECORATIONS: "));
#ifdef KWIN_BUILD_DECORATIONS
support.append(yes);
#else
support.append(no);
#endif
support.append(QStringLiteral("KWIN_BUILD_TABBOX: "));
#ifdef KWIN_BUILD_TABBOX
support.append(yes);
#else
support.append(no);
#endif
support.append(QStringLiteral("KWIN_BUILD_ACTIVITIES: "));
#ifdef KWIN_BUILD_ACTIVITIES
support.append(yes);
#else
support.append(no);
#endif
support.append(QStringLiteral("HAVE_DRM: "));
#if HAVE_DRM
support.append(yes);
#else
support.append(no);
#endif
support.append(QStringLiteral("HAVE_GBM: "));
#if HAVE_GBM
support.append(yes);
#else
support.append(no);
[platforms/drm] EGLStream DRM Backend Initial Implementation Summary: This is the initial implementation of a DRM backend based on the EGLDevice, EGLOutput, and EGLStream extensions, supporting NVIDIA graphics hardware using their proprietary driver. The new backend will be used if the environment variable KWIN_DRM_USE_EGL_STREAMS is set. On initialization, it will attempt to create an EGLDevice based on the DRM device currently in use and create EGLOutputs and EGLStreams for any attached displays. These are used to control presentation of the final composited frame. Additionally, it will register the wl_eglstream_controller Wayland interface so that native EGL windows created by clients can be attached to an EGLStream allowing buffer contents to be shared with the compositor as a GL texture. At this time there are two known bugs in the NVIDIA driver's EGL implementation affecting desktop functionality. The first can result in tooltip windows drawn by plasmashell to contain incorrect contents. The second prevents KWayland from being able to query the format of EGLStream-backed buffers which interferes with the blur effect. Fixes for both of these are currently in development and should appear in an upcoming NVIDIA driver release. Additionally, hardware cursors are currently not supported with this backend. Enabling them causes the desktop to intermittently hang for several seconds. This is also likely a bug in the NVIDIA DRM-KMS implementation but the root cause is still under investigation. Test Plan: On a system with an NVIDIA graphics card running a recent release of their proprietary driver * Ensure the nvidia_drm kernel module is loaded with the option "modeset=1" ("# cat /sys/module/nvidia_drm/parameters/modeset" should print "Y") * Ensure EGL external platform support is installed https://github.com/NVIDIA/eglexternalplatform * Ensure KWin was build with the CMake option KWIN_BUILD_EGL_STREAM_BACKEND=ON (this is the default) * Start a plasma wayland session with the environment variable KWIN_DRM_USE_EGL_STREAMS set * Ensure output from KWin OpenGL initialization indicates the NVIDIA EGL driver is in use (as opposed to Mesa / llvmpipe). * Desktop should be fully functional and perform smoothly. Reviewers: #kwin, romangg, davidedmundson Reviewed By: #kwin, romangg, davidedmundson Subscribers: kwin Tags: #kwin Differential Revision: https://phabricator.kde.org/D18570
2019-04-15 14:26:22 +00:00
#endif
support.append(QStringLiteral("HAVE_EGL_STREAMS: "));
#if HAVE_EGL_STREAMS
support.append(yes);
#else
support.append(no);
#endif
support.append(QStringLiteral("HAVE_X11_XCB: "));
#if HAVE_X11_XCB
support.append(yes);
#else
support.append(no);
#endif
support.append(QStringLiteral("HAVE_EPOXY_GLX: "));
#if HAVE_EPOXY_GLX
support.append(yes);
#else
support.append(no);
#endif
support.append(QStringLiteral("HAVE_WAYLAND_EGL: "));
#if HAVE_WAYLAND_EGL
support.append(yes);
#else
support.append(no);
#endif
support.append(QStringLiteral("\n"));
if (auto c = kwinApp()->x11Connection()) {
support.append(QStringLiteral("X11\n"));
support.append(QStringLiteral("===\n"));
auto x11setup = xcb_get_setup(c);
support.append(QStringLiteral("Vendor: %1\n").arg(QString::fromUtf8(QByteArray::fromRawData(xcb_setup_vendor(x11setup), xcb_setup_vendor_length(x11setup)))));
support.append(QStringLiteral("Vendor Release: %1\n").arg(x11setup->release_number));
support.append(QStringLiteral("Protocol Version/Revision: %1/%2\n").arg(x11setup->protocol_major_version).arg(x11setup->protocol_minor_version));
const auto extensions = Xcb::Extensions::self()->extensions();
for (const auto &e : extensions) {
support.append(QStringLiteral("%1: %2; Version: 0x%3\n").arg(QString::fromUtf8(e.name))
.arg(e.present ? yes.trimmed() : no.trimmed())
.arg(QString::number(e.version, 16)));
}
support.append(QStringLiteral("\n"));
}
if (auto bridge = Decoration::DecorationBridge::self()) {
support.append(QStringLiteral("Decoration\n"));
support.append(QStringLiteral("==========\n"));
support.append(bridge->supportInformation());
support.append(QStringLiteral("\n"));
}
support.append(QStringLiteral("Platform\n"));
support.append(QStringLiteral("==========\n"));
support.append(kwinApp()->platform()->supportInformation());
support.append(QStringLiteral("\n"));
support.append(QStringLiteral("Options\n"));
support.append(QStringLiteral("=======\n"));
const QMetaObject *metaOptions = options->metaObject();
auto printProperty = [] (const QVariant &variant) {
if (variant.type() == QVariant::Size) {
const QSize &s = variant.toSize();
return QStringLiteral("%1x%2").arg(QString::number(s.width())).arg(QString::number(s.height()));
}
if (QLatin1String(variant.typeName()) == QLatin1String("KWin::OpenGLPlatformInterface") ||
QLatin1String(variant.typeName()) == QLatin1String("KWin::Options::WindowOperation")) {
return QString::number(variant.toInt());
}
return variant.toString();
};
for (int i=0; i<metaOptions->propertyCount(); ++i) {
const QMetaProperty property = metaOptions->property(i);
if (QLatin1String(property.name()) == QLatin1String("objectName")) {
continue;
}
support.append(QStringLiteral("%1: %2\n").arg(property.name()).arg(printProperty(options->property(property.name()))));
}
support.append(QStringLiteral("\nScreen Edges\n"));
support.append(QStringLiteral( "============\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()) == QLatin1String("objectName")) {
continue;
}
support.append(QStringLiteral("%1: %2\n").arg(property.name()).arg(printProperty(ScreenEdges::self()->property(property.name()))));
}
support.append(QStringLiteral("\nScreens\n"));
support.append(QStringLiteral( "=======\n"));
support.append(QStringLiteral("Multi-Head: "));
if (is_multihead) {
support.append(QStringLiteral("yes\n"));
support.append(QStringLiteral("Head: %1\n").arg(screen_number));
} else {
support.append(QStringLiteral("no\n"));
}
support.append(QStringLiteral("Active screen follows mouse: "));
if (screens()->isCurrentFollowsMouse())
support.append(QStringLiteral(" yes\n"));
else
support.append(QStringLiteral(" no\n"));
support.append(QStringLiteral("Number of Screens: %1\n\n").arg(screens()->count()));
for (int i=0; i<screens()->count(); ++i) {
const QRect geo = screens()->geometry(i);
support.append(QStringLiteral("Screen %1:\n").arg(i));
support.append(QStringLiteral("---------\n"));
support.append(QStringLiteral("Name: %1\n").arg(screens()->name(i)));
support.append(QStringLiteral("Geometry: %1,%2,%3x%4\n")
.arg(geo.x())
.arg(geo.y())
.arg(geo.width())
.arg(geo.height()));
support.append(QStringLiteral("Scale: %1\n").arg(screens()->scale(i)));
support.append(QStringLiteral("Refresh Rate: %1\n\n").arg(screens()->refreshRate(i)));
}
support.append(QStringLiteral("\nCompositing\n"));
support.append(QStringLiteral( "===========\n"));
if (effects) {
support.append(QStringLiteral("Compositing is active\n"));
switch (effects->compositingType()) {
case OpenGL2Compositing:
case OpenGLCompositing: {
GLPlatform *platform = GLPlatform::instance();
if (platform->isGLES()) {
support.append(QStringLiteral("Compositing Type: OpenGL ES 2.0\n"));
} else {
support.append(QStringLiteral("Compositing Type: OpenGL\n"));
}
support.append(QStringLiteral("OpenGL vendor string: ") + QString::fromUtf8(platform->glVendorString()) + QStringLiteral("\n"));
support.append(QStringLiteral("OpenGL renderer string: ") + QString::fromUtf8(platform->glRendererString()) + QStringLiteral("\n"));
support.append(QStringLiteral("OpenGL version string: ") + QString::fromUtf8(platform->glVersionString()) + QStringLiteral("\n"));
support.append(QStringLiteral("OpenGL platform interface: "));
switch (platform->platformInterface()) {
case GlxPlatformInterface:
support.append(QStringLiteral("GLX"));
break;
case EglPlatformInterface:
support.append(QStringLiteral("EGL"));
break;
default:
support.append(QStringLiteral("UNKNOWN"));
}
support.append(QStringLiteral("\n"));
if (platform->supports(LimitedGLSL) || platform->supports(GLSL))
support.append(QStringLiteral("OpenGL shading language version string: ") + QString::fromUtf8(platform->glShadingLanguageVersionString()) + QStringLiteral("\n"));
support.append(QStringLiteral("Driver: ") + GLPlatform::driverToString(platform->driver()) + QStringLiteral("\n"));
if (!platform->isMesaDriver())
support.append(QStringLiteral("Driver version: ") + GLPlatform::versionToString(platform->driverVersion()) + QStringLiteral("\n"));
support.append(QStringLiteral("GPU class: ") + GLPlatform::chipClassToString(platform->chipClass()) + QStringLiteral("\n"));
support.append(QStringLiteral("OpenGL version: ") + GLPlatform::versionToString(platform->glVersion()) + QStringLiteral("\n"));
if (platform->supports(LimitedGLSL) || platform->supports(GLSL))
support.append(QStringLiteral("GLSL version: ") + GLPlatform::versionToString(platform->glslVersion()) + QStringLiteral("\n"));
if (platform->isMesaDriver())
support.append(QStringLiteral("Mesa version: ") + GLPlatform::versionToString(platform->mesaVersion()) + QStringLiteral("\n"));
if (platform->serverVersion() > 0)
support.append(QStringLiteral("X server version: ") + GLPlatform::versionToString(platform->serverVersion()) + QStringLiteral("\n"));
if (platform->kernelVersion() > 0)
support.append(QStringLiteral("Linux kernel version: ") + GLPlatform::versionToString(platform->kernelVersion()) + QStringLiteral("\n"));
support.append(QStringLiteral("Direct rendering: "));
support.append(QStringLiteral("Requires strict binding: "));
if (!platform->isLooseBinding()) {
support.append(QStringLiteral("yes\n"));
} else {
support.append(QStringLiteral("no\n"));
}
support.append(QStringLiteral("GLSL shaders: "));
if (platform->supports(GLSL)) {
if (platform->supports(LimitedGLSL)) {
support.append(QStringLiteral(" limited\n"));
} else {
support.append(QStringLiteral(" yes\n"));
}
} else {
support.append(QStringLiteral(" no\n"));
}
support.append(QStringLiteral("Texture NPOT support: "));
if (platform->supports(TextureNPOT)) {
if (platform->supports(LimitedNPOT)) {
support.append(QStringLiteral(" limited\n"));
} else {
support.append(QStringLiteral(" yes\n"));
}
} else {
support.append(QStringLiteral(" no\n"));
}
support.append(QStringLiteral("Virtual Machine: "));
if (platform->isVirtualMachine()) {
support.append(QStringLiteral(" yes\n"));
} else {
support.append(QStringLiteral(" no\n"));
}
support.append(QStringLiteral("OpenGL 2 Shaders are used\n"));
support.append(QStringLiteral("Painting blocks for vertical retrace: "));
if (m_compositor->scene()->blocksForRetrace())
support.append(QStringLiteral(" yes\n"));
else
support.append(QStringLiteral(" no\n"));
break;
}
case XRenderCompositing:
support.append(QStringLiteral("Compositing Type: XRender\n"));
break;
case QPainterCompositing:
support.append("Compositing Type: QPainter\n");
break;
case NoCompositing:
default:
support.append(QStringLiteral("Something is really broken, neither OpenGL nor XRender is used"));
}
support.append(QStringLiteral("\nLoaded Effects:\n"));
support.append(QStringLiteral( "---------------\n"));
foreach (const QString &effect, static_cast<EffectsHandlerImpl*>(effects)->loadedEffects()) {
support.append(effect + QStringLiteral("\n"));
}
support.append(QStringLiteral("\nCurrently Active Effects:\n"));
support.append(QStringLiteral( "-------------------------\n"));
foreach (const QString &effect, static_cast<EffectsHandlerImpl*>(effects)->activeEffects()) {
support.append(effect + QStringLiteral("\n"));
}
support.append(QStringLiteral("\nEffect Settings:\n"));
support.append(QStringLiteral( "----------------\n"));
foreach (const QString &effect, static_cast<EffectsHandlerImpl*>(effects)->loadedEffects()) {
support.append(static_cast<EffectsHandlerImpl*>(effects)->supportInformation(effect));
support.append(QStringLiteral("\n"));
}
} else {
support.append(QStringLiteral("Compositing is not active\n"));
}
return support;
}
Client *Workspace::findClient(std::function<bool (const Client*)> func) const
{
if (Client *ret = Toplevel::findInList(clients, func)) {
return ret;
}
if (Client *ret = Toplevel::findInList(desktops, func)) {
return ret;
}
return nullptr;
}
AbstractClient *Workspace::findAbstractClient(std::function<bool (const AbstractClient*)> func) const
{
if (AbstractClient *ret = Toplevel::findInList(m_allClients, func)) {
return ret;
}
if (Client *ret = Toplevel::findInList(desktops, func)) {
return ret;
}
if (waylandServer()) {
if (AbstractClient *ret = Toplevel::findInList(waylandServer()->internalClients(), func)) {
return ret;
}
}
return nullptr;
}
Unmanaged *Workspace::findUnmanaged(std::function<bool (const Unmanaged*)> func) const
{
return Toplevel::findInList(unmanaged, func);
}
Unmanaged *Workspace::findUnmanaged(xcb_window_t w) const
{
return findUnmanaged([w](const Unmanaged *u) {
return u->window() == w;
});
}
Client *Workspace::findClient(Predicate predicate, xcb_window_t w) const
{
switch (predicate) {
case Predicate::WindowMatch:
return findClient([w](const Client *c) {
return c->window() == w;
});
case Predicate::WrapperIdMatch:
return findClient([w](const Client *c) {
return c->wrapperId() == w;
});
case Predicate::FrameIdMatch:
return findClient([w](const Client *c) {
return c->frameId() == w;
});
case Predicate::InputIdMatch:
return findClient([w](const Client *c) {
return c->inputId() == w;
});
}
return nullptr;
}
Toplevel *Workspace::findToplevel(std::function<bool (const Toplevel*)> func) const
{
if (Client *ret = Toplevel::findInList(clients, func)) {
return ret;
}
if (Client *ret = Toplevel::findInList(desktops, func)) {
return ret;
}
if (Unmanaged *ret = Toplevel::findInList(unmanaged, func)) {
return ret;
}
return nullptr;
}
Toplevel *Workspace::findToplevel(QWindow *w) const
{
if (!w) {
return nullptr;
}
if (waylandServer()) {
if (auto c = waylandServer()->findClient(w)) {
return c;
}
}
return findUnmanaged(w->winId());
}
bool Workspace::hasClient(const AbstractClient *c)
{
if (auto cc = dynamic_cast<const Client*>(c)) {
return hasClient(cc);
} else {
return findAbstractClient([c](const AbstractClient *test) {
return test == c;
}) != nullptr;
}
return false;
}
2015-12-17 14:49:32 +00:00
void Workspace::forEachAbstractClient(std::function< void (AbstractClient*) > func)
{
std::for_each(m_allClients.constBegin(), m_allClients.constEnd(), func);
std::for_each(desktops.constBegin(), desktops.constEnd(), func);
}
Toplevel *Workspace::findInternal(QWindow *w) const
{
if (!w) {
return nullptr;
}
if (kwinApp()->operationMode() == Application::OperationModeX11) {
return findUnmanaged(w->winId());
} else {
return waylandServer()->findClient(w);
}
}
bool Workspace::compositing() const
{
return m_compositor && m_compositor->scene();
}
void Workspace::markXStackingOrderAsDirty()
{
m_xStackingDirty = true;
if (kwinApp()->x11Connection()) {
m_xStackingQueryTree.reset(new Xcb::Tree(kwinApp()->x11RootWindow()));
}
}
void Workspace::setWasUserInteraction()
{
if (was_user_interaction) {
return;
}
was_user_interaction = true;
// might be called from within the filter, so delay till we now the filter returned
QTimer::singleShot(0, this,
[this] {
m_wasUserInteractionFilter.reset();
}
);
}
void Workspace::updateTabbox()
{
#ifdef KWIN_BUILD_TABBOX
TabBox::TabBox *tabBox = TabBox::TabBox::self();
if (tabBox && tabBox->isDisplayed()) {
tabBox->reset(true);
}
#endif
}
} // namespace