2 * Copyright 2015-2016 Canonical Ltd.
3 * Copyright 2021 UBports Foundation
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.
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.
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/>.
18#include "windowstatestorage.h"
27#include <QStandardPaths>
29#include <unity/shell/application/ApplicationInfoInterface.h>
32class AsyncQuery: public QObject
37 AsyncQuery(const QString &dbName):
44 QSqlDatabase::removeDatabase(m_connectionName);
47 Q_INVOKABLE const QString getDbName()
52 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
53 return connection.databaseName();
56 Q_INVOKABLE bool initdb()
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();
68 QSqlQuery query(connection);
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)) {
78 if (!connection.tables().contains(QStringLiteral("state"))) {
79 QString stateQuery = QStringLiteral("CREATE TABLE state(windowId TEXT UNIQUE, state INTEGER);");
80 if (!query.exec(stateQuery)) {
86 if (!connection.tables().contains(QStringLiteral("stage"))) {
87 QString stageQuery = QStringLiteral("CREATE TABLE stage(appId TEXT UNIQUE, stage INTEGER);");
88 if (!query.exec(stageQuery)) {
97 Q_INVOKABLE int getState(const QString &windowId) const
102 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
103 QSqlQuery query(connection);
104 query.prepare(m_getStateQuery);
105 query.bindValue(":windowId", windowId);
107 if (!query.isActive() || !query.isSelect()) {
111 if (!query.first()) {
114 bool converted = false;
115 QVariant resultStr = query.value(0);
116 int result = resultStr.toInt(&converted);
120 qWarning() << "getState result expected integer, got " << resultStr;
125 Q_INVOKABLE QRect getGeometry(const QString &windowId) const
130 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
131 QSqlQuery query(connection);
132 query.prepare(m_getGeometryQuery);
133 query.bindValue(":windowId", windowId);
135 if (!query.isActive() || !query.isSelect()) {
140 if (!query.first()) {
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);
155 if (xConverted && yConverted && widthConverted && heightConverted) {
156 return QRect(x, y, width, height);
158 qWarning() << "getGeometry result expected integers, got x:"
159 << xResultStr << "y:" << yResultStr << "width" << widthResultStr
160 << "height:" << heightResultStr;
166 Q_INVOKABLE int getStage(const QString &appId) const
171 QSqlDatabase connection = QSqlDatabase::database(m_connectionName);
172 QSqlQuery query(connection);
173 query.prepare(m_getStageQuery);
174 query.bindValue(":appId", appId);
176 if (!query.isActive() || !query.isSelect()) {
180 if (!query.first()) {
183 bool converted = false;
184 QVariant resultStr = query.value(0);
185 int result = resultStr.toInt(&converted);
189 qWarning() << "getStage result expected integer, got " << resultStr;
196 void saveState(const QString &windowId, WindowStateStorage::WindowState state)
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);
211 void saveGeometry(const QString &windowId, const QRect &rect)
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());
229 void saveStage(const QString &appId, int stage)
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);
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;
253 static void logSqlError(const QSqlQuery query)
255 qWarning() << "Error executing query" << query.lastQuery()
256 << "Driver error:" << query.lastError().driverText()
257 << "Database error:" << query.lastError().databaseText();
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)");
272WindowStateStorage::WindowStateStorage(const QString &dbName, QObject *parent):
276 qRegisterMetaType<WindowStateStorage::WindowState>("WindowStateStorage::WindowState");
278 if (dbName.isEmpty()) {
279 const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QStringLiteral("/unity8/");
282 dbFile = QString(dbPath) + QStringLiteral("windowstatestorage.sqlite");
286 m_asyncQuery = new AsyncQuery(dbFile);
287 m_asyncQuery->moveToThread(&m_thread);
288 connect(&m_thread, &QThread::finished, m_asyncQuery, &QObject::deleteLater);
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);
300WindowStateStorage::~WindowStateStorage()
306WindowStateStorage::WindowState WindowStateStorage::getState(const QString &windowId, WindowStateStorage::WindowState defaultValue) const
310 QMetaObject::invokeMethod(m_asyncQuery, "getState", Qt::BlockingQueuedConnection,
311 Q_RETURN_ARG(int, state),
312 Q_ARG(const QString&, windowId)
319 return (WindowState)state;
322int WindowStateStorage::getStage(const QString &appId, int defaultValue) const
326 QMetaObject::invokeMethod(m_asyncQuery, "getStage", Qt::BlockingQueuedConnection,
327 Q_RETURN_ARG(int, stage),
328 Q_ARG(const QString&, appId)
338QRect WindowStateStorage::getGeometry(const QString &windowId, const QRect &defaultValue) const
341 QMetaObject::invokeMethod(m_asyncQuery, "getGeometry", Qt::BlockingQueuedConnection,
342 Q_RETURN_ARG(QRect, geometry),
343 Q_ARG(const QString&, windowId)
345 if (geometry.isNull() || !geometry.isValid()) {
351const QString WindowStateStorage::getDbName()
354 QMetaObject::invokeMethod(m_asyncQuery, "getDbName", Qt::BlockingQueuedConnection,
355 Q_RETURN_ARG(QString, dbName)
357 return QString(dbName);
360Mir::State WindowStateStorage::toMirState(WindowState state) const
362 // assumes a single state (not an OR of several)
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;
376 case WindowStateNormal:
377 case WindowStateRestored:
379 return Mir::RestoredState;
383#include "windowstatestorage.moc"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list