mirror of
https://github.com/hyprwm/hyprcursor.git
synced 2024-12-22 02:09:48 +01:00
parent
be7e9f93cf
commit
f4ea0297a0
13 changed files with 521 additions and 246 deletions
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../libhyprcursor/internalSharedTypes.hpp
|
1
hyprcursor-util/libhyprcursor
Symbolic link
1
hyprcursor-util/libhyprcursor
Symbolic link
|
@ -0,0 +1 @@
|
|||
../libhyprcursor
|
|
@ -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,
|
||||
|
@ -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<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";
|
||||
CManifest manifest(path + "/manifest");
|
||||
const auto PARSERESULT = manifest.parse();
|
||||
|
||||
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}; }
|
||||
if (PARSERESULT.has_value())
|
||||
return "couldn't parse manifest: " + *PARSERESULT;
|
||||
|
||||
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 + "/";
|
||||
|
||||
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))
|
||||
|
@ -158,28 +97,21 @@ static std::optional<std::string> createCursorThemeFromPath(const std::string& p
|
|||
|
||||
// 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;
|
||||
CMeta meta{METAPATH, true, true};
|
||||
const auto PARSERESULT2 = meta.parse();
|
||||
|
||||
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 (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<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";
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
|
55
libhyprcursor/VarList.cpp
Normal file
55
libhyprcursor/VarList.cpp
Normal 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
63
libhyprcursor/VarList.hpp
Normal 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;
|
||||
};
|
|
@ -3,12 +3,13 @@
|
|||
#include "internalDefines.hpp"
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <hyprlang.hpp>
|
||||
#include <zip.h>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <librsvg/rsvg.h>
|
||||
|
||||
#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<Hyprlang::CConfig> manifest;
|
||||
try {
|
||||
manifest = std::make_unique<Hyprlang::CConfig>(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<Hyprlang::STRING>(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<Hyprlang::CConfig> manifest;
|
||||
try {
|
||||
manifest = std::make_unique<Hyprlang::CConfig>(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<Hyprlang::STRING>(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<std::string> CHyprcursorImplementation::loadTheme() {
|
|||
if (!themeAccessible(themeFullDir))
|
||||
return "Theme inaccessible";
|
||||
|
||||
currentTheme = &theme;
|
||||
|
||||
// load manifest
|
||||
std::unique_ptr<Hyprlang::CConfig> manifest;
|
||||
try {
|
||||
// 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;
|
||||
}
|
||||
CManifest manifest(themeFullDir + "/manifest");
|
||||
const auto PARSERESULT = manifest.parse();
|
||||
|
||||
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;
|
||||
|
||||
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_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<std::string> CHyprcursorImplementation::loadTheme() {
|
|||
|
||||
buffer[readBytes] = '\0';
|
||||
|
||||
std::unique_ptr<Hyprlang::CConfig> meta;
|
||||
|
||||
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};
|
||||
}
|
||||
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<std::string> 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<std::string> CHyprcursorImplementation::loadTheme() {
|
|||
return "meta invalid: no images for shape " + cursor.path().stem().string();
|
||||
|
||||
SHAPE->directory = cursor.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);
|
||||
|
||||
zip_discard(zip);
|
||||
}
|
||||
|
|
75
libhyprcursor/manifest.cpp
Normal file
75
libhyprcursor/manifest.cpp
Normal 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;
|
||||
}
|
36
libhyprcursor/manifest.hpp
Normal file
36
libhyprcursor/manifest.hpp
Normal 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
174
libhyprcursor/meta.cpp
Normal 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
36
libhyprcursor/meta.hpp
Normal 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;
|
||||
};
|
|
@ -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";
|
||||
|
|
Loading…
Reference in a new issue