Compare commits

...

46 commits
v0.1.8 ... 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
23 changed files with 506 additions and 200 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 -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
run: |
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/
build/
.cache/

View file

@ -1,12 +1,14 @@
cmake_minimum_required(VERSION 3.19)
set(HYPRCURSOR_VERSION "0.1.7")
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
string(STRIP ${VER_RAW} HYPRCURSOR_VERSION)
add_compile_definitions(HYPRCURSOR_VERSION="${HYPRCURSOR_VERSION}")
project(hyprcursor
VERSION ${HYPRCURSOR_VERSION}
DESCRIPTION "A library and toolkit for the Hyprland cursor format"
)
project(
hyprcursor
VERSION ${HYPRCURSOR_VERSION}
DESCRIPTION "A library and toolkit for the Hyprland cursor format")
include(CTest)
include(GNUInstallDirs)
@ -18,48 +20,73 @@ set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR})
configure_file(hyprcursor.pc.in hyprcursor.pc @ONLY)
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)
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)
message(STATUS "Configuring hyprcursor in Debug")
add_compile_definitions(HYPRLAND_DEBUG)
message(STATUS "Configuring hyprcursor in Debug")
add_compile_definitions(HYPRLAND_DEBUG)
else()
add_compile_options(-O3)
message(STATUS "Configuring hyprcursor in Release")
add_compile_options(-O3)
message(STATUS "Configuring hyprcursor in Release")
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})
target_include_directories( hyprcursor
PUBLIC "./include"
PRIVATE "./libhyprcursor"
)
set_target_properties(hyprcursor PROPERTIES
VERSION ${hyprcursor_VERSION}
SOVERSION 0
PUBLIC_HEADER include/hyprcursor/hyprcursor.hpp include/hyprcursor/hyprcursor.h include/hyprcursor/shared.h
)
target_include_directories(
hyprcursor
PUBLIC "./include"
PRIVATE "./libhyprcursor")
set_target_properties(
hyprcursor
PROPERTIES VERSION ${hyprcursor_VERSION}
SOVERSION 0
PUBLIC_HEADER include/hyprcursor/hyprcursor.hpp
include/hyprcursor/hyprcursor.h include/hyprcursor/shared.h)
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
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})
target_include_directories(hyprcursor-util
PUBLIC "./include"
PRIVATE "./libhyprcursor" "./hyprcursor-util/src"
)
target_include_directories(
hyprcursor-util
PUBLIC "./include"
PRIVATE "./libhyprcursor" "./hyprcursor-util/src")
target_link_libraries(hyprcursor-util PkgConfig::deps hyprcursor)
# tests
@ -67,21 +94,37 @@ add_custom_target(tests)
add_executable(hyprcursor_test1 "tests/full_rendering.cpp")
target_link_libraries(hyprcursor_test1 PRIVATE hyprcursor)
add_test(NAME "Test libhyprcursor in C++ (full rendering)" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test1)
add_test(
NAME "Test libhyprcursor in C++ (full rendering)"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprcursor_test1)
add_dependencies(tests hyprcursor_test1)
add_executable(hyprcursor_test2 "tests/only_metadata.cpp")
target_link_libraries(hyprcursor_test2 PRIVATE hyprcursor)
add_test(NAME "Test libhyprcursor in C++ (only metadata)" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test2)
add_test(
NAME "Test libhyprcursor in C++ (only metadata)"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprcursor_test2)
add_dependencies(tests hyprcursor_test2)
add_executable(hyprcursor_test_c "tests/c_test.c")
target_link_libraries(hyprcursor_test_c PRIVATE hyprcursor)
add_test(NAME "Test libhyprcursor in C" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprcursor_test_c)
add_test(
NAME "Test libhyprcursor in C"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprcursor_test_c)
add_dependencies(tests hyprcursor_test_c)
# Installation
install(TARGETS hyprcursor)
install(TARGETS hyprcursor-util)
install(DIRECTORY "include/hyprcursor" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES ${CMAKE_BINARY_DIR}/hyprcursor.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
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
- 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
### hyprcursor-util
@ -32,20 +37,6 @@ It provides C and C++ bindings.
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
### Deps:
@ -53,11 +44,12 @@ Util:
- cairo
- libzip
- librsvg
- tomlplusplus
### Build
```sh
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:

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.1.13

View file

@ -49,7 +49,7 @@ Each cursor image is a separate directory. In it, multiple size variations can b
resize_algorithm = bilinear
# "hotspot" is where in your cursor the actual "click point" should be.
# this is in absolute coordinates. x+ is east, y+ is north.
# 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
@ -58,6 +58,7 @@ hotspot_y = 0.0 # this goes 0 - 1
# Define what cursor images this one should override.
# 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.
# 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 = default
@ -71,6 +72,7 @@ define_size = 32, image32.png
# define_size = 64, anim2.png, 500
# define_size = 64, anim3.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.
@ -101,4 +103,4 @@ define_override = 'shape1;shape2;shape3'
define_size = '24,image1.png,200;24,image2.png,200;32,image3.png,200'
```
You can put spaces around the semicolons if you prefer to.
You can put spaces around the semicolons if you prefer to.

38
flake.lock generated
View file

@ -2,6 +2,7 @@
"nodes": {
"hyprlang": {
"inputs": {
"hyprutils": "hyprutils",
"nixpkgs": [
"nixpkgs"
],
@ -10,11 +11,11 @@
]
},
"locked": {
"lastModified": 1713121246,
"narHash": "sha256-502X0Q0fhN6tJK7iEUA8CghONKSatW/Mqj4Wappd++0=",
"lastModified": 1749145882,
"narHash": "sha256-qr0KXeczF8Sma3Ae7+dR2NHhvG7YeLBJv19W4oMu6ZE=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "78fcaa27ae9e1d782faa3ff06c8ea55ddce63706",
"rev": "1bfb84f54d50c7ae6558c794d3cfd5f6a7e6e676",
"type": "github"
},
"original": {
@ -23,13 +24,38 @@
"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": {
"locked": {
"lastModified": 1712963716,
"narHash": "sha256-WKm9CvgCldeIVvRz87iOMi8CFVB1apJlkUT4GGvA0iM=",
"lastModified": 1748929857,
"narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "cfd6b5fc90b15709b780a5a1619695a88505a176",
"rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4",
"type": "github"
},
"original": {

View file

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

View file

@ -2,6 +2,7 @@
#include <zip.h>
#include <optional>
#include <filesystem>
#include <fstream>
#include <array>
#include <format>
#include <algorithm>
@ -15,12 +16,12 @@
#define ZIP_LENGTH_TO_END -1
#endif
enum eOperation {
enum eOperation : uint8_t {
OPERATION_CREATE = 0,
OPERATION_EXTRACT = 1,
};
eHyprcursorResizeAlgo explicitResizeAlgo = HC_RESIZE_INVALID;
static eHyprcursorResizeAlgo explicitResizeAlgo = HC_RESIZE_INVALID;
struct XCursorConfigEntry {
int size = 0, hotspotX = 0, hotspotY = 0, delay = 0;
@ -118,7 +119,7 @@ static std::optional<std::string> createCursorThemeFromPath(const std::string& p
return "couldn't parse meta: " + *PARSERESULT2;
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;
@ -387,7 +388,7 @@ int main(int argc, char** argv, char** envp) {
eOperation op = OPERATION_CREATE;
std::string path = "", out = "";
for (size_t i = 1; i < argc; ++i) {
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "-v" || arg == "--version") {

View file

@ -1,7 +1,7 @@
#pragma once
#include <vector>
#include <stdlib.h>
#include <cstdlib>
#include <string>
#include "shared.h"
@ -47,6 +47,22 @@ namespace Hyprcursor {
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.
@ -68,6 +84,7 @@ namespace Hyprcursor {
\since 0.1.6
*/
CHyprcursorManager(const char* themeName, PHYPRCURSORLOGFUNC fn);
CHyprcursorManager(const char* themeName, SManagerOptions options);
~CHyprcursorManager();
/*!
@ -97,7 +114,7 @@ namespace Hyprcursor {
SCursorShapeData data;
for (size_t i = 0; i < size; ++i) {
for (int i = 0; i < size; ++i) {
SCursorImageData image;
image.delay = images[i]->delay;
image.size = images[i]->size;
@ -172,11 +189,12 @@ namespace Hyprcursor {
private:
void init(const char* themeName_);
CHyprcursorImplementation* impl = nullptr;
bool finalizedAndValid = false;
PHYPRCURSORLOGFUNC logFn = nullptr;
CHyprcursorImplementation* impl = nullptr;
bool finalizedAndValid = false;
bool allowDefaultFallback = true;
PHYPRCURSORLOGFUNC logFn = nullptr;
friend class CHyprcursorImplementation;
};
}
}

View file

@ -54,6 +54,7 @@ struct SCursorRawShapeDataC {
char* overridenBy;
enum eHyprcursorResizeAlgo resizeAlgo;
enum eHyprcursorDataType type;
float nominalSize;
};
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};
size_t idx = 0;
size_t pos = 0;
std::ranges::replace_if(
args, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0);
std::ranges::replace_if(args, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0);
for (const auto& s : args | std::views::split(0)) {
if (removeEmpty && s.empty())
@ -39,7 +38,7 @@ CVarList::CVarList(const std::string& in, const size_t lastArgNo, const char del
break;
}
pos += s.size() + 1;
m_vArgs.emplace_back(removeBeginEndSpacesTabs(std::string_view{s}.data()));
m_vArgs.emplace_back(removeBeginEndSpacesTabs(s.data()));
}
}
@ -52,4 +51,4 @@ std::string CVarList::join(const std::string& joiner, size_t from, size_t to) co
}
return rolling;
}
}

View file

@ -2,6 +2,9 @@
#include "internalSharedTypes.hpp"
#include "internalDefines.hpp"
#include <array>
#include <cstddef>
#include <sstream>
#include <cstdio>
#include <filesystem>
#include <zip.h>
#include <cstring>
@ -15,11 +18,25 @@
using namespace Hyprcursor;
// directories for lookup
constexpr const std::array<const char*, 1> systemThemeDirs = {"/usr/share/icons"};
static std::vector<std::string> getSystemThemeDirs() {
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"};
//
static std::string themeNameFromEnv(PHYPRCURSORLOGFUNC logfn) {
const auto ENV = getenv("HYPRCURSOR_THEME");
if (!ENV) {
@ -80,7 +97,7 @@ static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) {
}
for (auto& dir : systemThemeDirs) {
const auto FULLPATH = dir;
const auto& FULLPATH = dir;
if (!pathAccessible(FULLPATH)) {
Debug::log(HC_LOG_TRACE, logfn, "Skipping path {} because it's inaccessible.", FULLPATH);
continue;
@ -108,7 +125,7 @@ static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) {
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");
if (!HOMEENV)
return "";
@ -134,7 +151,7 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL
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")) {
Debug::log(HC_LOG_INFO, logfn, "getFullPathForThemeName: found {}", themeDir.path().string());
return std::filesystem::canonical(themeDir.path()).string();
@ -159,7 +176,7 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL
}
for (auto& dir : systemThemeDirs) {
const auto FULLPATH = dir;
const auto& FULLPATH = dir;
if (!pathAccessible(FULLPATH)) {
Debug::log(HC_LOG_TRACE, logfn, "Skipping path {} because it's inaccessible.", FULLPATH);
continue;
@ -193,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);
return getFullPathForThemeName("", logfn);
return getFullPathForThemeName("", logfn, allowDefaultFallback);
}
return "";
}
SManagerOptions::SManagerOptions() : logFn(nullptr), allowDefaultFallback(true) {
;
}
CHyprcursorManager::CHyprcursorManager(const char* themeName_) {
init(themeName_);
}
CHyprcursorManager::CHyprcursorManager(const char* themeName_, PHYPRCURSORLOGFUNC fn) {
logFn = fn;
CHyprcursorManager::CHyprcursorManager(const char* themeName_, PHYPRCURSORLOGFUNC fn) : logFn(fn) {
init(themeName_);
}
CHyprcursorManager::CHyprcursorManager(const char* themeName_, SManagerOptions options) : allowDefaultFallback(options.allowDefaultFallback), logFn(options.logFn) {
init(themeName_);
}
void CHyprcursorManager::init(const char* themeName_) {
std::string themeName = themeName_ ? themeName_ : "";
if (themeName.empty()) {
if (allowDefaultFallback && themeName.empty()) {
// try reading from env
Debug::log(HC_LOG_INFO, logFn, "CHyprcursorManager: attempting to find theme from env");
themeName = themeNameFromEnv(logFn);
}
if (themeName.empty()) {
if (allowDefaultFallback && themeName.empty()) {
// try finding first, in the hierarchy
Debug::log(HC_LOG_INFO, logFn, "CHyprcursorManager: attempting to find any theme");
themeName = getFirstTheme(logFn);
@ -234,7 +258,7 @@ void CHyprcursorManager::init(const char* themeName_) {
// initialize theme
impl = new CHyprcursorImplementation(this, logFn);
impl->themeName = themeName;
impl->themeFullDir = getFullPathForThemeName(themeName, logFn);
impl->themeFullDir = getFullPathForThemeName(themeName, logFn, allowDefaultFallback);
if (impl->themeFullDir.empty())
return;
@ -257,8 +281,7 @@ void CHyprcursorManager::init(const char* themeName_) {
}
CHyprcursorManager::~CHyprcursorManager() {
if (impl)
delete impl;
delete impl;
}
bool CHyprcursorManager::valid() {
@ -280,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())
continue;
const int PIXELSIDE = std::round(info.size / shape->nominalSize);
hotX = shape->hotspotX;
hotY = shape->hotspotY;
// matched :)
bool foundAny = false;
for (auto& image : impl->loadedShapes[shape.get()].images) {
if (image->side != info.size)
if (image->side != PIXELSIDE)
continue;
// found size
@ -306,7 +331,7 @@ SCursorImageData** CHyprcursorManager::getShapesC(int& outSize, const char* shap
// find nearest
int leader = 13371337;
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;
leader = image->side;
@ -362,14 +387,25 @@ SCursorRawShapeDataC* CHyprcursorManager::getRawShapeDataC(const char* shape_) {
SCursorRawShapeDataC* data = new SCursorRawShapeDataC;
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) {
// if it's overridden just return the override
if (const auto IT = std::find(shape->overrides.begin(), shape->overrides.end(), SHAPE); IT != shape->overrides.end()) {
data->overridenBy = strdup(IT->c_str());
if (const auto IT = std::find_if(shape->overrides.begin(), shape->overrides.end(), [&](const auto& e) { return e == SHAPE && SHAPE != shape->directory; });
IT != shape->overrides.end()) {
data->overridenBy = strdup(shape->directory.c_str());
return data;
}
}
for (auto& shape : impl->theme.shapes) {
if (shape->directory != SHAPE)
continue;
@ -381,9 +417,10 @@ SCursorRawShapeDataC* CHyprcursorManager::getRawShapeDataC(const char* shape_) {
resultingImages.push_back(i.get());
}
data->hotspotX = shape->hotspotX;
data->hotspotY = shape->hotspotY;
data->type = shape->shapeType == SHAPE_PNG ? HC_DATA_PNG : HC_DATA_SVG;
data->hotspotX = shape->hotspotX;
data->hotspotY = shape->hotspotY;
data->nominalSize = shape->nominalSize;
data->type = shape->shapeType == SHAPE_PNG ? HC_DATA_PNG : HC_DATA_SVG;
break;
}
@ -413,8 +450,10 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
bool sizeFound = false;
if (shape->shapeType == SHAPE_PNG) {
const int IDEALSIDE = std::round(info.size / shape->nominalSize);
for (auto& image : impl->loadedShapes[shape.get()].images) {
if (image->side != info.size)
if (image->side != IDEALSIDE)
continue;
sizeFound = true;
@ -428,7 +467,7 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
SLoadedCursorImage* leader = nullptr;
int leaderVal = 1000000;
for (auto& image : impl->loadedShapes[shape.get()].images) {
if (image->side < info.size)
if (image->side < IDEALSIDE)
continue;
if (image->side > leaderVal)
@ -440,7 +479,7 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
if (!leader) {
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;
leaderVal = image->side;
@ -457,12 +496,16 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
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) {
auto& newImage = impl->loadedShapes[shape.get()].images.emplace_back(std::make_unique<SLoadedCursorImage>());
newImage->artificial = true;
newImage->side = info.size;
newImage->artificialData = new char[info.size * info.size * 4];
newImage->cairoSurface = cairo_image_surface_create_for_data((unsigned char*)newImage->artificialData, CAIRO_FORMAT_ARGB32, info.size, info.size, info.size * 4);
newImage->side = PIXELSIDE;
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, PIXELSIDE, PIXELSIDE, PIXELSIDE * 4);
newImage->delay = f->delay;
const auto PCAIRO = cairo_create(newImage->cairoSurface);
@ -476,12 +519,12 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
const auto PTN = cairo_pattern_create_for_surface(f->cairoSurface);
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_pattern_set_filter(PTN, shape->resizeAlgo == HC_RESIZE_BILINEAR ? CAIRO_FILTER_GOOD : CAIRO_FILTER_NEAREST);
cairo_set_source(PCAIRO, PTN);
cairo_rectangle(PCAIRO, 0, 0, info.size, info.size);
cairo_rectangle(PCAIRO, 0, 0, PIXELSIDE, PIXELSIDE);
cairo_fill(PCAIRO);
cairo_surface_flush(newImage->cairoSurface);
@ -494,12 +537,16 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
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) {
auto& newImage = impl->loadedShapes[shape.get()].images.emplace_back(std::make_unique<SLoadedCursorImage>());
newImage->artificial = true;
newImage->side = info.size;
newImage->artificialData = new char[info.size * info.size * 4];
newImage->cairoSurface = cairo_image_surface_create_for_data((unsigned char*)newImage->artificialData, CAIRO_FORMAT_ARGB32, info.size, info.size, info.size * 4);
newImage->side = PIXELSIDE;
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, PIXELSIDE, PIXELSIDE, PIXELSIDE * 4);
newImage->delay = f->delay;
const auto PCAIRO = cairo_create(newImage->cairoSurface);
@ -517,16 +564,18 @@ bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) {
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)) {
Debug::log(HC_LOG_ERR, logFn, "Failed rendering svg: {}", error->message);
g_object_unref(handle);
return false;
}
// done
cairo_surface_flush(newImage->cairoSurface);
cairo_destroy(PCAIRO);
g_object_unref(handle);
}
} else {
Debug::log(HC_LOG_ERR, logFn, "Invalid shapetype in loadThemeStyle");
@ -547,7 +596,7 @@ void CHyprcursorManager::cursorSurfaceStyleDone(const SCursorStyleInfo& info) {
const bool isArtificial = e->artificial;
// clean artificial rasters made for this
if (isArtificial && e->side == info.size)
if (isArtificial && e->side == std::round(info.size / shape->nominalSize))
return true;
// clean invalid non-svg rasters
@ -623,18 +672,22 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
int errp = 0;
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);
bool metaIsHL = true;
if (!meta_file) {
meta_file = zip_fopen(zip, "meta.toml", ZIP_FL_UNCHANGED);
metaIsHL = false;
if (!meta_file)
return "cursor" + cursor.path().string() + "failed to load meta";
zip_int64_t index = zip_name_locate(zip, "meta.hl", ZIP_FL_ENC_GUESS);
bool metaIsHL = true;
if (index == -1) {
index = zip_name_locate(zip, "meta.toml", ZIP_FL_ENC_GUESS);
metaIsHL = false;
}
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);
@ -654,11 +707,14 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
return "cursor" + cursor.path().string() + "failed to parse meta: " + *METAPARSERESULT;
for (auto& i : meta.parsedData.definedSizes) {
SHAPE->images.push_back(SCursorImage{i.file, i.size, i.delayMs});
SHAPE->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) {
if (SHAPE->shapeType == SHAPE_INVALID) {
if (i.filename.ends_with(".svg"))
@ -683,14 +739,23 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
IMAGE->delay = i.delay;
IMAGE->isSVG = SHAPE->shapeType == SHAPE_SVG;
// read from zip
zip_file_t* image_file = zip_fopen(zip, i.filename.c_str(), ZIP_FL_UNCHANGED);
if (!image_file)
index = zip_name_locate(zip, i.filename.c_str(), ZIP_FL_ENC_GUESS);
if (index == -1)
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);
@ -701,7 +766,7 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
IMAGE->cairoSurface = cairo_image_surface_create_from_png_stream(::readPNG, IMAGE);
if (const auto STATUS = cairo_surface_status(IMAGE->cairoSurface); STATUS != CAIRO_STATUS_SUCCESS) {
delete[](char*) IMAGE->data;
delete[] (char*)IMAGE->data;
IMAGE->data = nullptr;
return "Failed reading cairoSurface, status " + std::to_string((int)STATUS);
}
@ -713,10 +778,11 @@ std::optional<std::string> CHyprcursorImplementation::loadTheme() {
if (SHAPE->images.empty())
return "meta invalid: no images for shape " + cursor.path().stem().string();
SHAPE->directory = cursor.path().stem().string();
SHAPE->hotspotX = meta.parsedData.hotspotX;
SHAPE->hotspotY = meta.parsedData.hotspotY;
SHAPE->resizeAlgo = stringToAlgo(meta.parsedData.resizeAlgo);
SHAPE->directory = cursor.path().stem().string();
SHAPE->hotspotX = meta.parsedData.hotspotX;
SHAPE->hotspotY = meta.parsedData.hotspotY;
SHAPE->nominalSize = meta.parsedData.nominalSize;
SHAPE->resizeAlgo = stringToAlgo(meta.parsedData.resizeAlgo);
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) {
for (size_t i = 0; i < size; ++i) {
for (int i = 0; i < size; ++i) {
free(data[i]);
}
@ -49,7 +49,7 @@ void hyprcursor_style_done(hyprcursor_manager_t* manager, hyprcursor_cursor_styl
const auto MGR = (CHyprcursorManager*)manager;
SCursorStyleInfo info;
info.size = info_.size;
return MGR->cursorSurfaceStyleDone(info);
MGR->cursorSurfaceStyleDone(info);
}
void hyprcursor_register_logging_function(struct hyprcursor_manager_t* manager, PHYPRCURSORLOGFUNC fn) {
@ -71,4 +71,4 @@ CAPI void hyprcursor_raw_shape_data_free(hyprcursor_cursor_raw_shape_data* data)
delete[] data->images;
delete data;
}
}

View file

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

View file

@ -4,12 +4,13 @@
#include <toml++/toml.hpp>
#include <filesystem>
#include <regex>
#include <algorithm>
#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)
return;
@ -71,49 +72,53 @@ static Hyprlang::CParseResult parseDefineSize(const char* C, const char* V) {
Hyprlang::CParseResult result;
const std::string VALUE = V;
if (!VALUE.contains(",")) {
result.setError("Invalid define_size");
return result;
}
CVarList sizes(VALUE, 0, ';');
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());
for (const auto& sizeStr : sizes) {
if (!sizeStr.contains(",")) {
result.setError("Invalid define_size");
return result;
}
RHS = LL;
}
auto LHS = removeBeginEndSpacesTabs(sizeStr.substr(0, sizeStr.find_first_of(",")));
auto RHS = removeBeginEndSpacesTabs(sizeStr.substr(sizeStr.find_first_of(",") + 1));
auto DELAY = 0;
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;
}
CMeta::SDefinedSize size;
size.file = RHS;
if (RHS.contains(",")) {
const auto LL = removeBeginEndSpacesTabs(RHS.substr(0, RHS.find(',')));
const auto RR = removeBeginEndSpacesTabs(RHS.substr(RHS.find(',') + 1));
if (!size.file.ends_with(".svg")) {
try {
size.size = std::stoull(LHS);
} catch (std::exception& e) {
result.setError(e.what());
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;
}
} else
size.size = 0;
currentMeta->parsedData.definedSizes.push_back(size);
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;
}
@ -122,7 +127,11 @@ static Hyprlang::CParseResult parseOverride(const char* C, const char* V) {
Hyprlang::CParseResult result;
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;
}
@ -134,6 +143,7 @@ std::optional<std::string> CMeta::parseHL() {
meta = std::make_unique<Hyprlang::CConfig>(rawdata.c_str(), Hyprlang::SConfigOptions{.pathIsStream = !dataPath});
meta->addConfigValue("hotspot_x", Hyprlang::FLOAT{0.F});
meta->addConfigValue("hotspot_y", Hyprlang::FLOAT{0.F});
meta->addConfigValue("nominal_size", Hyprlang::FLOAT{1.F});
meta->addConfigValue("resize_algorithm", Hyprlang::STRING{"nearest"});
meta->registerHandler(::parseDefineSize, "define_size", {.allowFlags = false});
meta->registerHandler(::parseOverride, "define_override", {.allowFlags = false});
@ -143,9 +153,10 @@ std::optional<std::string> CMeta::parseHL() {
return RESULT.getError();
} catch (const char* err) { return "failed parsing meta: " + std::string{err}; }
parsedData.hotspotX = std::any_cast<Hyprlang::FLOAT>(meta->getConfigValue("hotspot_x"));
parsedData.hotspotY = std::any_cast<Hyprlang::FLOAT>(meta->getConfigValue("hotspot_y"));
parsedData.resizeAlgo = std::any_cast<Hyprlang::STRING>(meta->getConfigValue("resize_algorithm"));
parsedData.hotspotX = std::any_cast<Hyprlang::FLOAT>(meta->getConfigValue("hotspot_x"));
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"));
return {};
}
@ -154,8 +165,9 @@ std::optional<std::string> CMeta::parseTOML() {
try {
auto MANIFEST = dataPath ? toml::parse_file(rawdata) : toml::parse(rawdata);
parsedData.hotspotX = MANIFEST["General"]["hotspot_x"].value_or(0.f);
parsedData.hotspotY = MANIFEST["General"]["hotspot_y"].value_or(0.f);
parsedData.hotspotX = MANIFEST["General"]["hotspot_x"].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 SIZES = MANIFEST["General"]["define_size"].value_or("");

View file

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

View file

@ -7,6 +7,7 @@
hyprlang,
librsvg,
libzip,
xcur2png,
tomlplusplus,
version ? "git",
}:
@ -15,11 +16,6 @@ stdenv.mkDerivation {
inherit version;
src = ../.;
patches = [
# adds /run/current-system/sw/share/icons to the icon lookup directories
./dirs.patch
];
nativeBuildInputs = [
cmake
pkg-config
@ -30,6 +26,7 @@ stdenv.mkDerivation {
hyprlang
librsvg
libzip
xcur2png
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 6 2 longDate)
]);
version = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
in {
default = inputs.self.overlays.hyprcursor;
@ -14,10 +15,14 @@ in {
inputs.hyprlang.overlays.default
(final: prev: {
hyprcursor = prev.callPackage ./default.nix {
stdenv = prev.gcc13Stdenv;
version = "0.pre" + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty");
stdenv = prev.gcc15Stdenv;
version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty");
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");
if (!shapeData || shapeData->len <= 0) {
if (!shapeData) {
printf("failed querying left_ptr\n");
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) {
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);
@ -65,4 +76,4 @@ int main(int argc, char** argv) {
}
return 0;
}
}