feat: defer preview command until states are captured

This commit is contained in:
2026-03-01 05:08:58 +01:00
parent 1e9c175dd5
commit bf2f3d57c7
11 changed files with 232 additions and 187 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ qt_add_qml_module(${CORELIB_NAME}
Config/data.hpp
Config/manager.hpp Config/manager.cpp
logger.hpp logger.cpp
Service/manager.hpp
Service/manager.hpp Service/manager.cpp
Service/wallpaper.hpp Service/wallpaper.cpp
appoptions.hpp appoptions.cpp
)
+1
View File
@@ -141,6 +141,7 @@ struct StyleConfigItems {
struct CacheConfigItems {
bool saveSortMethod = true;
bool savePalette = true;
int maxImageEntries = 1000;
static const QString defaultSortType;
static const QString defaultSortDescending;
+10
View File
@@ -28,6 +28,11 @@ Manager::Manager(
const QString& configPath,
QObject* parent)
: QObject(parent), m_configDir(configDir) {
connect(this, &Manager::stateCaptured, this, [this]() {
m_stateCaptured = true;
WR_INFO("State capture completed");
});
// Load configPath if not empty, otherwise load from default location (configDir + s_DefaultConfigFileName)
if (configPath.isEmpty()) {
WR_INFO(QString("Configuration directory: %1").arg(m_configDir.absolutePath()));
@@ -384,6 +389,11 @@ void Manager::_loadWallpapers() {
}
void Manager::captureState() {
if (m_stateCaptured) {
WR_DEBUG("State already captured, skipping capture");
emit stateCaptured();
}
if (m_pendingCaptures > 0) {
WR_WARN("State capture already in progress, ignoring new capture request");
return;
+3
View File
@@ -59,6 +59,8 @@ class Manager : public QObject {
const CacheConfigItems& getCacheConfig() const { return m_cacheConfig; }
bool isStateCaptured() const { return m_stateCaptured; }
QSize getFocusImageSize() const {
return QSize{m_styleConfig.imageWidth, m_styleConfig.imageHeight} * m_styleConfig.imageFocusScale;
}
@@ -95,6 +97,7 @@ class Manager : public QObject {
QStringList m_wallpapers;
int m_pendingCaptures = 0;
bool m_stateCaptured = false; // changed and accessed in main thread, no lock needed
};
} // namespace WallReel::Core::Config
+1
View File
@@ -25,6 +25,7 @@ WallReel::Core::Image::Data::Data(const QString& path, const QSize& targetSize,
m_id = cacheMgr.cacheKey(m_file, m_targetSize);
m_cachedFile = cacheMgr.getImage(m_id, [this]() { return computeImage(); });
m_dominantColor = cacheMgr.getColor(m_id, [this]() { return computeDominantColor(loadImage()); });
m_isValid = m_cachedFile.isFile() && m_dominantColor.isValid();
}
QImage WallReel::Core::Image::Data::loadImage() const {
+3 -1
View File
@@ -54,6 +54,8 @@ class Data {
QColor m_dominantColor; ///< Dominant color of the image, used for palette matching
QHash<QString, QString> m_colorCache; ///< Cache for palette color matching results, key is palette name, value is matched color name
bool m_isValid = false;
QImage computeImage() const;
QColor computeDominantColor(const QImage& image) const;
@@ -75,7 +77,7 @@ class Data {
QUrl getUrl() const { return QUrl::fromLocalFile(m_cachedFile.absoluteFilePath()); }
bool isValid() const { return m_cachedFile.exists(); }
bool isValid() const { return m_isValid; }
QString getFullPath() const { return m_file.absoluteFilePath(); }
+9 -3
View File
@@ -251,6 +251,12 @@ class Carousel : public QObject {
}
});
// Defer preview until state captured
connect(m_configMgr,
&Config::Manager::stateCaptured,
m_serviceMgr,
&Service::Manager::onStateCaptured);
// Quit on selected
if (m_configMgr->getActionConfig().quitOnSelected) {
QObject::connect(
@@ -286,12 +292,12 @@ class Carousel : public QObject {
setSortDescending(m_cacheMgr->getSetting(
Cache::SettingsType::LastSortDescending,
[]() { return Config::CacheConfigItems::defaultSortDescending; }) == "true");
connect(this, &Carousel::sortTypeChanged, this, [this]() {
connect(app, &QApplication::aboutToQuit, this, [this]() {
m_cacheMgr->storeSetting(
Cache::SettingsType::LastSortType,
Config::sortTypeToString(m_imageMgr->sortType()));
});
connect(this, &Carousel::sortDescendingChanged, this, [this]() {
connect(app, &QApplication::aboutToQuit, this, [this]() {
m_cacheMgr->storeSetting(
Cache::SettingsType::LastSortDescending,
m_imageMgr->sortDescending() ? "true" : "false");
@@ -301,7 +307,7 @@ class Carousel : public QObject {
requestSelectPalette(m_cacheMgr->getSetting(
Cache::SettingsType::LastSelectedPalette,
[]() { return Config::CacheConfigItems::defaultSelectedPalette; }));
connect(this, &Carousel::selectedPaletteChanged, this, [this]() {
connect(app, &QApplication::aboutToQuit, this, [this]() {
m_cacheMgr->storeSetting(
Cache::SettingsType::LastSelectedPalette,
m_paletteMgr->getSelectedPaletteName());
+164
View File
@@ -0,0 +1,164 @@
#include "manager.hpp"
#include "Utils/texttemplate.hpp"
#include "logger.hpp"
WALLREEL_DECLARE_SENDER("ServiceManager")
namespace WallReel::Core::Service {
Manager::Manager(
const Config::ActionConfigItems& actionConfig,
Image::Manager& imageManager,
Palette::Manager& paletteManager,
QObject* parent) : m_actionConfig(actionConfig), m_imageManager(imageManager), m_paletteManager(paletteManager) {
m_wallpaperService = new WallpaperService(m_actionConfig.previewDebounceTime, this);
// Forward signals
// Direct signal 2 signal connection
connect(m_wallpaperService, &WallpaperService::previewCompleted, this, &Manager::previewCompleted);
// Signal 2 slot connection to handle processing state
connect(m_wallpaperService, &WallpaperService::selectCompleted, this, &Manager::_onSelectCompleted);
connect(m_wallpaperService, &WallpaperService::restoreCompleted, this, &Manager::_onRestoreCompleted);
}
void Manager::onStateCaptured() {
m_stateCaptured = true;
if (!m_pendingPreviewId.isEmpty()) {
WR_DEBUG("State captured, executing pending preview for id " + m_pendingPreviewId);
const QString pending = m_pendingPreviewId;
m_pendingPreviewId.clear();
previewWallpaper(pending);
}
}
void Manager::selectWallpaper(const QString& id) {
WR_DEBUG("Select action triggered for id " + id);
if (m_isProcessing) {
WR_DEBUG("Already processing an select action, ignoring new request");
return;
}
m_isProcessing = true;
emit isProcessingChanged();
const auto* data = m_imageManager.imageAt(id);
if (!data || !data->isValid()) {
WR_WARN(QString("No valid image data at id %1. Skipping select action.").arg(id));
m_isProcessing = false;
emit isProcessingChanged();
emit selectCompleted();
}
const auto command = _renderCommand(m_actionConfig.onSelected, _generateVariables(*data));
m_wallpaperService->select(command);
}
void Manager::restore() {
WR_DEBUG("Restore action triggered");
if (m_isProcessing) {
WR_DEBUG("Already processing an restore action, ignoring new request");
return;
}
if (!m_stateCaptured) {
WR_DEBUG("State not captured yet, skipping restore action");
emit restoreCompleted();
return;
}
m_isProcessing = true;
emit isProcessingChanged();
m_wallpaperService->restore(_renderCommand(m_actionConfig.onRestore, m_actionConfig.savedState));
}
void Manager::cancel() {
WR_DEBUG("Cancel action triggered");
m_wallpaperService->stopAll();
emit cancelCompleted();
}
void Manager::previewWallpaper(const QString& id) {
if (!m_stateCaptured) {
WR_DEBUG("State not captured yet, deferring preview for id " + id);
m_pendingPreviewId = id;
emit previewCompleted();
return;
}
WR_DEBUG("Preview action triggered for id " + id);
const auto* data = m_imageManager.imageAt(id);
if (!data || !data->isValid()) {
WR_WARN(QString("No valid image data at id %1. Skipping preview action.").arg(id));
emit previewCompleted();
return;
}
m_wallpaperService->preview(_renderCommand(m_actionConfig.onPreview, _generateVariables(*data)));
}
void Manager::restoreOnQuit() {
if (m_hasSelected) {
Logger::debug("ServiceManager", "Quit with selected wallpaper, no need to restore");
return;
}
Logger::debug("ServiceManager", "Restore on quit");
m_wallpaperService->stopAll();
QEventLoop loop;
connect(m_wallpaperService, &WallpaperService::restoreCompleted, &loop, &QEventLoop::quit);
// Call restore after the event loop starts
QTimer::singleShot(0, this, &Manager::restore);
loop.exec();
}
void Manager::_onSelectCompleted() {
Logger::debug("ServiceManager", "Select completed");
_onProcessCompleted();
m_hasSelected = true;
emit selectCompleted();
}
void Manager::_onRestoreCompleted() {
Logger::debug("ServiceManager", "Restore completed");
_onProcessCompleted();
emit restoreCompleted();
}
void Manager::_onProcessCompleted() {
m_isProcessing = false;
emit isProcessingChanged();
}
QString Manager::_renderCommand(const QString& templateStr, const QHash<QString, QString>& variables) const {
return Utils::renderTemplate(templateStr, variables);
}
QHash<QString, QString> Manager::_generateVariables(const Image::Data& imageData) const {
auto palette = m_paletteManager.getSelectedPaletteName();
if (palette.isEmpty()) {
palette = "null";
}
auto color = m_paletteManager.getCurrentColorName();
if (color.isEmpty()) {
color = "null";
}
auto hex = m_paletteManager.getCurrentColorHex();
if (hex.isEmpty()) {
hex = "null";
}
QHash<QString, QString> ret{
{"path", imageData.getFullPath()},
{"name", imageData.getFileName()},
{"size", QString::number(imageData.getSize())},
{"palette", palette},
{"colorName", color},
{"colorHex", hex},
{"domColorHex", imageData.getDominantColor().name()},
};
ret.insert(m_actionConfig.savedState);
return ret;
}
} // namespace WallReel::Core::Service
+18 -82
View File
@@ -8,7 +8,6 @@
#include "Image/manager.hpp"
#include "Palette/manager.hpp"
#include "Service/wallpaper.hpp"
#include "logger.hpp"
namespace WallReel::Core::Service {
@@ -22,16 +21,7 @@ class Manager : public QObject {
const Config::ActionConfigItems& actionConfig,
Image::Manager& imageManager,
Palette::Manager& paletteManager,
QObject* parent = nullptr) : m_actionConfig(actionConfig), m_imageManager(imageManager), m_paletteManager(paletteManager) {
m_wallpaperService = new WallpaperService(m_actionConfig, m_paletteManager, this);
// Forward signals
// Direct signal 2 signal connection
connect(m_wallpaperService, &WallpaperService::previewCompleted, this, &Manager::previewCompleted);
// Signal 2 slot connection to handle processing state
connect(m_wallpaperService, &WallpaperService::selectCompleted, this, &Manager::_onSelectCompleted);
connect(m_wallpaperService, &WallpaperService::restoreCompleted, this, &Manager::_onRestoreCompleted);
}
QObject* parent = nullptr);
bool isProcessing() const { return m_isProcessing; }
@@ -39,86 +29,25 @@ class Manager : public QObject {
public slots:
void selectWallpaper(const QString& id) {
Logger::debug("ServiceManager", QString("Select wallpaper with id %1").arg(id));
if (m_isProcessing) {
Logger::debug("ServiceManager", "Already processing an select action, ignoring new request");
return;
}
m_isProcessing = true;
emit isProcessingChanged();
const auto* data = m_imageManager.imageAt(id);
if (data) {
m_wallpaperService->select(*data);
} else {
Logger::warn("ServiceManager", QString("No image data at id %1. Skipping select action.").arg(id));
m_isProcessing = false;
emit isProcessingChanged();
emit selectCompleted();
}
}
void onStateCaptured();
void restore() {
Logger::debug("ServiceManager", "Restore states");
if (m_isProcessing) {
Logger::debug("ServiceManager", "Already processing an restore action, ignoring new request");
return;
}
m_isProcessing = true;
emit isProcessingChanged();
m_wallpaperService->restore();
}
void selectWallpaper(const QString& id);
void cancel() {
Logger::debug("ServiceManager", "Cancel action");
m_wallpaperService->stopAll();
emit cancelCompleted();
}
void restore();
void previewWallpaper(const QString& id) {
Logger::debug("ServiceManager", "Preview wallpaper");
const auto* data = m_imageManager.imageAt(id);
if (data) {
m_wallpaperService->preview(*data);
} else {
Logger::warn("ServiceManager", "No image data at id " + id + ". Skipping preview action.");
emit previewCompleted();
}
}
void cancel();
void restoreOnQuit() {
if (m_hasSelected) {
Logger::debug("ServiceManager", "Quit with selected wallpaper, no need to restore");
return;
}
Logger::debug("ServiceManager", "Restore on quit");
m_wallpaperService->stopAll();
QEventLoop loop;
connect(m_wallpaperService, &WallpaperService::restoreCompleted, &loop, &QEventLoop::quit);
// Call restore after the event loop starts
QTimer::singleShot(0, m_wallpaperService, &WallpaperService::restore);
loop.exec();
}
void previewWallpaper(const QString& id);
void restoreOnQuit();
private slots:
void _onSelectCompleted() {
Logger::debug("ServiceManager", "Select completed");
_onProcessCompleted();
m_hasSelected = true;
emit selectCompleted();
}
void _onSelectCompleted();
void _onRestoreCompleted() {
Logger::debug("ServiceManager", "Restore completed");
_onProcessCompleted();
emit restoreCompleted();
}
void _onRestoreCompleted();
void _onProcessCompleted() {
m_isProcessing = false;
emit isProcessingChanged();
}
void _onProcessCompleted();
signals:
void isProcessingChanged();
@@ -127,6 +56,10 @@ class Manager : public QObject {
void restoreCompleted();
void cancelCompleted();
private:
QString _renderCommand(const QString& templateStr, const QHash<QString, QString>& variables) const;
QHash<QString, QString> _generateVariables(const Image::Data& imageData) const;
private:
WallpaperService* m_wallpaperService;
const Config::ActionConfigItems& m_actionConfig;
@@ -135,6 +68,9 @@ class Manager : public QObject {
bool m_isProcessing = false;
bool m_hasSelected = false;
bool m_stateCaptured = false;
QString m_pendingPreviewId;
};
} // namespace WallReel::Core::Service
+14 -82
View File
@@ -1,25 +1,20 @@
#include "Service/wallpaper.hpp"
#include <QColor>
#include <iostream>
#include "Utils/texttemplate.hpp"
#include "logger.hpp"
WALLREEL_DECLARE_SENDER("WallpaperService")
namespace WallReel::Core::Service {
WallpaperService::WallpaperService(
const Config::ActionConfigItems& actionConfig,
const Palette::Manager& paletteManager,
QObject* parent)
: QObject(parent), m_actionConfig(actionConfig), m_paletteManager(paletteManager) {
WallpaperService::WallpaperService(int previewDebounceTime, QObject* parent)
: QObject(parent) {
m_previewDebounceTimer = new QTimer(this);
m_previewDebounceTimer->setSingleShot(true);
m_previewDebounceTimer->setInterval(m_actionConfig.previewDebounceTime);
m_previewDebounceTimer->setInterval(previewDebounceTime);
connect(m_previewDebounceTimer, &QTimer::timeout, this, [this]() {
_doPreview(*m_pendingImageData);
_doPreview(m_pendingPreviewCommand);
});
m_previewProcess = new QProcess(this);
@@ -67,71 +62,29 @@ void WallpaperService::stopAll() {
m_previewDebounceTimer->stop();
}
void WallpaperService::preview(const Image::Data& imageData) {
m_pendingImageData = &imageData;
void WallpaperService::preview(const QString& command) {
m_pendingPreviewCommand = command;
m_previewDebounceTimer->start();
}
void WallpaperService::select(const Image::Data& imageData) {
void WallpaperService::select(const QString& command) {
if (m_selectProcess->state() != QProcess::NotRunning) {
WR_WARN("Previous select command is still running. Ignoring new command.");
return;
}
WR_DEBUG(QString("Select wallpaper: %1").arg(imageData.getFullPath()));
_doSelect(imageData);
_doSelect(command);
}
void WallpaperService::restore() {
void WallpaperService::restore(const QString& command) {
if (m_restoreProcess->state() != QProcess::NotRunning) {
WR_WARN("Previous restore command is still running. Ignoring new command.");
return;
}
WR_DEBUG("Restore state");
_doRestore();
_doRestore(command);
}
QHash<QString, QString> WallpaperService::_generateVariables(const Image::Data& imageData) {
auto palette = m_paletteManager.getSelectedPaletteName();
if (palette.isEmpty()) {
palette = "null";
}
auto color = m_paletteManager.getCurrentColorName();
if (color.isEmpty()) {
color = "null";
}
auto hex = m_paletteManager.getCurrentColorHex();
if (hex.isEmpty()) {
hex = "null";
}
QHash<QString, QString> ret{
{"path", imageData.getFullPath()},
{"name", imageData.getFileName()},
{"size", QString::number(imageData.getSize())},
{"palette", palette},
{"colorName", color},
{"colorHex", hex},
{"domColorHex", imageData.getDominantColor().name()},
};
ret.insert(m_actionConfig.savedState);
return ret;
}
void WallpaperService::_doPreview(const Image::Data& imageData) {
QString path = imageData.getFullPath();
if (path.isEmpty()) {
WR_WARN("No valid image path for preview. Skipping preview action.");
emit previewCompleted();
return;
}
if (m_actionConfig.printPreview) {
std::cout << path.toStdString() << std::endl;
}
const auto variables = _generateVariables(imageData);
auto command = Utils::renderTemplate(m_actionConfig.onPreview, variables);
void WallpaperService::_doPreview(const QString& command) {
if (command.isEmpty()) {
WR_DEBUG("No preview command configured. Skipping preview action.");
emit previewCompleted();
@@ -146,21 +99,7 @@ void WallpaperService::_doPreview(const Image::Data& imageData) {
m_previewProcess->start("sh", QStringList() << "-c" << command);
}
void WallpaperService::_doSelect(const Image::Data& imageData) {
QString path = imageData.getFullPath();
if (path.isEmpty()) {
WR_WARN("No valid image path for select. Skipping select action.");
emit selectCompleted();
return;
}
if (m_actionConfig.printSelected) {
std::cout << path.toStdString() << std::endl;
}
const auto variables = _generateVariables(imageData);
auto command = Utils::renderTemplate(m_actionConfig.onSelected, variables);
void WallpaperService::_doSelect(const QString& command) {
if (command.isEmpty()) {
WR_DEBUG("No select command configured. Skipping select action.");
emit selectCompleted();
@@ -170,16 +109,9 @@ void WallpaperService::_doSelect(const Image::Data& imageData) {
m_selectProcess->start("sh", QStringList() << "-c" << command);
}
void WallpaperService::_doRestore() {
if (m_actionConfig.onRestore.isEmpty()) {
WR_DEBUG("No restore command configured. Skipping restore action.");
emit restoreCompleted();
return;
}
const QString command = Utils::renderTemplate(m_actionConfig.onRestore, m_actionConfig.savedState);
void WallpaperService::_doRestore(const QString& command) {
if (command.isEmpty()) {
WR_DEBUG("Restore command is empty after rendering. Skipping restore action.");
WR_DEBUG("Restore command is empty. Skipping restore action.");
emit restoreCompleted();
return;
}
+8 -18
View File
@@ -4,27 +4,20 @@
#include <QProcess>
#include <QTimer>
#include "Config/data.hpp"
#include "Image/data.hpp"
#include "Palette/manager.hpp"
namespace WallReel::Core::Service {
class WallpaperService : public QObject {
Q_OBJECT
public:
WallpaperService(
const Config::ActionConfigItems& actionConfig,
const Palette::Manager& paletteManager,
QObject* parent = nullptr);
WallpaperService(int previewDebounceTime, QObject* parent = nullptr);
void stopAll();
public slots:
void preview(const Image::Data& imageData); // execute after 500ms of inactivity
void select(const Image::Data& imageData); // execute immediately, ignore if already running
void restore(); // execute immediately, ignore if already running
void preview(const QString& command); // execute after 500ms of inactivity
void select(const QString& command); // execute immediately, ignore if already running
void restore(const QString& command); // execute immediately, ignore if already running
signals:
void previewCompleted();
@@ -32,15 +25,12 @@ class WallpaperService : public QObject {
void restoreCompleted();
private:
void _doPreview(const Image::Data& imageData);
void _doSelect(const Image::Data& imageData);
void _doRestore();
QHash<QString, QString> _generateVariables(const Image::Data& imageData);
void _doPreview(const QString& command);
void _doSelect(const QString& command);
void _doRestore(const QString& command);
const Config::ActionConfigItems& m_actionConfig;
const Palette::Manager& m_paletteManager;
QTimer* m_previewDebounceTimer;
const Image::Data* m_pendingImageData;
QString m_pendingPreviewCommand;
QProcess* m_previewProcess;
QProcess* m_selectProcess;
QProcess* m_restoreProcess;