From f4ea0297a0ea4927dcd761a3a8f8bcdad3ba70c6 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Thu, 4 Apr 2024 16:19:15 +0100 Subject: [PATCH] core: Add support for toml manifests and metas ref #20 --- CMakeLists.txt | 2 +- hyprcursor-util/CMakeLists.txt | 3 +- hyprcursor-util/internalSharedTypes.hpp | 1 - hyprcursor-util/libhyprcursor | 1 + hyprcursor-util/src/main.cpp | 129 ++++------------ libhyprcursor/VarList.cpp | 55 +++++++ libhyprcursor/VarList.hpp | 63 ++++++++ libhyprcursor/hyprcursor.cpp | 190 ++++++------------------ libhyprcursor/manifest.cpp | 75 ++++++++++ libhyprcursor/manifest.hpp | 36 +++++ libhyprcursor/meta.cpp | 174 ++++++++++++++++++++++ libhyprcursor/meta.hpp | 36 +++++ tests/test.cpp | 2 +- 13 files changed, 521 insertions(+), 246 deletions(-) delete mode 120000 hyprcursor-util/internalSharedTypes.hpp create mode 120000 hyprcursor-util/libhyprcursor create mode 100644 libhyprcursor/VarList.cpp create mode 100644 libhyprcursor/VarList.hpp create mode 100644 libhyprcursor/manifest.cpp create mode 100644 libhyprcursor/manifest.hpp create mode 100644 libhyprcursor/meta.cpp create mode 100644 libhyprcursor/meta.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a5362c4..fd567b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ configure_file(hyprcursor.pc.in hyprcursor.pc @ONLY) set(CMAKE_CXX_STANDARD 23) find_package(PkgConfig REQUIRED) -pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprlang>=0.4.2 libzip cairo librsvg-2.0) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprlang>=0.4.2 libzip cairo librsvg-2.0 tomlplusplus) if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) message(STATUS "Configuring hyprcursor in Debug") diff --git a/hyprcursor-util/CMakeLists.txt b/hyprcursor-util/CMakeLists.txt index 81d2847..24f522d 100644 --- a/hyprcursor-util/CMakeLists.txt +++ b/hyprcursor-util/CMakeLists.txt @@ -10,10 +10,11 @@ pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprlang>=0.4.0 libzip) add_compile_definitions(HYPRCURSOR_VERSION="${HYPRCURSOR_VERSION}") file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") +file(GLOB_RECURSE HCFILES CONFIGURE_DEPENDS "libhyprcursor/*.cpp") set(CMAKE_CXX_STANDARD 23) -add_executable(hyprcursor-util ${SRCFILES}) +add_executable(hyprcursor-util ${SRCFILES} ${HCFILES}) target_link_libraries(hyprcursor-util PkgConfig::deps) target_include_directories(hyprcursor-util diff --git a/hyprcursor-util/internalSharedTypes.hpp b/hyprcursor-util/internalSharedTypes.hpp deleted file mode 120000 index 9402209..0000000 --- a/hyprcursor-util/internalSharedTypes.hpp +++ /dev/null @@ -1 +0,0 @@ -../libhyprcursor/internalSharedTypes.hpp \ No newline at end of file diff --git a/hyprcursor-util/libhyprcursor b/hyprcursor-util/libhyprcursor new file mode 120000 index 0000000..05d7f90 --- /dev/null +++ b/hyprcursor-util/libhyprcursor @@ -0,0 +1 @@ +../libhyprcursor \ No newline at end of file diff --git a/hyprcursor-util/src/main.cpp b/hyprcursor-util/src/main.cpp index f219380..1c6d24b 100644 --- a/hyprcursor-util/src/main.cpp +++ b/hyprcursor-util/src/main.cpp @@ -6,7 +6,9 @@ #include #include #include -#include "internalSharedTypes.hpp" +#include "../libhyprcursor/internalSharedTypes.hpp" +#include "../libhyprcursor/manifest.hpp" +#include "../libhyprcursor/meta.hpp" enum eOperation { OPERATION_CREATE = 0, @@ -48,7 +50,7 @@ static bool promptForDeletion(const std::string& path) { emptyDirectory = !std::count_if(std::filesystem::begin(IT), std::filesystem::end(IT), [](auto& e) { return e.is_regular_file(); }); } - if (!std::filesystem::exists(path + "/manifest.hl") && std::filesystem::exists(path) && !emptyDirectory) { + if (!std::filesystem::exists(path + "/manifest.hl") && !std::filesystem::exists(path + "/manifest.toml") && std::filesystem::exists(path) && !emptyDirectory) { std::cout << "Refusing to remove " << path << " because it doesn't look like a hyprcursor theme.\n" << "Please set a valid, empty, nonexistent, or a theme directory as an output path\n"; exit(1); @@ -69,88 +71,25 @@ static bool promptForDeletion(const std::string& path) { return true; } -std::unique_ptr currentTheme; - -static Hyprlang::CParseResult parseDefineSize(const char* C, const char* V) { - Hyprlang::CParseResult result; - const std::string VALUE = V; - - if (!VALUE.contains(",")) { - result.setError("Invalid define_size"); - return result; - } - - auto LHS = removeBeginEndSpacesTabs(VALUE.substr(0, VALUE.find_first_of(","))); - auto RHS = removeBeginEndSpacesTabs(VALUE.substr(VALUE.find_first_of(",") + 1)); - auto DELAY = 0; - - SCursorImage image; - - if (RHS.contains(",")) { - const auto LL = removeBeginEndSpacesTabs(RHS.substr(0, RHS.find(","))); - const auto RR = removeBeginEndSpacesTabs(RHS.substr(RHS.find(",") + 1)); - - try { - image.delay = std::stoull(RR); - } catch (std::exception& e) { - result.setError(e.what()); - return result; - } - - RHS = LL; - } - - image.filename = RHS; - - try { - image.size = std::stoull(LHS); - } catch (std::exception& e) { - result.setError(e.what()); - return result; - } - - currentTheme->shapes.back()->images.push_back(image); - - return result; -} - -static Hyprlang::CParseResult parseOverride(const char* C, const char* V) { - Hyprlang::CParseResult result; - const std::string VALUE = V; - - currentTheme->shapes.back()->overrides.push_back(V); - - return result; -} - static std::optional createCursorThemeFromPath(const std::string& path_, const std::string& out_ = {}) { if (!std::filesystem::exists(path_)) return "input path does not exist"; + SCursorTheme currentTheme; + const std::string path = std::filesystem::canonical(path_); - const auto MANIFESTPATH = path + "/manifest.hl"; - if (!std::filesystem::exists(MANIFESTPATH)) - return "manifest.hl is missing"; + CManifest manifest(path + "/manifest"); + const auto PARSERESULT = manifest.parse(); - std::unique_ptr manifest; - try { - manifest = std::make_unique(MANIFESTPATH.c_str(), Hyprlang::SConfigOptions{}); - manifest->addConfigValue("cursors_directory", Hyprlang::STRING{""}); - manifest->addConfigValue("name", Hyprlang::STRING{""}); - manifest->addConfigValue("description", Hyprlang::STRING{""}); - manifest->addConfigValue("version", Hyprlang::STRING{""}); - manifest->commence(); - const auto RESULT = manifest->parse(); - if (RESULT.error) - return "Manifest has errors: \n" + std::string{RESULT.getError()}; - } catch (const char* err) { return "failed parsing manifest: " + std::string{err}; } + if (PARSERESULT.has_value()) + return "couldn't parse manifest: " + *PARSERESULT; - const std::string THEMENAME = std::any_cast(manifest->getConfigValue("name")); + const std::string THEMENAME = manifest.parsedData.name; std::string out = (out_.empty() ? path.substr(0, path.find_last_of('/') + 1) : out_) + "/theme_" + THEMENAME + "/"; - const std::string CURSORSSUBDIR = std::any_cast(manifest->getConfigValue("cursors_directory")); + const std::string CURSORSSUBDIR = manifest.parsedData.cursorsDirectory; const std::string CURSORDIR = path + "/" + CURSORSSUBDIR; if (CURSORSSUBDIR.empty() || !std::filesystem::exists(CURSORDIR)) @@ -158,28 +97,21 @@ static std::optional createCursorThemeFromPath(const std::string& p // iterate over the directory and record all cursors - currentTheme = std::make_unique(); for (auto& dir : std::filesystem::directory_iterator(CURSORDIR)) { - const auto METAPATH = dir.path().string() + "/meta.hl"; + const auto METAPATH = dir.path().string() + "/meta"; - auto& SHAPE = currentTheme->shapes.emplace_back(std::make_unique()); + auto& SHAPE = currentTheme.shapes.emplace_back(std::make_unique()); // - std::unique_ptr meta; + CMeta meta{METAPATH, true, true}; + const auto PARSERESULT2 = meta.parse(); - try { - meta = std::make_unique(METAPATH.c_str(), Hyprlang::SConfigOptions{}); - meta->addConfigValue("hotspot_x", Hyprlang::FLOAT{0.F}); - meta->addConfigValue("hotspot_y", Hyprlang::FLOAT{0.F}); - meta->addConfigValue("resize_algorithm", Hyprlang::STRING{"nearest"}); - meta->registerHandler(::parseDefineSize, "define_size", {.allowFlags = false}); - meta->registerHandler(::parseOverride, "define_override", {.allowFlags = false}); - meta->commence(); - const auto RESULT = meta->parse(); + if (PARSERESULT2.has_value()) + return "couldn't parse meta: " + *PARSERESULT2; - if (RESULT.error) - return "meta.hl has errors: \n" + std::string{RESULT.getError()}; - } catch (const char* err) { return "failed parsing meta (" + METAPATH + "): " + std::string{err}; } + for (auto& i : meta.parsedData.definedSizes) { + SHAPE->images.push_back(SCursorImage{i.file, i.size, i.delayMs}); + } // check if we have at least one image. for (auto& i : SHAPE->images) { @@ -209,9 +141,9 @@ static std::optional createCursorThemeFromPath(const std::string& p return "meta invalid: no images for shape " + dir.path().stem().string(); SHAPE->directory = dir.path().stem().string(); - SHAPE->hotspotX = std::any_cast(meta->getConfigValue("hotspot_x")); - SHAPE->hotspotY = std::any_cast(meta->getConfigValue("hotspot_y")); - SHAPE->resizeAlgo = stringToAlgo(std::any_cast(meta->getConfigValue("resize_algorithm"))); + SHAPE->hotspotX = meta.parsedData.hotspotX; + SHAPE->hotspotY = meta.parsedData.hotspotY; + SHAPE->resizeAlgo = stringToAlgo(meta.parsedData.resizeAlgo); std::cout << "Shape " << SHAPE->directory << ": \n\toverrides: " << SHAPE->overrides.size() << "\n\tsizes: " << SHAPE->images.size() << "\n"; } @@ -226,13 +158,13 @@ static std::optional createCursorThemeFromPath(const std::string& p } // manifest is copied - std::filesystem::copy(MANIFESTPATH, out + "/manifest.hl"); + std::filesystem::copy(manifest.getPath(), out + "/manifest." + (manifest.getPath().ends_with(".hl") ? "hl" : "toml")); // create subdir for cursors std::filesystem::create_directory(out + "/" + CURSORSSUBDIR); // create zips (.hlc) for each - for (auto& shape : currentTheme->shapes) { + for (auto& shape : currentTheme.shapes) { const auto CURRENTCURSORSDIR = path + "/" + CURSORSSUBDIR + "/" + shape->directory; const auto OUTPUTFILE = out + "/" + CURSORSSUBDIR + "/" + shape->directory + ".hlc"; int errp = 0; @@ -245,11 +177,12 @@ static std::optional createCursorThemeFromPath(const std::string& p } // add meta.hl - zip_source_t* meta = zip_source_file(zip, (CURRENTCURSORSDIR + "/meta.hl").c_str(), 0, 0); + const auto METADIR = std::filesystem::exists(CURRENTCURSORSDIR + "/meta.hl") ? (CURRENTCURSORSDIR + "/meta.hl") : (CURRENTCURSORSDIR + "/meta.toml"); + zip_source_t* meta = zip_source_file(zip, METADIR.c_str(), 0, 0); if (!meta) - return "(1) failed to add meta " + (CURRENTCURSORSDIR + "/meta.hl") + " to hlc"; + return "(1) failed to add meta " + METADIR + " to hlc"; if (zip_file_add(zip, "meta.hl", meta, ZIP_FL_ENC_UTF_8) < 0) - return "(2) failed to add meta " + (CURRENTCURSORSDIR + "/meta.hl") + " to hlc"; + return "(2) failed to add meta " + METADIR + " to hlc"; meta = nullptr; @@ -275,7 +208,7 @@ static std::optional createCursorThemeFromPath(const std::string& p } // done! - std::cout << "Done, written " << currentTheme->shapes.size() << " shapes.\n"; + std::cout << "Done, written " << currentTheme.shapes.size() << " shapes.\n"; return {}; } diff --git a/libhyprcursor/VarList.cpp b/libhyprcursor/VarList.cpp new file mode 100644 index 0000000..518acde --- /dev/null +++ b/libhyprcursor/VarList.cpp @@ -0,0 +1,55 @@ +#include "VarList.hpp" +#include +#include + +static std::string removeBeginEndSpacesTabs(std::string str) { + if (str.empty()) + return str; + + int countBefore = 0; + while (str[countBefore] == ' ' || str[countBefore] == '\t') { + countBefore++; + } + + int countAfter = 0; + while ((int)str.length() - countAfter - 1 >= 0 && (str[str.length() - countAfter - 1] == ' ' || str[str.length() - 1 - countAfter] == '\t')) { + countAfter++; + } + + str = str.substr(countBefore, str.length() - countBefore - countAfter); + + return str; +} + +CVarList::CVarList(const std::string& in, const size_t lastArgNo, const char delim, const bool removeEmpty) { + if (in.empty()) + m_vArgs.emplace_back(""); + + std::string args{in}; + size_t idx = 0; + size_t pos = 0; + std::ranges::replace_if( + args, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0); + + for (const auto& s : args | std::views::split(0)) { + if (removeEmpty && s.empty()) + continue; + if (++idx == lastArgNo) { + m_vArgs.emplace_back(removeBeginEndSpacesTabs(in.substr(pos))); + break; + } + pos += s.size() + 1; + m_vArgs.emplace_back(removeBeginEndSpacesTabs(std::string_view{s}.data())); + } +} + +std::string CVarList::join(const std::string& joiner, size_t from, size_t to) const { + size_t last = to == 0 ? size() : to; + + std::string rolling; + for (size_t i = from; i < last; ++i) { + rolling += m_vArgs[i] + (i + 1 < last ? joiner : ""); + } + + return rolling; +} \ No newline at end of file diff --git a/libhyprcursor/VarList.hpp b/libhyprcursor/VarList.hpp new file mode 100644 index 0000000..1374da6 --- /dev/null +++ b/libhyprcursor/VarList.hpp @@ -0,0 +1,63 @@ +#pragma once +#include +#include +#include + +class CVarList { + public: + /** Split string into arg list + @param lastArgNo stop splitting after argv reaches maximum size, last arg will contain rest of unsplit args + @param delim if delimiter is 's', use std::isspace + @param removeEmpty remove empty args from argv + */ + CVarList(const std::string& in, const size_t maxSize = 0, const char delim = ',', const bool removeEmpty = false); + + ~CVarList() = default; + + size_t size() const { + return m_vArgs.size(); + } + + std::string join(const std::string& joiner, size_t from = 0, size_t to = 0) const; + + void map(std::function func) { + for (auto& s : m_vArgs) + func(s); + } + + void append(const std::string arg) { + m_vArgs.emplace_back(arg); + } + + std::string operator[](const size_t& idx) const { + if (idx >= m_vArgs.size()) + return ""; + return m_vArgs[idx]; + } + + // for range-based loops + std::vector::iterator begin() { + return m_vArgs.begin(); + } + std::vector::const_iterator begin() const { + return m_vArgs.begin(); + } + std::vector::iterator end() { + return m_vArgs.end(); + } + std::vector::const_iterator end() const { + return m_vArgs.end(); + } + + bool contains(const std::string& el) { + for (auto& a : m_vArgs) { + if (a == el) + return true; + } + + return false; + } + + private: + std::vector m_vArgs; +}; \ No newline at end of file diff --git a/libhyprcursor/hyprcursor.cpp b/libhyprcursor/hyprcursor.cpp index 304ab9f..df1cda4 100644 --- a/libhyprcursor/hyprcursor.cpp +++ b/libhyprcursor/hyprcursor.cpp @@ -3,12 +3,13 @@ #include "internalDefines.hpp" #include #include -#include #include #include #include #include +#include "manifest.hpp" +#include "meta.hpp" #include "Log.hpp" using namespace Hyprcursor; @@ -39,7 +40,7 @@ static bool pathAccessible(const std::string& path) { } static bool themeAccessible(const std::string& path) { - return pathAccessible(path + "/manifest.hl"); + return pathAccessible(path + "/manifest.hl") || pathAccessible(path + "manifest.toml"); } static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) { @@ -68,9 +69,9 @@ static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) { continue; } - const auto MANIFESTPATH = themeDir.path().string() + "/manifest.hl"; + const auto MANIFESTPATH = themeDir.path().string() + "/manifest."; - if (std::filesystem::exists(MANIFESTPATH)) { + if (std::filesystem::exists(MANIFESTPATH + "hl") || std::filesystem::exists(MANIFESTPATH + "toml")) { Debug::log(HC_LOG_INFO, logfn, "getFirstTheme: found {}", themeDir.path().string()); return themeDir.path().stem().string(); } @@ -94,9 +95,9 @@ static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) { continue; } - const auto MANIFESTPATH = themeDir.path().string() + "/manifest.hl"; + const auto MANIFESTPATH = themeDir.path().string() + "/manifest."; - if (!std::filesystem::exists(MANIFESTPATH)) { + if (std::filesystem::exists(MANIFESTPATH + "hl") || std::filesystem::exists(MANIFESTPATH + "toml")) { Debug::log(HC_LOG_INFO, logfn, "getFirstTheme: found {}", themeDir.path().string()); return themeDir.path().stem().string(); } @@ -130,10 +131,10 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL continue; } - const auto MANIFESTPATH = themeDir.path().string() + "/manifest.hl"; + const auto MANIFESTPATH = themeDir.path().string() + "/manifest"; if (name.empty()) { - if (std::filesystem::exists(MANIFESTPATH)) { + if (std::filesystem::exists(MANIFESTPATH + ".hl") || std::filesystem::exists(MANIFESTPATH + ".toml")) { Debug::log(HC_LOG_INFO, logfn, "getFullPathForThemeName: found {}", themeDir.path().string()); return std::filesystem::canonical(themeDir.path()).string(); } @@ -143,15 +144,11 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL if (!std::filesystem::exists(MANIFESTPATH)) continue; - std::unique_ptr manifest; - try { - manifest = std::make_unique(MANIFESTPATH.c_str(), Hyprlang::SConfigOptions{}); - manifest->addConfigValue("name", Hyprlang::STRING{""}); - manifest->commence(); - manifest->parse(); - } catch (const char* e) { continue; } + CManifest manifest{MANIFESTPATH}; + if (!manifest.parse().has_value()) + continue; - const std::string NAME = std::any_cast(manifest->getConfigValue("name")); + const std::string NAME = manifest.parsedData.name; if (NAME != name && name != themeDir.path().stem().string()) continue; @@ -178,20 +175,16 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL continue; } - const auto MANIFESTPATH = themeDir.path().string() + "/manifest.hl"; + const auto MANIFESTPATH = themeDir.path().string() + "/manifest"; - if (!std::filesystem::exists(MANIFESTPATH)) + if (std::filesystem::exists(MANIFESTPATH + ".hl") || std::filesystem::exists(MANIFESTPATH + ".toml")) continue; - std::unique_ptr manifest; - try { - manifest = std::make_unique(MANIFESTPATH.c_str(), Hyprlang::SConfigOptions{}); - manifest->addConfigValue("name", Hyprlang::STRING{""}); - manifest->commence(); - manifest->parse(); - } catch (const char* e) { continue; } + CManifest manifest{MANIFESTPATH}; + if (!manifest.parse().has_value()) + continue; - const std::string NAME = std::any_cast(manifest->getConfigValue("name")); + const std::string NAME = manifest.parsedData.name; if (NAME != name && name != themeDir.path().stem().string()) continue; @@ -520,88 +513,6 @@ void CHyprcursorManager::registerLoggingFunction(PHYPRCURSORLOGFUNC fn) { /* -Implementation - -*/ - -static std::string removeBeginEndSpacesTabs(std::string str) { - if (str.empty()) - return str; - - int countBefore = 0; - while (str[countBefore] == ' ' || str[countBefore] == '\t') { - countBefore++; - } - - int countAfter = 0; - while ((int)str.length() - countAfter - 1 >= 0 && (str[str.length() - countAfter - 1] == ' ' || str[str.length() - 1 - countAfter] == '\t')) { - countAfter++; - } - - str = str.substr(countBefore, str.length() - countBefore - countAfter); - - return str; -} - -SCursorTheme* currentTheme; - -static Hyprlang::CParseResult parseDefineSize(const char* C, const char* V) { - Hyprlang::CParseResult result; - const std::string VALUE = V; - - if (!VALUE.contains(",")) { - result.setError("Invalid define_size"); - return result; - } - - auto LHS = removeBeginEndSpacesTabs(VALUE.substr(0, VALUE.find_first_of(","))); - auto RHS = removeBeginEndSpacesTabs(VALUE.substr(VALUE.find_first_of(",") + 1)); - auto DELAY = 0; - - SCursorImage image; - - if (RHS.contains(",")) { - const auto LL = removeBeginEndSpacesTabs(RHS.substr(0, RHS.find(","))); - const auto RR = removeBeginEndSpacesTabs(RHS.substr(RHS.find(",") + 1)); - - try { - image.delay = std::stoull(RR); - } catch (std::exception& e) { - result.setError(e.what()); - return result; - } - - RHS = LL; - } - - image.filename = RHS; - - if (!image.filename.ends_with(".svg")) { - try { - image.size = std::stoull(LHS); - } catch (std::exception& e) { - result.setError(e.what()); - return result; - } - } else - image.size = 0; - - currentTheme->shapes.back()->images.push_back(image); - - return result; -} - -static Hyprlang::CParseResult parseOverride(const char* C, const char* V) { - Hyprlang::CParseResult result; - const std::string VALUE = V; - - currentTheme->shapes.back()->overrides.push_back(V); - - return result; -} - -/* - PNG reading */ @@ -618,7 +529,7 @@ static cairo_status_t readPNG(void* data, unsigned char* output, unsigned int le DATA->readNeedle += toRead; if (DATA->readNeedle >= DATA->dataLen) { - delete[](char*) DATA->data; + delete[] (char*)DATA->data; DATA->data = nullptr; } @@ -636,22 +547,14 @@ std::optional CHyprcursorImplementation::loadTheme() { if (!themeAccessible(themeFullDir)) return "Theme inaccessible"; - currentTheme = &theme; - // load manifest - std::unique_ptr manifest; - try { - // TODO: unify this between util and lib - manifest = std::make_unique((themeFullDir + "/manifest.hl").c_str(), Hyprlang::SConfigOptions{}); - manifest->addConfigValue("cursors_directory", Hyprlang::STRING{""}); - manifest->commence(); - manifest->parse(); - } catch (const char* err) { - Debug::log(HC_LOG_ERR, logFn, "Failed parsing manifest due to {}", err); - return std::string{"failed: "} + err; - } + CManifest manifest(themeFullDir + "/manifest"); + const auto PARSERESULT = manifest.parse(); - const std::string CURSORSSUBDIR = std::any_cast(manifest->getConfigValue("cursors_directory")); + if (PARSERESULT.has_value()) + return "couldn't parse manifest: " + *PARSERESULT; + + const std::string CURSORSSUBDIR = manifest.parsedData.cursorsDirectory; const std::string CURSORDIR = themeFullDir + "/" + CURSORSSUBDIR; if (CURSORSSUBDIR.empty() || !std::filesystem::exists(CURSORDIR)) @@ -671,8 +574,13 @@ std::optional CHyprcursorImplementation::loadTheme() { zip_t* zip = zip_open(cursor.path().string().c_str(), ZIP_RDONLY, &errp); zip_file_t* meta_file = zip_fopen(zip, "meta.hl", ZIP_FL_UNCHANGED); - if (!meta_file) - return "cursor" + cursor.path().string() + "failed to load meta"; + bool metaIsHL = true; + if (!meta_file) { + meta_file = zip_fopen(zip, "meta.toml", ZIP_FL_UNCHANGED); + metaIsHL = false; + if (!meta_file) + return "cursor" + cursor.path().string() + "failed to load meta"; + } char* buffer = new char[1024 * 1024]; /* 1MB should be more than enough */ @@ -687,24 +595,18 @@ std::optional CHyprcursorImplementation::loadTheme() { buffer[readBytes] = '\0'; - std::unique_ptr meta; - - try { - meta = std::make_unique(buffer, Hyprlang::SConfigOptions{.pathIsStream = true}); - meta->addConfigValue("hotspot_x", Hyprlang::FLOAT{0.F}); - meta->addConfigValue("hotspot_y", Hyprlang::FLOAT{0.F}); - meta->addConfigValue("resize_algorithm", Hyprlang::STRING{"nearest"}); - meta->registerHandler(::parseDefineSize, "define_size", {.allowFlags = false}); - meta->registerHandler(::parseOverride, "define_override", {.allowFlags = false}); - meta->commence(); - meta->parse(); - } catch (const char* err) { - delete[] buffer; - return "failed parsing meta: " + std::string{err}; - } + CMeta meta{buffer, metaIsHL}; delete[] buffer; + const auto METAPARSERESULT = meta.parse(); + if (METAPARSERESULT.has_value()) + return "cursor" + cursor.path().string() + "failed to parse meta: " + *METAPARSERESULT; + + for (auto& i : meta.parsedData.definedSizes) { + SHAPE->images.push_back(SCursorImage{i.file, i.size, i.delayMs}); + } + for (auto& i : SHAPE->images) { if (SHAPE->shapeType == SHAPE_INVALID) { if (i.filename.ends_with(".svg")) @@ -747,7 +649,7 @@ std::optional CHyprcursorImplementation::loadTheme() { IMAGE->cairoSurface = cairo_image_surface_create_from_png_stream(::readPNG, IMAGE); if (const auto STATUS = cairo_surface_status(IMAGE->cairoSurface); STATUS != CAIRO_STATUS_SUCCESS) { - delete[](char*) IMAGE->data; + delete[] (char*)IMAGE->data; IMAGE->data = nullptr; return "Failed reading cairoSurface, status " + std::to_string((int)STATUS); } @@ -760,9 +662,9 @@ std::optional CHyprcursorImplementation::loadTheme() { return "meta invalid: no images for shape " + cursor.path().stem().string(); SHAPE->directory = cursor.path().stem().string(); - SHAPE->hotspotX = std::any_cast(meta->getConfigValue("hotspot_x")); - SHAPE->hotspotY = std::any_cast(meta->getConfigValue("hotspot_y")); - SHAPE->resizeAlgo = stringToAlgo(std::any_cast(meta->getConfigValue("resize_algorithm"))); + SHAPE->hotspotX = meta.parsedData.hotspotX; + SHAPE->hotspotY = meta.parsedData.hotspotY; + SHAPE->resizeAlgo = stringToAlgo(meta.parsedData.resizeAlgo); zip_discard(zip); } diff --git a/libhyprcursor/manifest.cpp b/libhyprcursor/manifest.cpp new file mode 100644 index 0000000..1118f50 --- /dev/null +++ b/libhyprcursor/manifest.cpp @@ -0,0 +1,75 @@ +#include "manifest.hpp" + +#include +#include + +#include + +CManifest::CManifest(const std::string& path_) { + try { + if (std::filesystem::exists(path_ + ".hl")) { + path = path_ + ".hl"; + selectedParser = PARSER_HYPRLANG; + return; + } + + if (std::filesystem::exists(path_ + ".toml")) { + path = path_ + ".toml"; + selectedParser = PARSER_TOML; + return; + } + } catch (...) { ; } +} + +std::optional CManifest::parse() { + if (path.empty()) + return "Failed to find an appropriate manifest."; + + if (selectedParser == PARSER_HYPRLANG) + return parseHL(); + if (selectedParser == PARSER_TOML) + return parseTOML(); + + return "No parser available for " + path; +} + +std::optional CManifest::parseHL() { + std::unique_ptr manifest; + try { + // TODO: unify this between util and lib + manifest = std::make_unique(path.c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true}); + manifest->addConfigValue("cursors_directory", Hyprlang::STRING{""}); + manifest->addConfigValue("name", Hyprlang::STRING{""}); + manifest->addConfigValue("description", Hyprlang::STRING{""}); + manifest->addConfigValue("version", Hyprlang::STRING{""}); + manifest->addConfigValue("author", Hyprlang::STRING{""}); + manifest->commence(); + manifest->parse(); + } catch (const char* err) { return std::string{"failed: "} + err; } + + parsedData.cursorsDirectory = std::any_cast(manifest->getConfigValue("cursors_directory")); + parsedData.name = std::any_cast(manifest->getConfigValue("name")); + parsedData.description = std::any_cast(manifest->getConfigValue("description")); + parsedData.version = std::any_cast(manifest->getConfigValue("version")); + parsedData.author = std::any_cast(manifest->getConfigValue("author")); + + return {}; +} + +std::optional CManifest::parseTOML() { + try { + auto MANIFEST = toml::parse_file(path); + + parsedData.cursorsDirectory = MANIFEST["General"]["cursors_directory"].value_or(""); + parsedData.name = MANIFEST["General"]["name"].value_or(""); + parsedData.description = MANIFEST["General"]["description"].value_or(""); + parsedData.version = MANIFEST["General"]["version"].value_or(""); + parsedData.author = MANIFEST["General"]["author"].value_or(""); + } catch (...) { return "Failed parsing toml"; } + + return {}; +} + +std::string CManifest::getPath() { + return path; +} \ No newline at end of file diff --git a/libhyprcursor/manifest.hpp b/libhyprcursor/manifest.hpp new file mode 100644 index 0000000..d5cc170 --- /dev/null +++ b/libhyprcursor/manifest.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +/* + Manifest can parse manifest.hl and manifest.toml +*/ +class CManifest { + public: + /* + path_ is the path to a manifest WITHOUT the extension. + CManifest will attempt all parsable extensions (.hl, .toml) + */ + CManifest(const std::string& path_); + + std::optional parse(); + std::string getPath(); + + struct { + std::string name, description, version, cursorsDirectory, author; + } parsedData; + + private: + enum eParser { + PARSER_HYPRLANG = 0, + PARSER_TOML + }; + + std::optional parseHL(); + std::optional parseTOML(); + + eParser selectedParser = PARSER_HYPRLANG; + + std::string path; +}; \ No newline at end of file diff --git a/libhyprcursor/meta.cpp b/libhyprcursor/meta.cpp new file mode 100644 index 0000000..77e554e --- /dev/null +++ b/libhyprcursor/meta.cpp @@ -0,0 +1,174 @@ +#include "meta.hpp" + +#include +#include +#include + +#include "VarList.hpp" + +CMeta* currentMeta = nullptr; + +CMeta::CMeta(const std::string& rawdata_, bool hyprlang_ /* false for toml */, bool dataIsPath) : rawdata(rawdata_), hyprlang(hyprlang_), dataPath(dataIsPath) { + if (!dataIsPath) + return; + + rawdata = ""; + + try { + if (std::filesystem::exists(rawdata_ + ".hl")) { + rawdata = rawdata_ + ".hl"; + hyprlang = true; + return; + } + + if (std::filesystem::exists(rawdata_ + ".toml")) { + rawdata = rawdata_ + ".toml"; + hyprlang = false; + return; + } + } catch (...) {} +} + +std::optional CMeta::parse() { + if (rawdata.empty()) + return "Invalid meta (missing?)"; + + std::optional res; + + currentMeta = this; + + if (hyprlang) + res = parseHL(); + else + res = parseTOML(); + + currentMeta = nullptr; + + return res; +} + +static std::string removeBeginEndSpacesTabs(std::string str) { + if (str.empty()) + return str; + + int countBefore = 0; + while (str[countBefore] == ' ' || str[countBefore] == '\t') { + countBefore++; + } + + int countAfter = 0; + while ((int)str.length() - countAfter - 1 >= 0 && (str[str.length() - countAfter - 1] == ' ' || str[str.length() - 1 - countAfter] == '\t')) { + countAfter++; + } + + str = str.substr(countBefore, str.length() - countBefore - countAfter); + + return str; +} + +static Hyprlang::CParseResult parseDefineSize(const char* C, const char* V) { + Hyprlang::CParseResult result; + const std::string VALUE = V; + + if (!VALUE.contains(",")) { + result.setError("Invalid define_size"); + return result; + } + + auto LHS = removeBeginEndSpacesTabs(VALUE.substr(0, VALUE.find_first_of(","))); + auto RHS = removeBeginEndSpacesTabs(VALUE.substr(VALUE.find_first_of(",") + 1)); + auto DELAY = 0; + + CMeta::SDefinedSize size; + + if (RHS.contains(",")) { + const auto LL = removeBeginEndSpacesTabs(RHS.substr(0, RHS.find(","))); + const auto RR = removeBeginEndSpacesTabs(RHS.substr(RHS.find(",") + 1)); + + try { + size.delayMs = std::stoull(RR); + } catch (std::exception& e) { + result.setError(e.what()); + return result; + } + + RHS = LL; + } + + size.file = RHS; + + if (!size.file.ends_with(".svg")) { + try { + size.size = std::stoull(LHS); + } catch (std::exception& e) { + result.setError(e.what()); + return result; + } + } else + size.size = 0; + + currentMeta->parsedData.definedSizes.push_back(size); + + return result; +} + +static Hyprlang::CParseResult parseOverride(const char* C, const char* V) { + Hyprlang::CParseResult result; + const std::string VALUE = V; + + currentMeta->parsedData.overrides.push_back(VALUE); + + return result; +} + +std::optional CMeta::parseHL() { + std::unique_ptr meta; + + try { + meta = std::make_unique(rawdata.c_str(), Hyprlang::SConfigOptions{.pathIsStream = !dataPath}); + meta->addConfigValue("hotspot_x", Hyprlang::FLOAT{0.F}); + meta->addConfigValue("hotspot_y", Hyprlang::FLOAT{0.F}); + meta->addConfigValue("resize_algorithm", Hyprlang::STRING{"nearest"}); + meta->registerHandler(::parseDefineSize, "define_size", {.allowFlags = false}); + meta->registerHandler(::parseOverride, "define_override", {.allowFlags = false}); + meta->commence(); + meta->parse(); + } catch (const char* err) { return "failed parsing meta: " + std::string{err}; } + + parsedData.hotspotX = std::any_cast(meta->getConfigValue("hotspot_x")); + parsedData.hotspotY = std::any_cast(meta->getConfigValue("hotspot_y")); + parsedData.resizeAlgo = std::any_cast(meta->getConfigValue("resize_algorithm")); + + return {}; +} + +std::optional CMeta::parseTOML() { + try { + auto MANIFEST = dataPath ? toml::parse_file(rawdata) : toml::parse(rawdata); + + parsedData.hotspotX = MANIFEST["General"]["hotspot_x"].value_or(0.f); + parsedData.hotspotY = MANIFEST["General"]["hotspot_y"].value_or(0.f); + + const std::string OVERRIDES = MANIFEST["General"]["define_override"].value_or(""); + const std::string SIZES = MANIFEST["General"]["define_size"].value_or(""); + + // + CVarList OVERRIDESLIST(OVERRIDES, 0, ';', true); + for (auto& o : OVERRIDESLIST) { + const auto RESULT = ::parseOverride("define_override", o.c_str()); + if (RESULT.error) + throw; + } + + CVarList SIZESLIST(SIZES, 0, ';', true); + for (auto& s : SIZESLIST) { + const auto RESULT = ::parseDefineSize("define_size", s.c_str()); + if (RESULT.error) + throw; + } + + parsedData.resizeAlgo = MANIFEST["General"]["resize_algorithm"].value_or(""); + } catch (std::exception& e) { return std::string{"Failed parsing toml: "} + e.what(); } + + return {}; +} diff --git a/libhyprcursor/meta.hpp b/libhyprcursor/meta.hpp new file mode 100644 index 0000000..837d5ee --- /dev/null +++ b/libhyprcursor/meta.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +/* + Meta can parse meta.hl and meta.toml +*/ +class CMeta { + public: + CMeta(const std::string& rawdata_, bool hyprlang_ /* false for toml */, bool dataIsPath = false); + + std::optional parse(); + + struct SDefinedSize { + std::string file; + int size = 0, delayMs = 200; + }; + + struct { + std::string resizeAlgo; + float hotspotX = 0, hotspotY = 0; + std::vector overrides; + std::vector definedSizes; + } parsedData; + + private: + std::optional parseHL(); + std::optional parseTOML(); + + bool dataPath = false; + bool hyprlang = true; + + std::string rawdata; +}; \ No newline at end of file diff --git a/tests/test.cpp b/tests/test.cpp index aa1b026..bc5fae6 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -11,7 +11,7 @@ void logFunction(enum eHyprcursorLogLevel level, char* message) { */ int main(int argc, char** argv) { - Hyprcursor::CHyprcursorManager mgr("classicFlatHypr", logFunction); + Hyprcursor::CHyprcursorManager mgr("TESTTHEME", logFunction); if (!mgr.valid()) { std::cout << "mgr is invalid\n";