Compare commits

..

No commits in common. "main" and "v0.1.6" have entirely different histories.
main ... v0.1.6

23 changed files with 243 additions and 563 deletions

View file

@ -1,101 +0,0 @@
WarningsAsErrors: '*'
HeaderFilterRegex: '.*\.hpp'
FormatStyle: file
Checks: >
-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-forward-declararion-namespace,
-bugprone-forward-declararion-namespace,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
-bugprone-branch-clone,
-bugprone-assignment-in-if-condition,
concurrency-*,
-concurrency-mt-unsafe,
cppcoreguidelines-*,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-explicit-virtual-functions,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-macro-to-enum,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-pro-type-reinterpret-cast,
google-global-names-in-headers,
-google-readability-casting,
google-runtime-operator,
misc-*,
-misc-unused-parameters,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-include-cleaner,
-misc-use-anonymous-namespace,
-misc-const-correctness,
modernize-*,
-modernize-return-braced-init-list,
-modernize-use-trailing-return-type,
-modernize-use-using,
-modernize-use-override,
-modernize-avoid-c-arrays,
-modernize-macro-to-enum,
-modernize-loop-convert,
-modernize-use-nodiscard,
-modernize-pass-by-value,
-modernize-use-auto,
performance-*,
-performance-avoid-endl,
-performance-unnecessary-value-param,
portability-std-allocator-const,
readability-*,
-readability-function-cognitive-complexity,
-readability-function-size,
-readability-identifier-length,
-readability-magic-numbers,
-readability-uppercase-literal-suffix,
-readability-braces-around-statements,
-readability-redundant-access-specifiers,
-readability-else-after-return,
-readability-container-data-pointer,
-readability-implicit-bool-conversion,
-readability-avoid-nested-conditional-operator,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-avoid-const-params-in-decls,
-readability-named-parameter,
-readability-convert-member-functions-to-static,
-readability-qualified-auto,
-readability-make-member-function-const,
-readability-isolate-declaration,
-readability-inconsistent-declaration-parameter-name,
-clang-diagnostic-error,
CheckOptions:
performance-for-range-copy.WarnOnAllAutoCopies: true
performance-inefficient-string-concatenation.StrictMode: true
readability-braces-around-statements.ShortStatementLines: 0
readability-identifier-naming.ClassCase: CamelCase
readability-identifier-naming.ClassIgnoredRegexp: I.*
readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!?
readability-identifier-naming.EnumCase: CamelCase
readability-identifier-naming.EnumPrefix: e
readability-identifier-naming.EnumConstantCase: UPPER_CASE
readability-identifier-naming.FunctionCase: camelBack
readability-identifier-naming.NamespaceCase: CamelCase
readability-identifier-naming.NamespacePrefix: N
readability-identifier-naming.StructPrefix: S
readability-identifier-naming.StructCase: CamelCase

View file

@ -18,10 +18,6 @@ jobs:
pacman --noconfirm --noprogressbar -Syyu pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang cairo librsvg git libzip tomlplusplus pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang cairo librsvg git libzip tomlplusplus
- name: Get hyprutils-git
run: |
git clone https://github.com/hyprwm/hyprutils && cd hyprutils && cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -B build && cmake --build build --target hyprutils && cmake --install build
- name: Install hyprlang - name: Install hyprlang
run: | run: |
git clone https://github.com/hyprwm/hyprlang --recursive git clone https://github.com/hyprwm/hyprlang --recursive

View file

@ -1,39 +0,0 @@
name: Test
on: [push, pull_request, workflow_dispatch]
jobs:
nix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
# not needed (yet)
# - uses: cachix/cachix-action@v12
# with:
# name: hyprland
# authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build
run: nix build .#hyprcursor-with-tests --print-build-logs --keep-going
# keep a fixed rev in case anything changes
- name: Install hyprcursor theme
run: nix build github:fufexan/dotfiles/4e05e373c1c70a2ae259b2c15eec2ad6e11ce581#bibata-hyprcursor --print-build-logs --keep-going
- name: Set up env
run: |
export HYPRCURSOR_THEME=Bibata-Modern-Classic-Hyprcursor
export HYPRCURSOR_SIZE=16
mkdir -p $HOME/.local/share/icons
ln -s $(realpath result/share/icons/Bibata-Modern-Classic-Hyprcursor) $HOME/.local/share/icons/
- name: Run test1
run: nix shell .#hyprcursor-with-tests -c hyprcursor_test1
- name: Run test2
run: nix shell .#hyprcursor-with-tests -c hyprcursor_test2
- name: Run test_c
run: nix shell .#hyprcursor-with-tests -c hyprcursor_test_c

1
.gitignore vendored
View file

@ -1,3 +1,2 @@
.vscode/ .vscode/
build/ build/
.cache/

View file

@ -1,14 +1,12 @@
cmake_minimum_required(VERSION 3.19) cmake_minimum_required(VERSION 3.19)
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) set(HYPRCURSOR_VERSION "0.1.6")
string(STRIP ${VER_RAW} HYPRCURSOR_VERSION)
add_compile_definitions(HYPRCURSOR_VERSION="${HYPRCURSOR_VERSION}") add_compile_definitions(HYPRCURSOR_VERSION="${HYPRCURSOR_VERSION}")
project( project(hyprcursor
hyprcursor VERSION ${HYPRCURSOR_VERSION}
VERSION ${HYPRCURSOR_VERSION} DESCRIPTION "A library and toolkit for the Hyprland cursor format"
DESCRIPTION "A library and toolkit for the Hyprland cursor format") )
include(CTest) include(CTest)
include(GNUInstallDirs) include(GNUInstallDirs)
@ -20,73 +18,48 @@ set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR})
configure_file(hyprcursor.pc.in hyprcursor.pc @ONLY) configure_file(hyprcursor.pc.in hyprcursor.pc @ONLY)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
add_compile_options(
-Wall
-Wextra
-Wpedantic
-Wno-unused-parameter
-Wno-unused-value
-Wno-missing-field-initializers
-Wno-narrowing
-Wno-pointer-arith)
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules( pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprlang>=0.4.2 libzip cairo librsvg-2.0 tomlplusplus)
deps
REQUIRED
IMPORTED_TARGET
hyprlang>=0.4.2
libzip
cairo
librsvg-2.0
tomlplusplus)
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Configuring hyprcursor in Debug") message(STATUS "Configuring hyprcursor in Debug")
add_compile_definitions(HYPRLAND_DEBUG) add_compile_definitions(HYPRLAND_DEBUG)
else() else()
add_compile_options(-O3) add_compile_options(-O3)
message(STATUS "Configuring hyprcursor in Release") message(STATUS "Configuring hyprcursor in Release")
endif() endif()
file( file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "libhyprcursor/*.cpp" "include/hyprcursor/hyprcursor.hpp" "include/hyprcursor/hyprcursor.h" "include/hyprcursor/shared.h")
GLOB_RECURSE
SRCFILES
CONFIGURE_DEPENDS
"libhyprcursor/*.cpp"
"include/hyprcursor/hyprcursor.hpp"
"include/hyprcursor/hyprcursor.h"
"include/hyprcursor/shared.h")
add_library(hyprcursor SHARED ${SRCFILES}) add_library(hyprcursor SHARED ${SRCFILES})
target_include_directories( target_include_directories( hyprcursor
hyprcursor PUBLIC "./include"
PUBLIC "./include" PRIVATE "./libhyprcursor"
PRIVATE "./libhyprcursor") )
set_target_properties( set_target_properties(hyprcursor PROPERTIES
hyprcursor VERSION ${hyprcursor_VERSION}
PROPERTIES VERSION ${hyprcursor_VERSION} SOVERSION 0
SOVERSION 0 PUBLIC_HEADER include/hyprcursor/hyprcursor.hpp include/hyprcursor/hyprcursor.h include/hyprcursor/shared.h
PUBLIC_HEADER include/hyprcursor/hyprcursor.hpp )
include/hyprcursor/hyprcursor.h include/hyprcursor/shared.h)
target_link_libraries(hyprcursor PkgConfig::deps) target_link_libraries(hyprcursor PkgConfig::deps)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# for std::expected.
# probably evil. Arch's clang is very outdated tho...
target_compile_options(hyprcursor PUBLIC
$<$<COMPILE_LANGUAGE:CXX>:-std=gnu++2b -D__cpp_concepts=202002L>
-Wno-builtin-macro-redefined)
endif()
# hyprcursor-util # hyprcursor-util
file( file(GLOB_RECURSE UTILSRCFILES CONFIGURE_DEPENDS "hyprcursor-util/src/*.cpp" "include/hyprcursor/hyprcursor.hpp" "include/hyprcursor/hyprcursor.h" "include/hyprcursor/shared.h")
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}) add_executable(hyprcursor-util ${UTILSRCFILES})
target_include_directories( target_include_directories(hyprcursor-util
hyprcursor-util PUBLIC "./include"
PUBLIC "./include" PRIVATE "./libhyprcursor" "./hyprcursor-util/src"
PRIVATE "./libhyprcursor" "./hyprcursor-util/src") )
target_link_libraries(hyprcursor-util PkgConfig::deps hyprcursor) target_link_libraries(hyprcursor-util PkgConfig::deps hyprcursor)
# tests # tests
@ -94,37 +67,21 @@ add_custom_target(tests)
add_executable(hyprcursor_test1 "tests/full_rendering.cpp") add_executable(hyprcursor_test1 "tests/full_rendering.cpp")
target_link_libraries(hyprcursor_test1 PRIVATE hyprcursor) target_link_libraries(hyprcursor_test1 PRIVATE hyprcursor)
add_test( add_test(NAME "Test libhyprcursor in C++ (full rendering)" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test1)
NAME "Test libhyprcursor in C++ (full rendering)"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprcursor_test1)
add_dependencies(tests hyprcursor_test1) add_dependencies(tests hyprcursor_test1)
add_executable(hyprcursor_test2 "tests/only_metadata.cpp") add_executable(hyprcursor_test2 "tests/only_metadata.cpp")
target_link_libraries(hyprcursor_test2 PRIVATE hyprcursor) target_link_libraries(hyprcursor_test2 PRIVATE hyprcursor)
add_test( add_test(NAME "Test libhyprcursor in C++ (only metadata)" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test2)
NAME "Test libhyprcursor in C++ (only metadata)"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprcursor_test2)
add_dependencies(tests hyprcursor_test2) add_dependencies(tests hyprcursor_test2)
add_executable(hyprcursor_test_c "tests/c_test.c") add_executable(hyprcursor_test_c "tests/c_test.c")
target_link_libraries(hyprcursor_test_c PRIVATE hyprcursor) target_link_libraries(hyprcursor_test_c PRIVATE hyprcursor)
add_test( add_test(NAME "Test libhyprcursor in C" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test_c)
NAME "Test libhyprcursor in C"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprcursor_test_c)
add_dependencies(tests hyprcursor_test_c) add_dependencies(tests hyprcursor_test_c)
# Installation # Installation
install(TARGETS hyprcursor) install(TARGETS hyprcursor)
install(TARGETS hyprcursor-util) install(TARGETS hyprcursor-util)
install(DIRECTORY "include/hyprcursor" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(DIRECTORY "include/hyprcursor" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES ${CMAKE_BINARY_DIR}/hyprcursor.pc install(FILES ${CMAKE_BINARY_DIR}/hyprcursor.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
if(INSTALL_TESTS)
install(TARGETS hyprcursor_test1)
install(TARGETS hyprcursor_test2)
install(TARGETS hyprcursor_test_c)
endif()

View file

@ -16,11 +16,6 @@ doesn't suck as much.
- Support for SVG cursors - Support for SVG cursors
- Way more space-efficient. As an example, Bibata-XCursor is 44.1MB, while it's 6.6MB in hyprcursor. - Way more space-efficient. As an example, Bibata-XCursor is 44.1MB, while it's 6.6MB in hyprcursor.
## Documentation
See the [wiki here](https://wiki.hyprland.org/Hypr-Ecosystem/hyprcursor/)
check out [docs/](./docs)
and [standards](https://standards.hyprland.org/hyprcursor)
## Tools ## Tools
### hyprcursor-util ### hyprcursor-util
@ -37,6 +32,20 @@ It provides C and C++ bindings.
For both C and C++, see `tests/`. For both C and C++, see `tests/`.
## Docs
See `docs/`.
## TODO
Library:
- [x] Support animated cursors
- [x] Support SVG cursors
Util:
- [ ] Support compiling a theme with X
- [x] Support decompiling animated cursors
## Building ## Building
### Deps: ### Deps:
@ -44,12 +53,11 @@ For both C and C++, see `tests/`.
- cairo - cairo
- libzip - libzip
- librsvg - librsvg
- tomlplusplus
### Build ### Build
```sh ```sh
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF` cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
``` ```
Install with: Install with:

View file

@ -1 +0,0 @@
0.1.13

View file

@ -49,16 +49,13 @@ Each cursor image is a separate directory. In it, multiple size variations can b
resize_algorithm = bilinear resize_algorithm = bilinear
# "hotspot" is where in your cursor the actual "click point" should be. # "hotspot" is where in your cursor the actual "click point" should be.
# this is in absolute coordinates. x+ is east, y+ is south. # 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_x = 0.0 # this goes 0 - 1
hotspot_y = 0.0 # this goes 0 - 1 hotspot_y = 0.0 # this goes 0 - 1
# Define what cursor images this one should override. # Define what cursor images this one should override.
# What this means is that a request for a cursor name e.g. "arrow" # What this means is that a request for a cursor name e.g. "arrow"
# will instead use this one, even if this one is named something else. # will instead use this one, even if this one is named something else.
# There is no unified list for all the available cursor names but this wayland list could be used as a reference https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/staging/cursor-shape/cursor-shape-v1.xml#L71 for wayland specific cursors.
define_override = arrow define_override = arrow
define_override = default define_override = default
@ -72,7 +69,6 @@ define_size = 32, image32.png
# define_size = 64, anim2.png, 500 # define_size = 64, anim2.png, 500
# define_size = 64, anim3.png, 500 # define_size = 64, anim3.png, 500
# define_size = 64, anim4.png, 500 # define_size = 64, anim4.png, 500
# Make sure the timeout is > 0, as otherwise the consumer might ignore your timeouts for being invalid.
``` ```
Supported cursor image types are png and svg. Supported cursor image types are png and svg.

59
flake.lock generated
View file

@ -2,20 +2,17 @@
"nodes": { "nodes": {
"hyprlang": { "hyprlang": {
"inputs": { "inputs": {
"hyprutils": "hyprutils",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
], ],
"systems": [ "systems": "systems"
"systems"
]
}, },
"locked": { "locked": {
"lastModified": 1749145882, "lastModified": 1709914708,
"narHash": "sha256-qr0KXeczF8Sma3Ae7+dR2NHhvG7YeLBJv19W4oMu6ZE=", "narHash": "sha256-bR4o3mynoTa1Wi4ZTjbnsZ6iqVcPGriXp56bZh5UFTk=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprlang", "repo": "hyprlang",
"rev": "1bfb84f54d50c7ae6558c794d3cfd5f6a7e6e676", "rev": "a685493fdbeec01ca8ccdf1f3655c044a8ce2fe2",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -24,38 +21,13 @@
"type": "github" "type": "github"
} }
}, },
"hyprutils": {
"inputs": {
"nixpkgs": [
"hyprlang",
"nixpkgs"
],
"systems": [
"hyprlang",
"systems"
]
},
"locked": {
"lastModified": 1749135356,
"narHash": "sha256-Q8mAKMDsFbCEuq7zoSlcTuxgbIBVhfIYpX0RjE32PS0=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "e36db00dfb3a3d3fdcc4069cb292ff60d2699ccb",
"type": "github"
},
"original": {
"owner": "hyprwm",
"repo": "hyprutils",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1748929857, "lastModified": 1708475490,
"narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=", "narHash": "sha256-g1v0TsWBQPX97ziznfJdWhgMyMGtoBFs102xSYO4syU=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4", "rev": "0e74ca98a74bc7270d28838369593635a5db3260",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -69,7 +41,7 @@
"inputs": { "inputs": {
"hyprlang": "hyprlang", "hyprlang": "hyprlang",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"systems": "systems" "systems": "systems_2"
} }
}, },
"systems": { "systems": {
@ -86,6 +58,21 @@
"repo": "default-linux", "repo": "default-linux",
"type": "github" "type": "github"
} }
},
"systems_2": {
"locked": {
"lastModified": 1689347949,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
"owner": "nix-systems",
"repo": "default-linux",
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default-linux",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View file

@ -30,7 +30,7 @@
packages = eachSystem (system: { packages = eachSystem (system: {
default = self.packages.${system}.hyprcursor; default = self.packages.${system}.hyprcursor;
inherit (pkgsFor.${system}) hyprcursor hyprcursor-with-tests; inherit (pkgsFor.${system}) hyprcursor;
}); });
checks = eachSystem (system: self.packages.${system}); checks = eachSystem (system: self.packages.${system});

View file

@ -2,26 +2,20 @@
#include <zip.h> #include <zip.h>
#include <optional> #include <optional>
#include <filesystem> #include <filesystem>
#include <fstream>
#include <array> #include <array>
#include <format> #include <format>
#include <algorithm> #include <algorithm>
#include <regex>
#include <hyprlang.hpp> #include <hyprlang.hpp>
#include "internalSharedTypes.hpp" #include "internalSharedTypes.hpp"
#include "manifest.hpp" #include "manifest.hpp"
#include "meta.hpp" #include "meta.hpp"
#ifndef ZIP_LENGTH_TO_END enum eOperation {
#define ZIP_LENGTH_TO_END -1
#endif
enum eOperation : uint8_t {
OPERATION_CREATE = 0, OPERATION_CREATE = 0,
OPERATION_EXTRACT = 1, OPERATION_EXTRACT = 1,
}; };
static eHyprcursorResizeAlgo explicitResizeAlgo = HC_RESIZE_INVALID; eHyprcursorResizeAlgo explicitResizeAlgo = HC_RESIZE_INVALID;
struct XCursorConfigEntry { struct XCursorConfigEntry {
int size = 0, hotspotX = 0, hotspotY = 0, delay = 0; int size = 0, hotspotX = 0, hotspotY = 0, delay = 0;
@ -93,7 +87,7 @@ static std::optional<std::string> createCursorThemeFromPath(const std::string& p
const std::string THEMENAME = manifest.parsedData.name; const std::string THEMENAME = manifest.parsedData.name;
std::string out = (out_.empty() ? path.substr(0, path.find_last_of('/')) : out_) + "/theme_" + THEMENAME; std::string out = (out_.empty() ? path.substr(0, path.find_last_of('/') + 1) : out_) + "/theme_" + THEMENAME + "/";
const std::string CURSORSSUBDIR = manifest.parsedData.cursorsDirectory; const std::string CURSORSSUBDIR = manifest.parsedData.cursorsDirectory;
const std::string CURSORDIR = path + "/" + CURSORSSUBDIR; const std::string CURSORDIR = path + "/" + CURSORSSUBDIR;
@ -104,9 +98,6 @@ static std::optional<std::string> createCursorThemeFromPath(const std::string& p
// iterate over the directory and record all cursors // iterate over the directory and record all cursors
for (auto& dir : std::filesystem::directory_iterator(CURSORDIR)) { for (auto& dir : std::filesystem::directory_iterator(CURSORDIR)) {
if (!std::regex_match(dir.path().stem().string(), std::regex("^[A-Za-z0-9_\\-\\.]+$")))
return "Invalid cursor directory name at " + dir.path().string() + " : characters must be within [A-Za-z0-9_\\-\\.]";
const auto METAPATH = dir.path().string() + "/meta"; 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>());
@ -119,11 +110,9 @@ static std::optional<std::string> createCursorThemeFromPath(const std::string& p
return "couldn't parse meta: " + *PARSERESULT2; return "couldn't parse meta: " + *PARSERESULT2;
for (auto& i : meta.parsedData.definedSizes) { for (auto& i : meta.parsedData.definedSizes) {
SHAPE->images.push_back(SCursorImage{.filename = i.file, .size = i.size, .delay = i.delayMs}); SHAPE->images.push_back(SCursorImage{i.file, i.size, i.delayMs});
} }
SHAPE->overrides = meta.parsedData.overrides;
// check if we have at least one image. // check if we have at least one image.
for (auto& i : SHAPE->images) { for (auto& i : SHAPE->images) {
@ -189,17 +178,17 @@ static std::optional<std::string> createCursorThemeFromPath(const std::string& p
// add meta.hl // add meta.hl
const auto METADIR = std::filesystem::exists(CURRENTCURSORSDIR + "/meta.hl") ? (CURRENTCURSORSDIR + "/meta.hl") : (CURRENTCURSORSDIR + "/meta.toml"); 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, ZIP_LENGTH_TO_END); zip_source_t* meta = zip_source_file(zip, METADIR.c_str(), 0, 0);
if (!meta) if (!meta)
return "(1) failed to add meta " + METADIR + " 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) if (zip_file_add(zip, "meta.hl", meta, ZIP_FL_ENC_UTF_8) < 0)
return "(2) failed to add meta " + METADIR + " to hlc"; return "(2) failed to add meta " + METADIR + " to hlc";
meta = nullptr; meta = nullptr;
// add each cursor image // add each cursor png
for (auto& i : shape->images) { for (auto& i : shape->images) {
zip_source_t* image = zip_source_file(zip, (CURRENTCURSORSDIR + "/" + i.filename).c_str(), 0, ZIP_LENGTH_TO_END); zip_source_t* image = zip_source_file(zip, (CURRENTCURSORSDIR + "/" + i.filename).c_str(), 0, 0);
if (!image) if (!image)
return "(1) failed to add image " + (CURRENTCURSORSDIR + "/" + i.filename) + " to hlc"; return "(1) failed to add image " + (CURRENTCURSORSDIR + "/" + i.filename) + " to hlc";
if (zip_file_add(zip, (i.filename).c_str(), image, ZIP_FL_ENC_UTF_8) < 0) if (zip_file_add(zip, (i.filename).c_str(), image, ZIP_FL_ENC_UTF_8) < 0)
@ -210,8 +199,9 @@ static std::optional<std::string> createCursorThemeFromPath(const std::string& p
// close zip and write // close zip and write
if (zip_close(zip) < 0) { if (zip_close(zip) < 0) {
zip_error_t* ziperror = zip_get_error(zip); zip_error_t ziperror;
return "Failed to write " + OUTPUTFILE + ": " + zip_error_strerror(ziperror); zip_error_init_with_code(&ziperror, errp);
return "Failed to write " + OUTPUTFILE + ": " + zip_error_strerror(&ziperror);
} }
std::cout << "Written " << OUTPUTFILE << "\n"; std::cout << "Written " << OUTPUTFILE << "\n";
@ -388,7 +378,7 @@ int main(int argc, char** argv, char** envp) {
eOperation op = OPERATION_CREATE; eOperation op = OPERATION_CREATE;
std::string path = "", out = ""; std::string path = "", out = "";
for (int i = 1; i < argc; ++i) { for (size_t i = 1; i < argc; ++i) {
std::string arg = argv[i]; std::string arg = argv[i];
if (arg == "-v" || arg == "--version") { if (arg == "-v" || arg == "--version") {

View file

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <vector> #include <vector>
#include <cstdlib> #include <stdlib.h>
#include <string> #include <string>
#include "shared.h" #include "shared.h"
@ -47,22 +47,6 @@ namespace Hyprcursor {
eHyprcursorDataType type = HC_DATA_PNG; eHyprcursorDataType type = HC_DATA_PNG;
}; };
/*!
struct for cursor manager options
*/
struct SManagerOptions {
explicit SManagerOptions();
/*!
The function used for logging by the cursor manager
*/
PHYPRCURSORLOGFUNC logFn;
/*!
Allow fallback to env and first theme found
*/
bool allowDefaultFallback;
};
/*! /*!
Basic Hyprcursor manager. Basic Hyprcursor manager.
@ -74,8 +58,6 @@ namespace Hyprcursor {
If none found, bool valid() will be false. If none found, bool valid() will be false.
If loading fails, bool valid() will be false. If loading fails, bool valid() will be false.
If theme has no valid cursor shapes, bool valid() will be false.
*/ */
class CHyprcursorManager { class CHyprcursorManager {
public: public:
@ -84,7 +66,6 @@ namespace Hyprcursor {
\since 0.1.6 \since 0.1.6
*/ */
CHyprcursorManager(const char* themeName, PHYPRCURSORLOGFUNC fn); CHyprcursorManager(const char* themeName, PHYPRCURSORLOGFUNC fn);
CHyprcursorManager(const char* themeName, SManagerOptions options);
~CHyprcursorManager(); ~CHyprcursorManager();
/*! /*!
@ -114,7 +95,7 @@ namespace Hyprcursor {
SCursorShapeData data; SCursorShapeData data;
for (int i = 0; i < size; ++i) { for (size_t i = 0; i < size; ++i) {
SCursorImageData image; SCursorImageData image;
image.delay = images[i]->delay; image.delay = images[i]->delay;
image.size = images[i]->size; image.size = images[i]->size;
@ -189,10 +170,9 @@ namespace Hyprcursor {
private: private:
void init(const char* themeName_); void init(const char* themeName_);
CHyprcursorImplementation* impl = nullptr; CHyprcursorImplementation* impl = nullptr;
bool finalizedAndValid = false; bool finalizedAndValid = false;
bool allowDefaultFallback = true; PHYPRCURSORLOGFUNC logFn = nullptr;
PHYPRCURSORLOGFUNC logFn = nullptr;
friend class CHyprcursorImplementation; friend class CHyprcursorImplementation;
}; };

View file

@ -54,7 +54,6 @@ struct SCursorRawShapeDataC {
char* overridenBy; char* overridenBy;
enum eHyprcursorResizeAlgo resizeAlgo; enum eHyprcursorResizeAlgo resizeAlgo;
enum eHyprcursorDataType type; enum eHyprcursorDataType type;
float nominalSize;
}; };
typedef struct SCursorRawShapeDataC hyprcursor_cursor_raw_shape_data; typedef struct SCursorRawShapeDataC hyprcursor_cursor_raw_shape_data;

View file

@ -28,7 +28,8 @@ CVarList::CVarList(const std::string& in, const size_t lastArgNo, const char del
std::string args{in}; std::string args{in};
size_t idx = 0; size_t idx = 0;
size_t pos = 0; size_t pos = 0;
std::ranges::replace_if(args, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 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)) { for (const auto& s : args | std::views::split(0)) {
if (removeEmpty && s.empty()) if (removeEmpty && s.empty())
@ -38,7 +39,7 @@ CVarList::CVarList(const std::string& in, const size_t lastArgNo, const char del
break; break;
} }
pos += s.size() + 1; pos += s.size() + 1;
m_vArgs.emplace_back(removeBeginEndSpacesTabs(s.data())); m_vArgs.emplace_back(removeBeginEndSpacesTabs(std::string_view{s}.data()));
} }
} }

View file

@ -2,14 +2,10 @@
#include "internalSharedTypes.hpp" #include "internalSharedTypes.hpp"
#include "internalDefines.hpp" #include "internalDefines.hpp"
#include <array> #include <array>
#include <cstddef>
#include <sstream>
#include <cstdio>
#include <filesystem> #include <filesystem>
#include <zip.h> #include <zip.h>
#include <cstring> #include <cstring>
#include <algorithm> #include <algorithm>
#include <cmath>
#include <librsvg/rsvg.h> #include <librsvg/rsvg.h>
#include "manifest.hpp" #include "manifest.hpp"
@ -18,25 +14,11 @@
using namespace Hyprcursor; using namespace Hyprcursor;
static std::vector<std::string> getSystemThemeDirs() { // directories for lookup
const auto envXdgData = std::getenv("XDG_DATA_DIRS"); constexpr const std::array<const char*, 1> systemThemeDirs = {"/usr/share/icons"};
std::vector<std::string> result;
if (envXdgData) {
std::stringstream envXdgStream(envXdgData);
std::string tmpStr;
while (getline(envXdgStream, tmpStr, ':'))
result.push_back((tmpStr + "/icons"));
} else
result = {"/usr/share/icons"};
return result;
}
const std::vector<std::string> systemThemeDirs = getSystemThemeDirs();
constexpr const std::array<const char*, 2> userThemeDirs = {"/.local/share/icons", "/.icons"}; constexpr const std::array<const char*, 2> userThemeDirs = {"/.local/share/icons", "/.icons"};
// //
static std::string themeNameFromEnv(PHYPRCURSORLOGFUNC logfn) { static std::string themeNameFromEnv(PHYPRCURSORLOGFUNC logfn) {
const auto ENV = getenv("HYPRCURSOR_THEME"); const auto ENV = getenv("HYPRCURSOR_THEME");
if (!ENV) { if (!ENV) {
@ -58,7 +40,7 @@ static bool pathAccessible(const std::string& path) {
} }
static bool themeAccessible(const std::string& path) { static bool themeAccessible(const std::string& path) {
return pathAccessible(path + "/manifest.hl") || pathAccessible(path + "/manifest.toml"); return pathAccessible(path + "/manifest.hl") || pathAccessible(path + "manifest.toml");
} }
static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) { static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) {
@ -97,7 +79,7 @@ static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) {
} }
for (auto& dir : systemThemeDirs) { for (auto& dir : systemThemeDirs) {
const auto& FULLPATH = dir; const auto FULLPATH = dir;
if (!pathAccessible(FULLPATH)) { if (!pathAccessible(FULLPATH)) {
Debug::log(HC_LOG_TRACE, logfn, "Skipping path {} because it's inaccessible.", FULLPATH); Debug::log(HC_LOG_TRACE, logfn, "Skipping path {} because it's inaccessible.", FULLPATH);
continue; continue;
@ -125,7 +107,7 @@ static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) {
return ""; return "";
} }
static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORLOGFUNC logfn, bool allowDefaultFallback) { static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORLOGFUNC logfn) {
const auto HOMEENV = getenv("HOME"); const auto HOMEENV = getenv("HOME");
if (!HOMEENV) if (!HOMEENV)
return ""; return "";
@ -151,7 +133,7 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL
const auto MANIFESTPATH = themeDir.path().string() + "/manifest"; const auto MANIFESTPATH = themeDir.path().string() + "/manifest";
if (allowDefaultFallback && name.empty()) { if (name.empty()) {
if (std::filesystem::exists(MANIFESTPATH + ".hl") || std::filesystem::exists(MANIFESTPATH + ".toml")) { if (std::filesystem::exists(MANIFESTPATH + ".hl") || std::filesystem::exists(MANIFESTPATH + ".toml")) {
Debug::log(HC_LOG_INFO, logfn, "getFullPathForThemeName: found {}", themeDir.path().string()); Debug::log(HC_LOG_INFO, logfn, "getFullPathForThemeName: found {}", themeDir.path().string());
return std::filesystem::canonical(themeDir.path()).string(); return std::filesystem::canonical(themeDir.path()).string();
@ -159,11 +141,12 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL
continue; continue;
} }
CManifest manifest{MANIFESTPATH}; if (!std::filesystem::exists(MANIFESTPATH))
if (const auto R = manifest.parse(); R.has_value()) { continue;
Debug::log(HC_LOG_ERR, logfn, "failed parsing Manifest of {}: {}", themeDir.path().string(), *R);
CManifest manifest{MANIFESTPATH};
if (!manifest.parse().has_value())
continue; continue;
}
const std::string NAME = manifest.parsedData.name; const std::string NAME = manifest.parsedData.name;
@ -176,7 +159,7 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL
} }
for (auto& dir : systemThemeDirs) { for (auto& dir : systemThemeDirs) {
const auto& FULLPATH = dir; const auto FULLPATH = dir;
if (!pathAccessible(FULLPATH)) { if (!pathAccessible(FULLPATH)) {
Debug::log(HC_LOG_TRACE, logfn, "Skipping path {} because it's inaccessible.", FULLPATH); Debug::log(HC_LOG_TRACE, logfn, "Skipping path {} because it's inaccessible.", FULLPATH);
continue; continue;
@ -194,11 +177,12 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL
const auto MANIFESTPATH = themeDir.path().string() + "/manifest"; const auto MANIFESTPATH = themeDir.path().string() + "/manifest";
CManifest manifest{MANIFESTPATH}; if (std::filesystem::exists(MANIFESTPATH + ".hl") || std::filesystem::exists(MANIFESTPATH + ".toml"))
if (const auto R = manifest.parse(); R.has_value()) { continue;
Debug::log(HC_LOG_ERR, logfn, "failed parsing Manifest of {}: {}", themeDir.path().string(), *R);
CManifest manifest{MANIFESTPATH};
if (!manifest.parse().has_value())
continue; continue;
}
const std::string NAME = manifest.parsedData.name; const std::string NAME = manifest.parsedData.name;
@ -210,40 +194,33 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL
} }
} }
if (allowDefaultFallback && !name.empty()) { // try without name if (!name.empty()) { // try without name
Debug::log(HC_LOG_INFO, logfn, "getFullPathForThemeName: failed, trying without name of {}", name); Debug::log(HC_LOG_INFO, logfn, "getFullPathForThemeName: failed, trying without name of {}", name);
return getFullPathForThemeName("", logfn, allowDefaultFallback); return getFullPathForThemeName("", logfn);
} }
return ""; return "";
} }
SManagerOptions::SManagerOptions() : logFn(nullptr), allowDefaultFallback(true) {
;
}
CHyprcursorManager::CHyprcursorManager(const char* themeName_) { CHyprcursorManager::CHyprcursorManager(const char* themeName_) {
init(themeName_); init(themeName_);
} }
CHyprcursorManager::CHyprcursorManager(const char* themeName_, PHYPRCURSORLOGFUNC fn) : logFn(fn) { CHyprcursorManager::CHyprcursorManager(const char* themeName_, PHYPRCURSORLOGFUNC fn) {
init(themeName_); logFn = fn;
}
CHyprcursorManager::CHyprcursorManager(const char* themeName_, SManagerOptions options) : allowDefaultFallback(options.allowDefaultFallback), logFn(options.logFn) {
init(themeName_); init(themeName_);
} }
void CHyprcursorManager::init(const char* themeName_) { void CHyprcursorManager::init(const char* themeName_) {
std::string themeName = themeName_ ? themeName_ : ""; std::string themeName = themeName_ ? themeName_ : "";
if (allowDefaultFallback && themeName.empty()) { if (themeName.empty()) {
// try reading from env // try reading from env
Debug::log(HC_LOG_INFO, logFn, "CHyprcursorManager: attempting to find theme from env"); Debug::log(HC_LOG_INFO, logFn, "CHyprcursorManager: attempting to find theme from env");
themeName = themeNameFromEnv(logFn); themeName = themeNameFromEnv(logFn);
} }
if (allowDefaultFallback && themeName.empty()) { if (themeName.empty()) {
// try finding first, in the hierarchy // try finding first, in the hierarchy
Debug::log(HC_LOG_INFO, logFn, "CHyprcursorManager: attempting to find any theme"); Debug::log(HC_LOG_INFO, logFn, "CHyprcursorManager: attempting to find any theme");
themeName = getFirstTheme(logFn); themeName = getFirstTheme(logFn);
@ -258,7 +235,7 @@ void CHyprcursorManager::init(const char* themeName_) {
// initialize theme // initialize theme
impl = new CHyprcursorImplementation(this, logFn); impl = new CHyprcursorImplementation(this, logFn);
impl->themeName = themeName; impl->themeName = themeName;
impl->themeFullDir = getFullPathForThemeName(themeName, logFn, allowDefaultFallback); impl->themeFullDir = getFullPathForThemeName(themeName, logFn);
if (impl->themeFullDir.empty()) if (impl->themeFullDir.empty())
return; return;
@ -272,16 +249,12 @@ void CHyprcursorManager::init(const char* themeName_) {
return; return;
} }
if (impl->theme.shapes.empty()) {
Debug::log(HC_LOG_ERR, logFn, "Theme {} has no valid cursor shapes\n", impl->themeName);
return;
}
finalizedAndValid = true; finalizedAndValid = true;
} }
CHyprcursorManager::~CHyprcursorManager() { CHyprcursorManager::~CHyprcursorManager() {
delete impl; if (impl)
delete impl;
} }
bool CHyprcursorManager::valid() { bool CHyprcursorManager::valid() {
@ -303,15 +276,13 @@ SCursorImageData** CHyprcursorManager::getShapesC(int& outSize, const char* shap
if (REQUESTEDSHAPE != shape->directory && std::find(shape->overrides.begin(), shape->overrides.end(), REQUESTEDSHAPE) == shape->overrides.end()) if (REQUESTEDSHAPE != shape->directory && std::find(shape->overrides.begin(), shape->overrides.end(), REQUESTEDSHAPE) == shape->overrides.end())
continue; continue;
const int PIXELSIDE = std::round(info.size / shape->nominalSize);
hotX = shape->hotspotX; hotX = shape->hotspotX;
hotY = shape->hotspotY; hotY = shape->hotspotY;
// matched :) // matched :)
bool foundAny = false; bool foundAny = false;
for (auto& image : impl->loadedShapes[shape.get()].images) { for (auto& image : impl->loadedShapes[shape.get()].images) {
if (image->side != PIXELSIDE) if (image->side != info.size)
continue; continue;
// found size // found size
@ -331,7 +302,7 @@ SCursorImageData** CHyprcursorManager::getShapesC(int& outSize, const char* shap
// find nearest // find nearest
int leader = 13371337; int leader = 13371337;
for (auto& image : impl->loadedShapes[shape.get()].images) { for (auto& image : impl->loadedShapes[shape.get()].images) {
if (std::abs((int)(image->side - PIXELSIDE)) > std::abs((int)(leader - PIXELSIDE))) if (std::abs((int)(image->side - info.size)) > leader)
continue; continue;
leader = image->side; leader = image->side;
@ -366,8 +337,8 @@ SCursorImageData** CHyprcursorManager::getShapesC(int& outSize, const char* shap
data[i]->delay = resultingImages[i]->delay; data[i]->delay = resultingImages[i]->delay;
data[i]->size = resultingImages[i]->side; data[i]->size = resultingImages[i]->side;
data[i]->surface = resultingImages[i]->cairoSurface; data[i]->surface = resultingImages[i]->cairoSurface;
data[i]->hotspotX = std::round(hotX * (float)data[i]->size); data[i]->hotspotX = hotX * data[i]->size;
data[i]->hotspotY = std::round(hotY * (float)data[i]->size); data[i]->hotspotY = hotY * data[i]->size;
} }
outSize = resultingImages.size(); outSize = resultingImages.size();
@ -387,25 +358,14 @@ SCursorRawShapeDataC* CHyprcursorManager::getRawShapeDataC(const char* shape_) {
SCursorRawShapeDataC* data = new SCursorRawShapeDataC; SCursorRawShapeDataC* data = new SCursorRawShapeDataC;
std::vector<SLoadedCursorImage*> resultingImages; std::vector<SLoadedCursorImage*> resultingImages;
data->overridenBy = nullptr;
data->images = nullptr;
data->len = 0;
data->hotspotX = 0.f;
data->hotspotY = 0.F;
data->nominalSize = 1.F;
data->resizeAlgo = eHyprcursorResizeAlgo::HC_RESIZE_NONE;
data->type = eHyprcursorDataType::HC_DATA_PNG;
for (auto& shape : impl->theme.shapes) { for (auto& shape : impl->theme.shapes) {
// if it's overridden just return the override // if it's overridden just return the override
if (const auto IT = std::find_if(shape->overrides.begin(), shape->overrides.end(), [&](const auto& e) { return e == SHAPE && SHAPE != shape->directory; }); if (const auto IT = std::find(shape->overrides.begin(), shape->overrides.end(), SHAPE); IT != shape->overrides.end()) {
IT != shape->overrides.end()) { data->overridenBy = strdup(IT->c_str());
data->overridenBy = strdup(shape->directory.c_str());
return data; return data;
} }
}
for (auto& shape : impl->theme.shapes) {
if (shape->directory != SHAPE) if (shape->directory != SHAPE)
continue; continue;
@ -417,10 +377,9 @@ SCursorRawShapeDataC* CHyprcursorManager::getRawShapeDataC(const char* shape_) {
resultingImages.push_back(i.get()); resultingImages.push_back(i.get());
} }
data->hotspotX = shape->hotspotX; data->hotspotX = shape->hotspotX;
data->hotspotY = shape->hotspotY; data->hotspotY = shape->hotspotY;
data->nominalSize = shape->nominalSize; data->type = shape->shapeType == SHAPE_PNG ? HC_DATA_PNG : HC_DATA_SVG;
data->type = shape->shapeType == SHAPE_PNG ? HC_DATA_PNG : HC_DATA_SVG;
break; break;
} }
@ -450,10 +409,8 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
bool sizeFound = false; bool sizeFound = false;
if (shape->shapeType == SHAPE_PNG) { if (shape->shapeType == SHAPE_PNG) {
const int IDEALSIDE = std::round(info.size / shape->nominalSize);
for (auto& image : impl->loadedShapes[shape.get()].images) { for (auto& image : impl->loadedShapes[shape.get()].images) {
if (image->side != IDEALSIDE) if (image->side != info.size)
continue; continue;
sizeFound = true; sizeFound = true;
@ -467,7 +424,7 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
SLoadedCursorImage* leader = nullptr; SLoadedCursorImage* leader = nullptr;
int leaderVal = 1000000; int leaderVal = 1000000;
for (auto& image : impl->loadedShapes[shape.get()].images) { for (auto& image : impl->loadedShapes[shape.get()].images) {
if (image->side < IDEALSIDE) if (image->side < info.size)
continue; continue;
if (image->side > leaderVal) if (image->side > leaderVal)
@ -479,7 +436,7 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
if (!leader) { if (!leader) {
for (auto& image : impl->loadedShapes[shape.get()].images) { for (auto& image : impl->loadedShapes[shape.get()].images) {
if (std::abs((int)(image->side - IDEALSIDE)) > leaderVal) if (std::abs((int)(image->side - info.size)) > leaderVal)
continue; continue;
leaderVal = image->side; leaderVal = image->side;
@ -496,16 +453,12 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
Debug::log(HC_LOG_TRACE, logFn, "loadThemeStyle: png shape {} has {} frames", shape->directory, FRAMES.size()); Debug::log(HC_LOG_TRACE, logFn, "loadThemeStyle: png shape {} has {} frames", shape->directory, FRAMES.size());
const int PIXELSIDE = std::round(info.size / shape->nominalSize);
Debug::log(HC_LOG_TRACE, logFn, "loadThemeStyle: png shape has nominal {:.2f}, pixel size will be {}x", shape->nominalSize, PIXELSIDE);
for (auto& f : FRAMES) { for (auto& f : FRAMES) {
auto& newImage = impl->loadedShapes[shape.get()].images.emplace_back(std::make_unique<SLoadedCursorImage>()); auto& newImage = impl->loadedShapes[shape.get()].images.emplace_back(std::make_unique<SLoadedCursorImage>());
newImage->artificial = true; newImage->artificial = true;
newImage->side = PIXELSIDE; newImage->side = info.size;
newImage->artificialData = new char[static_cast<unsigned long>(PIXELSIDE * PIXELSIDE * 4)]; newImage->artificialData = new char[info.size * info.size * 4];
newImage->cairoSurface = cairo_image_surface_create_for_data((unsigned char*)newImage->artificialData, CAIRO_FORMAT_ARGB32, PIXELSIDE, PIXELSIDE, PIXELSIDE * 4); newImage->cairoSurface = cairo_image_surface_create_for_data((unsigned char*)newImage->artificialData, CAIRO_FORMAT_ARGB32, info.size, info.size, info.size * 4);
newImage->delay = f->delay; newImage->delay = f->delay;
const auto PCAIRO = cairo_create(newImage->cairoSurface); const auto PCAIRO = cairo_create(newImage->cairoSurface);
@ -519,12 +472,12 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
const auto PTN = cairo_pattern_create_for_surface(f->cairoSurface); const auto PTN = cairo_pattern_create_for_surface(f->cairoSurface);
cairo_pattern_set_extend(PTN, CAIRO_EXTEND_NONE); cairo_pattern_set_extend(PTN, CAIRO_EXTEND_NONE);
const float scale = PIXELSIDE / (float)f->side; const float scale = info.size / (float)f->side;
cairo_scale(PCAIRO, scale, scale); cairo_scale(PCAIRO, scale, scale);
cairo_pattern_set_filter(PTN, shape->resizeAlgo == HC_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_set_source(PCAIRO, PTN);
cairo_rectangle(PCAIRO, 0, 0, PIXELSIDE, PIXELSIDE); cairo_rectangle(PCAIRO, 0, 0, info.size, info.size);
cairo_fill(PCAIRO); cairo_fill(PCAIRO);
cairo_surface_flush(newImage->cairoSurface); cairo_surface_flush(newImage->cairoSurface);
@ -537,16 +490,12 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
Debug::log(HC_LOG_TRACE, logFn, "loadThemeStyle: svg shape {} has {} frames", shape->directory, FRAMES.size()); Debug::log(HC_LOG_TRACE, logFn, "loadThemeStyle: svg shape {} has {} frames", shape->directory, FRAMES.size());
const int PIXELSIDE = std::round(info.size / shape->nominalSize);
Debug::log(HC_LOG_TRACE, logFn, "loadThemeStyle: svg shape has nominal {:.2f}, pixel size will be {}x", shape->nominalSize, PIXELSIDE);
for (auto& f : FRAMES) { for (auto& f : FRAMES) {
auto& newImage = impl->loadedShapes[shape.get()].images.emplace_back(std::make_unique<SLoadedCursorImage>()); auto& newImage = impl->loadedShapes[shape.get()].images.emplace_back(std::make_unique<SLoadedCursorImage>());
newImage->artificial = true; newImage->artificial = true;
newImage->side = PIXELSIDE; newImage->side = info.size;
newImage->artificialData = new char[static_cast<unsigned long>(PIXELSIDE * PIXELSIDE * 4)]; newImage->artificialData = new char[info.size * info.size * 4];
newImage->cairoSurface = cairo_image_surface_create_for_data((unsigned char*)newImage->artificialData, CAIRO_FORMAT_ARGB32, PIXELSIDE, PIXELSIDE, PIXELSIDE * 4); newImage->cairoSurface = cairo_image_surface_create_for_data((unsigned char*)newImage->artificialData, CAIRO_FORMAT_ARGB32, info.size, info.size, info.size * 4);
newImage->delay = f->delay; newImage->delay = f->delay;
const auto PCAIRO = cairo_create(newImage->cairoSurface); const auto PCAIRO = cairo_create(newImage->cairoSurface);
@ -564,18 +513,16 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
return false; return false;
} }
RsvgRectangle rect = {0, 0, (double)PIXELSIDE, (double)PIXELSIDE}; RsvgRectangle rect = {0, 0, (double)info.size, (double)info.size};
if (!rsvg_handle_render_document(handle, PCAIRO, &rect, &error)) { if (!rsvg_handle_render_document(handle, PCAIRO, &rect, &error)) {
Debug::log(HC_LOG_ERR, logFn, "Failed rendering svg: {}", error->message); Debug::log(HC_LOG_ERR, logFn, "Failed rendering svg: {}", error->message);
g_object_unref(handle);
return false; return false;
} }
// done // done
cairo_surface_flush(newImage->cairoSurface); cairo_surface_flush(newImage->cairoSurface);
cairo_destroy(PCAIRO); cairo_destroy(PCAIRO);
g_object_unref(handle);
} }
} else { } else {
Debug::log(HC_LOG_ERR, logFn, "Invalid shapetype in loadThemeStyle"); Debug::log(HC_LOG_ERR, logFn, "Invalid shapetype in loadThemeStyle");
@ -596,7 +543,7 @@ void CHyprcursorManager::cursorSurfaceStyleDone(const SCursorStyleInfo& info) {
const bool isArtificial = e->artificial; const bool isArtificial = e->artificial;
// clean artificial rasters made for this // clean artificial rasters made for this
if (isArtificial && e->side == std::round(info.size / shape->nominalSize)) if (isArtificial && e->side == info.size)
return true; return true;
// clean invalid non-svg rasters // clean invalid non-svg rasters
@ -672,22 +619,18 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
int errp = 0; int errp = 0;
zip_t* zip = zip_open(cursor.path().string().c_str(), ZIP_RDONLY, &errp); zip_t* zip = zip_open(cursor.path().string().c_str(), ZIP_RDONLY, &errp);
zip_int64_t index = zip_name_locate(zip, "meta.hl", ZIP_FL_ENC_GUESS); zip_file_t* meta_file = zip_fopen(zip, "meta.hl", ZIP_FL_UNCHANGED);
bool metaIsHL = true; bool metaIsHL = true;
if (!meta_file) {
if (index == -1) { meta_file = zip_fopen(zip, "meta.toml", ZIP_FL_UNCHANGED);
index = zip_name_locate(zip, "meta.toml", ZIP_FL_ENC_GUESS); metaIsHL = false;
metaIsHL = false; if (!meta_file)
return "cursor" + cursor.path().string() + "failed to load meta";
} }
if (index == -1) char* buffer = new char[1024 * 1024]; /* 1MB should be more than enough */
return "cursor" + cursor.path().string() + "failed to load meta";
zip_file_t* meta_file = zip_fopen_index(zip, index, ZIP_FL_UNCHANGED); int readBytes = zip_fread(meta_file, buffer, 1024 * 1024 - 1);
char* buffer = new char[static_cast<unsigned long>(1024 * 1024)]; /* 1MB should be more than enough */
int readBytes = zip_fread(meta_file, buffer, (1024 * 1024) - 1);
zip_fclose(meta_file); zip_fclose(meta_file);
@ -707,14 +650,9 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
return "cursor" + cursor.path().string() + "failed to parse meta: " + *METAPARSERESULT; return "cursor" + cursor.path().string() + "failed to parse meta: " + *METAPARSERESULT;
for (auto& i : meta.parsedData.definedSizes) { for (auto& i : meta.parsedData.definedSizes) {
SHAPE->images.push_back(SCursorImage{.filename = i.file, .size = i.size, .delay = i.delayMs}); SHAPE->images.push_back(SCursorImage{i.file, i.size, i.delayMs});
} }
SHAPE->overrides = meta.parsedData.overrides;
zip_stat_t sb;
zip_stat_init(&sb);
for (auto& i : SHAPE->images) { for (auto& i : SHAPE->images) {
if (SHAPE->shapeType == SHAPE_INVALID) { if (SHAPE->shapeType == SHAPE_INVALID) {
if (i.filename.ends_with(".svg")) if (i.filename.ends_with(".svg"))
@ -739,23 +677,14 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
IMAGE->delay = i.delay; IMAGE->delay = i.delay;
IMAGE->isSVG = SHAPE->shapeType == SHAPE_SVG; IMAGE->isSVG = SHAPE->shapeType == SHAPE_SVG;
index = zip_name_locate(zip, i.filename.c_str(), ZIP_FL_ENC_GUESS); // read from zip
if (index == -1) zip_file_t* image_file = zip_fopen(zip, i.filename.c_str(), ZIP_FL_UNCHANGED);
if (!image_file)
return "cursor" + cursor.path().string() + "failed to load image_file"; return "cursor" + cursor.path().string() + "failed to load image_file";
// read from zip IMAGE->data = new char[1024 * 1024]; /* 1MB should be more than enough, again. This probably should be in the spec. */
zip_file_t* image_file = zip_fopen_index(zip, index, ZIP_FL_UNCHANGED);
zip_stat_index(zip, index, ZIP_FL_UNCHANGED, &sb);
if (sb.valid & ZIP_STAT_SIZE) { IMAGE->dataLen = zip_fread(image_file, IMAGE->data, 1024 * 1024 - 1);
IMAGE->data = new char[sb.size + 1];
IMAGE->dataLen = sb.size + 1;
} else {
IMAGE->data = new char[static_cast<unsigned long>(1024 * 1024)]; /* 1MB should be more than enough, again. This probably should be in the spec. */
IMAGE->dataLen = static_cast<size_t>(1024 * 1024);
}
IMAGE->dataLen = zip_fread(image_file, IMAGE->data, IMAGE->dataLen - 1);
zip_fclose(image_file); zip_fclose(image_file);
@ -778,11 +707,10 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
if (SHAPE->images.empty()) if (SHAPE->images.empty())
return "meta invalid: no images for shape " + cursor.path().stem().string(); return "meta invalid: no images for shape " + cursor.path().stem().string();
SHAPE->directory = cursor.path().stem().string(); SHAPE->directory = cursor.path().stem().string();
SHAPE->hotspotX = meta.parsedData.hotspotX; SHAPE->hotspotX = meta.parsedData.hotspotX;
SHAPE->hotspotY = meta.parsedData.hotspotY; SHAPE->hotspotY = meta.parsedData.hotspotY;
SHAPE->nominalSize = meta.parsedData.nominalSize; SHAPE->resizeAlgo = stringToAlgo(meta.parsedData.resizeAlgo);
SHAPE->resizeAlgo = stringToAlgo(meta.parsedData.resizeAlgo);
zip_discard(zip); zip_discard(zip);
} }

View file

@ -38,7 +38,7 @@ struct SCursorImageData** hyprcursor_get_cursor_image_data(struct hyprcursor_man
} }
void hyprcursor_cursor_image_data_free(hyprcursor_cursor_image_data** data, int size) { void hyprcursor_cursor_image_data_free(hyprcursor_cursor_image_data** data, int size) {
for (int i = 0; i < size; ++i) { for (size_t i = 0; i < size; ++i) {
free(data[i]); free(data[i]);
} }
@ -49,7 +49,7 @@ void hyprcursor_style_done(hyprcursor_manager_t* manager, hyprcursor_cursor_styl
const auto MGR = (CHyprcursorManager*)manager; const auto MGR = (CHyprcursorManager*)manager;
SCursorStyleInfo info; SCursorStyleInfo info;
info.size = info_.size; info.size = info_.size;
MGR->cursorSurfaceStyleDone(info); return MGR->cursorSurfaceStyleDone(info);
} }
void hyprcursor_register_logging_function(struct hyprcursor_manager_t* manager, PHYPRCURSORLOGFUNC fn) { void hyprcursor_register_logging_function(struct hyprcursor_manager_t* manager, PHYPRCURSORLOGFUNC fn) {

View file

@ -37,7 +37,7 @@ struct SCursorImage {
struct SCursorShape { struct SCursorShape {
std::string directory; std::string directory;
float hotspotX = 0, hotspotY = 0, nominalSize = 1.F; float hotspotX = 0, hotspotY = 0;
eHyprcursorResizeAlgo resizeAlgo = HC_RESIZE_NEAREST; eHyprcursorResizeAlgo resizeAlgo = HC_RESIZE_NEAREST;
std::vector<SCursorImage> images; std::vector<SCursorImage> images;
std::vector<std::string> overrides; std::vector<std::string> overrides;

View file

@ -3,14 +3,12 @@
#include <hyprlang.hpp> #include <hyprlang.hpp>
#include <toml++/toml.hpp> #include <toml++/toml.hpp>
#include <filesystem> #include <filesystem>
#include <regex>
#include <algorithm>
#include "VarList.hpp" #include "VarList.hpp"
static CMeta* currentMeta = nullptr; CMeta* currentMeta = nullptr;
CMeta::CMeta(const std::string& rawdata_, bool hyprlang_ /* false for toml */, bool dataIsPath) : dataPath(dataIsPath), hyprlang(hyprlang_), rawdata(rawdata_) { CMeta::CMeta(const std::string& rawdata_, bool hyprlang_ /* false for toml */, bool dataIsPath) : rawdata(rawdata_), hyprlang(hyprlang_), dataPath(dataIsPath) {
if (!dataIsPath) if (!dataIsPath)
return; return;
@ -72,54 +70,45 @@ static Hyprlang::CParseResult parseDefineSize(const char* C, const char* V) {
Hyprlang::CParseResult result; Hyprlang::CParseResult result;
const std::string VALUE = V; const std::string VALUE = V;
CVarList sizes(VALUE, 0, ';'); if (!VALUE.contains(",")) {
result.setError("Invalid define_size");
for (const auto& sizeStr : sizes) { return result;
if (!sizeStr.contains(",")) {
result.setError("Invalid define_size");
return result;
}
auto LHS = removeBeginEndSpacesTabs(sizeStr.substr(0, sizeStr.find_first_of(",")));
auto RHS = removeBeginEndSpacesTabs(sizeStr.substr(sizeStr.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;
}
if (!std::regex_match(RHS, std::regex("^[A-Za-z0-9_\\-\\.]+$"))) {
result.setError("Invalid cursor file name, characters must be within [A-Za-z0-9_\\-\\.] (if this seems like a mistake, check for invisible characters)");
return result;
}
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);
} }
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; return result;
} }
@ -127,11 +116,7 @@ static Hyprlang::CParseResult parseOverride(const char* C, const char* V) {
Hyprlang::CParseResult result; Hyprlang::CParseResult result;
const std::string VALUE = V; const std::string VALUE = V;
CVarList overrides(VALUE, 0, ';'); currentMeta->parsedData.overrides.push_back(VALUE);
for (const auto& o : overrides) {
currentMeta->parsedData.overrides.push_back(o);
}
return result; return result;
} }
@ -143,20 +128,16 @@ std::optional<std::string> CMeta::parseHL() {
meta = std::make_unique<Hyprlang::CConfig>(rawdata.c_str(), Hyprlang::SConfigOptions{.pathIsStream = !dataPath}); meta = std::make_unique<Hyprlang::CConfig>(rawdata.c_str(), Hyprlang::SConfigOptions{.pathIsStream = !dataPath});
meta->addConfigValue("hotspot_x", Hyprlang::FLOAT{0.F}); meta->addConfigValue("hotspot_x", Hyprlang::FLOAT{0.F});
meta->addConfigValue("hotspot_y", Hyprlang::FLOAT{0.F}); meta->addConfigValue("hotspot_y", Hyprlang::FLOAT{0.F});
meta->addConfigValue("nominal_size", Hyprlang::FLOAT{1.F});
meta->addConfigValue("resize_algorithm", Hyprlang::STRING{"nearest"}); meta->addConfigValue("resize_algorithm", Hyprlang::STRING{"nearest"});
meta->registerHandler(::parseDefineSize, "define_size", {.allowFlags = false}); meta->registerHandler(::parseDefineSize, "define_size", {.allowFlags = false});
meta->registerHandler(::parseOverride, "define_override", {.allowFlags = false}); meta->registerHandler(::parseOverride, "define_override", {.allowFlags = false});
meta->commence(); meta->commence();
const auto RESULT = meta->parse(); meta->parse();
if (RESULT.error)
return RESULT.getError();
} catch (const char* err) { return "failed parsing meta: " + std::string{err}; } } catch (const char* err) { return "failed parsing meta: " + std::string{err}; }
parsedData.hotspotX = std::any_cast<Hyprlang::FLOAT>(meta->getConfigValue("hotspot_x")); parsedData.hotspotX = std::any_cast<Hyprlang::FLOAT>(meta->getConfigValue("hotspot_x"));
parsedData.hotspotY = std::any_cast<Hyprlang::FLOAT>(meta->getConfigValue("hotspot_y")); parsedData.hotspotY = std::any_cast<Hyprlang::FLOAT>(meta->getConfigValue("hotspot_y"));
parsedData.nominalSize = std::clamp(std::any_cast<Hyprlang::FLOAT>(meta->getConfigValue("nominal_size")), 0.1F, 2.F); parsedData.resizeAlgo = std::any_cast<Hyprlang::STRING>(meta->getConfigValue("resize_algorithm"));
parsedData.resizeAlgo = std::any_cast<Hyprlang::STRING>(meta->getConfigValue("resize_algorithm"));
return {}; return {};
} }
@ -165,9 +146,8 @@ std::optional<std::string> CMeta::parseTOML() {
try { try {
auto MANIFEST = dataPath ? toml::parse_file(rawdata) : toml::parse(rawdata); auto MANIFEST = dataPath ? toml::parse_file(rawdata) : toml::parse(rawdata);
parsedData.hotspotX = MANIFEST["General"]["hotspot_x"].value_or(0.f); parsedData.hotspotX = MANIFEST["General"]["hotspot_x"].value_or(0.f);
parsedData.hotspotY = MANIFEST["General"]["hotspot_y"].value_or(0.f); parsedData.hotspotY = MANIFEST["General"]["hotspot_y"].value_or(0.f);
parsedData.nominalSize = std::clamp(MANIFEST["General"]["nominal_size"].value_or(1.F), 0.1F, 2.F);
const std::string OVERRIDES = MANIFEST["General"]["define_override"].value_or(""); const std::string OVERRIDES = MANIFEST["General"]["define_override"].value_or("");
const std::string SIZES = MANIFEST["General"]["define_size"].value_or(""); const std::string SIZES = MANIFEST["General"]["define_size"].value_or("");

View file

@ -20,7 +20,7 @@ class CMeta {
struct { struct {
std::string resizeAlgo; std::string resizeAlgo;
float hotspotX = 0, hotspotY = 0, nominalSize = 1.F; float hotspotX = 0, hotspotY = 0;
std::vector<std::string> overrides; std::vector<std::string> overrides;
std::vector<SDefinedSize> definedSizes; std::vector<SDefinedSize> definedSizes;
} parsedData; } parsedData;

View file

@ -7,7 +7,6 @@
hyprlang, hyprlang,
librsvg, librsvg,
libzip, libzip,
xcur2png,
tomlplusplus, tomlplusplus,
version ? "git", version ? "git",
}: }:
@ -16,6 +15,11 @@ stdenv.mkDerivation {
inherit version; inherit version;
src = ../.; src = ../.;
patches = [
# adds /run/current-system/sw/share/icons to the icon lookup directories
./dirs.patch
];
nativeBuildInputs = [ nativeBuildInputs = [
cmake cmake
pkg-config pkg-config
@ -26,7 +30,6 @@ stdenv.mkDerivation {
hyprlang hyprlang
librsvg librsvg
libzip libzip
xcur2png
tomlplusplus tomlplusplus
]; ];

13
nix/dirs.patch Normal file
View file

@ -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"};
//

View file

@ -7,7 +7,6 @@
(builtins.substring 4 2 longDate) (builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate) (builtins.substring 6 2 longDate)
]); ]);
version = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
in { in {
default = inputs.self.overlays.hyprcursor; default = inputs.self.overlays.hyprcursor;
@ -15,14 +14,10 @@ in {
inputs.hyprlang.overlays.default inputs.hyprlang.overlays.default
(final: prev: { (final: prev: {
hyprcursor = prev.callPackage ./default.nix { hyprcursor = prev.callPackage ./default.nix {
stdenv = prev.gcc15Stdenv; stdenv = prev.gcc13Stdenv;
version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty"); version = "0.pre" + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty");
inherit (final) hyprlang; inherit (final) hyprlang;
}; };
hyprcursor-with-tests = final.hyprcursor.overrideAttrs (_: _: {
cmakeFlags = [(lib.cmakeBool "INSTALL_TESTS" true)];
});
}) })
]; ];
} }

View file

@ -27,26 +27,15 @@ int main(int argc, char** argv) {
} }
hyprcursor_cursor_raw_shape_data* shapeData = hyprcursor_get_raw_shape_data(mgr, "left_ptr"); hyprcursor_cursor_raw_shape_data* shapeData = hyprcursor_get_raw_shape_data(mgr, "left_ptr");
if (!shapeData) { if (!shapeData || shapeData->len <= 0) {
printf("failed querying left_ptr\n"); printf("failed querying left_ptr\n");
return 1; return 1;
} }
if (shapeData->overridenBy) { printf("left_ptr images: %d\n", shapeData->len);
hyprcursor_cursor_raw_shape_data* ov = hyprcursor_get_raw_shape_data(mgr, shapeData->overridenBy);
hyprcursor_raw_shape_data_free(shapeData);
shapeData = ov;
}
if (!shapeData || shapeData->len <= 0) {
printf("left_ptr has no images\n");
return 1;
}
printf("left_ptr images: %ld\n", shapeData->len);
for (size_t i = 0; i < shapeData->len; ++i) { for (size_t i = 0; i < shapeData->len; ++i) {
printf("left_ptr image size: %ld\n", shapeData->images[i].len); printf("left_ptr image size: %d\n", shapeData->images[i].len);
} }
hyprcursor_raw_shape_data_free(shapeData); hyprcursor_raw_shape_data_free(shapeData);