workspace: restore window position after output changes
BUG: 455066 BUG: 374908 CCBUG: 444082 CCBUG: 454003 CCBUG: 453589
This commit is contained in:
parent
8e8b614500
commit
ba0799974e
7 changed files with 307 additions and 0 deletions
|
@ -95,6 +95,7 @@ target_sources(kwin PRIVATE
|
|||
overlaywindow.cpp
|
||||
placeholderinputeventfilter.cpp
|
||||
placement.cpp
|
||||
placementtracker.cpp
|
||||
platform.cpp
|
||||
plugin.cpp
|
||||
pluginmanager.cpp
|
||||
|
|
197
src/placementtracker.cpp
Normal file
197
src/placementtracker.cpp
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
|
||||
|
||||
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 *, MaximizeMode>(&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 *, MaximizeMode>(&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<Window *>(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<Window *>(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--;
|
||||
}
|
||||
}
|
68
src/placementtracker.h
Normal file
68
src/placementtracker.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
KWin - the KDE window manager
|
||||
This file is part of the KDE project.
|
||||
|
||||
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "utils/common.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
#include <QUuid>
|
||||
#include <QVector>
|
||||
|
||||
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<Window *> m_savedWindows;
|
||||
QHash<QString, QHash<Window *, WindowData>> m_data;
|
||||
QHash<Window *, WindowData> m_lastRestoreData;
|
||||
QString m_currentKey;
|
||||
int m_inhibitCount = 0;
|
||||
Workspace *const m_workspace;
|
||||
};
|
||||
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<FocusChain>())
|
||||
, m_applicationMenu(std::make_unique<ApplicationMenu>())
|
||||
, m_placementTracker(std::make_unique<PlacementTracker>(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<const char *>(&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();
|
||||
}
|
||||
|
|
|
@ -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<Activities> m_activities;
|
||||
#endif
|
||||
std::unique_ptr<PlacementTracker> m_placementTracker;
|
||||
|
||||
Output *m_placeholderOutput = nullptr;
|
||||
std::unique_ptr<PlaceholderInputEventFilter> m_placeholderFilter;
|
||||
|
|
Loading…
Reference in a new issue