Skip to content

Commit

Permalink
core: Add support for toml manifests and metas
Browse files Browse the repository at this point in the history
ref #20
  • Loading branch information
vaxerski committed Apr 4, 2024
1 parent be7e9f9 commit f4ea029
Show file tree
Hide file tree
Showing 13 changed files with 525 additions and 250 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
3 changes: 2 additions & 1 deletion hyprcursor-util/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion hyprcursor-util/internalSharedTypes.hpp

This file was deleted.

1 change: 1 addition & 0 deletions hyprcursor-util/libhyprcursor
137 changes: 35 additions & 102 deletions hyprcursor-util/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#include <format>
#include <algorithm>
#include <hyprlang.hpp>
#include "internalSharedTypes.hpp"
#include "../libhyprcursor/internalSharedTypes.hpp"
#include "../libhyprcursor/manifest.hpp"
#include "../libhyprcursor/meta.hpp"

enum eOperation {
OPERATION_CREATE = 0,
Expand Down Expand Up @@ -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);
Expand All @@ -69,117 +71,47 @@ static bool promptForDeletion(const std::string& path) {
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_ = {}) {
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";

std::unique_ptr<Hyprlang::CConfig> manifest;
try {
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"));
CManifest manifest(path + "/manifest");
const auto PARSERESULT = manifest.parse();

if (PARSERESULT.has_value())
return "couldn't parse manifest: " + *PARSERESULT;

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<Hyprlang::STRING>(manifest->getConfigValue("cursors_directory"));
const std::string CURSORSSUBDIR = manifest.parsedData.cursorsDirectory;
const std::string CURSORDIR = path + "/" + CURSORSSUBDIR;

if (CURSORSSUBDIR.empty() || !std::filesystem::exists(CURSORDIR))
return "manifest: cursors_directory missing or empty";

// iterate over the directory and record all cursors

currentTheme = std::make_unique<SCursorTheme>();
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;

try {
meta = std::make_unique<Hyprlang::CConfig>(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 (RESULT.error)
return "meta.hl has errors: \n" + std::string{RESULT.getError()};
} catch (const char* err) { return "failed parsing meta (" + METAPATH + "): " + std::string{err}; }
CMeta meta{METAPATH, true, true};
const auto PARSERESULT2 = meta.parse();

if (PARSERESULT2.has_value())
return "couldn't parse meta: " + *PARSERESULT2;

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) {
Expand Down Expand Up @@ -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();

SHAPE->directory = dir.path().stem().string();
SHAPE->hotspotX = std::any_cast<float>(meta->getConfigValue("hotspot_x"));
SHAPE->hotspotY = std::any_cast<float>(meta->getConfigValue("hotspot_y"));
SHAPE->resizeAlgo = stringToAlgo(std::any_cast<Hyprlang::STRING>(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";
}
Expand All @@ -226,13 +158,13 @@ static std::optional<std::string> 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;
Expand All @@ -245,11 +177,12 @@ static std::optional<std::string> 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;

Expand All @@ -275,7 +208,7 @@ static std::optional<std::string> 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 {};
}
Expand Down
55 changes: 55 additions & 0 deletions libhyprcursor/VarList.cpp
Original file line number Diff line number Diff line change
@@ -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 changes: 63 additions & 0 deletions libhyprcursor/VarList.hpp
Original file line number Diff line number Diff line change
@@ -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;
};
Loading

0 comments on commit f4ea029

Please sign in to comment.