diff --git a/hyprcursor-util/src/main.cpp b/hyprcursor-util/src/main.cpp index 56ba21c..4be0754 100644 --- a/hyprcursor-util/src/main.cpp +++ b/hyprcursor-util/src/main.cpp @@ -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; @@ -329,7 +329,7 @@ static std::optional 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 += diff --git a/include/hyprcursor/hyprcursor.h b/include/hyprcursor/hyprcursor.h index dc894fa..b82ea45 100644 --- a/include/hyprcursor/hyprcursor.h +++ b/include/hyprcursor/hyprcursor.h @@ -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 \ No newline at end of file diff --git a/include/hyprcursor/hyprcursor.hpp b/include/hyprcursor/hyprcursor.hpp index 6c38538..6a8df94 100644 --- a/include/hyprcursor/hyprcursor.hpp +++ b/include/hyprcursor/hyprcursor.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "shared.h" @@ -28,6 +29,23 @@ namespace Hyprcursor { std::vector images; }; + /*! + C++ structs for hyprcursor_cursor_raw_shape_image and hyprcursor_cursor_raw_shape_data + */ + struct SCursorRawShapeImage { + std::vector data; + int size = 0; + int delay = 200; + }; + + struct SCursorRawShapeData { + std::vector images; + float hotspotX = 0; + float hotspotY = 0; + std::string overridenBy = ""; + eHyprcursorResizeAlgo resizeAlgo = HC_RESIZE_NONE; + }; + /*! Basic Hyprcursor manager. @@ -93,11 +111,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}; + + 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*)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 */ diff --git a/include/hyprcursor/shared.h b/include/hyprcursor/shared.h index 6e07a05..215d20d 100644 --- a/include/hyprcursor/shared.h +++ b/include/hyprcursor/shared.h @@ -25,6 +25,38 @@ 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; +}; + +typedef struct SCursorRawShapeDataC hyprcursor_cursor_raw_shape_data; + /* msg is owned by the caller and will be freed afterwards. */ diff --git a/libhyprcursor/hyprcursor.cpp b/libhyprcursor/hyprcursor.cpp index df1cda4..593721b 100644 --- a/libhyprcursor/hyprcursor.cpp +++ b/libhyprcursor/hyprcursor.cpp @@ -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; } @@ -348,11 +348,58 @@ 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 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; + 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; @@ -415,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); @@ -426,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); @@ -487,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) { @@ -520,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; @@ -528,11 +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; - } - return CAIRO_STATUS_SUCCESS; } diff --git a/libhyprcursor/hyprcursor_c.cpp b/libhyprcursor/hyprcursor_c.cpp index b0c5499..764031d 100644 --- a/libhyprcursor/hyprcursor_c.cpp +++ b/libhyprcursor/hyprcursor_c.cpp @@ -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; +} \ No newline at end of file diff --git a/libhyprcursor/internalDefines.hpp b/libhyprcursor/internalDefines.hpp index 18fe5de..ef087e2 100644 --- a/libhyprcursor/internalDefines.hpp +++ b/libhyprcursor/internalDefines.hpp @@ -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 diff --git a/libhyprcursor/internalSharedTypes.hpp b/libhyprcursor/internalSharedTypes.hpp index c5ac601..3a032aa 100644 --- a/libhyprcursor/internalSharedTypes.hpp +++ b/libhyprcursor/internalSharedTypes.hpp @@ -2,13 +2,7 @@ #include #include #include - -enum eResizeAlgo { - RESIZE_INVALID = 0, - RESIZE_NONE, - RESIZE_BILINEAR, - RESIZE_NEAREST, -}; +#include 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 images; std::vector overrides; eShapeType shapeType = SHAPE_INVALID; diff --git a/tests/test.c b/tests/test.c index 6f17443..4d1fdb2 100644 --- a/tests/test.c +++ b/tests/test.c @@ -24,6 +24,21 @@ int main(int argc, char** argv) { 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"); diff --git a/tests/test.cpp b/tests/test.cpp index 0ba938c..694c0ab 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -18,6 +18,17 @@ int main(int argc, char** argv) { return 1; } + // test raw data + const auto RAWDATA = mgr.getRawShapeData("left_ptr"); + if (RAWDATA.images.empty()) { + std::cout << "failed querying left_ptr\n"; + return 1; + } + + std::cout << "left_ptr images: " << RAWDATA.images.size() << "\n"; + for (auto& i : RAWDATA.images) + std::cout << "left_ptr data size: " << i.data.size() << "\n"; + Hyprcursor::SCursorStyleInfo style{.size = 48}; // preload size 48 for testing if (!mgr.loadThemeStyle(style)) {