mirror of
https://github.com/hyprwm/hyprpaper.git
synced 2025-12-20 04:30:02 +01:00
ui: add support for dynamic wallpapers
- 'path' can contain multiple images comma separated (including directories) - 'timeout' is used to define how long the wallpaper should be shown in seconds. Default: 30s
This commit is contained in:
parent
b431a94cfb
commit
9c75c84882
5 changed files with 135 additions and 15 deletions
|
|
@ -1,7 +1,10 @@
|
|||
#include "ConfigManager.hpp"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <hyprlang.hpp>
|
||||
#include <hyprutils/path/Path.hpp>
|
||||
#include <hyprutils/string/ConstVarList.hpp>
|
||||
#include <string>
|
||||
#include <sys/ucontext.h>
|
||||
#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<std::string, std::string> resolvePath(const std::string_vie
|
|||
return CAN;
|
||||
}
|
||||
|
||||
static std::expected<std::string, std::string> getFullPath(const std::string_view& sv) {
|
||||
if (sv.empty())
|
||||
static std::expected<std::string, std::string> 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::vector<std::string>, 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<std::string> 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::SSetting> CConfigManager::getSettings() {
|
||||
|
|
@ -78,11 +131,13 @@ std::vector<CConfigManager::SSetting> CConfigManager::getSettings() {
|
|||
|
||||
for (auto& key : keys) {
|
||||
std::string monitor, fitMode, path;
|
||||
int timeout;
|
||||
|
||||
try {
|
||||
monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("wallpaper", "monitor", key.c_str()));
|
||||
fitMode = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("wallpaper", "fit_mode", key.c_str()));
|
||||
path = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("wallpaper", "path", key.c_str()));
|
||||
timeout = std::any_cast<Hyprlang::INT>(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::SSetting> 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;
|
||||
|
|
|
|||
|
|
@ -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<std::string> paths;
|
||||
int timeout;
|
||||
uint32_t id = 0;
|
||||
};
|
||||
|
||||
constexpr static const uint32_t SETTING_INVALID = 0;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<Hyprtoolkit::IOutput> output, const std::string_view& path, Hyprtoolkit::eImageFitMode fitMode) : m_monitorName(output->port()) {
|
||||
struct CWallpaperTarget::ImagesData {
|
||||
public:
|
||||
ImagesData(Hyprtoolkit::eImageFitMode fitMode, const std::vector<std::string>& images, const int timeout = 0) :
|
||||
fitMode(fitMode), images(images), timeout(timeout > 0 ? timeout : 30) {}
|
||||
|
||||
const Hyprtoolkit::eImageFitMode fitMode;
|
||||
const std::vector<std::string> images;
|
||||
const int timeout;
|
||||
|
||||
std::string nextImage() {
|
||||
current = (current + 1) % images.size();
|
||||
return images[current];
|
||||
}
|
||||
|
||||
private:
|
||||
size_t current = 0;
|
||||
};
|
||||
|
||||
CWallpaperTarget::CWallpaperTarget(SP<Hyprtoolkit::IBackend> backend, SP<Hyprtoolkit::IOutput> output, const std::vector<std::string>& 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<Hyprlang::INT>(g_config->hyprlang(), "splash");
|
||||
static const auto PSPLASHOFFSET = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(g_config->hyprlang(), "splash_offset");
|
||||
static const auto PSPLASHALPHA = Hyprlang::CSimpleConfigValue<Hyprlang::FLOAT>(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<Hyprtoolkit::IOutput> 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<Hyprtoolkit::IOutput> 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<ImagesData>(fitMode, path, timeout);
|
||||
m_timer =
|
||||
m_backend->addTimer(std::chrono::milliseconds(std::chrono::seconds(m_imagesData->timeout)), [this](ASP<Hyprtoolkit::CTimer> 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<Hyprtoolkit::IOutput> 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<Hyprtoolkit::CTimer> self, void*) { onRepeatTimer(); }, nullptr);
|
||||
}
|
||||
|
||||
void CUI::registerOutput(const SP<Hyprtoolkit::IOutput>& mon) {
|
||||
g_matcher->registerOutput(mon->port());
|
||||
if (IPC::g_IPCSocket)
|
||||
|
|
@ -158,5 +207,5 @@ void CUI::targetChanged(const SP<Hyprtoolkit::IOutput>& mon) {
|
|||
|
||||
std::erase_if(m_targets, [&mon](const auto& e) { return e->m_monitorName == mon->port(); });
|
||||
|
||||
m_targets.emplace_back(makeShared<CWallpaperTarget>(mon, TARGET->get().path, toFitMode(TARGET->get().fitMode)));
|
||||
m_targets.emplace_back(makeShared<CWallpaperTarget>(m_backend, mon, TARGET->get().paths, toFitMode(TARGET->get().fitMode), TARGET->get().timeout));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include <hyprtoolkit/core/Backend.hpp>
|
||||
#include <hyprtoolkit/core/Timer.hpp>
|
||||
#include <hyprtoolkit/window/Window.hpp>
|
||||
#include <hyprtoolkit/element/Text.hpp>
|
||||
#include <hyprtoolkit/element/Null.hpp>
|
||||
|
|
@ -15,8 +16,9 @@
|
|||
|
||||
class CWallpaperTarget {
|
||||
public:
|
||||
CWallpaperTarget(SP<Hyprtoolkit::IOutput> output, const std::string_view& path, Hyprtoolkit::eImageFitMode fitMode = Hyprtoolkit::IMAGE_FIT_MODE_COVER);
|
||||
~CWallpaperTarget() = default;
|
||||
CWallpaperTarget(SP<Hyprtoolkit::IBackend> backend, SP<Hyprtoolkit::IOutput> output, const std::vector<std::string>& 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<ImagesData> m_imagesData;
|
||||
ASP<Hyprtoolkit::CTimer> m_timer;
|
||||
SP<Hyprtoolkit::IBackend> m_backend;
|
||||
SP<Hyprtoolkit::IWindow> m_window;
|
||||
SP<Hyprtoolkit::CNullElement> m_null;
|
||||
SP<Hyprtoolkit::CRectangleElement> m_bg;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue