Skip to content

Commit bf0ded4

Browse files
committed
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
1 parent b431a94 commit bf0ded4

5 files changed

Lines changed: 141 additions & 15 deletions

File tree

src/config/ConfigManager.cpp

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
#include "ConfigManager.hpp"
2+
#include <algorithm>
3+
#include <cctype>
24
#include <filesystem>
35
#include <hyprlang.hpp>
46
#include <hyprutils/path/Path.hpp>
7+
#include <hyprutils/string/ConstVarList.hpp>
58
#include <string>
69
#include <sys/ucontext.h>
710
#include "../helpers/Logger.hpp"
811
#include "WallpaperMatcher.hpp"
912

1013
using namespace std::string_literals;
1114

15+
[[nodiscard]] static bool isImage(const std::filesystem::path& path) {
16+
static constexpr std::array exts{".jpg", ".jpeg", ".png", ".bmp", ".webp", ".svg"};
17+
18+
auto ext = path.extension().string();
19+
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
20+
return std::ranges::any_of(exts, [&ext](const auto& e) { return ext == e; });
21+
}
22+
1223
static std::string getMainConfigPath() {
1324
static const auto paths = Hyprutils::Path::findConfig("hyprpaper");
1425

@@ -30,6 +41,7 @@ void CConfigManager::init() {
3041
m_config.addSpecialConfigValue("wallpaper", "monitor", Hyprlang::STRING{""});
3142
m_config.addSpecialConfigValue("wallpaper", "path", Hyprlang::STRING{""});
3243
m_config.addSpecialConfigValue("wallpaper", "fit_mode", Hyprlang::STRING{"cover"});
44+
m_config.addSpecialConfigValue("wallpaper", "timeout", Hyprlang::INT{0});
3345

3446
m_config.commence();
3547

@@ -55,19 +67,62 @@ static std::expected<std::string, std::string> resolvePath(const std::string_vie
5567
return CAN;
5668
}
5769

58-
static std::expected<std::string, std::string> getFullPath(const std::string_view& sv) {
59-
if (sv.empty())
70+
static std::expected<std::string, std::string> getPath(const std::string_view& path) {
71+
if (path.empty()) {
6072
return std::unexpected("empty path");
73+
}
6174

62-
if (sv[0] == '~') {
75+
if (path[0] == '~') {
6376
static auto HOME = getenv("HOME");
6477
if (!HOME || HOME[0] == '\0')
6578
return std::unexpected("home path but no $HOME");
6679

67-
return resolvePath(std::string{HOME} + "/"s + std::string{sv.substr(1)});
80+
return resolvePath(std::string{HOME} + "/"s + std::string{path.substr(1)});
6881
}
6982

70-
return resolvePath(sv);
83+
return resolvePath(path);
84+
}
85+
86+
static std::expected<std::vector<std::string>, std::string> getFullPath(const std::string& sv) {
87+
if (sv.empty())
88+
return std::unexpected("empty path");
89+
90+
static constexpr const size_t maxImagesCount{100};
91+
92+
const auto paths = Hyprutils::String::CConstVarList(sv);
93+
std::vector<std::string> result;
94+
result.reserve(paths.size());
95+
96+
for (const auto& path : paths) {
97+
if (result.size() >= maxImagesCount) {
98+
g_logger->log(LOG_WARN, "Maximum number of images ({}) reached", maxImagesCount);
99+
break;
100+
}
101+
102+
const auto resolved = getPath(path);
103+
if (!resolved) {
104+
return std::unexpected(resolved.error());
105+
}
106+
107+
const auto resolvedPath = resolved.value();
108+
109+
if (std::filesystem::is_directory(resolvedPath)) {
110+
for (const auto& entry : std::filesystem::directory_iterator(resolvedPath)) {
111+
g_logger->log(LOG_DEBUG, "Found image: {}", entry.path().string());
112+
if (entry.is_regular_file() && isImage(entry.path())) {
113+
result.push_back(entry.path());
114+
}
115+
}
116+
} else {
117+
if (isImage(resolvedPath)) {
118+
result.push_back(resolvedPath);
119+
} else {
120+
g_logger->log(LOG_WARN, "File '{}' is not an image", resolvedPath);
121+
}
122+
}
123+
}
124+
125+
return result;
71126
}
72127

73128
std::vector<CConfigManager::SSetting> CConfigManager::getSettings() {
@@ -78,11 +133,13 @@ std::vector<CConfigManager::SSetting> CConfigManager::getSettings() {
78133

79134
for (auto& key : keys) {
80135
std::string monitor, fitMode, path;
136+
int timeout;
81137

82138
try {
83139
monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("wallpaper", "monitor", key.c_str()));
84140
fitMode = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("wallpaper", "fit_mode", key.c_str()));
85141
path = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("wallpaper", "path", key.c_str()));
142+
timeout = std::any_cast<Hyprlang::INT>(m_config.getSpecialConfigValue("wallpaper", "timeout", key.c_str()));
86143
} catch (...) {
87144
g_logger->log(LOG_ERR, "Failed parsing wallpaper for key {}", key);
88145
continue;
@@ -95,7 +152,12 @@ std::vector<CConfigManager::SSetting> CConfigManager::getSettings() {
95152
continue;
96153
}
97154

98-
result.emplace_back(SSetting{.monitor = std::move(monitor), .fitMode = std::move(fitMode), .path = RESOLVE_PATH.value()});
155+
if (RESOLVE_PATH.value().empty()) {
156+
g_logger->log(LOG_ERR, "Provided path(s) '{}' does not contain a valid image", path);
157+
continue;
158+
}
159+
160+
result.emplace_back(SSetting{.monitor = std::move(monitor), .fitMode = std::move(fitMode), .paths = RESOLVE_PATH.value(), .timeout = timeout});
99161
}
100162

101163
return result;

src/config/ConfigManager.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ class CConfigManager {
1414
CConfigManager(CConfigManager&&) = delete;
1515

1616
struct SSetting {
17-
std::string monitor, fitMode, path;
18-
uint32_t id = 0;
17+
std::string monitor, fitMode;
18+
std::vector<std::string> paths;
19+
int timeout;
20+
uint32_t id = 0;
1921
};
2022

2123
constexpr static const uint32_t SETTING_INVALID = 0;

src/ipc/IPC.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ void CWallpaperObject::apply() {
8585
g_matcher->addState(CConfigManager::SSetting{
8686
.monitor = std::move(m_monitor),
8787
.fitMode = fitModeToStr(m_fitMode),
88-
.path = std::move(m_path),
88+
.paths = std::vector{std::move(m_path)},
8989
});
9090

9191
m_object->sendSuccess();

src/ui/UI.cpp

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "UI.hpp"
2+
#include "../defines.hpp"
23
#include "../helpers/Logger.hpp"
34
#include "../ipc/HyprlandSocket.hpp"
45
#include "../ipc/IPC.hpp"
@@ -12,13 +13,34 @@ CUI::~CUI() {
1213
m_targets.clear();
1314
}
1415

15-
CWallpaperTarget::CWallpaperTarget(SP<Hyprtoolkit::IOutput> output, const std::string_view& path, Hyprtoolkit::eImageFitMode fitMode) : m_monitorName(output->port()) {
16+
struct CWallpaperTarget::ImagesData {
17+
public:
18+
ImagesData(Hyprtoolkit::eImageFitMode fitMode, const std::vector<std::string>& images, const int timeout = 0) :
19+
fitMode(fitMode), images(images), timeout(timeout > 0 ? timeout : 30) {}
20+
21+
const Hyprtoolkit::eImageFitMode fitMode;
22+
const std::vector<std::string> images;
23+
const int timeout;
24+
25+
std::string nextImage() {
26+
current = (current + 1) % images.size();
27+
return images[current];
28+
}
29+
30+
private:
31+
std::size_t current = 0;
32+
};
33+
34+
CWallpaperTarget::CWallpaperTarget(SP<Hyprtoolkit::IBackend> backend, SP<Hyprtoolkit::IOutput> output, const std::vector<std::string>& path, Hyprtoolkit::eImageFitMode fitMode,
35+
const int timeout) : m_monitorName(output->port()), m_backend(backend) {
1636
static const auto SPLASH_REPLY = HyprlandSocket::getFromSocket("/splash");
1737

1838
static const auto PENABLESPLASH = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(g_config->hyprlang(), "splash");
1939
static const auto PSPLASHOFFSET = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(g_config->hyprlang(), "splash_offset");
2040
static const auto PSPLASHALPHA = Hyprlang::CSimpleConfigValue<Hyprlang::FLOAT>(g_config->hyprlang(), "splash_opacity");
2141

42+
ASSERT(path.size() > 0);
43+
2244
m_window = Hyprtoolkit::CWindowBuilder::begin()
2345
->type(Hyprtoolkit::HT_WINDOW_LAYER)
2446
->prefferedOutput(output)
@@ -33,9 +55,10 @@ CWallpaperTarget::CWallpaperTarget(SP<Hyprtoolkit::IOutput> output, const std::s
3355
->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1, 1}})
3456
->color([] { return Hyprtoolkit::CHyprColor{0xFF000000}; })
3557
->commence();
36-
m_null = Hyprtoolkit::CNullBuilder::begin()->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1, 1}})->commence();
58+
m_null = Hyprtoolkit::CNullBuilder::begin()->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1, 1}})->commence();
59+
3760
m_image = Hyprtoolkit::CImageBuilder::begin()
38-
->path(std::string{path})
61+
->path(std::string{path.front()})
3962
->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}})
4063
->sync(true)
4164
->fitMode(fitMode)
@@ -44,6 +67,12 @@ CWallpaperTarget::CWallpaperTarget(SP<Hyprtoolkit::IOutput> output, const std::s
4467
m_image->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE);
4568
m_image->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_CENTER, true);
4669

70+
if (path.size() > 1) {
71+
m_imagesData = makeUnique<ImagesData>(fitMode, path, timeout);
72+
m_timer =
73+
m_backend->addTimer(std::chrono::milliseconds(std::chrono::seconds(m_imagesData->timeout)), [this](ASP<Hyprtoolkit::CTimer> self, void*) { onRepeatTimer(); }, nullptr);
74+
}
75+
4776
m_window->m_rootElement->addChild(m_bg);
4877
m_window->m_rootElement->addChild(m_null);
4978
m_null->addChild(m_image);
@@ -68,6 +97,30 @@ CWallpaperTarget::CWallpaperTarget(SP<Hyprtoolkit::IOutput> output, const std::s
6897
m_window->open();
6998
}
7099

100+
CWallpaperTarget::~CWallpaperTarget() {
101+
if (m_timer && !m_timer->passed())
102+
m_timer->cancel();
103+
}
104+
105+
void CWallpaperTarget::onRepeatTimer() {
106+
107+
ASSERT(m_imagesData);
108+
109+
m_null->removeChild(m_image);
110+
m_image = Hyprtoolkit::CImageBuilder::begin()
111+
->path(m_imagesData->nextImage())
112+
->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}})
113+
->sync(true)
114+
->fitMode(m_imagesData->fitMode)
115+
->commence();
116+
m_image->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE);
117+
m_image->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_CENTER, true);
118+
m_null->addChild(m_image);
119+
120+
m_timer =
121+
m_backend->addTimer(std::chrono::milliseconds(std::chrono::seconds(m_imagesData->timeout)), [this](ASP<Hyprtoolkit::CTimer> self, void*) { onRepeatTimer(); }, nullptr);
122+
}
123+
71124
void CUI::registerOutput(const SP<Hyprtoolkit::IOutput>& mon) {
72125
g_matcher->registerOutput(mon->port());
73126
if (IPC::g_IPCSocket)
@@ -158,5 +211,5 @@ void CUI::targetChanged(const SP<Hyprtoolkit::IOutput>& mon) {
158211

159212
std::erase_if(m_targets, [&mon](const auto& e) { return e->m_monitorName == mon->port(); });
160213

161-
m_targets.emplace_back(makeShared<CWallpaperTarget>(mon, TARGET->get().path, toFitMode(TARGET->get().fitMode)));
214+
m_targets.emplace_back(makeShared<CWallpaperTarget>(m_backend, mon, TARGET->get().paths, toFitMode(TARGET->get().fitMode), TARGET->get().timeout));
162215
}

src/ui/UI.hpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <vector>
44

55
#include <hyprtoolkit/core/Backend.hpp>
6+
#include <hyprtoolkit/core/Timer.hpp>
67
#include <hyprtoolkit/window/Window.hpp>
78
#include <hyprtoolkit/element/Text.hpp>
89
#include <hyprtoolkit/element/Null.hpp>
@@ -15,8 +16,9 @@
1516

1617
class CWallpaperTarget {
1718
public:
18-
CWallpaperTarget(SP<Hyprtoolkit::IOutput> output, const std::string_view& path, Hyprtoolkit::eImageFitMode fitMode = Hyprtoolkit::IMAGE_FIT_MODE_COVER);
19-
~CWallpaperTarget() = default;
19+
CWallpaperTarget(SP<Hyprtoolkit::IBackend> backend, SP<Hyprtoolkit::IOutput> output, const std::vector<std::string>& path,
20+
Hyprtoolkit::eImageFitMode fitMode = Hyprtoolkit::IMAGE_FIT_MODE_COVER, const int timeout = 0);
21+
~CWallpaperTarget();
2022

2123
CWallpaperTarget(const CWallpaperTarget&) = delete;
2224
CWallpaperTarget(CWallpaperTarget&) = delete;
@@ -25,6 +27,13 @@ class CWallpaperTarget {
2527
std::string m_monitorName;
2628

2729
private:
30+
void onRepeatTimer();
31+
32+
struct ImagesData;
33+
34+
UP<ImagesData> m_imagesData;
35+
ASP<Hyprtoolkit::CTimer> m_timer;
36+
SP<Hyprtoolkit::IBackend> m_backend;
2837
SP<Hyprtoolkit::IWindow> m_window;
2938
SP<Hyprtoolkit::CNullElement> m_null;
3039
SP<Hyprtoolkit::CRectangleElement> m_bg;

0 commit comments

Comments
 (0)