summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-08-31 22:03:58 +1000
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-08-31 22:03:58 +1000
commit7729546469a0f8517d639a9bc98554bd7ec13332 (patch)
treec49bcdea57d211b14b43f9a3cfe706dc24ebae9a
parentplugin/cim: check if cache is valid image (diff)
downloadcaelestia-shell-7729546469a0f8517d639a9bc98554bd7ec13332.tar.gz
caelestia-shell-7729546469a0f8517d639a9bc98554bd7ec13332.tar.bz2
caelestia-shell-7729546469a0f8517d639a9bc98554bd7ec13332.zip
plugin: add FileSystemModel
-rw-r--r--modules/launcher/items/WallpaperItem.qml5
-rw-r--r--nix/default.nix2
-rw-r--r--plugin/src/Caelestia/CMakeLists.txt1
-rw-r--r--plugin/src/Caelestia/filesystemmodel.cpp171
-rw-r--r--plugin/src/Caelestia/filesystemmodel.hpp104
-rw-r--r--services/Wallpapers.qml55
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("."))
- }
}