feat: add directory wallpaper support and deferred preloading

- Add support for selecting random wallpapers from directories
- Add defer_preload option to reduce memory usage
- Update README with new features
- Improve path handling with tilde expansion
This commit is contained in:
y0usaf 2025-02-04 09:37:09 -05:00
parent f827dc3197
commit 17cc5c13ad
4 changed files with 163 additions and 41 deletions

View file

@ -80,6 +80,8 @@ preload = /path/to/next_image.png
wallpaper = monitor1,/path/to/image.png
#if more than one monitor in use, can load a 2nd image
wallpaper = monitor2,/path/to/next_image.png
# you can also specify a directory path to randomly select a wallpaper from
wallpaper = monitor3,/path/to/directory
# .. more monitors
#enable splash text rendering over the wallpaper
@ -88,6 +90,9 @@ splash = true
#fully disable ipc
# ipc = off
#enable deferred preloading (reduces memory usage but may cause slight delay when switching wallpapers)
# defer_preload = 1
```

View file

@ -3,6 +3,7 @@
#include <fstream>
#include <signal.h>
#include <sys/types.h>
#include <random>
CHyprpaper::CHyprpaper() = default;
@ -152,35 +153,74 @@ void CHyprpaper::unloadWallpaper(const std::string& path) {
}
void CHyprpaper::preloadAllWallpapersFromConfig() {
if (std::any_cast<Hyprlang::INT>(g_pConfigManager->config->getConfigValue("defer_preload"))) {
g_pConfigManager->m_dRequestedPreloads.clear();
return;
}
if (g_pConfigManager->m_dRequestedPreloads.empty())
return;
for (auto& wp : g_pConfigManager->m_dRequestedPreloads) {
// For directory-based preloads, always reselect a random file.
// For files, we'll skip if already loaded.
bool shouldSkip = !std::filesystem::is_directory(wp);
// check if it doesnt exist
bool exists = false;
for (auto& [ewp, cls] : m_mWallpaperTargets) {
if (ewp == wp) {
Debug::log(LOG, "Ignoring request to preload {} as it already is preloaded!", ewp);
exists = true;
break;
if (std::filesystem::is_directory(wp)) {
// Always unload previous wallpaper from this directory
std::vector<std::string> toUnload;
for (auto& [loadedPath, target] : m_mWallpaperTargets) {
std::filesystem::path pLoaded(loadedPath);
if (pLoaded.parent_path() == std::filesystem::path(wp)) {
toUnload.push_back(loadedPath);
break; // Only unload one wallpaper per directory
}
}
for (auto& path : toUnload) {
unloadWallpaper(path);
}
std::vector<std::filesystem::path> images;
for (auto& entry : std::filesystem::directory_iterator(wp)) {
if (entry.is_regular_file()) {
auto ext = entry.path().extension().string();
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".webp" || ext == ".jxl")
images.push_back(entry.path());
}
}
if (!images.empty()) {
// Use CRandomGenerator instead
wp = images[CRandomGenerator::get().getRandomIndex(images.size())].string();
} else {
Debug::log(LOG, "No valid images in directory {}", wp);
continue;
}
}
if (exists)
continue;
// For non-directory requests, skip if already loaded.
if (shouldSkip) {
bool exists = false;
for (auto& [ewp, cls] : m_mWallpaperTargets) {
if (ewp == wp) {
Debug::log(LOG, "Ignoring request to preload {} as it already is preloaded!", ewp);
exists = true;
break;
}
}
if (exists)
continue;
}
m_mWallpaperTargets[wp] = CWallpaperTarget();
if (std::filesystem::is_symlink(wp)) {
auto real_wp = std::filesystem::read_symlink(wp);
auto real_wp = std::filesystem::read_symlink(wp);
std::filesystem::path absolute_path = std::filesystem::path(wp).parent_path() / real_wp;
absolute_path = absolute_path.lexically_normal();
m_mWallpaperTargets[wp].create(absolute_path);
absolute_path = absolute_path.lexically_normal();
m_mWallpaperTargets[wp].create(absolute_path.string());
} else {
m_mWallpaperTargets[wp].create(wp);
}
}
g_pConfigManager->m_dRequestedPreloads.clear();
}

View file

@ -2,54 +2,101 @@
#include "../Hyprpaper.hpp"
#include <hyprutils/path/Path.hpp>
#include <filesystem>
#include <random>
// Utility function to safely expand a tilde at the beginning of a path.
static std::string expandTilde(const std::string& path) {
if (!path.empty() && path[0] == '~') {
if (const char* home = getenv("HOME"))
return std::string(home) + path.substr(1);
}
return path;
}
static Hyprlang::CParseResult handleWallpaper(const char* C, const char* V) {
const std::string COMMAND = C;
const std::string VALUE = V;
const std::string COMMAND = C;
const std::string VALUE = V;
Hyprlang::CParseResult result;
if (VALUE.find_first_of(',') == std::string::npos) {
// Cache the position of the comma delimiter.
auto delimPos = VALUE.find_first_of(',');
if (delimPos == std::string::npos) {
result.setError("wallpaper failed (syntax)");
return result;
}
auto MONITOR = VALUE.substr(0, VALUE.find_first_of(','));
auto WALLPAPER = g_pConfigManager->trimPath(VALUE.substr(VALUE.find_first_of(',') + 1));
auto MONITOR = VALUE.substr(0, delimPos);
auto WALLPAPER = g_pConfigManager->trimPath(VALUE.substr(delimPos + 1));
bool contain = false;
if (WALLPAPER.find("contain:") == 0) {
WALLPAPER = WALLPAPER.substr(8);
contain = true;
}
bool tile = false;
if (WALLPAPER.find("tile:") == 0) {
WALLPAPER = WALLPAPER.substr(5);
tile = true;
}
if (WALLPAPER[0] == '~') {
static const char* const ENVHOME = getenv("HOME");
WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1);
}
// Use the helper to expand '~'
WALLPAPER = expandTilde(WALLPAPER);
std::error_code ec;
if (!std::filesystem::exists(WALLPAPER, ec)) {
result.setError((std::string{"wallpaper failed ("} + (ec ? ec.message() : std::string{"no such file"}) + std::string{": "} + WALLPAPER + std::string{")"}).c_str());
result.setError(((ec ? ec.message() : std::string{"no such file"}) + std::string{": "} + WALLPAPER).c_str());
return result;
}
if (std::find(g_pConfigManager->m_dRequestedPreloads.begin(), g_pConfigManager->m_dRequestedPreloads.end(), WALLPAPER) == g_pConfigManager->m_dRequestedPreloads.end() &&
!g_pHyprpaper->isPreloaded(WALLPAPER)) {
result.setError("wallpaper failed (not preloaded)");
return result;
// If it's a directory, always force a reload
if (std::filesystem::is_directory(WALLPAPER)) {
std::string currentWallpaper;
// First get the current wallpaper if it exists
for (auto& [loadedPath, target] : g_pHyprpaper->m_mWallpaperTargets) {
if (std::filesystem::path(loadedPath).parent_path() == std::filesystem::path(WALLPAPER)) {
currentWallpaper = loadedPath;
g_pHyprpaper->unloadWallpaper(loadedPath);
break;
}
}
std::vector<std::filesystem::path> images;
for (auto& entry : std::filesystem::directory_iterator(WALLPAPER)) {
if (entry.is_regular_file()) {
auto ext = entry.path().extension().string();
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".webp" || ext == ".jxl") {
if (entry.path().string() != currentWallpaper) {
images.push_back(entry.path());
}
}
}
}
if (images.empty()) {
if (!currentWallpaper.empty()) {
images.push_back(currentWallpaper);
} else {
result.setError("No valid images in directory");
return result;
}
}
WALLPAPER = images[CRandomGenerator::get().getRandomIndex(images.size())].string();
}
if (!g_pHyprpaper->isPreloaded(WALLPAPER)) {
if (std::any_cast<Hyprlang::INT>(g_pConfigManager->config->getConfigValue("defer_preload"))) {
g_pHyprpaper->m_mWallpaperTargets[WALLPAPER] = CWallpaperTarget();
g_pHyprpaper->m_mWallpaperTargets[WALLPAPER].create(WALLPAPER);
} else {
result.setError("wallpaper failed (not preloaded)");
return result;
}
}
g_pHyprpaper->clearWallpaperFromMonitor(MONITOR);
g_pHyprpaper->m_mMonitorActiveWallpapers[MONITOR] = WALLPAPER;
g_pHyprpaper->m_mMonitorActiveWallpapers[MONITOR] = WALLPAPER;
g_pHyprpaper->m_mMonitorWallpaperRenderData[MONITOR].contain = contain;
g_pHyprpaper->m_mMonitorWallpaperRenderData[MONITOR].tile = tile;
@ -57,7 +104,7 @@ static Hyprlang::CParseResult handleWallpaper(const char* C, const char* V) {
for (auto& m : g_pHyprpaper->m_vMonitors) {
if (!m->hasATarget || m->wildcard) {
g_pHyprpaper->clearWallpaperFromMonitor(m->name);
g_pHyprpaper->m_mMonitorActiveWallpapers[m->name] = WALLPAPER;
g_pHyprpaper->m_mMonitorActiveWallpapers[m->name] = WALLPAPER;
g_pHyprpaper->m_mMonitorWallpaperRenderData[m->name].contain = contain;
g_pHyprpaper->m_mMonitorWallpaperRenderData[m->name].tile = tile;
}
@ -76,13 +123,10 @@ static Hyprlang::CParseResult handlePreload(const char* C, const char* V) {
const std::string VALUE = V;
auto WALLPAPER = VALUE;
if (WALLPAPER[0] == '~') {
static const char* const ENVHOME = getenv("HOME");
WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1);
}
// Use the helper function for tilde expansion.
WALLPAPER = expandTilde(WALLPAPER);
std::error_code ec;
if (!std::filesystem::exists(WALLPAPER, ec)) {
Hyprlang::CParseResult result;
result.setError(((ec ? ec.message() : std::string{"no such file"}) + std::string{": "} + WALLPAPER).c_str());
@ -130,10 +174,8 @@ static Hyprlang::CParseResult handleUnload(const char* C, const char* V) {
if (VALUE == "all" || VALUE == "unused")
return handleUnloadAll(C, V);
if (WALLPAPER[0] == '~') {
static const char* const ENVHOME = getenv("HOME");
WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1);
}
// Use the helper function for tilde expansion.
WALLPAPER = expandTilde(WALLPAPER);
g_pHyprpaper->unloadWallpaper(WALLPAPER);
@ -189,6 +231,7 @@ CConfigManager::CConfigManager() {
config->addConfigValue("splash", Hyprlang::INT{0L});
config->addConfigValue("splash_offset", Hyprlang::FLOAT{2.F});
config->addConfigValue("splash_color", Hyprlang::INT{0x55ffffff});
config->addConfigValue("defer_preload", Hyprlang::INT{0L});
config->registerHandler(&handleWallpaper, "wallpaper", {.allowFlags = false});
config->registerHandler(&handleUnload, "unload", {.allowFlags = false});

View file

@ -0,0 +1,34 @@
#pragma once
#include <random>
class CRandomGenerator {
public:
static CRandomGenerator& get() {
static CRandomGenerator instance;
return instance;
}
// Generate random index for vector size
size_t getRandomIndex(size_t size) {
if (size == 0) return 0;
std::uniform_int_distribution<size_t> dis(0, size - 1);
return dis(m_Generator);
}
// Get raw generator if needed
std::mt19937& getGenerator() {
return m_Generator;
}
private:
CRandomGenerator() : m_Generator(m_RandomDevice()) {}
std::random_device m_RandomDevice;
std::mt19937 m_Generator;
// Delete copy/move to ensure singleton
CRandomGenerator(const CRandomGenerator&) = delete;
CRandomGenerator& operator=(const CRandomGenerator&) = delete;
CRandomGenerator(CRandomGenerator&&) = delete;
CRandomGenerator& operator=(CRandomGenerator&&) = delete;
};