Lomiri
Loading...
Searching...
No Matches
windowstatestorage.cpp
1/*
2 * Copyright 2015-2016 Canonical Ltd.
3 * Copyright 2021 UBports Foundation
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation; version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "windowstatestorage.h"
19
20#include <QDebug>
21#include <QDir>
22#include <QMetaObject>
23#include <QObject>
24#include <QSqlQuery>
25#include <QSqlError>
26#include <QSqlResult>
27#include <QStandardPaths>
28#include <QRect>
29#include <lomiri/shell/application/ApplicationInfoInterface.h>
30
31
32class AsyncQuery: public QObject
33{
34 Q_OBJECT
35
36public:
37 AsyncQuery(const QString &dbName):
38 m_dbName(dbName)
39 {
40 }
41
42 ~AsyncQuery()
43 {
44 QSqlDatabase::removeDatabase(m_connectionName);
45 }
46
47 Q_INVOKABLE const QString getDbName()
48 {
49 if (!m_ok) {
50 return "ERROR";
51 }
52 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
53 return connection.databaseName();
54 }
55
56 Q_INVOKABLE bool initdb()
57 {
58 if (m_ok) {
59 return true;
60 }
61 QSqlDatabase connection = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName);
62 connection.setDatabaseName(m_dbName);
63 connection.setConnectOptions(QStringLiteral("QSQLITE_BUSY_TIMEOUT=1000"));
64 if (!connection.open()) {
65 qWarning() << "AsyncQuery::initdb: Error opening state database. Window positions will not be saved or restored." << m_dbName << connection.lastError().driverText() << connection.lastError().databaseText();
66 return false;
67 }
68 QSqlQuery query(connection);
69
70 if (!connection.tables().contains(QStringLiteral("geometry"))) {
71 QString geometryQuery = QStringLiteral("CREATE TABLE geometry(windowId TEXT UNIQUE, x INTEGER, y INTEGER, width INTEGER, height INTEGER);");
72 if (!query.exec(geometryQuery)) {
73 logSqlError(query);
74 return false;
75 }
76 }
77
78 if (!connection.tables().contains(QStringLiteral("state"))) {
79 QString stateQuery = QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);");
80 if (!query.exec(stateQuery)) {
81 logSqlError(query);
82 return false;
83 }
84 }
85
86 if (!connection.tables().contains(QStringLiteral("stage"))) {
87 QString stageQuery = QStringLiteral("CREATE TABLE stage(appId TEXT UNIQUE, stage INTEGER);");
88 if (!query.exec(stageQuery)) {
89 logSqlError(query);
90 return false;
91 }
92 }
93 m_ok = true;
94 return true;
95 }
96
97 Q_INVOKABLE int getState(const QString &windowId) const
98 {
99 if (!m_ok) {
100 return -1;
101 }
102 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
103 QSqlQuery query(connection);
104 query.prepare(m_getStateQuery);
105 query.bindValue(":windowId", windowId);
106 query.exec();
107 if (!query.isActive() || !query.isSelect()) {
108 logSqlError(query);
109 return -1;
110 }
111 if (!query.first()) {
112 return -1;
113 }
114 bool converted = false;
115 QVariant resultStr = query.value(0);
116 int result = resultStr.toInt(&converted);
117 if (converted) {
118 return result;
119 } else {
120 qWarning() << "getState result expected integer, got " << resultStr;
121 return -1;
122 }
123 }
124
125 Q_INVOKABLE QRect getGeometry(const QString &windowId) const
126 {
127 if (!m_ok) {
128 return QRect();
129 }
130 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
131 QSqlQuery query(connection);
132 query.prepare(m_getGeometryQuery);
133 query.bindValue(":windowId", windowId);
134 query.exec();
135 if (!query.isActive() || !query.isSelect()) {
136 logSqlError(query);
137 return QRect();
138 }
139
140 if (!query.first()) {
141 return QRect();
142 }
143
144 bool xConverted, yConverted, widthConverted, heightConverted = false;
145 int x, y, width, height;
146 QVariant xResultStr = query.value(QStringLiteral("x"));
147 QVariant yResultStr = query.value(QStringLiteral("y"));
148 QVariant widthResultStr = query.value(QStringLiteral("width"));
149 QVariant heightResultStr = query.value(QStringLiteral("height"));
150 x = xResultStr.toInt(&xConverted);
151 y = yResultStr.toInt(&yConverted);
152 width = widthResultStr.toInt(&widthConverted);
153 height = heightResultStr.toInt(&heightConverted);
154
155 if (xConverted && yConverted && widthConverted && heightConverted) {
156 return QRect(x, y, width, height);
157 } else {
158 qWarning() << "getGeometry result expected integers, got x:"
159 << xResultStr << "y:" << yResultStr << "width" << widthResultStr
160 << "height:" << heightResultStr;
161 return QRect();
162 }
163
164 }
165
166 Q_INVOKABLE int getStage(const QString &appId) const
167 {
168 if (!m_ok) {
169 return -1;
170 }
171 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
172 QSqlQuery query(connection);
173 query.prepare(m_getStageQuery);
174 query.bindValue(":appId", appId);
175 query.exec();
176 if (!query.isActive() || !query.isSelect()) {
177 logSqlError(query);
178 return -1;
179 }
180 if (!query.first()) {
181 return -1;
182 }
183 bool converted = false;
184 QVariant resultStr = query.value(0);
185 int result = resultStr.toInt(&converted);
186 if (converted) {
187 return result;
188 } else {
189 qWarning() << "getStage result expected integer, got " << resultStr;
190 return -1;
191 }
192 }
193
194public Q_SLOTS:
195
196 void saveState(const QString &windowId, WindowStateStorage::WindowState state)
197 {
198 if (!m_ok) {
199 return;
200 }
201 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
202 QSqlQuery query(connection);
203 query.prepare(m_saveStateQuery);
204 query.bindValue(":windowId", windowId);
205 query.bindValue(":state", (int)state);
206 if (!query.exec()) {
207 logSqlError(query);
208 }
209 }
210
211 void saveGeometry(const QString &windowId, const QRect &rect)
212 {
213 if (!m_ok) {
214 return;
215 }
216 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
217 QSqlQuery query(connection);
218 query.prepare(m_saveGeometryQuery);
219 query.bindValue(":windowId", windowId);
220 query.bindValue(":x", rect.x());
221 query.bindValue(":y", rect.y());
222 query.bindValue(":width", rect.width());
223 query.bindValue(":height", rect.height());
224 if (!query.exec()) {
225 logSqlError(query);
226 }
227 }
228
229 void saveStage(const QString &appId, int stage)
230 {
231 if (!m_ok) {
232 return;
233 }
234 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
235 QSqlQuery query(connection);
236 query.prepare(m_saveStageQuery);
237 query.bindValue(":appId", appId);
238 query.bindValue(":stage", stage);
239 if (!query.exec()) {
240 logSqlError(query);
241 }
242 }
243
244private:
245 static const QString m_connectionName;
246 static const QString m_getStateQuery;
247 static const QString m_saveStateQuery;
248 static const QString m_getGeometryQuery;
249 static const QString m_saveGeometryQuery;
250 static const QString m_getStageQuery;
251 static const QString m_saveStageQuery;
252
253 static void logSqlError(const QSqlQuery query)
254 {
255 qWarning() << "Error executing query" << query.lastQuery()
256 << "Driver error:" << query.lastError().driverText()
257 << "Database error:" << query.lastError().databaseText();
258 }
259
260 QString m_dbName;
261 bool m_ok = false;
262};
263
264const QString AsyncQuery::m_connectionName = QStringLiteral("WindowStateStorage");
265const QString AsyncQuery::m_getStateQuery = QStringLiteral("SELECT state FROM state WHERE windowId = :windowId");
266const QString AsyncQuery::m_saveStateQuery = QStringLiteral("INSERT OR REPLACE INTO state (windowId, state) values (:windowId, :state)");
267const QString AsyncQuery::m_getGeometryQuery = QStringLiteral("SELECT * FROM geometry WHERE windowId = :windowId");
268const QString AsyncQuery::m_saveGeometryQuery = QStringLiteral("INSERT OR REPLACE INTO geometry (windowId, x, y, width, height) values (:windowId, :x, :y, :width, :height)");
269const QString AsyncQuery::m_getStageQuery = QStringLiteral("SELECT stage FROM stage WHERE appId = :appId");
270const QString AsyncQuery::m_saveStageQuery = QStringLiteral("INSERT OR REPLACE INTO stage (appId, stage) values (:appId, :stage)");
271
272WindowStateStorage::WindowStateStorage(const QString &dbName, QObject *parent):
273 QObject(parent),
274 m_thread()
275{
276 qRegisterMetaType<WindowStateStorage::WindowState>("WindowStateStorage::WindowState");
277 QString dbFile;
278 if (dbName.isEmpty()) {
279 const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/lomiri/");
280 QDir dir;
281 dir.mkpath(dbPath);
282 dbFile = QString(dbPath) + QStringLiteral("windowstatestorage.sqlite");
283 } else {
284 dbFile = dbName;
285 }
286 m_asyncQuery = new AsyncQuery(dbFile);
287 m_asyncQuery->moveToThread(&m_thread);
288 connect(&m_thread, &QThread::finished, m_asyncQuery, &QObject::deleteLater);
289 m_thread.start();
290 // Note that we're relying on initdb being called before any other methods
291 // on AsyncQuery. Given the current behavior of QueuedConnection (slots
292 // invoked in the order they are received), this is fine.
293 QMetaObject::invokeMethod(m_asyncQuery, "initdb",
294 Qt::QueuedConnection);
295 connect(this, &WindowStateStorage::saveState, m_asyncQuery, &AsyncQuery::saveState);
296 connect(this, &WindowStateStorage::saveGeometry, m_asyncQuery, &AsyncQuery::saveGeometry);
297 connect(this, &WindowStateStorage::saveStage, m_asyncQuery, &AsyncQuery::saveStage);
298}
299
300WindowStateStorage::~WindowStateStorage()
301{
302 m_thread.quit();
303 m_thread.wait();
304}
305
306WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
307{
308 int state;
309
310 QMetaObject::invokeMethod(m_asyncQuery, "getState", Qt::BlockingQueuedConnection,
311 Q_RETURN_ARG(int, state),
312 Q_ARG(const QString&, windowId)
313 );
314
315 if (state == -1) {
316 return defaultValue;
317 }
318
319 return (WindowState)state;
320}
321
322int WindowStateStorage::getStage(const QString &appId, int defaultValue) const
323{
324 int stage;
325
326 QMetaObject::invokeMethod(m_asyncQuery, "getStage", Qt::BlockingQueuedConnection,
327 Q_RETURN_ARG(int, stage),
328 Q_ARG(const QString&, appId)
329 );
330
331 if (stage == -1) {
332 return defaultValue;
333 }
334
335 return stage;
336}
337
338QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
339{
340 QRect geometry;
341 QMetaObject::invokeMethod(m_asyncQuery, "getGeometry", Qt::BlockingQueuedConnection,
342 Q_RETURN_ARG(QRect, geometry),
343 Q_ARG(const QString&, windowId)
344 );
345 if (geometry.isNull() || !geometry.isValid()) {
346 return defaultValue;
347 }
348 return geometry;
349}
350
351const QString WindowStateStorage::getDbName()
352{
353 QString dbName;
354 QMetaObject::invokeMethod(m_asyncQuery, "getDbName", Qt::BlockingQueuedConnection,
355 Q_RETURN_ARG(QString, dbName)
356 );
357 return QString(dbName);
358}
359
360Mir::State WindowStateStorage::toMirState(WindowState state) const
361{
362 // assumes a single state (not an OR of several)
363 switch (state) {
364 case WindowStateMaximized: return Mir::MaximizedState;
365 case WindowStateMinimized: return Mir::MinimizedState;
366 case WindowStateFullscreen: return Mir::FullscreenState;
367 case WindowStateMaximizedLeft: return Mir::MaximizedLeftState;
368 case WindowStateMaximizedRight: return Mir::MaximizedRightState;
369 case WindowStateMaximizedHorizontally: return Mir::HorizMaximizedState;
370 case WindowStateMaximizedVertically: return Mir::VertMaximizedState;
371 case WindowStateMaximizedTopLeft: return Mir::MaximizedTopLeftState;
372 case WindowStateMaximizedTopRight: return Mir::MaximizedTopRightState;
373 case WindowStateMaximizedBottomLeft: return Mir::MaximizedBottomLeftState;
374 case WindowStateMaximizedBottomRight: return Mir::MaximizedBottomRightState;
375
376 case WindowStateNormal:
377 case WindowStateRestored:
378 default:
379 return Mir::RestoredState;
380 }
381}
382
383#include "windowstatestorage.moc"