File indexing completed on 2024-05-12 03:54:29

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2012 Benjamin Port <benjamin.port@ben2367.fr>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "kwindowconfig.h"
0009 #include "ksharedconfig.h"
0010 
0011 #include <QGuiApplication>
0012 #include <QScreen>
0013 #include <QWindow>
0014 
0015 static const char s_initialSizePropertyName[] = "_kconfig_initial_size";
0016 static const char s_initialScreenSizePropertyName[] = "_kconfig_initial_screen_size";
0017 
0018 // Convenience function to get a space-separated list of all connected screens
0019 static QString allConnectedScreens()
0020 {
0021     QStringList names;
0022     const auto screens = QGuiApplication::screens();
0023     names.reserve(screens.length());
0024     for (auto screen : screens) {
0025         names << screen->name();
0026     }
0027     // A string including the connector names is used in the config file key for
0028     // storing per-screen-arrangement size and position data, which means we
0029     // need this string to be consistent for the same screen arrangement. But
0030     // connector order is non-deterministic. We need to sort the list to keep a
0031     // consistent order and avoid losing multi-screen size and position data.
0032     names.sort();
0033     return names.join(QLatin1Char(' '));
0034 }
0035 
0036 // Convenience function to return screen by its name from window screen siblings
0037 // returns current window screen if not found
0038 static QScreen *findScreenByName(const QWindow *window, const QString screenName)
0039 {
0040     if (screenName == window->screen()->name()) {
0041         return window->screen();
0042     }
0043     for (QScreen *s : window->screen()->virtualSiblings()) {
0044         if (s->name() == screenName) {
0045             return s;
0046         }
0047     }
0048     return window->screen();
0049 }
0050 
0051 // Convenience function to get an appropriate config file key under which to
0052 // save window size, position, or maximization information.
0053 static QString configFileString(const QString &key)
0054 {
0055     QString returnString;
0056     const int numberOfScreens = QGuiApplication::screens().length();
0057 
0058     if (numberOfScreens == 1) {
0059         // For single-screen setups, we save data on a per-resolution basis.
0060         const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
0061         returnString = QStringLiteral("%1x%2 screen: %3").arg(QString::number(screenGeometry.width()), QString::number(screenGeometry.height()), key);
0062     } else {
0063         // For multi-screen setups, we save data based on the number of screens.
0064         // Distinguishing individual screens based on their names is unreliable
0065         // due to name strings being inherently volatile.
0066         returnString = QStringLiteral("%1 screens: %2").arg(QString::number(numberOfScreens), key);
0067     }
0068     return returnString;
0069 }
0070 
0071 // Convenience function for "window is maximized" string
0072 static QString screenMaximizedString()
0073 {
0074     return configFileString(QStringLiteral("Window-Maximized"));
0075 }
0076 // Convenience function for window width string
0077 static QString windowWidthString()
0078 {
0079     return configFileString(QStringLiteral("Width"));
0080 }
0081 // Convenience function for window height string
0082 static QString windowHeightString()
0083 {
0084     return configFileString(QStringLiteral("Height"));
0085 }
0086 // Convenience function for window X position string
0087 static QString windowXPositionString()
0088 {
0089     return configFileString(QStringLiteral("XPosition"));
0090 }
0091 // Convenience function for window Y position string
0092 static QString windowYPositionString()
0093 {
0094     return configFileString(QStringLiteral("YPosition"));
0095 }
0096 static QString windowScreenPositionString()
0097 {
0098     return QStringLiteral("%1").arg(allConnectedScreens());
0099 }
0100 
0101 void KWindowConfig::saveWindowSize(const QWindow *window, KConfigGroup &config, KConfigGroup::WriteConfigFlags options)
0102 {
0103     // QWindow::screen() shouldn't return null, but it sometimes does due to bugs.
0104     if (!window || !window->screen()) {
0105         return;
0106     }
0107     const QScreen *screen = window->screen();
0108 
0109     const QSize sizeToSave = window->size();
0110     const bool isMaximized = window->windowState() & Qt::WindowMaximized;
0111 
0112     // Save size only if window is not maximized
0113     if (!isMaximized) {
0114         const QSize defaultSize(window->property(s_initialSizePropertyName).toSize());
0115         const QSize defaultScreenSize(window->property(s_initialScreenSizePropertyName).toSize());
0116         const bool sizeValid = defaultSize.isValid() && defaultScreenSize.isValid();
0117         if (!sizeValid || (sizeValid && (defaultSize != sizeToSave || defaultScreenSize != screen->geometry().size()))) {
0118             config.writeEntry(windowWidthString(), sizeToSave.width(), options);
0119             config.writeEntry(windowHeightString(), sizeToSave.height(), options);
0120             // Don't keep the maximized string in the file since the window is
0121             // no longer maximized at this point
0122             config.deleteEntry(screenMaximizedString());
0123         }
0124     }
0125     if ((isMaximized == false) && !config.hasDefault(screenMaximizedString())) {
0126         config.revertToDefault(screenMaximizedString());
0127     } else {
0128         config.writeEntry(screenMaximizedString(), isMaximized, options);
0129     }
0130 }
0131 
0132 bool KWindowConfig::hasSavedWindowSize(KConfigGroup &config)
0133 {
0134     return config.hasKey(windowWidthString()) || config.hasKey(windowHeightString()) || config.hasKey(screenMaximizedString());
0135 }
0136 
0137 void KWindowConfig::restoreWindowSize(QWindow *window, const KConfigGroup &config)
0138 {
0139     if (!window) {
0140         return;
0141     }
0142 
0143     const QString screenName = config.readEntry(windowScreenPositionString(), window->screen()->name());
0144 
0145     const int width = config.readEntry(windowWidthString(), -1);
0146     const int height = config.readEntry(windowHeightString(), -1);
0147     const bool isMaximized = config.readEntry(configFileString(QStringLiteral("Window-Maximized")), false);
0148 
0149     // Check default size
0150     const QSize defaultSize(window->property(s_initialSizePropertyName).toSize());
0151     const QSize defaultScreenSize(window->property(s_initialScreenSizePropertyName).toSize());
0152     if (!defaultSize.isValid() || !defaultScreenSize.isValid()) {
0153         const QScreen *screen = findScreenByName(window, screenName);
0154         window->setProperty(s_initialSizePropertyName, window->size());
0155         window->setProperty(s_initialScreenSizePropertyName, screen->geometry().size());
0156     }
0157 
0158     if (width > 0 && height > 0) {
0159         window->resize(width, height);
0160     }
0161 
0162     if (isMaximized) {
0163         window->setWindowState(Qt::WindowMaximized);
0164     }
0165 }
0166 
0167 void KWindowConfig::saveWindowPosition(const QWindow *window, KConfigGroup &config, KConfigGroup::WriteConfigFlags options)
0168 {
0169     // On Wayland, the compositor is solely responsible for window positioning,
0170     // So this needs to be a no-op
0171     if (!window || QGuiApplication::platformName() == QLatin1String{"wayland"}) {
0172         return;
0173     }
0174 
0175     // If the window is maximized, saving the position will only serve to mis-position
0176     // it once de-maximized, so let's not do that
0177     if (window->windowState() & Qt::WindowMaximized) {
0178         return;
0179     }
0180 
0181     config.writeEntry(windowXPositionString(), window->x(), options);
0182     config.writeEntry(windowYPositionString(), window->y(), options);
0183     config.writeEntry(windowScreenPositionString(), window->screen()->name(), options);
0184 }
0185 
0186 bool KWindowConfig::hasSavedWindowPosition(KConfigGroup &config)
0187 {
0188     // Window position save/restore features outside of the compositor are not
0189     // supported on Wayland
0190     if (QGuiApplication::platformName() == QLatin1String{"wayland"}) {
0191         return false;
0192     }
0193 
0194     return config.hasKey(windowXPositionString()) || config.hasKey(windowYPositionString()) || config.hasKey(windowScreenPositionString());
0195 }
0196 
0197 void KWindowConfig::restoreWindowPosition(QWindow *window, const KConfigGroup &config)
0198 {
0199     // On Wayland, the compositor is solely responsible for window positioning,
0200     // So this needs to be a no-op
0201     if (!window || QGuiApplication::platformName() == QLatin1String{"wayland"}) {
0202         return;
0203     }
0204 
0205     const bool isMaximized = config.readEntry(configFileString(QStringLiteral("Window-Maximized")), false);
0206 
0207     // Don't need to restore position if the window was maximized
0208     if (isMaximized) {
0209         window->setWindowState(Qt::WindowMaximized);
0210         return;
0211     }
0212 
0213     // Move window to proper screen
0214     const QScreen *screen = window->screen();
0215     const QString screenName = config.readEntry(windowScreenPositionString(), screen->name());
0216     if (screenName != screen->name()) {
0217         QScreen *screenConf = findScreenByName(window, screenName);
0218         window->setScreen(screenConf);
0219         restoreWindowScreenPosition(window, screenConf, config);
0220         return;
0221     }
0222     restoreWindowScreenPosition(window, screen, config);
0223 }
0224 
0225 void KWindowConfig::restoreWindowScreenPosition(QWindow *window, const QScreen *screen, const KConfigGroup &config)
0226 {
0227     Q_UNUSED(screen);
0228     const int xPos = config.readEntry(windowXPositionString(), -1);
0229     const int yPos = config.readEntry(windowYPositionString(), -1);
0230 
0231     if (xPos == -1 || yPos == -1) {
0232         return;
0233     }
0234 
0235     window->setX(xPos);
0236     window->setY(yPos);
0237 }