diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-08-31 22:03:58 +1000 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-08-31 22:03:58 +1000 |
| commit | 7729546469a0f8517d639a9bc98554bd7ec13332 (patch) | |
| tree | c49bcdea57d211b14b43f9a3cfe706dc24ebae9a | |
| parent | plugin/cim: check if cache is valid image (diff) | |
| download | caelestia-shell-7729546469a0f8517d639a9bc98554bd7ec13332.tar.gz caelestia-shell-7729546469a0f8517d639a9bc98554bd7ec13332.tar.bz2 caelestia-shell-7729546469a0f8517d639a9bc98554bd7ec13332.zip | |
plugin: add FileSystemModel
| -rw-r--r-- | modules/launcher/items/WallpaperItem.qml | 5 | ||||
| -rw-r--r-- | nix/default.nix | 2 | ||||
| -rw-r--r-- | plugin/src/Caelestia/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | plugin/src/Caelestia/filesystemmodel.cpp | 171 | ||||
| -rw-r--r-- | plugin/src/Caelestia/filesystemmodel.hpp | 104 | ||||
| -rw-r--r-- | services/Wallpapers.qml | 55 |
6 files changed, 288 insertions, 50 deletions
diff --git a/modules/launcher/items/WallpaperItem.qml b/modules/launcher/items/WallpaperItem.qml index 5a4cc67..0b8f03a 100644 --- a/modules/launcher/items/WallpaperItem.qml +++ b/modules/launcher/items/WallpaperItem.qml @@ -3,6 +3,7 @@ import qs.components.effects import qs.components.images import qs.services import qs.config +import Caelestia import Quickshell import Quickshell.Widgets import QtQuick @@ -10,7 +11,7 @@ import QtQuick StyledRect { id: root - required property Wallpapers.Wallpaper modelData + required property FileSystemEntry modelData required property PersistentProperties visibilities scale: 0.5 @@ -75,7 +76,7 @@ StyledRect { horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight renderType: Text.QtRendering - text: root.modelData.name + text: root.modelData.relativePath font.pointSize: Appearance.font.size.normal } diff --git a/nix/default.nix b/nix/default.nix index d0522a2..b97bc1b 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -19,7 +19,6 @@ bash, hyprland, coreutils, - findutils, file, material-symbols, rubik, @@ -56,7 +55,6 @@ bash hyprland coreutils - findutils file ] ++ extraRuntimeDeps diff --git a/plugin/src/Caelestia/CMakeLists.txt b/plugin/src/Caelestia/CMakeLists.txt index dfc7f78..a56074e 100644 --- a/plugin/src/Caelestia/CMakeLists.txt +++ b/plugin/src/Caelestia/CMakeLists.txt @@ -4,6 +4,7 @@ qt_add_qml_module(caelestia SOURCES cutils.hpp cutils.cpp cachingimagemanager.hpp cachingimagemanager.cpp + filesystemmodel.hpp filesystemmodel.cpp ) qt_query_qml_module(caelestia diff --git a/plugin/src/Caelestia/filesystemmodel.cpp b/plugin/src/Caelestia/filesystemmodel.cpp new file mode 100644 index 0000000..052b065 --- /dev/null +++ b/plugin/src/Caelestia/filesystemmodel.cpp @@ -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(); +} diff --git a/plugin/src/Caelestia/filesystemmodel.hpp b/plugin/src/Caelestia/filesystemmodel.hpp new file mode 100644 index 0000000..8584e87 --- /dev/null +++ b/plugin/src/Caelestia/filesystemmodel.hpp @@ -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(); +}; diff --git a/services/Wallpapers.qml b/services/Wallpapers.qml index 30d7b0e..9e2bf2d 100644 --- a/services/Wallpapers.qml +++ b/services/Wallpapers.qml @@ -2,6 +2,7 @@ pragma Singleton import qs.config import qs.utils +import Caelestia import Quickshell import Quickshell.Io import QtQuick @@ -39,7 +40,7 @@ Searcher { reloadableId: "wallpapers" - list: wallpapers.instances + list: wallpapers.files useFuzzy: Config.launcher.useFuzzy.wallpapers extraOpts: useFuzzy ? ({}) : ({ forward: false @@ -71,6 +72,13 @@ Searcher { } } + FileSystemModel { + id: wallpapers + + path: Paths.expandTilde(Paths.wallsdir) + filter: FileSystemModel.ImagesOnly + } + Process { 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(".")) - } } |