forked from quickshell/quickshell
		
	core/qmljson: add support for synthesized .qml.json files
This commit is contained in:
		
							parent
							
								
									6026c4ce27
								
							
						
					
					
						commit
						5193426cd7
					
				
					 5 changed files with 112 additions and 18 deletions
				
			
		| 
						 | 
					@ -30,7 +30,7 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
 | 
				
			||||||
    : rootPath(rootPath)
 | 
					    : rootPath(rootPath)
 | 
				
			||||||
    , scanner(std::move(scanner))
 | 
					    , scanner(std::move(scanner))
 | 
				
			||||||
    , urlInterceptor(this->rootPath)
 | 
					    , urlInterceptor(this->rootPath)
 | 
				
			||||||
    , interceptNetFactory(this->scanner.qmldirIntercepts)
 | 
					    , interceptNetFactory(this->scanner.fileIntercepts)
 | 
				
			||||||
    , engine(new QQmlEngine()) {
 | 
					    , engine(new QQmlEngine()) {
 | 
				
			||||||
	g_generations.insert(this->engine, this);
 | 
						g_generations.insert(this->engine, this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,9 +49,9 @@ QUrl QsUrlInterceptor::intercept(
 | 
				
			||||||
	return url;
 | 
						return url;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QsInterceptDataReply::QsInterceptDataReply(const QString& qmldir, QObject* parent)
 | 
					QsInterceptDataReply::QsInterceptDataReply(const QString& data, QObject* parent)
 | 
				
			||||||
    : QNetworkReply(parent)
 | 
					    : QNetworkReply(parent)
 | 
				
			||||||
    , content(qmldir.toUtf8()) {
 | 
					    , content(data.toUtf8()) {
 | 
				
			||||||
	this->setOpenMode(QIODevice::ReadOnly);
 | 
						this->setOpenMode(QIODevice::ReadOnly);
 | 
				
			||||||
	this->setFinished(true);
 | 
						this->setFinished(true);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -65,11 +65,11 @@ qint64 QsInterceptDataReply::readData(char* data, qint64 maxSize) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QsInterceptNetworkAccessManager::QsInterceptNetworkAccessManager(
 | 
					QsInterceptNetworkAccessManager::QsInterceptNetworkAccessManager(
 | 
				
			||||||
    const QHash<QString, QString>& qmldirIntercepts,
 | 
					    const QHash<QString, QString>& fileIntercepts,
 | 
				
			||||||
    QObject* parent
 | 
					    QObject* parent
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
    : QNetworkAccessManager(parent)
 | 
					    : QNetworkAccessManager(parent)
 | 
				
			||||||
    , qmldirIntercepts(qmldirIntercepts) {}
 | 
					    , fileIntercepts(fileIntercepts) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QNetworkReply* QsInterceptNetworkAccessManager::createRequest(
 | 
					QNetworkReply* QsInterceptNetworkAccessManager::createRequest(
 | 
				
			||||||
    QNetworkAccessManager::Operation op,
 | 
					    QNetworkAccessManager::Operation op,
 | 
				
			||||||
| 
						 | 
					@ -80,10 +80,10 @@ QNetworkReply* QsInterceptNetworkAccessManager::createRequest(
 | 
				
			||||||
	if (url.scheme() == "qsintercept") {
 | 
						if (url.scheme() == "qsintercept") {
 | 
				
			||||||
		auto path = url.path();
 | 
							auto path = url.path();
 | 
				
			||||||
		qCDebug(logQsIntercept) << "Got intercept for" << path << "contains"
 | 
							qCDebug(logQsIntercept) << "Got intercept for" << path << "contains"
 | 
				
			||||||
		                        << this->qmldirIntercepts.value(path);
 | 
							                        << this->fileIntercepts.value(path);
 | 
				
			||||||
		auto qmldir = this->qmldirIntercepts.value(path);
 | 
							auto data = this->fileIntercepts.value(path);
 | 
				
			||||||
		if (qmldir != nullptr) {
 | 
							if (data != nullptr) {
 | 
				
			||||||
			return new QsInterceptDataReply(qmldir, this);
 | 
								return new QsInterceptDataReply(data, this);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		auto fileReq = req;
 | 
							auto fileReq = req;
 | 
				
			||||||
| 
						 | 
					@ -98,5 +98,5 @@ QNetworkReply* QsInterceptNetworkAccessManager::createRequest(
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QNetworkAccessManager* QsInterceptNetworkAccessManagerFactory::create(QObject* parent) {
 | 
					QNetworkAccessManager* QsInterceptNetworkAccessManagerFactory::create(QObject* parent) {
 | 
				
			||||||
	return new QsInterceptNetworkAccessManager(this->qmldirIntercepts, parent);
 | 
						return new QsInterceptNetworkAccessManager(this->fileIntercepts, parent);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ class QsInterceptDataReply: public QNetworkReply {
 | 
				
			||||||
	Q_OBJECT;
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	QsInterceptDataReply(const QString& qmldir, QObject* parent = nullptr);
 | 
						QsInterceptDataReply(const QString& data, QObject* parent = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	qint64 readData(char* data, qint64 maxSize) override;
 | 
						qint64 readData(char* data, qint64 maxSize) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ class QsInterceptNetworkAccessManager: public QNetworkAccessManager {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	QsInterceptNetworkAccessManager(
 | 
						QsInterceptNetworkAccessManager(
 | 
				
			||||||
	    const QHash<QString, QString>& qmldirIntercepts,
 | 
						    const QHash<QString, QString>& fileIntercepts,
 | 
				
			||||||
	    QObject* parent = nullptr
 | 
						    QObject* parent = nullptr
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,15 +55,15 @@ protected:
 | 
				
			||||||
	) override;
 | 
						) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	const QHash<QString, QString>& qmldirIntercepts;
 | 
						const QHash<QString, QString>& fileIntercepts;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class QsInterceptNetworkAccessManagerFactory: public QQmlNetworkAccessManagerFactory {
 | 
					class QsInterceptNetworkAccessManagerFactory: public QQmlNetworkAccessManagerFactory {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	QsInterceptNetworkAccessManagerFactory(const QHash<QString, QString>& qmldirIntercepts)
 | 
						QsInterceptNetworkAccessManagerFactory(const QHash<QString, QString>& fileIntercepts)
 | 
				
			||||||
	    : qmldirIntercepts(qmldirIntercepts) {}
 | 
						    : fileIntercepts(fileIntercepts) {}
 | 
				
			||||||
	QNetworkAccessManager* create(QObject* parent) override;
 | 
						QNetworkAccessManager* create(QObject* parent) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	const QHash<QString, QString>& qmldirIntercepts;
 | 
						const QHash<QString, QString>& fileIntercepts;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,18 @@
 | 
				
			||||||
#include "scan.hpp"
 | 
					#include "scan.hpp"
 | 
				
			||||||
 | 
					#include <cmath>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <qcontainerfwd.h>
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
#include <qdir.h>
 | 
					#include <qdir.h>
 | 
				
			||||||
#include <qfileinfo.h>
 | 
					#include <qfileinfo.h>
 | 
				
			||||||
 | 
					#include <qjsonarray.h>
 | 
				
			||||||
 | 
					#include <qjsondocument.h>
 | 
				
			||||||
 | 
					#include <qjsonobject.h>
 | 
				
			||||||
 | 
					#include <qjsonvalue.h>
 | 
				
			||||||
#include <qlogging.h>
 | 
					#include <qlogging.h>
 | 
				
			||||||
#include <qloggingcategory.h>
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qpair.h>
 | 
				
			||||||
#include <qstring.h>
 | 
					#include <qstring.h>
 | 
				
			||||||
 | 
					#include <qstringliteral.h>
 | 
				
			||||||
#include <qtextstream.h>
 | 
					#include <qtextstream.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Q_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
 | 
					Q_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
 | 
				
			||||||
| 
						 | 
					@ -32,6 +39,9 @@ void QmlScanner::scanDir(const QString& path) {
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				entries.push_back(entry);
 | 
									entries.push_back(entry);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							} else if (entry.at(0).isUpper() && entry.endsWith(".qml.json")) {
 | 
				
			||||||
 | 
								this->scanQmlJson(dir.filePath(entry));
 | 
				
			||||||
 | 
								singletons.push_back(entry.first(entry.length() - 5));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,7 +63,7 @@ void QmlScanner::scanDir(const QString& path) {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		qCDebug(logQmlScanner) << "Synthesized qmldir for" << path << qPrintable("\n" + qmldir);
 | 
							qCDebug(logQmlScanner) << "Synthesized qmldir for" << path << qPrintable("\n" + qmldir);
 | 
				
			||||||
		this->qmldirIntercepts.insert(QDir(path).filePath("qmldir"), qmldir);
 | 
							this->fileIntercepts.insert(QDir(path).filePath("qmldir"), qmldir);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -125,3 +135,84 @@ bool QmlScanner::scanQmlFile(const QString& path) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return singleton;
 | 
						return singleton;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void QmlScanner::scanQmlJson(const QString& path) {
 | 
				
			||||||
 | 
						qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto file = QFile(path);
 | 
				
			||||||
 | 
						if (!file.open(QFile::ReadOnly | QFile::Text)) {
 | 
				
			||||||
 | 
							qCWarning(logQmlScanner) << "Failed to open file" << path;
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto data = file.readAll();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Importing this makes CI builds fail for some reason.
 | 
				
			||||||
 | 
						QJsonParseError error; // NOLINT (misc-include-cleaner)
 | 
				
			||||||
 | 
						auto json = QJsonDocument::fromJson(data, &error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (error.error != QJsonParseError::NoError) {
 | 
				
			||||||
 | 
							qCCritical(logQmlScanner).nospace()
 | 
				
			||||||
 | 
							    << "Failed to parse qml.json file at " << path << ": " << error.errorString();
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const QString body =
 | 
				
			||||||
 | 
						    "pragma Singleton\nimport QtQuick as Q\n\n" % QmlScanner::jsonToQml(json.object()).second;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qCDebug(logQmlScanner) << "Synthesized qml file for" << path << qPrintable("\n" + body);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->fileIntercepts.insert(path.first(path.length() - 5), body);
 | 
				
			||||||
 | 
						this->scannedFiles.push_back(path);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QPair<QString, QString> QmlScanner::jsonToQml(const QJsonValue& value, int indent) {
 | 
				
			||||||
 | 
						if (value.isObject()) {
 | 
				
			||||||
 | 
							const auto& object = value.toObject();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto valIter = object.constBegin();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							QString accum = "Q.QtObject {\n";
 | 
				
			||||||
 | 
							for (const auto& key: object.keys()) {
 | 
				
			||||||
 | 
								const auto& val = *valIter++;
 | 
				
			||||||
 | 
								auto [type, repr] = QmlScanner::jsonToQml(val, indent + 2);
 | 
				
			||||||
 | 
								accum += QString(' ').repeated(indent + 2) % "readonly property " % type % ' ' % key % ": "
 | 
				
			||||||
 | 
								       % repr % ";\n";
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							accum += QString(' ').repeated(indent) % '}';
 | 
				
			||||||
 | 
							return qMakePair(QStringLiteral("Q.QtObject"), accum);
 | 
				
			||||||
 | 
						} else if (value.isArray()) {
 | 
				
			||||||
 | 
							return qMakePair(
 | 
				
			||||||
 | 
							    QStringLiteral("var"),
 | 
				
			||||||
 | 
							    QJsonDocument(value.toArray()).toJson(QJsonDocument::Compact)
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						} else if (value.isString()) {
 | 
				
			||||||
 | 
							const auto& str = value.toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (str.startsWith('#') && (str.length() == 4 || str.length() == 7 || str.length() == 9)) {
 | 
				
			||||||
 | 
								for (auto c: str.sliced(1)) {
 | 
				
			||||||
 | 
									if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
 | 
				
			||||||
 | 
										goto noncolor;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return qMakePair(QStringLiteral("Q.color"), '"' % str % '"');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						noncolor:
 | 
				
			||||||
 | 
							return qMakePair(QStringLiteral("string"), '"' % QString(str).replace("\"", "\\\"") % '"');
 | 
				
			||||||
 | 
						} else if (value.isDouble()) {
 | 
				
			||||||
 | 
							auto num = value.toDouble();
 | 
				
			||||||
 | 
							double whole = 0;
 | 
				
			||||||
 | 
							if (std::modf(num, &whole) == 0.0) {
 | 
				
			||||||
 | 
								return qMakePair(QStringLiteral("int"), QString::number(static_cast<int>(whole)));
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return qMakePair(QStringLiteral("real"), QString::number(num));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if (value.isBool()) {
 | 
				
			||||||
 | 
							return qMakePair(QStringLiteral("bool"), value.toBool() ? "true" : "false");
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return qMakePair(QStringLiteral("var"), "null");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,8 +20,11 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QVector<QString> scannedDirs;
 | 
						QVector<QString> scannedDirs;
 | 
				
			||||||
	QVector<QString> scannedFiles;
 | 
						QVector<QString> scannedFiles;
 | 
				
			||||||
	QHash<QString, QString> qmldirIntercepts;
 | 
						QHash<QString, QString> fileIntercepts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	QDir rootPath;
 | 
						QDir rootPath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void scanQmlJson(const QString& path);
 | 
				
			||||||
 | 
						[[nodiscard]] static QPair<QString, QString> jsonToQml(const QJsonValue& value, int indent = 0);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue