core: Add support for toml manifests and metas

ref #20
This commit is contained in:
Vaxry 2024-04-04 16:19:15 +01:00
parent be7e9f93cf
commit f4ea0297a0
13 changed files with 521 additions and 246 deletions

View file

@ -20,7 +20,7 @@ configure_file(hyprcursor.pc.in hyprcursor.pc @ONLY)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
find_package(PkgConfig REQUIRED) 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) if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Configuring hyprcursor in Debug") message(STATUS "Configuring hyprcursor in Debug")

View file

@ -10,10 +10,11 @@ pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprlang>=0.4.0 libzip)
add_compile_definitions(HYPRCURSOR_VERSION="${HYPRCURSOR_VERSION}") add_compile_definitions(HYPRCURSOR_VERSION="${HYPRCURSOR_VERSION}")
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
file(GLOB_RECURSE HCFILES CONFIGURE_DEPENDS "libhyprcursor/*.cpp")
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
add_executable(hyprcursor-util ${SRCFILES}) add_executable(hyprcursor-util ${SRCFILES} ${HCFILES})
target_link_libraries(hyprcursor-util PkgConfig::deps) target_link_libraries(hyprcursor-util PkgConfig::deps)
target_include_directories(hyprcursor-util target_include_directories(hyprcursor-util

View file

@ -1 +0,0 @@
../libhyprcursor/internalSharedTypes.hpp

View file

@ -0,0 +1 @@
../libhyprcursor

View file

@ -6,7 +6,9 @@
#include <format> #include <format>
#include <algorithm> #include <algorithm>
#include <hyprlang.hpp> #include <hyprlang.hpp>
#include "internalSharedTypes.hpp" #include "../libhyprcursor/internalSharedTypes.hpp"
#include "../libhyprcursor/manifest.hpp"
#include "../libhyprcursor/meta.hpp"
enum eOperation { enum eOperation {
OPERATION_CREATE = 0, 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(); }); 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" 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"; << "Please set a valid, empty, nonexistent, or a theme directory as an output path\n";
exit(1); exit(1);
@ -69,88 +71,25 @@ static bool promptForDeletion(const std::string& path) {
return true; return true;
} }
std::unique_ptr<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;
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<std::string> createCursorThemeFromPath(const std::string& path_, const std::string& out_ = {}) { static std::optional<std::string> createCursorThemeFromPath(const std::string& path_, const std::string& out_ = {}) {
if (!std::filesystem::exists(path_)) if (!std::filesystem::exists(path_))
return "input path does not exist"; return "input path does not exist";
SCursorTheme currentTheme;
const std::string path = std::filesystem::canonical(path_); const std::string path = std::filesystem::canonical(path_);
const auto MANIFESTPATH = path + "/manifest.hl"; CManifest manifest(path + "/manifest");
if (!std::filesystem::exists(MANIFESTPATH)) const auto PARSERESULT = manifest.parse();
return "manifest.hl is missing";
std::unique_ptr<Hyprlang::CConfig> manifest; if (PARSERESULT.has_value())
try { return "couldn't parse manifest: " + *PARSERESULT;
manifest = std::make_unique<Hyprlang::CConfig>(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}; }
const std::string THEMENAME = std::any_cast<Hyprlang::STRING>(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 + "/"; std::string out = (out_.empty() ? path.substr(0, path.find_last_of('/') + 1) : out_) + "/theme_" + THEMENAME + "/";
const std::string CURSORSSUBDIR = std::any_cast<Hyprlang::STRING>(manifest->getConfigValue("cursors_directory")); const std::string CURSORSSUBDIR = manifest.parsedData.cursorsDirectory;
const std::string CURSORDIR = path + "/" + CURSORSSUBDIR; const std::string CURSORDIR = path + "/" + CURSORSSUBDIR;
if (CURSORSSUBDIR.empty() || !std::filesystem::exists(CURSORDIR)) if (CURSORSSUBDIR.empty() || !std::filesystem::exists(CURSORDIR))
@ -158,28 +97,21 @@ static std::optional<std::string> createCursorThemeFromPath(const std::string& p
// iterate over the directory and record all cursors // iterate over the directory and record all cursors
currentTheme = std::make_unique<SCursorTheme>();
for (auto& dir : std::filesystem::directory_iterator(CURSORDIR)) { 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<SCursorShape>()); auto& SHAPE = currentTheme.shapes.emplace_back(std::make_unique<SCursorShape>());
// //
std::unique_ptr<Hyprlang::CConfig> meta; CMeta meta{METAPATH, true, true};
const auto PARSERESULT2 = meta.parse();
try { if (PARSERESULT2.has_value())
meta = std::make_unique<Hyprlang::CConfig>(METAPATH.c_str(), Hyprlang::SConfigOptions{}); return "couldn't parse meta: " + *PARSERESULT2;
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 (RESULT.error) for (auto& i : meta.parsedData.definedSizes) {
return "meta.hl has errors: \n" + std::string{RESULT.getError()}; SHAPE->images.push_back(SCursorImage{i.file, i.size, i.delayMs});
} catch (const char* err) { return "failed parsing meta (" + METAPATH + "): " + std::string{err}; } }
// check if we have at least one image. // check if we have at least one image.
for (auto& i : SHAPE->images) { for (auto& i : SHAPE->images) {
@ -209,9 +141,9 @@ static std::optional<std::string> createCursorThemeFromPath(const std::string& p
return "meta invalid: no images for shape " + dir.path().stem().string(); return "meta invalid: no images for shape " + dir.path().stem().string();
SHAPE->directory = dir.path().stem().string(); SHAPE->directory = dir.path().stem().string();
SHAPE->hotspotX = std::any_cast<float>(meta->getConfigValue("hotspot_x")); SHAPE->hotspotX = meta.parsedData.hotspotX;
SHAPE->hotspotY = std::any_cast<float>(meta->getConfigValue("hotspot_y")); SHAPE->hotspotY = meta.parsedData.hotspotY;
SHAPE->resizeAlgo = stringToAlgo(std::any_cast<Hyprlang::STRING>(meta->getConfigValue("resize_algorithm"))); SHAPE->resizeAlgo = stringToAlgo(meta.parsedData.resizeAlgo);
std::cout << "Shape " << SHAPE->directory << ": \n\toverrides: " << SHAPE->overrides.size() << "\n\tsizes: " << SHAPE->images.size() << "\n"; std::cout << "Shape " << SHAPE->directory << ": \n\toverrides: " << SHAPE->overrides.size() << "\n\tsizes: " << SHAPE->images.size() << "\n";
} }
@ -226,13 +158,13 @@ static std::optional<std::string> createCursorThemeFromPath(const std::string& p
} }
// manifest is copied // 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 // create subdir for cursors
std::filesystem::create_directory(out + "/" + CURSORSSUBDIR); std::filesystem::create_directory(out + "/" + CURSORSSUBDIR);
// create zips (.hlc) for each // create zips (.hlc) for each
for (auto& shape : currentTheme->shapes) { for (auto& shape : currentTheme.shapes) {
const auto CURRENTCURSORSDIR = path + "/" + CURSORSSUBDIR + "/" + shape->directory; const auto CURRENTCURSORSDIR = path + "/" + CURSORSSUBDIR + "/" + shape->directory;
const auto OUTPUTFILE = out + "/" + CURSORSSUBDIR + "/" + shape->directory + ".hlc"; const auto OUTPUTFILE = out + "/" + CURSORSSUBDIR + "/" + shape->directory + ".hlc";
int errp = 0; int errp = 0;
@ -245,11 +177,12 @@ static std::optional<std::string> createCursorThemeFromPath(const std::string& p
} }
// add meta.hl // 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) 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) 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; meta = nullptr;
@ -275,7 +208,7 @@ static std::optional<std::string> createCursorThemeFromPath(const std::string& p
} }
// done! // done!
std::cout << "Done, written " << currentTheme->shapes.size() << " shapes.\n"; std::cout << "Done, written " << currentTheme.shapes.size() << " shapes.\n";
return {}; return {};
} }

55
libhyprcursor/VarList.cpp Normal file
View file

@ -0,0 +1,55 @@
#include "VarList.hpp"
#include <ranges>
#include <algorithm>
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;
}

63
libhyprcursor/VarList.hpp Normal file
View file

@ -0,0 +1,63 @@
#pragma once
#include <functional>
#include <vector>
#include <string>
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<void(std::string&)> 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<std::string>::iterator begin() {
return m_vArgs.begin();
}
std::vector<std::string>::const_iterator begin() const {
return m_vArgs.begin();
}
std::vector<std::string>::iterator end() {
return m_vArgs.end();
}
std::vector<std::string>::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<std::string> m_vArgs;
};

View file

@ -3,12 +3,13 @@
#include "internalDefines.hpp" #include "internalDefines.hpp"
#include <array> #include <array>
#include <filesystem> #include <filesystem>
#include <hyprlang.hpp>
#include <zip.h> #include <zip.h>
#include <cstring> #include <cstring>
#include <algorithm> #include <algorithm>
#include <librsvg/rsvg.h> #include <librsvg/rsvg.h>
#include "manifest.hpp"
#include "meta.hpp"
#include "Log.hpp" #include "Log.hpp"
using namespace Hyprcursor; using namespace Hyprcursor;
@ -39,7 +40,7 @@ static bool pathAccessible(const std::string& path) {
} }
static bool themeAccessible(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) { static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) {
@ -68,9 +69,9 @@ static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) {
continue; 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()); Debug::log(HC_LOG_INFO, logfn, "getFirstTheme: found {}", themeDir.path().string());
return themeDir.path().stem().string(); return themeDir.path().stem().string();
} }
@ -94,9 +95,9 @@ static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) {
continue; 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()); Debug::log(HC_LOG_INFO, logfn, "getFirstTheme: found {}", themeDir.path().string());
return themeDir.path().stem().string(); return themeDir.path().stem().string();
} }
@ -130,10 +131,10 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL
continue; continue;
} }
const auto MANIFESTPATH = themeDir.path().string() + "/manifest.hl"; const auto MANIFESTPATH = themeDir.path().string() + "/manifest";
if (name.empty()) { 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()); Debug::log(HC_LOG_INFO, logfn, "getFullPathForThemeName: found {}", themeDir.path().string());
return std::filesystem::canonical(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)) if (!std::filesystem::exists(MANIFESTPATH))
continue; continue;
std::unique_ptr<Hyprlang::CConfig> manifest; CManifest manifest{MANIFESTPATH};
try { if (!manifest.parse().has_value())
manifest = std::make_unique<Hyprlang::CConfig>(MANIFESTPATH.c_str(), Hyprlang::SConfigOptions{}); continue;
manifest->addConfigValue("name", Hyprlang::STRING{""});
manifest->commence();
manifest->parse();
} catch (const char* e) { continue; }
const std::string NAME = std::any_cast<Hyprlang::STRING>(manifest->getConfigValue("name")); const std::string NAME = manifest.parsedData.name;
if (NAME != name && name != themeDir.path().stem().string()) if (NAME != name && name != themeDir.path().stem().string())
continue; continue;
@ -178,20 +175,16 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL
continue; 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; continue;
std::unique_ptr<Hyprlang::CConfig> manifest; CManifest manifest{MANIFESTPATH};
try { if (!manifest.parse().has_value())
manifest = std::make_unique<Hyprlang::CConfig>(MANIFESTPATH.c_str(), Hyprlang::SConfigOptions{}); continue;
manifest->addConfigValue("name", Hyprlang::STRING{""});
manifest->commence();
manifest->parse();
} catch (const char* e) { continue; }
const std::string NAME = std::any_cast<Hyprlang::STRING>(manifest->getConfigValue("name")); const std::string NAME = manifest.parsedData.name;
if (NAME != name && name != themeDir.path().stem().string()) if (NAME != name && name != themeDir.path().stem().string())
continue; 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 PNG reading
*/ */
@ -618,7 +529,7 @@ static cairo_status_t readPNG(void* data, unsigned char* output, unsigned int le
DATA->readNeedle += toRead; DATA->readNeedle += toRead;
if (DATA->readNeedle >= DATA->dataLen) { if (DATA->readNeedle >= DATA->dataLen) {
delete[](char*) DATA->data; delete[] (char*)DATA->data;
DATA->data = nullptr; DATA->data = nullptr;
} }
@ -636,22 +547,14 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
if (!themeAccessible(themeFullDir)) if (!themeAccessible(themeFullDir))
return "Theme inaccessible"; return "Theme inaccessible";
currentTheme = &theme;
// load manifest // load manifest
std::unique_ptr<Hyprlang::CConfig> manifest; CManifest manifest(themeFullDir + "/manifest");
try { const auto PARSERESULT = manifest.parse();
// TODO: unify this between util and lib
manifest = std::make_unique<Hyprlang::CConfig>((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;
}
const std::string CURSORSSUBDIR = std::any_cast<Hyprlang::STRING>(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; const std::string CURSORDIR = themeFullDir + "/" + CURSORSSUBDIR;
if (CURSORSSUBDIR.empty() || !std::filesystem::exists(CURSORDIR)) if (CURSORSSUBDIR.empty() || !std::filesystem::exists(CURSORDIR))
@ -671,8 +574,13 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
zip_t* zip = zip_open(cursor.path().string().c_str(), ZIP_RDONLY, &errp); 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); zip_file_t* meta_file = zip_fopen(zip, "meta.hl", ZIP_FL_UNCHANGED);
if (!meta_file) bool metaIsHL = true;
return "cursor" + cursor.path().string() + "failed to load meta"; 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 */ char* buffer = new char[1024 * 1024]; /* 1MB should be more than enough */
@ -687,24 +595,18 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
buffer[readBytes] = '\0'; buffer[readBytes] = '\0';
std::unique_ptr<Hyprlang::CConfig> meta; CMeta meta{buffer, metaIsHL};
try {
meta = std::make_unique<Hyprlang::CConfig>(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};
}
delete[] buffer; 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) { for (auto& i : SHAPE->images) {
if (SHAPE->shapeType == SHAPE_INVALID) { if (SHAPE->shapeType == SHAPE_INVALID) {
if (i.filename.ends_with(".svg")) if (i.filename.ends_with(".svg"))
@ -747,7 +649,7 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
IMAGE->cairoSurface = cairo_image_surface_create_from_png_stream(::readPNG, IMAGE); IMAGE->cairoSurface = cairo_image_surface_create_from_png_stream(::readPNG, IMAGE);
if (const auto STATUS = cairo_surface_status(IMAGE->cairoSurface); STATUS != CAIRO_STATUS_SUCCESS) { if (const auto STATUS = cairo_surface_status(IMAGE->cairoSurface); STATUS != CAIRO_STATUS_SUCCESS) {
delete[](char*) IMAGE->data; delete[] (char*)IMAGE->data;
IMAGE->data = nullptr; IMAGE->data = nullptr;
return "Failed reading cairoSurface, status " + std::to_string((int)STATUS); return "Failed reading cairoSurface, status " + std::to_string((int)STATUS);
} }
@ -760,9 +662,9 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
return "meta invalid: no images for shape " + cursor.path().stem().string(); return "meta invalid: no images for shape " + cursor.path().stem().string();
SHAPE->directory = cursor.path().stem().string(); SHAPE->directory = cursor.path().stem().string();
SHAPE->hotspotX = std::any_cast<float>(meta->getConfigValue("hotspot_x")); SHAPE->hotspotX = meta.parsedData.hotspotX;
SHAPE->hotspotY = std::any_cast<float>(meta->getConfigValue("hotspot_y")); SHAPE->hotspotY = meta.parsedData.hotspotY;
SHAPE->resizeAlgo = stringToAlgo(std::any_cast<Hyprlang::STRING>(meta->getConfigValue("resize_algorithm"))); SHAPE->resizeAlgo = stringToAlgo(meta.parsedData.resizeAlgo);
zip_discard(zip); zip_discard(zip);
} }

View file

@ -0,0 +1,75 @@
#include "manifest.hpp"
#include <toml++/toml.hpp>
#include <hyprlang.hpp>
#include <filesystem>
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<std::string> 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<std::string> CManifest::parseHL() {
std::unique_ptr<Hyprlang::CConfig> manifest;
try {
// TODO: unify this between util and lib
manifest = std::make_unique<Hyprlang::CConfig>(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<Hyprlang::STRING>(manifest->getConfigValue("cursors_directory"));
parsedData.name = std::any_cast<Hyprlang::STRING>(manifest->getConfigValue("name"));
parsedData.description = std::any_cast<Hyprlang::STRING>(manifest->getConfigValue("description"));
parsedData.version = std::any_cast<Hyprlang::STRING>(manifest->getConfigValue("version"));
parsedData.author = std::any_cast<Hyprlang::STRING>(manifest->getConfigValue("author"));
return {};
}
std::optional<std::string> 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;
}

View file

@ -0,0 +1,36 @@
#pragma once
#include <string>
#include <optional>
/*
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<std::string> parse();
std::string getPath();
struct {
std::string name, description, version, cursorsDirectory, author;
} parsedData;
private:
enum eParser {
PARSER_HYPRLANG = 0,
PARSER_TOML
};
std::optional<std::string> parseHL();
std::optional<std::string> parseTOML();
eParser selectedParser = PARSER_HYPRLANG;
std::string path;
};

174
libhyprcursor/meta.cpp Normal file
View file

@ -0,0 +1,174 @@
#include "meta.hpp"
#include <hyprlang.hpp>
#include <toml++/toml.hpp>
#include <filesystem>
#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<std::string> CMeta::parse() {
if (rawdata.empty())
return "Invalid meta (missing?)";
std::optional<std::string> 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<std::string> CMeta::parseHL() {
std::unique_ptr<Hyprlang::CConfig> meta;
try {
meta = std::make_unique<Hyprlang::CConfig>(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<Hyprlang::FLOAT>(meta->getConfigValue("hotspot_x"));
parsedData.hotspotY = std::any_cast<Hyprlang::FLOAT>(meta->getConfigValue("hotspot_y"));
parsedData.resizeAlgo = std::any_cast<Hyprlang::STRING>(meta->getConfigValue("resize_algorithm"));
return {};
}
std::optional<std::string> 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 {};
}

36
libhyprcursor/meta.hpp Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include <string>
#include <optional>
#include <vector>
/*
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<std::string> parse();
struct SDefinedSize {
std::string file;
int size = 0, delayMs = 200;
};
struct {
std::string resizeAlgo;
float hotspotX = 0, hotspotY = 0;
std::vector<std::string> overrides;
std::vector<SDefinedSize> definedSizes;
} parsedData;
private:
std::optional<std::string> parseHL();
std::optional<std::string> parseTOML();
bool dataPath = false;
bool hyprlang = true;
std::string rawdata;
};

View file

@ -11,7 +11,7 @@ void logFunction(enum eHyprcursorLogLevel level, char* message) {
*/ */
int main(int argc, char** argv) { int main(int argc, char** argv) {
Hyprcursor::CHyprcursorManager mgr("classicFlatHypr", logFunction); Hyprcursor::CHyprcursorManager mgr("TESTTHEME", logFunction);
if (!mgr.valid()) { if (!mgr.valid()) {
std::cout << "mgr is invalid\n"; std::cout << "mgr is invalid\n";