plugin: add FileSystemModel

This commit is contained in:
2 * r + 2 * t 2025-08-31 22:03:58 +10:00
parent 4838e803a7
commit 7729546469
6 changed files with 288 additions and 50 deletions

View file

@ -3,6 +3,7 @@ import qs.components.effects
import qs.components.images import qs.components.images
import qs.services import qs.services
import qs.config import qs.config
import Caelestia
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import QtQuick import QtQuick
@ -10,7 +11,7 @@ import QtQuick
StyledRect { StyledRect {
id: root id: root
required property Wallpapers.Wallpaper modelData required property FileSystemEntry modelData
required property PersistentProperties visibilities required property PersistentProperties visibilities
scale: 0.5 scale: 0.5
@ -75,7 +76,7 @@ StyledRect {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight elide: Text.ElideRight
renderType: Text.QtRendering renderType: Text.QtRendering
text: root.modelData.name text: root.modelData.relativePath
font.pointSize: Appearance.font.size.normal font.pointSize: Appearance.font.size.normal
} }

View file

@ -19,7 +19,6 @@
bash, bash,
hyprland, hyprland,
coreutils, coreutils,
findutils,
file, file,
material-symbols, material-symbols,
rubik, rubik,
@ -56,7 +55,6 @@
bash bash
hyprland hyprland
coreutils coreutils
findutils
file file
] ]
++ extraRuntimeDeps ++ extraRuntimeDeps

View file

@ -4,6 +4,7 @@ qt_add_qml_module(caelestia
SOURCES SOURCES
cutils.hpp cutils.cpp cutils.hpp cutils.cpp
cachingimagemanager.hpp cachingimagemanager.cpp cachingimagemanager.hpp cachingimagemanager.cpp
filesystemmodel.hpp filesystemmodel.cpp
) )
qt_query_qml_module(caelestia qt_query_qml_module(caelestia

View file

@ -0,0 +1,171 @@
#include "filesystemmodel.hpp"
#include <QObject>
#include <qqmlintegration.h>
#include <QAbstractListModel>
#include <QFileInfo>
#include <QDir>
#include <QDirIterator>
#include <QImageReader>
int FileSystemModel::rowCount(const QModelIndex& parent) const {
Q_UNUSED(parent);
return m_files.size();
}
QVariant FileSystemModel::data(const QModelIndex& index, int role) const {
if (!index.isValid() || index.row() >= m_files.size()) {
return QVariant();
}
const FileSystemEntry* file = m_files.at(index.row());
switch (role) {
case FilePathRole:
return file->path();
case RelativeFilePathRole:
return file->relativePath();
case FileNameRole:
return file->name();
case ParentDirRole:
return file->parentDir();
case FileSizeRole:
return file->size();
default:
return QVariant();
}
}
QHash<int, QByteArray> FileSystemModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[FilePathRole] = "filePath";
roles[RelativeFilePathRole] = "relativeFilePath";
roles[FileNameRole] = "fileName";
roles[ParentDirRole] = "parentDir";
roles[FileSizeRole] = "fileSize";
return roles;
}
QString FileSystemModel::path() const {
return m_path;
}
void FileSystemModel::setPath(const QString& path) {
if (m_path == path) {
return;
}
m_path = path;
emit pathChanged();
m_dir.setPath(m_path);
update();
}
bool FileSystemModel::recursive() const {
return m_recursive;
}
void FileSystemModel::setRecursive(bool recursive) {
if (m_recursive == recursive) {
return;
}
m_recursive = recursive;
emit recursiveChanged();
update();
}
FileSystemModel::Filter FileSystemModel::filter() const {
return m_filter;
}
void FileSystemModel::setFilter(Filter filter) {
if (m_filter == filter) {
return;
}
m_filter = filter;
emit filterChanged();
update();
}
QList<FileSystemEntry*> FileSystemModel::files() const {
return m_files;
}
void FileSystemModel::watchDirIfRecursive(const QString& path) {
if (m_recursive) {
QDirIterator iter(path, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
while (iter.hasNext()) {
m_watcher.addPath(iter.next());
}
}
}
void FileSystemModel::update() {
updateWatcher();
updateFiles();
}
void FileSystemModel::updateWatcher() {
if (!m_watcher.directories().isEmpty()) {
m_watcher.removePaths(m_watcher.directories());
}
if (m_path.isEmpty()) {
return;
}
m_watcher.addPath(m_path);
watchDirIfRecursive(m_path);
}
void FileSystemModel::updateFiles() {
if (m_path.isEmpty()) {
beginResetModel();
qDeleteAll(m_files);
m_files.clear();
emit filesChanged();
endResetModel();
return;
}
beginResetModel();
qDeleteAll(m_files);
m_files.clear();
const auto flags = m_recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags;
std::optional<QDirIterator> iter;
if (m_filter == ImagesOnly) {
QStringList filters;
for (const auto& format : QImageReader::supportedImageFormats()) {
filters << "*." + format;
}
iter.emplace(m_path, filters, QDir::Files, flags);
} else {
iter.emplace(m_path, QDir::Files, flags);
}
while (iter.value().hasNext()) {
QString file = iter.value().next();
if (m_filter == ImagesOnly) {
QImageReader reader(file);
if (reader.canRead()) {
m_files << new FileSystemEntry(file, m_dir.relativeFilePath(file), this);
}
} else {
m_files << new FileSystemEntry(file, m_dir.relativeFilePath(file), this);
}
}
emit filesChanged();
endResetModel();
}

View file

@ -0,0 +1,104 @@
#pragma once
#include <QObject>
#include <qqmlintegration.h>
#include <QAbstractListModel>
#include <QFileSystemWatcher>
#include <QFileInfo>
#include <QDir>
class FileSystemEntry : public QObject {
Q_OBJECT
QML_ELEMENT;
QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel");
Q_PROPERTY(QString path READ path CONSTANT);
Q_PROPERTY(QString relativePath READ relativePath CONSTANT);
Q_PROPERTY(QString name READ name CONSTANT);
Q_PROPERTY(QString parentDir READ parentDir CONSTANT);
Q_PROPERTY(qint64 size READ size CONSTANT);
public:
explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr)
: QObject(parent), m_fileInfo(QFileInfo(path)), m_path(path), m_relativePath(relativePath) {}
QString path() const { return m_path; };
QString relativePath() const { return m_relativePath; };
QString name() const { return m_fileInfo.fileName(); };
QString parentDir() const { return m_fileInfo.absolutePath(); };
qint64 size() const { return m_fileInfo.size(); };
private:
const QFileInfo m_fileInfo;
const QString m_path;
const QString m_relativePath;
};
class FileSystemModel : public QAbstractListModel {
Q_OBJECT;
QML_ELEMENT;
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged);
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged);
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged);
Q_PROPERTY(QList<FileSystemEntry*> files READ files NOTIFY filesChanged);
public:
enum Roles {
FilePathRole = Qt::UserRole + 1,
RelativeFilePathRole,
FileNameRole,
ParentDirRole,
FileSizeRole
};
enum Filter {
NoFilter,
ImagesOnly
};
Q_ENUM(Filter)
explicit FileSystemModel(QObject* parent = nullptr)
: QAbstractListModel(parent), m_recursive(true), m_filter(NoFilter) {
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::watchDirIfRecursive);
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::updateFiles);
}
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
QString path() const;
void setPath(const QString& path);
bool recursive() const;
void setRecursive(bool recursive);
Filter filter() const;
void setFilter(Filter filter);
QList<FileSystemEntry*> files() const;
signals:
void pathChanged();
void recursiveChanged();
void filterChanged();
void filesChanged();
private:
QDir m_dir;
QFileSystemWatcher m_watcher;
QList<FileSystemEntry*> m_files;
QString m_path;
bool m_recursive;
Filter m_filter;
void watchDirIfRecursive(const QString& path);
void update();
void updateWatcher();
void updateFiles();
};

View file

@ -2,6 +2,7 @@ pragma Singleton
import qs.config import qs.config
import qs.utils import qs.utils
import Caelestia
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import QtQuick import QtQuick
@ -39,7 +40,7 @@ Searcher {
reloadableId: "wallpapers" reloadableId: "wallpapers"
list: wallpapers.instances list: wallpapers.files
useFuzzy: Config.launcher.useFuzzy.wallpapers useFuzzy: Config.launcher.useFuzzy.wallpapers
extraOpts: useFuzzy ? ({}) : ({ extraOpts: useFuzzy ? ({}) : ({
forward: false forward: false
@ -71,6 +72,13 @@ Searcher {
} }
} }
FileSystemModel {
id: wallpapers
path: Paths.expandTilde(Paths.wallsdir)
filter: FileSystemModel.ImagesOnly
}
Process { Process {
id: getPreviewColoursProc id: getPreviewColoursProc
@ -82,49 +90,4 @@ Searcher {
} }
} }
} }
Process {
id: getWallsProc
running: true
command: ["find", "-L", Paths.expandTilde(Paths.wallsdir), "-type", "d", "-path", '*/.*', "-prune", "-o", "-not", "-name", '.*', "-type", "f", "-print"]
stdout: StdioCollector {
onStreamFinished: wallpapers.model = text.trim().split("\n").filter(w => Images.isValidImageByName(w)).sort()
}
}
Process {
id: watchWallsProc
running: true
command: ["inotifywait", "-r", "-e", "close_write,moved_to,create", "-m", Paths.expandTilde(Paths.wallsdir)]
stdout: SplitParser {
onRead: data => {
if (Images.isValidImageByName(data))
getWallsProc.running = true;
}
}
}
Connections {
target: Config.paths
function onWallpaperDirChanged(): void {
getWallsProc.running = true;
watchWallsProc.running = false;
watchWallsProc.running = true;
}
}
Variants {
id: wallpapers
Wallpaper {}
}
component Wallpaper: QtObject {
required property string modelData
readonly property string path: modelData
readonly property string name: path.slice(Paths.expandTilde(Paths.wallsdir).length + 1, path.lastIndexOf("."))
}
} }