Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/next.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Other Changes

- Added support for Hyprland's new workspace address format.
- Added `DiskCache` pragma which loads the config from a mirror in the runtime dir to enable the qml disk cache, visible in `Component.url` and `Qt.resolvedUrl` results.

## Bug Fixes

Expand Down
31 changes: 23 additions & 8 deletions src/core/generation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ QS_LOGGING_CATEGORY(logScene, "scene");

static QHash<const QQmlEngine*, EngineGeneration*> g_generations; // NOLINT

EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner, QString vfsPath)
: rootPath(rootPath)
, scanner(std::move(scanner))
, urlInterceptor(this->rootPath)
, vfsPath(std::move(vfsPath))
, urlInterceptor(this->rootPath, this->vfsPath)
, interceptNetFactory(this->rootPath, this->scanner.fileIntercepts)
, engine(new QQmlEngine()) {
g_generations.insert(this->engine, this);
Expand All @@ -46,7 +47,12 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
QObject::connect(this->engine, &QQmlEngine::warnings, this, &EngineGeneration::onEngineWarnings);

this->engine->addUrlInterceptor(&this->urlInterceptor);
this->engine->addImportPath("qs:@/");

if (this->vfsPath.isEmpty()) {
this->engine->addImportPath("qs:@/");
} else {
this->engine->addImportPath(this->vfsPath);
}

this->engine->setNetworkAccessManagerFactory(&this->interceptNetFactory);
this->incubationController.initLoop();
Expand All @@ -59,7 +65,7 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
QsEnginePlugin::runConstructGeneration(*this);
}

EngineGeneration::EngineGeneration(): EngineGeneration(QDir(), QmlScanner()) {}
EngineGeneration::EngineGeneration(): EngineGeneration(QDir(), QmlScanner(), QString()) {}

EngineGeneration::~EngineGeneration() {
for (auto* extension: this->extensions.values()) {
Expand Down Expand Up @@ -256,11 +262,9 @@ void EngineGeneration::onDirectoryChanged() {
}
}

void EngineGeneration::onEngineWarnings(const QList<QQmlError>& warnings) {
void EngineGeneration::onEngineWarnings(const QList<QQmlError>& warnings) const {
for (const auto& error: warnings) {
const auto& url = error.url();
auto rel = url.scheme() == "qs" && url.path().startsWith("@/qs/") ? "@" % url.path().sliced(5)
: url.toString();
auto rel = this->relativeUrl(error.url());

QString objectName;
auto desc = error.description();
Expand All @@ -274,6 +278,17 @@ void EngineGeneration::onEngineWarnings(const QList<QQmlError>& warnings) {
}
}

QString EngineGeneration::relativeUrl(const QUrl& url) const {
if (url.scheme() == "qs" && url.path().startsWith("@/qs/")) return '@' % url.path().sliced(5);

if (!this->vfsPath.isEmpty() && url.isLocalFile()) {
const QString prefix = this->vfsPath % "/qs/";
if (url.path().startsWith(prefix)) return '@' % url.path().sliced(prefix.length());
}

return url.toString();
}

void EngineGeneration::registerExtension(const void* key, EngineGenerationExt* extension) {
if (this->extensions.contains(key)) {
delete this->extensions.value(key);
Expand Down
9 changes: 7 additions & 2 deletions src/core/generation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
#include <qqmlerror.h>
#include <qqmlincubator.h>
#include <qquickwindow.h>
#include <qstring.h>
#include <qtclasshelpermacros.h>
#include <qurl.h>

#include "incubator.hpp"
#include "qsintercept.hpp"
Expand All @@ -32,7 +34,7 @@ class EngineGeneration: public QObject {

public:
explicit EngineGeneration();
explicit EngineGeneration(const QDir& rootPath, QmlScanner scanner);
explicit EngineGeneration(const QDir& rootPath, QmlScanner scanner, QString vfsPath);
~EngineGeneration() override;
Q_DISABLE_COPY_MOVE(EngineGeneration);

Expand All @@ -47,6 +49,8 @@ class EngineGeneration: public QObject {
void registerExtension(const void* key, EngineGenerationExt* extension);
EngineGenerationExt* findExtension(const void* key);

[[nodiscard]] QString relativeUrl(const QUrl& url) const;

static EngineGeneration* findEngineGeneration(const QQmlEngine* engine);
static EngineGeneration* findObjectGeneration(const QObject* object);

Expand All @@ -57,6 +61,7 @@ class EngineGeneration: public QObject {
RootWrapper* wrapper = nullptr;
QDir rootPath;
QmlScanner scanner;
QString vfsPath;
QsUrlInterceptor urlInterceptor;
QsInterceptNetworkAccessManagerFactory interceptNetFactory;
QQmlEngine* engine = nullptr;
Expand Down Expand Up @@ -85,7 +90,7 @@ private slots:
void onFileChanged(const QString& name);
void onDirectoryChanged();
void onTrackedWindowDestroyed(QObject* object);
static void onEngineWarnings(const QList<QQmlError>& warnings);
void onEngineWarnings(const QList<QQmlError>& warnings) const;

private:
void postReload();
Expand Down
43 changes: 43 additions & 0 deletions src/core/qsintercept.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ QUrl QsUrlInterceptor::intercept(
return QUrl("qrc:/qs-blackhole");
}

// types are keyed by intercepted url, a second url for a document doubles its singletons
if (!this->vfsPath.isEmpty()
&& (type != QQmlAbstractUrlInterceptor::DataType::UrlString || path.endsWith(".qml")))
{
auto newUrl = url;
newUrl.setScheme("file");
newUrl.setPath(this->toVfsPath(path));
qCDebug(logQsIntercept) << "Rewrote intercept" << url << "to" << newUrl;
return newUrl;
}

// Some types such as Image take into account where they are loading from, and force
// asynchronous loading over a network. qs: is considered to be over a network.
// In those cases we want to return a file:// url so asynchronous loading is not forced.
Expand All @@ -66,9 +77,41 @@ QUrl QsUrlInterceptor::intercept(
}
}

// documents from real paths (Quickshell.shellDir) go to the mirror for the same reason,
// everything else resolved against a mirror document goes back to the real path
if (!this->vfsPath.isEmpty() && url.scheme() == "file") {
auto isDocument =
type == QQmlAbstractUrlInterceptor::DataType::QmlFile
|| type == QQmlAbstractUrlInterceptor::DataType::JavaScriptFile
|| type == QQmlAbstractUrlInterceptor::DataType::QmldirFile
|| (type == QQmlAbstractUrlInterceptor::DataType::UrlString && url.path().endsWith(".qml"));

auto path = url.path();
auto newPath = isDocument ? this->toVfsPath(path) : this->toConfigPath(path);

if (newPath != path) {
auto newUrl = url;
newUrl.setPath(newPath);
qCDebug(logQsIntercept) << "Canonicalized intercept" << url << "to" << newUrl;
return newUrl;
}
}

return url;
}

QString QsUrlInterceptor::toVfsPath(const QString& path) const {
const auto& rootPath = this->configRoot.path();
if (!path.startsWith(rootPath % '/')) return path;
return this->vfsPath % "/qs" % path.sliced(rootPath.length());
}

QString QsUrlInterceptor::toConfigPath(const QString& path) const {
const QString mirrorPath = this->vfsPath % "/qs";
if (!path.startsWith(mirrorPath % '/')) return path;
return this->configRoot.path() % path.sliced(mirrorPath.length());
}

QsInterceptDataReply::QsInterceptDataReply(const QString& data, QObject* parent)
: QNetworkReply(parent)
, content(data.toUtf8()) {
Expand Down
11 changes: 10 additions & 1 deletion src/core/qsintercept.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <utility>

#include <qdir.h>
#include <qhash.h>
#include <qloggingcategory.h>
Expand All @@ -8,6 +10,7 @@
#include <qnetworkrequest.h>
#include <qqmlabstracturlinterceptor.h>
#include <qqmlnetworkaccessmanagerfactory.h>
#include <qstring.h>
#include <qurl.h>

#include "logcat.hpp"
Expand All @@ -16,12 +19,18 @@ QS_DECLARE_LOGGING_CATEGORY(logQsIntercept);

class QsUrlInterceptor: public QQmlAbstractUrlInterceptor {
public:
explicit QsUrlInterceptor(const QDir& configRoot): configRoot(configRoot) {}
explicit QsUrlInterceptor(const QDir& configRoot, QString vfsPath)
: configRoot(configRoot)
, vfsPath(std::move(vfsPath)) {}

QUrl intercept(const QUrl& originalUrl, QQmlAbstractUrlInterceptor::DataType type) override;

private:
[[nodiscard]] QString toVfsPath(const QString& path) const;
[[nodiscard]] QString toConfigPath(const QString& path) const;

QDir configRoot;
QString vfsPath;
};

class QsInterceptDataReply: public QNetworkReply {
Expand Down
36 changes: 27 additions & 9 deletions src/core/rootwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
#include "scan.hpp"
#include "toolsupport.hpp"

RootWrapper::RootWrapper(QString rootPath, QString shellId)
RootWrapper::RootWrapper(QString rootPath, QString shellId, bool diskCache)
: QObject(nullptr)
, rootPath(std::move(rootPath))
, shellId(std::move(shellId))
, diskCache(diskCache)
, originalWorkingDirectory(QDir::current().absolutePath()) {
QObject::connect(
QuickshellSettings::instance(),
Expand Down Expand Up @@ -60,9 +61,14 @@ void RootWrapper::reloadGraph(bool hard) {
auto scanner = QmlScanner(rootPath);
scanner.scanQmlRoot(this->rootPath);

qs::core::QmlToolingSupport::updateTooling(rootPath, scanner);
auto tooling = qs::core::QmlToolingSupport::updateTooling(rootPath, this->diskCache);
this->configDirWatcher.addPath(rootPath.path());

QString mirrorPath;
if (tooling || this->diskCache) {
mirrorPath = qs::core::QmlToolingSupport::updateMirror(rootPath, scanner, this->diskCache);
}

// todo: move into EngineGeneration
if (this->generation != nullptr) {
qInfo() << "Reloading configuration...";
Expand Down Expand Up @@ -95,12 +101,23 @@ void RootWrapper::reloadGraph(bool hard) {
return;
}

auto* generation = new EngineGeneration(rootPath, std::move(scanner));
// the qml disk cache only accepts file urls, so DiskCache loads from the vfs mirror
auto vfsPath = this->diskCache ? mirrorPath : QString();
if (this->diskCache && vfsPath.isEmpty()) {
qWarning() << "Could not create config mirror, QML disk cache disabled";
}

auto* generation = new EngineGeneration(rootPath, std::move(scanner), vfsPath);
generation->wrapper = this;

QUrl url;
url.setScheme("qs");
url.setPath("@/qs/" % rootFile.fileName());
if (vfsPath.isEmpty()) {
url.setScheme("qs");
url.setPath("@/qs/" % rootFile.fileName());
} else {
url = QUrl::fromLocalFile(vfsPath % "/qs/" % rootFile.fileName());
}

auto component = QQmlComponent(generation->engine, url);

if (!component.isReady()) {
Expand All @@ -109,9 +126,7 @@ void RootWrapper::reloadGraph(bool hard) {

auto errors = component.errors();
for (auto& error: errors) {
const auto& url = error.url();
auto rel = url.scheme() == "qs" && url.path().startsWith("@/qs/") ? "@" % url.path().sliced(5)
: url.toString();
auto rel = generation->relativeUrl(error.url());
auto msg = " caused by " % rel % '[' % QString::number(error.line()) % ':'
% QString::number(error.column()) % "]: " % error.description();
errorString += '\n' % msg;
Expand Down Expand Up @@ -211,5 +226,8 @@ void RootWrapper::onWatchedFilesChanged() { this->reloadGraph(false); }
void RootWrapper::updateTooling() {
if (!this->generation) return;
auto configDir = QFileInfo(this->rootPath).dir();
qs::core::QmlToolingSupport::updateTooling(configDir, this->generation->scanner);
auto tooling = qs::core::QmlToolingSupport::updateTooling(configDir, this->diskCache);
if (!tooling && !this->diskCache) return;

qs::core::QmlToolingSupport::updateMirror(configDir, this->generation->scanner, this->diskCache);
}
3 changes: 2 additions & 1 deletion src/core/rootwrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class RootWrapper: public QObject {
Q_OBJECT;

public:
explicit RootWrapper(QString rootPath, QString shellId);
explicit RootWrapper(QString rootPath, QString shellId, bool diskCache);
~RootWrapper() override;
Q_DISABLE_COPY_MOVE(RootWrapper);

Expand All @@ -28,6 +28,7 @@ private slots:
private:
QString rootPath;
QString shellId;
bool diskCache;
EngineGeneration* generation = nullptr;
QString originalWorkingDirectory;
QFileSystemWatcher configDirWatcher;
Expand Down
Loading
Loading