File indexing completed on 2024-05-12 15:34:14
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 // QScreen::name() returns garbage on Windows; see https://bugreports.qt.io/browse/QTBUG-74317 0016 // So we use the screens' serial numbers to identify them instead 0017 // FIXME: remove this once we can depend on Qt 6.4, where this is fixed 0018 #if defined(Q_OS_WINDOWS) && QT_VERSION < QT_VERSION_CHECK(6, 4, 0) 0019 #define SCREENNAME serialNumber 0020 #else 0021 #define SCREENNAME name 0022 #endif 0023 0024 static const char s_initialSizePropertyName[] = "_kconfig_initial_size"; 0025 static const char s_initialScreenSizePropertyName[] = "_kconfig_initial_screen_size"; 0026 0027 // Convenience function to get a space-separated list of all connected screens 0028 static QString allConnectedScreens() 0029 { 0030 QStringList names; 0031 const auto screens = QGuiApplication::screens(); 0032 names.reserve(screens.length()); 0033 for (auto screen : screens) { 0034 names << screen->SCREENNAME(); 0035 } 0036 // A string including the connector names is used in the config file key for 0037 // storing per-screen-arrangement size and position data, which means we 0038 // need this string to be consistent for the same screen arrangement. But 0039 // connector order is non-deterministic. We need to sort the list to keep a 0040 // consistent order and avoid losing multi-screen size and position data. 0041 names.sort(); 0042 return names.join(QLatin1Char(' ')); 0043 } 0044 0045 // Convenience function to return screen by its name from window screen siblings 0046 // returns current window screen if not found 0047 static QScreen *findScreenByName(const QWindow *window, const QString screenName) 0048 { 0049 if (screenName == window->screen()->SCREENNAME()) { 0050 return window->screen(); 0051 } 0052 for (QScreen *s : window->screen()->virtualSiblings()) { 0053 if (s->SCREENNAME() == screenName) { 0054 return s; 0055 } 0056 } 0057 return window->screen(); 0058 } 0059 0060 // Convenience function to get an appropriate config file key under which to 0061 // save window size, position, or maximization information. 0062 static QString configFileString(const QScreen *screen, const QString &key) 0063 { 0064 Q_UNUSED(screen); 0065 QString returnString; 0066 const int numberOfScreens = QGuiApplication::screens().length(); 0067 0068 if (numberOfScreens == 1) { 0069 // For single-screen setups, we save data on a per-resolution basis. 0070 const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry(); 0071 returnString = QStringLiteral("%1x%2 screen: %3").arg(QString::number(screenGeometry.width()), QString::number(screenGeometry.height()), key); 0072 } else { 0073 // For multi-screen setups, we save data based on the number of screens. 0074 // Distinguishing individual screens based on their names is unreliable 0075 // due to name strings being inherently volatile. 0076 returnString = QStringLiteral("%1 screens: %2").arg(QString::number(numberOfScreens), key); 0077 } 0078 return returnString; 0079 } 0080 0081 // Convenience function for "window is maximized" string 0082 static QString screenMaximizedString(const QScreen *screen) 0083 { 0084 return configFileString(screen, QStringLiteral("Window-Maximized")); 0085 } 0086 // Convenience function for window width string 0087 static QString windowWidthString(const QScreen *screen) 0088 { 0089 return configFileString(screen, QStringLiteral("Width")); 0090 } 0091 // Convenience function for window height string 0092 static QString windowHeightString(const QScreen *screen) 0093 { 0094 return configFileString(screen, QStringLiteral("Height")); 0095 } 0096 // Convenience function for window X position string 0097 static QString windowXPositionString(const QScreen *screen) 0098 { 0099 return configFileString(screen, QStringLiteral("XPosition")); 0100 } 0101 // Convenience function for window Y position string 0102 static QString windowYPositionString(const QScreen *screen) 0103 { 0104 return configFileString(screen, QStringLiteral("YPosition")); 0105 } 0106 static QString windowScreenPositionString() 0107 { 0108 return QStringLiteral("%1").arg(allConnectedScreens()); 0109 } 0110 0111 void KWindowConfig::saveWindowSize(const QWindow *window, KConfigGroup &config, KConfigGroup::WriteConfigFlags options) 0112 { 0113 // QWindow::screen() shouldn't return null, but it sometimes does due to bugs. 0114 if (!window || !window->screen()) { 0115 return; 0116 } 0117 const QScreen *screen = window->screen(); 0118 0119 const QSize sizeToSave = window->size(); 0120 const bool isMaximized = window->windowState() & Qt::WindowMaximized; 0121 0122 // Save size only if window is not maximized 0123 if (!isMaximized) { 0124 const QSize defaultSize(window->property(s_initialSizePropertyName).toSize()); 0125 const QSize defaultScreenSize(window->property(s_initialScreenSizePropertyName).toSize()); 0126 const bool sizeValid = defaultSize.isValid() && defaultScreenSize.isValid(); 0127 if (!sizeValid || (sizeValid && (defaultSize != sizeToSave || defaultScreenSize != screen->geometry().size()))) { 0128 config.writeEntry(windowWidthString(screen), sizeToSave.width(), options); 0129 config.writeEntry(windowHeightString(screen), sizeToSave.height(), options); 0130 // Don't keep the maximized string in the file since the window is 0131 // no longer maximized at this point 0132 config.deleteEntry(screenMaximizedString(screen)); 0133 } 0134 } 0135 if ((isMaximized == false) && !config.hasDefault(screenMaximizedString(screen))) { 0136 config.revertToDefault(screenMaximizedString(screen)); 0137 } else { 0138 config.writeEntry(screenMaximizedString(screen), isMaximized, options); 0139 } 0140 } 0141 0142 void KWindowConfig::restoreWindowSize(QWindow *window, const KConfigGroup &config) 0143 { 0144 if (!window) { 0145 return; 0146 } 0147 0148 const QString screenName = config.readEntry(windowScreenPositionString(), window->screen()->SCREENNAME()); 0149 const QScreen *screen = findScreenByName(window, screenName); 0150 0151 // Fall back to non-per-screen-arrangement info if it's available but 0152 // per-screen-arrangement information is not 0153 // TODO: Remove in KF6 or maybe even KF5.80 or something. It really only needs 0154 // to be here to transition existing users once they upgrade from 5.73 -> 5.74 0155 const int fallbackWidth = config.readEntry(QStringLiteral("Width %1").arg(screen->geometry().width()), window->size().width()); 0156 const int fallbackHeight = config.readEntry(QStringLiteral("Height %1").arg(screen->geometry().height()), window->size().height()); 0157 0158 const int width = config.readEntry(windowWidthString(screen), fallbackWidth); 0159 const int height = config.readEntry(windowHeightString(screen), fallbackHeight); 0160 const bool isMaximized = config.readEntry(configFileString(screen, QStringLiteral("Window-Maximized")), false); 0161 0162 // Check default size 0163 const QSize defaultSize(window->property(s_initialSizePropertyName).toSize()); 0164 const QSize defaultScreenSize(window->property(s_initialScreenSizePropertyName).toSize()); 0165 if (!defaultSize.isValid() || !defaultScreenSize.isValid()) { 0166 window->setProperty(s_initialSizePropertyName, window->size()); 0167 window->setProperty(s_initialScreenSizePropertyName, screen->geometry().size()); 0168 } 0169 0170 // If window is maximized set maximized state and in all case set the size 0171 window->resize(width, height); 0172 if (isMaximized) { 0173 window->setWindowState(Qt::WindowMaximized); 0174 } 0175 } 0176 0177 void KWindowConfig::saveWindowPosition(const QWindow *window, KConfigGroup &config, KConfigGroup::WriteConfigFlags options) 0178 { 0179 // On Wayland, the compositor is solely responsible for window positioning, 0180 // So this needs to be a no-op 0181 if (!window || QGuiApplication::platformName() == QLatin1String{"wayland"}) { 0182 return; 0183 } 0184 0185 // If the window is maximized, saving the position will only serve to mis-position 0186 // it once de-maximized, so let's not do that 0187 if (window->windowState() & Qt::WindowMaximized) { 0188 return; 0189 } 0190 0191 const QScreen *screen = window->screen(); 0192 config.writeEntry(windowXPositionString(screen), window->x(), options); 0193 config.writeEntry(windowYPositionString(screen), window->y(), options); 0194 config.writeEntry(windowScreenPositionString(), screen->SCREENNAME(), options); 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 QScreen *screen = window->screen(); 0206 const bool isMaximized = config.readEntry(configFileString(screen, QStringLiteral("Window-Maximized")), false); 0207 0208 // Don't need to restore position if the window was maximized 0209 if (isMaximized) { 0210 window->setWindowState(Qt::WindowMaximized); 0211 return; 0212 } 0213 0214 // Move window to proper screen 0215 const QString screenName = config.readEntry(windowScreenPositionString(), screen->SCREENNAME()); 0216 if (screenName != screen->SCREENNAME()) { 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 const QRect desk = window->screen()->geometry(); 0228 // Fall back to non-per-resolution info if it's available but 0229 // per-resolution information is not 0230 // TODO: Remove in KF6 or maybe even KF5.85 or something. It really only needs 0231 // to be here to transition existing users once they upgrade from 5.78 -> 5.79 0232 const int fallbackXPosition = config.readEntry(QStringLiteral("%1 XPosition %2").arg(allConnectedScreens(), QString::number(desk.width())), -1); 0233 const int fallbackYPosition = config.readEntry(QStringLiteral("%1 YPosition %2").arg(allConnectedScreens(), QString::number(desk.height())), -1); 0234 const int xPos = config.readEntry(windowXPositionString(screen), fallbackXPosition); 0235 const int yPos = config.readEntry(windowYPositionString(screen), fallbackYPosition); 0236 0237 if (xPos == -1 || yPos == -1) { 0238 return; 0239 } 0240 0241 window->setX(xPos); 0242 window->setY(yPos); 0243 }