Compare commits

..

62 commits
v0.1.6 ... main

Author SHA1 Message Date
44e91d467b
version: bump to 0.1.13 2025-07-31 14:14:09 +02:00
35c6fe201b
core: fix resizing raster images 2025-07-31 14:09:14 +02:00
Friday
45fcc10b4c
nix: use gcc15 (#91)
also updated dependencies
2025-06-05 21:28:51 +01:00
Saroj Regmi
ac903e80b3
docs: updated MAKING_THEMS.md (#86)
* docs: updated MAKING_THEMS.md 

Added a link to find all cursor names that the server may request.

* docs: updated the cursor name list reference and clarified about it.
2025-04-29 19:40:57 +02:00
2fd36421c2 README: remove todos 2025-03-17 12:46:18 +00:00
028bedbc63 version: bump to 0.1.12 2025-03-17 12:44:33 +00:00
7c6d165e1e meta: fix hyprlang colon handling
fixes #80
2025-02-04 10:29:10 +00:00
Honkazel
0a8e83d35b
core: clang-tidy and comp fixes (#79)
* clang-tidy and comp fixes

* oops
2025-02-03 20:49:19 +01:00
Honkazel
43e5139076
cmake: remove clang workaround (#78) 2025-02-02 19:43:23 +01:00
Asromo
dcadd3398a
core: fix memory leak by freeing rsvg handle (#77) 2025-01-29 20:17:35 +01:00
9c5dd1f7c8
flake.lock: update 2025-01-23 14:22:17 +02:00
Drewry Pope
3219b31128
tests: fix %d->%ld (#76) 2025-01-20 16:38:36 +00:00
69270ba8f0
flake.lock: update 2024-12-23 00:29:00 +02:00
3b3259e52a version: bump to 0.1.11 2024-12-21 20:06:59 +00:00
abc1c60eb5 meta: clamp nominalSize 2024-12-21 20:00:02 +00:00
Vaxry
84203d8126
core: add nominal size support (#73)
Adds nominal size support
2024-12-21 20:31:12 +01:00
f388aacd22
flake.lock: update
overlays: gcc13Stdenv -> gcc14Stdenv
2024-12-16 17:58:29 +02:00
Jan Beich
c18572a92e
util: add missing header for libc++ (#71)
hyprcursor-util/src/main.cpp:260:19: error: implicit instantiation of undefined template 'std::basic_ofstream<char>'
  260 |     std::ofstream manifest(out + "/manifest.hl", std::ios::trunc);
      |                   ^
/usr/include/c++/v1/__fwd/fstream.h:26:28: note: template is declared here
   26 | class _LIBCPP_TEMPLATE_VIS basic_ofstream;
      |                            ^
hyprcursor-util/src/main.cpp:292:41: error: implicit instantiation of undefined template 'std::basic_ifstream<char>'
  292 |         std::ifstream                   xconfig("/tmp/hyprcursor-util/" + xcursor.path().stem().string() + ".conf");
      |                                         ^
/usr/include/c++/v1/__fwd/fstream.h:24:28: note: template is declared here
   24 | class _LIBCPP_TEMPLATE_VIS basic_ifstream;
      |                            ^
hyprcursor-util/src/main.cpp:370:23: error: implicit instantiation of undefined template 'std::basic_ofstream<char>'
  370 |         std::ofstream meta(CURSORDIR + "/meta.hl", std::ios::trunc);
      |                       ^
/usr/include/c++/v1/__fwd/fstream.h:26:28: note: template is declared here
   26 | class _LIBCPP_TEMPLATE_VIS basic_ofstream;
      |                            ^
2024-12-14 14:05:23 +01:00
0264e69814 core: allow ;-separated values in hl format
fixes #67
2024-10-11 19:02:18 +01:00
70fb494aa6
CI: add test action 2024-10-08 23:47:22 +03:00
572cb49bb7
Nix: add hyprcursor-with-tests 2024-10-08 23:45:47 +03:00
53a23e4b41
CMake: allow installing tests 2024-10-08 23:44:50 +03:00
d60e1e01e6 tests: fixup C test override checking 2024-10-01 23:26:44 +01:00
5729b9733d core: fixup overridenBy reporting 2024-10-01 23:26:36 +01:00
Jan Beich
34efe230c2
core: add missing header for libc++ after 5a95d8512b (#66)
libhyprcursor/hyprcursor.cpp:23:27: error: implicit instantiation of undefined template 'std::basic_stringstream<char>'
   23 |         std::stringstream envXdgStream(envXdgData);
      |                           ^
/usr/include/c++/v1/__fwd/sstream.h:29:28: note: template is declared here
   29 | class _LIBCPP_TEMPLATE_VIS basic_stringstream;
      |                            ^
2024-10-01 10:08:26 +01:00
704cd7fed0 version: bump to 0.1.10 2024-09-30 22:25:02 +01:00
e8acfdb903 gitignore: add .cache 2024-09-30 18:29:41 +01:00
6b4131ee52 core: initialize C shape data fully 2024-09-30 18:12:34 +01:00
66648429bd core: avoid uninitialized overriddenBy of raw shape data
ref #64
2024-09-30 18:10:29 +01:00
Pascal Lasnier
b98726e431
docs: Correction in hotspot coordinates documentation (#63) 2024-09-28 15:13:23 +01:00
Jacob Birkett
912d56025f nix: pkg: add missing dep xcur2png 2024-08-02 21:24:31 +03:00
Libadoxon
5a95d8512b
lib: Use XDG_DATA_DIRS to query themes (#58)
* lib: use XDG_DATA_DIRS to search themes

* lib: fix some stylistic errors

* lib: more stylistic errors fixed
2024-07-30 22:04:10 +02:00
4493a972b4
flake.lock: update 2024-07-18 22:19:31 +03:00
04cb4df80a
CMake: fmt 2024-07-18 22:18:46 +03:00
4efcbd36a2
CMake, Nix: add VERSION file 2024-07-18 22:18:35 +03:00
Ikalco
a5c0d57325
core: only alloc as much as needed when reading in cursor images (#51) 2024-07-04 17:59:59 +02:00
Vaxry
66d5b46ff9
README: add standards link 2024-06-15 13:24:35 +02:00
dd3a853c82 README: add toml++ to dep list 2024-06-14 14:32:02 +02:00
c50b2f0f1d ci: fix missing dep 2024-06-14 14:31:45 +02:00
d5f4a6c708 docs: mention timeouts to be > 0 2024-06-13 12:11:39 +02:00
Visual-Dawg
9e27a2c2ce
README: add wiki (#45) 2024-05-31 21:55:20 +03:00
57298fc4f1 cmake: bump ver to 0.1.9 2024-05-24 20:46:51 +02:00
Ikalco
27ca640abe
core/API: add option to not use default fallbacks (env and first available) (#43)
* add option to not use default fallbacks (env and first available)
2024-05-21 23:45:11 +02:00
7c3aa03dff shapes: fix nearest size finding for png cursors
fixes #14
2024-05-15 17:50:17 +01:00
dfba774650 cmake: bump ver to 0.1.8 2024-05-15 17:50:17 +01:00
Daniel Horton
4a32d0cf25
README: Fixed getconf command in build instructions (#42)
getconf NPROCESSORS_CONF isn't a valid command. The correct command is getconf _NPROCESSORS_CONF.
2024-05-14 16:14:13 +01:00
Eric Leblond
cab4746180
zip: fix build for some distros (#37) (#38)
libzip 1.10.1 is not available on some distributions. This patch
introduces a workaround to fix the build instead of jumping to
1.10.1 release.
2024-04-20 12:23:33 +01:00
0a53b9957f
flake.lock: update 2024-04-15 23:54:23 +03:00
c38dcf160d util: fix printing overrides
ref #36
2024-04-15 14:21:28 +01:00
1f4c960cf1 cmake: require libzip>=1.10.1
fixes #35
2024-04-14 19:33:07 +01:00
SoSeDiK
92af141a01
util: Minor cleanup (#34)
Avoid double // in paths
Replace magic 0 value with libzip's ZIP_LENGTH_TO_END
Correct png to image (can be svg as well)
2024-04-12 11:55:59 +01:00
SoSeDiK
d41e8ac8d1
zip: Properly report error on zip_close (#33) 2024-04-12 11:54:39 +01:00
SoSeDiK
178717746d
lib: Add validation for cursor file names and propagate the error from parsing HL cursor (#32)
* Validate cursor file names

* Propagate errors from parsing HL cursor

* Validate cursor directory names
2024-04-12 01:01:33 +01:00
SoSeDiK
f6a6322a03
lib: Count cursor-less themes as invalid (#31) 2024-04-10 17:29:25 +01:00
6742e9d3e2 props: bump ver to 0.1.7 2024-04-09 16:30:19 +01:00
Jin Liu
e5e3d140a4
docs: specify that pixel coordinates of hotspot are rounded to the nearest (#28)
Since we now have a raw API, we shall specify how this is calculated, so
users of the raw API can do the same in their rendering code.
2024-04-09 02:29:44 +01:00
17ebb7fff0 lib: add missing header
ref #30
2024-04-08 23:31:11 +01:00
bd56398f19 lib: round hotspots in getShapes
ref #28
2024-04-08 18:23:14 +01:00
af4ce3953d lib: minor manifest reading fixes
fixes #29
2024-04-08 18:20:10 +01:00
65507c093f lib: fix missing overrides
ref #27
2024-04-08 11:46:28 +01:00
033416cedc util: pack meta with the correct extension
fixes #25
2024-04-08 10:23:45 +01:00
95cd9376e8 lib: fix missing / in path 2024-04-08 10:21:12 +01:00
23 changed files with 554 additions and 234 deletions

101
.clang-tidy Normal file
View file

@ -0,0 +1,101 @@
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,6 +18,10 @@ 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

39
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,39 @@
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,2 +1,3 @@
.vscode/ .vscode/
build/ build/
.cache/

View file

@ -1,12 +1,14 @@
cmake_minimum_required(VERSION 3.19) cmake_minimum_required(VERSION 3.19)
set(HYPRCURSOR_VERSION "0.1.6") file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
string(STRIP ${VER_RAW} HYPRCURSOR_VERSION)
add_compile_definitions(HYPRCURSOR_VERSION="${HYPRCURSOR_VERSION}") add_compile_definitions(HYPRCURSOR_VERSION="${HYPRCURSOR_VERSION}")
project(hyprcursor project(
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)
@ -18,9 +20,27 @@ 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(deps REQUIRED IMPORTED_TARGET hyprlang>=0.4.2 libzip cairo librsvg-2.0 tomlplusplus) pkg_check_modules(
deps
REQUIRED
IMPORTED_TARGET
hyprlang>=0.4.2
libzip
cairo
librsvg-2.0
tomlplusplus)
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Configuring hyprcursor in Debug") message(STATUS "Configuring hyprcursor in Debug")
@ -30,36 +50,43 @@ else()
message(STATUS "Configuring hyprcursor in Release") message(STATUS "Configuring hyprcursor in Release")
endif() endif()
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "libhyprcursor/*.cpp" "include/hyprcursor/hyprcursor.hpp" "include/hyprcursor/hyprcursor.h" "include/hyprcursor/shared.h") file(
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( hyprcursor target_include_directories(
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(GLOB_RECURSE UTILSRCFILES CONFIGURE_DEPENDS "hyprcursor-util/src/*.cpp" "include/hyprcursor/hyprcursor.hpp" "include/hyprcursor/hyprcursor.h" "include/hyprcursor/shared.h") 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}) add_executable(hyprcursor-util ${UTILSRCFILES})
target_include_directories(hyprcursor-util target_include_directories(
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
@ -67,21 +94,37 @@ 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(NAME "Test libhyprcursor in C++ (full rendering)" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test1) add_test(
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(NAME "Test libhyprcursor in C++ (only metadata)" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test2) add_test(
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(NAME "Test libhyprcursor in C" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test_c) add_test(
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 DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) install(FILES ${CMAKE_BINARY_DIR}/hyprcursor.pc
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,6 +16,11 @@ 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
@ -32,20 +37,6 @@ 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:
@ -53,11 +44,12 @@ Util:
- 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:

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.1.13

View file

@ -49,13 +49,16 @@ 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 north. # this is in absolute coordinates. x+ is east, y+ is south.
# 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
@ -69,6 +72,7 @@ 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,17 +2,20 @@
"nodes": { "nodes": {
"hyprlang": { "hyprlang": {
"inputs": { "inputs": {
"hyprutils": "hyprutils",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
], ],
"systems": "systems" "systems": [
"systems"
]
}, },
"locked": { "locked": {
"lastModified": 1709914708, "lastModified": 1749145882,
"narHash": "sha256-bR4o3mynoTa1Wi4ZTjbnsZ6iqVcPGriXp56bZh5UFTk=", "narHash": "sha256-qr0KXeczF8Sma3Ae7+dR2NHhvG7YeLBJv19W4oMu6ZE=",
"owner": "hyprwm", "owner": "hyprwm",
"repo": "hyprlang", "repo": "hyprlang",
"rev": "a685493fdbeec01ca8ccdf1f3655c044a8ce2fe2", "rev": "1bfb84f54d50c7ae6558c794d3cfd5f6a7e6e676",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -21,13 +24,38 @@
"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": 1708475490, "lastModified": 1748929857,
"narHash": "sha256-g1v0TsWBQPX97ziznfJdWhgMyMGtoBFs102xSYO4syU=", "narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "0e74ca98a74bc7270d28838369593635a5db3260", "rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -41,7 +69,7 @@
"inputs": { "inputs": {
"hyprlang": "hyprlang", "hyprlang": "hyprlang",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"systems": "systems_2" "systems": "systems"
} }
}, },
"systems": { "systems": {
@ -58,21 +86,6 @@
"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; inherit (pkgsFor.${system}) hyprcursor hyprcursor-with-tests;
}); });
checks = eachSystem (system: self.packages.${system}); checks = eachSystem (system: self.packages.${system});

View file

@ -2,20 +2,26 @@
#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"
enum eOperation { #ifndef ZIP_LENGTH_TO_END
#define ZIP_LENGTH_TO_END -1
#endif
enum eOperation : uint8_t {
OPERATION_CREATE = 0, OPERATION_CREATE = 0,
OPERATION_EXTRACT = 1, OPERATION_EXTRACT = 1,
}; };
eHyprcursorResizeAlgo explicitResizeAlgo = HC_RESIZE_INVALID; static 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;
@ -87,7 +93,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('/') + 1) : out_) + "/theme_" + THEMENAME + "/"; std::string out = (out_.empty() ? path.substr(0, path.find_last_of('/')) : 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;
@ -98,6 +104,9 @@ 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>());
@ -110,9 +119,11 @@ 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{i.file, i.size, i.delayMs}); SHAPE->images.push_back(SCursorImage{.filename = i.file, .size = i.size, .delay = 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) {
@ -178,17 +189,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, 0); zip_source_t* meta = zip_source_file(zip, METADIR.c_str(), 0, ZIP_LENGTH_TO_END);
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, "meta.hl", meta, ZIP_FL_ENC_UTF_8) < 0) 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"; return "(2) failed to add meta " + METADIR + " to hlc";
meta = nullptr; meta = nullptr;
// add each cursor png // add each cursor image
for (auto& i : shape->images) { for (auto& i : shape->images) {
zip_source_t* image = zip_source_file(zip, (CURRENTCURSORSDIR + "/" + i.filename).c_str(), 0, 0); zip_source_t* image = zip_source_file(zip, (CURRENTCURSORSDIR + "/" + i.filename).c_str(), 0, ZIP_LENGTH_TO_END);
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)
@ -199,9 +210,8 @@ 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_error_t* ziperror = zip_get_error(zip);
zip_error_init_with_code(&ziperror, errp); return "Failed to write " + OUTPUTFILE + ": " + zip_error_strerror(ziperror);
return "Failed to write " + OUTPUTFILE + ": " + zip_error_strerror(&ziperror);
} }
std::cout << "Written " << OUTPUTFILE << "\n"; std::cout << "Written " << OUTPUTFILE << "\n";
@ -378,7 +388,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 (size_t i = 1; i < argc; ++i) { for (int 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 <stdlib.h> #include <cstdlib>
#include <string> #include <string>
#include "shared.h" #include "shared.h"
@ -47,6 +47,22 @@ 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.
@ -58,6 +74,8 @@ 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:
@ -66,6 +84,7 @@ 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();
/*! /*!
@ -95,7 +114,7 @@ namespace Hyprcursor {
SCursorShapeData data; SCursorShapeData data;
for (size_t i = 0; i < size; ++i) { for (int 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;
@ -172,6 +191,7 @@ namespace Hyprcursor {
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,6 +54,7 @@ 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,8 +28,7 @@ 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( std::ranges::replace_if(args, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0);
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())
@ -39,7 +38,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(std::string_view{s}.data())); m_vArgs.emplace_back(removeBeginEndSpacesTabs(s.data()));
} }
} }

View file

@ -2,10 +2,14 @@
#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"
@ -14,11 +18,25 @@
using namespace Hyprcursor; using namespace Hyprcursor;
// directories for lookup static std::vector<std::string> getSystemThemeDirs() {
constexpr const std::array<const char*, 1> systemThemeDirs = {"/usr/share/icons"}; const auto envXdgData = std::getenv("XDG_DATA_DIRS");
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) {
@ -40,7 +58,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) {
@ -79,7 +97,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;
@ -107,7 +125,7 @@ static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) {
return ""; return "";
} }
static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORLOGFUNC logfn) { static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORLOGFUNC logfn, bool allowDefaultFallback) {
const auto HOMEENV = getenv("HOME"); const auto HOMEENV = getenv("HOME");
if (!HOMEENV) if (!HOMEENV)
return ""; return "";
@ -133,7 +151,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 (name.empty()) { if (allowDefaultFallback && 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();
@ -141,12 +159,11 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL
continue; continue;
} }
if (!std::filesystem::exists(MANIFESTPATH))
continue;
CManifest manifest{MANIFESTPATH}; CManifest manifest{MANIFESTPATH};
if (!manifest.parse().has_value()) if (const auto R = manifest.parse(); R.has_value()) {
Debug::log(HC_LOG_ERR, logfn, "failed parsing Manifest of {}: {}", themeDir.path().string(), *R);
continue; continue;
}
const std::string NAME = manifest.parsedData.name; const std::string NAME = manifest.parsedData.name;
@ -159,7 +176,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;
@ -177,12 +194,11 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL
const auto MANIFESTPATH = themeDir.path().string() + "/manifest"; const auto MANIFESTPATH = themeDir.path().string() + "/manifest";
if (std::filesystem::exists(MANIFESTPATH + ".hl") || std::filesystem::exists(MANIFESTPATH + ".toml"))
continue;
CManifest manifest{MANIFESTPATH}; CManifest manifest{MANIFESTPATH};
if (!manifest.parse().has_value()) if (const auto R = manifest.parse(); R.has_value()) {
Debug::log(HC_LOG_ERR, logfn, "failed parsing Manifest of {}: {}", themeDir.path().string(), *R);
continue; continue;
}
const std::string NAME = manifest.parsedData.name; const std::string NAME = manifest.parsedData.name;
@ -194,33 +210,40 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL
} }
} }
if (!name.empty()) { // try without name if (allowDefaultFallback && !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); return getFullPathForThemeName("", logfn, allowDefaultFallback);
} }
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) { CHyprcursorManager::CHyprcursorManager(const char* themeName_, PHYPRCURSORLOGFUNC fn) : logFn(fn) {
logFn = fn; init(themeName_);
}
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 (themeName.empty()) { if (allowDefaultFallback && 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 (themeName.empty()) { if (allowDefaultFallback && 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);
@ -235,7 +258,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); impl->themeFullDir = getFullPathForThemeName(themeName, logFn, allowDefaultFallback);
if (impl->themeFullDir.empty()) if (impl->themeFullDir.empty())
return; return;
@ -249,11 +272,15 @@ 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() {
if (impl)
delete impl; delete impl;
} }
@ -276,13 +303,15 @@ 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 != info.size) if (image->side != PIXELSIDE)
continue; continue;
// found size // found size
@ -302,7 +331,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 - info.size)) > leader) if (std::abs((int)(image->side - PIXELSIDE)) > std::abs((int)(leader - PIXELSIDE)))
continue; continue;
leader = image->side; leader = image->side;
@ -337,8 +366,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 = hotX * data[i]->size; data[i]->hotspotX = std::round(hotX * (float)data[i]->size);
data[i]->hotspotY = hotY * data[i]->size; data[i]->hotspotY = std::round(hotY * (float)data[i]->size);
} }
outSize = resultingImages.size(); outSize = resultingImages.size();
@ -358,14 +387,25 @@ 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(shape->overrides.begin(), shape->overrides.end(), SHAPE); IT != shape->overrides.end()) { if (const auto IT = std::find_if(shape->overrides.begin(), shape->overrides.end(), [&](const auto& e) { return e == SHAPE && SHAPE != shape->directory; });
data->overridenBy = strdup(IT->c_str()); IT != shape->overrides.end()) {
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;
@ -379,6 +419,7 @@ SCursorRawShapeDataC* CHyprcursorManager::getRawShapeDataC(const char* shape_) {
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;
} }
@ -409,8 +450,10 @@ 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 != info.size) if (image->side != IDEALSIDE)
continue; continue;
sizeFound = true; sizeFound = true;
@ -424,7 +467,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 < info.size) if (image->side < IDEALSIDE)
continue; continue;
if (image->side > leaderVal) if (image->side > leaderVal)
@ -436,7 +479,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 - info.size)) > leaderVal) if (std::abs((int)(image->side - IDEALSIDE)) > leaderVal)
continue; continue;
leaderVal = image->side; leaderVal = image->side;
@ -453,12 +496,16 @@ 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 = info.size; newImage->side = PIXELSIDE;
newImage->artificialData = new char[info.size * info.size * 4]; newImage->artificialData = new char[static_cast<unsigned long>(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->cairoSurface = cairo_image_surface_create_for_data((unsigned char*)newImage->artificialData, CAIRO_FORMAT_ARGB32, PIXELSIDE, PIXELSIDE, PIXELSIDE * 4);
newImage->delay = f->delay; newImage->delay = f->delay;
const auto PCAIRO = cairo_create(newImage->cairoSurface); const auto PCAIRO = cairo_create(newImage->cairoSurface);
@ -472,12 +519,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 = info.size / (float)f->side; const float scale = PIXELSIDE / (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, info.size, info.size); cairo_rectangle(PCAIRO, 0, 0, PIXELSIDE, PIXELSIDE);
cairo_fill(PCAIRO); cairo_fill(PCAIRO);
cairo_surface_flush(newImage->cairoSurface); cairo_surface_flush(newImage->cairoSurface);
@ -490,12 +537,16 @@ 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 = info.size; newImage->side = PIXELSIDE;
newImage->artificialData = new char[info.size * info.size * 4]; newImage->artificialData = new char[static_cast<unsigned long>(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->cairoSurface = cairo_image_surface_create_for_data((unsigned char*)newImage->artificialData, CAIRO_FORMAT_ARGB32, PIXELSIDE, PIXELSIDE, PIXELSIDE * 4);
newImage->delay = f->delay; newImage->delay = f->delay;
const auto PCAIRO = cairo_create(newImage->cairoSurface); const auto PCAIRO = cairo_create(newImage->cairoSurface);
@ -513,16 +564,18 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
return false; return false;
} }
RsvgRectangle rect = {0, 0, (double)info.size, (double)info.size}; RsvgRectangle rect = {0, 0, (double)PIXELSIDE, (double)PIXELSIDE};
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");
@ -543,7 +596,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 == info.size) if (isArtificial && e->side == std::round(info.size / shape->nominalSize))
return true; return true;
// clean invalid non-svg rasters // clean invalid non-svg rasters
@ -619,18 +672,22 @@ 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_file_t* meta_file = zip_fopen(zip, "meta.hl", ZIP_FL_UNCHANGED); zip_int64_t index = zip_name_locate(zip, "meta.hl", ZIP_FL_ENC_GUESS);
bool metaIsHL = true; bool metaIsHL = true;
if (!meta_file) {
meta_file = zip_fopen(zip, "meta.toml", ZIP_FL_UNCHANGED); if (index == -1) {
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";
} }
char* buffer = new char[1024 * 1024]; /* 1MB should be more than enough */ if (index == -1)
return "cursor" + cursor.path().string() + "failed to load meta";
int readBytes = zip_fread(meta_file, buffer, 1024 * 1024 - 1); zip_file_t* meta_file = zip_fopen_index(zip, index, ZIP_FL_UNCHANGED);
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);
@ -650,9 +707,14 @@ 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{i.file, i.size, i.delayMs}); SHAPE->images.push_back(SCursorImage{.filename = i.file, .size = i.size, .delay = 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"))
@ -677,14 +739,23 @@ 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;
// read from zip index = zip_name_locate(zip, i.filename.c_str(), ZIP_FL_ENC_GUESS);
zip_file_t* image_file = zip_fopen(zip, i.filename.c_str(), ZIP_FL_UNCHANGED); if (index == -1)
if (!image_file)
return "cursor" + cursor.path().string() + "failed to load image_file"; return "cursor" + cursor.path().string() + "failed to load image_file";
IMAGE->data = new char[1024 * 1024]; /* 1MB should be more than enough, again. This probably should be in the spec. */ // read from zip
zip_file_t* image_file = zip_fopen_index(zip, index, ZIP_FL_UNCHANGED);
zip_stat_index(zip, index, ZIP_FL_UNCHANGED, &sb);
IMAGE->dataLen = zip_fread(image_file, IMAGE->data, 1024 * 1024 - 1); if (sb.valid & ZIP_STAT_SIZE) {
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);
@ -710,6 +781,7 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
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 (size_t i = 0; i < size; ++i) { for (int 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;
return MGR->cursorSurfaceStyleDone(info); 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; float hotspotX = 0, hotspotY = 0, nominalSize = 1.F;
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,12 +3,14 @@
#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"
CMeta* currentMeta = nullptr; static CMeta* currentMeta = nullptr;
CMeta::CMeta(const std::string& rawdata_, bool hyprlang_ /* false for toml */, bool dataIsPath) : rawdata(rawdata_), hyprlang(hyprlang_), dataPath(dataIsPath) { CMeta::CMeta(const std::string& rawdata_, bool hyprlang_ /* false for toml */, bool dataIsPath) : dataPath(dataIsPath), hyprlang(hyprlang_), rawdata(rawdata_) {
if (!dataIsPath) if (!dataIsPath)
return; return;
@ -70,20 +72,23 @@ 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;
if (!VALUE.contains(",")) { CVarList sizes(VALUE, 0, ';');
for (const auto& sizeStr : sizes) {
if (!sizeStr.contains(",")) {
result.setError("Invalid define_size"); result.setError("Invalid define_size");
return result; return result;
} }
auto LHS = removeBeginEndSpacesTabs(VALUE.substr(0, VALUE.find_first_of(","))); auto LHS = removeBeginEndSpacesTabs(sizeStr.substr(0, sizeStr.find_first_of(",")));
auto RHS = removeBeginEndSpacesTabs(VALUE.substr(VALUE.find_first_of(",") + 1)); auto RHS = removeBeginEndSpacesTabs(sizeStr.substr(sizeStr.find_first_of(",") + 1));
auto DELAY = 0; auto DELAY = 0;
CMeta::SDefinedSize size; CMeta::SDefinedSize size;
if (RHS.contains(",")) { if (RHS.contains(",")) {
const auto LL = removeBeginEndSpacesTabs(RHS.substr(0, RHS.find(","))); const auto LL = removeBeginEndSpacesTabs(RHS.substr(0, RHS.find(',')));
const auto RR = removeBeginEndSpacesTabs(RHS.substr(RHS.find(",") + 1)); const auto RR = removeBeginEndSpacesTabs(RHS.substr(RHS.find(',') + 1));
try { try {
size.delayMs = std::stoull(RR); size.delayMs = std::stoull(RR);
@ -95,6 +100,11 @@ static Hyprlang::CParseResult parseDefineSize(const char* C, const char* V) {
RHS = LL; 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; size.file = RHS;
if (!size.file.ends_with(".svg")) { if (!size.file.ends_with(".svg")) {
@ -108,6 +118,7 @@ static Hyprlang::CParseResult parseDefineSize(const char* C, const char* V) {
size.size = 0; size.size = 0;
currentMeta->parsedData.definedSizes.push_back(size); currentMeta->parsedData.definedSizes.push_back(size);
}
return result; return result;
} }
@ -116,7 +127,11 @@ 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;
currentMeta->parsedData.overrides.push_back(VALUE); CVarList overrides(VALUE, 0, ';');
for (const auto& o : overrides) {
currentMeta->parsedData.overrides.push_back(o);
}
return result; return result;
} }
@ -128,15 +143,19 @@ 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();
meta->parse(); const auto RESULT = 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 {};
@ -148,6 +167,7 @@ std::optional<std::string> CMeta::parseTOML() {
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; float hotspotX = 0, hotspotY = 0, nominalSize = 1.F;
std::vector<std::string> overrides; std::vector<std::string> overrides;
std::vector<SDefinedSize> definedSizes; std::vector<SDefinedSize> definedSizes;
} parsedData; } parsedData;

View file

@ -7,6 +7,7 @@
hyprlang, hyprlang,
librsvg, librsvg,
libzip, libzip,
xcur2png,
tomlplusplus, tomlplusplus,
version ? "git", version ? "git",
}: }:
@ -15,11 +16,6 @@ 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
@ -30,6 +26,7 @@ stdenv.mkDerivation {
hyprlang hyprlang
librsvg librsvg
libzip libzip
xcur2png
tomlplusplus tomlplusplus
]; ];

View file

@ -1,13 +0,0 @@
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,6 +7,7 @@
(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;
@ -14,10 +15,14 @@ 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.gcc13Stdenv; stdenv = prev.gcc15Stdenv;
version = "0.pre" + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty"); version = version + "+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,15 +27,26 @@ 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 || shapeData->len <= 0) { if (!shapeData) {
printf("failed querying left_ptr\n"); printf("failed querying left_ptr\n");
return 1; return 1;
} }
printf("left_ptr images: %d\n", shapeData->len); if (shapeData->overridenBy) {
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: %d\n", shapeData->images[i].len); printf("left_ptr image size: %ld\n", shapeData->images[i].len);
} }
hyprcursor_raw_shape_data_free(shapeData); hyprcursor_raw_shape_data_free(shapeData);