diff --git a/CMakeLists.txt b/CMakeLists.txt index 67349a0..2f093d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,7 @@ target_link_libraries(hyprpaper GLESv2 pthread magic + hyprlang ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_SOURCE_DIR}/wlr-layer-shell-unstable-v1-protocol.o ${CMAKE_SOURCE_DIR}/xdg-shell-protocol.o diff --git a/README.md b/README.md index 22ac60f..6b89c18 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,21 @@ The development files of these packages need to be installed on the system for ` - libglvnd-core - libjpeg-turbo - libwebp +- hyprlang -Also `gcc-c++` and `ninja` needs to installed. +Please note hyprpaper > 0.5.0 depends on [hyprlang](https://github.com/hyprwm/hyprlang) which is new +and might not be packaged for your distro yet. If that's the case, build and install it from source. To install all of these in Fedora, run this command: ``` sudo dnf install wayland-devel wayland-protocols-devel pango-devel cairo-devel file-devel libglvnd-devel libglvnd-core-devel libjpeg-turbo-devel libwebp-devel gcc-c++ ``` +On Arch: +``` +sudo pacman -S ninja gcc wayland-protocols libjpeg-turbo libwebp pango cairo pkgconf cmake libglvnd wayland +``` + On OpenSUSE: ``` sudo zypper install ninja gcc-c++ wayland-protocols-devel Mesa-libGLESv3-devel file-devel diff --git a/src/Hyprpaper.cpp b/src/Hyprpaper.cpp index 1bbd469..9574caf 100644 --- a/src/Hyprpaper.cpp +++ b/src/Hyprpaper.cpp @@ -1,8 +1,8 @@ #include "Hyprpaper.hpp" #include #include -#include #include +#include CHyprpaper::CHyprpaper() = default; @@ -18,6 +18,8 @@ void CHyprpaper::init() { g_pConfigManager = std::make_unique(); g_pIPCSocket = std::make_unique(); + g_pConfigManager->parse(); + m_sDisplay = (wl_display*)wl_display_connect(nullptr); if (!m_sDisplay) { @@ -27,7 +29,7 @@ void CHyprpaper::init() { preloadAllWallpapersFromConfig(); - if (m_bIPCEnabled) + if (std::any_cast(g_pConfigManager->config->getConfigValue("ipc"))) g_pIPCSocket->initialize(); // run @@ -129,7 +131,6 @@ void CHyprpaper::preloadAllWallpapersFromConfig() { } else { m_mWallpaperTargets[wp].create(wp); } - } g_pConfigManager->m_dRequestedPreloads.clear(); @@ -450,6 +451,8 @@ SPoolBuffer* CHyprpaper::getPoolBuffer(SMonitor* pMonitor, CWallpaperTarget* pWa } void CHyprpaper::renderWallpaperForMonitor(SMonitor* pMonitor) { + static auto* const PRENDERSPLASH = reinterpret_cast(g_pConfigManager->config->getConfigValuePtr("splash")); + static auto* const PSPLASHOFFSET = reinterpret_cast(g_pConfigManager->config->getConfigValuePtr("splash_offset")); const auto PWALLPAPERTARGET = m_mMonitorActiveWallpaperTargets[pMonitor]; const auto CONTAIN = m_mMonitorWallpaperRenderData[pMonitor->name].contain; @@ -508,7 +511,7 @@ void CHyprpaper::renderWallpaperForMonitor(SMonitor* pMonitor) { cairo_paint(PCAIRO); - if (g_pHyprpaper->m_bRenderSplash && getenv("HYPRLAND_INSTANCE_SIGNATURE")) { + if (*PRENDERSPLASH && getenv("HYPRLAND_INSTANCE_SIGNATURE")) { auto SPLASH = execAndGet("hyprctl splash"); SPLASH.pop_back(); @@ -524,9 +527,9 @@ void CHyprpaper::renderWallpaperForMonitor(SMonitor* pMonitor) { cairo_text_extents_t textExtents; cairo_text_extents(PCAIRO, SPLASH.c_str(), &textExtents); - cairo_move_to(PCAIRO, ((DIMENSIONS.x - textExtents.width * scale) / 2.0) / scale, ((DIMENSIONS.y * (100 - m_fSplashOffset)) / 100 - textExtents.height * scale) / scale); + cairo_move_to(PCAIRO, ((DIMENSIONS.x - textExtents.width * scale) / 2.0) / scale, ((DIMENSIONS.y * (100 - *PSPLASHOFFSET)) / 100 - textExtents.height * scale) / scale); - Debug::log(LOG, "Splash font size: %d, pos: %.2f, %.2f", FONTSIZE, (DIMENSIONS.x - textExtents.width) / 2.0 / scale, ((DIMENSIONS.y * (100 - m_fSplashOffset)) / 100 - textExtents.height * scale) / scale); + Debug::log(LOG, "Splash font size: %d, pos: %.2f, %.2f", FONTSIZE, (DIMENSIONS.x - textExtents.width) / 2.0 / scale, ((DIMENSIONS.y * (100 - *PSPLASHOFFSET)) / 100 - textExtents.height * scale) / scale); cairo_show_text(PCAIRO, SPLASH.c_str()); diff --git a/src/Hyprpaper.hpp b/src/Hyprpaper.hpp index 10dc569..2c06066 100644 --- a/src/Hyprpaper.hpp +++ b/src/Hyprpaper.hpp @@ -1,13 +1,13 @@ #pragma once -#include "defines.hpp" #include "config/ConfigManager.hpp" -#include "render/WallpaperTarget.hpp" -#include "helpers/Monitor.hpp" +#include "defines.hpp" #include "events/Events.hpp" -#include "helpers/PoolBuffer.hpp" #include "helpers/MiscFunctions.hpp" +#include "helpers/Monitor.hpp" +#include "helpers/PoolBuffer.hpp" #include "ipc/Socket.hpp" +#include "render/WallpaperTarget.hpp" #include struct SWallpaperRenderData { @@ -17,17 +17,17 @@ struct SWallpaperRenderData { class CHyprpaper { public: // important - wl_display* m_sDisplay; // assured - wl_compositor* m_sCompositor; // assured - wl_shm* m_sSHM; // assured - zwlr_layer_shell_v1* m_sLayerShell = nullptr; // expected + wl_display* m_sDisplay; // assured + wl_compositor* m_sCompositor; // assured + wl_shm* m_sSHM; // assured + zwlr_layer_shell_v1* m_sLayerShell = nullptr; // expected wp_fractional_scale_manager_v1* m_sFractionalScale = nullptr; // will remain null if not bound - wp_viewporter* m_sViewporter = nullptr; // expected + wp_viewporter* m_sViewporter = nullptr; // expected // init the utility CHyprpaper(); - void init(); - void tick(bool force); + void init(); + void tick(bool force); std::unordered_map m_mWallpaperTargets; std::unordered_map m_mMonitorActiveWallpapers; @@ -36,39 +36,36 @@ public: std::vector> m_vBuffers; std::vector> m_vMonitors; - bool m_bIPCEnabled = true; - bool m_bRenderSplash = false; - float m_fSplashOffset = 2; std::string m_szExplicitConfigPath; - bool m_bNoFractionalScale = false; + bool m_bNoFractionalScale = false; - void removeOldHyprpaperImages(); - void preloadAllWallpapersFromConfig(); - void recheckAllMonitors(); - void ensureMonitorHasActiveWallpaper(SMonitor*); - void createLSForMonitor(SMonitor*); - void renderWallpaperForMonitor(SMonitor*); - void createBuffer(SPoolBuffer*, int32_t, int32_t, uint32_t); - void destroyBuffer(SPoolBuffer*); - int createPoolFile(size_t, std::string&); - bool setCloexec(const int&); - void clearWallpaperFromMonitor(const std::string&); - SMonitor* getMonitorFromName(const std::string&); - bool isPreloaded(const std::string&); - void recheckMonitor(SMonitor*); - void ensurePoolBuffersPresent(); + void removeOldHyprpaperImages(); + void preloadAllWallpapersFromConfig(); + void recheckAllMonitors(); + void ensureMonitorHasActiveWallpaper(SMonitor*); + void createLSForMonitor(SMonitor*); + void renderWallpaperForMonitor(SMonitor*); + void createBuffer(SPoolBuffer*, int32_t, int32_t, uint32_t); + void destroyBuffer(SPoolBuffer*); + int createPoolFile(size_t, std::string&); + bool setCloexec(const int&); + void clearWallpaperFromMonitor(const std::string&); + SMonitor* getMonitorFromName(const std::string&); + bool isPreloaded(const std::string&); + void recheckMonitor(SMonitor*); + void ensurePoolBuffersPresent(); SPoolBuffer* getPoolBuffer(SMonitor*, CWallpaperTarget*); - void unloadWallpaper(const std::string&); - void createSeat(wl_seat*); - bool lockSingleInstance(); // fails on multi-instance - void unlockSingleInstance(); + void unloadWallpaper(const std::string&); + void createSeat(wl_seat*); + bool lockSingleInstance(); // fails on multi-instance + void unlockSingleInstance(); - std::mutex m_mtTickMutex; + std::mutex m_mtTickMutex; + + SMonitor* m_pLastMonitor = nullptr; - SMonitor* m_pLastMonitor = nullptr; private: - - bool m_bShouldExit = false; + bool m_bShouldExit = false; }; inline std::unique_ptr g_pHyprpaper; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 1971583..1fcd34f 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -1,135 +1,18 @@ #include "ConfigManager.hpp" #include "../Hyprpaper.hpp" -CConfigManager::CConfigManager() { - // Initialize the configuration - // Read file from default location - // or from an explicit location given by user +static Hyprlang::CParseResult handleWallpaper(const char* C, const char* V) { + const std::string COMMAND = C; + const std::string VALUE = V; + Hyprlang::CParseResult result; - std::string configPath = getMainConfigPath(); - - std::ifstream ifs; - ifs.open(configPath); - - if (!ifs.good()) { - Debug::log(WARN, "Config file `%s` couldn't be opened. Running without a config!", configPath.c_str()); - return; - } - - std::string line = ""; - int linenum = 1; - if (ifs.is_open()) { - while (std::getline(ifs, line)) { - // Read line by line - try { - parseLine(line); - } catch (...) { - Debug::log(ERR, "Error reading line from config. Line:"); - Debug::log(NONE, "%s", line.c_str()); - - parseError += "Config error at line " + std::to_string(linenum) + ": Line parsing error."; - } - - if (!parseError.empty()) { - parseError = "Config error at line " + std::to_string(linenum) + ": " + parseError; - break; - } - - ++linenum; - } - - ifs.close(); - } - - if (!parseError.empty()) { - Debug::log(WARN, "Config parse error: \n%s\n\nRunning and ignoring errors...\n", parseError.c_str()); - return; - } -} - -std::string CConfigManager::getMainConfigPath() { - if (!g_pHyprpaper->m_szExplicitConfigPath.empty()) - return g_pHyprpaper->m_szExplicitConfigPath; - - static const char* xdgConfigHome = getenv("XDG_CONFIG_HOME"); - std::string configPath; - if (!xdgConfigHome) - configPath = getenv("HOME") + std::string("/.config"); - else - configPath = xdgConfigHome; - - return configPath + "/hypr/hyprpaper.conf"; -} - -std::string CConfigManager::removeBeginEndSpacesTabs(std::string str) { - while (str[0] == ' ' || str[0] == '\t') { - str = str.substr(1); - } - - while (str.length() != 0 && (str[str.length() - 1] == ' ' || str[str.length() - 1] == '\t')) { - str = str.substr(0, str.length() - 1); - } - - return str; -} - -void CConfigManager::parseLine(std::string& line) { - // first check if its not a comment - const auto COMMENTSTART = line.find_first_of('#'); - if (COMMENTSTART == 0) - return; - - // now, cut the comment off - if (COMMENTSTART != std::string::npos) - line = line.substr(0, COMMENTSTART); - - // Strip line - while (line[0] == ' ' || line[0] == '\t') { - line = line.substr(1); - } - - // And parse - // check if command - const auto EQUALSPLACE = line.find_first_of('='); - - if (EQUALSPLACE == std::string::npos) - return; - - const auto COMMAND = removeBeginEndSpacesTabs(line.substr(0, EQUALSPLACE)); - const auto VALUE = removeBeginEndSpacesTabs(line.substr(EQUALSPLACE + 1)); - - parseKeyword(COMMAND, VALUE); -} - -void CConfigManager::parseKeyword(const std::string& COMMAND, const std::string& VALUE) { - if (COMMAND == "wallpaper") - handleWallpaper(COMMAND, VALUE); - else if (COMMAND == "preload") - handlePreload(COMMAND, VALUE); - else if (COMMAND == "unload") - handleUnload(COMMAND, VALUE); - else if (COMMAND == "ipc") - g_pHyprpaper->m_bIPCEnabled = VALUE == "1" || VALUE == "yes" || VALUE == "on" || VALUE == "true"; - else if (COMMAND == "splash") - g_pHyprpaper->m_bRenderSplash = VALUE == "1" || VALUE == "yes" || VALUE == "on" || VALUE == "true"; - else if (COMMAND == "splash_offset") { - try { - g_pHyprpaper->m_fSplashOffset = std::clamp(std::stof(VALUE), 0.f, 100.f); - } catch (std::exception& e) { - parseError = "invalid splash_offset value " + VALUE; - } - } else - parseError = "unknown keyword " + COMMAND; -} - -void CConfigManager::handleWallpaper(const std::string& COMMAND, const std::string& VALUE) { if (VALUE.find_first_of(',') == std::string::npos) { - parseError = "wallpaper failed (syntax)"; - return; + result.setError("wallpaper failed (syntax)"); + return result; } auto MONITOR = VALUE.substr(0, VALUE.find_first_of(',')); - auto WALLPAPER = trimPath(VALUE.substr(VALUE.find_first_of(',') + 1)); + auto WALLPAPER = g_pConfigManager->trimPath(VALUE.substr(VALUE.find_first_of(',') + 1)); bool contain = false; @@ -144,21 +27,25 @@ void CConfigManager::handleWallpaper(const std::string& COMMAND, const std::stri } if (!std::filesystem::exists(WALLPAPER)) { - parseError = "wallpaper failed (no such file)"; - return; + result.setError("wallpaper failed (no such file)"); + return result; } - if (std::find(m_dRequestedPreloads.begin(), m_dRequestedPreloads.end(), WALLPAPER) == m_dRequestedPreloads.end() && !g_pHyprpaper->isPreloaded(WALLPAPER)) { - parseError = "wallpaper failed (not preloaded)"; - return; + 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; } g_pHyprpaper->clearWallpaperFromMonitor(MONITOR); g_pHyprpaper->m_mMonitorActiveWallpapers[MONITOR] = WALLPAPER; g_pHyprpaper->m_mMonitorWallpaperRenderData[MONITOR].contain = contain; + + return result; } -void CConfigManager::handlePreload(const std::string& COMMAND, const std::string& VALUE) { +static Hyprlang::CParseResult handlePreload(const char* C, const char* V) { + const std::string COMMAND = C; + const std::string VALUE = V; auto WALLPAPER = VALUE; if (WALLPAPER[0] == '~') { @@ -167,30 +54,19 @@ void CConfigManager::handlePreload(const std::string& COMMAND, const std::string } if (!std::filesystem::exists(WALLPAPER)) { - parseError = "preload failed (no such file)"; - return; + Hyprlang::CParseResult result; + result.setError((std::string{"no such file: "} + WALLPAPER).c_str()); + return result; } - m_dRequestedPreloads.emplace_back(WALLPAPER); + g_pConfigManager->m_dRequestedPreloads.emplace_back(WALLPAPER); + + return Hyprlang::CParseResult{}; } -void CConfigManager::handleUnload(const std::string& COMMAND, const std::string& VALUE) { - auto WALLPAPER = VALUE; - - if (VALUE == "all") { - handleUnloadAll(COMMAND, VALUE); - return; - } - - if (WALLPAPER[0] == '~') { - static const char* const ENVHOME = getenv("HOME"); - WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1); - } - - g_pHyprpaper->unloadWallpaper(WALLPAPER); -} - -void CConfigManager::handleUnloadAll(const std::string& COMMAND, const std::string& VALUE) { +static Hyprlang::CParseResult handleUnloadAll(const char* C, const char* V) { + const std::string COMMAND = C; + const std::string VALUE = V; std::vector toUnload; for (auto& [name, target] : g_pHyprpaper->m_mWallpaperTargets) { @@ -211,6 +87,69 @@ void CConfigManager::handleUnloadAll(const std::string& COMMAND, const std::stri for (auto& tu : toUnload) g_pHyprpaper->unloadWallpaper(tu); + + return Hyprlang::CParseResult{}; +} + +static Hyprlang::CParseResult handleUnload(const char* C, const char* V) { + const std::string COMMAND = C; + const std::string VALUE = V; + auto WALLPAPER = VALUE; + + if (VALUE == "all") + return handleUnloadAll(C, V); + + if (WALLPAPER[0] == '~') { + static const char* const ENVHOME = getenv("HOME"); + WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1); + } + + g_pHyprpaper->unloadWallpaper(WALLPAPER); + + return Hyprlang::CParseResult{}; +} + +CConfigManager::CConfigManager() { + // Initialize the configuration + // Read file from default location + // or from an explicit location given by user + + std::string configPath = getMainConfigPath(); + + config = std::make_unique(configPath.c_str(), Hyprlang::SConfigOptions{}); + + config->addConfigValue("ipc", {1L}); + config->addConfigValue("splash", {1L}); + config->addConfigValue("splash_offset", {2.F}); + + config->registerHandler(&handleWallpaper, "wallpaper", {.allowFlags = false}); + config->registerHandler(&handleUnload, "unload", {.allowFlags = false}); + config->registerHandler(&handlePreload, "preload", {.allowFlags = false}); + config->registerHandler(&handleUnloadAll, "unloadAll", {.allowFlags = false}); + + config->commence(); +} + +void CConfigManager::parse() { + const auto ERROR = config->parse(); + + if (ERROR.error) + std::cout << "Error in config: \n" + << ERROR.getError() << "\n"; +} + +std::string CConfigManager::getMainConfigPath() { + if (!g_pHyprpaper->m_szExplicitConfigPath.empty()) + return g_pHyprpaper->m_szExplicitConfigPath; + + static const char* xdgConfigHome = getenv("XDG_CONFIG_HOME"); + std::string configPath; + if (!xdgConfigHome) + configPath = getenv("HOME") + std::string("/.config"); + else + configPath = xdgConfigHome; + + return configPath + "/hypr/hyprpaper.conf"; } // trim from both ends diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index b424b61..67700e1 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -1,5 +1,6 @@ #pragma once #include "../defines.hpp" +#include class CIPCSocket; @@ -7,23 +8,15 @@ class CConfigManager { public: // gets all the data from the config CConfigManager(); + void parse(); std::deque m_dRequestedPreloads; std::string getMainConfigPath(); - -private: - std::string parseError; - - void parseLine(std::string&); - std::string removeBeginEndSpacesTabs(std::string in); - void parseKeyword(const std::string&, const std::string&); - - void handleWallpaper(const std::string&, const std::string&); - void handlePreload(const std::string&, const std::string&); - void handleUnload(const std::string&, const std::string&); - void handleUnloadAll(const std::string&, const std::string&); std::string trimPath(std::string path); + std::unique_ptr config; + +private: friend class CIPCSocket; }; diff --git a/src/ipc/Socket.cpp b/src/ipc/Socket.cpp index bd065b1..eeb4238 100644 --- a/src/ipc/Socket.cpp +++ b/src/ipc/Socket.cpp @@ -99,12 +99,11 @@ bool CIPCSocket::mainThreadParseRequest() { // parse if (copy.find("wallpaper") == 0 || copy.find("preload") == 0 || copy.find("unload") == 0) { - g_pConfigManager->parseError = ""; // reset parse error - g_pConfigManager->parseKeyword(copy.substr(0, copy.find_first_of(' ')), copy.substr(copy.find_first_of(' ') + 1)); + const auto RESULT = g_pConfigManager->config->parseDynamic(copy.substr(0, copy.find_first_of(' ')).c_str(), copy.substr(copy.find_first_of(' ') + 1).c_str()); - if (!g_pConfigManager->parseError.empty()) { - m_szReply = g_pConfigManager->parseError; + if (RESULT.error) { + m_szReply = RESULT.getError(); m_bReplyReady = true; m_bRequestReady = false; return false;