mirror of
https://github.com/hyprwm/hyprcursor.git
synced 2024-12-22 18:29: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)
|
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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 <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
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 "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);
|
||||||
}
|
}
|
||||||
|
|
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) {
|
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";
|
||||||
|
|
Loading…
Reference in a new issue