1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2012 Benjamin Port <benjamin.port@ben2367.fr>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "kwindowconfig.h"
9#include "ksharedconfig.h"
10
11#include <QGuiApplication>
12#include <QScreen>
13#include <QWindow>
14
15static const char s_initialSizePropertyName[] = "_kconfig_initial_size";
16static const char s_initialScreenSizePropertyName[] = "_kconfig_initial_screen_size";
17
18// Convenience function to get a space-separated list of all connected screens
19static QString allConnectedScreens()
20{
21 QStringList names;
22 const auto screens = QGuiApplication::screens();
23 names.reserve(asize: screens.length());
24 for (auto screen : screens) {
25 names << screen->name();
26 }
27 // A string including the connector names is used in the config file key for
28 // storing per-screen-arrangement size and position data, which means we
29 // need this string to be consistent for the same screen arrangement. But
30 // connector order is non-deterministic. We need to sort the list to keep a
31 // consistent order and avoid losing multi-screen size and position data.
32 names.sort();
33 return names.join(sep: QLatin1Char(' '));
34}
35
36// Convenience function to return screen by its name from window screen siblings
37// returns current window screen if not found
38static QScreen *findScreenByName(const QWindow *window, const QString screenName)
39{
40 if (screenName == window->screen()->name()) {
41 return window->screen();
42 }
43
44 const auto virtualSiblings = window->screen()->virtualSiblings();
45 for (QScreen *s : virtualSiblings) {
46 if (s->name() == screenName) {
47 return s;
48 }
49 }
50 return window->screen();
51}
52
53// Convenience function to get an appropriate config file key under which to
54// save window size, position, or maximization information.
55static QString configFileString(const QString &key)
56{
57 QString returnString;
58 const int numberOfScreens = QGuiApplication::screens().length();
59
60 if (numberOfScreens == 1) {
61 // For single-screen setups, we save data on a per-resolution basis.
62 const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
63 returnString = QStringLiteral("%1x%2 screen: %3").arg(args: QString::number(screenGeometry.width()), args: QString::number(screenGeometry.height()), args: key);
64 } else {
65 // For multi-screen setups, we save data based on the number of screens.
66 // Distinguishing individual screens based on their names is unreliable
67 // due to name strings being inherently volatile.
68 returnString = QStringLiteral("%1 screens: %2").arg(args: QString::number(numberOfScreens), args: key);
69 }
70 return returnString;
71}
72
73// Convenience function for "window is maximized" string
74static QString screenMaximizedString()
75{
76 return configFileString(QStringLiteral("Window-Maximized"));
77}
78// Convenience function for window width string
79static QString windowWidthString()
80{
81 return configFileString(QStringLiteral("Width"));
82}
83// Convenience function for window height string
84static QString windowHeightString()
85{
86 return configFileString(QStringLiteral("Height"));
87}
88// Convenience function for window X position string
89static QString windowXPositionString()
90{
91 return configFileString(QStringLiteral("XPosition"));
92}
93// Convenience function for window Y position string
94static QString windowYPositionString()
95{
96 return configFileString(QStringLiteral("YPosition"));
97}
98static QString windowScreenPositionString()
99{
100 return QStringLiteral("%1").arg(a: allConnectedScreens());
101}
102
103void KWindowConfig::saveWindowSize(const QWindow *window, KConfigGroup &config, KConfigGroup::WriteConfigFlags options)
104{
105 // QWindow::screen() shouldn't return null, but it sometimes does due to bugs.
106 if (!window || !window->screen()) {
107 return;
108 }
109 const QScreen *screen = window->screen();
110
111 const QSize sizeToSave = window->size();
112 const bool isMaximized = window->windowState() & Qt::WindowMaximized;
113
114 // Save size only if window is not maximized
115 if (!isMaximized) {
116 const QSize defaultSize(window->property(name: s_initialSizePropertyName).toSize());
117 const QSize defaultScreenSize(window->property(name: s_initialScreenSizePropertyName).toSize());
118 const bool sizeValid = defaultSize.isValid() && defaultScreenSize.isValid();
119 if (!sizeValid || (defaultSize != sizeToSave || defaultScreenSize != screen->geometry().size())) {
120 config.writeEntry(key: windowWidthString(), value: sizeToSave.width(), pFlags: options);
121 config.writeEntry(key: windowHeightString(), value: sizeToSave.height(), pFlags: options);
122 // Don't keep the maximized string in the file since the window is
123 // no longer maximized at this point
124 config.deleteEntry(pKey: screenMaximizedString());
125 }
126 // Revert width and height to default if they are same as defaults
127 else {
128 config.revertToDefault(key: windowWidthString());
129 config.revertToDefault(key: windowHeightString());
130 }
131 }
132 if ((isMaximized == false) && !config.hasDefault(key: screenMaximizedString())) {
133 config.revertToDefault(key: screenMaximizedString());
134 } else {
135 config.writeEntry(key: screenMaximizedString(), value: isMaximized, pFlags: options);
136 }
137}
138
139bool KWindowConfig::hasSavedWindowSize(KConfigGroup &config)
140{
141 return config.hasKey(key: windowWidthString()) || config.hasKey(key: windowHeightString()) || config.hasKey(key: screenMaximizedString());
142}
143
144void KWindowConfig::restoreWindowSize(QWindow *window, const KConfigGroup &config)
145{
146 if (!window) {
147 return;
148 }
149
150 const int width = config.readEntry(key: windowWidthString(), aDefault: -1);
151 const int height = config.readEntry(key: windowHeightString(), aDefault: -1);
152 const bool isMaximized = config.readEntry(key: configFileString(QStringLiteral("Window-Maximized")), aDefault: false);
153
154 // Check default size
155 const QSize defaultSize(window->property(name: s_initialSizePropertyName).toSize());
156 const QSize defaultScreenSize(window->property(name: s_initialScreenSizePropertyName).toSize());
157 if (!defaultSize.isValid() || !defaultScreenSize.isValid()) {
158 const QString screenName = config.readEntry(key: windowScreenPositionString(), aDefault: window->screen()->name());
159 const QScreen *screen = findScreenByName(window, screenName);
160 window->setProperty(name: s_initialSizePropertyName, value: window->size());
161 window->setProperty(name: s_initialScreenSizePropertyName, value: screen->geometry().size());
162 }
163
164 if (width > 0 && height > 0) {
165 window->resize(w: width, h: height);
166 }
167
168 if (isMaximized) {
169 window->setWindowState(Qt::WindowMaximized);
170 }
171}
172
173void KWindowConfig::saveWindowPosition(const QWindow *window, KConfigGroup &config, KConfigGroup::WriteConfigFlags options)
174{
175 // On Wayland, the compositor is solely responsible for window positioning,
176 // So this needs to be a no-op
177 if (!window || QGuiApplication::platformName() == QLatin1String{"wayland"}) {
178 return;
179 }
180
181 // If the window is maximized, saving the position will only serve to mis-position
182 // it once de-maximized, so let's not do that
183 if (window->windowState() & Qt::WindowMaximized) {
184 return;
185 }
186
187 config.writeEntry(key: windowXPositionString(), value: window->x(), pFlags: options);
188 config.writeEntry(key: windowYPositionString(), value: window->y(), pFlags: options);
189 config.writeEntry(key: windowScreenPositionString(), value: window->screen()->name(), pFlags: options);
190}
191
192bool KWindowConfig::hasSavedWindowPosition(KConfigGroup &config)
193{
194 // Window position save/restore features outside of the compositor are not
195 // supported on Wayland
196 if (QGuiApplication::platformName() == QLatin1String{"wayland"}) {
197 return false;
198 }
199
200 return config.hasKey(key: windowXPositionString()) || config.hasKey(key: windowYPositionString()) || config.hasKey(key: windowScreenPositionString());
201}
202
203void KWindowConfig::restoreWindowPosition(QWindow *window, const KConfigGroup &config)
204{
205 // On Wayland, the compositor is solely responsible for window positioning,
206 // So this needs to be a no-op
207 if (!window || QGuiApplication::platformName() == QLatin1String{"wayland"}) {
208 return;
209 }
210
211 const bool isMaximized = config.readEntry(key: configFileString(QStringLiteral("Window-Maximized")), aDefault: false);
212
213 // Don't need to restore position if the window was maximized
214 if (isMaximized) {
215 window->setWindowState(Qt::WindowMaximized);
216 return;
217 }
218
219 // Move window to proper screen
220 const QScreen *screen = window->screen();
221 const QString screenName = config.readEntry(key: windowScreenPositionString(), aDefault: screen->name());
222 if (screenName != screen->name()) {
223 QScreen *screenConf = findScreenByName(window, screenName);
224 window->setScreen(screenConf);
225 restoreWindowScreenPosition(window, screen: screenConf, config);
226 return;
227 }
228 restoreWindowScreenPosition(window, screen, config);
229}
230
231void KWindowConfig::restoreWindowScreenPosition(QWindow *window, const QScreen *screen, const KConfigGroup &config)
232{
233 Q_UNUSED(screen);
234 const int xPos = config.readEntry(key: windowXPositionString(), aDefault: -1);
235 const int yPos = config.readEntry(key: windowYPositionString(), aDefault: -1);
236
237 if (xPos == -1 || yPos == -1) {
238 return;
239 }
240
241 window->setX(xPos);
242 window->setY(yPos);
243}
244

source code of kconfig/src/gui/kwindowconfig.cpp