Skip to content

Commit

Permalink
lib: Added a raw data API (#23)
Browse files Browse the repository at this point in the history
* raw data API

* add missing type arg

* tests: better stuff
  • Loading branch information
vaxerski authored Apr 6, 2024
1 parent 981b661 commit 7561459
Show file tree
Hide file tree
Showing 12 changed files with 337 additions and 47 deletions.
15 changes: 10 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,17 @@ 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)
Expand Down
4 changes: 2 additions & 2 deletions hyprcursor-util/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ enum eOperation {
OPERATION_EXTRACT = 1,
};

eResizeAlgo explicitResizeAlgo = RESIZE_INVALID;
eHyprcursorResizeAlgo explicitResizeAlgo = HC_RESIZE_INVALID;

struct XCursorConfigEntry {
int size = 0, hotspotX = 0, hotspotY = 0, delay = 0;
Expand Down Expand Up @@ -329,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 +=
Expand Down
17 changes: 17 additions & 0 deletions include/hyprcursor/hyprcursor.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,21 @@ CAPI void hyprcursor_style_done(struct hyprcursor_manager_t* manager, struct hyp
*/
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
55 changes: 55 additions & 0 deletions include/hyprcursor/hyprcursor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <vector>
#include <stdlib.h>
#include <string>

#include "shared.h"

Expand All @@ -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.
Expand Down Expand Up @@ -93,11 +112,47 @@ 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
*/
Expand Down
33 changes: 33 additions & 0 deletions include/hyprcursor/shared.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,39 @@ enum eHyprcursorLogLevel {
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.
*/
Expand Down
66 changes: 56 additions & 10 deletions libhyprcursor/hyprcursor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ 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) {
if (shape->resizeAlgo != HC_RESIZE_NONE) {
Debug::log(HC_LOG_ERR, logFn, "getSurfaceFor didn't match a size?");
return nullptr;
}
Expand Down Expand Up @@ -348,11 +348,59 @@ SCursorImageData** CHyprcursorManager::getShapesC(int& outSize, const char* shap
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) {
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;
Expand Down Expand Up @@ -415,7 +463,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);
Expand All @@ -426,7 +474,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);
Expand Down Expand Up @@ -487,7 +535,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) {
Expand Down Expand Up @@ -520,6 +568,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;

Expand All @@ -528,11 +579,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;
}

return CAIRO_STATUS_SUCCESS;
}

Expand Down
16 changes: 16 additions & 0 deletions libhyprcursor/hyprcursor_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,19 @@ void hyprcursor_register_logging_function(struct hyprcursor_manager_t* manager,
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;
}
2 changes: 1 addition & 1 deletion libhyprcursor/internalDefines.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
26 changes: 10 additions & 16 deletions libhyprcursor/internalSharedTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,27 @@
#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,
SHAPE_PNG,
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";
}

Expand All @@ -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;
Expand Down
Loading

0 comments on commit 7561459

Please sign in to comment.