diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d0856054c6..7b35e6752c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -95,6 +95,7 @@ target_sources(kwin PRIVATE overlaywindow.cpp placeholderinputeventfilter.cpp placement.cpp + placementtracker.cpp platform.cpp plugin.cpp pluginmanager.cpp diff --git a/src/placementtracker.cpp b/src/placementtracker.cpp new file mode 100644 index 0000000000..5ac07bdb78 --- /dev/null +++ b/src/placementtracker.cpp @@ -0,0 +1,197 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2022 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "placementtracker.h" +#include "output.h" +#include "platform.h" +#include "window.h" +#include "workspace.h" + +namespace KWin +{ + +PlacementTracker::PlacementTracker(Workspace *workspace) + : m_workspace(workspace) +{ +} + +PlacementTracker::WindowData PlacementTracker::dataForWindow(Window *window) const +{ + return WindowData{ + .outputUuid = workspace()->outputAt(window->moveResizeGeometry().center())->uuid(), + .geometry = window->moveResizeGeometry(), + .maximize = window->requestedMaximizeMode(), + .quickTile = window->quickTileMode(), + .geometryRestore = window->geometryRestore(), + .fullscreen = window->isFullScreen(), + .fullscreenGeometryRestore = window->fullscreenGeometryRestore(), + .interactiveMoveResizeCount = window->interactiveMoveResizeCount(), + }; +} + +void PlacementTracker::add(Window *window) +{ + if (window->isUnmanaged() || window->isAppletPopup() || window->isSpecialWindow()) { + return; + } + connect(window, &Window::frameGeometryChanged, this, &PlacementTracker::saveGeometry); + connect(window, qOverload(&Window::clientMaximizedStateChanged), this, &PlacementTracker::saveMaximize); + connect(window, &Window::quickTileModeChanged, this, &PlacementTracker::saveQuickTile); + connect(window, &Window::fullScreenChanged, this, &PlacementTracker::saveFullscreen); + connect(window, &Window::clientFinishUserMovedResized, this, &PlacementTracker::saveInteractionCounter); + WindowData data = dataForWindow(window); + m_data[m_currentKey][window] = data; + m_lastRestoreData[window] = data; + m_savedWindows.push_back(window); +} + +void PlacementTracker::remove(Window *window) +{ + if (m_savedWindows.contains(window)) { + disconnect(window, &Window::frameGeometryChanged, this, &PlacementTracker::saveGeometry); + disconnect(window, qOverload(&Window::clientMaximizedStateChanged), this, &PlacementTracker::saveMaximize); + disconnect(window, &Window::quickTileModeChanged, this, &PlacementTracker::saveQuickTile); + disconnect(window, &Window::fullScreenChanged, this, &PlacementTracker::saveFullscreen); + disconnect(window, &Window::clientFinishUserMovedResized, this, &PlacementTracker::saveInteractionCounter); + for (auto &dataMap : m_data) { + dataMap.remove(window); + } + m_lastRestoreData.remove(window); + m_savedWindows.removeOne(window); + } +} + +void PlacementTracker::restore(const QString &key) +{ + if (key == m_currentKey) { + return; + } + auto &dataMap = m_data[key]; + auto &oldDataMap = m_data[m_currentKey]; + const auto outputs = m_workspace->outputs(); + + inhibit(); + for (const auto window : std::as_const(m_savedWindows)) { + bool restore = true; + if (auto lastRestoreData = m_lastRestoreData.constFind(window); lastRestoreData != m_lastRestoreData.constEnd()) { + // don't touch windows where the user intentionally changed their state + restore = window->interactiveMoveResizeCount() == lastRestoreData->interactiveMoveResizeCount + && window->requestedMaximizeMode() == lastRestoreData->maximize + && window->quickTileMode() == lastRestoreData->quickTile + && window->isFullScreen() == lastRestoreData->fullscreen; + if (!restore) { + // restore anyways if the output the window was on got removed + if (const auto oldData = oldDataMap.find(window); oldData != oldDataMap.end()) { + restore |= std::none_of(outputs.begin(), outputs.end(), [&oldData](const auto output) { + return output->uuid() == oldData->outputUuid; + }); + } + } + } + if (restore) { + const auto it = dataMap.find(window); + if (it != dataMap.end()) { + const WindowData &newData = it.value(); + window->setFullScreen(false); + window->setQuickTileMode(QuickTileFlag::None, true); + window->setMaximize(false, false); + if (newData.quickTile || newData.maximize) { + window->moveResize(newData.geometryRestore); + window->setQuickTileMode(newData.quickTile, true); + window->setMaximize(newData.maximize & MaximizeMode::MaximizeVertical, newData.maximize & MaximizeMode::MaximizeHorizontal); + } + if (newData.fullscreen) { + window->moveResize(newData.fullscreenGeometryRestore); + window->setFullScreen(newData.fullscreen); + } + if (newData.quickTile || newData.maximize || newData.fullscreen) { + // restore geometry isn't necessarily on the output the window was, so explicitly restore it + const auto outputIt = std::find_if(outputs.begin(), outputs.end(), [&newData](const auto output) { + return output->uuid() == newData.outputUuid; + }); + if (outputIt != outputs.end()) { + window->sendToOutput(*outputIt); + } + } else { + window->moveResize(newData.geometry); + } + } + // if the window got restored or would've been restored if data was available, + // make sure the current state counts as being not touched by the user + m_lastRestoreData[window] = dataForWindow(window); + } + // ensure data in current map is always up to date + dataMap[window] = dataForWindow(window); + } + uninhibit(); + m_currentKey = key; +} + +void PlacementTracker::init(const QString &key) +{ + m_currentKey = key; +} + +void PlacementTracker::saveGeometry(Window *window) +{ + if (m_inhibitCount == 0) { + auto &data = m_data[m_currentKey][window]; + data.geometry = window->moveResizeGeometry(); + data.outputUuid = workspace()->outputAt(window->moveResizeGeometry().center())->uuid(); + } +} + +void PlacementTracker::saveInteractionCounter(Window *window) +{ + if (m_inhibitCount == 0) { + m_data[m_currentKey][window].interactiveMoveResizeCount = window->interactiveMoveResizeCount(); + } +} + +void PlacementTracker::saveMaximize(Window *window, MaximizeMode mode) +{ + if (m_inhibitCount == 0) { + auto &data = m_data[m_currentKey][window]; + data.maximize = mode; + data.geometryRestore = window->geometryRestore(); + } +} + +void PlacementTracker::saveQuickTile() +{ + Window *window = qobject_cast(QObject::sender()); + Q_ASSERT(window); + if (m_inhibitCount == 0) { + auto &data = m_data[m_currentKey][window]; + data.quickTile = window->quickTileMode(); + data.geometryRestore = window->geometryRestore(); + } +} + +void PlacementTracker::saveFullscreen() +{ + Window *window = qobject_cast(QObject::sender()); + Q_ASSERT(window); + if (m_inhibitCount == 0) { + auto &data = m_data[m_currentKey][window]; + data.fullscreen = window->isFullScreen(); + data.fullscreenGeometryRestore = window->fullscreenGeometryRestore(); + } +} + +void PlacementTracker::inhibit() +{ + m_inhibitCount++; +} + +void PlacementTracker::uninhibit() +{ + Q_ASSERT(m_inhibitCount > 0); + m_inhibitCount--; +} +} diff --git a/src/placementtracker.h b/src/placementtracker.h new file mode 100644 index 0000000000..0be06e7a04 --- /dev/null +++ b/src/placementtracker.h @@ -0,0 +1,68 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2022 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once + +#include "utils/common.h" + +#include +#include +#include +#include +#include + +namespace KWin +{ + +class Window; +class Workspace; + +class PlacementTracker : public QObject +{ + Q_OBJECT +public: + PlacementTracker(Workspace *workspace); + + void add(Window *window); + void remove(Window *window); + + void restore(const QString &key); + void init(const QString &key); + + void inhibit(); + void uninhibit(); + +private: + struct WindowData + { + QUuid outputUuid; + QRectF geometry; + MaximizeMode maximize; + QuickTileMode quickTile; + QRectF geometryRestore; + bool fullscreen; + QRectF fullscreenGeometryRestore; + uint32_t interactiveMoveResizeCount; + }; + + void saveGeometry(Window *window); + void saveInteractionCounter(Window *window); + void saveMaximize(KWin::Window *window, MaximizeMode mode); + void saveQuickTile(); + void saveFullscreen(); + WindowData dataForWindow(Window *window) const; + + QVector m_savedWindows; + QHash> m_data; + QHash m_lastRestoreData; + QString m_currentKey; + int m_inhibitCount = 0; + Workspace *const m_workspace; +}; + +} diff --git a/src/window.cpp b/src/window.cpp index 962582170e..64d6886e48 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1697,6 +1697,7 @@ void Window::finishInteractiveMoveResize(bool cancel) } setElectricBorderMode(QuickTileMode(QuickTileFlag::None)); + m_interactiveMoveResize.counter++; Q_EMIT clientFinishUserMovedResized(this); } @@ -4476,6 +4477,11 @@ quint32 Window::lastUsageSerial() const return m_lastUsageSerial; } +uint32_t Window::interactiveMoveResizeCount() const +{ + return m_interactiveMoveResize.counter; +} + } // namespace KWin #include "moc_window.cpp" diff --git a/src/window.h b/src/window.h index 25060d7217..4a66995f5c 100644 --- a/src/window.h +++ b/src/window.h @@ -1413,6 +1413,8 @@ public: void setLastUsageSerial(quint32 serial); quint32 lastUsageSerial() const; + uint32_t interactiveMoveResizeCount() const; + public Q_SLOTS: virtual void closeWindow() = 0; @@ -1979,6 +1981,7 @@ private: CursorShape cursor = Qt::ArrowCursor; Output *startOutput = nullptr; QTimer *delayedTimer = nullptr; + uint32_t counter = 0; } m_interactiveMoveResize; struct diff --git a/src/workspace.cpp b/src/workspace.cpp index 590c923297..53585323af 100644 --- a/src/workspace.cpp +++ b/src/workspace.cpp @@ -48,6 +48,7 @@ #include "decorations/decorationbridge.h" #include "main.h" #include "placeholderinputeventfilter.h" +#include "placementtracker.h" #include "unmanaged.h" #include "useractions.h" #include "utils/xcbutils.h" @@ -126,6 +127,7 @@ Workspace::Workspace() , m_sessionManager(new SessionManager(this)) , m_focusChain(std::make_unique()) , m_applicationMenu(std::make_unique()) + , m_placementTracker(std::make_unique(this)) { // If KWin was already running it saved its configuration after loosing the selection -> Reread #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) @@ -287,6 +289,29 @@ void Workspace::init() QMetaObject::invokeMethod(this, "workspaceInitialized", Qt::QueuedConnection); // TODO: ungrabXServer() + + connect(this, &Workspace::windowAdded, m_placementTracker.get(), &PlacementTracker::add); + connect(this, &Workspace::windowRemoved, m_placementTracker.get(), &PlacementTracker::remove); + m_placementTracker->init(getPlacementTrackerHash()); +} + +QString Workspace::getPlacementTrackerHash() +{ + QStringList hashes; + for (const auto &output : std::as_const(m_outputs)) { + QCryptographicHash hash(QCryptographicHash::Md5); + if (!output->edid().isEmpty()) { + hash.addData(output->edid()); + } else { + hash.addData(output->name().toLatin1()); + } + const auto geometry = output->geometry(); + hash.addData(reinterpret_cast(&geometry), sizeof(geometry)); + hashes.push_back(QString::fromLatin1(hash.result().toHex())); + } + std::sort(hashes.begin(), hashes.end()); + const auto hash = QCryptographicHash::hash(hashes.join(QString()).toLatin1(), QCryptographicHash::Md5); + return QString::fromLatin1(hash.toHex()); } void Workspace::initializeX11() @@ -2236,6 +2261,8 @@ void Workspace::checkTransients(xcb_window_t w) */ void Workspace::desktopResized() { + m_placementTracker->inhibit(); + const QRect oldGeometry = m_geometry; m_geometry = QRect(); for (const Output *output : std::as_const(m_outputs)) { @@ -2271,6 +2298,8 @@ void Workspace::desktopResized() // TODO: emit a signal instead and remove the deep function calls into edges and effects m_screenEdges->recreateEdges(); + m_placementTracker->uninhibit(); + m_placementTracker->restore(getPlacementTrackerHash()); if (m_geometry != oldGeometry) { Q_EMIT geometryChanged(); } diff --git a/src/workspace.h b/src/workspace.h index ae8029459e..050f51b3b5 100644 --- a/src/workspace.h +++ b/src/workspace.h @@ -66,6 +66,7 @@ class X11Window; class X11EventFilter; class FocusChain; class ApplicationMenu; +class PlacementTracker; enum class Predicate; class Outline; class RuleBook; @@ -633,6 +634,7 @@ private: void activateWindowOnNewDesktop(VirtualDesktop *desktop); Window *findWindowToActivateOnDesktop(VirtualDesktop *desktop); void removeWindow(Window *window); + QString getPlacementTrackerHash(); void updateOutputConfiguration(); @@ -751,6 +753,7 @@ private: #if KWIN_BUILD_ACTIVITIES std::unique_ptr m_activities; #endif + std::unique_ptr m_placementTracker; Output *m_placeholderOutput = nullptr; std::unique_ptr m_placeholderFilter;