Skip to content

Commit 14278ee

Browse files
committed
core/qmljson: add support for synthesized .qml.json files
1 parent dcfd247 commit 14278ee

File tree

5 files changed

+112
-18
lines changed

5 files changed

+112
-18
lines changed

src/core/generation.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
3030
: rootPath(rootPath)
3131
, scanner(std::move(scanner))
3232
, urlInterceptor(this->rootPath)
33-
, interceptNetFactory(this->scanner.qmldirIntercepts)
33+
, interceptNetFactory(this->scanner.fileIntercepts)
3434
, engine(new QQmlEngine()) {
3535
g_generations.insert(this->engine, this);
3636

src/core/qsintercept.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ QUrl QsUrlInterceptor::intercept(
4949
return url;
5050
}
5151

52-
QsInterceptDataReply::QsInterceptDataReply(const QString& qmldir, QObject* parent)
52+
QsInterceptDataReply::QsInterceptDataReply(const QString& data, QObject* parent)
5353
: QNetworkReply(parent)
54-
, content(qmldir.toUtf8()) {
54+
, content(data.toUtf8()) {
5555
this->setOpenMode(QIODevice::ReadOnly);
5656
this->setFinished(true);
5757
}
@@ -65,11 +65,11 @@ qint64 QsInterceptDataReply::readData(char* data, qint64 maxSize) {
6565
}
6666

6767
QsInterceptNetworkAccessManager::QsInterceptNetworkAccessManager(
68-
const QHash<QString, QString>& qmldirIntercepts,
68+
const QHash<QString, QString>& fileIntercepts,
6969
QObject* parent
7070
)
7171
: QNetworkAccessManager(parent)
72-
, qmldirIntercepts(qmldirIntercepts) {}
72+
, fileIntercepts(fileIntercepts) {}
7373

7474
QNetworkReply* QsInterceptNetworkAccessManager::createRequest(
7575
QNetworkAccessManager::Operation op,
@@ -80,10 +80,10 @@ QNetworkReply* QsInterceptNetworkAccessManager::createRequest(
8080
if (url.scheme() == "qsintercept") {
8181
auto path = url.path();
8282
qCDebug(logQsIntercept) << "Got intercept for" << path << "contains"
83-
<< this->qmldirIntercepts.value(path);
84-
auto qmldir = this->qmldirIntercepts.value(path);
85-
if (qmldir != nullptr) {
86-
return new QsInterceptDataReply(qmldir, this);
83+
<< this->fileIntercepts.value(path);
84+
auto data = this->fileIntercepts.value(path);
85+
if (data != nullptr) {
86+
return new QsInterceptDataReply(data, this);
8787
}
8888

8989
auto fileReq = req;
@@ -98,5 +98,5 @@ QNetworkReply* QsInterceptNetworkAccessManager::createRequest(
9898
}
9999

100100
QNetworkAccessManager* QsInterceptNetworkAccessManagerFactory::create(QObject* parent) {
101-
return new QsInterceptNetworkAccessManager(this->qmldirIntercepts, parent);
101+
return new QsInterceptNetworkAccessManager(this->fileIntercepts, parent);
102102
}

src/core/qsintercept.hpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class QsInterceptDataReply: public QNetworkReply {
2626
Q_OBJECT;
2727

2828
public:
29-
QsInterceptDataReply(const QString& qmldir, QObject* parent = nullptr);
29+
QsInterceptDataReply(const QString& data, QObject* parent = nullptr);
3030

3131
qint64 readData(char* data, qint64 maxSize) override;
3232

@@ -43,7 +43,7 @@ class QsInterceptNetworkAccessManager: public QNetworkAccessManager {
4343

4444
public:
4545
QsInterceptNetworkAccessManager(
46-
const QHash<QString, QString>& qmldirIntercepts,
46+
const QHash<QString, QString>& fileIntercepts,
4747
QObject* parent = nullptr
4848
);
4949

@@ -55,15 +55,15 @@ class QsInterceptNetworkAccessManager: public QNetworkAccessManager {
5555
) override;
5656

5757
private:
58-
const QHash<QString, QString>& qmldirIntercepts;
58+
const QHash<QString, QString>& fileIntercepts;
5959
};
6060

6161
class QsInterceptNetworkAccessManagerFactory: public QQmlNetworkAccessManagerFactory {
6262
public:
63-
QsInterceptNetworkAccessManagerFactory(const QHash<QString, QString>& qmldirIntercepts)
64-
: qmldirIntercepts(qmldirIntercepts) {}
63+
QsInterceptNetworkAccessManagerFactory(const QHash<QString, QString>& fileIntercepts)
64+
: fileIntercepts(fileIntercepts) {}
6565
QNetworkAccessManager* create(QObject* parent) override;
6666

6767
private:
68-
const QHash<QString, QString>& qmldirIntercepts;
68+
const QHash<QString, QString>& fileIntercepts;
6969
};

src/core/scan.cpp

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
#include "scan.hpp"
2+
#include <cmath>
23

34
#include <qcontainerfwd.h>
45
#include <qdir.h>
56
#include <qfileinfo.h>
7+
#include <qjsonarray.h>
8+
#include <qjsondocument.h>
9+
#include <qjsonobject.h>
10+
#include <qjsonvalue.h>
611
#include <qlogging.h>
712
#include <qloggingcategory.h>
13+
#include <qpair.h>
814
#include <qstring.h>
15+
#include <qstringliteral.h>
916
#include <qtextstream.h>
1017

1118
Q_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
@@ -32,6 +39,9 @@ void QmlScanner::scanDir(const QString& path) {
3239
} else {
3340
entries.push_back(entry);
3441
}
42+
} else if (entry.at(0).isUpper() && entry.endsWith(".qml.json")) {
43+
this->scanQmlJson(dir.filePath(entry));
44+
singletons.push_back(entry.first(entry.length() - 5));
3545
}
3646
}
3747

@@ -53,7 +63,7 @@ void QmlScanner::scanDir(const QString& path) {
5363
}
5464

5565
qCDebug(logQmlScanner) << "Synthesized qmldir for" << path << qPrintable("\n" + qmldir);
56-
this->qmldirIntercepts.insert(QDir(path).filePath("qmldir"), qmldir);
66+
this->fileIntercepts.insert(QDir(path).filePath("qmldir"), qmldir);
5767
}
5868
}
5969

@@ -125,3 +135,84 @@ bool QmlScanner::scanQmlFile(const QString& path) {
125135

126136
return singleton;
127137
}
138+
139+
void QmlScanner::scanQmlJson(const QString& path) {
140+
qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
141+
142+
auto file = QFile(path);
143+
if (!file.open(QFile::ReadOnly | QFile::Text)) {
144+
qCWarning(logQmlScanner) << "Failed to open file" << path;
145+
return;
146+
}
147+
148+
auto data = file.readAll();
149+
150+
// Importing this makes CI builds fail for some reason.
151+
QJsonParseError error; // NOLINT (misc-include-cleaner)
152+
auto json = QJsonDocument::fromJson(data, &error);
153+
154+
if (error.error != QJsonParseError::NoError) {
155+
qCCritical(logQmlScanner).nospace()
156+
<< "Failed to parse qml.json file at " << path << ": " << error.errorString();
157+
return;
158+
}
159+
160+
const QString body =
161+
"pragma Singleton\nimport QtQuick as Q\n\n" % QmlScanner::jsonToQml(json.object()).second;
162+
163+
qCDebug(logQmlScanner) << "Synthesized qml file for" << path << qPrintable("\n" + body);
164+
165+
this->fileIntercepts.insert(path.first(path.length() - 5), body);
166+
this->scannedFiles.push_back(path);
167+
}
168+
169+
QPair<QString, QString> QmlScanner::jsonToQml(const QJsonValue& value, int indent) {
170+
if (value.isObject()) {
171+
const auto& object = value.toObject();
172+
173+
auto valIter = object.constBegin();
174+
175+
QString accum = "Q.QtObject {\n";
176+
for (const auto& key: object.keys()) {
177+
const auto& val = *valIter++;
178+
auto [type, repr] = QmlScanner::jsonToQml(val, indent + 2);
179+
accum += QString(' ').repeated(indent + 2) % "readonly property " % type % ' ' % key % ": "
180+
% repr % ";\n";
181+
}
182+
183+
accum += QString(' ').repeated(indent) % '}';
184+
return qMakePair(QStringLiteral("Q.QtObject"), accum);
185+
} else if (value.isArray()) {
186+
return qMakePair(
187+
QStringLiteral("var"),
188+
QJsonDocument(value.toArray()).toJson(QJsonDocument::Compact)
189+
);
190+
} else if (value.isString()) {
191+
const auto& str = value.toString();
192+
193+
if (str.startsWith('#') && (str.length() == 4 || str.length() == 7 || str.length() == 9)) {
194+
for (auto c: str.sliced(1)) {
195+
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
196+
goto noncolor;
197+
}
198+
}
199+
200+
return qMakePair(QStringLiteral("Q.color"), '"' % str % '"');
201+
}
202+
203+
noncolor:
204+
return qMakePair(QStringLiteral("string"), '"' % QString(str).replace("\"", "\\\"") % '"');
205+
} else if (value.isDouble()) {
206+
auto num = value.toDouble();
207+
double whole = 0;
208+
if (std::modf(num, &whole) == 0.0) {
209+
return qMakePair(QStringLiteral("int"), QString::number(static_cast<int>(whole)));
210+
} else {
211+
return qMakePair(QStringLiteral("real"), QString::number(num));
212+
}
213+
} else if (value.isBool()) {
214+
return qMakePair(QStringLiteral("bool"), value.toBool() ? "true" : "false");
215+
} else {
216+
return qMakePair(QStringLiteral("var"), "null");
217+
}
218+
}

src/core/scan.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ class QmlScanner {
2020

2121
QVector<QString> scannedDirs;
2222
QVector<QString> scannedFiles;
23-
QHash<QString, QString> qmldirIntercepts;
23+
QHash<QString, QString> fileIntercepts;
2424

2525
private:
2626
QDir rootPath;
27+
28+
void scanQmlJson(const QString& path);
29+
[[nodiscard]] static QPair<QString, QString> jsonToQml(const QJsonValue& value, int indent = 0);
2730
};

0 commit comments

Comments
 (0)