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 for (QScreen *s : window->screen()->virtualSiblings()) {
44 if (s->name() == screenName) {
45 return s;
46 }
47 }
48 return window->screen();
49}
50
51// Convenience function to get an appropriate config file key under which to
52// save window size, position, or maximization information.
53static QString configFileString(const QString &key)
54{
55 QString returnString;
56 const int numberOfScreens = QGuiApplication::screens().length();
57
58 if (numberOfScreens == 1) {
59 // For single-screen setups, we save data on a per-resolution basis.
60 const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
61 returnString = QStringLiteral("%1x%2 screen: %3").arg(args: QString::number(screenGeometry.width()), args: QString::number(screenGeometry.height()), args: key);
62 } else {
63 // For multi-screen setups, we save data based on the number of screens.
64 // Distinguishing individual screens based on their names is unreliable
65 // due to name strings being inherently volatile.
66 returnString = QStringLiteral("%1 screens: %2").arg(args: QString::number(numberOfScreens), args: key);
67 }
68 return returnString;
69}
70
71// Convenience function for "window is maximized" string
72static QString screenMaximizedString()
73{
74 return configFileString(QStringLiteral("Window-Maximized"));
75}
76// Convenience function for window width string
77static QString windowWidthString()
78{
79 return configFileString(QStringLiteral("Width"));
80}
81// Convenience function for window height string
82static QString windowHeightString()
83{
84 return configFileString(QStringLiteral("Height"));
85}
86// Convenience function for window X position string
87static QString windowXPositionString()
88{
89 return configFileString(QStringLiteral("XPosition"));
90}
91// Convenience function for window Y position string
92static QString windowYPositionString()
93{
94 return configFileString(QStringLiteral("YPosition"));
95}
96static QString windowScreenPositionString()
97{
98 return QStringLiteral("%1").arg(a: allConnectedScreens());
99}
100
101void KWindowConfig::saveWindowSize(const QWindow *window, KConfigGroup &config, KConfigGroup::WriteConfigFlags options)
102{
103 // QWindow::screen() shouldn't return null, but it sometimes does due to bugs.
104 if (!window || !window->screen()) {
105 return;
106 }
107 const QScreen *screen = window->screen();
108
109 const QSize sizeToSave = window->size();
110 const bool isMaximized = window->windowState() & Qt::WindowMaximized;
111
112 // Save size only if window is not maximized
113 if (!isMaximized) {
114 const QSize defaultSize(window->property(name: s_initialSizePropertyName).toSize());
115 const QSize defaultScreenSize(window->property(name: s_initialScreenSizePropertyName).toSize());
116 const bool sizeValid = defaultSize.isValid() && defaultScreenSize.isValid();
117 if (!sizeValid || (sizeValid && (defaultSize != sizeToSave || defaultScreenSize != screen->geometry().size()))) {
118 config.writeEntry(key: windowWidthString(), value: sizeToSave.width(), pFlags: options);
119 config.writeEntry(key: windowHeightString(), value: sizeToSave.height(), pFlags: options);
120 // Don't keep the maximized string in the file since the window is
121 // no longer maximized at this point
122 config.deleteEntry(pKey: screenMaximizedString());
123 }
124 }
125 if ((isMaximized == false) && !config.hasDefault(key: screenMaximizedString())) {
126 config.revertToDefault(key: screenMaximizedString());
127 } else {
128 config.writeEntry(key: screenMaximizedString(), value: isMaximized, pFlags: options);
129 }
130}
131
132bool KWindowConfig::hasSavedWindowSize(KConfigGroup &config)
133{
134 return config.hasKey(key: windowWidthString()) || config.hasKey(key: windowHeightString()) || config.hasKey(key: screenMaximizedString());
135}
136
137void KWindowConfig::restoreWindowSize(QWindow *window, const KConfigGroup &config)
138{
139 if (!window) {
140 return;
141 }
142
143 const QString screenName = config.readEntry(key: windowScreenPositionString(), aDefault: window->screen()->name());
144
145 const int width = config.readEntry(key: windowWidthString(), aDefault: -1);
146 const int height = config.readEntry(key: windowHeightString(), aDefault: -1);
147 const bool isMaximized = config.readEntry(key: configFileString(QStringLiteral("Window-Maximized")), aDefault: false);
148
149 // Check default size
150 const QSize defaultSize(window->property(name: s_initialSizePropertyName).toSize());
151 const QSize defaultScreenSize(window->property(name: s_initialScreenSizePropertyName).toSize());
152 if (!defaultSize.isValid() || !defaultScreenSize.isValid()) {
153 const QScreen *screen = findScreenByName(window, screenName);
154 window->setProperty(name: s_initialSizePropertyName, value: window->size());
155 window->setProperty(name: s_initialScreenSizePropertyName, value: screen->geometry().size());
156 }
157
158 if (width > 0 && height > 0) {
159 window->resize(w: width, h: height);
160 }
161
162 if (isMaximized) {
163 window->setWindowState(Qt::WindowMaximized);
164 }
165}
166
167void KWindowConfig::saveWindowPosition(const QWindow *window, KConfigGroup &config, KConfigGroup::WriteConfigFlags options)
168{
169 // On Wayland, the compositor is solely responsible for window positioning,
170 // So this needs to be a no-op
171 if (!window || QGuiApplication::platformName() == QLatin1String{"wayland"}) {
172 return;
173 }
174
175 // If the window is maximized, saving the position will only serve to mis-position
176 // it once de-maximized, so let's not do that
177 if (window->windowState() & Qt::WindowMaximized) {
178 return;
179 }
180
181 config.writeEntry(key: windowXPositionString(), value: window->x(), pFlags: options);
182 config.writeEntry(key: windowYPositionString(), value: window->y(), pFlags: options);
183 config.writeEntry(key: windowScreenPositionString(), value: window->screen()->name(), pFlags: options);
184}
185
186bool KWindowConfig::hasSavedWindowPosition(KConfigGroup &config)
187{
188 // Window position save/restore features outside of the compositor are not
189 // supported on Wayland
190 if (QGuiApplication::platformName() == QLatin1String{"wayland"}) {
191 return false;
192 }
193
194 return config.hasKey(key: windowXPositionString()) || config.hasKey(key: windowYPositionString()) || config.hasKey(key: windowScreenPositionString());
195}
196
197void KWindowConfig::restoreWindowPosition(QWindow *window, const KConfigGroup &config)
198{
199 // On Wayland, the compositor is solely responsible for window positioning,
200 // So this needs to be a no-op
201 if (!window || QGuiApplication::platformName() == QLatin1String{"wayland"}) {
202 return;
203 }
204
205 const bool isMaximized = config.readEntry(key: configFileString(QStringLiteral("Window-Maximized")), aDefault: false);
206
207 // Don't need to restore position if the window was maximized
208 if (isMaximized) {
209 window->setWindowState(Qt::WindowMaximized);
210 return;
211 }
212
213 // Move window to proper screen
214 const QScreen *screen = window->screen();
215 const QString screenName = config.readEntry(key: windowScreenPositionString(), aDefault: screen->name());
216 if (screenName != screen->name()) {
217 QScreen *screenConf = findScreenByName(window, screenName);
218 window->setScreen(screenConf);
219 restoreWindowScreenPosition(window, screen: screenConf, config);
220 return;
221 }
222 restoreWindowScreenPosition(window, screen, config);
223}
224
225void KWindowConfig::restoreWindowScreenPosition(QWindow *window, const QScreen *screen, const KConfigGroup &config)
226{
227 Q_UNUSED(screen);
228 const int xPos = config.readEntry(key: windowXPositionString(), aDefault: -1);
229 const int yPos = config.readEntry(key: windowYPositionString(), aDefault: -1);
230
231 if (xPos == -1 || yPos == -1) {
232 return;
233 }
234
235 window->setX(xPos);
236 window->setY(yPos);
237}
238

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