File indexing completed on 2024-04-28 16:48:58

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "placementtracker.h"
0010 #include "core/output.h"
0011 #include "window.h"
0012 #include "workspace.h"
0013 
0014 namespace KWin
0015 {
0016 
0017 PlacementTracker::PlacementTracker(Workspace *workspace)
0018     : m_workspace(workspace)
0019 {
0020 }
0021 
0022 PlacementTracker::WindowData PlacementTracker::dataForWindow(Window *window) const
0023 {
0024     return WindowData{
0025         .outputUuid = window->moveResizeOutput()->uuid(),
0026         .geometry = window->moveResizeGeometry(),
0027         .maximize = window->requestedMaximizeMode(),
0028         .quickTile = window->quickTileMode(),
0029         .geometryRestore = window->geometryRestore(),
0030         .fullscreen = window->isFullScreen(),
0031         .fullscreenGeometryRestore = window->fullscreenGeometryRestore(),
0032         .interactiveMoveResizeCount = window->interactiveMoveResizeCount(),
0033     };
0034 }
0035 
0036 void PlacementTracker::add(Window *window)
0037 {
0038     if (window->isUnmanaged() || window->isAppletPopup() || window->isSpecialWindow()) {
0039         return;
0040     }
0041     connect(window, &Window::frameGeometryChanged, this, &PlacementTracker::saveGeometry);
0042     connect(window, qOverload<Window *, MaximizeMode>(&Window::clientMaximizedStateChanged), this, &PlacementTracker::saveMaximize);
0043     connect(window, &Window::quickTileModeChanged, this, &PlacementTracker::saveQuickTile);
0044     connect(window, &Window::fullScreenChanged, this, &PlacementTracker::saveFullscreen);
0045     connect(window, &Window::clientFinishUserMovedResized, this, &PlacementTracker::saveInteractionCounter);
0046     WindowData data = dataForWindow(window);
0047     m_data[m_currentKey][window] = data;
0048     m_savedWindows.push_back(window);
0049 }
0050 
0051 void PlacementTracker::remove(Window *window)
0052 {
0053     if (m_savedWindows.contains(window)) {
0054         disconnect(window, &Window::frameGeometryChanged, this, &PlacementTracker::saveGeometry);
0055         disconnect(window, qOverload<Window *, MaximizeMode>(&Window::clientMaximizedStateChanged), this, &PlacementTracker::saveMaximize);
0056         disconnect(window, &Window::quickTileModeChanged, this, &PlacementTracker::saveQuickTile);
0057         disconnect(window, &Window::fullScreenChanged, this, &PlacementTracker::saveFullscreen);
0058         disconnect(window, &Window::clientFinishUserMovedResized, this, &PlacementTracker::saveInteractionCounter);
0059         for (auto &dataMap : m_data) {
0060             dataMap.remove(window);
0061         }
0062         m_savedWindows.removeOne(window);
0063     }
0064 }
0065 
0066 void PlacementTracker::restore(const QString &key)
0067 {
0068     if (key == m_currentKey) {
0069         return;
0070     }
0071     auto &dataMap = m_data[key];
0072     auto &oldDataMap = m_data[m_currentKey];
0073     const auto outputs = m_workspace->outputs();
0074 
0075     inhibit();
0076     for (const auto window : std::as_const(m_savedWindows)) {
0077         const auto it = dataMap.find(window);
0078         if (it != dataMap.end()) {
0079             const WindowData &newData = it.value();
0080 
0081             // don't touch windows where the user intentionally changed their state
0082             bool restore = window->interactiveMoveResizeCount() == newData.interactiveMoveResizeCount
0083                 && window->requestedMaximizeMode() == newData.maximize
0084                 && window->quickTileMode() == newData.quickTile
0085                 && window->isFullScreen() == newData.fullscreen;
0086             if (!restore) {
0087                 // the logic above can have false negatives if PlacementTracker changed the window state
0088                 // to prevent that, also restore if the window still has the same state from that
0089                 if (const auto it = m_lastRestoreData.find(window); it != m_lastRestoreData.end()) {
0090                     restore = window->interactiveMoveResizeCount() == it->interactiveMoveResizeCount
0091                         && window->requestedMaximizeMode() == it->maximize
0092                         && window->quickTileMode() == it->quickTile
0093                         && window->isFullScreen() == it->fullscreen
0094                         && window->moveResizeOutput()->uuid() == it->outputUuid;
0095                 }
0096             }
0097             if (!restore) {
0098                 // restore anyways if the output the window was on got removed
0099                 if (const auto oldData = oldDataMap.find(window); oldData != oldDataMap.end()) {
0100                     restore = std::none_of(outputs.begin(), outputs.end(), [&oldData](const auto output) {
0101                         return output->uuid() == oldData->outputUuid;
0102                     });
0103                 }
0104             }
0105             if (restore) {
0106                 window->setFullScreen(false);
0107                 window->setQuickTileMode(QuickTileFlag::None, true);
0108                 window->setMaximize(false, false);
0109                 if (newData.quickTile || newData.maximize) {
0110                     window->moveResize(newData.geometryRestore);
0111                     window->setQuickTileMode(newData.quickTile, true);
0112                     window->setMaximize(newData.maximize & MaximizeMode::MaximizeVertical, newData.maximize & MaximizeMode::MaximizeHorizontal);
0113                 }
0114                 if (newData.fullscreen) {
0115                     window->moveResize(newData.fullscreenGeometryRestore);
0116                     window->setFullScreen(newData.fullscreen);
0117                 }
0118                 if (newData.quickTile || newData.maximize || newData.fullscreen) {
0119                     // restore geometry isn't necessarily on the output the window was, so explicitly restore it
0120                     const auto outputIt = std::find_if(outputs.begin(), outputs.end(), [&newData](const auto output) {
0121                         return output->uuid() == newData.outputUuid;
0122                     });
0123                     if (outputIt != outputs.end()) {
0124                         window->sendToOutput(*outputIt);
0125                     }
0126                 } else {
0127                     window->moveResize(newData.geometry);
0128                 }
0129                 m_lastRestoreData[window] = dataForWindow(window);
0130             }
0131         }
0132         // ensure data in current map is always up to date
0133         dataMap[window] = dataForWindow(window);
0134     }
0135     uninhibit();
0136     m_currentKey = key;
0137 }
0138 
0139 void PlacementTracker::init(const QString &key)
0140 {
0141     m_currentKey = key;
0142 }
0143 
0144 void PlacementTracker::saveGeometry(Window *window)
0145 {
0146     if (m_inhibitCount == 0) {
0147         auto &data = m_data[m_currentKey][window];
0148         data.geometry = window->moveResizeGeometry();
0149         data.outputUuid = window->moveResizeOutput()->uuid();
0150     }
0151 }
0152 
0153 void PlacementTracker::saveInteractionCounter(Window *window)
0154 {
0155     if (m_inhibitCount == 0) {
0156         m_data[m_currentKey][window].interactiveMoveResizeCount = window->interactiveMoveResizeCount();
0157     }
0158 }
0159 
0160 void PlacementTracker::saveMaximize(Window *window, MaximizeMode mode)
0161 {
0162     if (m_inhibitCount == 0) {
0163         auto &data = m_data[m_currentKey][window];
0164         data.maximize = mode;
0165         data.geometryRestore = window->geometryRestore();
0166     }
0167 }
0168 
0169 void PlacementTracker::saveQuickTile()
0170 {
0171     Window *window = qobject_cast<Window *>(QObject::sender());
0172     Q_ASSERT(window);
0173     if (m_inhibitCount == 0) {
0174         auto &data = m_data[m_currentKey][window];
0175         data.quickTile = window->quickTileMode();
0176         data.geometryRestore = window->geometryRestore();
0177     }
0178 }
0179 
0180 void PlacementTracker::saveFullscreen()
0181 {
0182     Window *window = qobject_cast<Window *>(QObject::sender());
0183     Q_ASSERT(window);
0184     if (m_inhibitCount == 0) {
0185         auto &data = m_data[m_currentKey][window];
0186         data.fullscreen = window->isFullScreen();
0187         data.fullscreenGeometryRestore = window->fullscreenGeometryRestore();
0188     }
0189 }
0190 
0191 void PlacementTracker::inhibit()
0192 {
0193     m_inhibitCount++;
0194 }
0195 
0196 void PlacementTracker::uninhibit()
0197 {
0198     Q_ASSERT(m_inhibitCount > 0);
0199     m_inhibitCount--;
0200 }
0201 }