New upstream version 0.1.7
This commit is contained in:
parent
e1ad3198ee
commit
f6be58e58e
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
run: |
|
||||
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
|
||||
pacman --noconfirm --noprogressbar -Syyu
|
||||
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang cairo librsvg git libzip
|
||||
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang cairo librsvg git libzip tomlplusplus
|
||||
|
||||
- name: Install hyprlang
|
||||
run: |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
cmake_minimum_required(VERSION 3.19)
|
||||
|
||||
set(HYPRCURSOR_VERSION "0.1.5")
|
||||
set(HYPRCURSOR_VERSION "0.1.7")
|
||||
add_compile_definitions(HYPRCURSOR_VERSION="${HYPRCURSOR_VERSION}")
|
||||
|
||||
project(hyprcursor
|
||||
|
|
@ -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")
|
||||
|
|
@ -54,23 +54,34 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|||
endif()
|
||||
|
||||
# hyprcursor-util
|
||||
add_subdirectory(hyprcursor-util)
|
||||
|
||||
install(TARGETS hyprcursor)
|
||||
file(GLOB_RECURSE UTILSRCFILES CONFIGURE_DEPENDS "hyprcursor-util/src/*.cpp" "include/hyprcursor/hyprcursor.hpp" "include/hyprcursor/hyprcursor.h" "include/hyprcursor/shared.h")
|
||||
add_executable(hyprcursor-util ${UTILSRCFILES})
|
||||
target_include_directories(hyprcursor-util
|
||||
PUBLIC "./include"
|
||||
PRIVATE "./libhyprcursor" "./hyprcursor-util/src"
|
||||
)
|
||||
target_link_libraries(hyprcursor-util PkgConfig::deps hyprcursor)
|
||||
|
||||
# tests
|
||||
add_custom_target(tests)
|
||||
|
||||
add_executable(hyprcursor_test "tests/test.cpp")
|
||||
target_link_libraries(hyprcursor_test PRIVATE hyprcursor)
|
||||
add_test(NAME "Test libhyprcursor in C++" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test)
|
||||
add_dependencies(tests hyprcursor_test)
|
||||
add_executable(hyprcursor_test1 "tests/full_rendering.cpp")
|
||||
target_link_libraries(hyprcursor_test1 PRIVATE hyprcursor)
|
||||
add_test(NAME "Test libhyprcursor in C++ (full rendering)" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test1)
|
||||
add_dependencies(tests hyprcursor_test1)
|
||||
|
||||
add_executable(hyprcursor_test_c "tests/test.c")
|
||||
add_executable(hyprcursor_test2 "tests/only_metadata.cpp")
|
||||
target_link_libraries(hyprcursor_test2 PRIVATE hyprcursor)
|
||||
add_test(NAME "Test libhyprcursor in C++ (only metadata)" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test2)
|
||||
add_dependencies(tests hyprcursor_test2)
|
||||
|
||||
add_executable(hyprcursor_test_c "tests/c_test.c")
|
||||
target_link_libraries(hyprcursor_test_c PRIVATE hyprcursor)
|
||||
add_test(NAME "Test libhyprcursor in C" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test_c)
|
||||
add_dependencies(tests hyprcursor_test_c)
|
||||
|
||||
# Installation
|
||||
install(TARGETS hyprcursor)
|
||||
install(TARGETS hyprcursor-util)
|
||||
install(DIRECTORY "include/hyprcursor" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(FILES ${CMAKE_BINARY_DIR}/hyprcursor.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ resize_algorithm = bilinear
|
|||
|
||||
# "hotspot" is where in your cursor the actual "click point" should be.
|
||||
# this is in absolute coordinates. x+ is east, y+ is north.
|
||||
# the pixel coordinates of the hotspot at size are rounded to the nearest:
|
||||
# (round(size * hotspot_x), round(size * hotspot_y))
|
||||
hotspot_x = 0.0 # this goes 0 - 1
|
||||
hotspot_y = 0.0 # this goes 0 - 1
|
||||
|
||||
|
|
@ -77,4 +79,26 @@ If you are using an svg cursor, the size parameter will be ignored.
|
|||
|
||||
Mixing png and svg cursor images in one shape will result in an error.
|
||||
|
||||
Please note animated svgs are not supported, you need to add a separate svg for every frame.
|
||||
All cursors are required to have an aspect ratio of 1:1.
|
||||
|
||||
Please note animated svgs are not supported, you need to add a separate svg for every frame.
|
||||
|
||||
### TOML
|
||||
|
||||
You are allowed to use TOML for all .hl files. Make sure to change the extension from `.hl` to `.toml`!
|
||||
|
||||
#### Manifest
|
||||
|
||||
Append `[General]` to the top, and wrap all the values in quotes.
|
||||
|
||||
#### Meta
|
||||
|
||||
Append `[General]` to the top, and wrap all values except hotspot in quotes.
|
||||
|
||||
Additionally, if you have multiple `define_*` keys, merge them into one like this:
|
||||
```toml
|
||||
define_override = 'shape1;shape2;shape3'
|
||||
define_size = '24,image1.png,200;24,image2.png,200;32,image3.png,200'
|
||||
```
|
||||
|
||||
You can put spaces around the semicolons if you prefer to.
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
hyprlang = {
|
||||
url = "github:hyprwm/hyprlang";
|
||||
inputs.systems.follows = "systems";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.19)
|
||||
|
||||
project(
|
||||
hyprcursor-util
|
||||
DESCRIPTION "A utility for creating and converting hyprcursor themes"
|
||||
)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
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")
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
|
||||
add_executable(hyprcursor-util ${SRCFILES})
|
||||
|
||||
target_link_libraries(hyprcursor-util PkgConfig::deps)
|
||||
target_include_directories(hyprcursor-util
|
||||
PRIVATE
|
||||
.
|
||||
)
|
||||
|
||||
install(TARGETS hyprcursor-util)
|
||||
|
|
@ -1 +0,0 @@
|
|||
../libhyprcursor/internalSharedTypes.hpp
|
||||
|
|
@ -7,13 +7,15 @@
|
|||
#include <algorithm>
|
||||
#include <hyprlang.hpp>
|
||||
#include "internalSharedTypes.hpp"
|
||||
#include "manifest.hpp"
|
||||
#include "meta.hpp"
|
||||
|
||||
enum eOperation {
|
||||
OPERATION_CREATE = 0,
|
||||
OPERATION_EXTRACT = 1,
|
||||
};
|
||||
|
||||
eResizeAlgo explicitResizeAlgo = RESIZE_INVALID;
|
||||
eHyprcursorResizeAlgo explicitResizeAlgo = HC_RESIZE_INVALID;
|
||||
|
||||
struct XCursorConfigEntry {
|
||||
int size = 0, hotspotX = 0, hotspotY = 0, delay = 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";
|
||||
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 "(1) failed to add meta " + METADIR + " to hlc";
|
||||
if (zip_file_add(zip, (std::string{"meta."} + (METADIR.ends_with(".hl") ? "hl" : "toml")).c_str(), meta, ZIP_FL_ENC_UTF_8) < 0)
|
||||
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 {};
|
||||
}
|
||||
|
|
@ -396,7 +329,7 @@ static std::optional<std::string> extractXTheme(const std::string& xpath_, const
|
|||
}
|
||||
|
||||
// write a meta.hl
|
||||
std::string metaString = std::format("resize_algorithm = {}\n", explicitResizeAlgo == RESIZE_INVALID ? "none" : algoToString(explicitResizeAlgo));
|
||||
std::string metaString = std::format("resize_algorithm = {}\n", explicitResizeAlgo == HC_RESIZE_INVALID ? "none" : algoToString(explicitResizeAlgo));
|
||||
|
||||
// find hotspot from first entry
|
||||
metaString +=
|
||||
|
|
|
|||
|
|
@ -43,6 +43,13 @@ struct hyprcursor_cursor_style_info {
|
|||
*/
|
||||
CAPI struct hyprcursor_manager_t* hyprcursor_manager_create(const char* theme_name);
|
||||
|
||||
/*!
|
||||
\since 0.1.6
|
||||
|
||||
Same as hyprcursor_manager_create, but with a logger.
|
||||
*/
|
||||
CAPI struct hyprcursor_manager_t* hyprcursor_manager_create_with_logger(const char* theme_name, PHYPRCURSORLOGFUNC fn);
|
||||
|
||||
/*!
|
||||
Free a hyprcursor_manager_t*
|
||||
*/
|
||||
|
|
@ -71,7 +78,8 @@ CAPI int hyprcursor_load_theme_style(struct hyprcursor_manager_t* manager, struc
|
|||
|
||||
Once done with a size, call hyprcursor_style_done()
|
||||
*/
|
||||
CAPI hyprcursor_cursor_image_data** hyprcursor_get_cursor_image_data(struct hyprcursor_manager_t* manager, const char* shape, struct hyprcursor_cursor_style_info info, int* out_size);
|
||||
CAPI hyprcursor_cursor_image_data** hyprcursor_get_cursor_image_data(struct hyprcursor_manager_t* manager, const char* shape, struct hyprcursor_cursor_style_info info,
|
||||
int* out_size);
|
||||
|
||||
/*!
|
||||
Free a returned hyprcursor_cursor_image_data.
|
||||
|
|
@ -83,4 +91,32 @@ CAPI void hyprcursor_cursor_image_data_free(hyprcursor_cursor_image_data** data,
|
|||
*/
|
||||
CAPI void hyprcursor_style_done(struct hyprcursor_manager_t* manager, struct hyprcursor_cursor_style_info info);
|
||||
|
||||
/*!
|
||||
\since 0.1.6
|
||||
|
||||
Registers a logging function to a hyprcursor_manager_t*
|
||||
|
||||
PHYPRCURSORLOGFUNC's msg is owned by the caller and will be freed afterwards.
|
||||
|
||||
fn can be null to remove a logger.
|
||||
*/
|
||||
CAPI void hyprcursor_register_logging_function(struct hyprcursor_manager_t* manager, PHYPRCURSORLOGFUNC fn);
|
||||
|
||||
/*!
|
||||
\since 0.1.6
|
||||
|
||||
Returns the raw image data of a cursor shape, not rendered at all, alongside the metadata.
|
||||
|
||||
The object needs to be freed instantly after using, see hyprcursor_raw_shape_data_free()
|
||||
*/
|
||||
CAPI hyprcursor_cursor_raw_shape_data* hyprcursor_get_raw_shape_data(struct hyprcursor_manager_t* manager, char* shape);
|
||||
|
||||
/*!
|
||||
\since 0.1.6
|
||||
|
||||
See hyprcursor_get_raw_shape_data.
|
||||
Frees the returned object.
|
||||
*/
|
||||
CAPI void hyprcursor_raw_shape_data_free(hyprcursor_cursor_raw_shape_data* data);
|
||||
|
||||
#endif
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <vector>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
|
||||
#include "shared.h"
|
||||
|
||||
|
|
@ -28,6 +29,24 @@ namespace Hyprcursor {
|
|||
std::vector<SCursorImageData> images;
|
||||
};
|
||||
|
||||
/*!
|
||||
C++ structs for hyprcursor_cursor_raw_shape_image and hyprcursor_cursor_raw_shape_data
|
||||
*/
|
||||
struct SCursorRawShapeImage {
|
||||
std::vector<unsigned char> data;
|
||||
int size = 0;
|
||||
int delay = 200;
|
||||
};
|
||||
|
||||
struct SCursorRawShapeData {
|
||||
std::vector<SCursorRawShapeImage> images;
|
||||
float hotspotX = 0;
|
||||
float hotspotY = 0;
|
||||
std::string overridenBy = "";
|
||||
eHyprcursorResizeAlgo resizeAlgo = HC_RESIZE_NONE;
|
||||
eHyprcursorDataType type = HC_DATA_PNG;
|
||||
};
|
||||
|
||||
/*!
|
||||
Basic Hyprcursor manager.
|
||||
|
||||
|
|
@ -43,6 +62,10 @@ namespace Hyprcursor {
|
|||
class CHyprcursorManager {
|
||||
public:
|
||||
CHyprcursorManager(const char* themeName);
|
||||
/*!
|
||||
\since 0.1.6
|
||||
*/
|
||||
CHyprcursorManager(const char* themeName, PHYPRCURSORLOGFUNC fn);
|
||||
~CHyprcursorManager();
|
||||
|
||||
/*!
|
||||
|
|
@ -89,19 +112,69 @@ namespace Hyprcursor {
|
|||
return data;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 0.1.6
|
||||
|
||||
Returns the raw image data of a cursor shape, not rendered at all, alongside the metadata.
|
||||
*/
|
||||
SCursorRawShapeData getRawShapeData(const char* shape_) {
|
||||
auto CDATA = getRawShapeDataC(shape_);
|
||||
|
||||
if (CDATA->overridenBy) {
|
||||
SCursorRawShapeData d{.overridenBy = CDATA->overridenBy};
|
||||
free(CDATA->overridenBy);
|
||||
delete CDATA;
|
||||
return d;
|
||||
}
|
||||
|
||||
SCursorRawShapeData data{.hotspotX = CDATA->hotspotX, .hotspotY = CDATA->hotspotY, .overridenBy = "", .resizeAlgo = CDATA->resizeAlgo, .type = CDATA->type};
|
||||
|
||||
for (size_t i = 0; i < CDATA->len; ++i) {
|
||||
SCursorRawShapeImageC* cimage = &CDATA->images[i];
|
||||
SCursorRawShapeImage& img = data.images.emplace_back();
|
||||
img.size = cimage->size;
|
||||
img.delay = cimage->delay;
|
||||
img.data = std::vector<unsigned char>{(unsigned char*)cimage->data, (unsigned char*)cimage->data + (std::size_t)cimage->len};
|
||||
}
|
||||
|
||||
delete[] CDATA->images;
|
||||
delete CDATA;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/*!
|
||||
Prefer getShape, this is for C compat.
|
||||
*/
|
||||
SCursorImageData** getShapesC(int& outSize, const char* shape_, const SCursorStyleInfo& info);
|
||||
|
||||
/*!
|
||||
Prefer getShapeData, this is for C compat.
|
||||
*/
|
||||
SCursorRawShapeDataC* getRawShapeDataC(const char* shape_);
|
||||
|
||||
/*!
|
||||
Marks a certain style as done, allowing it to be potentially freed
|
||||
*/
|
||||
void cursorSurfaceStyleDone(const SCursorStyleInfo&);
|
||||
|
||||
/*!
|
||||
\since 0.1.6
|
||||
|
||||
Registers a logging function to this manager.
|
||||
PHYPRCURSORLOGFUNC's msg is owned by the caller and will be freed afterwards.
|
||||
fn can be null to unregister a logger.
|
||||
*/
|
||||
void registerLoggingFunction(PHYPRCURSORLOGFUNC fn);
|
||||
|
||||
private:
|
||||
void init(const char* themeName_);
|
||||
|
||||
CHyprcursorImplementation* impl = nullptr;
|
||||
bool finalizedAndValid = false;
|
||||
PHYPRCURSORLOGFUNC logFn = nullptr;
|
||||
|
||||
friend class CHyprcursorImplementation;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -16,4 +16,51 @@ struct SCursorImageData {
|
|||
|
||||
typedef struct SCursorImageData hyprcursor_cursor_image_data;
|
||||
|
||||
enum eHyprcursorLogLevel {
|
||||
HC_LOG_NONE = 0,
|
||||
HC_LOG_TRACE,
|
||||
HC_LOG_INFO,
|
||||
HC_LOG_WARN,
|
||||
HC_LOG_ERR,
|
||||
HC_LOG_CRITICAL,
|
||||
};
|
||||
|
||||
enum eHyprcursorDataType {
|
||||
HC_DATA_PNG = 0,
|
||||
HC_DATA_SVG,
|
||||
};
|
||||
|
||||
enum eHyprcursorResizeAlgo {
|
||||
HC_RESIZE_INVALID = 0,
|
||||
HC_RESIZE_NONE,
|
||||
HC_RESIZE_BILINEAR,
|
||||
HC_RESIZE_NEAREST,
|
||||
};
|
||||
|
||||
struct SCursorRawShapeImageC {
|
||||
void* data;
|
||||
unsigned long int len;
|
||||
int size;
|
||||
int delay;
|
||||
};
|
||||
|
||||
typedef struct SCursorRawShapeImageC hyprcursor_cursor_raw_shape_image;
|
||||
|
||||
struct SCursorRawShapeDataC {
|
||||
struct SCursorRawShapeImageC* images;
|
||||
unsigned long int len;
|
||||
float hotspotX;
|
||||
float hotspotY;
|
||||
char* overridenBy;
|
||||
enum eHyprcursorResizeAlgo resizeAlgo;
|
||||
enum eHyprcursorDataType type;
|
||||
};
|
||||
|
||||
typedef struct SCursorRawShapeDataC hyprcursor_cursor_raw_shape_data;
|
||||
|
||||
/*
|
||||
msg is owned by the caller and will be freed afterwards.
|
||||
*/
|
||||
typedef void (*PHYPRCURSORLOGFUNC)(enum eHyprcursorLogLevel level, char* msg);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,53 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
enum eLogLevel {
|
||||
TRACE = 0,
|
||||
INFO,
|
||||
LOG,
|
||||
WARN,
|
||||
ERR,
|
||||
CRIT,
|
||||
NONE
|
||||
};
|
||||
|
||||
#include <string>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
|
||||
#include <hyprcursor/shared.h>
|
||||
|
||||
namespace Debug {
|
||||
inline bool quiet = false;
|
||||
inline bool verbose = false;
|
||||
|
||||
template <typename... Args>
|
||||
void log(eLogLevel level, const std::string& fmt, Args&&... args) {
|
||||
|
||||
#ifndef HYPRLAND_DEBUG
|
||||
// don't log in release
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (!verbose && level == TRACE)
|
||||
void log(eHyprcursorLogLevel level, PHYPRCURSORLOGFUNC fn, const std::string& fmt, Args&&... args) {
|
||||
if (!fn)
|
||||
return;
|
||||
|
||||
if (quiet)
|
||||
return;
|
||||
const std::string LOG = std::vformat(fmt, std::make_format_args(args...));
|
||||
|
||||
if (level != NONE) {
|
||||
std::cout << '[';
|
||||
|
||||
switch (level) {
|
||||
case TRACE: std::cout << "TRACE"; break;
|
||||
case INFO: std::cout << "INFO"; break;
|
||||
case LOG: std::cout << "LOG"; break;
|
||||
case WARN: std::cout << "WARN"; break;
|
||||
case ERR: std::cout << "ERR"; break;
|
||||
case CRIT: std::cout << "CRITICAL"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
std::cout << "] ";
|
||||
}
|
||||
|
||||
std::cout << std::vformat(fmt, std::make_format_args(args...)) << "\n";
|
||||
fn(level, (char*)LOG.c_str());
|
||||
}
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,14 @@
|
|||
#include "internalDefines.hpp"
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <hyprlang.hpp>
|
||||
#include <zip.h>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <librsvg/rsvg.h>
|
||||
|
||||
#include "manifest.hpp"
|
||||
#include "meta.hpp"
|
||||
#include "Log.hpp"
|
||||
|
||||
using namespace Hyprcursor;
|
||||
|
|
@ -18,17 +20,19 @@ constexpr const std::array<const char*, 1> systemThemeDirs = {"/usr/share/icons"
|
|||
constexpr const std::array<const char*, 2> userThemeDirs = {"/.local/share/icons", "/.icons"};
|
||||
|
||||
//
|
||||
static std::string themeNameFromEnv() {
|
||||
static std::string themeNameFromEnv(PHYPRCURSORLOGFUNC logfn) {
|
||||
const auto ENV = getenv("HYPRCURSOR_THEME");
|
||||
if (!ENV)
|
||||
if (!ENV) {
|
||||
Debug::log(HC_LOG_INFO, logfn, "themeNameFromEnv: env unset");
|
||||
return "";
|
||||
}
|
||||
|
||||
return std::string{ENV};
|
||||
}
|
||||
|
||||
static bool themeAccessible(const std::string& path) {
|
||||
static bool pathAccessible(const std::string& path) {
|
||||
try {
|
||||
if (!std::filesystem::exists(path + "/manifest.hl"))
|
||||
if (!std::filesystem::exists(path))
|
||||
return false;
|
||||
|
||||
} catch (std::exception& e) { return false; }
|
||||
|
|
@ -36,7 +40,11 @@ static bool themeAccessible(const std::string& path) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static std::string getFirstTheme() {
|
||||
static bool themeAccessible(const std::string& path) {
|
||||
return pathAccessible(path + "/manifest.hl") || pathAccessible(path + "/manifest.toml");
|
||||
}
|
||||
|
||||
static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) {
|
||||
// try user directories first
|
||||
|
||||
const auto HOMEENV = getenv("HOME");
|
||||
|
|
@ -47,42 +55,60 @@ static std::string getFirstTheme() {
|
|||
|
||||
for (auto& dir : userThemeDirs) {
|
||||
const auto FULLPATH = HOME + dir;
|
||||
if (!std::filesystem::exists(FULLPATH))
|
||||
if (!pathAccessible(FULLPATH)) {
|
||||
Debug::log(HC_LOG_TRACE, logfn, "Skipping path {} because it's inaccessible.", FULLPATH);
|
||||
continue;
|
||||
}
|
||||
|
||||
// loop over dirs and see if any has a manifest.hl
|
||||
for (auto& themeDir : std::filesystem::directory_iterator(FULLPATH)) {
|
||||
if (!themeDir.is_directory())
|
||||
continue;
|
||||
|
||||
const auto MANIFESTPATH = themeDir.path().string() + "/manifest.hl";
|
||||
if (!themeAccessible(themeDir.path().string())) {
|
||||
Debug::log(HC_LOG_TRACE, logfn, "Skipping theme {} because it's inaccessible.", themeDir.path().string());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(MANIFESTPATH))
|
||||
const auto MANIFESTPATH = themeDir.path().string() + "/manifest.";
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& dir : systemThemeDirs) {
|
||||
const auto FULLPATH = dir;
|
||||
if (!std::filesystem::exists(FULLPATH))
|
||||
if (!pathAccessible(FULLPATH)) {
|
||||
Debug::log(HC_LOG_TRACE, logfn, "Skipping path {} because it's inaccessible.", FULLPATH);
|
||||
continue;
|
||||
}
|
||||
|
||||
// loop over dirs and see if any has a manifest.hl
|
||||
for (auto& themeDir : std::filesystem::directory_iterator(FULLPATH)) {
|
||||
if (!themeDir.is_directory())
|
||||
continue;
|
||||
|
||||
const auto MANIFESTPATH = themeDir.path().string() + "/manifest.hl";
|
||||
if (!themeAccessible(themeDir.path().string())) {
|
||||
Debug::log(HC_LOG_TRACE, logfn, "Skipping theme {} because it's inaccessible.", themeDir.path().string());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(MANIFESTPATH))
|
||||
const auto MANIFESTPATH = themeDir.path().string() + "/manifest.";
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static std::string getFullPathForThemeName(const std::string& name) {
|
||||
static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORLOGFUNC logfn) {
|
||||
const auto HOMEENV = getenv("HOME");
|
||||
if (!HOMEENV)
|
||||
return "";
|
||||
|
|
@ -91,103 +117,134 @@ static std::string getFullPathForThemeName(const std::string& name) {
|
|||
|
||||
for (auto& dir : userThemeDirs) {
|
||||
const auto FULLPATH = HOME + dir;
|
||||
if (!std::filesystem::exists(FULLPATH))
|
||||
if (!pathAccessible(FULLPATH)) {
|
||||
Debug::log(HC_LOG_TRACE, logfn, "Skipping path {} because it's inaccessible.", FULLPATH);
|
||||
continue;
|
||||
}
|
||||
|
||||
// loop over dirs and see if any has a manifest.hl
|
||||
for (auto& themeDir : std::filesystem::directory_iterator(FULLPATH)) {
|
||||
if (!themeDir.is_directory())
|
||||
continue;
|
||||
|
||||
const auto MANIFESTPATH = themeDir.path().string() + "/manifest.hl";
|
||||
|
||||
if (name.empty()) {
|
||||
if (std::filesystem::exists(MANIFESTPATH))
|
||||
return std::filesystem::canonical(themeDir.path()).string();
|
||||
if (!themeAccessible(themeDir.path().string())) {
|
||||
Debug::log(HC_LOG_TRACE, logfn, "Skipping theme {} because it's inaccessible.", themeDir.path().string());
|
||||
continue;
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
const std::string NAME = std::any_cast<Hyprlang::STRING>(manifest->getConfigValue("name"));
|
||||
|
||||
if (NAME != name)
|
||||
const auto MANIFESTPATH = themeDir.path().string() + "/manifest";
|
||||
|
||||
if (name.empty()) {
|
||||
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();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
CManifest manifest{MANIFESTPATH};
|
||||
if (const auto R = manifest.parse(); R.has_value()) {
|
||||
Debug::log(HC_LOG_ERR, logfn, "failed parsing Manifest of {}: {}", themeDir.path().string(), *R);
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string NAME = manifest.parsedData.name;
|
||||
|
||||
if (NAME != name && name != themeDir.path().stem().string())
|
||||
continue;
|
||||
|
||||
Debug::log(HC_LOG_INFO, logfn, "getFullPathForThemeName: found {}", themeDir.path().string());
|
||||
return std::filesystem::canonical(themeDir.path()).string();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& dir : systemThemeDirs) {
|
||||
const auto FULLPATH = dir;
|
||||
if (!std::filesystem::exists(FULLPATH))
|
||||
if (!pathAccessible(FULLPATH)) {
|
||||
Debug::log(HC_LOG_TRACE, logfn, "Skipping path {} because it's inaccessible.", FULLPATH);
|
||||
continue;
|
||||
}
|
||||
|
||||
// loop over dirs and see if any has a manifest.hl
|
||||
for (auto& themeDir : std::filesystem::directory_iterator(FULLPATH)) {
|
||||
if (!themeDir.is_directory())
|
||||
continue;
|
||||
|
||||
if (!name.empty() && themeDir.path().stem().string() != name)
|
||||
if (!themeAccessible(themeDir.path().string())) {
|
||||
Debug::log(HC_LOG_TRACE, logfn, "Skipping theme {} because it's inaccessible.", themeDir.path().string());
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto MANIFESTPATH = themeDir.path().string() + "/manifest";
|
||||
|
||||
CManifest manifest{MANIFESTPATH};
|
||||
if (const auto R = manifest.parse(); R.has_value()) {
|
||||
Debug::log(HC_LOG_ERR, logfn, "failed parsing Manifest of {}: {}", themeDir.path().string(), *R);
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string NAME = manifest.parsedData.name;
|
||||
|
||||
if (NAME != name && name != themeDir.path().stem().string())
|
||||
continue;
|
||||
|
||||
if (!themeAccessible(themeDir.path().string()))
|
||||
continue;
|
||||
|
||||
const auto MANIFESTPATH = themeDir.path().string() + "/manifest.hl";
|
||||
|
||||
if (std::filesystem::exists(MANIFESTPATH))
|
||||
return std::filesystem::canonical(themeDir.path()).string();
|
||||
Debug::log(HC_LOG_INFO, logfn, "getFullPathForThemeName: found {}", themeDir.path().string());
|
||||
return std::filesystem::canonical(themeDir.path()).string();
|
||||
}
|
||||
}
|
||||
|
||||
if (!name.empty()) // try without name
|
||||
return getFullPathForThemeName("");
|
||||
if (!name.empty()) { // try without name
|
||||
Debug::log(HC_LOG_INFO, logfn, "getFullPathForThemeName: failed, trying without name of {}", name);
|
||||
return getFullPathForThemeName("", logfn);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
CHyprcursorManager::CHyprcursorManager(const char* themeName_) {
|
||||
init(themeName_);
|
||||
}
|
||||
|
||||
CHyprcursorManager::CHyprcursorManager(const char* themeName_, PHYPRCURSORLOGFUNC fn) {
|
||||
logFn = fn;
|
||||
init(themeName_);
|
||||
}
|
||||
|
||||
void CHyprcursorManager::init(const char* themeName_) {
|
||||
std::string themeName = themeName_ ? themeName_ : "";
|
||||
|
||||
if (themeName.empty()) {
|
||||
// try reading from env
|
||||
themeName = themeNameFromEnv();
|
||||
Debug::log(HC_LOG_INFO, logFn, "CHyprcursorManager: attempting to find theme from env");
|
||||
themeName = themeNameFromEnv(logFn);
|
||||
}
|
||||
|
||||
if (themeName.empty()) {
|
||||
// try finding first, in the hierarchy
|
||||
themeName = getFirstTheme();
|
||||
Debug::log(HC_LOG_INFO, logFn, "CHyprcursorManager: attempting to find any theme");
|
||||
themeName = getFirstTheme(logFn);
|
||||
}
|
||||
|
||||
if (themeName.empty()) {
|
||||
// holy shit we're done
|
||||
Debug::log(HC_LOG_INFO, logFn, "CHyprcursorManager: no themes matched");
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize theme
|
||||
impl = new CHyprcursorImplementation;
|
||||
impl = new CHyprcursorImplementation(this, logFn);
|
||||
impl->themeName = themeName;
|
||||
impl->themeFullDir = getFullPathForThemeName(themeName);
|
||||
impl->themeFullDir = getFullPathForThemeName(themeName, logFn);
|
||||
|
||||
if (impl->themeFullDir.empty())
|
||||
return;
|
||||
|
||||
Debug::log(LOG, "Found theme {} at {}\n", impl->themeName, impl->themeFullDir);
|
||||
Debug::log(HC_LOG_INFO, logFn, "Found theme {} at {}\n", impl->themeName, impl->themeFullDir);
|
||||
|
||||
const auto LOADSTATUS = impl->loadTheme();
|
||||
|
||||
if (LOADSTATUS.has_value()) {
|
||||
Debug::log(ERR, "Theme failed to load with {}\n", LOADSTATUS.value());
|
||||
Debug::log(HC_LOG_ERR, logFn, "Theme failed to load with {}\n", LOADSTATUS.value());
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -204,6 +261,11 @@ bool CHyprcursorManager::valid() {
|
|||
}
|
||||
|
||||
SCursorImageData** CHyprcursorManager::getShapesC(int& outSize, const char* shape_, const SCursorStyleInfo& info) {
|
||||
if (!shape_) {
|
||||
Debug::log(HC_LOG_ERR, logFn, "getShapesC: shape of nullptr is invalid");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string REQUESTEDSHAPE = shape_;
|
||||
|
||||
std::vector<SLoadedCursorImage*> resultingImages;
|
||||
|
|
@ -231,8 +293,8 @@ SCursorImageData** CHyprcursorManager::getShapesC(int& outSize, const char* shap
|
|||
break;
|
||||
|
||||
// if we get here, means loadThemeStyle wasn't called most likely. If resize algo is specified, this is an error.
|
||||
if (shape->resizeAlgo != RESIZE_NONE) {
|
||||
Debug::log(ERR, "getSurfaceFor didn't match a size?");
|
||||
if (shape->resizeAlgo != HC_RESIZE_NONE) {
|
||||
Debug::log(HC_LOG_ERR, logFn, "getSurfaceFor didn't match a size?");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -246,7 +308,7 @@ SCursorImageData** CHyprcursorManager::getShapesC(int& outSize, const char* shap
|
|||
}
|
||||
|
||||
if (leader == 13371337) { // ???
|
||||
Debug::log(ERR, "getSurfaceFor didn't match any nearest size?");
|
||||
Debug::log(HC_LOG_ERR, logFn, "getSurfaceFor didn't match any nearest size?");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -263,7 +325,7 @@ SCursorImageData** CHyprcursorManager::getShapesC(int& outSize, const char* shap
|
|||
if (foundAny)
|
||||
break;
|
||||
|
||||
Debug::log(ERR, "getSurfaceFor didn't match any nearest size (2)?");
|
||||
Debug::log(HC_LOG_ERR, logFn, "getSurfaceFor didn't match any nearest size (2)?");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -274,19 +336,74 @@ SCursorImageData** CHyprcursorManager::getShapesC(int& outSize, const char* shap
|
|||
data[i]->delay = resultingImages[i]->delay;
|
||||
data[i]->size = resultingImages[i]->side;
|
||||
data[i]->surface = resultingImages[i]->cairoSurface;
|
||||
data[i]->hotspotX = hotX * data[i]->size;
|
||||
data[i]->hotspotY = hotY * data[i]->size;
|
||||
data[i]->hotspotX = std::round(hotX * (float)data[i]->size);
|
||||
data[i]->hotspotY = std::round(hotY * (float)data[i]->size);
|
||||
}
|
||||
|
||||
outSize = resultingImages.size();
|
||||
|
||||
Debug::log(HC_LOG_INFO, logFn, "getShapesC: found {} images for {}", outSize, shape_);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
SCursorRawShapeDataC* CHyprcursorManager::getRawShapeDataC(const char* shape_) {
|
||||
if (!shape_) {
|
||||
Debug::log(HC_LOG_ERR, logFn, "getShapeDataC: shape of nullptr is invalid");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::string SHAPE = shape_;
|
||||
|
||||
SCursorRawShapeDataC* data = new SCursorRawShapeDataC;
|
||||
std::vector<SLoadedCursorImage*> resultingImages;
|
||||
|
||||
for (auto& shape : impl->theme.shapes) {
|
||||
// if it's overridden just return the override
|
||||
if (const auto IT = std::find(shape->overrides.begin(), shape->overrides.end(), SHAPE); IT != shape->overrides.end()) {
|
||||
data->overridenBy = strdup(IT->c_str());
|
||||
return data;
|
||||
}
|
||||
|
||||
if (shape->directory != SHAPE)
|
||||
continue;
|
||||
|
||||
if (!impl->loadedShapes.contains(shape.get()))
|
||||
continue; // ??
|
||||
|
||||
// found it
|
||||
for (auto& i : impl->loadedShapes[shape.get()].images) {
|
||||
resultingImages.push_back(i.get());
|
||||
}
|
||||
|
||||
data->hotspotX = shape->hotspotX;
|
||||
data->hotspotY = shape->hotspotY;
|
||||
data->type = shape->shapeType == SHAPE_PNG ? HC_DATA_PNG : HC_DATA_SVG;
|
||||
break;
|
||||
}
|
||||
|
||||
data->len = resultingImages.size();
|
||||
data->images = new SCursorRawShapeImageC[data->len];
|
||||
|
||||
for (size_t i = 0; i < data->len; ++i) {
|
||||
data->images[i].data = resultingImages[i]->data;
|
||||
data->images[i].len = resultingImages[i]->dataLen;
|
||||
data->images[i].size = resultingImages[i]->side;
|
||||
data->images[i].delay = resultingImages[i]->delay;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
|
||||
Debug::log(HC_LOG_INFO, logFn, "loadThemeStyle: loading for size {}", info.size);
|
||||
|
||||
for (auto& shape : impl->theme.shapes) {
|
||||
if (shape->resizeAlgo == RESIZE_NONE && shape->shapeType != SHAPE_SVG)
|
||||
continue; // don't resample NONE style cursors
|
||||
if (shape->resizeAlgo == HC_RESIZE_NONE && shape->shapeType != SHAPE_SVG) {
|
||||
// don't resample NONE style cursors
|
||||
Debug::log(HC_LOG_TRACE, logFn, "loadThemeStyle: ignoring {}", shape->directory);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool sizeFound = false;
|
||||
|
||||
|
|
@ -327,12 +444,14 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
|
|||
}
|
||||
|
||||
if (!leader) {
|
||||
Debug::log(ERR, "Resampling failed to find a candidate???");
|
||||
Debug::log(HC_LOG_ERR, logFn, "Resampling failed to find a candidate???");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto FRAMES = impl->getFramesFor(shape.get(), leader->side);
|
||||
|
||||
Debug::log(HC_LOG_TRACE, logFn, "loadThemeStyle: png shape {} has {} frames", shape->directory, FRAMES.size());
|
||||
|
||||
for (auto& f : FRAMES) {
|
||||
auto& newImage = impl->loadedShapes[shape.get()].images.emplace_back(std::make_unique<SLoadedCursorImage>());
|
||||
newImage->artificial = true;
|
||||
|
|
@ -343,7 +462,7 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
|
|||
|
||||
const auto PCAIRO = cairo_create(newImage->cairoSurface);
|
||||
|
||||
cairo_set_antialias(PCAIRO, shape->resizeAlgo == RESIZE_BILINEAR ? CAIRO_ANTIALIAS_GOOD : CAIRO_ANTIALIAS_NONE);
|
||||
cairo_set_antialias(PCAIRO, shape->resizeAlgo == HC_RESIZE_BILINEAR ? CAIRO_ANTIALIAS_GOOD : CAIRO_ANTIALIAS_NONE);
|
||||
|
||||
cairo_save(PCAIRO);
|
||||
cairo_set_operator(PCAIRO, CAIRO_OPERATOR_CLEAR);
|
||||
|
|
@ -354,7 +473,7 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
|
|||
cairo_pattern_set_extend(PTN, CAIRO_EXTEND_NONE);
|
||||
const float scale = info.size / (float)f->side;
|
||||
cairo_scale(PCAIRO, scale, scale);
|
||||
cairo_pattern_set_filter(PTN, shape->resizeAlgo == RESIZE_BILINEAR ? CAIRO_FILTER_GOOD : CAIRO_FILTER_NEAREST);
|
||||
cairo_pattern_set_filter(PTN, shape->resizeAlgo == HC_RESIZE_BILINEAR ? CAIRO_FILTER_GOOD : CAIRO_FILTER_NEAREST);
|
||||
cairo_set_source(PCAIRO, PTN);
|
||||
|
||||
cairo_rectangle(PCAIRO, 0, 0, info.size, info.size);
|
||||
|
|
@ -368,6 +487,8 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
|
|||
} else if (shape->shapeType == SHAPE_SVG) {
|
||||
const auto FRAMES = impl->getFramesFor(shape.get(), 0);
|
||||
|
||||
Debug::log(HC_LOG_TRACE, logFn, "loadThemeStyle: svg shape {} has {} frames", shape->directory, FRAMES.size());
|
||||
|
||||
for (auto& f : FRAMES) {
|
||||
auto& newImage = impl->loadedShapes[shape.get()].images.emplace_back(std::make_unique<SLoadedCursorImage>());
|
||||
newImage->artificial = true;
|
||||
|
|
@ -387,14 +508,14 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
|
|||
RsvgHandle* handle = rsvg_handle_new_from_data((unsigned char*)f->data, f->dataLen, &error);
|
||||
|
||||
if (!handle) {
|
||||
Debug::log(ERR, "Failed reading svg: {}", error->message);
|
||||
Debug::log(HC_LOG_ERR, logFn, "Failed reading svg: {}", error->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
RsvgRectangle rect = {0, 0, (double)info.size, (double)info.size};
|
||||
|
||||
if (!rsvg_handle_render_document(handle, PCAIRO, &rect, &error)) {
|
||||
Debug::log(ERR, "Failed rendering svg: {}", error->message);
|
||||
Debug::log(HC_LOG_ERR, logFn, "Failed rendering svg: {}", error->message);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -403,7 +524,7 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
|
|||
cairo_destroy(PCAIRO);
|
||||
}
|
||||
} else {
|
||||
Debug::log(ERR, "Invalid shapetype in loadThemeStyle");
|
||||
Debug::log(HC_LOG_ERR, logFn, "Invalid shapetype in loadThemeStyle");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -413,7 +534,7 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
|
|||
|
||||
void CHyprcursorManager::cursorSurfaceStyleDone(const SCursorStyleInfo& info) {
|
||||
for (auto& shape : impl->theme.shapes) {
|
||||
if (shape->resizeAlgo == RESIZE_NONE && shape->shapeType != SHAPE_SVG)
|
||||
if (shape->resizeAlgo == HC_RESIZE_NONE && shape->shapeType != SHAPE_SVG)
|
||||
continue;
|
||||
|
||||
std::erase_if(impl->loadedShapes[shape.get()].images, [info, &shape](const auto& e) {
|
||||
|
|
@ -433,86 +554,8 @@ void CHyprcursorManager::cursorSurfaceStyleDone(const SCursorStyleInfo& info) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
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;
|
||||
void CHyprcursorManager::registerLoggingFunction(PHYPRCURSORLOGFUNC fn) {
|
||||
logFn = fn;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -524,6 +567,9 @@ PNG reading
|
|||
static cairo_status_t readPNG(void* data, unsigned char* output, unsigned int len) {
|
||||
const auto DATA = (SLoadedCursorImage*)data;
|
||||
|
||||
if (DATA->readNeedle >= DATA->dataLen)
|
||||
return CAIRO_STATUS_READ_ERROR;
|
||||
|
||||
if (!DATA->data)
|
||||
return CAIRO_STATUS_READ_ERROR;
|
||||
|
||||
|
|
@ -532,12 +578,6 @@ static cairo_status_t readPNG(void* data, unsigned char* output, unsigned int le
|
|||
std::memcpy(output, (uint8_t*)DATA->data + DATA->readNeedle, toRead);
|
||||
DATA->readNeedle += toRead;
|
||||
|
||||
if (DATA->readNeedle >= DATA->dataLen) {
|
||||
delete[] (char*)DATA->data;
|
||||
DATA->data = nullptr;
|
||||
Debug::log(TRACE, "cairo: png read, freeing mem");
|
||||
}
|
||||
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
@ -552,30 +592,24 @@ 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(ERR, "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))
|
||||
return "loadTheme: cursors_directory missing or empty";
|
||||
|
||||
for (auto& cursor : std::filesystem::directory_iterator(CURSORDIR)) {
|
||||
if (!cursor.is_regular_file())
|
||||
if (!cursor.is_regular_file()) {
|
||||
Debug::log(HC_LOG_TRACE, logFn, "loadTheme: skipping {}", cursor.path().string());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& SHAPE = theme.shapes.emplace_back(std::make_unique<SCursorShape>());
|
||||
auto& LOADEDSHAPE = loadedShapes[SHAPE.get()];
|
||||
|
|
@ -585,8 +619,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 */
|
||||
|
||||
|
|
@ -601,21 +640,20 @@ 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) { 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});
|
||||
}
|
||||
|
||||
SHAPE->overrides = meta.parsedData.overrides;
|
||||
|
||||
for (auto& i : SHAPE->images) {
|
||||
if (SHAPE->shapeType == SHAPE_INVALID) {
|
||||
if (i.filename.ends_with(".svg"))
|
||||
|
|
@ -623,7 +661,7 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
|
|||
else if (i.filename.ends_with(".png"))
|
||||
SHAPE->shapeType = SHAPE_PNG;
|
||||
else {
|
||||
std::cout << "WARNING: image " << i.filename << " has no known extension, assuming png.\n";
|
||||
Debug::log(HC_LOG_WARN, logFn, "WARNING: image {} has no known extension, assuming png.", i.filename);
|
||||
SHAPE->shapeType = SHAPE_PNG;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -634,7 +672,7 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
|
|||
}
|
||||
|
||||
// load image
|
||||
Debug::log(TRACE, "Loading {} for shape {}", i.filename, cursor.path().stem().string());
|
||||
Debug::log(HC_LOG_TRACE, logFn, "Loading {} for shape {}", i.filename, cursor.path().stem().string());
|
||||
auto* IMAGE = LOADEDSHAPE.images.emplace_back(std::make_unique<SLoadedCursorImage>()).get();
|
||||
IMAGE->side = SHAPE->shapeType == SHAPE_SVG ? 0 : i.size;
|
||||
IMAGE->delay = i.delay;
|
||||
|
|
@ -651,19 +689,19 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
|
|||
|
||||
zip_fclose(image_file);
|
||||
|
||||
Debug::log(TRACE, "Cairo: set up surface read");
|
||||
Debug::log(HC_LOG_TRACE, logFn, "Cairo: set up surface read");
|
||||
|
||||
if (SHAPE->shapeType == SHAPE_PNG) {
|
||||
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
Debug::log(LOG, "Skipping cairo load for a svg surface");
|
||||
Debug::log(HC_LOG_TRACE, logFn, "Skipping cairo load for a svg surface");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -671,9 +709,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);
|
||||
}
|
||||
|
|
@ -695,4 +733,4 @@ std::vector<SLoadedCursorImage*> CHyprcursorImplementation::getFramesFor(SCursor
|
|||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ hyprcursor_manager_t* hyprcursor_manager_create(const char* theme_name) {
|
|||
return (hyprcursor_manager_t*)new CHyprcursorManager(theme_name);
|
||||
}
|
||||
|
||||
hyprcursor_manager_t* hyprcursor_manager_create_with_logger(const char* theme_name, PHYPRCURSORLOGFUNC fn) {
|
||||
return (hyprcursor_manager_t*)new CHyprcursorManager(theme_name, fn);
|
||||
}
|
||||
|
||||
void hyprcursor_manager_free(hyprcursor_manager_t* manager) {
|
||||
delete (CHyprcursorManager*)manager;
|
||||
}
|
||||
|
|
@ -46,4 +50,25 @@ void hyprcursor_style_done(hyprcursor_manager_t* manager, hyprcursor_cursor_styl
|
|||
SCursorStyleInfo info;
|
||||
info.size = info_.size;
|
||||
return MGR->cursorSurfaceStyleDone(info);
|
||||
}
|
||||
|
||||
void hyprcursor_register_logging_function(struct hyprcursor_manager_t* manager, PHYPRCURSORLOGFUNC fn) {
|
||||
const auto MGR = (CHyprcursorManager*)manager;
|
||||
MGR->registerLoggingFunction(fn);
|
||||
}
|
||||
|
||||
CAPI hyprcursor_cursor_raw_shape_data* hyprcursor_get_raw_shape_data(struct hyprcursor_manager_t* manager, char* shape) {
|
||||
const auto MGR = (CHyprcursorManager*)manager;
|
||||
return MGR->getRawShapeDataC(shape);
|
||||
}
|
||||
|
||||
CAPI void hyprcursor_raw_shape_data_free(hyprcursor_cursor_raw_shape_data* data) {
|
||||
if (data->overridenBy) {
|
||||
free(data->overridenBy);
|
||||
delete data;
|
||||
return;
|
||||
}
|
||||
|
||||
delete[] data->images;
|
||||
delete data;
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ struct SLoadedCursorImage {
|
|||
|
||||
// read stuff
|
||||
size_t readNeedle = 0;
|
||||
void* data = nullptr;
|
||||
void* data = nullptr; // raw png / svg data, not image data
|
||||
size_t dataLen = 0;
|
||||
bool isSVG = false; // if true, data is just a string of chars
|
||||
|
||||
|
|
@ -37,10 +37,17 @@ struct SLoadedCursorShape {
|
|||
|
||||
class CHyprcursorImplementation {
|
||||
public:
|
||||
std::string themeName;
|
||||
std::string themeFullDir;
|
||||
CHyprcursorImplementation(Hyprcursor::CHyprcursorManager* mgr, PHYPRCURSORLOGFUNC fn) : owner(mgr), logFn(fn) {
|
||||
;
|
||||
}
|
||||
|
||||
SCursorTheme theme;
|
||||
Hyprcursor::CHyprcursorManager* owner = nullptr;
|
||||
PHYPRCURSORLOGFUNC logFn = nullptr;
|
||||
|
||||
std::string themeName;
|
||||
std::string themeFullDir;
|
||||
|
||||
SCursorTheme theme;
|
||||
|
||||
//
|
||||
std::unordered_map<SCursorShape*, SLoadedCursorShape> loadedShapes;
|
||||
|
|
|
|||
|
|
@ -2,13 +2,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
enum eResizeAlgo {
|
||||
RESIZE_INVALID = 0,
|
||||
RESIZE_NONE,
|
||||
RESIZE_BILINEAR,
|
||||
RESIZE_NEAREST,
|
||||
};
|
||||
#include <hyprcursor/shared.h>
|
||||
|
||||
enum eShapeType {
|
||||
SHAPE_INVALID = 0,
|
||||
|
|
@ -16,19 +10,19 @@ enum eShapeType {
|
|||
SHAPE_SVG,
|
||||
};
|
||||
|
||||
inline eResizeAlgo stringToAlgo(const std::string& s) {
|
||||
inline eHyprcursorResizeAlgo stringToAlgo(const std::string& s) {
|
||||
if (s == "none")
|
||||
return RESIZE_NONE;
|
||||
return HC_RESIZE_NONE;
|
||||
if (s == "nearest")
|
||||
return RESIZE_NEAREST;
|
||||
return RESIZE_BILINEAR;
|
||||
return HC_RESIZE_NEAREST;
|
||||
return HC_RESIZE_BILINEAR;
|
||||
}
|
||||
|
||||
inline std::string algoToString(const eResizeAlgo a) {
|
||||
inline std::string algoToString(const eHyprcursorResizeAlgo a) {
|
||||
switch (a) {
|
||||
case RESIZE_BILINEAR: return "bilinear";
|
||||
case RESIZE_NEAREST: return "nearest";
|
||||
case RESIZE_NONE: return "none";
|
||||
case HC_RESIZE_BILINEAR: return "bilinear";
|
||||
case HC_RESIZE_NEAREST: return "nearest";
|
||||
case HC_RESIZE_NONE: return "none";
|
||||
default: return "none";
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +38,7 @@ struct SCursorImage {
|
|||
struct SCursorShape {
|
||||
std::string directory;
|
||||
float hotspotX = 0, hotspotY = 0;
|
||||
eResizeAlgo resizeAlgo = RESIZE_NEAREST;
|
||||
eHyprcursorResizeAlgo resizeAlgo = HC_RESIZE_NEAREST;
|
||||
std::vector<SCursorImage> images;
|
||||
std::vector<std::string> overrides;
|
||||
eShapeType shapeType = SHAPE_INVALID;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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 {};
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
hyprlang,
|
||||
librsvg,
|
||||
libzip,
|
||||
tomlplusplus,
|
||||
version ? "git",
|
||||
}:
|
||||
stdenv.mkDerivation {
|
||||
|
|
@ -14,6 +15,11 @@ stdenv.mkDerivation {
|
|||
inherit version;
|
||||
src = ../.;
|
||||
|
||||
patches = [
|
||||
# adds /run/current-system/sw/share/icons to the icon lookup directories
|
||||
./dirs.patch
|
||||
];
|
||||
|
||||
nativeBuildInputs = [
|
||||
cmake
|
||||
pkg-config
|
||||
|
|
@ -24,6 +30,7 @@ stdenv.mkDerivation {
|
|||
hyprlang
|
||||
librsvg
|
||||
libzip
|
||||
tomlplusplus
|
||||
];
|
||||
|
||||
outputs = [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
diff --git a/libhyprcursor/hyprcursor.cpp b/libhyprcursor/hyprcursor.cpp
|
||||
index 304ab9f..1f7e95d 100644
|
||||
--- a/libhyprcursor/hyprcursor.cpp
|
||||
+++ b/libhyprcursor/hyprcursor.cpp
|
||||
@@ -14,7 +14,7 @@
|
||||
using namespace Hyprcursor;
|
||||
|
||||
// directories for lookup
|
||||
-constexpr const std::array<const char*, 1> systemThemeDirs = {"/usr/share/icons"};
|
||||
+constexpr const std::array<const char*, 2> systemThemeDirs = {"/usr/share/icons", "/run/current-system/sw/share/icons"};
|
||||
constexpr const std::array<const char*, 2> userThemeDirs = {"/.local/share/icons", "/.icons"};
|
||||
|
||||
//
|
||||
|
|
@ -1,19 +1,46 @@
|
|||
/*
|
||||
hyprlang-test in C.
|
||||
Renders a cursor shape to /tmp at 48px
|
||||
|
||||
For better explanations, see the cpp tests.
|
||||
*/
|
||||
|
||||
#include <hyprcursor/hyprcursor.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void logFunction(enum eHyprcursorLogLevel level, char* message) {
|
||||
printf("[hc] %s\n", message);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
struct hyprcursor_manager_t* mgr = hyprcursor_manager_create(NULL);
|
||||
struct hyprcursor_manager_t* mgr = hyprcursor_manager_create_with_logger(NULL, logFunction);
|
||||
|
||||
if (!mgr) {
|
||||
printf("mgr null\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!hyprcursor_manager_valid(mgr)) {
|
||||
printf("mgr is invalid\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
hyprcursor_cursor_raw_shape_data* shapeData = hyprcursor_get_raw_shape_data(mgr, "left_ptr");
|
||||
if (!shapeData || shapeData->len <= 0) {
|
||||
printf("failed querying left_ptr\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("left_ptr images: %d\n", shapeData->len);
|
||||
|
||||
for (size_t i = 0; i < shapeData->len; ++i) {
|
||||
printf("left_ptr image size: %d\n", shapeData->images[i].len);
|
||||
}
|
||||
|
||||
hyprcursor_raw_shape_data_free(shapeData);
|
||||
shapeData = NULL;
|
||||
|
||||
struct hyprcursor_cursor_style_info info = {.size = 48};
|
||||
if (!hyprcursor_load_theme_style(mgr, info)) {
|
||||
printf("load failed\n");
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
|
||||
/*
|
||||
full_rendering.cpp
|
||||
|
||||
This example shows probably what you want to do.
|
||||
Hyprcursor will render a left_ptr shape at 48x48px to a file called /tmp/arrow.png
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <hyprcursor/hyprcursor.hpp>
|
||||
|
||||
void logFunction(enum eHyprcursorLogLevel level, char* message) {
|
||||
std::cout << "[hc] " << message << "\n";
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
/*
|
||||
Create a manager. You can optionally pass a logger function.
|
||||
*/
|
||||
Hyprcursor::CHyprcursorManager mgr(nullptr, logFunction);
|
||||
|
||||
/*
|
||||
Manager could be invalid if no themes were found, or
|
||||
a specified theme was invalid.
|
||||
*/
|
||||
if (!mgr.valid()) {
|
||||
std::cout << "mgr is invalid\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Style describes what pixel size you want your cursor
|
||||
images to be.
|
||||
|
||||
Remember to free styles once you're done with them
|
||||
(e.g. the user requested to change the cursor size to something else)
|
||||
*/
|
||||
Hyprcursor::SCursorStyleInfo style{.size = 48};
|
||||
if (!mgr.loadThemeStyle(style)) {
|
||||
std::cout << "failed loading style\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Get a shape. This will return the data about available image(s),
|
||||
their delay, hotspot, etc.
|
||||
*/
|
||||
const auto SHAPEDATA = mgr.getShape("left_ptr", style);
|
||||
|
||||
/*
|
||||
If the size doesn't exist, images will be empty.
|
||||
*/
|
||||
if (SHAPEDATA.images.empty()) {
|
||||
std::cout << "no images\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "hyprcursor returned " << SHAPEDATA.images.size() << " images\n";
|
||||
|
||||
/*
|
||||
Save to disk with cairo
|
||||
*/
|
||||
const auto RET = cairo_surface_write_to_png(SHAPEDATA.images[0].surface, "/tmp/arrow.png");
|
||||
|
||||
std::cout << "Cairo returned for write: " << RET << "\n";
|
||||
|
||||
/*
|
||||
As mentioned before, clean up by releasing the style.
|
||||
*/
|
||||
mgr.cursorSurfaceStyleDone(style);
|
||||
|
||||
if (RET)
|
||||
return 1;
|
||||
|
||||
return !mgr.valid();
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
/*
|
||||
only_metadata.cpp
|
||||
|
||||
This is a mode in which you probably do NOT want to operate,
|
||||
but major DEs might want their own renderer for
|
||||
cursor shapes.
|
||||
|
||||
Prefer full_rendering.cpp for consistency and simplicity.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <hyprcursor/hyprcursor.hpp>
|
||||
|
||||
void logFunction(enum eHyprcursorLogLevel level, char* message) {
|
||||
std::cout << "[hc] " << message << "\n";
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
/*
|
||||
Create a manager. You can optionally pass a logger function.
|
||||
*/
|
||||
Hyprcursor::CHyprcursorManager mgr(nullptr, logFunction);
|
||||
|
||||
/*
|
||||
Manager could be invalid if no themes were found, or
|
||||
a specified theme was invalid.
|
||||
*/
|
||||
if (!mgr.valid()) {
|
||||
std::cout << "mgr is invalid\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
If you are planning on using your own renderer,
|
||||
you do not want to load in any styles, as those
|
||||
are rendered once you make your call.
|
||||
|
||||
Instead, let's request left_ptr's metadata
|
||||
*/
|
||||
auto RAWDATA = mgr.getRawShapeData("left_ptr");
|
||||
|
||||
/*
|
||||
if images are empty, check overridenBy
|
||||
*/
|
||||
if (RAWDATA.images.empty()) {
|
||||
|
||||
/*
|
||||
if overridenBy is empty, the current theme doesn't have this shape.
|
||||
*/
|
||||
if (RAWDATA.overridenBy.empty())
|
||||
return false;
|
||||
|
||||
/*
|
||||
load what it's overriden by.
|
||||
*/
|
||||
RAWDATA = mgr.getRawShapeData(RAWDATA.overridenBy.c_str());
|
||||
}
|
||||
|
||||
/*
|
||||
If we still have no images, the theme seems broken.
|
||||
*/
|
||||
if (RAWDATA.images.empty()) {
|
||||
std::cout << "failed querying left_ptr\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
You can query the images (animation frames)
|
||||
or their properties.
|
||||
|
||||
Every image has .data and .type for you to handle.
|
||||
*/
|
||||
std::cout << "left_ptr images: " << RAWDATA.images.size() << "\n";
|
||||
for (auto& i : RAWDATA.images)
|
||||
std::cout << "left_ptr data size: " << i.data.size() << "\n";
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
#include <iostream>
|
||||
#include <hyprcursor/hyprcursor.hpp>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
Hyprcursor::CHyprcursorManager mgr(nullptr);
|
||||
|
||||
if (!mgr.valid()) {
|
||||
std::cout << "mgr is invalid\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
Hyprcursor::SCursorStyleInfo style{.size = 48};
|
||||
// preload size 48 for testing
|
||||
if (!mgr.loadThemeStyle(style)) {
|
||||
std::cout << "failed loading style\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
// get cursor for left_ptr
|
||||
const auto SHAPEDATA = mgr.getShape("left_ptr", style);
|
||||
|
||||
if (SHAPEDATA.images.empty()) {
|
||||
std::cout << "no images\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "hyprcursor returned " << SHAPEDATA.images.size() << " images\n";
|
||||
|
||||
// save to disk
|
||||
const auto RET = cairo_surface_write_to_png(SHAPEDATA.images[0].surface, "/tmp/arrow.png");
|
||||
|
||||
std::cout << "Cairo returned for write: " << RET << "\n";
|
||||
|
||||
mgr.cursorSurfaceStyleDone(style);
|
||||
|
||||
if (RET)
|
||||
return 1;
|
||||
|
||||
return !mgr.valid();
|
||||
}
|
||||
Loading…
Reference in New Issue