diff --git a/activities.cpp b/activities.cpp index af8007f6d..03e4e8321 100644 --- a/activities.cpp +++ b/activities.cpp @@ -1,218 +1,222 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin 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 . *********************************************************************/ #include "activities.h" // KWin #include "x11client.h" #include "workspace.h" // KDE #include #include // Qt #include #include #include #include namespace KWin { KWIN_SINGLETON_FACTORY(Activities) Activities::Activities(QObject *parent) : QObject(parent) , m_controller(new KActivities::Controller(this)) { connect(m_controller, &KActivities::Controller::activityRemoved, this, &Activities::slotRemoved); connect(m_controller, &KActivities::Controller::activityRemoved, this, &Activities::removed); connect(m_controller, &KActivities::Controller::activityAdded, this, &Activities::added); connect(m_controller, &KActivities::Controller::currentActivityChanged, this, &Activities::slotCurrentChanged); } Activities::~Activities() { s_self = nullptr; } KActivities::Consumer::ServiceStatus Activities::serviceStatus() const { return m_controller->serviceStatus(); } void Activities::setCurrent(const QString &activity) { m_controller->setCurrentActivity(activity); } void Activities::slotCurrentChanged(const QString &newActivity) { if (m_current == newActivity) { return; } m_previous = m_current; m_current = newActivity; emit currentChanged(newActivity); } void Activities::slotRemoved(const QString &activity) { foreach (X11Client *client, Workspace::self()->clientList()) { + if (client->isDesktop()) + continue; client->setOnActivity(activity, false); } //toss out any session data for it KConfigGroup cg(KSharedConfig::openConfig(), QByteArray("SubSession: ").append(activity.toUtf8()).constData()); cg.deleteGroup(); } void Activities::toggleClientOnActivity(X11Client *c, const QString &activity, bool dont_activate) { //int old_desktop = c->desktop(); bool was_on_activity = c->isOnActivity(activity); bool was_on_all = c->isOnAllActivities(); //note: all activities === no activities bool enable = was_on_all || !was_on_activity; c->setOnActivity(activity, enable); if (c->isOnActivity(activity) == was_on_activity && c->isOnAllActivities() == was_on_all) // No change return; Workspace *ws = Workspace::self(); if (c->isOnCurrentActivity()) { if (c->wantsTabFocus() && options->focusPolicyIsReasonable() && !was_on_activity && // for stickyness changes //FIXME not sure if the line above refers to the correct activity !dont_activate) ws->requestFocus(c); else ws->restackClientUnderActive(c); } else ws->raiseClient(c); //notifyWindowDesktopChanged( c, old_desktop ); auto transients_stacking_order = ws->ensureStackingOrder(c->transients()); for (auto it = transients_stacking_order.constBegin(); it != transients_stacking_order.constEnd(); ++it) { X11Client *c = dynamic_cast(*it); if (!c) { continue; } toggleClientOnActivity(c, activity, dont_activate); } ws->updateClientArea(); } bool Activities::start(const QString &id) { Workspace *ws = Workspace::self(); if (ws->sessionManager()->state() == SessionState::Saving) { return false; //ksmserver doesn't queue requests (yet) } if (!all().contains(id)) { return false; //bogus id } ws->loadSubSessionInfo(id); QDBusInterface ksmserver("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface"); if (ksmserver.isValid()) { ksmserver.asyncCall("restoreSubSession", id); } else { qCDebug(KWIN_CORE) << "couldn't get ksmserver interface"; return false; } return true; } bool Activities::stop(const QString &id) { if (Workspace::self()->sessionManager()->state() == SessionState::Saving) { return false; //ksmserver doesn't queue requests (yet) //FIXME what about session *loading*? } //ugly hack to avoid dbus deadlocks QMetaObject::invokeMethod(this, "reallyStop", Qt::QueuedConnection, Q_ARG(QString, id)); //then lie and assume it worked. return true; } void Activities::reallyStop(const QString &id) { Workspace *ws = Workspace::self(); if (ws->sessionManager()->state() == SessionState::Saving) return; //ksmserver doesn't queue requests (yet) qCDebug(KWIN_CORE) << id; QSet saveSessionIds; QSet dontCloseSessionIds; const QList &clients = ws->clientList(); for (auto it = clients.constBegin(); it != clients.constEnd(); ++it) { const X11Client *c = (*it); + if (c->isDesktop()) + continue; const QByteArray sessionId = c->sessionId(); if (sessionId.isEmpty()) { continue; //TODO support old wm_command apps too? } //qDebug() << sessionId; //if it's on the activity that's closing, it needs saving //but if a process is on some other open activity, I don't wanna close it yet //this is, of course, complicated by a process having many windows. if (c->isOnAllActivities()) { dontCloseSessionIds << sessionId; continue; } const QStringList activities = c->activities(); foreach (const QString & activityId, activities) { if (activityId == id) { saveSessionIds << sessionId; } else if (running().contains(activityId)) { dontCloseSessionIds << sessionId; } } } ws->storeSubSession(id, saveSessionIds); QStringList saveAndClose; QStringList saveOnly; foreach (const QByteArray & sessionId, saveSessionIds) { if (dontCloseSessionIds.contains(sessionId)) { saveOnly << sessionId; } else { saveAndClose << sessionId; } } qCDebug(KWIN_CORE) << "saveActivity" << id << saveAndClose << saveOnly; //pass off to ksmserver QDBusInterface ksmserver("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface"); if (ksmserver.isValid()) { ksmserver.asyncCall("saveSubSession", id, saveAndClose, saveOnly); } else { qCDebug(KWIN_CORE) << "couldn't get ksmserver interface"; } } } // namespace diff --git a/composite.cpp b/composite.cpp index ee0b17414..acd26e760 100644 --- a/composite.cpp +++ b/composite.cpp @@ -1,1067 +1,1055 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2006 Lubos Lunak 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 . *********************************************************************/ #include "composite.h" #include "dbusinterface.h" #include "x11client.h" #include "decorations/decoratedclient.h" #include "deleted.h" #include "effects.h" #include "internal_client.h" #include "overlaywindow.h" #include "platform.h" #include "scene.h" #include "screens.h" #include "shadow.h" #include "unmanaged.h" #include "useractions.h" #include "utils.h" #include "wayland_server.h" #include "workspace.h" #include "xcbutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KWin::X11Compositor::SuspendReason) namespace KWin { // See main.cpp: extern int screen_number; extern bool is_multihead; extern int currentRefreshRate(); Compositor *Compositor::s_compositor = nullptr; Compositor *Compositor::self() { return s_compositor; } WaylandCompositor *WaylandCompositor::create(QObject *parent) { Q_ASSERT(!s_compositor); auto *compositor = new WaylandCompositor(parent); s_compositor = compositor; return compositor; } X11Compositor *X11Compositor::create(QObject *parent) { Q_ASSERT(!s_compositor); auto *compositor = new X11Compositor(parent); s_compositor = compositor; return compositor; } class CompositorSelectionOwner : public KSelectionOwner { Q_OBJECT public: CompositorSelectionOwner(const char *selection) : KSelectionOwner(selection, connection(), rootWindow()) , m_owning(false) { connect (this, &CompositorSelectionOwner::lostOwnership, this, [this]() { m_owning = false; }); } bool owning() const { return m_owning; } void setOwning(bool own) { m_owning = own; } private: bool m_owning; }; static inline qint64 milliToNano(int milli) { return qint64(milli) * 1000 * 1000; } static inline qint64 nanoToMilli(int nano) { return nano / (1000*1000); } Compositor::Compositor(QObject* workspace) : QObject(workspace) , m_state(State::Off) , m_selectionOwner(nullptr) , vBlankInterval(0) , fpsInterval(0) , m_timeSinceLastVBlank(0) , m_scene(nullptr) , m_bufferSwapPending(false) , m_composeAtSwapCompletion(false) { connect(options, &Options::configChanged, this, &Compositor::configChanged); connect(options, &Options::animationSpeedChanged, this, &Compositor::configChanged); m_monotonicClock.start(); // 2 sec which should be enough to restart the compositor. static const int compositorLostMessageDelay = 2000; m_releaseSelectionTimer.setSingleShot(true); m_releaseSelectionTimer.setInterval(compositorLostMessageDelay); connect(&m_releaseSelectionTimer, &QTimer::timeout, this, &Compositor::releaseCompositorSelection); m_unusedSupportPropertyTimer.setInterval(compositorLostMessageDelay); m_unusedSupportPropertyTimer.setSingleShot(true); connect(&m_unusedSupportPropertyTimer, &QTimer::timeout, this, &Compositor::deleteUnusedSupportProperties); // Delay the call to start by one event cycle. // The ctor of this class is invoked from the Workspace ctor, that means before // Workspace is completely constructed, so calling Workspace::self() would result // in undefined behavior. This is fixed by using a delayed invocation. if (kwinApp()->platform()->isReady()) { QTimer::singleShot(0, this, &Compositor::start); } connect(kwinApp()->platform(), &Platform::readyChanged, this, [this] (bool ready) { if (ready) { start(); } else { stop(); } }, Qt::QueuedConnection ); if (qEnvironmentVariableIsSet("KWIN_MAX_FRAMES_TESTED")) m_framesToTestForSafety = qEnvironmentVariableIntValue("KWIN_MAX_FRAMES_TESTED"); // register DBus new CompositorDBusInterface(this); } Compositor::~Compositor() { emit aboutToDestroy(); stop(); deleteUnusedSupportProperties(); destroyCompositorSelection(); s_compositor = nullptr; } bool Compositor::setupStart() { if (kwinApp()->isTerminating()) { // Don't start while KWin is terminating. An event to restart might be lingering // in the event queue due to graphics reset. return false; } if (m_state != State::Off) { return false; } m_state = State::Starting; options->reloadCompositingSettings(true); setupX11Support(); // There might still be a deleted around, needs to be cleared before // creating the scene (BUG 333275). if (Workspace::self()) { while (!Workspace::self()->deletedList().isEmpty()) { Workspace::self()->deletedList().first()->discard(); } } emit aboutToToggleCompositing(); auto supportedCompositors = kwinApp()->platform()->supportedCompositors(); const auto userConfigIt = std::find(supportedCompositors.begin(), supportedCompositors.end(), options->compositingMode()); if (userConfigIt != supportedCompositors.end()) { supportedCompositors.erase(userConfigIt); supportedCompositors.prepend(options->compositingMode()); } else { qCWarning(KWIN_CORE) << "Configured compositor not supported by Platform. Falling back to defaults"; } const auto availablePlugins = KPluginLoader::findPlugins(QStringLiteral("org.kde.kwin.scenes")); for (const KPluginMetaData &pluginMetaData : availablePlugins) { qCDebug(KWIN_CORE) << "Available scene plugin:" << pluginMetaData.fileName(); } for (auto type : qAsConst(supportedCompositors)) { switch (type) { case XRenderCompositing: qCDebug(KWIN_CORE) << "Attempting to load the XRender scene"; break; case OpenGLCompositing: case OpenGL2Compositing: qCDebug(KWIN_CORE) << "Attempting to load the OpenGL scene"; break; case QPainterCompositing: qCDebug(KWIN_CORE) << "Attempting to load the QPainter scene"; break; case NoCompositing: Q_UNREACHABLE(); } const auto pluginIt = std::find_if(availablePlugins.begin(), availablePlugins.end(), [type] (const auto &plugin) { const auto &metaData = plugin.rawData(); auto it = metaData.find(QStringLiteral("CompositingType")); if (it != metaData.end()) { if ((*it).toInt() == int{type}) { return true; } } return false; }); if (pluginIt != availablePlugins.end()) { std::unique_ptr factory{ qobject_cast(pluginIt->instantiate()) }; if (factory) { m_scene = factory->create(this); if (m_scene) { if (!m_scene->initFailed()) { qCDebug(KWIN_CORE) << "Instantiated compositing plugin:" << pluginIt->name(); break; } else { delete m_scene; m_scene = nullptr; } } } } } if (m_scene == nullptr || m_scene->initFailed()) { qCCritical(KWIN_CORE) << "Failed to initialize compositing, compositing disabled"; m_state = State::Off; delete m_scene; m_scene = nullptr; if (m_selectionOwner) { m_selectionOwner->setOwning(false); m_selectionOwner->release(); } if (!supportedCompositors.contains(NoCompositing)) { qCCritical(KWIN_CORE) << "The used windowing system requires compositing"; qCCritical(KWIN_CORE) << "We are going to quit KWin now as it is broken"; qApp->quit(); } return false; } CompositingType compositingType = m_scene->compositingType(); if (compositingType & OpenGLCompositing) { // Override for OpenGl sub-type OpenGL2Compositing. compositingType = OpenGLCompositing; } kwinApp()->platform()->setSelectedCompositor(compositingType); if (!Workspace::self() && m_scene && m_scene->compositingType() == QPainterCompositing) { // Force Software QtQuick on first startup with QPainter. QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software); } connect(m_scene, &Scene::resetCompositing, this, &Compositor::reinitialize); emit sceneCreated(); return true; } void Compositor::claimCompositorSelection() { if (!m_selectionOwner) { char selection_name[ 100 ]; sprintf(selection_name, "_NET_WM_CM_S%d", Application::x11ScreenNumber()); m_selectionOwner = new CompositorSelectionOwner(selection_name); connect(m_selectionOwner, &CompositorSelectionOwner::lostOwnership, this, &Compositor::stop); } if (!m_selectionOwner) { // No X11 yet. return; } if (!m_selectionOwner->owning()) { // Force claim ownership. m_selectionOwner->claim(true); m_selectionOwner->setOwning(true); } } void Compositor::setupX11Support() { auto *con = kwinApp()->x11Connection(); if (!con) { delete m_selectionOwner; m_selectionOwner = nullptr; return; } claimCompositorSelection(); xcb_composite_redirect_subwindows(con, kwinApp()->x11RootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } void Compositor::startupWithWorkspace() { connect(kwinApp(), &Application::x11ConnectionChanged, this, &Compositor::setupX11Support, Qt::UniqueConnection); Workspace::self()->markXStackingOrderAsDirty(); Q_ASSERT(m_scene); connect(workspace(), &Workspace::destroyed, this, [this] { compositeTimer.stop(); }); setupX11Support(); fpsInterval = options->maxFpsInterval(); if (m_scene->syncsToVBlank()) { // If we do vsync, set the fps to the next multiple of the vblank rate. vBlankInterval = milliToNano(1000) / currentRefreshRate(); fpsInterval = qMax((fpsInterval / vBlankInterval) * vBlankInterval, vBlankInterval); } else { // No vsync - DO NOT set "0", would cause div-by-zero segfaults. vBlankInterval = milliToNano(1); } // Sets also the 'effects' pointer. kwinApp()->platform()->createEffectsHandler(this, m_scene); connect(Workspace::self(), &Workspace::deletedRemoved, m_scene, &Scene::removeToplevel); connect(effects, &EffectsHandler::screenGeometryChanged, this, &Compositor::addRepaintFull); for (X11Client *c : Workspace::self()->clientList()) { c->setupCompositing(); c->updateShadow(); } - for (X11Client *c : Workspace::self()->desktopList()) { - c->setupCompositing(); - } for (Unmanaged *c : Workspace::self()->unmanagedList()) { c->setupCompositing(); c->updateShadow(); } for (InternalClient *client : workspace()->internalClients()) { client->setupCompositing(); client->updateShadow(); } if (auto *server = waylandServer()) { const auto clients = server->clients(); for (AbstractClient *c : clients) { c->setupCompositing(); c->updateShadow(); } } m_state = State::On; emit compositingToggled(true); if (m_releaseSelectionTimer.isActive()) { m_releaseSelectionTimer.stop(); } // Render at least once. addRepaintFull(); performCompositing(); } void Compositor::scheduleRepaint() { if (!compositeTimer.isActive()) setCompositeTimer(); } void Compositor::stop() { if (m_state == State::Off || m_state == State::Stopping) { return; } m_state = State::Stopping; emit aboutToToggleCompositing(); m_releaseSelectionTimer.start(); // Some effects might need access to effect windows when they are about to // be destroyed, for example to unreference deleted windows, so we have to // make sure that effect windows outlive effects. delete effects; effects = nullptr; if (Workspace::self()) { for (X11Client *c : Workspace::self()->clientList()) { m_scene->removeToplevel(c); } - for (X11Client *c : Workspace::self()->desktopList()) { - m_scene->removeToplevel(c); - } for (Unmanaged *c : Workspace::self()->unmanagedList()) { m_scene->removeToplevel(c); } for (InternalClient *client : workspace()->internalClients()) { m_scene->removeToplevel(client); } for (X11Client *c : Workspace::self()->clientList()) { c->finishCompositing(); } - for (X11Client *c : Workspace::self()->desktopList()) { - c->finishCompositing(); - } for (Unmanaged *c : Workspace::self()->unmanagedList()) { c->finishCompositing(); } for (InternalClient *client : workspace()->internalClients()) { client->finishCompositing(); } if (auto *con = kwinApp()->x11Connection()) { xcb_composite_unredirect_subwindows(con, kwinApp()->x11RootWindow(), XCB_COMPOSITE_REDIRECT_MANUAL); } while (!workspace()->deletedList().isEmpty()) { workspace()->deletedList().first()->discard(); } } if (waylandServer()) { for (AbstractClient *c : waylandServer()->clients()) { m_scene->removeToplevel(c); } for (AbstractClient *c : waylandServer()->clients()) { c->finishCompositing(); } } delete m_scene; m_scene = nullptr; compositeTimer.stop(); repaints_region = QRegion(); m_state = State::Off; emit compositingToggled(false); } void Compositor::destroyCompositorSelection() { delete m_selectionOwner; m_selectionOwner = nullptr; } void Compositor::releaseCompositorSelection() { switch (m_state) { case State::On: // We are compositing at the moment. Don't release. break; case State::Off: if (m_selectionOwner) { qCDebug(KWIN_CORE) << "Releasing compositor selection"; m_selectionOwner->setOwning(false); m_selectionOwner->release(); } break; case State::Starting: case State::Stopping: // Still starting or shutting down the compositor. Starting might fail // or after stopping a restart might follow. So test again later on. m_releaseSelectionTimer.start(); break; } } void Compositor::keepSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties.removeAll(atom); } void Compositor::removeSupportProperty(xcb_atom_t atom) { m_unusedSupportProperties << atom; m_unusedSupportPropertyTimer.start(); } void Compositor::deleteUnusedSupportProperties() { if (m_state == State::Starting || m_state == State::Stopping) { // Currently still maybe restarting the compositor. m_unusedSupportPropertyTimer.start(); return; } if (auto *con = kwinApp()->x11Connection()) { for (const xcb_atom_t &atom : qAsConst(m_unusedSupportProperties)) { // remove property from root window xcb_delete_property(con, kwinApp()->x11RootWindow(), atom); } m_unusedSupportProperties.clear(); } } void Compositor::configChanged() { reinitialize(); addRepaintFull(); } void Compositor::reinitialize() { // Reparse config. Config options will be reloaded by start() kwinApp()->config()->reparseConfiguration(); // Restart compositing stop(); start(); if (effects) { // start() may fail effects->reconfigure(); } } void Compositor::addRepaint(int x, int y, int w, int h) { if (m_state != State::On) { return; } repaints_region += QRegion(x, y, w, h); scheduleRepaint(); } void Compositor::addRepaint(const QRect& r) { if (m_state != State::On) { return; } repaints_region += r; scheduleRepaint(); } void Compositor::addRepaint(const QRegion& r) { if (m_state != State::On) { return; } repaints_region += r; scheduleRepaint(); } void Compositor::addRepaintFull() { if (m_state != State::On) { return; } const QSize &s = screens()->size(); repaints_region = QRegion(0, 0, s.width(), s.height()); scheduleRepaint(); } void Compositor::timerEvent(QTimerEvent *te) { if (te->timerId() == compositeTimer.timerId()) { performCompositing(); } else QObject::timerEvent(te); } void Compositor::aboutToSwapBuffers() { Q_ASSERT(!m_bufferSwapPending); m_bufferSwapPending = true; } void Compositor::bufferSwapComplete() { Q_ASSERT(m_bufferSwapPending); m_bufferSwapPending = false; emit bufferSwapCompleted(); if (m_composeAtSwapCompletion) { m_composeAtSwapCompletion = false; performCompositing(); } } void Compositor::performCompositing() { // If a buffer swap is still pending, we return to the event loop and // continue processing events until the swap has completed. if (m_bufferSwapPending) { m_composeAtSwapCompletion = true; compositeTimer.stop(); return; } // If outputs are disabled, we return to the event loop and // continue processing events until the outputs are enabled again if (!kwinApp()->platform()->areOutputsEnabled()) { compositeTimer.stop(); return; } // Create a list of all windows in the stacking order QList windows = Workspace::self()->xStackingOrder(); QList damaged; // Reset the damage state of each window and fetch the damage region // without waiting for a reply for (Toplevel *win : windows) { if (win->resetAndFetchDamage()) { damaged << win; } } if (damaged.count() > 0) { m_scene->triggerFence(); if (auto c = kwinApp()->x11Connection()) { xcb_flush(c); } } // Move elevated windows to the top of the stacking order for (EffectWindow *c : static_cast(effects)->elevatedWindows()) { Toplevel *t = static_cast(c)->window(); windows.removeAll(t); windows.append(t); } // Get the replies for (Toplevel *win : damaged) { // Discard the cached lanczos texture if (win->effectWindow()) { const QVariant texture = win->effectWindow()->data(LanczosCacheRole); if (texture.isValid()) { delete static_cast(texture.value()); win->effectWindow()->setData(LanczosCacheRole, QVariant()); } } win->getDamageRegionReply(); } if (repaints_region.isEmpty() && !windowRepaintsPending()) { m_scene->idle(); m_timeSinceLastVBlank = fpsInterval - (options->vBlankTime() + 1); // means "start now" // Note: It would seem here we should undo suspended unredirect, but when scenes need // it for some reason, e.g. transformations or translucency, the next pass that does not // need this anymore and paints normally will also reset the suspended unredirect. // Otherwise the window would not be painted normally anyway. compositeTimer.stop(); return; } // Skip windows that are not yet ready for being painted and if screen is locked skip windows // that are neither lockscreen nor inputmethod windows. // // TODO? This cannot be used so carelessly - needs protections against broken clients, the // window should not get focus before it's displayed, handle unredirected windows properly and // so on. for (Toplevel *win : windows) { if (!win->readyForPainting()) { windows.removeAll(win); } if (waylandServer() && waylandServer()->isScreenLocked()) { if(!win->isLockScreen() && !win->isInputMethod()) { windows.removeAll(win); } } } QRegion repaints = repaints_region; // clear all repaints, so that post-pass can add repaints for the next repaint repaints_region = QRegion(); if (m_framesToTestForSafety > 0 && (m_scene->compositingType() & OpenGLCompositing)) { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PreFrame); } m_timeSinceLastVBlank = m_scene->paint(repaints, windows); if (m_framesToTestForSafety > 0) { if (m_scene->compositingType() & OpenGLCompositing) { kwinApp()->platform()->createOpenGLSafePoint(Platform::OpenGLSafePoint::PostFrame); } m_framesToTestForSafety--; if (m_framesToTestForSafety == 0 && (m_scene->compositingType() & OpenGLCompositing)) { kwinApp()->platform()->createOpenGLSafePoint( Platform::OpenGLSafePoint::PostLastGuardedFrame); } } if (waylandServer()) { const auto currentTime = static_cast(m_monotonicClock.elapsed()); for (Toplevel *win : qAsConst(windows)) { if (auto surface = win->surface()) { surface->frameRendered(currentTime); } } } // Stop here to ensure *we* cause the next repaint schedule - not some effect // through m_scene->paint(). compositeTimer.stop(); // Trigger at least one more pass even if there would be nothing to paint, so that scene->idle() // is called the next time. If there would be nothing pending, it will not restart the timer and // scheduleRepaint() would restart it again somewhen later, called from functions that // would again add something pending. if (m_bufferSwapPending && m_scene->syncsToVBlank()) { m_composeAtSwapCompletion = true; } else { scheduleRepaint(); } } template static bool repaintsPending(const QList &windows) { return std::any_of(windows.begin(), windows.end(), [](T *t) { return !t->repaints().isEmpty(); }); } bool Compositor::windowRepaintsPending() const { if (repaintsPending(Workspace::self()->clientList())) { return true; } - if (repaintsPending(Workspace::self()->desktopList())) { - return true; - } if (repaintsPending(Workspace::self()->unmanagedList())) { return true; } if (repaintsPending(Workspace::self()->deletedList())) { return true; } if (auto *server = waylandServer()) { const auto &clients = server->clients(); auto test = [](AbstractClient *c) { return c->readyForPainting() && !c->repaints().isEmpty(); }; if (std::any_of(clients.begin(), clients.end(), test)) { return true; } } const auto &internalClients = workspace()->internalClients(); auto internalTest = [] (InternalClient *client) { return client->isShown(true) && !client->repaints().isEmpty(); }; if (std::any_of(internalClients.begin(), internalClients.end(), internalTest)) { return true; } return false; } void Compositor::setCompositeTimer() { if (m_state != State::On) { return; } // Don't start the timer if we're waiting for a swap event if (m_bufferSwapPending && m_composeAtSwapCompletion) return; // Don't start the timer if all outputs are disabled if (!kwinApp()->platform()->areOutputsEnabled()) { return; } uint waitTime = 1; if (m_scene->blocksForRetrace()) { // TODO: make vBlankTime dynamic?! // It's required because glXWaitVideoSync will *likely* block a full frame if one enters // a retrace pass which can last a variable amount of time, depending on the actual screen // Now, my ooold 19" CRT can do such retrace so that 2ms are entirely sufficient, // while another ooold 15" TFT requires about 6ms qint64 padding = m_timeSinceLastVBlank; if (padding > fpsInterval) { // We're at low repaints or spent more time in painting than the user wanted to wait // for that frame. Align to next vblank: padding = vBlankInterval - (padding % vBlankInterval); } else { // Align to the next maxFps tick: // "remaining time of the first vsync" + "time for the other vsyncs of the frame" padding = ((vBlankInterval - padding % vBlankInterval) + (fpsInterval / vBlankInterval - 1) * vBlankInterval); } if (padding < options->vBlankTime()) { // We'll likely miss this frame so we add one: waitTime = nanoToMilli(padding + vBlankInterval - options->vBlankTime()); } else { waitTime = nanoToMilli(padding - options->vBlankTime()); } } else { // w/o blocking vsync we just jump to the next demanded tick if (fpsInterval > m_timeSinceLastVBlank) { waitTime = nanoToMilli(fpsInterval - m_timeSinceLastVBlank); if (!waitTime) { // Will ensure we don't block out the eventloop - the system's just not faster ... waitTime = 1; } } /* else if (m_scene->syncsToVBlank() && m_timeSinceLastVBlank - fpsInterval < (vBlankInterval<<1)) { // NOTICE - "for later" ------------------------------------------------------------------ // It can happen that we push two frames within one refresh cycle. // Swapping will then block even with triple buffering when the GPU does not discard but // queues frames // now here's the mean part: if we take that as "OMG, we're late - next frame ASAP", // there'll immediately be 2 frames in the pipe, swapping will block, we think we're // late ... ewww // so instead we pad to the clock again and add 2ms safety to ensure the pipe is really // free // NOTICE: obviously m_timeSinceLastVBlank can be too big because we're too slow as well // So if this code was enabled, we'd needlessly half the framerate once more (15 instead of 30) waitTime = nanoToMilli(vBlankInterval - (m_timeSinceLastVBlank - fpsInterval)%vBlankInterval) + 2; }*/ else { // "0" would be sufficient here, but the compositor isn't the WMs only task. waitTime = 1; } } // Force 4fps minimum: compositeTimer.start(qMin(waitTime, 250u), this); } bool Compositor::isActive() { return m_state == State::On; } WaylandCompositor::WaylandCompositor(QObject *parent) : Compositor(parent) { connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, &WaylandCompositor::destroyCompositorSelection); } void WaylandCompositor::toggleCompositing() { // For the shortcut. Not possible on Wayland because we always composite. } void WaylandCompositor::start() { if (!Compositor::setupStart()) { // Internal setup failed, abort. return; } if (Workspace::self()) { startupWithWorkspace(); } else { connect(kwinApp(), &Application::workspaceCreated, this, &WaylandCompositor::startupWithWorkspace); } } int WaylandCompositor::refreshRate() const { // TODO: This makes no sense on Wayland. First step would be to atleast // set the refresh rate to the highest available one. Second step // would be to not use a uniform value at all but per screen. return KWin::currentRefreshRate(); } X11Compositor::X11Compositor(QObject *parent) : Compositor(parent) , m_suspended(options->isUseCompositing() ? NoReasonSuspend : UserSuspend) , m_xrrRefreshRate(0) { } void X11Compositor::toggleCompositing() { if (m_suspended) { // Direct user call; clear all bits. resume(AllReasonSuspend); } else { // But only set the user one (sufficient to suspend). suspend(UserSuspend); } } void X11Compositor::reinitialize() { // Resume compositing if suspended. m_suspended = NoReasonSuspend; Compositor::reinitialize(); } void X11Compositor::configChanged() { if (m_suspended) { stop(); return; } Compositor::configChanged(); } void X11Compositor::suspend(X11Compositor::SuspendReason reason) { Q_ASSERT(reason != NoReasonSuspend); m_suspended |= reason; if (reason & ScriptSuspend) { // When disabled show a shortcut how the user can get back compositing. const auto shortcuts = KGlobalAccel::self()->shortcut( workspace()->findChild(QStringLiteral("Suspend Compositing"))); if (!shortcuts.isEmpty()) { // Display notification only if there is the shortcut. const QString message = i18n("Desktop effects have been suspended by another application.
" "You can resume using the '%1' shortcut.", shortcuts.first().toString(QKeySequence::NativeText)); KNotification::event(QStringLiteral("compositingsuspendeddbus"), message); } } stop(); } void X11Compositor::resume(X11Compositor::SuspendReason reason) { Q_ASSERT(reason != NoReasonSuspend); m_suspended &= ~reason; start(); } void X11Compositor::start() { if (m_suspended) { QStringList reasons; if (m_suspended & UserSuspend) { reasons << QStringLiteral("Disabled by User"); } if (m_suspended & BlockRuleSuspend) { reasons << QStringLiteral("Disabled by Window"); } if (m_suspended & ScriptSuspend) { reasons << QStringLiteral("Disabled by Script"); } qCDebug(KWIN_CORE) << "Compositing is suspended, reason:" << reasons; return; } else if (!kwinApp()->platform()->compositingPossible()) { qCCritical(KWIN_CORE) << "Compositing is not possible"; return; } if (!Compositor::setupStart()) { // Internal setup failed, abort. return; } m_xrrRefreshRate = KWin::currentRefreshRate(); startupWithWorkspace(); } void X11Compositor::performCompositing() { if (scene()->usesOverlayWindow() && !isOverlayWindowVisible()) { // Return since nothing is visible. return; } Compositor::performCompositing(); } bool X11Compositor::checkForOverlayWindow(WId w) const { if (!scene()) { // No scene, so it cannot be the overlay window. return false; } if (!scene()->overlayWindow()) { // No overlay window, it cannot be the overlay. return false; } // Compare the window ID's. return w == scene()->overlayWindow()->window(); } bool X11Compositor::isOverlayWindowVisible() const { if (!scene()) { return false; } if (!scene()->overlayWindow()) { return false; } return scene()->overlayWindow()->isVisible(); } int X11Compositor::refreshRate() const { return m_xrrRefreshRate; } void X11Compositor::updateClientCompositeBlocking(X11Client *c) { if (c) { if (c->isBlockingCompositing()) { // Do NOT attempt to call suspend(true) from within the eventchain! if (!(m_suspended & BlockRuleSuspend)) QMetaObject::invokeMethod(this, [this]() { suspend(BlockRuleSuspend); }, Qt::QueuedConnection); } } else if (m_suspended & BlockRuleSuspend) { // If !c we just check if we can resume in case a blocking client was lost. bool shouldResume = true; for (auto it = Workspace::self()->clientList().constBegin(); it != Workspace::self()->clientList().constEnd(); ++it) { if ((*it)->isBlockingCompositing()) { shouldResume = false; break; } } if (shouldResume) { // Do NOT attempt to call suspend(false) from within the eventchain! QMetaObject::invokeMethod(this, [this]() { resume(BlockRuleSuspend); }, Qt::QueuedConnection); } } } X11Compositor *X11Compositor::self() { return qobject_cast(Compositor::self()); } } // included for CompositorSelectionOwner #include "composite.moc" diff --git a/debug_console.cpp b/debug_console.cpp index 4da68c8fb..e2985197b 100644 --- a/debug_console.cpp +++ b/debug_console.cpp @@ -1,1598 +1,1575 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin 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 . *********************************************************************/ #include "debug_console.h" #include "composite.h" #include "x11client.h" #include "input_event.h" #include "internal_client.h" #include "main.h" #include "scene.h" #include "unmanaged.h" #include "wayland_server.h" #include "workspace.h" #include "keyboard_input.h" #include "input_event.h" #include "libinput/connection.h" #include "libinput/device.h" #include #include #include "ui_debug_console.h" // KWayland #include #include #include #include // frameworks #include #include // Qt #include #include #include // xkb #include #include namespace KWin { static QString tableHeaderRow(const QString &title) { return QStringLiteral("%1").arg(title); } template static QString tableRow(const QString &title, const T &argument) { return QStringLiteral("%1%2").arg(title).arg(argument); } static QString timestampRow(quint32 timestamp) { return tableRow(i18n("Timestamp"), timestamp); } static QString timestampRowUsec(quint64 timestamp) { return tableRow(i18n("Timestamp (µsec)"), timestamp); } static QString buttonToString(Qt::MouseButton button) { switch (button) { case Qt::LeftButton: return i18nc("A mouse button", "Left"); case Qt::RightButton: return i18nc("A mouse button", "Right"); case Qt::MiddleButton: return i18nc("A mouse button", "Middle"); case Qt::BackButton: return i18nc("A mouse button", "Back"); case Qt::ForwardButton: return i18nc("A mouse button", "Forward"); case Qt::TaskButton: return i18nc("A mouse button", "Task"); case Qt::ExtraButton4: return i18nc("A mouse button", "Extra Button 4"); case Qt::ExtraButton5: return i18nc("A mouse button", "Extra Button 5"); case Qt::ExtraButton6: return i18nc("A mouse button", "Extra Button 6"); case Qt::ExtraButton7: return i18nc("A mouse button", "Extra Button 7"); case Qt::ExtraButton8: return i18nc("A mouse button", "Extra Button 8"); case Qt::ExtraButton9: return i18nc("A mouse button", "Extra Button 9"); case Qt::ExtraButton10: return i18nc("A mouse button", "Extra Button 10"); case Qt::ExtraButton11: return i18nc("A mouse button", "Extra Button 11"); case Qt::ExtraButton12: return i18nc("A mouse button", "Extra Button 12"); case Qt::ExtraButton13: return i18nc("A mouse button", "Extra Button 13"); case Qt::ExtraButton14: return i18nc("A mouse button", "Extra Button 14"); case Qt::ExtraButton15: return i18nc("A mouse button", "Extra Button 15"); case Qt::ExtraButton16: return i18nc("A mouse button", "Extra Button 16"); case Qt::ExtraButton17: return i18nc("A mouse button", "Extra Button 17"); case Qt::ExtraButton18: return i18nc("A mouse button", "Extra Button 18"); case Qt::ExtraButton19: return i18nc("A mouse button", "Extra Button 19"); case Qt::ExtraButton20: return i18nc("A mouse button", "Extra Button 20"); case Qt::ExtraButton21: return i18nc("A mouse button", "Extra Button 21"); case Qt::ExtraButton22: return i18nc("A mouse button", "Extra Button 22"); case Qt::ExtraButton23: return i18nc("A mouse button", "Extra Button 23"); case Qt::ExtraButton24: return i18nc("A mouse button", "Extra Button 24"); default: return QString(); } } static QString deviceRow(LibInput::Device *device) { if (!device) { return tableRow(i18n("Input Device"), i18nc("The input device of the event is not known", "Unknown")); } return tableRow(i18n("Input Device"), QStringLiteral("%1 (%2)").arg(device->name()).arg(device->sysName())); } static QString buttonsToString(Qt::MouseButtons buttons) { QString ret; for (uint i = 1; i < Qt::ExtraButton24; i = i << 1) { if (buttons & i) { ret.append(buttonToString(Qt::MouseButton(uint(buttons) & i))); ret.append(QStringLiteral(" ")); } }; return ret.trimmed(); } static const QString s_hr = QStringLiteral("
"); static const QString s_tableStart = QStringLiteral(""); static const QString s_tableEnd = QStringLiteral("
"); DebugConsoleFilter::DebugConsoleFilter(QTextEdit *textEdit) : InputEventSpy() , m_textEdit(textEdit) { } DebugConsoleFilter::~DebugConsoleFilter() = default; void DebugConsoleFilter::pointerEvent(MouseEvent *event) { QString text = s_hr; const QString timestamp = timestampRow(event->timestamp()); text.append(s_tableStart); switch (event->type()) { case QEvent::MouseMove: { text.append(tableHeaderRow(i18nc("A mouse pointer motion event", "Pointer Motion"))); text.append(deviceRow(event->device())); text.append(timestamp); if (event->timestampMicroseconds() != 0) { text.append(timestampRowUsec(event->timestampMicroseconds())); } if (event->delta() != QSizeF()) { text.append(tableRow(i18nc("The relative mouse movement", "Delta"), QStringLiteral("%1/%2").arg(event->delta().width()).arg(event->delta().height()))); } if (event->deltaUnaccelerated() != QSizeF()) { text.append(tableRow(i18nc("The relative mouse movement", "Delta (not accelerated)"), QStringLiteral("%1/%2").arg(event->deltaUnaccelerated().width()).arg(event->deltaUnaccelerated().height()))); } text.append(tableRow(i18nc("The global mouse pointer position", "Global Position"), QStringLiteral("%1/%2").arg(event->pos().x()).arg(event->pos().y()))); break; } case QEvent::MouseButtonPress: text.append(tableHeaderRow(i18nc("A mouse pointer button press event", "Pointer Button Press"))); text.append(deviceRow(event->device())); text.append(timestamp); text.append(tableRow(i18nc("A button in a mouse press/release event", "Button"), buttonToString(event->button()))); text.append(tableRow(i18nc("A button in a mouse press/release event", "Native Button code"), event->nativeButton())); text.append(tableRow(i18nc("All currently pressed buttons in a mouse press/release event", "Pressed Buttons"), buttonsToString(event->buttons()))); break; case QEvent::MouseButtonRelease: text.append(tableHeaderRow(i18nc("A mouse pointer button release event", "Pointer Button Release"))); text.append(deviceRow(event->device())); text.append(timestamp); text.append(tableRow(i18nc("A button in a mouse press/release event", "Button"), buttonToString(event->button()))); text.append(tableRow(i18nc("A button in a mouse press/release event", "Native Button code"), event->nativeButton())); text.append(tableRow(i18nc("All currently pressed buttons in a mouse press/release event", "Pressed Buttons"), buttonsToString(event->buttons()))); break; default: break; } text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::wheelEvent(WheelEvent *event) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A mouse pointer axis (wheel) event", "Pointer Axis"))); text.append(deviceRow(event->device())); text.append(timestampRow(event->timestamp())); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; text.append(tableRow(i18nc("The orientation of a pointer axis event", "Orientation"), orientation == Qt::Horizontal ? i18nc("An orientation of a pointer axis event", "Horizontal") : i18nc("An orientation of a pointer axis event", "Vertical"))); text.append(tableRow(i18nc("The angle delta of a pointer axis event", "Delta"), orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::keyEvent(KeyEvent *event) { QString text = s_hr; text.append(s_tableStart); switch (event->type()) { case QEvent::KeyPress: text.append(tableHeaderRow(i18nc("A key press event", "Key Press"))); break; case QEvent::KeyRelease: text.append(tableHeaderRow(i18nc("A key release event", "Key Release"))); break; default: break; } text.append(deviceRow(event->device())); auto modifiersToString = [event] { QString ret; if (event->modifiers().testFlag(Qt::ShiftModifier)) { ret.append(i18nc("A keyboard modifier", "Shift")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::ControlModifier)) { ret.append(i18nc("A keyboard modifier", "Control")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::AltModifier)) { ret.append(i18nc("A keyboard modifier", "Alt")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::MetaModifier)) { ret.append(i18nc("A keyboard modifier", "Meta")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::KeypadModifier)) { ret.append(i18nc("A keyboard modifier", "Keypad")); ret.append(QStringLiteral(" ")); } if (event->modifiers().testFlag(Qt::GroupSwitchModifier)) { ret.append(i18nc("A keyboard modifier", "Group-switch")); ret.append(QStringLiteral(" ")); } return ret; }; text.append(timestampRow(event->timestamp())); text.append(tableRow(i18nc("Whether the event is an automatic key repeat", "Repeat"), event->isAutoRepeat())); const auto keyMetaObject = Qt::qt_getEnumMetaObject(Qt::Key()); const auto enumerator = keyMetaObject->enumerator(keyMetaObject->indexOfEnumerator("Key")); text.append(tableRow(i18nc("The code as read from the input device", "Scan code"), event->nativeScanCode())); text.append(tableRow(i18nc("Key according to Qt", "Qt::Key code"), enumerator.valueToKey(event->key()))); text.append(tableRow(i18nc("The translated code to an Xkb symbol", "Xkb symbol"), event->nativeVirtualKey())); text.append(tableRow(i18nc("The translated code interpreted as text", "Utf8"), event->text())); text.append(tableRow(i18nc("The currently active modifiers", "Modifiers"), modifiersToString())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::touchDown(qint32 id, const QPointF &pos, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch down event", "Touch down"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(tableRow(i18nc("The global position of the touch point", "Global position"), QStringLiteral("%1/%2").arg(pos.x()).arg(pos.y()))); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::touchMotion(qint32 id, const QPointF &pos, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch motion event", "Touch Motion"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(tableRow(i18nc("The global position of the touch point", "Global position"), QStringLiteral("%1/%2").arg(pos.x()).arg(pos.y()))); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::touchUp(qint32 id, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A touch up event", "Touch Up"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::pinchGestureBegin(int fingerCount, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture is started", "Pinch start"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Number of fingers in this pinch gesture", "Finger count"), fingerCount)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::pinchGestureUpdate(qreal scale, qreal angleDelta, const QSizeF &delta, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture is updated", "Pinch update"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Current scale in pinch gesture", "Scale"), scale)); text.append(tableRow(i18nc("Current angle in pinch gesture", "Angle delta"), angleDelta)); text.append(tableRow(i18nc("Current delta in pinch gesture", "Delta x"), delta.width())); text.append(tableRow(i18nc("Current delta in pinch gesture", "Delta y"), delta.height())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::pinchGestureEnd(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture ended", "Pinch end"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::pinchGestureCancelled(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A pinch gesture got cancelled", "Pinch cancelled"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::swipeGestureBegin(int fingerCount, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture is started", "Swipe start"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Number of fingers in this swipe gesture", "Finger count"), fingerCount)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::swipeGestureUpdate(const QSizeF &delta, quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture is updated", "Swipe update"))); text.append(timestampRow(time)); text.append(tableRow(i18nc("Current delta in swipe gesture", "Delta x"), delta.width())); text.append(tableRow(i18nc("Current delta in swipe gesture", "Delta y"), delta.height())); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::swipeGestureEnd(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture ended", "Swipe end"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::swipeGestureCancelled(quint32 time) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A swipe gesture got cancelled", "Swipe cancelled"))); text.append(timestampRow(time)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::switchEvent(SwitchEvent *event) { QString text = s_hr; text.append(s_tableStart); text.append(tableHeaderRow(i18nc("A hardware switch (e.g. notebook lid) got toggled", "Switch toggled"))); text.append(timestampRow(event->timestamp())); if (event->timestampMicroseconds() != 0) { text.append(timestampRowUsec(event->timestampMicroseconds())); } text.append(deviceRow(event->device())); QString switchName; if (event->device()->isLidSwitch()) { switchName = i18nc("Name of a hardware switch", "Notebook lid"); } else if (event->device()->isTabletModeSwitch()) { switchName = i18nc("Name of a hardware switch", "Tablet mode"); } text.append(tableRow(i18nc("A hardware switch", "Switch"), switchName)); QString switchState; switch (event->state()) { case SwitchEvent::State::Off: switchState = i18nc("The hardware switch got turned off", "Off"); break; case SwitchEvent::State::On: switchState = i18nc("The hardware switch got turned on", "On"); break; default: Q_UNREACHABLE(); } text.append(tableRow(i18nc("State of a hardware switch (on/off)", "State"), switchState)); text.append(s_tableEnd); m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::tabletToolEvent(TabletEvent *event) { QString typeString; { QDebug d(&typeString); d << event->type(); } QString text = s_hr + s_tableStart + tableHeaderRow(i18n("Tablet Tool")) + tableRow(i18n("EventType"), typeString) + tableRow(i18n("Position"), QStringLiteral("%1,%2").arg(event->pos().x()).arg(event->pos().y())) + tableRow(i18n("Tilt"), QStringLiteral("%1,%2").arg(event->xTilt()).arg(event->yTilt())) + tableRow(i18n("Rotation"), QString::number(event->rotation())) + tableRow(i18n("Pressure"), QString::number(event->pressure())) + tableRow(i18n("Buttons"), QString::number(event->buttons())) + tableRow(i18n("Modifiers"), QString::number(event->modifiers())) + s_tableEnd; m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::tabletToolButtonEvent(const QSet &pressedButtons) { QString buttons; for (uint b : pressedButtons) { buttons += QString::number(b) + ' '; } QString text = s_hr + s_tableStart + tableHeaderRow(i18n("Tablet Tool Button")) + tableRow(i18n("Pressed Buttons"), buttons) + s_tableEnd; m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::tabletPadButtonEvent(const QSet &pressedButtons) { QString buttons; for (uint b : pressedButtons) { buttons += QString::number(b) + ' '; } QString text = s_hr + s_tableStart + tableHeaderRow(i18n("Tablet Pad Button")) + tableRow(i18n("Pressed Buttons"), buttons) + s_tableEnd; m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::tabletPadStripEvent(int number, int position, bool isFinger) { QString text = s_hr + s_tableStart + tableHeaderRow(i18n("Tablet Pad Strip")) + tableRow(i18n("Number"), number) + tableRow(i18n("Position"), position) + tableRow(i18n("isFinger"), isFinger) + s_tableEnd; m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } void DebugConsoleFilter::tabletPadRingEvent(int number, int position, bool isFinger) { QString text = s_hr + s_tableStart + tableHeaderRow(i18n("Tablet Pad Ring")) + tableRow(i18n("Number"), number) + tableRow(i18n("Position"), position) + tableRow(i18n("isFinger"), isFinger) + s_tableEnd; m_textEdit->insertHtml(text); m_textEdit->ensureCursorVisible(); } DebugConsole::DebugConsole() : QWidget() , m_ui(new Ui::DebugConsole) { setAttribute(Qt::WA_ShowWithoutActivating); m_ui->setupUi(this); m_ui->windowsView->setItemDelegate(new DebugConsoleDelegate(this)); m_ui->windowsView->setModel(new DebugConsoleModel(this)); m_ui->surfacesView->setModel(new SurfaceTreeModel(this)); if (kwinApp()->usesLibinput()) { m_ui->inputDevicesView->setModel(new InputDeviceModel(this)); m_ui->inputDevicesView->setItemDelegate(new DebugConsoleDelegate(this)); } m_ui->quitButton->setIcon(QIcon::fromTheme(QStringLiteral("application-exit"))); m_ui->tabWidget->setTabIcon(0, QIcon::fromTheme(QStringLiteral("view-list-tree"))); m_ui->tabWidget->setTabIcon(1, QIcon::fromTheme(QStringLiteral("view-list-tree"))); if (kwinApp()->operationMode() == Application::OperationMode::OperationModeX11) { m_ui->tabWidget->setTabEnabled(1, false); m_ui->tabWidget->setTabEnabled(2, false); } if (!kwinApp()->usesLibinput()) { m_ui->tabWidget->setTabEnabled(3, false); } connect(m_ui->quitButton, &QAbstractButton::clicked, this, &DebugConsole::deleteLater); connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, [this] (int index) { // delay creation of input event filter until the tab is selected if (index == 2 && m_inputFilter.isNull()) { m_inputFilter.reset(new DebugConsoleFilter(m_ui->inputTextEdit)); input()->installInputEventSpy(m_inputFilter.data()); } if (index == 5) { updateKeyboardTab(); connect(input(), &InputRedirection::keyStateChanged, this, &DebugConsole::updateKeyboardTab); } } ); // for X11 setWindowFlags(Qt::X11BypassWindowManagerHint); initGLTab(); } DebugConsole::~DebugConsole() = default; void DebugConsole::initGLTab() { if (!effects || !effects->isOpenGLCompositing()) { m_ui->noOpenGLLabel->setVisible(true); m_ui->glInfoScrollArea->setVisible(false); return; } GLPlatform *gl = GLPlatform::instance(); m_ui->noOpenGLLabel->setVisible(false); m_ui->glInfoScrollArea->setVisible(true); m_ui->glVendorStringLabel->setText(QString::fromLocal8Bit(gl->glVendorString())); m_ui->glRendererStringLabel->setText(QString::fromLocal8Bit(gl->glRendererString())); m_ui->glVersionStringLabel->setText(QString::fromLocal8Bit(gl->glVersionString())); m_ui->glslVersionStringLabel->setText(QString::fromLocal8Bit(gl->glShadingLanguageVersionString())); m_ui->glDriverLabel->setText(GLPlatform::driverToString(gl->driver())); m_ui->glGPULabel->setText(GLPlatform::chipClassToString(gl->chipClass())); m_ui->glVersionLabel->setText(GLPlatform::versionToString(gl->glVersion())); m_ui->glslLabel->setText(GLPlatform::versionToString(gl->glslVersion())); auto extensionsString = [] (const auto &extensions) { QString text = QStringLiteral("
    "); for (auto extension : extensions) { text.append(QStringLiteral("
  • %1
  • ").arg(QString::fromLocal8Bit(extension))); } text.append(QStringLiteral("
")); return text; }; m_ui->platformExtensionsLabel->setText(extensionsString(Compositor::self()->scene()->openGLPlatformInterfaceExtensions())); m_ui->openGLExtensionsLabel->setText(extensionsString(openGLExtensions())); } template QString keymapComponentToString(xkb_keymap *map, const T &count, std::function f) { QString text = QStringLiteral("
    "); for (T i = 0; i < count; i++) { text.append(QStringLiteral("
  • %1
  • ").arg(QString::fromLocal8Bit(f(map, i)))); } text.append(QStringLiteral("
")); return text; } template QString stateActiveComponents(xkb_state *state, const T &count, std::function f, std::function name) { QString text = QStringLiteral("
    "); xkb_keymap *map = xkb_state_get_keymap(state); for (T i = 0; i < count; i++) { if (f(state, i) == 1) { text.append(QStringLiteral("
  • %1
  • ").arg(QString::fromLocal8Bit(name(map, i)))); } } text.append(QStringLiteral("
")); return text; } void DebugConsole::updateKeyboardTab() { auto xkb = input()->keyboard()->xkb(); xkb_keymap *map = xkb->keymap(); xkb_state *state = xkb->state(); m_ui->layoutsLabel->setText(keymapComponentToString(map, xkb_keymap_num_layouts(map), &xkb_keymap_layout_get_name)); m_ui->currentLayoutLabel->setText(xkb_keymap_layout_get_name(map, xkb->currentLayout())); m_ui->modifiersLabel->setText(keymapComponentToString(map, xkb_keymap_num_mods(map), &xkb_keymap_mod_get_name)); m_ui->ledsLabel->setText(keymapComponentToString(map, xkb_keymap_num_leds(map), &xkb_keymap_led_get_name)); m_ui->activeLedsLabel->setText(stateActiveComponents(state, xkb_keymap_num_leds(map), &xkb_state_led_index_is_active, &xkb_keymap_led_get_name)); using namespace std::placeholders; auto modActive = std::bind(xkb_state_mod_index_is_active, _1, _2, XKB_STATE_MODS_EFFECTIVE); m_ui->activeModifiersLabel->setText(stateActiveComponents(state, xkb_keymap_num_mods(map), modActive, &xkb_keymap_mod_get_name)); } void DebugConsole::showEvent(QShowEvent *event) { QWidget::showEvent(event); // delay the connection to the show event as in ctor the windowHandle returns null connect(windowHandle(), &QWindow::visibleChanged, this, [this] (bool visible) { if (visible) { // ignore return; } deleteLater(); } ); } DebugConsoleDelegate::DebugConsoleDelegate(QObject *parent) : QStyledItemDelegate(parent) { } DebugConsoleDelegate::~DebugConsoleDelegate() = default; QString DebugConsoleDelegate::displayText(const QVariant &value, const QLocale &locale) const { switch (value.userType()) { case QMetaType::QPoint: { const QPoint p = value.toPoint(); return QStringLiteral("%1,%2").arg(p.x()).arg(p.y()); } case QMetaType::QPointF: { const QPointF p = value.toPointF(); return QStringLiteral("%1,%2").arg(p.x()).arg(p.y()); } case QMetaType::QSize: { const QSize s = value.toSize(); return QStringLiteral("%1x%2").arg(s.width()).arg(s.height()); } case QMetaType::QSizeF: { const QSizeF s = value.toSizeF(); return QStringLiteral("%1x%2").arg(s.width()).arg(s.height()); } case QMetaType::QRect: { const QRect r = value.toRect(); return QStringLiteral("%1,%2 %3x%4").arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height()); } default: if (value.userType() == qMetaTypeId()) { if (auto s = value.value()) { return QStringLiteral("KWaylandServer::SurfaceInterface(0x%1)").arg(qulonglong(s), 0, 16); } else { return QStringLiteral("nullptr"); } } if (value.userType() == qMetaTypeId()) { const auto buttons = value.value(); if (buttons == Qt::NoButton) { return i18n("No Mouse Buttons"); } QStringList list; if (buttons.testFlag(Qt::LeftButton)) { list << i18nc("Mouse Button", "left"); } if (buttons.testFlag(Qt::RightButton)) { list << i18nc("Mouse Button", "right"); } if (buttons.testFlag(Qt::MiddleButton)) { list << i18nc("Mouse Button", "middle"); } if (buttons.testFlag(Qt::BackButton)) { list << i18nc("Mouse Button", "back"); } if (buttons.testFlag(Qt::ForwardButton)) { list << i18nc("Mouse Button", "forward"); } if (buttons.testFlag(Qt::ExtraButton1)) { list << i18nc("Mouse Button", "extra 1"); } if (buttons.testFlag(Qt::ExtraButton2)) { list << i18nc("Mouse Button", "extra 2"); } if (buttons.testFlag(Qt::ExtraButton3)) { list << i18nc("Mouse Button", "extra 3"); } if (buttons.testFlag(Qt::ExtraButton4)) { list << i18nc("Mouse Button", "extra 4"); } if (buttons.testFlag(Qt::ExtraButton5)) { list << i18nc("Mouse Button", "extra 5"); } if (buttons.testFlag(Qt::ExtraButton6)) { list << i18nc("Mouse Button", "extra 6"); } if (buttons.testFlag(Qt::ExtraButton7)) { list << i18nc("Mouse Button", "extra 7"); } if (buttons.testFlag(Qt::ExtraButton8)) { list << i18nc("Mouse Button", "extra 8"); } if (buttons.testFlag(Qt::ExtraButton9)) { list << i18nc("Mouse Button", "extra 9"); } if (buttons.testFlag(Qt::ExtraButton10)) { list << i18nc("Mouse Button", "extra 10"); } if (buttons.testFlag(Qt::ExtraButton11)) { list << i18nc("Mouse Button", "extra 11"); } if (buttons.testFlag(Qt::ExtraButton12)) { list << i18nc("Mouse Button", "extra 12"); } if (buttons.testFlag(Qt::ExtraButton13)) { list << i18nc("Mouse Button", "extra 13"); } if (buttons.testFlag(Qt::ExtraButton14)) { list << i18nc("Mouse Button", "extra 14"); } if (buttons.testFlag(Qt::ExtraButton15)) { list << i18nc("Mouse Button", "extra 15"); } if (buttons.testFlag(Qt::ExtraButton16)) { list << i18nc("Mouse Button", "extra 16"); } if (buttons.testFlag(Qt::ExtraButton17)) { list << i18nc("Mouse Button", "extra 17"); } if (buttons.testFlag(Qt::ExtraButton18)) { list << i18nc("Mouse Button", "extra 18"); } if (buttons.testFlag(Qt::ExtraButton19)) { list << i18nc("Mouse Button", "extra 19"); } if (buttons.testFlag(Qt::ExtraButton20)) { list << i18nc("Mouse Button", "extra 20"); } if (buttons.testFlag(Qt::ExtraButton21)) { list << i18nc("Mouse Button", "extra 21"); } if (buttons.testFlag(Qt::ExtraButton22)) { list << i18nc("Mouse Button", "extra 22"); } if (buttons.testFlag(Qt::ExtraButton23)) { list << i18nc("Mouse Button", "extra 23"); } if (buttons.testFlag(Qt::ExtraButton24)) { list << i18nc("Mouse Button", "extra 24"); } if (buttons.testFlag(Qt::TaskButton)) { list << i18nc("Mouse Button", "task"); } return list.join(QStringLiteral(", ")); } break; } return QStyledItemDelegate::displayText(value, locale); } static const int s_x11ClientId = 1; static const int s_x11UnmanagedId = 2; static const int s_waylandClientId = 3; static const int s_workspaceInternalId = 4; static const quint32 s_propertyBitMask = 0xFFFF0000; static const quint32 s_clientBitMask = 0x0000FFFF; static const quint32 s_idDistance = 10000; template void DebugConsoleModel::add(int parentRow, QVector &clients, T *client) { beginInsertRows(index(parentRow, 0, QModelIndex()), clients.count(), clients.count()); clients.append(client); endInsertRows(); } template void DebugConsoleModel::remove(int parentRow, QVector &clients, T *client) { const int remove = clients.indexOf(client); if (remove == -1) { return; } beginRemoveRows(index(parentRow, 0, QModelIndex()), remove, remove); clients.removeAt(remove); endRemoveRows(); } DebugConsoleModel::DebugConsoleModel(QObject *parent) : QAbstractItemModel(parent) { if (waylandServer()) { const auto clients = waylandServer()->clients(); for (auto c : clients) { m_waylandClients.append(c); } // TODO: that only includes windows getting shown, not those which are only created connect(waylandServer(), &WaylandServer::shellClientAdded, this, [this] (AbstractClient *c) { add(s_waylandClientId -1, m_waylandClients, c); } ); connect(waylandServer(), &WaylandServer::shellClientRemoved, this, [this] (AbstractClient *c) { remove(s_waylandClientId -1, m_waylandClients, c); } ); } const auto x11Clients = workspace()->clientList(); for (auto c : x11Clients) { m_x11Clients.append(c); } - const auto x11DesktopClients = workspace()->desktopList(); - for (auto c : x11DesktopClients) { - m_x11Clients.append(c); - } connect(workspace(), &Workspace::clientAdded, this, [this] (AbstractClient *client) { if (X11Client *x11Client = qobject_cast(client)) { add(s_x11ClientId -1, m_x11Clients, x11Client); } } ); connect(workspace(), &Workspace::clientRemoved, this, [this] (AbstractClient *ac) { X11Client *c = qobject_cast(ac); if (!c) { return; } remove(s_x11ClientId -1, m_x11Clients, c); } ); const auto unmangeds = workspace()->unmanagedList(); for (auto u : unmangeds) { m_unmanageds.append(u); } connect(workspace(), &Workspace::unmanagedAdded, this, [this] (Unmanaged *u) { add(s_x11UnmanagedId -1, m_unmanageds, u); } ); connect(workspace(), &Workspace::unmanagedRemoved, this, [this] (Unmanaged *u) { remove(s_x11UnmanagedId -1, m_unmanageds, u); } ); for (InternalClient *client : workspace()->internalClients()) { m_internalClients.append(client); } connect(workspace(), &Workspace::internalClientAdded, this, [this](InternalClient *client) { add(s_workspaceInternalId -1, m_internalClients, client); } ); connect(workspace(), &Workspace::internalClientRemoved, this, [this](InternalClient *client) { remove(s_workspaceInternalId -1, m_internalClients, client); } ); } DebugConsoleModel::~DebugConsoleModel() = default; int DebugConsoleModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 2; } int DebugConsoleModel::topLevelRowCount() const { return kwinApp()->shouldUseWaylandForCompositing() ? 4 : 2; } template int DebugConsoleModel::propertyCount(const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const { if (T *t = (this->*filter)(parent)) { return t->metaObject()->propertyCount(); } return 0; } int DebugConsoleModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return topLevelRowCount(); } switch (parent.internalId()) { case s_x11ClientId: return m_x11Clients.count(); case s_x11UnmanagedId: return m_unmanageds.count(); case s_waylandClientId: return m_waylandClients.count(); case s_workspaceInternalId: return m_internalClients.count(); default: break; } if (parent.internalId() & s_propertyBitMask) { // properties do not have children return 0; } if (parent.internalId() < s_idDistance * (s_x11ClientId + 1)) { return propertyCount(parent, &DebugConsoleModel::x11Client); } else if (parent.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return propertyCount(parent, &DebugConsoleModel::unmanaged); } else if (parent.internalId() < s_idDistance * (s_waylandClientId + 1)) { return propertyCount(parent, &DebugConsoleModel::waylandClient); } else if (parent.internalId() < s_idDistance * (s_workspaceInternalId + 1)) { return propertyCount(parent, &DebugConsoleModel::internalClient); } return 0; } template QModelIndex DebugConsoleModel::indexForClient(int row, int column, const QVector &clients, int id) const { if (column != 0) { return QModelIndex(); } if (row >= clients.count()) { return QModelIndex(); } return createIndex(row, column, s_idDistance * id + row); } template QModelIndex DebugConsoleModel::indexForProperty(int row, int column, const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex&) const) const { if (T *t = (this->*filter)(parent)) { if (row >= t->metaObject()->propertyCount()) { return QModelIndex(); } return createIndex(row, column, quint32(row + 1) << 16 | parent.internalId()); } return QModelIndex(); } QModelIndex DebugConsoleModel::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid()) { // index for a top level item if (column != 0 || row >= topLevelRowCount()) { return QModelIndex(); } return createIndex(row, column, row + 1); } if (column >= 2) { // max of 2 columns return QModelIndex(); } // index for a client (second level) switch (parent.internalId()) { case s_x11ClientId: return indexForClient(row, column, m_x11Clients, s_x11ClientId); case s_x11UnmanagedId: return indexForClient(row, column, m_unmanageds, s_x11UnmanagedId); case s_waylandClientId: return indexForClient(row, column, m_waylandClients, s_waylandClientId); case s_workspaceInternalId: return indexForClient(row, column, m_internalClients, s_workspaceInternalId); default: break; } // index for a property (third level) if (parent.internalId() < s_idDistance * (s_x11ClientId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::x11Client); } else if (parent.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::unmanaged); } else if (parent.internalId() < s_idDistance * (s_waylandClientId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::waylandClient); } else if (parent.internalId() < s_idDistance * (s_workspaceInternalId + 1)) { return indexForProperty(row, column, parent, &DebugConsoleModel::internalClient); } return QModelIndex(); } QModelIndex DebugConsoleModel::parent(const QModelIndex &child) const { if (child.internalId() <= s_workspaceInternalId) { return QModelIndex(); } if (child.internalId() & s_propertyBitMask) { // a property const quint32 parentId = child.internalId() & s_clientBitMask; if (parentId < s_idDistance * (s_x11ClientId + 1)) { return createIndex(parentId - (s_idDistance * s_x11ClientId), 0, parentId); } else if (parentId < s_idDistance * (s_x11UnmanagedId + 1)) { return createIndex(parentId - (s_idDistance * s_x11UnmanagedId), 0, parentId); } else if (parentId < s_idDistance * (s_waylandClientId + 1)) { return createIndex(parentId - (s_idDistance * s_waylandClientId), 0, parentId); } else if (parentId < s_idDistance * (s_workspaceInternalId + 1)) { return createIndex(parentId - (s_idDistance * s_workspaceInternalId), 0, parentId); } return QModelIndex(); } if (child.internalId() < s_idDistance * (s_x11ClientId + 1)) { return createIndex(s_x11ClientId -1, 0, s_x11ClientId); } else if (child.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) { return createIndex(s_x11UnmanagedId -1, 0, s_x11UnmanagedId); } else if (child.internalId() < s_idDistance * (s_waylandClientId + 1)) { return createIndex(s_waylandClientId -1, 0, s_waylandClientId); } else if (child.internalId() < s_idDistance * (s_workspaceInternalId + 1)) { return createIndex(s_workspaceInternalId -1, 0, s_workspaceInternalId); } return QModelIndex(); } QVariant DebugConsoleModel::propertyData(QObject *object, const QModelIndex &index, int role) const { Q_UNUSED(role) const auto property = object->metaObject()->property(index.row()); if (index.column() == 0) { return property.name(); } else { const QVariant value = property.read(object); if (qstrcmp(property.name(), "windowType") == 0) { switch (value.toInt()) { case NET::Normal: return QStringLiteral("NET::Normal"); case NET::Desktop: return QStringLiteral("NET::Desktop"); case NET::Dock: return QStringLiteral("NET::Dock"); case NET::Toolbar: return QStringLiteral("NET::Toolbar"); case NET::Menu: return QStringLiteral("NET::Menu"); case NET::Dialog: return QStringLiteral("NET::Dialog"); case NET::Override: return QStringLiteral("NET::Override"); case NET::TopMenu: return QStringLiteral("NET::TopMenu"); case NET::Utility: return QStringLiteral("NET::Utility"); case NET::Splash: return QStringLiteral("NET::Splash"); case NET::DropdownMenu: return QStringLiteral("NET::DropdownMenu"); case NET::PopupMenu: return QStringLiteral("NET::PopupMenu"); case NET::Tooltip: return QStringLiteral("NET::Tooltip"); case NET::Notification: return QStringLiteral("NET::Notification"); case NET::ComboBox: return QStringLiteral("NET::ComboBox"); case NET::DNDIcon: return QStringLiteral("NET::DNDIcon"); case NET::OnScreenDisplay: return QStringLiteral("NET::OnScreenDisplay"); case NET::CriticalNotification: return QStringLiteral("NET::CriticalNotification"); case NET::Unknown: default: return QStringLiteral("NET::Unknown"); } } return value; } return QVariant(); } template QVariant DebugConsoleModel::clientData(const QModelIndex &index, int role, const QVector clients) const { if (index.row() >= clients.count()) { return QVariant(); } auto c = clients.at(index.row()); if (role == Qt::DisplayRole) { return QStringLiteral("%1: %2").arg(c->window()).arg(c->caption()); } else if (role == Qt::DecorationRole) { return c->icon(); } return QVariant(); } QVariant DebugConsoleModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (!index.parent().isValid()) { // one of the top levels if (index.column() != 0 || role != Qt::DisplayRole) { return QVariant(); } switch (index.internalId()) { case s_x11ClientId: return i18n("X11 Client Windows"); case s_x11UnmanagedId: return i18n("X11 Unmanaged Windows"); case s_waylandClientId: return i18n("Wayland Windows"); case s_workspaceInternalId: return i18n("Internal Windows"); default: return QVariant(); } } if (index.internalId() & s_propertyBitMask) { if (index.column() >= 2 || role != Qt::DisplayRole) { return QVariant(); } if (AbstractClient *c = waylandClient(index)) { return propertyData(c, index, role); } else if (InternalClient *c = internalClient(index)) { return propertyData(c, index, role); } else if (X11Client *c = x11Client(index)) { return propertyData(c, index, role); } else if (Unmanaged *u = unmanaged(index)) { return propertyData(u, index, role); } } else { if (index.column() != 0) { return QVariant(); } switch (index.parent().internalId()) { case s_x11ClientId: return clientData(index, role, m_x11Clients); case s_x11UnmanagedId: { if (index.row() >= m_unmanageds.count()) { return QVariant(); } auto u = m_unmanageds.at(index.row()); if (role == Qt::DisplayRole) { return u->window(); } break; } case s_waylandClientId: return clientData(index, role, m_waylandClients); case s_workspaceInternalId: return clientData(index, role, m_internalClients); default: break; } } return QVariant(); } template static T *clientForIndex(const QModelIndex &index, const QVector &clients, int id) { const qint32 row = (index.internalId() & s_clientBitMask) - (s_idDistance * id); if (row < 0 || row >= clients.count()) { return nullptr; } return clients.at(row); } AbstractClient *DebugConsoleModel::waylandClient(const QModelIndex &index) const { return clientForIndex(index, m_waylandClients, s_waylandClientId); } InternalClient *DebugConsoleModel::internalClient(const QModelIndex &index) const { return clientForIndex(index, m_internalClients, s_workspaceInternalId); } X11Client *DebugConsoleModel::x11Client(const QModelIndex &index) const { return clientForIndex(index, m_x11Clients, s_x11ClientId); } Unmanaged *DebugConsoleModel::unmanaged(const QModelIndex &index) const { return clientForIndex(index, m_unmanageds, s_x11UnmanagedId); } /////////////////////////////////////// SurfaceTreeModel SurfaceTreeModel::SurfaceTreeModel(QObject *parent) : QAbstractItemModel(parent) { // TODO: it would be nice to not have to reset the model on each change auto reset = [this] { beginResetModel(); endResetModel(); }; using namespace KWaylandServer; const auto unmangeds = workspace()->unmanagedList(); for (auto u : unmangeds) { if (!u->surface()) { continue; } connect(u->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } for (auto c : workspace()->allClientList()) { if (!c->surface()) { continue; } connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } - for (auto c : workspace()->desktopList()) { - if (!c->surface()) { - continue; - } - connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); - } if (waylandServer()) { connect(waylandServer(), &WaylandServer::shellClientAdded, this, [this, reset] (AbstractClient *c) { connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); reset(); } ); } connect(workspace(), &Workspace::clientAdded, this, [this, reset] (AbstractClient *c) { if (c->surface()) { connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } reset(); } ); connect(workspace(), &Workspace::clientRemoved, this, reset); connect(workspace(), &Workspace::unmanagedAdded, this, [this, reset] (Unmanaged *u) { if (u->surface()) { connect(u->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset); } reset(); } ); connect(workspace(), &Workspace::unmanagedRemoved, this, reset); } SurfaceTreeModel::~SurfaceTreeModel() = default; int SurfaceTreeModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 1; } int SurfaceTreeModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { using namespace KWaylandServer; if (SurfaceInterface *surface = static_cast(parent.internalPointer())) { const auto &children = surface->childSubSurfaces(); return children.count(); } return 0; } // toplevel are all windows return workspace()->allClientList().count() + - workspace()->desktopList().count() + workspace()->unmanagedList().count(); } QModelIndex SurfaceTreeModel::index(int row, int column, const QModelIndex &parent) const { if (column != 0) { // invalid column return QModelIndex(); } if (parent.isValid()) { using namespace KWaylandServer; if (SurfaceInterface *surface = static_cast(parent.internalPointer())) { const auto &children = surface->childSubSurfaces(); if (row < children.count()) { return createIndex(row, column, children.at(row)->surface().data()); } } return QModelIndex(); } // a window const auto &allClients = workspace()->allClientList(); if (row < allClients.count()) { // references a client return createIndex(row, column, allClients.at(row)->surface()); } int reference = allClients.count(); - const auto &desktopClients = workspace()->desktopList(); - if (row < reference + desktopClients.count()) { - return createIndex(row, column, desktopClients.at(row-reference)->surface()); - } - reference += desktopClients.count(); const auto &unmanaged = workspace()->unmanagedList(); if (row < reference + unmanaged.count()) { return createIndex(row, column, unmanaged.at(row-reference)->surface()); } reference += unmanaged.count(); // not found return QModelIndex(); } QModelIndex SurfaceTreeModel::parent(const QModelIndex &child) const { using namespace KWaylandServer; if (SurfaceInterface *surface = static_cast(child.internalPointer())) { const auto &subsurface = surface->subSurface(); if (subsurface.isNull()) { // doesn't reference a subsurface, this is a top-level window return QModelIndex(); } SurfaceInterface *parent = subsurface->parentSurface().data(); if (!parent) { // something is wrong return QModelIndex(); } // is the parent a subsurface itself? if (parent->subSurface()) { auto grandParent = parent->subSurface()->parentSurface(); if (grandParent.isNull()) { // something is wrong return QModelIndex(); } const auto &children = grandParent->childSubSurfaces(); for (int row = 0; row < children.count(); row++) { if (children.at(row).data() == parent->subSurface().data()) { return createIndex(row, 0, parent); } } return QModelIndex(); } // not a subsurface, thus it's a true window int row = 0; const auto &allClients = workspace()->allClientList(); for (; row < allClients.count(); row++) { if (allClients.at(row)->surface() == parent) { return createIndex(row, 0, parent); } } row = allClients.count(); - const auto &desktopClients = workspace()->desktopList(); - for (int i = 0; i < desktopClients.count(); i++) { - if (desktopClients.at(i)->surface() == parent) { - return createIndex(row + i, 0, parent); - } - } - row += desktopClients.count(); const auto &unmanaged = workspace()->unmanagedList(); for (int i = 0; i < unmanaged.count(); i++) { if (unmanaged.at(i)->surface() == parent) { return createIndex(row + i, 0, parent); } } row += unmanaged.count(); } return QModelIndex(); } QVariant SurfaceTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } using namespace KWaylandServer; if (SurfaceInterface *surface = static_cast(index.internalPointer())) { if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { return QStringLiteral("%1 (%2) - %3").arg(surface->client()->executablePath()) .arg(surface->client()->processId()) .arg(surface->id()); } else if (role == Qt::DecorationRole) { if (auto buffer = surface->buffer()) { if (buffer->shmBuffer()) { return buffer->data().scaled(QSize(64, 64), Qt::KeepAspectRatio); } } } } return QVariant(); } InputDeviceModel::InputDeviceModel(QObject *parent) : QAbstractItemModel(parent) , m_devices(LibInput::Connection::self()->devices()) { for (auto it = m_devices.constBegin(); it != m_devices.constEnd(); ++it) { setupDeviceConnections(*it); } auto c = LibInput::Connection::self(); connect(c, &LibInput::Connection::deviceAdded, this, [this] (LibInput::Device *d) { beginInsertRows(QModelIndex(), m_devices.count(), m_devices.count()); m_devices << d; setupDeviceConnections(d); endInsertRows(); } ); connect(c, &LibInput::Connection::deviceRemoved, this, [this] (LibInput::Device *d) { const int index = m_devices.indexOf(d); if (index == -1) { return; } beginRemoveRows(QModelIndex(), index, index); m_devices.removeAt(index); endRemoveRows(); } ); } InputDeviceModel::~InputDeviceModel() = default; int InputDeviceModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 2; } QVariant InputDeviceModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (!index.parent().isValid() && index.column() == 0) { const auto devices = LibInput::Connection::self()->devices(); if (index.row() >= devices.count()) { return QVariant(); } if (role == Qt::DisplayRole) { return devices.at(index.row())->name(); } } if (index.parent().isValid()) { if (role == Qt::DisplayRole) { const auto device = LibInput::Connection::self()->devices().at(index.parent().row()); const auto property = device->metaObject()->property(index.row()); if (index.column() == 0) { return property.name(); } else if (index.column() == 1) { return device->property(property.name()); } } } return QVariant(); } QModelIndex InputDeviceModel::index(int row, int column, const QModelIndex &parent) const { if (column >= 2) { return QModelIndex(); } if (parent.isValid()) { if (parent.internalId() & s_propertyBitMask) { return QModelIndex(); } if (row >= LibInput::Connection::self()->devices().at(parent.row())->metaObject()->propertyCount()) { return QModelIndex(); } return createIndex(row, column, quint32(row + 1) << 16 | parent.internalId()); } if (row >= LibInput::Connection::self()->devices().count()) { return QModelIndex(); } return createIndex(row, column, row + 1); } int InputDeviceModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return LibInput::Connection::self()->devices().count(); } if (parent.internalId() & s_propertyBitMask) { return 0; } return LibInput::Connection::self()->devices().at(parent.row())->metaObject()->propertyCount(); } QModelIndex InputDeviceModel::parent(const QModelIndex &child) const { if (child.internalId() & s_propertyBitMask) { const quintptr parentId = child.internalId() & s_clientBitMask; return createIndex(parentId - 1, 0, parentId); } return QModelIndex(); } void InputDeviceModel::setupDeviceConnections(LibInput::Device *device) { connect(device, &LibInput::Device::enabledChanged, this, [this, device] { const QModelIndex parent = index(m_devices.indexOf(device), 0, QModelIndex()); const QModelIndex child = index(device->metaObject()->indexOfProperty("enabled"), 1, parent); emit dataChanged(child, child, QVector{Qt::DisplayRole}); } ); connect(device, &LibInput::Device::leftHandedChanged, this, [this, device] { const QModelIndex parent = index(m_devices.indexOf(device), 0, QModelIndex()); const QModelIndex child = index(device->metaObject()->indexOfProperty("leftHanded"), 1, parent); emit dataChanged(child, child, QVector{Qt::DisplayRole}); } ); connect(device, &LibInput::Device::pointerAccelerationChanged, this, [this, device] { const QModelIndex parent = index(m_devices.indexOf(device), 0, QModelIndex()); const QModelIndex child = index(device->metaObject()->indexOfProperty("pointerAcceleration"), 1, parent); emit dataChanged(child, child, QVector{Qt::DisplayRole}); } ); } } diff --git a/layers.cpp b/layers.cpp index 248468e4a..69ca273f2 100644 --- a/layers.cpp +++ b/layers.cpp @@ -1,864 +1,861 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak 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 . *********************************************************************/ // SELI zmenit doc /* This file contains things relevant to stacking order and layers. Design: Normal unconstrained stacking order, as requested by the user (by clicking on windows to raise them, etc.), is in Workspace::unconstrained_stacking_order. That list shouldn't be used at all, except for building Workspace::stacking_order. The building is done in Workspace::constrainedStackingOrder(). Only Workspace::stackingOrder() should be used to get the stacking order, because it also checks the stacking order is up to date. All clients are also stored in Workspace::clients (except for isDesktop() clients, as those are very special, and are stored in Workspace::desktops), in the order the clients were created. Every window has one layer assigned in which it is. There are 7 layers, from bottom : DesktopLayer, BelowLayer, NormalLayer, DockLayer, AboveLayer, NotificationLayer, ActiveLayer, CriticalNotificationLayer, and OnScreenDisplayLayer (see also NETWM sect.7.10.). The layer a window is in depends on the window type, and on other things like whether the window is active. We extend the layers provided in NETWM by the NotificationLayer, OnScreenDisplayLayer, and CriticalNotificationLayer. The NoficationLayer contains notification windows which are kept above all windows except the active fullscreen window. The CriticalNotificationLayer contains notification windows which are important enough to keep them even above fullscreen windows. The OnScreenDisplayLayer is used for eg. volume and brightness change feedback and is kept above all windows since it provides immediate response to a user action. NET::Splash clients belong to the Normal layer. NET::TopMenu clients belong to Dock layer. Clients that are both NET::Dock and NET::KeepBelow are in the Normal layer in order to keep the 'allow window to cover the panel' Kicker setting to work as intended (this may look like a slight spec violation, but a) I have no better idea, b) the spec allows adjusting the stacking order if the WM thinks it's a good idea . We put all NET::KeepAbove above all Docks too, even though the spec suggests putting them in the same layer. Most transients are in the same layer as their mainwindow, see Workspace::constrainedStackingOrder(), they may also be in higher layers, but they should never be below their mainwindow. When some client attribute changes (above/below flag, transiency...), Workspace::updateClientLayer() should be called in order to make sure it's moved to the appropriate layer QList if needed. Currently the things that affect client in which layer a client belongs: KeepAbove/Keep Below flags, window type, fullscreen state and whether the client is active, mainclient (transiency). Make sure updateStackingOrder() is called in order to make Workspace::stackingOrder() up to date and propagated to the world. Using Workspace::blockStackingUpdates() (or the StackingUpdatesBlocker helper class) it's possible to temporarily disable updates and the stacking order will be updated once after it's allowed again. */ #include "utils.h" #include "x11client.h" #include "focuschain.h" #include "netinfo.h" #include "workspace.h" #include "tabbox.h" #include "group.h" #include "rules.h" #include "screens.h" #include "unmanaged.h" #include "deleted.h" #include "effects.h" #include "composite.h" #include "screenedge.h" #include "wayland_server.h" #include "internal_client.h" #include namespace KWin { //******************************* // Workspace //******************************* void Workspace::updateClientLayer(AbstractClient* c) { if (c) c->updateLayer(); } void Workspace::updateStackingOrder(bool propagate_new_clients) { if (block_stacking_updates > 0) { if (propagate_new_clients) blocked_propagating_new_clients = true; return; } QList new_stacking_order = constrainedStackingOrder(); bool changed = (force_restacking || new_stacking_order != stacking_order); force_restacking = false; stacking_order = new_stacking_order; if (changed || propagate_new_clients) { propagateClients(propagate_new_clients); emit stackingOrderChanged(); if (m_compositor) { m_compositor->addRepaintFull(); } if (active_client) active_client->updateMouseGrab(); } } /** * Some fullscreen effects have to raise the screenedge on top of an input window, thus all windows * this function puts them back where they belong for regular use and is some cheap variant of * the regular propagateClients function in that it completely ignores managed clients and everything * else and also does not update the NETWM property. * Called from Effects::destroyInputWindow so far. */ void Workspace::stackScreenEdgesUnderOverrideRedirect() { if (!rootInfo()) { return; } Xcb::restackWindows(QVector() << rootInfo()->supportWindow() << ScreenEdges::self()->windows()); } /** * Propagates the managed clients to the world. * Called ONLY from updateStackingOrder(). */ void Workspace::propagateClients(bool propagate_new_clients) { if (!rootInfo()) { return; } // restack the windows according to the stacking order // supportWindow > electric borders > clients > hidden clients QVector newWindowStack; // Stack all windows under the support window. The support window is // not used for anything (besides the NETWM property), and it's not shown, // but it was lowered after kwin startup. Stacking all clients below // it ensures that no client will be ever shown above override-redirect // windows (e.g. popups). newWindowStack << rootInfo()->supportWindow(); newWindowStack << ScreenEdges::self()->windows(); newWindowStack << manual_overlays; newWindowStack.reserve(newWindowStack.size() + 2*stacking_order.size()); // *2 for inputWindow for (int i = stacking_order.size() - 1; i >= 0; --i) { X11Client *client = qobject_cast(stacking_order.at(i)); if (!client || client->hiddenPreview()) { continue; } if (client->inputId()) // Stack the input window above the frame newWindowStack << client->inputId(); newWindowStack << client->frameId(); } // when having hidden previews, stack hidden windows below everything else // (as far as pure X stacking order is concerned), in order to avoid having // these windows that should be unmapped to interfere with other windows for (int i = stacking_order.size() - 1; i >= 0; --i) { X11Client *client = qobject_cast(stacking_order.at(i)); if (!client || !client->hiddenPreview()) continue; newWindowStack << client->frameId(); } // TODO isn't it too inefficient to restack always all clients? // TODO don't restack not visible windows? Q_ASSERT(newWindowStack.at(0) == rootInfo()->supportWindow()); Xcb::restackWindows(newWindowStack); int pos = 0; xcb_window_t *cl(nullptr); if (propagate_new_clients) { - cl = new xcb_window_t[ manual_overlays.count() + desktops.count() + clients.count()]; + cl = new xcb_window_t[ manual_overlays.count() + clients.count()]; for (const auto win : manual_overlays) { cl[pos++] = win; } - // TODO this is still not completely in the map order - for (auto it = desktops.constBegin(); it != desktops.constEnd(); ++it) - cl[pos++] = (*it)->window(); for (auto it = clients.constBegin(); it != clients.constEnd(); ++it) cl[pos++] = (*it)->window(); rootInfo()->setClientList(cl, pos); delete [] cl; } cl = new xcb_window_t[ manual_overlays.count() + stacking_order.count()]; pos = 0; for (auto it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { X11Client *client = qobject_cast(*it); if (client) { cl[pos++] = client->window(); } } for (const auto win : manual_overlays) { cl[pos++] = win; } rootInfo()->setClientListStacking(cl, pos); delete [] cl; // Make the cached stacking order invalid here, in case we need the new stacking order before we get // the matching event, due to X being asynchronous. markXStackingOrderAsDirty(); } /** * Returns topmost visible client. Windows on the dock, the desktop * or of any other special kind are excluded. Also if the window * doesn't accept focus it's excluded. */ // TODO misleading name for this method, too many slightly different ways to use it AbstractClient* Workspace::topClientOnDesktop(int desktop, int screen, bool unconstrained, bool only_normal) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); QList list; if (!unconstrained) list = stacking_order; else list = unconstrained_stacking_order; for (int i = list.size() - 1; i >= 0; --i) { AbstractClient *c = qobject_cast(list.at(i)); if (!c) { continue; } if (c->isOnDesktop(desktop) && c->isShown(false) && c->isOnCurrentActivity()) { if (screen != -1 && c->screen() != screen) continue; if (!only_normal) return c; if (c->wantsTabFocus() && !c->isSpecialWindow()) return c; } } return nullptr; } AbstractClient* Workspace::findDesktop(bool topmost, int desktop) const { // TODO Q_ASSERT( block_stacking_updates == 0 ); if (topmost) { for (int i = stacking_order.size() - 1; i >= 0; i--) { AbstractClient *c = qobject_cast(stacking_order.at(i)); if (c && c->isOnDesktop(desktop) && c->isDesktop() && c->isShown(true)) return c; } } else { // bottom-most foreach (Toplevel * c, stacking_order) { AbstractClient *client = qobject_cast(c); if (client && c->isOnDesktop(desktop) && c->isDesktop() && client->isShown(true)) return client; } } return nullptr; } void Workspace::raiseOrLowerClient(AbstractClient *c) { if (!c) return; AbstractClient* topmost = nullptr; // TODO Q_ASSERT( block_stacking_updates == 0 ); if (most_recently_raised && stacking_order.contains(most_recently_raised) && most_recently_raised->isShown(true) && c->isOnCurrentDesktop()) topmost = most_recently_raised; else topmost = topClientOnDesktop(c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop(), options->isSeparateScreenFocus() ? c->screen() : -1); if (c == topmost) lowerClient(c); else raiseClient(c); } void Workspace::lowerClient(AbstractClient* c, bool nogroup) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.prepend(c); if (!nogroup && c->isTransient()) { // lower also all windows in the group, in their reversed stacking order QList wins; if (auto group = c->group()) { wins = ensureStackingOrder(group->members()); } for (int i = wins.size() - 1; i >= 0; --i) { if (wins[ i ] != c) lowerClient(wins[ i ], true); } } if (c == most_recently_raised) most_recently_raised = nullptr; } void Workspace::lowerClientWithinApplication(AbstractClient* c) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); bool lowered = false; // first try to put it below the bottom-most window of the application for (auto it = unconstrained_stacking_order.begin(); it != unconstrained_stacking_order.end(); ++it) { AbstractClient *client = qobject_cast(*it); if (!client) { continue; } if (AbstractClient::belongToSameApplication(client, c)) { unconstrained_stacking_order.insert(it, c); lowered = true; break; } } if (!lowered) unconstrained_stacking_order.prepend(c); // ignore mainwindows } void Workspace::raiseClient(AbstractClient* c, bool nogroup) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); if (!nogroup && c->isTransient()) { QList transients; AbstractClient *transient_parent = c; while ((transient_parent = transient_parent->transientFor())) transients << transient_parent; foreach (transient_parent, transients) raiseClient(transient_parent, true); } unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.append(c); if (!c->isSpecialWindow()) { most_recently_raised = c; } } void Workspace::raiseClientWithinApplication(AbstractClient* c) { if (!c) return; c->cancelAutoRaise(); StackingUpdatesBlocker blocker(this); // ignore mainwindows // first try to put it above the top-most window of the application for (int i = unconstrained_stacking_order.size() - 1; i > -1 ; --i) { AbstractClient *other = qobject_cast(unconstrained_stacking_order.at(i)); if (!other) { continue; } if (other == c) // don't lower it just because it asked to be raised return; if (AbstractClient::belongToSameApplication(other, c)) { unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(other) + 1, c); // insert after the found one break; } } } void Workspace::raiseClientRequest(KWin::AbstractClient *c, NET::RequestSource src, xcb_timestamp_t timestamp) { if (src == NET::FromTool || allowFullClientRaising(c, timestamp)) raiseClient(c); else { raiseClientWithinApplication(c); c->demandAttention(); } } void Workspace::lowerClientRequest(KWin::X11Client *c, NET::RequestSource src, xcb_timestamp_t /*timestamp*/) { // If the client has support for all this focus stealing prevention stuff, // do only lowering within the application, as that's the more logical // variant of lowering when application requests it. // No demanding of attention here of course. if (src == NET::FromTool || !c->hasUserTimeSupport()) lowerClient(c); else lowerClientWithinApplication(c); } void Workspace::lowerClientRequest(KWin::AbstractClient *c) { lowerClientWithinApplication(c); } void Workspace::restack(AbstractClient* c, AbstractClient* under, bool force) { Q_ASSERT(unconstrained_stacking_order.contains(under)); if (!force && !AbstractClient::belongToSameApplication(under, c)) { // put in the stacking order below _all_ windows belonging to the active application for (int i = 0; i < unconstrained_stacking_order.size(); ++i) { AbstractClient *other = qobject_cast(unconstrained_stacking_order.at(i)); if (other && other->layer() == c->layer() && AbstractClient::belongToSameApplication(under, other)) { under = (c == other) ? nullptr : other; break; } } } if (under) { unconstrained_stacking_order.removeAll(c); unconstrained_stacking_order.insert(unconstrained_stacking_order.indexOf(under), c); } Q_ASSERT(unconstrained_stacking_order.contains(c)); FocusChain::self()->moveAfterClient(c, under); updateStackingOrder(); } void Workspace::restackClientUnderActive(AbstractClient* c) { if (!active_client || active_client == c || active_client->layer() != c->layer()) { raiseClient(c); return; } restack(c, active_client); } void Workspace::restoreSessionStackingOrder(X11Client *c) { if (c->sessionStackingOrder() < 0) return; StackingUpdatesBlocker blocker(this); unconstrained_stacking_order.removeAll(c); for (auto it = unconstrained_stacking_order.begin(); // from bottom it != unconstrained_stacking_order.end(); ++it) { X11Client *current = qobject_cast(*it); if (!current) { continue; } if (current->sessionStackingOrder() > c->sessionStackingOrder()) { unconstrained_stacking_order.insert(it, c); return; } } unconstrained_stacking_order.append(c); } /** * Returns a stacking order based upon \a list that fulfills certain contained. */ QList Workspace::constrainedStackingOrder() { QList layer[ NumLayers ]; // build the order from layers QVector< QMultiMap > minimum_layer(screens()->count()); for (auto it = unconstrained_stacking_order.constBegin(), end = unconstrained_stacking_order.constEnd(); it != end; ++it) { Layer l = (*it)->layer(); const int screen = (*it)->screen(); X11Client *c = qobject_cast(*it); QMap< Group*, Layer >::iterator mLayer = minimum_layer[screen].find(c ? c->group() : nullptr); if (mLayer != minimum_layer[screen].end()) { // If a window is raised above some other window in the same window group // which is in the ActiveLayer (i.e. it's fulscreened), make sure it stays // above that window (see #95731). if (*mLayer == ActiveLayer && (l > BelowLayer)) l = ActiveLayer; *mLayer = l; } else if (c) { minimum_layer[screen].insert(c->group(), l); } layer[ l ].append(*it); } QList stacking; for (int lay = FirstLayer; lay < NumLayers; ++lay) { stacking += layer[lay]; } // now keep transients above their mainwindows // TODO this could(?) use some optimization for (int i = stacking.size() - 1; i >= 0;) { // Index of the main window for the current transient window. int i2 = -1; // If the current transient has "child" transients, we'd like to restart // construction of the constrained stacking order from the position where // the current transient will be moved. bool hasTransients = false; // Find topmost client this one is transient for. if (auto *client = qobject_cast(stacking[i])) { if (!client->isTransient()) { --i; continue; } for (i2 = stacking.size() - 1; i2 >= 0; --i2) { auto *c2 = qobject_cast(stacking[i2]); if (!c2) { continue; } if (c2 == client) { i2 = -1; // Don't reorder, already on top of its main window. break; } if (c2->hasTransient(client, true) && keepTransientAbove(c2, client)) { break; } } hasTransients = !client->transients().isEmpty(); // If the current transient doesn't have any "alive" transients, check // whether it has deleted transients that have to be raised. const bool searchForDeletedTransients = !hasTransients && !deletedList().isEmpty(); if (searchForDeletedTransients) { for (int j = i + 1; j < stacking.count(); ++j) { auto *deleted = qobject_cast(stacking[j]); if (!deleted) { continue; } if (deleted->wasTransientFor(client)) { hasTransients = true; break; } } } } else if (auto *deleted = qobject_cast(stacking[i])) { if (!deleted->wasTransient()) { --i; continue; } for (i2 = stacking.size() - 1; i2 >= 0; --i2) { Toplevel *c2 = stacking[i2]; if (c2 == deleted) { i2 = -1; // Don't reorder, already on top of its main window. break; } if (deleted->wasTransientFor(c2) && keepDeletedTransientAbove(c2, deleted)) { break; } } hasTransients = !deleted->transients().isEmpty(); } if (i2 == -1) { --i; continue; } Toplevel *current = stacking[i]; stacking.removeAt(i); --i; // move onto the next item (for next for () iteration) --i2; // adjust index of the mainwindow after the remove above if (hasTransients) { // this one now can be possibly above its transients, i = i2; // so go again higher in the stack order and possibly move those transients again } ++i2; // insert after (on top of) the mainwindow, it's ok if it2 is now stacking.end() stacking.insert(i2, current); } return stacking; } void Workspace::blockStackingUpdates(bool block) { if (block) { if (block_stacking_updates == 0) blocked_propagating_new_clients = false; ++block_stacking_updates; } else // !block if (--block_stacking_updates == 0) { updateStackingOrder(blocked_propagating_new_clients); if (effects) static_cast(effects)->checkInputWindowStacking(); } } namespace { template QList ensureStackingOrderInList(const QList &stackingOrder, const QList &list) { static_assert(std::is_base_of::value, "U must be derived from T"); // TODO Q_ASSERT( block_stacking_updates == 0 ); if (list.count() < 2) return list; // TODO is this worth optimizing? QList result = list; for (auto it = stackingOrder.begin(); it != stackingOrder.end(); ++it) { T *c = qobject_cast(*it); if (!c) { continue; } if (result.removeAll(c) != 0) result.append(c); } return result; } } // Ensure list is in stacking order QList Workspace::ensureStackingOrder(const QList &list) const { return ensureStackingOrderInList(stacking_order, list); } QList Workspace::ensureStackingOrder(const QList &list) const { return ensureStackingOrderInList(stacking_order, list); } // check whether a transient should be actually kept above its mainwindow // there may be some special cases where this rule shouldn't be enfored bool Workspace::keepTransientAbove(const AbstractClient* mainwindow, const AbstractClient* transient) { // #93832 - don't keep splashscreens above dialogs if (transient->isSplash() && mainwindow->isDialog()) return false; // This is rather a hack for #76026. Don't keep non-modal dialogs above // the mainwindow, but only if they're group transient (since only such dialogs // have taskbar entry in Kicker). A proper way of doing this (both kwin and kicker) // needs to be found. if (transient->isDialog() && !transient->isModal() && transient->groupTransient()) return false; // #63223 - don't keep transients above docks, because the dock is kept high, // and e.g. dialogs for them would be too high too // ignore this if the transient has a placement hint which indicates it should go above it's parent if (mainwindow->isDock() && !transient->hasTransientPlacementHint()) return false; return true; } bool Workspace::keepDeletedTransientAbove(const Toplevel *mainWindow, const Deleted *transient) const { // #93832 - Don't keep splashscreens above dialogs. if (transient->isSplash() && mainWindow->isDialog()) { return false; } if (transient->wasX11Client()) { // If a group transient was active, we should keep it above no matter // what, because at the time when the transient was closed, it was above // the main window. if (transient->wasGroupTransient() && transient->wasActive()) { return true; } // This is rather a hack for #76026. Don't keep non-modal dialogs above // the mainwindow, but only if they're group transient (since only such // dialogs have taskbar entry in Kicker). A proper way of doing this // (both kwin and kicker) needs to be found. if (transient->wasGroupTransient() && transient->isDialog() && !transient->isModal()) { return false; } // #63223 - Don't keep transients above docks, because the dock is kept // high, and e.g. dialogs for them would be too high too. if (mainWindow->isDock()) { return false; } } return true; } // Returns all windows in their stacking order on the root window. QList Workspace::xStackingOrder() const { if (m_xStackingDirty) { const_cast(this)->updateXStackingOrder(); } return x_stacking; } void Workspace::updateXStackingOrder() { x_stacking.clear(); std::unique_ptr tree{std::move(m_xStackingQueryTree)}; // use our own stacking order, not the X one, as they may differ foreach (Toplevel * c, stacking_order) x_stacking.append(c); if (tree && !tree->isNull()) { xcb_window_t *windows = tree->children(); const auto count = tree->data()->children_len; int foundUnmanagedCount = unmanaged.count(); for (unsigned int i = 0; i < count; ++i) { for (auto it = unmanaged.constBegin(); it != unmanaged.constEnd(); ++it) { Unmanaged *u = *it; if (u->window() == windows[i]) { x_stacking.append(u); foundUnmanagedCount--; break; } } if (foundUnmanagedCount == 0) { break; } } } for (InternalClient *client : workspace()->internalClients()) { if (client->isShown(false)) { x_stacking.append(client); } } m_xStackingDirty = false; } //******************************* // Client //******************************* void X11Client::restackWindow(xcb_window_t above, int detail, NET::RequestSource src, xcb_timestamp_t timestamp, bool send_event) { X11Client *other = nullptr; if (detail == XCB_STACK_MODE_OPPOSITE) { other = workspace()->findClient(Predicate::WindowMatch, above); if (!other) { workspace()->raiseOrLowerClient(this); return; } auto it = workspace()->stackingOrder().constBegin(), end = workspace()->stackingOrder().constEnd(); while (it != end) { if (*it == this) { detail = XCB_STACK_MODE_ABOVE; break; } else if (*it == other) { detail = XCB_STACK_MODE_BELOW; break; } ++it; } } else if (detail == XCB_STACK_MODE_TOP_IF) { other = workspace()->findClient(Predicate::WindowMatch, above); if (other && other->frameGeometry().intersects(frameGeometry())) workspace()->raiseClientRequest(this, src, timestamp); return; } else if (detail == XCB_STACK_MODE_BOTTOM_IF) { other = workspace()->findClient(Predicate::WindowMatch, above); if (other && other->frameGeometry().intersects(frameGeometry())) workspace()->lowerClientRequest(this, src, timestamp); return; } if (!other) other = workspace()->findClient(Predicate::WindowMatch, above); if (other && detail == XCB_STACK_MODE_ABOVE) { auto it = workspace()->stackingOrder().constEnd(), begin = workspace()->stackingOrder().constBegin(); while (--it != begin) { if (*it == other) { // the other one is top on stack it = begin; // invalidate src = NET::FromTool; // force break; } X11Client *c = qobject_cast(*it); if (!c || !( (*it)->isNormalWindow() && c->isShown(true) && (*it)->isOnCurrentDesktop() && (*it)->isOnCurrentActivity() && (*it)->isOnScreen(screen()) )) continue; // irrelevant clients if (*(it - 1) == other) break; // "it" is the one above the target one, stack below "it" } if (it != begin && (*(it - 1) == other)) other = qobject_cast(*it); else other = nullptr; } if (other) workspace()->restack(this, other); else if (detail == XCB_STACK_MODE_BELOW) workspace()->lowerClientRequest(this, src, timestamp); else if (detail == XCB_STACK_MODE_ABOVE) workspace()->raiseClientRequest(this, src, timestamp); if (send_event) sendSyntheticConfigureNotify(); } bool X11Client::belongsToDesktop() const { foreach (const X11Client *c, group()->members()) { if (c->isDesktop()) return true; } return false; } } // namespace diff --git a/workspace.cpp b/workspace.cpp index 5a6fa444e..d2892ac7d 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -1,2787 +1,2769 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak Copyright (C) 2019 Vlad Zahorodnii 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 . *********************************************************************/ // own #include "workspace.h" // kwin libs #include // kwin #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "appmenu.h" #include "atoms.h" #include "x11client.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 "internal_client.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" #include "syncalarmx11filter.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox.h" #endif #include "unmanaged.h" #include "useractions.h" #include "virtualdesktops.h" #include "was_user_interaction_x11_filter.h" #include "wayland_server.h" #include "xcbutils.h" #include "main.h" #include "decorations/decorationbridge.h" #include "xwaylandclient.h" // KDE #include #include #include #include // Qt #include 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 (X11Client *c = dynamic_cast(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 = nullptr; Workspace::Workspace() : QObject(nullptr) , m_compositor(nullptr) // Unsorted , active_popup(nullptr) , active_popup_client(nullptr) , m_initialDesktop(1) , active_client(nullptr) , last_active_client(nullptr) , most_recently_raised(nullptr) , movingClient(nullptr) , delayfocus_client(nullptr) , force_restacking(false) , showing_desktop(false) , was_user_interaction(false) , block_focus(0) , m_userActionsMenu(new UserActionsMenu(this)) , client_keys_dialog(nullptr) , client_keys_client(nullptr) , global_shortcuts_disabled_for_client(false) , workspaceInit(true) , startup(nullptr) , set_active_client_recursion(0) , block_stacking_updates(0) , m_sessionManager(new SessionManager(this)) { // If KWin was already running it saved its configuration after loosing the selection -> Reread QFuture 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 = nullptr; RuleBook::create(this)->load(); kwinApp()->createScreens(); 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); connect(m_sessionManager, &SessionManager::loadSessionRequested, this, &Workspace::loadSessionInfo); connect(m_sessionManager, &SessionManager::prepareSessionSaveRequested, this, [this](const QString &name) { storeSession(name, SMSavePhase0); }); connect(m_sessionManager, &SessionManager::finishSessionSaveRequested, this, [this](const QString &name) { storeSession(name, SMSavePhase2); }); new DBusInterface(this); Outline::create(this); initShortcuts(); init(); } void Workspace::init() { KSharedConfigPtr config = kwinApp()->config(); 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(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); reconfigureTimer.setSingleShot(true); updateToolWindowsTimer.setSingleShot(true); 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 = nullptr; initWithX11(); Scripting::create(this); if (auto server = waylandServer()) { connect(server, &WaylandServer::shellClientAdded, this, &Workspace::addShellClient); connect(server, &WaylandServer::shellClientRemoved, this, &Workspace::removeShellClient); } // 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() } 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); } if (Xcb::Extensions::self()->isSyncAvailable()) { m_syncAlarmFilter.reset(new SyncAlarmX11Filter); } 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 windowAttributes(tree->children_len); QVector 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 == nullptr && activeClient() == nullptr && should_get_focus.count() == 0) { // No client activated in manage() if (new_active_client == nullptr) new_active_client = topClientOnDesktop(VirtualDesktopManager::self()->current(), -1); - if (new_active_client == nullptr && !desktops.isEmpty()) + if (new_active_client == nullptr) new_active_client = findDesktop(true, VirtualDesktopManager::self()->current()); } if (new_active_client != nullptr) activateClient(new_active_client); } Workspace::~Workspace() { blockStackingUpdates(true); // TODO: grabXServer(); // Use stacking_order, so that kwin --replace keeps stacking order const QList 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 (auto it = stack.constBegin(), end = stack.constEnd(); it != end; ++it) { X11Client *c = qobject_cast(const_cast(*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); } X11Client::cleanupX11(); if (waylandServer()) { const QList shellClients = waylandServer()->clients(); for (AbstractClient *client : shellClients) { client->destroyClient(); } } for (auto it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it) (*it)->release(ReleaseReason::KWinShutsDown); for (InternalClient *client : m_internalClients) { client->destroyClient(); } 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; qDeleteAll(session); // TODO: ungrabXServer(); Xcb::Extensions::destroy(); _self = nullptr; } 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)); } X11Client *Workspace::createClient(xcb_window_t w, bool is_mapped) { StackingUpdatesBlocker blocker(this); X11Client *c = nullptr; if (kwinApp()->operationMode() == Application::OperationModeX11) { c = new X11Client(); } else { c = new XwaylandClient(); } setupClientConnections(c); if (X11Compositor *compositor = X11Compositor::self()) { connect(c, &X11Client::blockingCompositingChanged, compositor, &X11Compositor::updateClientCompositeBlocking); } connect(c, SIGNAL(clientFullScreenSet(KWin::X11Client *,bool,bool)), ScreenEdges::self(), SIGNAL(checkBlocking())); if (!c->manage(w, is_mapped)) { X11Client::deleteClient(c); return nullptr; } addClient(c); return c; } Unmanaged* Workspace::createUnmanaged(xcb_window_t w) { if (X11Compositor *compositor = X11Compositor::self()) { if (compositor->checkForOverlayWindow(w)) { return nullptr; } } Unmanaged* c = new Unmanaged(); if (!c->track(w)) { Unmanaged::deleteUnmanaged(c); return nullptr; } connect(c, &Unmanaged::needsRepaint, m_compositor, &Compositor::scheduleRepaint); addUnmanaged(c); emit unmanagedAdded(c); return c; } void Workspace::addClient(X11Client *c) { Group* grp = findGroup(c->window()); emit clientAdded(c); if (grp != nullptr) grp->gotLeader(c); if (c->isDesktop()) { - desktops.append(c); if (active_client == nullptr && 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); - m_allClients.append(c); } + clients.append(c); + 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(); 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() == nullptr && 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); updateTabbox(); } void Workspace::addUnmanaged(Unmanaged* c) { unmanaged.append(c); markXStackingOrderAsDirty(); } /** * Destroys the client \a c */ void Workspace::removeClient(X11Client *c) { if (c == active_popup_client) closeActivePopup(); if (m_userActionsMenu->isMenuClient(c)) { m_userActionsMenu->close(); } 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 } - Q_ASSERT(clients.contains(c) || desktops.contains(c)); + Q_ASSERT(clients.contains(c)); // TODO: if marked client is removed, notify the marked list clients.removeAll(c); m_allClients.removeAll(c); - desktops.removeAll(c); markXStackingOrderAsDirty(); attention_chain.removeAll(c); Group* group = findGroup(c->window()); if (group != nullptr) group->lostLeader(); if (c == most_recently_raised) most_recently_raised = nullptr; should_get_focus.removeAll(c); Q_ASSERT(c != active_client); if (c == last_active_client) last_active_client = nullptr; if (c == delayfocus_client) cancelDelayFocus(); emit clientRemoved(c); updateStackingOrder(true); updateClientArea(); updateTabbox(); } void Workspace::removeUnmanaged(Unmanaged* c) { Q_ASSERT(unmanaged.contains(c)); unmanaged.removeAll(c); emit unmanagedRemoved(c); markXStackingOrderAsDirty(); } void Workspace::addDeleted(Deleted* c, Toplevel *orig) { Q_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); } markXStackingOrderAsDirty(); connect(c, &Deleted::needsRepaint, m_compositor, &Compositor::scheduleRepaint); } void Workspace::removeDeleted(Deleted* c) { Q_ASSERT(deleted.contains(c)); emit deletedRemoved(c); deleted.removeAll(c); unconstrained_stacking_order.removeAll(c); stacking_order.removeAll(c); markXStackingOrderAsDirty(); if (!c->wasClient()) { return; } if (X11Compositor *compositor = X11Compositor::self()) { compositor->updateClientCompositeBlocking(); } } void Workspace::addShellClient(AbstractClient *client) { setupClientConnections(client); client->updateDecoration(false); updateClientLayer(client); const QRect area = clientArea(PlacementArea, Screens::self()->current(), client->desktop()); bool placementDone = false; if (client->isInitialPositionSet()) { placementDone = true; } if (client->isFullScreen()) { placementDone = true; } if (client->maximizeMode() == MaximizeMode::MaximizeFull) { placementDone = true; } if (client->rules()->checkPosition(invalidPoint, true) != invalidPoint) { placementDone = true; } if (!placementDone) { client->placeIn(area); } m_allClients.append(client); if (!unconstrained_stacking_order.contains(client)) { unconstrained_stacking_order.append(client); // Raise if it hasn't got any stacking position yet } if (!stacking_order.contains(client)) { // It'll be updated later, and updateToolWindows() requires stacking_order.append(client); // client to be in stacking_order } markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); if (client->wantsInput() && !client->isMinimized()) { activateClient(client); } updateTabbox(); connect(client, &AbstractClient::windowShown, this, [this, client] { updateClientLayer(client); // TODO: when else should we send the client through placement? if (client->hasTransientPlacementHint()) { const QRect area = clientArea(PlacementArea, Screens::self()->current(), client->desktop()); client->placeIn(area); } markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); if (client->wantsInput()) { activateClient(client); } }); connect(client, &AbstractClient::windowHidden, this, [this] { // TODO: update tabbox if it's displayed markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); }); } void Workspace::removeShellClient(AbstractClient *client) { m_allClients.removeAll(client); if (client == most_recently_raised) { most_recently_raised = nullptr; } if (client == delayfocus_client) { cancelDelayFocus(); } if (client == last_active_client) { last_active_client = nullptr; } if (client_keys_client == client) { setupWindowShortcutDone(false); } if (!client->shortcut().isEmpty()) { client->setShortcut(QString()); // Remove from client_keys } clientHidden(client); emit clientRemoved(client); markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); updateTabbox(); } 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 (auto it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->hideClient(false); return; } 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) { 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? QVector to_show, to_hide; for (auto it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { auto c = qobject_cast(*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 != nullptr && c->group() == client->group()) show = true; else show = false; } else { if (group != nullptr && c->group() == group) show = true; else if (client != nullptr && client->hasTransient(c, true)) show = true; else show = false; } if (!show && also_hide) { const auto mainclients = c->mainClients(); // Don't hide utility windows which are standalone(?) or // have e.g. kicker as mainwindow if (mainclients.isEmpty()) show = true; for (auto it2 = mainclients.constBegin(); it2 != mainclients.constEnd(); ++it2) { if ((*it2)->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 (auto 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); } void Workspace::slotReloadConfig() { reconfigure(); } void Workspace::reconfigure() { reconfigureTimer.start(200); } /** * Reread settings */ void Workspace::slotReconfigure() { qCDebug(KWIN_CORE) << "Workspace::slotReconfigure()"; reconfigureTimer.stop(); bool borderlessMaximizedWindows = options->borderlessMaximizedWindows(); kwinApp()->config()->reparseConfiguration(); options->updateSettings(); emit configChanged(); m_userActionsMenu->discard(); updateToolWindows(true); RuleBook::self()->load(); for (auto it = m_allClients.begin(); it != m_allClients.end(); ++it) { (*it)->setupWindowRules(true); (*it)->applyWindowRules(); RuleBook::self()->discardUsed(*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 (auto it = m_allClients.begin(); it != m_allClients.end(); ++it) { if ((*it)->maximizeMode() == MaximizeFull) (*it)->checkNoBorder(); } } } void Workspace::slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop) { closeActivePopup(); ++block_focus; 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 (auto it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { X11Client *c = qobject_cast(*it); if (!c) { continue; } if (!c->isOnDesktop(newDesktop) && c != movingClient && c->isOnCurrentActivity()) { (c)->updateVisibility(); } } // 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) { X11Client *c = qobject_cast(stacking_order.at(i)); 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 = nullptr; 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 ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop()) c = active_client; - if (c == nullptr && !desktops.isEmpty()) + if (!c) c = findDesktop(true, desktop); if (c != active_client) setActiveClient(nullptr); if (c) requestFocus(c); - else if (!desktops.isEmpty()) - requestFocus(findDesktop(true, desktop)); else focusToNull(); } AbstractClient *Workspace::findClientToActivateOnDesktop(uint desktop) { if (movingClient != nullptr && 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()) { auto it = stackingOrder().constEnd(); while (it != stackingOrder().constBegin()) { X11Client *client = qobject_cast(*(--it)); if (!client) { continue; } if (!(client->isShown(false) && client->isOnDesktop(desktop) && client->isOnCurrentActivity() && client->isOnActiveScreen())) continue; if (client->frameGeometry().contains(Cursors::self()->mouse()->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 if (!Activities::self()) { return; } //closeActivePopup(); ++block_focus; // TODO: Q_ASSERT( block_stacking_updates == 0 ); // Make sure stacking_order is up to date 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 (auto it = stacking_order.constBegin(); it != stacking_order.constEnd(); ++it) { X11Client *c = qobject_cast(*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) { X11Client *c = qobject_cast(stacking_order.at(i)); if (!c) { continue; } if (c->isOnActivity(new_activity)) c->updateVisibility(); } //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 = nullptr; //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 ) else if (active_client && active_client->isShown(true) && active_client->isOnCurrentDesktop() && active_client->isOnCurrentActivity()) c = active_client; - if (c == nullptr && !desktops.isEmpty()) + if (!c) c = findDesktop(true, VirtualDesktopManager::self()->current()); if (c != active_client) setActiveClient(nullptr); 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( effects )->desktopChanged( old_desktop ); if (compositing() && m_compositor) m_compositor->addRepaintFull(); #else Q_UNUSED(new_activity) #endif } 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); } 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) { if ((desk < 1 && desk != NET::OnAllDesktops) || desk > static_cast(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 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 ); 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(); } /** * 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) { c->sendToScreen(screen); } /** * Delayed focus functions */ void Workspace::delayFocus() { requestFocus(delayfocus_client); cancelDelayFocus(); } void Workspace::requestDelayFocus(AbstractClient* 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 = nullptr; } bool Workspace::checkStartupNotification(xcb_window_t 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() { if (m_nullFocus) { m_nullFocus->focus(); } } 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(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 (X11Client *cm, group->members()) { cm->updateLayer(); } } } } } } // ~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); } 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() << disable); QDBusConnection::sessionBus().asyncCall(message); global_shortcuts_disabled_for_client = disable; // Update also Alt+LMB actions etc. for (auto it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->updateMouseGrab(); } 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" "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); #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; ipropertyCount(); ++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; ipropertyCount(); ++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; icount(); ++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(effects)->loadedEffects()) { support.append(effect + QStringLiteral("\n")); } support.append(QStringLiteral("\nCurrently Active Effects:\n")); support.append(QStringLiteral( "-------------------------\n")); foreach (const QString &effect, static_cast(effects)->activeEffects()) { support.append(effect + QStringLiteral("\n")); } support.append(QStringLiteral("\nEffect Settings:\n")); support.append(QStringLiteral( "----------------\n")); foreach (const QString &effect, static_cast(effects)->loadedEffects()) { support.append(static_cast(effects)->supportInformation(effect)); support.append(QStringLiteral("\n")); } } else { support.append(QStringLiteral("Compositing is not active\n")); } return support; } X11Client *Workspace::findClient(std::function func) const { if (X11Client *ret = Toplevel::findInList(clients, func)) { return ret; } - if (X11Client *ret = Toplevel::findInList(desktops, func)) { - return ret; - } return nullptr; } AbstractClient *Workspace::findAbstractClient(std::function func) const { if (AbstractClient *ret = Toplevel::findInList(m_allClients, func)) { return ret; } - if (X11Client *ret = Toplevel::findInList(desktops, func)) { - return ret; - } if (InternalClient *ret = Toplevel::findInList(m_internalClients, func)) { return ret; } return nullptr; } Unmanaged *Workspace::findUnmanaged(std::function 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; }); } X11Client *Workspace::findClient(Predicate predicate, xcb_window_t w) const { switch (predicate) { case Predicate::WindowMatch: return findClient([w](const X11Client *c) { return c->window() == w; }); case Predicate::WrapperIdMatch: return findClient([w](const X11Client *c) { return c->wrapperId() == w; }); case Predicate::FrameIdMatch: return findClient([w](const X11Client *c) { return c->frameId() == w; }); case Predicate::InputIdMatch: return findClient([w](const X11Client *c) { return c->inputId() == w; }); } return nullptr; } Toplevel *Workspace::findToplevel(std::function func) const { if (X11Client *ret = Toplevel::findInList(clients, func)) { return ret; } - if (X11Client *ret = Toplevel::findInList(desktops, func)) { - return ret; - } if (Unmanaged *ret = Toplevel::findInList(unmanaged, func)) { return ret; } if (InternalClient *ret = Toplevel::findInList(m_internalClients, func)) { return ret; } return nullptr; } void Workspace::forEachToplevel(std::function func) { std::for_each(m_allClients.constBegin(), m_allClients.constEnd(), func); - std::for_each(desktops.constBegin(), desktops.constEnd(), func); std::for_each(deleted.constBegin(), deleted.constEnd(), func); std::for_each(unmanaged.constBegin(), unmanaged.constEnd(), func); std::for_each(m_internalClients.constBegin(), m_internalClients.constEnd(), func); } bool Workspace::hasClient(const AbstractClient *c) { if (auto cc = dynamic_cast(c)) { return hasClient(cc); } else { return findAbstractClient([c](const AbstractClient *test) { return test == c; }) != nullptr; } return false; } 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); std::for_each(m_internalClients.constBegin(), m_internalClients.constEnd(), func); } Toplevel *Workspace::findInternal(QWindow *w) const { if (!w) { return nullptr; } if (kwinApp()->operationMode() == Application::OperationModeX11) { return findUnmanaged(w->winId()); } for (InternalClient *client : m_internalClients) { if (client->internalWindow() == w) { return client; } } return nullptr; } 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->isDisplayed()) { tabBox->reset(true); } #endif } void Workspace::addInternalClient(InternalClient *client) { m_internalClients.append(client); setupClientConnections(client); client->updateLayer(); if (client->isDecorated()) { client->keepInArea(clientArea(FullScreenArea, client)); } markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); emit internalClientAdded(client); } void Workspace::removeInternalClient(InternalClient *client) { m_internalClients.removeOne(client); markXStackingOrderAsDirty(); updateStackingOrder(true); updateClientArea(); emit internalClientRemoved(client); } Group* Workspace::findGroup(xcb_window_t leader) const { Q_ASSERT(leader != XCB_WINDOW_NONE); for (auto it = groups.constBegin(); it != groups.constEnd(); ++it) if ((*it)->leader() == leader) return *it; return nullptr; } // Client is group transient, but has no group set. Try to find // group with windows with the same client leader. Group* Workspace::findClientLeaderGroup(const X11Client *c) const { Group* ret = nullptr; for (auto it = clients.constBegin(); it != clients.constEnd(); ++it) { if (*it == c) continue; if ((*it)->wmClientLeader() == c->wmClientLeader()) { if (ret == nullptr || ret == (*it)->group()) ret = (*it)->group(); else { // There are already two groups with the same client leader. // This most probably means the app uses group transients without // setting group for its windows. Merging the two groups is a bad // hack, but there's no really good solution for this case. QList old_group = (*it)->group()->members(); // old_group autodeletes when being empty for (int pos = 0; pos < old_group.count(); ++pos) { X11Client *tmp = old_group[ pos ]; if (tmp != c) tmp->changeClientLeaderGroup(ret); } } } } return ret; } void Workspace::updateMinimizedOfTransients(AbstractClient* c) { // if mainwindow is minimized or shaded, minimize transients too if (c->isMinimized()) { for (auto it = c->transients().constBegin(); it != c->transients().constEnd(); ++it) { if ((*it)->isModal()) continue; // there's no reason to hide modal dialogs with the main client // but to keep them to eg. watch progress or whatever if (!(*it)->isMinimized()) { (*it)->minimize(); updateMinimizedOfTransients((*it)); } } if (c->isModal()) { // if a modal dialog is minimized, minimize its mainwindow too foreach (AbstractClient * c2, c->mainClients()) c2->minimize(); } } else { // else unmiminize the transients for (auto it = c->transients().constBegin(); it != c->transients().constEnd(); ++it) { if ((*it)->isMinimized()) { (*it)->unminimize(); updateMinimizedOfTransients((*it)); } } if (c->isModal()) { foreach (AbstractClient * c2, c->mainClients()) c2->unminimize(); } } } /** * Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops. */ void Workspace::updateOnAllDesktopsOfTransients(AbstractClient* c) { for (auto it = c->transients().constBegin(); it != c->transients().constEnd(); ++it) { if ((*it)->isOnAllDesktops() != c->isOnAllDesktops()) (*it)->setOnAllDesktops(c->isOnAllDesktops()); } } // A new window has been mapped. Check if it's not a mainwindow for some already existing transient window. void Workspace::checkTransients(xcb_window_t w) { for (auto it = clients.constBegin(); it != clients.constEnd(); ++it) (*it)->checkTransient(w); } /** * Resizes the workspace after an XRANDR screen size change */ void Workspace::desktopResized() { QRect geom = screens()->geometry(); if (rootInfo()) { NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo()->setDesktopGeometry(desktop_geometry); } updateClientArea(); saveOldScreenSizes(); // after updateClientArea(), so that one still uses the previous one // TODO: emit a signal instead and remove the deep function calls into edges and effects ScreenEdges::self()->recreateEdges(); if (effects) { static_cast(effects)->desktopResized(geom.size()); } } void Workspace::saveOldScreenSizes() { olddisplaysize = screens()->displaySize(); oldscreensizes.clear(); for( int i = 0; i < screens()->count(); ++i ) oldscreensizes.append( screens()->geometry( i )); } /** * Updates the current client areas according to the current clients. * * If the area changes or force is @c true, the new areas are propagated to the world. * * The client area is the area that is available for clients (that * which is not taken by windows like panels, the top-of-screen menu * etc). * * @see clientArea() */ void Workspace::updateClientArea(bool force) { const Screens *s = Screens::self(); int nscreens = s->count(); const int numberOfDesktops = VirtualDesktopManager::self()->count(); QVector< QRect > new_wareas(numberOfDesktops + 1); QVector< StrutRects > new_rmoveareas(numberOfDesktops + 1); QVector< QVector< QRect > > new_sareas(numberOfDesktops + 1); QVector< QRect > screens(nscreens); QRect desktopArea; for (int i = 0; i < nscreens; i++) { desktopArea |= s->geometry(i); } for (int iS = 0; iS < nscreens; iS ++) { screens [iS] = s->geometry(iS); } for (int i = 1; i <= numberOfDesktops; ++i) { new_wareas[ i ] = desktopArea; new_sareas[ i ].resize(nscreens); for (int iS = 0; iS < nscreens; iS ++) new_sareas[ i ][ iS ] = screens[ iS ]; } for (auto it = clients.constBegin(); it != clients.constEnd(); ++it) { if (!(*it)->hasStrut()) continue; QRect r = (*it)->adjustedClientArea(desktopArea, desktopArea); // sanity check that a strut doesn't exclude a complete screen geometry // this is a violation to EWMH, as KWin just ignores the strut for (int i = 0; i < Screens::self()->count(); i++) { if (!r.intersects(Screens::self()->geometry(i))) { qCDebug(KWIN_CORE) << "Adjusted client area would exclude a complete screen, ignore"; r = desktopArea; break; } } StrutRects strutRegion = (*it)->strutRects(); const QRect clientsScreenRect = KWin::screens()->geometry((*it)->screen()); for (auto strut = strutRegion.begin(); strut != strutRegion.end(); strut++) { *strut = StrutRect((*strut).intersected(clientsScreenRect), (*strut).area()); } // Ignore offscreen xinerama struts. These interfere with the larger monitors on the setup // and should be ignored so that applications that use the work area to work out where // windows can go can use the entire visible area of the larger monitors. // This goes against the EWMH description of the work area but it is a toss up between // having unusable sections of the screen (Which can be quite large with newer monitors) // or having some content appear offscreen (Relatively rare compared to other). bool hasOffscreenXineramaStrut = (*it)->hasOffscreenXineramaStrut(); if ((*it)->isOnAllDesktops()) { for (int i = 1; i <= numberOfDesktops; ++i) { if (!hasOffscreenXineramaStrut) new_wareas[ i ] = new_wareas[ i ].intersected(r); new_rmoveareas[ i ] += strutRegion; for (int iS = 0; iS < nscreens; iS ++) { const auto geo = new_sareas[ i ][ iS ].intersected( (*it)->adjustedClientArea(desktopArea, screens[ iS ])); // ignore the geometry if it results in the screen getting removed completely if (!geo.isEmpty()) { new_sareas[ i ][ iS ] = geo; } } } } else { if (!hasOffscreenXineramaStrut) new_wareas[(*it)->desktop()] = new_wareas[(*it)->desktop()].intersected(r); new_rmoveareas[(*it)->desktop()] += strutRegion; for (int iS = 0; iS < nscreens; iS ++) { // qDebug() << "adjusting new_sarea: " << screens[ iS ]; const auto geo = new_sareas[(*it)->desktop()][ iS ].intersected( (*it)->adjustedClientArea(desktopArea, screens[ iS ])); // ignore the geometry if it results in the screen getting removed completely if (!geo.isEmpty()) { new_sareas[(*it)->desktop()][ iS ] = geo; } } } } if (waylandServer()) { auto updateStrutsForWaylandClient = [&] (AbstractClient *c) { // assuming that only docks have "struts" and that all docks have a strut if (!c->hasStrut()) { return; } auto margins = [c] (const QRect &geometry) { QMargins margins; if (!geometry.intersects(c->frameGeometry())) { return margins; } // figure out which areas of the overall screen setup it borders const bool left = c->frameGeometry().left() == geometry.left(); const bool right = c->frameGeometry().right() == geometry.right(); const bool top = c->frameGeometry().top() == geometry.top(); const bool bottom = c->frameGeometry().bottom() == geometry.bottom(); const bool horizontal = c->frameGeometry().width() >= c->frameGeometry().height(); if (left && ((!top && !bottom) || !horizontal)) { margins.setLeft(c->frameGeometry().width()); } if (right && ((!top && !bottom) || !horizontal)) { margins.setRight(c->frameGeometry().width()); } if (top && ((!left && !right) || horizontal)) { margins.setTop(c->frameGeometry().height()); } if (bottom && ((!left && !right) || horizontal)) { margins.setBottom(c->frameGeometry().height()); } return margins; }; auto marginsToStrutArea = [] (const QMargins &margins) { if (margins.left() != 0) { return StrutAreaLeft; } if (margins.right() != 0) { return StrutAreaRight; } if (margins.top() != 0) { return StrutAreaTop; } if (margins.bottom() != 0) { return StrutAreaBottom; } return StrutAreaInvalid; }; const auto strut = margins(KWin::screens()->geometry(c->screen())); const StrutRects strutRegion = StrutRects{StrutRect(c->frameGeometry(), marginsToStrutArea(strut))}; QRect r = desktopArea - margins(KWin::screens()->geometry()); if (c->isOnAllDesktops()) { for (int i = 1; i <= numberOfDesktops; ++i) { new_wareas[ i ] = new_wareas[ i ].intersected(r); for (int iS = 0; iS < nscreens; ++iS) { new_sareas[ i ][ iS ] = new_sareas[ i ][ iS ].intersected(screens[iS] - margins(screens[iS])); } new_rmoveareas[ i ] += strutRegion; } } else { new_wareas[c->desktop()] = new_wareas[c->desktop()].intersected(r); for (int iS = 0; iS < nscreens; iS++) { new_sareas[c->desktop()][ iS ] = new_sareas[c->desktop()][ iS ].intersected(screens[iS] - margins(screens[iS])); } new_rmoveareas[ c->desktop() ] += strutRegion; } }; const auto clients = waylandServer()->clients(); for (auto c : clients) { updateStrutsForWaylandClient(c); } } #if 0 for (int i = 1; i <= numberOfDesktops(); ++i) { for (int iS = 0; iS < nscreens; iS ++) qCDebug(KWIN_CORE) << "new_sarea: " << new_sareas[ i ][ iS ]; } #endif bool changed = force; if (screenarea.isEmpty()) changed = true; for (int i = 1; !changed && i <= numberOfDesktops; ++i) { if (workarea[ i ] != new_wareas[ i ]) changed = true; if (restrictedmovearea[ i ] != new_rmoveareas[ i ]) changed = true; if (screenarea[ i ].size() != new_sareas[ i ].size()) changed = true; for (int iS = 0; !changed && iS < nscreens; iS ++) if (new_sareas[ i ][ iS ] != screenarea [ i ][ iS ]) changed = true; } if (changed) { workarea = new_wareas; oldrestrictedmovearea = restrictedmovearea; restrictedmovearea = new_rmoveareas; screenarea = new_sareas; if (rootInfo()) { NETRect r; for (int i = 1; i <= numberOfDesktops; i++) { r.pos.x = workarea[ i ].x(); r.pos.y = workarea[ i ].y(); r.size.width = workarea[ i ].width(); r.size.height = workarea[ i ].height(); rootInfo()->setWorkArea(i, r); } } for (auto it = m_allClients.constBegin(); it != m_allClients.constEnd(); ++it) (*it)->checkWorkspacePosition(); oldrestrictedmovearea.clear(); // reset, no longer valid or needed } } void Workspace::updateClientArea() { updateClientArea(false); } /** * Returns the area available for clients. This is the desktop * geometry minus windows on the dock. Placement algorithms should * refer to this rather than Screens::geometry. */ QRect Workspace::clientArea(clientAreaOption opt, int screen, int desktop) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = VirtualDesktopManager::self()->current(); if (screen == -1) screen = screens()->current(); const QSize displaySize = screens()->displaySize(); QRect sarea, warea; if (is_multihead) { sarea = (!screenarea.isEmpty() && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes ? screenarea[ desktop ][ screen_number ] : screens()->geometry(screen_number); warea = workarea[ desktop ].isNull() ? screens()->geometry(screen_number) : workarea[ desktop ]; } else { sarea = (!screenarea.isEmpty() && screen < screenarea[ desktop ].size()) // screens may be missing during KWin initialization or screen config changes ? screenarea[ desktop ][ screen ] : screens()->geometry(screen); warea = workarea[ desktop ].isNull() ? QRect(0, 0, displaySize.width(), displaySize.height()) : workarea[ desktop ]; } switch(opt) { case MaximizeArea: case PlacementArea: return sarea; case MaximizeFullArea: case FullScreenArea: case MovementArea: case ScreenArea: if (is_multihead) return screens()->geometry(screen_number); else return screens()->geometry(screen); case WorkArea: if (is_multihead) return sarea; else return warea; case FullArea: return QRect(0, 0, displaySize.width(), displaySize.height()); } abort(); } QRect Workspace::clientArea(clientAreaOption opt, const QPoint& p, int desktop) const { return clientArea(opt, screens()->number(p), desktop); } QRect Workspace::clientArea(clientAreaOption opt, const AbstractClient* c) const { return clientArea(opt, c->frameGeometry().center(), c->desktop()); } QRegion Workspace::restrictedMoveArea(int desktop, StrutAreas areas) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = VirtualDesktopManager::self()->current(); QRegion region; foreach (const StrutRect & rect, restrictedmovearea[desktop]) if (areas & rect.area()) region += rect; return region; } bool Workspace::inUpdateClientArea() const { return !oldrestrictedmovearea.isEmpty(); } QRegion Workspace::previousRestrictedMoveArea(int desktop, StrutAreas areas) const { if (desktop == NETWinInfo::OnAllDesktops || desktop == 0) desktop = VirtualDesktopManager::self()->current(); QRegion region; foreach (const StrutRect & rect, oldrestrictedmovearea.at(desktop)) if (areas & rect.area()) region += rect; return region; } QVector< QRect > Workspace::previousScreenSizes() const { return oldscreensizes; } int Workspace::oldDisplayWidth() const { return olddisplaysize.width(); } int Workspace::oldDisplayHeight() const { return olddisplaysize.height(); } /** * Client \a c is moved around to position \a pos. This gives the * workspace the opportunity to interveniate and to implement * snap-to-windows functionality. * * The parameter \a snapAdjust is a multiplier used to calculate the * effective snap zones. When 1.0, it means that the snap zones will be * used without change. */ QPoint Workspace::adjustClientPosition(AbstractClient* c, QPoint pos, bool unrestricted, double snapAdjust) { QSize borderSnapZone(options->borderSnapZone(), options->borderSnapZone()); QRect maxRect; int guideMaximized = MaximizeRestore; if (c->maximizeMode() != MaximizeRestore) { maxRect = clientArea(MaximizeArea, pos + c->rect().center(), c->desktop()); QRect geo = c->frameGeometry(); if (c->maximizeMode() & MaximizeHorizontal && (geo.x() == maxRect.left() || geo.right() == maxRect.right())) { guideMaximized |= MaximizeHorizontal; borderSnapZone.setWidth(qMax(borderSnapZone.width() + 2, maxRect.width() / 16)); } if (c->maximizeMode() & MaximizeVertical && (geo.y() == maxRect.top() || geo.bottom() == maxRect.bottom())) { guideMaximized |= MaximizeVertical; borderSnapZone.setHeight(qMax(borderSnapZone.height() + 2, maxRect.height() / 16)); } } if (options->windowSnapZone() || !borderSnapZone.isNull() || options->centerSnapZone()) { const bool sOWO = options->isSnapOnlyWhenOverlapping(); const int screen = screens()->number(pos + c->rect().center()); if (maxRect.isNull()) maxRect = clientArea(MovementArea, screen, c->desktop()); const int xmin = maxRect.left(); const int xmax = maxRect.right() + 1; //desk size const int ymin = maxRect.top(); const int ymax = maxRect.bottom() + 1; const int cx(pos.x()); const int cy(pos.y()); const int cw(c->width()); const int ch(c->height()); const int rx(cx + cw); const int ry(cy + ch); //these don't change int nx(cx), ny(cy); //buffers int deltaX(xmax); int deltaY(ymax); //minimum distance to other clients int lx, ly, lrx, lry; //coords and size for the comparison client, l // border snap const int snapX = borderSnapZone.width() * snapAdjust; //snap trigger const int snapY = borderSnapZone.height() * snapAdjust; if (snapX || snapY) { QRect geo = c->frameGeometry(); QMargins frameMargins = c->frameMargins(); // snap to titlebar / snap to window borders on inner screen edges AbstractClient::Position titlePos = c->titlebarPosition(); if (frameMargins.left() && (titlePos == AbstractClient::PositionLeft || (c->maximizeMode() & MaximizeHorizontal) || screens()->intersecting(geo.translated(maxRect.x() - (frameMargins.left() + geo.x()), 0)) > 1)) { frameMargins.setLeft(0); } if (frameMargins.right() && (titlePos == AbstractClient::PositionRight || (c->maximizeMode() & MaximizeHorizontal) || screens()->intersecting(geo.translated(maxRect.right() + frameMargins.right() - geo.right(), 0)) > 1)) { frameMargins.setRight(0); } if (frameMargins.top() && (titlePos == AbstractClient::PositionTop || (c->maximizeMode() & MaximizeVertical) || screens()->intersecting(geo.translated(0, maxRect.y() - (frameMargins.top() + geo.y()))) > 1)) { frameMargins.setTop(0); } if (frameMargins.bottom() && (titlePos == AbstractClient::PositionBottom || (c->maximizeMode() & MaximizeVertical) || screens()->intersecting(geo.translated(0, maxRect.bottom() + frameMargins.bottom() - geo.bottom())) > 1)) { frameMargins.setBottom(0); } if ((sOWO ? (cx < xmin) : true) && (qAbs(xmin - cx) < snapX)) { deltaX = xmin - cx; nx = xmin - frameMargins.left(); } if ((sOWO ? (rx > xmax) : true) && (qAbs(rx - xmax) < snapX) && (qAbs(xmax - rx) < deltaX)) { deltaX = rx - xmax; nx = xmax - cw + frameMargins.right(); } if ((sOWO ? (cy < ymin) : true) && (qAbs(ymin - cy) < snapY)) { deltaY = ymin - cy; ny = ymin - frameMargins.top(); } if ((sOWO ? (ry > ymax) : true) && (qAbs(ry - ymax) < snapY) && (qAbs(ymax - ry) < deltaY)) { deltaY = ry - ymax; ny = ymax - ch + frameMargins.bottom(); } } // windows snap int snap = options->windowSnapZone() * snapAdjust; if (snap) { for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) { if ((*l) == c) continue; if ((*l)->isMinimized()) continue; // is minimized if (!(*l)->isShown(false)) continue; if (!((*l)->isOnDesktop(c->desktop()) || c->isOnDesktop((*l)->desktop()))) continue; // wrong virtual desktop if (!(*l)->isOnCurrentActivity()) continue; // wrong activity if ((*l)->isDesktop() || (*l)->isSplash()) continue; lx = (*l)->x(); ly = (*l)->y(); lrx = lx + (*l)->width(); lry = ly + (*l)->height(); if (!(guideMaximized & MaximizeHorizontal) && (((cy <= lry) && (cy >= ly)) || ((ry >= ly) && (ry <= lry)) || ((cy <= ly) && (ry >= lry)))) { if ((sOWO ? (cx < lrx) : true) && (qAbs(lrx - cx) < snap) && (qAbs(lrx - cx) < deltaX)) { deltaX = qAbs(lrx - cx); nx = lrx; } if ((sOWO ? (rx > lx) : true) && (qAbs(rx - lx) < snap) && (qAbs(rx - lx) < deltaX)) { deltaX = qAbs(rx - lx); nx = lx - cw; } } if (!(guideMaximized & MaximizeVertical) && (((cx <= lrx) && (cx >= lx)) || ((rx >= lx) && (rx <= lrx)) || ((cx <= lx) && (rx >= lrx)))) { if ((sOWO ? (cy < lry) : true) && (qAbs(lry - cy) < snap) && (qAbs(lry - cy) < deltaY)) { deltaY = qAbs(lry - cy); ny = lry; } //if ( (qAbs( ry-ly ) < snap) && (qAbs( ry - ly ) < deltaY )) if ((sOWO ? (ry > ly) : true) && (qAbs(ry - ly) < snap) && (qAbs(ry - ly) < deltaY)) { deltaY = qAbs(ry - ly); ny = ly - ch; } } // Corner snapping if (!(guideMaximized & MaximizeVertical) && (nx == lrx || nx + cw == lx)) { if ((sOWO ? (ry > lry) : true) && (qAbs(lry - ry) < snap) && (qAbs(lry - ry) < deltaY)) { deltaY = qAbs(lry - ry); ny = lry - ch; } if ((sOWO ? (cy < ly) : true) && (qAbs(cy - ly) < snap) && (qAbs(cy - ly) < deltaY)) { deltaY = qAbs(cy - ly); ny = ly; } } if (!(guideMaximized & MaximizeHorizontal) && (ny == lry || ny + ch == ly)) { if ((sOWO ? (rx > lrx) : true) && (qAbs(lrx - rx) < snap) && (qAbs(lrx - rx) < deltaX)) { deltaX = qAbs(lrx - rx); nx = lrx - cw; } if ((sOWO ? (cx < lx) : true) && (qAbs(cx - lx) < snap) && (qAbs(cx - lx) < deltaX)) { deltaX = qAbs(cx - lx); nx = lx; } } } } // center snap snap = options->centerSnapZone() * snapAdjust; //snap trigger if (snap) { int diffX = qAbs((xmin + xmax) / 2 - (cx + cw / 2)); int diffY = qAbs((ymin + ymax) / 2 - (cy + ch / 2)); if (diffX < snap && diffY < snap && diffX < deltaX && diffY < deltaY) { // Snap to center of screen nx = (xmin + xmax) / 2 - cw / 2; ny = (ymin + ymax) / 2 - ch / 2; } else if (options->borderSnapZone()) { // Enhance border snap if ((nx == xmin || nx == xmax - cw) && diffY < snap && diffY < deltaY) { // Snap to vertical center on screen edge ny = (ymin + ymax) / 2 - ch / 2; } else if (((unrestricted ? ny == ymin : ny <= ymin) || ny == ymax - ch) && diffX < snap && diffX < deltaX) { // Snap to horizontal center on screen edge nx = (xmin + xmax) / 2 - cw / 2; } } } pos = QPoint(nx, ny); } return pos; } QRect Workspace::adjustClientSize(AbstractClient* c, QRect moveResizeGeom, int mode) { //adapted from adjustClientPosition on 29May2004 //this function is called when resizing a window and will modify //the new dimensions to snap to other windows/borders if appropriate if (options->windowSnapZone() || options->borderSnapZone()) { // || options->centerSnapZone ) const bool sOWO = options->isSnapOnlyWhenOverlapping(); const QRect maxRect = clientArea(MovementArea, c->rect().center(), c->desktop()); const int xmin = maxRect.left(); const int xmax = maxRect.right(); //desk size const int ymin = maxRect.top(); const int ymax = maxRect.bottom(); const int cx(moveResizeGeom.left()); const int cy(moveResizeGeom.top()); const int rx(moveResizeGeom.right()); const int ry(moveResizeGeom.bottom()); int newcx(cx), newcy(cy); //buffers int newrx(rx), newry(ry); int deltaX(xmax); int deltaY(ymax); //minimum distance to other clients int lx, ly, lrx, lry; //coords and size for the comparison client, l // border snap int snap = options->borderSnapZone(); //snap trigger if (snap) { deltaX = int(snap); deltaY = int(snap); #define SNAP_BORDER_TOP \ if ((sOWO?(newcyymax):true) && (qAbs(ymax-newry)xmax):true) && (qAbs(xmax-newrx)windowSnapZone(); if (snap) { deltaX = int(snap); deltaY = int(snap); for (auto l = m_allClients.constBegin(); l != m_allClients.constEnd(); ++l) { if ((*l)->isOnDesktop(VirtualDesktopManager::self()->current()) && !(*l)->isMinimized() && (*l) != c) { lx = (*l)->x() - 1; ly = (*l)->y() - 1; lrx = (*l)->x() + (*l)->width(); lry = (*l)->y() + (*l)->height(); #define WITHIN_HEIGHT ((( newcy <= lry ) && ( newcy >= ly )) || \ (( newry >= ly ) && ( newry <= lry )) || \ (( newcy <= ly ) && ( newry >= lry )) ) #define WITHIN_WIDTH ( (( cx <= lrx ) && ( cx >= lx )) || \ (( rx >= lx ) && ( rx <= lrx )) || \ (( cx <= lx ) && ( rx >= lrx )) ) #define SNAP_WINDOW_TOP if ( (sOWO?(newcyly):true) \ && WITHIN_WIDTH \ && (qAbs( ly - newry ) < deltaY) ) { \ deltaY = qAbs( ly - newry ); \ newry=ly; \ } #define SNAP_WINDOW_LEFT if ( (sOWO?(newcxlx):true) \ && WITHIN_HEIGHT \ && (qAbs( lx - newrx ) < deltaX)) \ { \ deltaX = qAbs( lx - newrx ); \ newrx=lx; \ } #define SNAP_WINDOW_C_TOP if ( (sOWO?(newcylry):true) \ && (newcx == lrx || newrx == lx) \ && qAbs(lry-newry) < deltaY ) { \ deltaY = qAbs( lry - newry - 1 ); \ newry = lry - 1; \ } #define SNAP_WINDOW_C_LEFT if ( (sOWO?(newcxlrx):true) \ && (newcy == lry || newry == ly) \ && qAbs(lrx-newrx) < deltaX ) { \ deltaX = qAbs( lrx - newrx - 1 ); \ newrx = lrx - 1; \ } switch(mode) { case AbstractClient::PositionBottomRight: SNAP_WINDOW_BOTTOM SNAP_WINDOW_RIGHT SNAP_WINDOW_C_BOTTOM SNAP_WINDOW_C_RIGHT break; case AbstractClient::PositionRight: SNAP_WINDOW_RIGHT SNAP_WINDOW_C_RIGHT break; case AbstractClient::PositionBottom: SNAP_WINDOW_BOTTOM SNAP_WINDOW_C_BOTTOM break; case AbstractClient::PositionTopLeft: SNAP_WINDOW_TOP SNAP_WINDOW_LEFT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_LEFT break; case AbstractClient::PositionLeft: SNAP_WINDOW_LEFT SNAP_WINDOW_C_LEFT break; case AbstractClient::PositionTop: SNAP_WINDOW_TOP SNAP_WINDOW_C_TOP break; case AbstractClient::PositionTopRight: SNAP_WINDOW_TOP SNAP_WINDOW_RIGHT SNAP_WINDOW_C_TOP SNAP_WINDOW_C_RIGHT break; case AbstractClient::PositionBottomLeft: SNAP_WINDOW_BOTTOM SNAP_WINDOW_LEFT SNAP_WINDOW_C_BOTTOM SNAP_WINDOW_C_LEFT break; default: abort(); break; } } } } // center snap //snap = options->centerSnapZone; //if (snap) // { // // Don't resize snap to center as it interferes too much // // There are two ways of implementing this if wanted: // // 1) Snap only to the same points that the move snap does, and // // 2) Snap to the horizontal and vertical center lines of the screen // } moveResizeGeom = QRect(QPoint(newcx, newcy), QPoint(newrx, newry)); } return moveResizeGeom; } /** * Marks the client as being moved or resized by the user. */ void Workspace::setMoveResizeClient(AbstractClient *c) { Q_ASSERT(!c || !movingClient); // Catch attempts to move a second // window while still moving the first one. movingClient = c; if (movingClient) ++block_focus; else --block_focus; } // When kwin crashes, windows will not be gravitated back to their original position // and will remain offset by the size of the decoration. So when restarting, fix this // (the property with the size of the frame remains on the window after the crash). void Workspace::fixPositionAfterCrash(xcb_window_t w, const xcb_get_geometry_reply_t *geometry) { NETWinInfo i(connection(), w, rootWindow(), NET::WMFrameExtents, NET::Properties2()); NETStrut frame = i.frameExtents(); if (frame.left != 0 || frame.top != 0) { // left and top needed due to narrowing conversations restrictions in C++11 const uint32_t left = frame.left; const uint32_t top = frame.top; const uint32_t values[] = { geometry->x - left, geometry->y - top }; xcb_configure_window(connection(), w, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); } } } // namespace diff --git a/workspace.h b/workspace.h index edcc62ed8..831084e08 100644 --- a/workspace.h +++ b/workspace.h @@ -1,807 +1,799 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich Copyright (C) 2003 Lubos Lunak Copyright (C) 2009 Lucas Murray Copyright (C) 2019 Vlad Zahorodnii 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 . *********************************************************************/ #ifndef KWIN_WORKSPACE_H #define KWIN_WORKSPACE_H // kwin #include "options.h" #include "sm.h" #include "utils.h" // Qt #include #include // std #include #include class KConfig; class KConfigGroup; class KStartupInfo; class KStartupInfoData; class KStartupInfoId; class QStringList; namespace KWin { namespace Xcb { class Tree; class Window; } class AbstractClient; class Compositor; class Deleted; class Group; class InternalClient; class KillWindow; class ShortcutDialog; class Toplevel; class Unmanaged; class UserActionsMenu; class X11Client; class X11EventFilter; enum class Predicate; class KWIN_EXPORT Workspace : public QObject { Q_OBJECT public: explicit Workspace(); ~Workspace() override; static Workspace* self() { return _self; } bool workspaceEvent(xcb_generic_event_t*); bool workspaceEvent(QEvent*); bool hasClient(const X11Client *); bool hasClient(const AbstractClient*); /** * @brief Finds the first Client matching the condition expressed by passed in @p func. * * Internally findClient uses the std::find_if algorithm and that determines how the function * needs to be implemented. An example usage for finding a Client with a matching windowId * @code * xcb_window_t w; // our test window * X11Client *client = findClient([w](const X11Client *c) -> bool { * return c->window() == w; * }); * @endcode * * For the standard cases of matching the window id with one of the Client's windows use * the simplified overload method findClient(Predicate, xcb_window_t). Above example * can be simplified to: * @code * xcb_window_t w; // our test window * X11Client *client = findClient(Predicate::WindowMatch, w); * @endcode * * @param func Unary function that accepts a X11Client *as argument and * returns a value convertible to bool. The value returned indicates whether the * X11Client *is considered a match in the context of this function. * The function shall not modify its argument. * This can either be a function pointer or a function object. * @return KWin::X11Client *The found Client or @c null * @see findClient(Predicate, xcb_window_t) */ X11Client *findClient(std::function func) const; AbstractClient *findAbstractClient(std::function func) const; /** * @brief Finds the Client matching the given match @p predicate for the given window. * * @param predicate Which window should be compared * @param w The window id to test against * @return KWin::X11Client *The found Client or @c null * @see findClient(std::function) */ X11Client *findClient(Predicate predicate, xcb_window_t w) const; void forEachClient(std::function func); void forEachAbstractClient(std::function func); Unmanaged *findUnmanaged(std::function func) const; /** * @brief Finds the Unmanaged with the given window id. * * @param w The window id to search for * @return KWin::Unmanaged* Found Unmanaged or @c null if there is no Unmanaged with given Id. */ Unmanaged *findUnmanaged(xcb_window_t w) const; void forEachUnmanaged(std::function func); Toplevel *findToplevel(std::function func) const; void forEachToplevel(std::function func); /** * @brief Finds a Toplevel for the internal window @p w. * * Internal window means a window created by KWin itself. On X11 this is an Unmanaged * and mapped by the window id, on Wayland a XdgShellClient mapped on the internal window id. * * @returns Toplevel */ Toplevel *findInternal(QWindow *w) const; QRect clientArea(clientAreaOption, const QPoint& p, int desktop) const; QRect clientArea(clientAreaOption, const AbstractClient* c) const; QRect clientArea(clientAreaOption, int screen, int desktop) const; QRegion restrictedMoveArea(int desktop, StrutAreas areas = StrutAreaAll) const; bool initializing() const; /** * Returns the active client, i.e. the client that has the focus (or None * if no client has the focus) */ AbstractClient* activeClient() const; /** * Client that was activated, but it's not yet really activeClient(), because * we didn't process yet the matching FocusIn event. Used mostly in focus * stealing prevention code. */ AbstractClient* mostRecentlyActivatedClient() const; AbstractClient* clientUnderMouse(int screen) const; void activateClient(AbstractClient*, bool force = false); void requestFocus(AbstractClient* c, bool force = false); enum ActivityFlag { ActivityFocus = 1 << 0, // focus the window ActivityFocusForce = 1 << 1 | ActivityFocus, // focus even if Dock etc. ActivityRaise = 1 << 2 // raise the window }; Q_DECLARE_FLAGS(ActivityFlags, ActivityFlag) void takeActivity(AbstractClient* c, ActivityFlags flags); bool allowClientActivation(const AbstractClient* c, xcb_timestamp_t time = -1U, bool focus_in = false, bool ignore_desktop = false); void restoreFocus(); void gotFocusIn(const AbstractClient*); void setShouldGetFocus(AbstractClient*); bool activateNextClient(AbstractClient* c); bool focusChangeEnabled() { return block_focus == 0; } /** * Indicates that the client c is being moved or resized by the user. */ void setMoveResizeClient(AbstractClient* c); QPoint adjustClientPosition(AbstractClient* c, QPoint pos, bool unrestricted, double snapAdjust = 1.0); QRect adjustClientSize(AbstractClient* c, QRect moveResizeGeom, int mode); void raiseClient(AbstractClient* c, bool nogroup = false); void lowerClient(AbstractClient* c, bool nogroup = false); void raiseClientRequest(AbstractClient* c, NET::RequestSource src = NET::FromApplication, xcb_timestamp_t timestamp = 0); void lowerClientRequest(X11Client *c, NET::RequestSource src, xcb_timestamp_t timestamp); void lowerClientRequest(AbstractClient* c); void restackClientUnderActive(AbstractClient*); void restack(AbstractClient *c, AbstractClient *under, bool force = false); void updateClientLayer(AbstractClient* c); void raiseOrLowerClient(AbstractClient*); void resetUpdateToolWindowsTimer(); void restoreSessionStackingOrder(X11Client *c); void updateStackingOrder(bool propagate_new_clients = false); void forceRestacking(); void clientHidden(AbstractClient*); void clientAttentionChanged(AbstractClient* c, bool set); /** * @return List of clients currently managed by Workspace */ const QList &clientList() const { return clients; } /** * @return List of unmanaged "clients" currently registered in Workspace */ const QList &unmanagedList() const { return unmanaged; } - /** - * @return List of desktop "clients" currently managed by Workspace - */ - const QList &desktopList() const { - return desktops; - } /** * @return List of deleted "clients" currently managed by Workspace */ const QList &deletedList() const { return deleted; } /** * @returns List of all clients (either X11 or Wayland) currently managed by Workspace */ const QList allClientList() const { return m_allClients; } /** * @returns List of all internal clients currently managed by Workspace */ const QList &internalClients() const { return m_internalClients; } void stackScreenEdgesUnderOverrideRedirect(); SessionManager *sessionManager() const; public: QPoint cascadeOffset(const AbstractClient *c) const; private: Compositor *m_compositor; //------------------------------------------------- // Unsorted public: bool isOnCurrentHead(); // True when performing Workspace::updateClientArea(). // The calls below are valid only in that case. bool inUpdateClientArea() const; QRegion previousRestrictedMoveArea(int desktop, StrutAreas areas = StrutAreaAll) const; QVector< QRect > previousScreenSizes() const; int oldDisplayWidth() const; int oldDisplayHeight() const; /** * Returns the list of clients sorted in stacking order, with topmost client * at the last position */ const QList &stackingOrder() const; QList xStackingOrder() const; QList ensureStackingOrder(const QList &clients) const; QList ensureStackingOrder(const QList &clients) const; AbstractClient* topClientOnDesktop(int desktop, int screen, bool unconstrained = false, bool only_normal = true) const; AbstractClient* findDesktop(bool topmost, int desktop) const; void sendClientToDesktop(AbstractClient* c, int desktop, bool dont_activate); void windowToPreviousDesktop(AbstractClient* c); void windowToNextDesktop(AbstractClient* c); void sendClientToScreen(AbstractClient* c, int screen); void addManualOverlay(xcb_window_t id) { manual_overlays << id; } void removeManualOverlay(xcb_window_t id) { manual_overlays.removeOne(id); } /** * Shows the menu operations menu for the client and makes it active if * it's not already. */ void showWindowMenu(const QRect& pos, AbstractClient* cl); const UserActionsMenu *userActionsMenu() const { return m_userActionsMenu; } void showApplicationMenu(const QRect &pos, AbstractClient *c, int actionId); void updateMinimizedOfTransients(AbstractClient*); void updateOnAllDesktopsOfTransients(AbstractClient*); void checkTransients(xcb_window_t w); void storeSession(const QString &sessionName, SMSavePhase phase); void storeClient(KConfigGroup &cg, int num, X11Client *c); void storeSubSession(const QString &name, QSet sessionIds); void loadSubSessionInfo(const QString &name); SessionInfo* takeSessionInfo(X11Client *); // D-Bus interface QString supportInformation() const; void setCurrentScreen(int new_screen); void setShowingDesktop(bool showing); bool showingDesktop() const; void removeClient(X11Client *); // Only called from X11Client::destroyClient() or X11Client::releaseWindow() void setActiveClient(AbstractClient*); Group* findGroup(xcb_window_t leader) const; void addGroup(Group* group); void removeGroup(Group* group); Group* findClientLeaderGroup(const X11Client *c) const; void removeUnmanaged(Unmanaged*); // Only called from Unmanaged::release() void removeDeleted(Deleted*); void addDeleted(Deleted*, Toplevel*); bool checkStartupNotification(xcb_window_t w, KStartupInfoId& id, KStartupInfoData& data); void focusToNull(); // SELI TODO: Public? void clientShortcutUpdated(AbstractClient* c); bool shortcutAvailable(const QKeySequence &cut, AbstractClient* ignore = nullptr) const; bool globalShortcutsDisabled() const; void disableGlobalShortcutsForClient(bool disable); void setWasUserInteraction(); bool wasUserInteraction() const; int packPositionLeft(const AbstractClient *client, int oldX, bool leftEdge) const; int packPositionRight(const AbstractClient *client, int oldX, bool rightEdge) const; int packPositionUp(const AbstractClient *client, int oldY, bool topEdge) const; int packPositionDown(const AbstractClient *client, int oldY, bool bottomEdge) const; void cancelDelayFocus(); void requestDelayFocus(AbstractClient*); /** * updates the mouse position to track whether a focus follow mouse focus change was caused by * an actual mouse move * is esp. called on enter/motion events of inactive windows * since an active window doesn't receive mouse events, it must also be invoked if a (potentially) * active window might be moved/resize away from the cursor (causing a leave event) */ void updateFocusMousePosition(const QPoint& pos); QPoint focusMousePosition() const; /** * Returns a client that is currently being moved or resized by the user. * * If none of clients is being moved or resized, @c null will be returned. */ AbstractClient* moveResizeClient() { return movingClient; } /** * @returns Whether we have a Compositor and it is active (Scene created) */ bool compositing() const; void registerEventFilter(X11EventFilter *filter); void unregisterEventFilter(X11EventFilter *filter); void markXStackingOrderAsDirty(); void quickTileWindow(QuickTileMode mode); enum Direction { DirectionNorth, DirectionEast, DirectionSouth, DirectionWest }; void switchWindow(Direction direction); ShortcutDialog *shortcutDialog() const { return client_keys_dialog; } /** * Adds the internal client to Workspace. * * This method will be called by InternalClient when it's mapped. * * @see internalClientAdded * @internal */ void addInternalClient(InternalClient *client); /** * Removes the internal client from Workspace. * * This method is meant to be called only by InternalClient. * * @see internalClientRemoved * @internal */ void removeInternalClient(InternalClient *client); public Q_SLOTS: void performWindowOperation(KWin::AbstractClient* c, Options::WindowOperation op); // Keybindings //void slotSwitchToWindow( int ); void slotWindowToDesktop(uint i); //void slotWindowToListPosition( int ); void slotSwitchToScreen(); void slotWindowToScreen(); void slotSwitchToNextScreen(); void slotWindowToNextScreen(); void slotSwitchToPrevScreen(); void slotWindowToPrevScreen(); void slotToggleShowDesktop(); void slotWindowMaximize(); void slotWindowMaximizeVertical(); void slotWindowMaximizeHorizontal(); void slotWindowMinimize(); void slotWindowShade(); void slotWindowRaise(); void slotWindowLower(); void slotWindowRaiseOrLower(); void slotActivateAttentionWindow(); void slotWindowPackLeft(); void slotWindowPackRight(); void slotWindowPackUp(); void slotWindowPackDown(); void slotWindowGrowHorizontal(); void slotWindowGrowVertical(); void slotWindowShrinkHorizontal(); void slotWindowShrinkVertical(); void slotIncreaseWindowOpacity(); void slotLowerWindowOpacity(); void slotWindowOperations(); void slotWindowClose(); void slotWindowMove(); void slotWindowResize(); void slotWindowAbove(); void slotWindowBelow(); void slotWindowOnAllDesktops(); void slotWindowFullScreen(); void slotWindowNoBorder(); void slotWindowToNextDesktop(); void slotWindowToPreviousDesktop(); void slotWindowToDesktopRight(); void slotWindowToDesktopLeft(); void slotWindowToDesktopUp(); void slotWindowToDesktopDown(); void reconfigure(); void slotReconfigure(); void slotKillWindow(); void slotSetupWindowShortcut(); void setupWindowShortcutDone(bool); void updateClientArea(); private Q_SLOTS: void desktopResized(); void selectWmInputEventMask(); void slotUpdateToolWindows(); void delayFocus(); void slotReloadConfig(); void updateCurrentActivity(const QString &new_activity); // virtual desktop handling void slotDesktopCountChanged(uint previousCount, uint newCount); void slotCurrentDesktopChanged(uint oldDesktop, uint newDesktop); Q_SIGNALS: /** * Emitted after the Workspace has setup the complete initialization process. * This can be used to connect to for performing post-workspace initialization. */ void workspaceInitialized(); //Signals required for the scripting interface void desktopPresenceChanged(KWin::AbstractClient*, int); void currentDesktopChanged(int, KWin::AbstractClient*); void clientAdded(KWin::AbstractClient *); void clientRemoved(KWin::AbstractClient*); void clientActivated(KWin::AbstractClient*); void clientDemandsAttentionChanged(KWin::AbstractClient*, bool); void clientMinimizedChanged(KWin::AbstractClient*); void groupAdded(KWin::Group*); void unmanagedAdded(KWin::Unmanaged*); void unmanagedRemoved(KWin::Unmanaged*); void deletedRemoved(KWin::Deleted*); void configChanged(); void showingDesktopChanged(bool showing); /** * This signels is emitted when ever the stacking order is change, ie. a window is risen * or lowered */ void stackingOrderChanged(); /** * This signal is emitted whenever an internal client is created. */ void internalClientAdded(KWin::InternalClient *client); /** * This signal is emitted whenever an internal client gets removed. */ void internalClientRemoved(KWin::InternalClient *client); private: void init(); void initWithX11(); void initShortcuts(); template void initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut, Slot slot, const QVariant &data = QVariant()); template void initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut, T *receiver, Slot slot, const QVariant &data = QVariant()); void setupWindowShortcut(AbstractClient* c); bool switchWindow(AbstractClient *c, Direction direction, QPoint curPos, int desktop); void propagateClients(bool propagate_new_clients); // Called only from updateStackingOrder QList constrainedStackingOrder(); void raiseClientWithinApplication(AbstractClient* c); void lowerClientWithinApplication(AbstractClient* c); bool allowFullClientRaising(const AbstractClient* c, xcb_timestamp_t timestamp); bool keepTransientAbove(const AbstractClient* mainwindow, const AbstractClient* transient); bool keepDeletedTransientAbove(const Toplevel *mainWindow, const Deleted *transient) const; void blockStackingUpdates(bool block); void updateToolWindows(bool also_hide); void fixPositionAfterCrash(xcb_window_t w, const xcb_get_geometry_reply_t *geom); void saveOldScreenSizes(); /// This is the right way to create a new client X11Client *createClient(xcb_window_t w, bool is_mapped); void setupClientConnections(AbstractClient *client); void addClient(X11Client *c); Unmanaged* createUnmanaged(xcb_window_t w); void addUnmanaged(Unmanaged* c); void addShellClient(AbstractClient *client); void removeShellClient(AbstractClient *client); //--------------------------------------------------------------------- void closeActivePopup(); void updateClientArea(bool force); void resetClientAreas(uint desktopCount); void updateClientVisibilityOnDesktopChange(uint newDesktop); void activateClientOnNewDesktop(uint desktop); AbstractClient *findClientToActivateOnDesktop(uint desktop); QWidget* active_popup; AbstractClient* active_popup_client; int m_initialDesktop; void loadSessionInfo(const QString &sessionName); void addSessionInfo(KConfigGroup &cg); QList session; void updateXStackingOrder(); void updateTabbox(); AbstractClient* active_client; AbstractClient* last_active_client; AbstractClient* most_recently_raised; // Used ONLY by raiseOrLowerClient() AbstractClient* movingClient; // Delay(ed) window focus timer and client QTimer* delayFocusTimer; AbstractClient* delayfocus_client; QPoint focusMousePos; QList clients; QList m_allClients; - QList desktops; QList unmanaged; QList deleted; QList m_internalClients; QList unconstrained_stacking_order; // Topmost last QList stacking_order; // Topmost last QVector manual_overlays; //Topmost last bool force_restacking; QList x_stacking; // From XQueryTree() std::unique_ptr m_xStackingQueryTree; bool m_xStackingDirty = false; QList should_get_focus; // Last is most recent QList attention_chain; bool showing_desktop; QList groups; bool was_user_interaction; QScopedPointer m_wasUserInteractionFilter; int session_active_client; int session_desktop; int block_focus; /** * Holds the menu containing the user actions which is shown * on e.g. right click the window decoration. */ UserActionsMenu *m_userActionsMenu; void modalActionsSwitch(bool enabled); ShortcutDialog* client_keys_dialog; AbstractClient* client_keys_client; bool global_shortcuts_disabled_for_client; // Timer to collect requests for 'reconfigure' QTimer reconfigureTimer; QTimer updateToolWindowsTimer; static Workspace* _self; bool workspaceInit; KStartupInfo* startup; QVector workarea; // Array of workareas for virtual desktops // Array of restricted areas that window cannot be moved into QVector restrictedmovearea; // Array of the previous restricted areas that window cannot be moved into QVector oldrestrictedmovearea; QVector< QVector > screenarea; // Array of workareas per xinerama screen for all virtual desktops QVector< QRect > oldscreensizes; // array of previous sizes of xinerama screens QSize olddisplaysize; // previous sizes od displayWidth()/displayHeight() int set_active_client_recursion; int block_stacking_updates; // When > 0, stacking updates are temporarily disabled bool blocked_propagating_new_clients; // Propagate also new clients after enabling stacking updates? QScopedPointer m_nullFocus; friend class StackingUpdatesBlocker; QScopedPointer m_windowKiller; QList m_eventFilters; QList m_genericEventFilters; QScopedPointer m_movingClientFilter; QScopedPointer m_syncAlarmFilter; SessionManager *m_sessionManager; private: friend bool performTransiencyCheck(); friend Workspace *workspace(); }; /** * Helper for Workspace::blockStackingUpdates() being called in pairs (True/false) */ class StackingUpdatesBlocker { public: explicit StackingUpdatesBlocker(Workspace* w) : ws(w) { ws->blockStackingUpdates(true); } ~StackingUpdatesBlocker() { ws->blockStackingUpdates(false); } private: Workspace* ws; }; class ColorMapper : public QObject { Q_OBJECT public: ColorMapper(QObject *parent); ~ColorMapper() override; public Q_SLOTS: void update(); private: xcb_colormap_t m_default; xcb_colormap_t m_installed; }; //--------------------------------------------------------- // Unsorted inline bool Workspace::initializing() const { return workspaceInit; } inline AbstractClient *Workspace::activeClient() const { return active_client; } inline AbstractClient *Workspace::mostRecentlyActivatedClient() const { return should_get_focus.count() > 0 ? should_get_focus.last() : active_client; } inline void Workspace::addGroup(Group* group) { emit groupAdded(group); groups.append(group); } inline void Workspace::removeGroup(Group* group) { groups.removeAll(group); } inline const QList &Workspace::stackingOrder() const { // TODO: Q_ASSERT( block_stacking_updates == 0 ); return stacking_order; } inline bool Workspace::wasUserInteraction() const { return was_user_interaction; } inline SessionManager *Workspace::sessionManager() const { return m_sessionManager; } inline bool Workspace::showingDesktop() const { return showing_desktop; } inline bool Workspace::globalShortcutsDisabled() const { return global_shortcuts_disabled_for_client; } inline void Workspace::forceRestacking() { force_restacking = true; StackingUpdatesBlocker blocker(this); // Do restacking if not blocked } inline void Workspace::updateFocusMousePosition(const QPoint& pos) { focusMousePos = pos; } inline QPoint Workspace::focusMousePosition() const { return focusMousePos; } inline void Workspace::forEachClient(std::function< void (X11Client *) > func) { std::for_each(clients.constBegin(), clients.constEnd(), func); - std::for_each(desktops.constBegin(), desktops.constEnd(), func); } inline void Workspace::forEachUnmanaged(std::function< void (Unmanaged*) > func) { std::for_each(unmanaged.constBegin(), unmanaged.constEnd(), func); } inline bool Workspace::hasClient(const X11Client *c) { return findClient([c](const X11Client *test) { return test == c; }); } inline Workspace *workspace() { return Workspace::_self; } } // namespace Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::Workspace::ActivityFlags) #endif