This commit is contained in:
zhanglin2603 2026-02-25 23:27:44 +03:00 committed by GitHub
commit 9bede48780
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 472 additions and 46 deletions

View file

@ -1,11 +1,13 @@
#include "ConfigManager.hpp"
#include <algorithm>
#include <cctype>
#include <filesystem>
#include <glob.h>
#include <hyprlang.hpp>
#include <hyprutils/path/Path.hpp>
#include <hyprutils/string/String.hpp>
#include <hyprutils/utils/ScopeGuard.hpp>
#include <optional>
#include <string>
#include "../helpers/Logger.hpp"
#include "WallpaperMatcher.hpp"
@ -63,6 +65,7 @@ bool CConfigManager::init() {
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.addSpecialConfigValue("wallpaper", "triggers", Hyprlang::STRING{""});
m_config.registerHandler(&handleSource, "source", Hyprlang::SHandlerOptions{});
@ -118,19 +121,11 @@ static std::expected<std::string, std::string> getPath(const std::string_view& s
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 std::expected<std::vector<std::string>, std::string> getFullPathResolved(const std::string& resolvedPath) {
static constexpr const size_t maxImagesCount{1024};
std::vector<std::string> result;
const auto resolved = getPath(sv);
if (!resolved)
return std::unexpected(resolved.error());
const auto resolvedPath = resolved.value();
if (!std::filesystem::exists(resolvedPath))
return std::unexpected(std::format("File '{}' does not exist", resolvedPath));
@ -150,6 +145,106 @@ static std::expected<std::vector<std::string>, std::string> getFullPath(const st
return result;
}
struct SParsedTriggers {
uint32_t flags = 0;
int fileChangeDebounceMs = 0;
bool hasTimeout = false;
int timeout = 0;
bool hasValidEntry = false;
};
static std::optional<int> parsePositiveInt(const std::string_view& sv) {
if (sv.empty())
return std::nullopt;
try {
size_t idx = 0;
int val = std::stoi(std::string{sv}, &idx);
if (idx != sv.size() || val <= 0)
return std::nullopt;
return val;
} catch (...) {
return std::nullopt;
}
}
static std::optional<int> parseNonNegativeInt(const std::string_view& sv) {
if (sv.empty())
return std::nullopt;
try {
size_t idx = 0;
int val = std::stoi(std::string{sv}, &idx);
if (idx != sv.size() || val < 0)
return std::nullopt;
return val;
} catch (...) {
return std::nullopt;
}
}
static SParsedTriggers parseTriggers(const std::string_view& triggerSpec, const std::string_view& keyName) {
SParsedTriggers out;
size_t pos = 0;
while (pos <= triggerSpec.size()) {
const auto nextComma = triggerSpec.find(',', pos);
const auto token =
Hyprutils::String::trim(nextComma == std::string::npos ? triggerSpec.substr(pos) : triggerSpec.substr(pos, nextComma - pos));
if (!token.empty()) {
std::string lowerToken{token};
std::transform(lowerToken.begin(), lowerToken.end(), lowerToken.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
if (lowerToken == "sighup") {
out.flags |= CConfigManager::SSetting::TRIGGER_SIGHUP;
out.hasValidEntry = true;
} else if (lowerToken == "sigusr1") {
out.flags |= CConfigManager::SSetting::TRIGGER_SIGUSR1;
out.hasValidEntry = true;
} else if (lowerToken == "sigusr2") {
out.flags |= CConfigManager::SSetting::TRIGGER_SIGUSR2;
out.hasValidEntry = true;
} else if (lowerToken.starts_with("timeout")) {
auto arg = Hyprutils::String::trim(token.substr(std::string_view{"timeout"}.size()));
if (!arg.empty() && arg.front() == '=')
arg = Hyprutils::String::trim(arg.substr(1));
const auto parsedTimeout = parsePositiveInt(arg);
if (!parsedTimeout)
g_logger->log(LOG_WARN, "Invalid timeout trigger '{}' for wallpaper key {}, expected: timeout <seconds>", token, keyName);
else {
out.hasTimeout = true;
out.timeout = *parsedTimeout;
out.hasValidEntry = true;
}
} else if (lowerToken.starts_with("file_change")) {
out.flags |= CConfigManager::SSetting::TRIGGER_FILE_CHANGE;
out.hasValidEntry = true;
auto arg = Hyprutils::String::trim(token.substr(std::string_view{"file_change"}.size()));
if (!arg.empty() && arg.front() == '=')
arg = Hyprutils::String::trim(arg.substr(1));
if (!arg.empty()) {
const auto parsedDebounce = parseNonNegativeInt(arg);
if (!parsedDebounce)
g_logger->log(LOG_WARN, "Invalid file_change trigger '{}' for wallpaper key {}, expected: file_change [milliseconds]", token, keyName);
else
out.fileChangeDebounceMs = *parsedDebounce;
}
} else
g_logger->log(LOG_WARN, "Unknown trigger '{}' for wallpaper key {}", token, keyName);
}
if (nextComma == std::string::npos)
break;
pos = nextComma + 1;
}
return out;
}
std::vector<CConfigManager::SSetting> CConfigManager::getSettings() {
std::vector<CConfigManager::SSetting> result;
@ -157,20 +252,28 @@ std::vector<CConfigManager::SSetting> CConfigManager::getSettings() {
result.reserve(keys.size());
for (auto& key : keys) {
std::string monitor, fitMode, path;
int timeout;
std::string monitor, fitMode, path, triggers;
int timeout, fileChangeDebounceMs = 0;
uint32_t triggerFlags = 0;
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()));
triggers = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("wallpaper", "triggers", key.c_str()));
} catch (...) {
g_logger->log(LOG_ERR, "Failed parsing wallpaper for key {}", key);
continue;
}
const auto RESOLVE_PATH = getFullPath(path);
const auto RESOLVED_INPUT_PATH = getPath(path);
if (!RESOLVED_INPUT_PATH) {
g_logger->log(LOG_ERR, "Failed to resolve path {}: {}", path, RESOLVED_INPUT_PATH.error());
continue;
}
const auto RESOLVE_PATH = getFullPathResolved(RESOLVED_INPUT_PATH.value());
if (!RESOLVE_PATH) {
g_logger->log(LOG_ERR, "Failed to resolve path {}: {}", path, RESOLVE_PATH.error());
@ -182,7 +285,24 @@ std::vector<CConfigManager::SSetting> CConfigManager::getSettings() {
continue;
}
result.emplace_back(SSetting{.monitor = std::move(monitor), .fitMode = std::move(fitMode), .paths = RESOLVE_PATH.value(), .timeout = timeout});
const auto PARSED_TRIGGERS = parseTriggers(triggers, key);
triggerFlags = PARSED_TRIGGERS.flags;
fileChangeDebounceMs = PARSED_TRIGGERS.fileChangeDebounceMs;
if (PARSED_TRIGGERS.hasTimeout)
timeout = PARSED_TRIGGERS.timeout;
else if (PARSED_TRIGGERS.hasValidEntry && timeout <= 0)
timeout = -1;
result.emplace_back(SSetting{
.monitor = std::move(monitor),
.fitMode = std::move(fitMode),
.paths = RESOLVE_PATH.value(),
.timeout = timeout,
.triggers = triggerFlags,
.fileChangeDebounceMs = fileChangeDebounceMs,
.sourcePath = RESOLVED_INPUT_PATH.value(),
});
}
return result;

View file

@ -14,9 +14,19 @@ class CConfigManager {
CConfigManager(CConfigManager&&) = delete;
struct SSetting {
enum : uint32_t {
TRIGGER_SIGHUP = 1U << 0,
TRIGGER_SIGUSR1 = 1U << 1,
TRIGGER_SIGUSR2 = 1U << 2,
TRIGGER_FILE_CHANGE = 1U << 3,
};
std::string monitor, fitMode;
std::vector<std::string> paths;
int timeout = 0;
uint32_t triggers = 0;
int fileChangeDebounceMs = 0;
std::string sourcePath;
uint32_t id = 0;
};

View file

@ -1,18 +1,38 @@
#include "UI.hpp"
#include "../config/WallpaperMatcher.hpp"
#include "../defines.hpp"
#include "../helpers/Logger.hpp"
#include "../helpers/GlobalState.hpp"
#include "../helpers/Logger.hpp"
#include "../ipc/HyprlandSocket.hpp"
#include "../ipc/IPC.hpp"
#include "../config/WallpaperMatcher.hpp"
#include <hyprtoolkit/core/Output.hpp>
#include <hyprutils/string/String.hpp>
#include <algorithm>
#include <array>
#include <cerrno>
#include <cctype>
#include <chrono>
#include <csignal>
#include <cstring>
#include <filesystem>
#include <string_view>
#include <sys/signalfd.h>
#include <unistd.h>
#include <unordered_map>
using namespace std::chrono_literals;
static constexpr auto FILE_WATCH_POLL_INTERVAL = 500ms;
CUI::CUI() = default;
CUI::~CUI() {
if (m_signalFD >= 0)
close(m_signalFD);
m_targets.clear();
}
@ -22,26 +42,171 @@ static std::string_view pruneDesc(const std::string_view& sv) {
return sv;
}
static Hyprtoolkit::eImageFitMode toFitMode(const std::string_view& sv) {
if (sv.starts_with("contain"))
return Hyprtoolkit::IMAGE_FIT_MODE_CONTAIN;
if (sv.starts_with("cover"))
return Hyprtoolkit::IMAGE_FIT_MODE_COVER;
if (sv.starts_with("tile"))
return Hyprtoolkit::IMAGE_FIT_MODE_TILE;
if (sv.starts_with("fill"))
return Hyprtoolkit::IMAGE_FIT_MODE_STRETCH;
return Hyprtoolkit::IMAGE_FIT_MODE_COVER;
}
template <typename T>
static void hashCombine(size_t& seed, const T& val) {
seed ^= std::hash<T>{}(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
struct SPathSnapshot {
std::vector<std::string> images;
std::unordered_map<std::string, uint64_t> writeTimes;
size_t signature = 0;
};
static bool isSupportedImagePath(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(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return std::ranges::any_of(exts, [&ext](const auto& e) { return ext == e; });
}
static uint64_t toNs(std::filesystem::file_time_type tp) {
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(tp.time_since_epoch()).count());
}
static SPathSnapshot snapshotSourcePath(const std::string& sourcePath) {
SPathSnapshot out;
if (sourcePath.empty())
return out;
std::error_code ec;
const auto path = std::filesystem::path{sourcePath};
const auto addFile = [&out](const std::filesystem::path& filePath) {
std::error_code ec;
const auto mtime = std::filesystem::last_write_time(filePath, ec);
if (ec)
return;
const auto pathStr = filePath.string();
out.images.emplace_back(pathStr);
out.writeTimes[pathStr] = toNs(mtime);
};
if (std::filesystem::is_directory(path, ec) && !ec) {
auto directoryIt = std::filesystem::directory_iterator(path, std::filesystem::directory_options::skip_permission_denied, ec);
auto endIt = std::filesystem::directory_iterator{};
for (; !ec && directoryIt != endIt; directoryIt.increment(ec)) {
const auto& entry = *directoryIt;
std::error_code iterEc;
if (!entry.is_regular_file(iterEc) || iterEc)
continue;
if (!isSupportedImagePath(entry.path()))
continue;
addFile(entry.path());
}
}
ec.clear();
if (std::filesystem::is_regular_file(path, ec) && !ec && isSupportedImagePath(path))
addFile(path);
std::sort(out.images.begin(), out.images.end());
size_t sig = 0;
for (const auto& image : out.images) {
hashCombine(sig, image);
const auto mtIt = out.writeTimes.find(image);
if (mtIt != out.writeTimes.end())
hashCombine(sig, mtIt->second);
}
out.signature = sig;
return out;
}
class CWallpaperTarget::CImagesData {
public:
CImagesData(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() {
if (images.empty())
return "";
current = (current + 1) % images.size();
return images[current];
}
std::string currentImage() const {
if (images.empty())
return "";
return images[current];
}
bool contains(const std::string& path) const {
return std::ranges::find(images, path) != images.end();
}
bool setCurrentImage(const std::string& path) {
const auto IT = std::ranges::find(images, path);
if (IT == images.end())
return false;
current = std::distance(images.begin(), IT);
return true;
}
void updateImages(std::vector<std::string> newImages, const std::string& currentPath) {
if (newImages.empty()) {
images.clear();
current = 0;
return;
}
size_t nextCurrent = 0;
const auto IT = std::ranges::find(newImages, currentPath);
if (IT != newImages.end())
nextCurrent = std::distance(newImages.begin(), IT);
images = std::move(newImages);
current = std::min(nextCurrent, images.size() - 1);
}
private:
size_t current = 0;
std::vector<std::string> images;
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) {
class CWallpaperTarget::CFileWatchData {
public:
CFileWatchData(std::string sourcePath, int debounceMs) : sourcePath(std::move(sourcePath)), debounceMs(std::max(0, debounceMs)) {
lastSnapshot = snapshotSourcePath(this->sourcePath);
}
std::string sourcePath;
int debounceMs = 0;
SPathSnapshot lastSnapshot;
SPathSnapshot pendingSnapshot;
bool dirty = false;
std::chrono::steady_clock::time_point lastChange;
};
CWallpaperTarget::CWallpaperTarget(SP<Hyprtoolkit::IBackend> backend, SP<Hyprtoolkit::IOutput> output, const std::vector<std::string>& path,
Hyprtoolkit::eImageFitMode fitMode, const int timeout, const uint32_t triggers, const int fileChangeDebounceMs,
const std::string& sourcePath) :
m_monitorName(output->port()), m_backend(backend), m_triggers(triggers) {
static const auto SPLASH_REPLY = HyprlandSocket::getFromSocket("/splash");
static const auto PENABLESPLASH = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(g_config->hyprlang(), "splash");
@ -78,10 +243,14 @@ CWallpaperTarget::CWallpaperTarget(SP<Hyprtoolkit::IBackend> backend, SP<Hyprtoo
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<CImagesData>(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_imagesData = makeUnique<CImagesData>(fitMode, path, timeout);
if (path.size() > 1 && timeout != -1)
m_timer = m_backend->addTimer(std::chrono::seconds(m_imagesData->timeout), [this](ASP<Hyprtoolkit::CTimer> self, void*) { onRepeatTimer(); }, nullptr);
if ((m_triggers & CConfigManager::SSetting::TRIGGER_FILE_CHANGE) && !sourcePath.empty()) {
m_fileWatchData = makeUnique<CFileWatchData>(sourcePath, fileChangeDebounceMs);
m_fileWatchTimer = m_backend->addTimer(FILE_WATCH_POLL_INTERVAL, [this](ASP<Hyprtoolkit::CTimer> self, void*) { onFileWatchTimer(); }, nullptr);
}
m_window->m_rootElement->addChild(m_bg);
@ -111,13 +280,16 @@ CWallpaperTarget::CWallpaperTarget(SP<Hyprtoolkit::IBackend> backend, SP<Hyprtoo
CWallpaperTarget::~CWallpaperTarget() {
if (m_timer && !m_timer->passed())
m_timer->cancel();
if (m_fileWatchTimer && !m_fileWatchTimer->passed())
m_fileWatchTimer->cancel();
}
void CWallpaperTarget::onRepeatTimer() {
void CWallpaperTarget::setImagePath(const std::string& path) {
if (path.empty())
return;
ASSERT(m_imagesData);
m_lastPath = m_imagesData->nextImage();
m_lastPath = path;
m_image->rebuild()
->path(std::string{m_lastPath})
@ -126,10 +298,88 @@ void CWallpaperTarget::onRepeatTimer() {
->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);
if (IPC::g_IPCSocket)
IPC::g_IPCSocket->onWallpaperChanged(m_monitorName, m_lastPath);
}
IPC::g_IPCSocket->onWallpaperChanged(m_monitorName, m_lastPath);
void CWallpaperTarget::cycleImage() {
ASSERT(m_imagesData);
setImagePath(m_imagesData->nextImage());
}
void CWallpaperTarget::onRepeatTimer() {
cycleImage();
m_timer = m_backend->addTimer(std::chrono::seconds(m_imagesData->timeout), [this](ASP<Hyprtoolkit::CTimer> self, void*) { onRepeatTimer(); }, nullptr);
}
bool CWallpaperTarget::supportsSignal(int signal) const {
switch (signal) {
case SIGHUP: return m_triggers & CConfigManager::SSetting::TRIGGER_SIGHUP;
case SIGUSR1: return m_triggers & CConfigManager::SSetting::TRIGGER_SIGUSR1;
case SIGUSR2: return m_triggers & CConfigManager::SSetting::TRIGGER_SIGUSR2;
default: return false;
}
}
void CWallpaperTarget::onSignal(int signal) {
if (!supportsSignal(signal))
return;
cycleImage();
}
void CWallpaperTarget::onFileWatchTimer() {
ASSERT(m_fileWatchData);
const auto snapshot = snapshotSourcePath(m_fileWatchData->sourcePath);
const auto now = std::chrono::steady_clock::now();
if (snapshot.signature != m_fileWatchData->lastSnapshot.signature) {
m_fileWatchData->pendingSnapshot = snapshot;
m_fileWatchData->dirty = true;
m_fileWatchData->lastChange = now;
}
if (m_fileWatchData->dirty) {
const auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_fileWatchData->lastChange).count();
if (elapsedMs >= m_fileWatchData->debounceMs) {
auto& oldSnapshot = m_fileWatchData->lastSnapshot;
auto newSnapshot = std::move(m_fileWatchData->pendingSnapshot);
m_fileWatchData->dirty = false;
if (!newSnapshot.images.empty()) {
std::vector<std::string> changedOrAdded;
changedOrAdded.reserve(newSnapshot.images.size());
for (const auto& image : newSnapshot.images) {
const auto NEW_IT = newSnapshot.writeTimes.find(image);
const auto OLD_IT = oldSnapshot.writeTimes.find(image);
if (OLD_IT == oldSnapshot.writeTimes.end())
changedOrAdded.push_back(image);
else if (NEW_IT != newSnapshot.writeTimes.end() && NEW_IT->second != OLD_IT->second)
changedOrAdded.push_back(image);
}
const bool hadCurrentBefore = m_imagesData->contains(m_lastPath);
m_imagesData->updateImages(newSnapshot.images, m_lastPath);
if (!changedOrAdded.empty()) {
m_imagesData->setCurrentImage(changedOrAdded.front());
setImagePath(m_imagesData->currentImage());
} else if (!hadCurrentBefore || !m_imagesData->contains(m_lastPath))
setImagePath(m_imagesData->currentImage());
}
m_fileWatchData->lastSnapshot = std::move(newSnapshot);
}
}
m_fileWatchTimer = m_backend->addTimer(FILE_WATCH_POLL_INTERVAL, [this](ASP<Hyprtoolkit::CTimer> self, void*) { onFileWatchTimer(); }, nullptr);
}
void CUI::registerOutput(const SP<Hyprtoolkit::IOutput>& mon) {
@ -144,10 +394,35 @@ void CUI::registerOutput(const SP<Hyprtoolkit::IOutput>& mon) {
});
}
void CUI::onSignalFDReadable() {
if (m_signalFD < 0)
return;
signalfd_siginfo fdsi;
while (true) {
const auto readBytes = read(m_signalFD, &fdsi, sizeof(fdsi));
if (readBytes == static_cast<ssize_t>(sizeof(fdsi))) {
for (const auto& target : m_targets) {
target->onSignal(static_cast<int>(fdsi.ssi_signo));
}
continue;
}
if (readBytes < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
return;
if (readBytes < 0)
g_logger->log(LOG_WARN, "Failed reading signal fd: {}", std::strerror(errno));
return;
}
}
bool CUI::run() {
static const auto PENABLEIPC = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(g_config->hyprlang(), "ipc");
//
Hyprtoolkit::IBackend::SBackendCreationData data;
data.pLogConnection = makeShared<Hyprutils::CLI::CLoggerConnection>(*g_logger);
data.pLogConnection->setName("hyprtoolkit");
@ -158,6 +433,21 @@ bool CUI::run() {
if (!m_backend)
return false;
sigset_t signalMask;
sigemptyset(&signalMask);
sigaddset(&signalMask, SIGHUP);
sigaddset(&signalMask, SIGUSR1);
sigaddset(&signalMask, SIGUSR2);
if (sigprocmask(SIG_BLOCK, &signalMask, nullptr) == 0) {
m_signalFD = signalfd(-1, &signalMask, SFD_NONBLOCK | SFD_CLOEXEC);
if (m_signalFD >= 0)
m_backend->addFd(m_signalFD, [this]() { onSignalFDReadable(); });
else
g_logger->log(LOG_WARN, "Failed setting up signalfd: {}", std::strerror(errno));
} else
g_logger->log(LOG_WARN, "Failed blocking trigger signals: {}", std::strerror(errno));
if (*PENABLEIPC)
IPC::g_IPCSocket = makeUnique<IPC::CSocket>();
@ -171,7 +461,6 @@ bool CUI::run() {
g_logger->log(LOG_DEBUG, "Found {} output(s)", MONITORS.size());
// load the config now, then bind
for (const auto& m : MONITORS) {
targetChanged(m);
}
@ -187,18 +476,6 @@ SP<Hyprtoolkit::IBackend> CUI::backend() {
return m_backend;
}
static Hyprtoolkit::eImageFitMode toFitMode(const std::string_view& sv) {
if (sv.starts_with("contain"))
return Hyprtoolkit::IMAGE_FIT_MODE_CONTAIN;
if (sv.starts_with("cover"))
return Hyprtoolkit::IMAGE_FIT_MODE_COVER;
if (sv.starts_with("tile"))
return Hyprtoolkit::IMAGE_FIT_MODE_TILE;
if (sv.starts_with("fill"))
return Hyprtoolkit::IMAGE_FIT_MODE_STRETCH;
return Hyprtoolkit::IMAGE_FIT_MODE_COVER;
}
void CUI::targetChanged(const std::string_view& monName) {
const auto MONITORS = m_backend->getOutputs();
SP<Hyprtoolkit::IOutput> monitor;
@ -228,7 +505,9 @@ 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>(m_backend, mon, TARGET->get().paths, toFitMode(TARGET->get().fitMode), TARGET->get().timeout));
m_targets.emplace_back(
makeShared<CWallpaperTarget>(m_backend, mon, TARGET->get().paths, toFitMode(TARGET->get().fitMode), TARGET->get().timeout, TARGET->get().triggers,
TARGET->get().fileChangeDebounceMs, TARGET->get().sourcePath));
}
const std::vector<SP<CWallpaperTarget>>& CUI::targets() {

View file

@ -1,5 +1,7 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <hyprtoolkit/core/Backend.hpp>
@ -17,23 +19,34 @@
class CWallpaperTarget {
public:
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);
Hyprtoolkit::eImageFitMode fitMode = Hyprtoolkit::IMAGE_FIT_MODE_COVER, const int timeout = 0, const uint32_t triggers = 0,
const int fileChangeDebounceMs = 0, const std::string& sourcePath = "");
~CWallpaperTarget();
CWallpaperTarget(const CWallpaperTarget&) = delete;
CWallpaperTarget(CWallpaperTarget&) = delete;
CWallpaperTarget(CWallpaperTarget&&) = delete;
void onSignal(int signal);
std::string m_monitorName, m_lastPath;
private:
void onRepeatTimer();
void onFileWatchTimer();
void setImagePath(const std::string& path);
void cycleImage();
bool supportsSignal(int signal) const;
class CImagesData;
class CFileWatchData;
UP<CImagesData> m_imagesData;
UP<CFileWatchData> m_fileWatchData;
ASP<Hyprtoolkit::CTimer> m_timer;
ASP<Hyprtoolkit::CTimer> m_fileWatchTimer;
SP<Hyprtoolkit::IBackend> m_backend;
uint32_t m_triggers = 0;
SP<Hyprtoolkit::IWindow> m_window;
SP<Hyprtoolkit::CNullElement> m_null;
SP<Hyprtoolkit::CRectangleElement> m_bg;
@ -54,9 +67,12 @@ class CUI {
void targetChanged(const SP<Hyprtoolkit::IOutput>& mon);
void targetChanged(const std::string_view& monName);
void registerOutput(const SP<Hyprtoolkit::IOutput>& mon);
void onSignalFDReadable();
SP<Hyprtoolkit::IBackend> m_backend;
int m_signalFD = -1;
std::vector<SP<CWallpaperTarget>> m_targets;
struct {

View file

@ -9,6 +9,7 @@ ConditionEnvironment=WAYLAND_DISPLAY
[Service]
Type=simple
ExecStart=@CMAKE_INSTALL_PREFIX@/bin/hyprpaper
ExecReload=kill -HUP $MAINPID
Slice=session.slice
Restart=on-failure