core: hash scanned files and don't trigger a reload if matching

Nix builds often trip QFileSystemWatcher, causing random reloads.
This commit is contained in:
outfoxxed 2026-03-11 19:45:14 -07:00
parent bd62179277
commit 9a9c605250
No known key found for this signature in database
GPG key ID: 4C88A185FB89301E
4 changed files with 48 additions and 11 deletions

View file

@ -33,6 +33,7 @@ set shell id.
- IPC operations filter available instances to the current display connection by default.
- PwNodeLinkTracker ignores sound level monitoring programs.
- Replaced breakpad with cpptrace.
- Reloads are prevented if no file content has changed.
## Bug Fixes

View file

@ -209,6 +209,8 @@ bool EngineGeneration::setExtraWatchedFiles(const QVector<QString>& files) {
for (const auto& file: files) {
if (!this->scanner.scannedFiles.contains(file)) {
this->extraWatchedFiles.append(file);
QByteArray data;
this->scanner.readAndHashFile(file, data);
}
}
@ -229,6 +231,11 @@ void EngineGeneration::onFileChanged(const QString& name) {
auto fileInfo = QFileInfo(name);
if (fileInfo.isFile() && fileInfo.size() == 0) return;
if (!this->scanner.hasFileContentChanged(name)) {
qCDebug(logQmlScanner) << "Ignoring file change with unchanged content:" << name;
return;
}
emit this->filesChanged();
}
}
@ -237,6 +244,11 @@ void EngineGeneration::onDirectoryChanged() {
// try to find any files that were just deleted from a replace operation
for (auto& file: this->deletedWatchedFiles) {
if (QFileInfo(file).exists()) {
if (!this->scanner.hasFileContentChanged(file)) {
qCDebug(logQmlScanner) << "Ignoring restored file with unchanged content:" << file;
continue;
}
emit this->filesChanged();
break;
}

View file

@ -3,6 +3,7 @@
#include <utility>
#include <qcontainerfwd.h>
#include <qcryptographichash.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qjsengine.h>
@ -21,6 +22,25 @@
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
bool QmlScanner::readAndHashFile(const QString& path, QByteArray& data) {
auto file = QFile(path);
if (!file.open(QFile::ReadOnly)) return false;
data = file.readAll();
this->fileHashes.insert(path, QCryptographicHash::hash(data, QCryptographicHash::Md5));
return true;
}
bool QmlScanner::hasFileContentChanged(const QString& path) const {
auto it = this->fileHashes.constFind(path);
if (it == this->fileHashes.constEnd()) return true;
auto file = QFile(path);
if (!file.open(QFile::ReadOnly)) return true;
auto newHash = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
return newHash != it.value();
}
void QmlScanner::scanDir(const QDir& dir) {
if (this->scannedDirs.contains(dir)) return;
this->scannedDirs.push_back(dir);
@ -109,13 +129,13 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
qCDebug(logQmlScanner) << "Scanning qml file" << path;
auto file = QFile(path);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
QByteArray fileData;
if (!this->readAndHashFile(path, fileData)) {
qCWarning(logQmlScanner) << "Failed to open file" << path;
return false;
}
auto stream = QTextStream(&file);
auto stream = QTextStream(&fileData);
auto imports = QVector<QString>();
bool inHeader = true;
@ -219,8 +239,6 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
postError("unclosed preprocessor if block");
}
file.close();
if (isOverridden) {
this->fileIntercepts.insert(path, overrideText);
}
@ -257,8 +275,11 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
continue;
}
if (import.endsWith(".js")) this->scannedFiles.push_back(cpath);
else this->scanDir(cpath);
if (import.endsWith(".js")) {
this->scannedFiles.push_back(cpath);
QByteArray jsData;
this->readAndHashFile(cpath, jsData);
} else this->scanDir(cpath);
}
return true;
@ -273,14 +294,12 @@ void QmlScanner::scanQmlRoot(const QString& path) {
bool QmlScanner::scanQmlJson(const QString& path) {
qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
auto file = QFile(path);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
QByteArray data;
if (!this->readAndHashFile(path, data)) {
qCWarning(logQmlScanner) << "Failed to open file" << path;
return false;
}
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);

View file

@ -1,5 +1,6 @@
#pragma once
#include <qbytearray.h>
#include <qcontainerfwd.h>
#include <qdir.h>
#include <qhash.h>
@ -21,6 +22,7 @@ public:
QVector<QDir> scannedDirs;
QVector<QString> scannedFiles;
QHash<QString, QByteArray> fileHashes;
QHash<QString, QString> fileIntercepts;
struct ScanError {
@ -31,6 +33,9 @@ public:
QVector<ScanError> scanErrors;
bool readAndHashFile(const QString& path, QByteArray& data);
[[nodiscard]] bool hasFileContentChanged(const QString& path) const;
private:
QDir rootPath;