diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 984e480..63cddf7 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -1,7 +1,10 @@ #include "ConfigManager.hpp" +#include +#include #include #include #include +#include #include #include #include "../helpers/Logger.hpp" @@ -9,6 +12,14 @@ using namespace std::string_literals; +[[nodiscard]] static bool isImage(const std::filesystem::path& path) { + static constexpr std::array exts{".jpg", ".jpeg", ".png", ".bmp", ".webp", ".svg"}; + + auto ext = path.extension().string(); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + return std::ranges::any_of(exts, [&ext](const auto& e) { return ext == e; }); +} + static std::string getMainConfigPath() { static const auto paths = Hyprutils::Path::findConfig("hyprpaper"); @@ -30,6 +41,7 @@ void CConfigManager::init() { m_config.addSpecialConfigValue("wallpaper", "monitor", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("wallpaper", "path", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("wallpaper", "fit_mode", Hyprlang::STRING{"cover"}); + m_config.addSpecialConfigValue("wallpaper", "timeout", Hyprlang::INT{0}); m_config.commence(); @@ -55,19 +67,60 @@ static std::expected resolvePath(const std::string_vie return CAN; } -static std::expected getFullPath(const std::string_view& sv) { - if (sv.empty()) +static std::expected getPath(const std::string_view& path) { + if (path.empty()) return std::unexpected("empty path"); - if (sv[0] == '~') { + if (path[0] == '~') { static auto HOME = getenv("HOME"); if (!HOME || HOME[0] == '\0') return std::unexpected("home path but no $HOME"); - return resolvePath(std::string{HOME} + "/"s + std::string{sv.substr(1)}); + return resolvePath(std::string{HOME} + "/"s + std::string{path.substr(1)}); } - return resolvePath(sv); + return resolvePath(path); +} + +static std::expected, std::string> getFullPath(const std::string& sv) { + if (sv.empty()) + return std::unexpected("empty path"); + + static constexpr const size_t maxImagesCount{100}; + + const auto paths = Hyprutils::String::CConstVarList(sv); + std::vector result; + result.reserve(paths.size()); + + for (const auto& path : paths) { + if (result.size() >= maxImagesCount) { + g_logger->log(LOG_WARN, "Maximum number of images ({}) reached", maxImagesCount); + break; + } + + const auto resolved = getPath(path); + if (!resolved) + return std::unexpected(resolved.error()); + + const auto resolvedPath = resolved.value(); + if (!std::filesystem::exists(resolvedPath)) { + g_logger->log(LOG_WARN, "File '{}' does not exist", resolvedPath); + continue; + } + + if (std::filesystem::is_directory(resolvedPath)) + for (const auto& entry : std::filesystem::directory_iterator(resolvedPath, std::filesystem::directory_options::skip_permission_denied)) { + if (entry.is_regular_file() && isImage(entry.path())) { + result.push_back(entry.path()); + } + } + else if (isImage(resolvedPath)) + result.push_back(resolvedPath); + else + g_logger->log(LOG_WARN, "File '{}' is not an image", resolvedPath); + } + + return result; } std::vector CConfigManager::getSettings() { @@ -78,11 +131,13 @@ std::vector CConfigManager::getSettings() { for (auto& key : keys) { std::string monitor, fitMode, path; + int timeout; try { monitor = std::any_cast(m_config.getSpecialConfigValue("wallpaper", "monitor", key.c_str())); fitMode = std::any_cast(m_config.getSpecialConfigValue("wallpaper", "fit_mode", key.c_str())); path = std::any_cast(m_config.getSpecialConfigValue("wallpaper", "path", key.c_str())); + timeout = std::any_cast(m_config.getSpecialConfigValue("wallpaper", "timeout", key.c_str())); } catch (...) { g_logger->log(LOG_ERR, "Failed parsing wallpaper for key {}", key); continue; @@ -95,7 +150,12 @@ std::vector CConfigManager::getSettings() { continue; } - result.emplace_back(SSetting{.monitor = std::move(monitor), .fitMode = std::move(fitMode), .path = RESOLVE_PATH.value()}); + if (RESOLVE_PATH.value().empty()) { + g_logger->log(LOG_ERR, "Provided path(s) '{}' does not contain a valid image", path); + continue; + } + + result.emplace_back(SSetting{.monitor = std::move(monitor), .fitMode = std::move(fitMode), .paths = RESOLVE_PATH.value(), .timeout = timeout}); } return result; diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index 65fca52..a9c57fe 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -14,8 +14,10 @@ class CConfigManager { CConfigManager(CConfigManager&&) = delete; struct SSetting { - std::string monitor, fitMode, path; - uint32_t id = 0; + std::string monitor, fitMode; + std::vector paths; + int timeout; + uint32_t id = 0; }; constexpr static const uint32_t SETTING_INVALID = 0; diff --git a/src/ipc/IPC.cpp b/src/ipc/IPC.cpp index 706edad..c54aa08 100644 --- a/src/ipc/IPC.cpp +++ b/src/ipc/IPC.cpp @@ -85,7 +85,7 @@ void CWallpaperObject::apply() { g_matcher->addState(CConfigManager::SSetting{ .monitor = std::move(m_monitor), .fitMode = fitModeToStr(m_fitMode), - .path = std::move(m_path), + .paths = std::vector{std::move(m_path)}, }); m_object->sendSuccess(); diff --git a/src/ui/UI.cpp b/src/ui/UI.cpp index 072a0eb..3b96064 100644 --- a/src/ui/UI.cpp +++ b/src/ui/UI.cpp @@ -1,4 +1,5 @@ #include "UI.hpp" +#include "../defines.hpp" #include "../helpers/Logger.hpp" #include "../ipc/HyprlandSocket.hpp" #include "../ipc/IPC.hpp" @@ -12,13 +13,34 @@ CUI::~CUI() { m_targets.clear(); } -CWallpaperTarget::CWallpaperTarget(SP output, const std::string_view& path, Hyprtoolkit::eImageFitMode fitMode) : m_monitorName(output->port()) { +struct CWallpaperTarget::ImagesData { + public: + ImagesData(Hyprtoolkit::eImageFitMode fitMode, const std::vector& images, const int timeout = 0) : + fitMode(fitMode), images(images), timeout(timeout > 0 ? timeout : 30) {} + + const Hyprtoolkit::eImageFitMode fitMode; + const std::vector images; + const int timeout; + + std::string nextImage() { + current = (current + 1) % images.size(); + return images[current]; + } + + private: + size_t current = 0; +}; + +CWallpaperTarget::CWallpaperTarget(SP backend, SP output, const std::vector& path, Hyprtoolkit::eImageFitMode fitMode, + const int timeout) : m_monitorName(output->port()), m_backend(backend) { static const auto SPLASH_REPLY = HyprlandSocket::getFromSocket("/splash"); static const auto PENABLESPLASH = Hyprlang::CSimpleConfigValue(g_config->hyprlang(), "splash"); static const auto PSPLASHOFFSET = Hyprlang::CSimpleConfigValue(g_config->hyprlang(), "splash_offset"); static const auto PSPLASHALPHA = Hyprlang::CSimpleConfigValue(g_config->hyprlang(), "splash_opacity"); + ASSERT(path.size() > 0); + m_window = Hyprtoolkit::CWindowBuilder::begin() ->type(Hyprtoolkit::HT_WINDOW_LAYER) ->prefferedOutput(output) @@ -33,9 +55,10 @@ CWallpaperTarget::CWallpaperTarget(SP output, const std::s ->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1, 1}}) ->color([] { return Hyprtoolkit::CHyprColor{0xFF000000}; }) ->commence(); - m_null = Hyprtoolkit::CNullBuilder::begin()->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1, 1}})->commence(); + m_null = Hyprtoolkit::CNullBuilder::begin()->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1, 1}})->commence(); + m_image = Hyprtoolkit::CImageBuilder::begin() - ->path(std::string{path}) + ->path(std::string{path.front()}) ->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}}) ->sync(true) ->fitMode(fitMode) @@ -44,6 +67,12 @@ CWallpaperTarget::CWallpaperTarget(SP output, const std::s m_image->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE); m_image->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_CENTER, true); + if (path.size() > 1) { + m_imagesData = makeUnique(fitMode, path, timeout); + m_timer = + m_backend->addTimer(std::chrono::milliseconds(std::chrono::seconds(m_imagesData->timeout)), [this](ASP self, void*) { onRepeatTimer(); }, nullptr); + } + m_window->m_rootElement->addChild(m_bg); m_window->m_rootElement->addChild(m_null); m_null->addChild(m_image); @@ -68,6 +97,26 @@ CWallpaperTarget::CWallpaperTarget(SP output, const std::s m_window->open(); } +CWallpaperTarget::~CWallpaperTarget() { + if (m_timer && !m_timer->passed()) + m_timer->cancel(); +} + +void CWallpaperTarget::onRepeatTimer() { + + ASSERT(m_imagesData); + + m_image->rebuild() + ->path(m_imagesData->nextImage()) + ->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}}) + ->sync(true) + ->fitMode(m_imagesData->fitMode) + ->commence(); + + m_timer = + m_backend->addTimer(std::chrono::milliseconds(std::chrono::seconds(m_imagesData->timeout)), [this](ASP self, void*) { onRepeatTimer(); }, nullptr); +} + void CUI::registerOutput(const SP& mon) { g_matcher->registerOutput(mon->port()); if (IPC::g_IPCSocket) @@ -158,5 +207,5 @@ void CUI::targetChanged(const SP& mon) { std::erase_if(m_targets, [&mon](const auto& e) { return e->m_monitorName == mon->port(); }); - m_targets.emplace_back(makeShared(mon, TARGET->get().path, toFitMode(TARGET->get().fitMode))); + m_targets.emplace_back(makeShared(m_backend, mon, TARGET->get().paths, toFitMode(TARGET->get().fitMode), TARGET->get().timeout)); } diff --git a/src/ui/UI.hpp b/src/ui/UI.hpp index f2d2da8..f817e1a 100644 --- a/src/ui/UI.hpp +++ b/src/ui/UI.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -15,8 +16,9 @@ class CWallpaperTarget { public: - CWallpaperTarget(SP output, const std::string_view& path, Hyprtoolkit::eImageFitMode fitMode = Hyprtoolkit::IMAGE_FIT_MODE_COVER); - ~CWallpaperTarget() = default; + CWallpaperTarget(SP backend, SP output, const std::vector& path, + Hyprtoolkit::eImageFitMode fitMode = Hyprtoolkit::IMAGE_FIT_MODE_COVER, const int timeout = 0); + ~CWallpaperTarget(); CWallpaperTarget(const CWallpaperTarget&) = delete; CWallpaperTarget(CWallpaperTarget&) = delete; @@ -25,6 +27,13 @@ class CWallpaperTarget { std::string m_monitorName; private: + void onRepeatTimer(); + + struct ImagesData; + + UP m_imagesData; + ASP m_timer; + SP m_backend; SP m_window; SP m_null; SP m_bg;