Compare commits

...

161 commits
v0.1.0 ... main

Author SHA1 Message Date
Tom Englund
5ac060bfcf
signal: check for trivially copyable (#95)
use is_trivially_copyable instead because is_arithmetic_v exludes small
POD structs, enums, and certain trivially copyable types.

also add a if guard in emit if we have no listeners, no point emitting.
2025-12-19 16:12:51 +00:00
1c527b30fe
cli/logger: flush stdout after logging 2025-12-17 20:09:42 +00:00
fe686486ac
version: bump to 0.11.0 2025-12-05 19:18:01 +00:00
2f2413801b
logger: don't crash on failing to print to stdout 2025-12-02 00:58:52 +00:00
Vaxry
9f8e158dbd
memory/shared: add dynamicPointerCast (#92) 2025-12-01 21:18:31 +00:00
EvilLary
7e6346f84b
i18n: fix typo in sorting entries (#94) 2025-11-30 00:54:28 +00:00
Maximilian Seidler
a64517c236
animation: allow/intend for animated vars to be unique pointers (#93) 2025-11-28 17:08:50 +00:00
0168583075
cli/argumentParser: improve formatting of description 2025-11-24 14:54:18 +00:00
5e1a14bc29
cli/argumentParser: allow empty short 2025-11-24 14:44:43 +00:00
bc9803c4b8
version: bump to 0.10.4 2025-11-24 14:13:22 +00:00
SASANO Takayoshi
44c2ba0354
src/cli/ArgumentParser.cpp: clang requires <algorithm> (#91) 2025-11-24 13:53:20 +00:00
96df6f6535
cli/logger: add redirection of connections 2025-11-23 18:30:59 +00:00
a9fe9748ae
version: bump to 0.10.3 2025-11-23 16:03:58 +00:00
Vaxry
b311dc90dc
cli: add logger (#90)
Adds a Logger to the CLI namespace
2025-11-23 15:45:14 +00:00
Vaxry
16a7fe760d
cli: add CArgumentParser (#89) 2025-11-23 00:20:24 +00:00
31f29957df
Nix: always test in debug mode 2025-11-22 00:35:18 +02:00
1454845751
cmake: add coverage reports 2025-11-21 22:19:01 +00:00
Vaxry
2698ac1194
tests: move tests out of source tree (#88) 2025-11-21 16:27:29 +00:00
671792bcfe
i18n: fix error with localizing multi-var strings 2025-11-19 00:51:03 +00:00
0c6411851c
i18n/engine: optimize string generation 2025-11-16 20:02:11 +00:00
e3cae692f6
i18n/engine: fix pure stem matches 2025-11-15 22:21:40 +00:00
Constantin Piber
cb3e797fde
internal: fix missing headers (#86) 2025-11-10 22:02:48 +00:00
0a28f35c00
version: bump to 0.10.2 2025-11-10 13:00:04 +00:00
Vaxry
01afe9245b
tests: move to gtest (#85)
---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-11-09 18:05:01 +00:00
Vaxry
9a9745d7aa
string: add VarList2 (#84)
reworks how varList works, mixes best of both ConstVarList and regular VarList. Deprecates both.
2025-11-09 15:10:58 +00:00
Vaxry
968f881222
i18n: add i18n engine (#83)
Adds a translation engine
2025-11-09 14:18:33 +00:00
926689ddb9
version: bump to 0.10.1 2025-11-06 00:09:00 +00:00
9637961a55
string/constVarList: fix UAF 2025-11-05 15:04:02 +00:00
Freevatar
164a30b3d8
animation/bezier: Fix OOB in getYForPoint for non-monotonic 4-point curves (#81) 2025-11-03 22:25:56 +00:00
3df7bde01e
version: bump to 0.10.0 2025-10-05 00:12:03 +01:00
9ab64319e9
math/region: reinit region for scale()
Apparently on some setups directly modifying the rectangles is a no-go, see #78.

Pixman is another piece of undocumented shit. I hope whomever wrote this stubs their toe.

Note to self: drop pixman, rewrite region. Fucking idiots.
2025-10-04 21:22:03 +01:00
Maximilian Seidler
feaaf44d59
memory: make SP/ASP control blocks type agnostic (#79) 2025-10-04 20:35:48 +02:00
94cce79434
tests/math: add region scale test 2025-10-03 12:28:46 +01:00
c1f541256e
version: bump to 0.9.0 2025-10-03 12:16:41 +01:00
1f80045da1
bezier: fix with first point being non-0 2025-09-30 15:05:40 +01:00
a20932e200
bezier: add setup4 2025-09-30 13:56:37 +01:00
Tom Englund
64446e1a4c vector2d: make vector trivial
while profiling vector showed up as a marginal waster because it wasnt a
trivial class, a few if(m_someVector != Vector()) was causing a lot of
churn because of not being trivial and doing a lot of allocaitons and
destructions. make it trivial by defaulting constructor, and destructor,
and while we are at it make it constexpr friendly on constructors and
operators.
2025-09-28 00:01:31 +02:00
Tom Englund
05f0fb2774 memory: fix clangd warnings
modernize validHierarchy and isConstructible, mark move operator
noexcept, use default destructor instead of {}, __deleter is a reserved
name, remove a underscore.

as per clangd docs.
The C standard additionally reserves names beginning with a double underscore,
while the C++ standard strengthens this to reserve names with a double underscore occurring anywhere.
2025-09-27 01:01:56 +02:00
Tom Englund
427332a7ca memory: use initializer list in constructors
for trivial types this is optimized away, non trivial it default
constructs and then assigns it upon construction, minor waste. so lets
use initializer list for these custom types.
2025-09-27 01:01:56 +02:00
61e295340d
region: avoid tons of allocations for scale()
pixman_region32_rectangles returns the actual rects, so we shouldnt waste cycles when scaling em
2025-09-26 23:17:45 +01:00
b2ae320484
version: bump to 0.8.4 2025-08-25 12:23:08 +02:00
Ikalco
bc193efa4b
os/process: add setStdinFD (#74) 2025-08-25 12:04:44 +02:00
b364dcb739
nix: make mold optional 2025-08-21 20:05:54 +03:00
e631ea36dd version: bump to 0.8.3 2025-08-17 09:35:20 +02:00
Lukas
69efb6291c
memory: smart pointer factories should not be static (#72) 2025-08-14 16:16:28 +01:00
Kamikadze
df6b8820c4
memory: Add C++ cast aliases; replace existing casts and remove redundant casts (#71)
* Add and use c++ cast aliases

* wrap cast aliases in namespace

* remove redundant pragma once
2025-08-06 14:00:50 +02:00
c65d41d4f4
version: bump to 0.8.2 2025-07-29 16:49:27 +02:00
Tom Englund
8dd20c73e0
mat3x3: check for finite in toString (#70)
if values are NaN or inf it will crash std::format, safeguard it.
2025-07-23 12:14:37 +02:00
Maximilian Seidler
18fbac5a98
memory: add missing lock (#69) 2025-07-22 16:58:12 +02:00
Maximilian Seidler
b074d4abc7
memory: fix CAtomicSharedPointer mutex unlock and lifetime issues (#68) 2025-07-21 21:09:54 +02:00
Tom Englund
bcabcbada9 signal: reserve vector size to avoid reallocations
reserve the vector size to avoid reallocations.
2025-07-11 18:45:10 +02:00
Tom Englund
86905e2590 mat3x3: use float versions of sin/cos reduce dereferencing
use the sinf, cosf versions instead of casting the double one to float.
reduce the derefencing in multiply by pointing to the data directly.
2025-07-11 18:45:10 +02:00
Tom Englund
e21b18ff8f
region: introduce foreach and cleanup clangd warnings (#65)
* CRegion: introduce forEachRect

makes us able to directly call a function on each rect instead of using
getRects() allocating a vector every single time, especially when its
used in hot paths like the renderer.

* CRegion: cleanup CRegion of clangd warnings

overload pixman() with const, remove const_cast, mark move as noexcept.
ensure self assignment doesnt occur.
2025-07-10 11:12:26 +02:00
a8229739cf
version: bump to 0.8.1 2025-07-07 13:34:25 +02:00
FrancisTheCat
e89269578a
math/vector: Added transform method to Vector2D class (#64) 2025-07-05 23:18:58 +02:00
boundlessvoid0
4737241eaf
animation/beziercurve: add getter for control points (hyprwm/Hyprland#10413) (#63)
Signed-off-by: boundlessvoid <boundlessvoid0@gmail.com>
2025-06-28 00:04:42 +02:00
Brahmajit Das
1eb6759ae7
internal: fix missing cstdint include for GCC 16 (#62)
Without cstdint, building with GCC 16 fails with error

In file included from /tmp/hyprutils/tests/memory.cpp:1:
/tmp/hyprutils/./include/hyprutils/memory/UniquePtr.hpp: In member function ‘bool Hyprutils::Memory::CUniquePointer<T>::operator()(const Hyprutils:
:Memory::CUniquePointer<T>&, const Hyprutils::Memory::CUniquePointer<T>&) const’:
/tmp/hyprutils/./include/hyprutils/memory/UniquePtr.hpp:77:41: error: ‘uintptr_t’ does not name a type [-Wtemplate-body]
   77 |                 return reinterpret_cast<uintptr_t>(lhs.impl_) < reinterpret_cast<uintptr_t>(rhs.impl_);
      |                                         ^~~~~~~~~
/tmp/hyprutils/./include/hyprutils/memory/UniquePtr.hpp:4:1: note: ‘uintptr_t’ is defined in header ‘<cstdint>’; this is probably fixable by adding
 ‘#include <cstdint>’
    3 | #include "ImplBase.hpp"
  +++ |+#include <cstdint>
    4 |
/tmp/hyprutils/./include/hyprutils/memory/UniquePtr.hpp:77:82: error: ‘uintptr_t’ does not name a type [-Wtemplate-body]
   77 |                 return reinterpret_cast<uintptr_t>(lhs.impl_) < reinterpret_cast<uintptr_t>(rhs.impl_);
      |                                                                                  ^~~~~~~~~
/tmp/hyprutils/./include/hyprutils/memory/UniquePtr.hpp:77:82: note: ‘uintptr_t’ is defined in header ‘<cstdint>’; this is probably fixable by addi
ng ‘#include <cstdint>’

Downstream-bug: https://bugs.gentoo.org/957409

Signed-off-by: Brahmajit Das <listout@listout.xyz>
2025-06-26 21:29:11 +02:00
d844a08d83
signal: don't mark signal as C++ deprecated 2025-06-26 19:49:52 +02:00
2cd5e4fcd5
version: bump to 0.8.0 2025-06-26 18:23:47 +02:00
376d0209c8
signal: don't spam logs 2025-06-26 18:23:27 +02:00
93246269d4
signal: Typed signals (part 2) (#60)
* signals: make CSignalT API compatible with CSignal

Also fixes emitting reference types

* signals: add a lot of tests

* animation: use CSignalT

* signals: automatically const-ref non arithmetic value types

* signals: allow listeners to ignore args

* signals: add forward()
2025-06-26 12:27:31 +02:00
6ee59e4eb8
core: add editorconfig (#61) 2025-06-26 11:44:09 +02:00
Maximilian Seidler
925f26633f
memory: add CAtomicSharedPointer and CAtomicWeakPointer (#57) 2025-06-25 19:41:24 +02:00
1b8090e5d8
signals: add typed signals with CSignalT<> (#58)
Also more tests
2025-06-23 22:51:38 +02:00
d46bd32da5
os/process: add env to async calls too 2025-06-23 20:25:26 +02:00
38f3a21165
CI/Nix: add cache-nix-action
Use nixbuild/nix-quick-install-action which pairs well with
nix-community/cache-nix-action.

Should help with build times by reducing the number of packages needing
to be re-downloaded on each run.

Parameters are taken from https://github.com/nix-community/cache-nix-action
and may be tweaked later.
2025-06-20 01:11:36 +03:00
15df5e39af
Nix: move overlays to nix/ and add debug package 2025-06-20 01:11:34 +03:00
c9cd5f153c
os/process: disallow copying / moving 2025-06-16 19:42:14 +02:00
57ab2a867d
tests: fix matrix test on 32b archs
fixes #55
2025-06-13 15:05:19 +02:00
Friday
e36db00dfb
nix: use gcc15 (#54)
updated nixpkgs in flake.lock for gcc15 to be available
2025-06-05 15:55:56 +01:00
Sergei Trofimovich
f1d0879444
sharedPtr: include missing <cstdint> (#52)
Without the change the build on upcoming gcc-16 fails as:

    [ 12%] Building CXX object CMakeFiles/hyprutils.dir/src/signal/Listener.cpp.o
    In file included from /build/source/./include/hyprutils/signal/Listener.hpp:5,
                     from /build/source/src/signal/Listener.cpp:1:
    /build/source/./include/hyprutils/memory/SharedPtr.hpp: In member function 'bool Hyprutils::Memory::CSharedPointer<T>::operator()(const Hyprutils::Memory::CSharedPointer<T>&, const Hyprutils::Memory::CSharedPointer<T>&) const':
    /build/source/./include/hyprutils/memory/SharedPtr.hpp:116:41: error: 'uintptr_t' does not name a type [-Wtemplate-body]
      116 |                 return reinterpret_cast<uintptr_t>(lhs.impl_) < reinterpret_cast<uintptr_t>(rhs.impl_);
          |                                         ^~~~~~~~~
2025-05-31 14:07:05 +01:00
674ea57373
version: bump to 0.7.1 2025-05-07 17:27:05 +01:00
7f00411949
string: add ConstVarList 2025-05-07 17:26:37 +01:00
05878d9470
version: bump to 0.7.0 2025-04-30 02:16:55 +01:00
5f51dcea4a
os/process: add exitCode() 2025-04-30 02:16:30 +01:00
f2dc70e448 version: bump to 0.6.0 2025-04-06 15:38:07 +01:00
966d0c0b6a os/process: add fd control for async 2025-04-06 15:34:33 +01:00
Sergey Fedorov
7248194a2c
animations: minor follow-up fix for std::string (#49) 2025-03-26 11:17:49 +01:00
fbd02eb032 animation: avoid using constexpr std::string
fixes #48
2025-03-26 01:13:40 +00:00
dd1f720cbc version: bump to 0.5.2 2025-03-09 15:38:08 +00:00
Lee Bousfield
6b0154b183
memory: Add shared pointer reinterpretPointerCast function (#47)
* memory: Add force reinterpret constructor to shared pointer

* memory: Change constructor to reinterpretPointerCast function

* memory: Add reinterpretPointerCast test
2025-03-04 22:26:24 +01:00
61a5382f4b version: bump to 0.5.1 2025-02-18 15:12:08 +00:00
77d7ea3498 os/process: populate pid when ran sync 2025-02-18 14:14:20 +00:00
e4e018a2ca
CI: remove deprecated magic-nix-cache-action 2025-02-08 23:08:53 +02:00
Honkazel
3d9ae75886
core: reenable wreorder (#46)
* cmake: re-enable wreorder

* implbase: fix wreorder

This fixes all reorder warnings... uh...
2025-02-03 20:44:09 +01:00
Honkazel
9aad80acd4
core: clang, clang-tidy fixes and comp options (#45)
* clang-tidy/clang fixes and comp options

* clang-format

* cmake: silence reorder for now

27 of them... Damn
2025-02-02 20:36:28 +01:00
Maximilian Seidler
dd790b90d7
animation: fix end callbacks readding the animation var (#43)
* animation: fix end callbacks readding the animation var

* include fix
2025-02-02 18:33:44 +01:00
6a8bc9d2a4 version: bump to 0.5.0 2025-01-27 11:45:43 +00:00
Maximilian Seidler
de58286a21
animation: fix crashes and cleanup of active vars (#42)
Minor stuff

---------

Co-authored-by: Vaxry <vaxry@vaxry.net>
2025-01-27 12:45:01 +01:00
nyx
fb0c2d1de3
memory/weak: add missing nullptr comparison operators (#41) 2025-01-24 14:31:48 +01:00
3411e5b42d memory: add WP == UP comparison fn 2025-01-24 01:31:47 +00:00
006620eb29 version: bump to 0.4.0 2025-01-23 11:39:23 +00:00
Maximilian Seidler
ca1253c626
animation: add virtual dtor to AnimationManager (#31) 2025-01-23 11:38:09 +00:00
423c69d697 memory: implement a unique pointer
this unique pointer differs from the STL, because it allows weak pointers to it, which cannot be lock()ed.

under the hood, it's just a SP that cannot be referenced.
2025-01-23 11:34:35 +00:00
59414c4cee
math/region: fix scale using x scale as y scale (#39) 2025-01-22 09:35:48 +01:00
Maximilian Seidler
72dfbf5296
animations: fix adding/removing vars during ::tick (#35) 2025-01-11 16:37:12 +00:00
5dd4f4a9f7 animation: don't fire update events if the avar is not being animated 2025-01-10 16:44:32 +01:00
3c895da64b version: bump to 0.3.3 2025-01-06 12:55:19 +01:00
Jan Beich
fb9a816cb9
animation: add missing header for libc++ after 6a26d08bac (#30)
In file included from src/animation/AnimatedVariable.cpp:1:
In file included from ./include/hyprutils/animation/AnimatedVariable.hpp:3:
./include/hyprutils/animation/AnimationConfig.hpp:17:60: error: implicit instantiation of undefined template 'std::basic_string<char>'
   17 |             std::string                                    internalBezier  = "";
      |                                                            ^
/usr/include/c++/v1/__fwd/string.h:43:28: note: template is declared here
   43 | class _LIBCPP_TEMPLATE_VIS basic_string;
      |                            ^
In file included from src/animation/AnimatedVariable.cpp:1:
In file included from ./include/hyprutils/animation/AnimatedVariable.hpp:3:
./include/hyprutils/animation/AnimationConfig.hpp:18:60: error: implicit instantiation of undefined template 'std::basic_string<char>'
   18 |             std::string                                    internalStyle   = "";
      |                                                            ^
/usr/include/c++/v1/__fwd/string.h:43:28: note: template is declared here
   43 | class _LIBCPP_TEMPLATE_VIS basic_string;
      |                            ^
2025-01-03 22:47:04 +00:00
Maximilian Seidler
6a26d08bac
animation: add CAnimationConfigTree (#29)
Co-authored-by: Maximilian Seidler <paideia_dilemma@losfuzzys.net>
2025-01-02 14:02:21 +00:00
c42ce87eb3 version: bump to 0.3.1 2025-01-01 14:16:26 +01:00
Maximilian Seidler
8af7e4b9de
animation: add BezierCurve, AnimationManager and AnimatedVariable (#27) 2024-12-29 19:26:01 +00:00
8f15d45b12 region: Revert "fix header path"
This reverts commit 104117aed6.

Fixes #28
2024-12-27 17:23:03 +01:00
nyx
9be03a8562
misc: fix some compile warnings (#25)
- Cleans up compile warnings
- Formats code
- Cleans up unused vars
- etc
2024-12-25 19:10:14 +00:00
5e45b1a1b9 version: bump to 0.3.0 2024-12-22 23:05:34 +00:00
c3331116eb os/process: add pid() 2024-12-21 15:47:53 +00:00
Austin Horstman
e6cf45cd18
flake.nix: gcc13 -> gcc14; flake.lock: update (#22)
* flake.nix: gcc13 -> gcc14

* flake.lock: update
2024-12-16 22:24:07 +01:00
104117aed6 region: fix header path 2024-12-06 16:24:01 +00:00
b26f33cc1c README: fix invalid getconf value 2024-11-22 15:11:21 +00:00
Tom Englund
2e21319c8e
os: implent a new FileDescriptor class (#21)
makes you able to RAII the filedescriptor making it somewhat easier to
not leak.
2024-11-21 15:26:34 +00:00
e911361a68 version: bump to 0.2.6 2024-11-15 20:30:27 +00:00
e74177e025 os: add setEnv to process 2024-11-15 20:28:59 +00:00
315fba5d21 process: avoid zombies
fixes #20
2024-11-13 17:19:47 +00:00
91c1634727 version: update to 0.2.5 2024-11-12 12:25:15 +00:00
4c5f18d06b process: better reading for spawnSync 2024-11-11 22:34:56 +00:00
8d21d1dfa9 os: fixup process possible deadlock
Sloppy, but should work... Process shouldn't be used in performance-critical paths anyways.

ref https://github.com/hyprwm/Hyprland/issues/8425
2024-11-11 22:12:44 +00:00
Jan Beich
d504d45114
os: add missing header for BSDs after fd4be8b9ca (#16)
src/os/Process.cpp:105:9: error: use of undeclared identifier 'sigemptyset'
  105 |         sigemptyset(&set);
      |         ^
src/os/Process.cpp:106:21: error: use of undeclared identifier 'SIG_SETMASK'
  106 |         sigprocmask(SIG_SETMASK, &set, NULL);
      |                     ^
2024-11-09 20:30:25 +00:00
60d3dece30 version: bump to 0.2.4 2024-11-09 14:42:18 +00:00
3ce0cde870
nix: add debug flag
enable keepDebugInfo adapter on debug
build with mold linker
2024-11-07 10:41:43 +02:00
fd4be8b9ca os: Add process 2024-10-14 22:27:36 +01:00
3f5293432b tests: enhance memory + format 2024-09-25 22:44:05 +01:00
db956287d3 mat3x3: fix comment 2024-09-25 00:05:20 +01:00
7373e87215 version: bump to 0.2.3 2024-09-24 23:58:26 +01:00
Vaxry
a9f85a4bca
Math: add Mat3x3 (#14) 2024-09-24 18:18:25 +01:00
d97af4f6bd version: bump to 0.2.2 2024-09-21 00:29:09 +01:00
8976e3f6a5
gitignore: add CMake residual files 2024-08-30 00:21:23 +03:00
aadf9a27dd string: fixup isNumber not accepting -1.0
fixes #12
2024-08-28 18:53:00 +02:00
0252fd13e7 utils: add ScopeGuard 2024-08-05 16:45:41 +02:00
5dcbbc1e3d version: bump to 0.2.1 2024-07-27 18:47:29 +02:00
a950624669
math: add Edges (#10) 2024-07-27 17:38:55 +01:00
962582a090
flake.lock: update 2024-07-18 20:35:02 +03:00
04697c69ab
CMake: fmt 2024-07-18 20:34:31 +03:00
928d9b4ee9
CMake, Nix: add VERSION file 2024-07-18 20:34:06 +03:00
eb1ceff2b8 cmake: bump ver to 0.2.0 2024-07-15 21:28:57 +02:00
6174a2a25f
path: add findConfig and dir utils (#8) 2024-07-09 19:11:16 +02:00
c342d5ca44 memory: do not release pointers after emitting a signal
A signal called is allowed to free ourselves, in which case we're not allowed to use this anymore. Only perform the housekeeping of removing stale events before emit, and in registerListener.
2024-07-08 23:06:28 +02:00
a8c3a13570 math/region: add expand 2024-07-05 20:17:24 +02:00
Maximilian Seidler
259ecfa098
string: respect removeEmpty when VarList input is empty (#7) 2024-07-04 18:04:41 +02:00
1d20063da2 math: avoid assert fail in std::clamp in closestPoint 2024-07-04 15:49:06 +02:00
drendog
bbc76ba4e4
math: adjust right and bottom box edges (#6) 2024-07-02 12:16:59 +02:00
Tom Englund
7e1b6610a2
memory: avoid undefined behaviour from downcasting ptr implentation (#5)
* sharedptr: dont downcast ptr implentation

avoid runtime errors of wrong downcasts by adding a underlaying ptr data
getter and only cast the data, downcasting the implentation type isnt
inherited from eachother, while the intention was do upcast/downcast
a derived class to a base class. found with UBSAN "runtime error:
downcast of address which does not point to an object of type
"CSharedPointer_::impl<IKeyboard>" note: object is of type
"Hyprutils::Memory::CSharedPointer_::impl<CKeyboard>"

also make dataNonNull check against != nullptr.

* sharedptr: use reinterpret_cast instead of c style

make it more type safe, C style casts tries every single one until one
works, or not. compilers also produces better warnings/errors when using
c++ casts in favour of C style ones.
2024-06-28 18:49:58 +02:00
1f6bbec595 cmake: bump ver to 0.1.5 2024-06-25 13:48:22 +02:00
sslater11
5606bf985b
README: Update build instructions (#4) 2024-06-25 01:18:45 +02:00
Jasson Cordones
7a2c2c96ec
Math: Some more box improvements and test cases (#3)
* Added some constants to handle floating point presicion comparisons and other calculations plus some refactoring

* Removed validation

* Added comments to understand how box header works

* Extended the EXPECT macro to evaluate Vector2D test cases

* Added box.cpp test cases

* Applied clang-format
2024-06-25 01:17:44 +02:00
Jasson Cordones
ff343e0279
Math: Added some constants to handle floating point presicion comparisons (#2)
* Added some constants to handle floating point presicion comparisons and other calculations plus some refactoring

* Removed validation
2024-06-23 20:14:57 +02:00
725f63aabc vector: prefix macros with AQ_ 2024-06-19 23:16:16 +02:00
4f1351295c cmake: bump ver to 0.1.4 2024-06-19 15:34:38 +02:00
4436fbfc67
Nix: add pixman dep 2024-06-19 09:12:49 +03:00
1f59192a2d ci: add pixman to dependencies 2024-06-18 23:36:10 +02:00
b4b5389bc1 Math: add an int ctor to vec 2024-06-18 18:17:55 +02:00
e9d4a99e13 Math: init Region, Vector2D and Box 2024-06-18 14:10:18 +02:00
a8f9373474 cmake: bump version to 0.1.3 2024-06-17 13:24:41 +02:00
d4c71b925c
Nix: don't strip debug symbols 2024-06-17 13:05:21 +03:00
ea8fd1d3ea string: fix isNumber accepting - as valid number 2024-06-15 11:36:28 +02:00
8e10e0626f cmake: bump ver to 0.1.2 2024-06-13 11:36:49 +02:00
Tom Englund
1f4743a1f7
sharedptr: decrement on destruction (#1)
if we dont decrement the sharedptr on destruction any weakptr remaining
will have an impl_ with a ref of 1 and upon destruction of the weakptr
it wont delete the implentation because it thinks a shared pointer still
exist.
2024-06-13 11:29:50 +02:00
0693f9398a
CI: add nix 2024-06-09 00:15:34 +03:00
ef441d2178
CMake: fix description 2024-06-09 00:14:07 +03:00
ebd3c47d3f
Nix: init 2024-06-09 00:13:58 +03:00
fce960f0dd cmake: bump ver to 0.1.1 2024-06-08 22:38:46 +02:00
f73a28ca03 string: add replaceInString 2024-06-08 22:35:40 +02:00
90 changed files with 7244 additions and 380 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

11
.editorconfig Normal file
View file

@ -0,0 +1,11 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
[*.{cmake,nix,yml,yaml},CMakeLists.txt]
indent_size = 2

View file

@ -17,7 +17,7 @@ jobs:
run: | run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pacman --noconfirm --noprogressbar -Sy gcc gtest base-devel cmake clang libc++ pixman
- name: Build hyprutils with gcc - name: Build hyprutils with gcc
run: | run: |
@ -44,7 +44,7 @@ jobs:
run: | run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pacman --noconfirm --noprogressbar -Sy gcc gtest base-devel cmake clang libc++ pixman
- name: Build hyprutils with clang - name: Build hyprutils with clang
run: | run: |

54
.github/workflows/nix.yml vendored Normal file
View file

@ -0,0 +1,54 @@
name: Build & Test
on: [push, pull_request, workflow_dispatch]
jobs:
nix:
strategy:
matrix:
package:
- hyprutils
- hyprutils-with-tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Nix
uses: nixbuild/nix-quick-install-action@v31
with:
nix_conf: |
keep-env-derivations = true
keep-outputs = true
- name: Restore and save Nix store
uses: nix-community/cache-nix-action@v6
with:
# restore and save a cache using this key
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
# if there's no cache hit, restore a cache by this prefix
restore-prefixes-first-match: nix-${{ runner.os }}-
# collect garbage until the Nix store size (in bytes) is at most this number
# before trying to save a new cache
# 1G = 1073741824
gc-max-store-size-linux: 1G
# do purge caches
purge: true
# purge all versions of the cache
purge-prefixes: nix-${{ runner.os }}-
# created more than this number of seconds ago
purge-created: 0
# or, last accessed more than this number of seconds ago
# relative to the start of the `Post Restore and save Nix store` phase
purge-last-accessed: 0
# except any version with the key that is the same as the `primary-key`
purge-primary-key: never
# not needed (yet)
# - uses: cachix/cachix-action@v12
# with:
# name: hyprland
# authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build & Test
run: nix build .#${{ matrix.package }} --print-build-logs

12
.gitignore vendored
View file

@ -34,3 +34,15 @@
build/ build/
.vscode/ .vscode/
.cache/ .cache/
.direnv/
.cmake/
CMakeCache.txt
CMakeFiles/
CTestTestfile.cmake
DartConfiguration.tcl
Makefile
cmake_install.cmake
compile_commands.json
hyprutils.pc
.envrc

View file

@ -1,12 +1,13 @@
cmake_minimum_required(VERSION 3.19) cmake_minimum_required(VERSION 3.19)
set(HYPRUTILS_VERSION "0.1.0") file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
string(STRIP ${VER_RAW} HYPRUTILS_VERSION)
add_compile_definitions(HYPRUTILS_VERSION="${HYPRUTILS_VERSION}") add_compile_definitions(HYPRUTILS_VERSION="${HYPRUTILS_VERSION}")
project(hyprutils project(
VERSION ${HYPRUTILS_VERSION} hyprutils
DESCRIPTION "A library and toolkit for the Hyprland cursor format" VERSION ${HYPRUTILS_VERSION}
) DESCRIPTION "Small C++ library for utilities used across the Hypr* ecosystem")
include(CTest) include(CTest)
include(GNUInstallDirs) include(GNUInstallDirs)
@ -18,47 +19,67 @@ set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR})
configure_file(hyprutils.pc.in hyprutils.pc @ONLY) configure_file(hyprutils.pc.in hyprutils.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)
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 hyprutils in Debug") message(STATUS "Configuring hyprutils in Debug")
add_compile_definitions(HYPRLAND_DEBUG) add_compile_definitions(HYPRLAND_DEBUG)
set(BUILD_TESTING ON)
else() else()
add_compile_options(-O3) add_compile_options(-O3)
message(STATUS "Configuring hyprutils in Release") message(STATUS "Configuring hyprutils in Release")
set(BUILD_TESTING OFF)
endif() endif()
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp") file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp")
file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "include/*.hpp") file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "include/*.hpp")
find_package(PkgConfig REQUIRED)
pkg_check_modules(deps REQUIRED IMPORTED_TARGET pixman-1)
add_library(hyprutils SHARED ${SRCFILES}) add_library(hyprutils SHARED ${SRCFILES})
target_include_directories( hyprutils target_include_directories(
hyprutils
PUBLIC "./include"
PRIVATE "./src")
set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION}
SOVERSION 10)
target_link_libraries(hyprutils PkgConfig::deps)
if(BUILD_TESTING)
# GTest
find_package(GTest CONFIG REQUIRED)
include(GoogleTest)
file(GLOB_RECURSE TESTFILES CONFIGURE_DEPENDS "tests/*.cpp")
add_executable(hyprutils_tests ${TESTFILES})
target_compile_options(hyprutils_tests PRIVATE --coverage)
target_link_options(hyprutils_tests PRIVATE --coverage)
target_include_directories(
hyprutils_tests
PUBLIC "./include" PUBLIC "./include"
PRIVATE "./src" PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}")
) target_link_libraries(hyprutils_tests PRIVATE hyprutils GTest::gtest_main
set_target_properties(hyprutils PROPERTIES PkgConfig::deps)
VERSION ${hyprutils_VERSION} gtest_discover_tests(hyprutils_tests)
SOVERSION 0
)
# tests # Add coverage to hyprutils for test builds
add_custom_target(tests) target_compile_options(hyprutils PRIVATE --coverage)
target_link_options(hyprutils PRIVATE --coverage)
add_executable(hyprutils_memory "tests/memory.cpp") endif()
target_link_libraries(hyprutils_memory PRIVATE hyprutils)
add_test(NAME "Memory" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_memory "memory")
add_dependencies(tests hyprutils_memory)
add_executable(hyprutils_string "tests/string.cpp")
target_link_libraries(hyprutils_string PRIVATE hyprutils)
add_test(NAME "String" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_string "string")
add_dependencies(tests hyprutils_string)
add_executable(hyprutils_signal "tests/signal.cpp")
target_link_libraries(hyprutils_signal PRIVATE hyprutils)
add_test(NAME "Signal" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_signal "signal")
add_dependencies(tests hyprutils_signal)
# Installation # Installation
install(TARGETS hyprutils) install(TARGETS hyprutils)
install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES ${CMAKE_BINARY_DIR}/hyprutils.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) install(FILES ${CMAKE_BINARY_DIR}/hyprutils.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)

View file

@ -9,6 +9,9 @@ Hyprutils depends on the ABI stability of the stdlib implementation of your comp
## Building ## Building
```sh ```sh
git clone https://github.com/hyprwm/hyprutils.git
cd hyprutils/
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`
sudo cmake --install build
``` ```

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.11.0

43
flake.lock generated Normal file
View file

@ -0,0 +1,43 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1748929857,
"narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"systems": "systems"
}
},
"systems": {
"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",
"version": 7
}

31
flake.nix Normal file
View file

@ -0,0 +1,31 @@
{
description = "Small C++ library for utilities used across the Hypr* ecosystem";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
systems.url = "github:nix-systems/default-linux";
};
outputs = {
self,
nixpkgs,
systems,
}: let
inherit (nixpkgs) lib;
eachSystem = lib.genAttrs (import systems);
pkgsFor = eachSystem (system:
import nixpkgs {
localSystem.system = system;
overlays = with self.overlays; [hyprutils];
});
in {
overlays = import ./nix/overlays.nix {inherit self lib;};
packages = eachSystem (system: {
default = self.packages.${system}.hyprutils;
inherit (pkgsFor.${system}) hyprutils hyprutils-debug hyprutils-with-tests;
});
formatter = eachSystem (system: pkgsFor.${system}.alejandra);
};
}

View file

@ -0,0 +1,238 @@
#pragma once
#include "AnimationConfig.hpp"
#include "../memory/WeakPtr.hpp"
#include "../memory/SharedPtr.hpp"
#include "../signal/Signal.hpp"
#include "AnimationManager.hpp"
#include <functional>
#include <chrono>
namespace Hyprutils {
namespace Animation {
/* A base class for animated variables. */
class CBaseAnimatedVariable {
public:
using CallbackFun = std::function<void(Memory::CWeakPointer<CBaseAnimatedVariable> thisptr)>;
CBaseAnimatedVariable() {
; // m_bDummy = true;
};
void create(CAnimationManager*, int, Memory::CSharedPointer<CBaseAnimatedVariable>);
void create2(CAnimationManager*, int, Memory::CWeakPointer<CBaseAnimatedVariable>);
void connectToActive();
void disconnectFromActive();
/* Needs to call disconnectFromActive to remove `m_pSelf` from the active animation list */
virtual ~CBaseAnimatedVariable() {
disconnectFromActive();
};
virtual void warp(bool endCallback = true, bool forceDisconnect = true) = 0;
CBaseAnimatedVariable(const CBaseAnimatedVariable&) = delete;
CBaseAnimatedVariable(CBaseAnimatedVariable&&) = delete;
CBaseAnimatedVariable& operator=(const CBaseAnimatedVariable&) = delete;
CBaseAnimatedVariable& operator=(CBaseAnimatedVariable&&) = delete;
//
void setConfig(Memory::CSharedPointer<SAnimationPropertyConfig> pConfig) {
m_pConfig = pConfig;
}
Memory::CWeakPointer<SAnimationPropertyConfig> getConfig() const {
return m_pConfig;
}
bool enabled() const;
const std::string& getBezierName() const;
const std::string& getStyle() const;
/* returns the spent (completion) % */
float getPercent() const;
/* returns the current curve value. */
float getCurveValue() const;
/* checks if an animation is in progress */
bool isBeingAnimated() const {
return m_bIsBeingAnimated;
}
/* checks m_bDummy and m_pAnimationManager */
bool ok() const;
/* calls the update callback */
void onUpdate();
/* sets a function to be ran when an animation ended.
if "remove" is set to true, it will remove the callback when ran. */
void setCallbackOnEnd(CallbackFun func, bool remove = true);
/* sets a function to be ran when an animation is started.
if "remove" is set to true, it will remove the callback when ran. */
void setCallbackOnBegin(CallbackFun func, bool remove = true);
/* sets the update callback, called every time the value is animated and a step is done
Warning: calling unregisterVar/registerVar in this handler will cause UB */
void setUpdateCallback(CallbackFun func);
/* resets all callbacks. Does not call any. */
void resetAllCallbacks();
void onAnimationEnd();
void onAnimationBegin();
/* returns whether the parent CAnimationManager is dead */
bool isAnimationManagerDead() const;
int m_Type = -1;
protected:
friend class CAnimationManager;
CAnimationManager* m_pAnimationManager = nullptr;
bool m_bIsConnectedToActive = false;
bool m_bIsBeingAnimated = false;
Memory::CWeakPointer<CBaseAnimatedVariable> m_pSelf;
Memory::CWeakPointer<CAnimationManager::SAnimationManagerSignals> m_pSignals;
private:
Memory::CWeakPointer<SAnimationPropertyConfig> m_pConfig;
std::chrono::steady_clock::time_point animationBegin;
bool m_bDummy = true;
bool m_bRemoveEndAfterRan = true;
bool m_bRemoveBeginAfterRan = true;
CallbackFun m_fEndCallback;
CallbackFun m_fBeginCallback;
CallbackFun m_fUpdateCallback;
};
/* This concept represents the minimum requirement for a type to be used with CGenericAnimatedVariable */
template <class ValueImpl>
concept AnimatedType = requires(ValueImpl val) {
requires std::is_copy_constructible_v<ValueImpl>;
{ val == val } -> std::same_as<bool>; // requires operator==
{ val = val }; // requires operator=
};
/*
A generic class for variables.
VarType is the type of the variable to be animated.
AnimationContext is there to attach additional data to the animation.
In Hyprland that struct would contain a reference to window, workspace or layer for example.
*/
template <AnimatedType VarType, class AnimationContext>
class CGenericAnimatedVariable : public CBaseAnimatedVariable {
public:
CGenericAnimatedVariable() = default;
/* Deprecated: use create2 */
void create(const int typeInfo, CAnimationManager* pAnimationManager, Memory::CSharedPointer<CGenericAnimatedVariable<VarType, AnimationContext>> pSelf,
const VarType& initialValue) {
m_Begun = initialValue;
m_Value = initialValue;
m_Goal = initialValue;
CBaseAnimatedVariable::create(pAnimationManager, typeInfo, pSelf);
}
/* Equivalent to create, except that it allows animated variables to be UP's */
void create2(const int typeInfo, CAnimationManager* pAnimationManager, Memory::CWeakPointer<CGenericAnimatedVariable<VarType, AnimationContext>> pSelf,
const VarType& initialValue) {
m_Begun = initialValue;
m_Value = initialValue;
m_Goal = initialValue;
CBaseAnimatedVariable::create2(pAnimationManager, typeInfo, pSelf);
}
CGenericAnimatedVariable(const CGenericAnimatedVariable&) = delete;
CGenericAnimatedVariable(CGenericAnimatedVariable&&) = delete;
CGenericAnimatedVariable& operator=(const CGenericAnimatedVariable&) = delete;
CGenericAnimatedVariable& operator=(CGenericAnimatedVariable&&) = delete;
virtual void warp(bool endCallback = true, bool forceDisconnect = true) {
if (!m_bIsBeingAnimated)
return;
m_Value = m_Goal;
onUpdate();
m_bIsBeingAnimated = false;
if (forceDisconnect)
disconnectFromActive();
if (endCallback)
onAnimationEnd();
}
const VarType& value() const {
return m_Value;
}
/* used to update the value each tick via the AnimationManager */
VarType& value() {
return m_Value;
}
const VarType& goal() const {
return m_Goal;
}
const VarType& begun() const {
return m_Begun;
}
CGenericAnimatedVariable& operator=(const VarType& v) {
if (v == m_Goal)
return *this;
m_Goal = v;
m_Begun = m_Value;
onAnimationBegin();
return *this;
}
/* Sets the actual stored value, without affecting the goal, but resets the timer*/
void setValue(const VarType& v) {
if (v == m_Value)
return;
m_Value = v;
m_Begun = m_Value;
onAnimationBegin();
}
/* Sets the actual value and goal*/
void setValueAndWarp(const VarType& v) {
m_Goal = v;
m_bIsBeingAnimated = true;
warp();
}
AnimationContext m_Context;
private:
VarType m_Value{};
VarType m_Goal{};
VarType m_Begun{};
};
}
}

View file

@ -0,0 +1,56 @@
#pragma once
#include "../memory/WeakPtr.hpp"
#include <string>
#include <unordered_map>
namespace Hyprutils {
namespace Animation {
/*
Structure for animation properties.
Config properties need to have a static lifetime to allow for config reload.
*/
struct SAnimationPropertyConfig {
bool overridden = false;
std::string internalBezier = "";
std::string internalStyle = "";
float internalSpeed = 0.f;
int internalEnabled = -1;
Memory::CWeakPointer<SAnimationPropertyConfig> pValues;
Memory::CWeakPointer<SAnimationPropertyConfig> pParentAnimation;
};
/* A class to manage SAnimationPropertyConfig objects in a tree structure */
class CAnimationConfigTree {
public:
CAnimationConfigTree() = default;
~CAnimationConfigTree() = default;
/* Add a new animation node inheriting from a parent.
If parent is empty, a root node will be created that references it's own values.
Make sure the parent node has already been created through this interface. */
void createNode(const std::string& nodeName, const std::string& parent = "");
/* check if a node name has been created using createNode */
bool nodeExists(const std::string& nodeName) const;
/* Override the values of a node. The root node can also be overriden. */
void setConfigForNode(const std::string& nodeName, int enabled, float speed, const std::string& bezier, const std::string& style = "");
Memory::CSharedPointer<SAnimationPropertyConfig> getConfig(const std::string& name) const;
const std::unordered_map<std::string, Memory::CSharedPointer<SAnimationPropertyConfig>>& getFullConfig() const;
CAnimationConfigTree(const CAnimationConfigTree&) = delete;
CAnimationConfigTree(CAnimationConfigTree&&) = delete;
CAnimationConfigTree& operator=(const CAnimationConfigTree&) = delete;
CAnimationConfigTree& operator=(CAnimationConfigTree&&) = delete;
private:
void setAnimForChildren(Memory::CSharedPointer<SAnimationPropertyConfig> PANIM);
std::unordered_map<std::string, Memory::CSharedPointer<SAnimationPropertyConfig>> m_mAnimationConfig;
};
}
}

View file

@ -0,0 +1,60 @@
#pragma once
#include "./BezierCurve.hpp"
#include "../math/Vector2D.hpp"
#include "../memory/WeakPtr.hpp"
#include "../signal/Signal.hpp"
#include <cstdint>
#include <unordered_map>
#include <vector>
namespace Hyprutils {
namespace Animation {
class CBaseAnimatedVariable;
/* A class for managing bezier curves and variables that are being animated. */
class CAnimationManager {
public:
CAnimationManager();
virtual ~CAnimationManager() = default;
void tickDone();
void rotateActive();
bool shouldTickForNext();
virtual void scheduleTick() = 0;
virtual void onTicked() = 0;
void addBezierWithName(std::string, const Math::Vector2D&, const Math::Vector2D&);
void removeAllBeziers();
bool bezierExists(const std::string&);
Memory::CSharedPointer<CBezierCurve> getBezier(const std::string&);
const std::unordered_map<std::string, Memory::CSharedPointer<CBezierCurve>>& getAllBeziers();
struct SAnimationManagerSignals {
Signal::CSignalT<Memory::CWeakPointer<CBaseAnimatedVariable>> connect;
Signal::CSignalT<Memory::CWeakPointer<CBaseAnimatedVariable>> disconnect;
};
Memory::CWeakPointer<SAnimationManagerSignals> getSignals() const;
std::vector<Memory::CWeakPointer<CBaseAnimatedVariable>> m_vActiveAnimatedVariables;
private:
std::unordered_map<std::string, Memory::CSharedPointer<CBezierCurve>> m_mBezierCurves;
bool m_bTickScheduled = false;
struct SAnimVarListeners {
Signal::CHyprSignalListener connect;
Signal::CHyprSignalListener disconnect;
};
Memory::CUniquePointer<SAnimVarListeners> m_listeners;
Memory::CUniquePointer<SAnimationManagerSignals> m_events;
};
}
}

View file

@ -0,0 +1,35 @@
#pragma once
#include <array>
#include <vector>
#include "../math/Vector2D.hpp"
namespace Hyprutils {
namespace Animation {
constexpr int BAKEDPOINTS = 255;
constexpr float INVBAKEDPOINTS = 1.f / BAKEDPOINTS;
/* An implementation of a cubic bezier curve. */
class CBezierCurve {
public:
/* Calculates a cubic bezier curve based on 2 control points (EXCLUDES the 0,0 and 1,1 points). */
void setup(const std::array<Hyprutils::Math::Vector2D, 2>& points);
/* Calculates a cubic bezier curve based on 4 control points. */
void setup4(const std::array<Hyprutils::Math::Vector2D, 4>& points);
float getYForT(float const& t) const;
float getXForT(float const& t) const;
float getYForPoint(float const& x) const;
/* this INCLUDES the 0,0 and 1,1 points. */
const std::vector<Hyprutils::Math::Vector2D>& getControlPoints() const;
private:
/* this INCLUDES the 0,0 and 1,1 points. */
std::vector<Hyprutils::Math::Vector2D> m_vPoints;
std::array<Hyprutils::Math::Vector2D, BAKEDPOINTS> m_aPointsBaked;
};
}
}

View file

@ -0,0 +1,36 @@
#pragma once
#include <span>
#include <string>
#include <string_view>
#include <optional>
#include <expected>
#include "../memory/UniquePtr.hpp"
namespace Hyprutils::CLI {
class CArgumentParserImpl;
class CArgumentParser {
public:
CArgumentParser(const std::span<const char*>& args);
~CArgumentParser() = default;
std::expected<void, std::string> registerBoolOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description);
std::expected<void, std::string> registerIntOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description);
std::expected<void, std::string> registerFloatOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description);
std::expected<void, std::string> registerStringOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description);
std::optional<bool> getBool(const std::string_view& name);
std::optional<int> getInt(const std::string_view& name);
std::optional<float> getFloat(const std::string_view& name);
std::optional<std::string_view> getString(const std::string_view& name);
// commence the parsing after registering
std::expected<void, std::string> parse();
std::string getDescription(const std::string_view& header, std::optional<size_t> maxWidth = {});
private:
Memory::CUniquePointer<CArgumentParserImpl> m_impl;
};
};

View file

@ -0,0 +1,116 @@
#pragma once
#include <format>
#include <expected>
#include <string_view>
#include "../memory/UniquePtr.hpp"
#include "../memory/WeakPtr.hpp"
namespace Hyprutils::CLI {
class CLoggerImpl;
enum eLogLevel : uint8_t {
LOG_TRACE = 0,
LOG_DEBUG,
LOG_WARN,
LOG_ERR,
LOG_CRIT,
};
// CLogger is a thread-safe, general purpose logger.
// the logger's stdout is enabled by default.
// color is enabled by default, it's only for stdout.
// everything else is disabled.
class CLogger {
public:
CLogger();
~CLogger();
CLogger(const CLogger&) = delete;
CLogger(CLogger&) = delete;
CLogger(CLogger&&) = delete;
void setLogLevel(eLogLevel level);
void setTime(bool enabled);
void setEnableStdout(bool enabled);
void setEnableColor(bool enabled);
void setEnableRolling(bool enabled);
std::expected<void, std::string> setOutputFile(const std::string_view& file);
const std::string& rollingLog();
void log(eLogLevel level, const std::string_view& msg);
template <typename... Args>
// NOLINTNEXTLINE
void log(eLogLevel level, std::format_string<Args...> fmt, Args&&... args) {
if (!m_shouldLogAtAll)
return;
if (level < m_logLevel)
return;
std::string logMsg = std::vformat(fmt.get(), std::make_format_args(args...));
log(level, logMsg);
}
private:
Memory::CUniquePointer<CLoggerImpl> m_impl;
// this has to be here as part of important optimization of trace logs
eLogLevel m_logLevel = LOG_DEBUG;
// this has to be here as part of important optimization of disabled logging
bool m_shouldLogAtAll = false;
friend class CLoggerImpl;
friend class CLoggerConnection;
};
// CLoggerConnection is a "handle" to a logger, that can be created from a logger and
// allows to send messages to a logger via a proxy
// this does not allow for any changes to the logger itself, only sending logs.
// Logger connections keep their own logLevel. They inherit it at creation, but can be changed
class CLoggerConnection {
public:
CLoggerConnection(CLogger& logger);
~CLoggerConnection();
CLoggerConnection(const CLoggerConnection&) = delete;
CLoggerConnection(CLoggerConnection&) = delete;
// Allow move
CLoggerConnection(CLoggerConnection&&) = default;
void setName(const std::string_view& name);
void setLogLevel(eLogLevel level);
void log(eLogLevel level, const std::string_view& msg);
CLogger* getLogger();
void redirect(CLogger& logger);
template <typename... Args>
// NOLINTNEXTLINE
void log(eLogLevel level, std::format_string<Args...> fmt, Args&&... args) {
if (!m_impl || !m_logger)
return;
if (!m_logger->m_shouldLogAtAll)
return;
if (level < m_logLevel)
return;
std::string logMsg = std::vformat(fmt.get(), std::make_format_args(args...));
log(level, logMsg);
}
private:
Memory::CWeakPointer<CLoggerImpl> m_impl;
CLogger* m_logger = nullptr;
eLogLevel m_logLevel = LOG_DEBUG;
std::string m_name = "";
};
};

View file

@ -0,0 +1,55 @@
#pragma once
#include "../memory/UniquePtr.hpp"
#include <cstdint>
#include <string>
#include <unordered_map>
#include <functional>
namespace Hyprutils::I18n {
struct SI18nEngineImpl;
typedef std::unordered_map<std::string, std::string> translationVarMap;
typedef std::function<std::string(const translationVarMap&)> translationFn;
class CI18nLocale {
public:
~CI18nLocale() = default;
std::string locale();
std::string stem();
std::string full();
private:
CI18nLocale(std::string fullLocale);
std::string m_locale, m_rawFullLocale;
friend class CI18nEngine;
};
class CI18nEngine {
public:
CI18nEngine();
~CI18nEngine();
/*
Register a translation entry. The internal translation db is kept as a vector,
so make sure your keys are linear, don't use e.g. 2 billion as that will call
.resize() on the vec to 2 billion.
If you pass a Fn, you can do logic, e.g. "1 point" vs "2 points".
*/
void registerEntry(const std::string& locale, uint64_t key, std::string&& translationUTF8);
void registerEntry(const std::string& locale, uint64_t key, translationFn&& translationFn);
void setFallbackLocale(const std::string& locale);
std::string localizeEntry(const std::string& locale, uint64_t key, const translationVarMap& map);
CI18nLocale getSystemLocale();
private:
Memory::CUniquePointer<SI18nEngineImpl> m_impl;
};
}

View file

@ -0,0 +1,188 @@
#pragma once
#include "./Vector2D.hpp"
#include "./Misc.hpp"
namespace Hyprutils::Math {
/**
* @brief Represents the extents of a bounding box.
*/
struct SBoxExtents {
Vector2D topLeft;
Vector2D bottomRight;
/**
* @brief Scales the extents by a given factor.
* @param scale The scaling factor.
* @return Scaled SBoxExtents.
*/
SBoxExtents operator*(const double& scale) const {
return SBoxExtents{topLeft * scale, bottomRight * scale};
}
/**
* @brief Rounds the coordinates of the extents.
* @return Rounded SBoxExtents.
*/
SBoxExtents round() {
return {topLeft.round(), bottomRight.round()};
}
/**
* @brief Checks equality between two SBoxExtents objects.
* @param other Another SBoxExtents object to compare.
* @return True if both SBoxExtents are equal, false otherwise.
*/
bool operator==(const SBoxExtents& other) const {
return topLeft == other.topLeft && bottomRight == other.bottomRight;
}
/**
* @brief Adjusts the extents to encompass another SBoxExtents.
* @param other Another SBoxExtents to add to this one.
*/
void addExtents(const SBoxExtents& other) {
topLeft = topLeft.getComponentMax(other.topLeft);
bottomRight = bottomRight.getComponentMax(other.bottomRight);
}
};
/**
* @brief Represents a 2D bounding box.
*/
class CBox {
public:
/**
* @brief Constructs a CBox with specified position and dimensions.
* @param x_ X-coordinate of the top-left corner.
* @param y_ Y-coordinate of the top-left corner.
* @param w_ Width of the box.
* @param h_ Height of the box.
*/
CBox(double x_, double y_, double w_, double h_) {
x = x_;
y = y_;
w = w_;
h = h_;
}
/**
* @brief Default constructor. Initializes an empty box (0 width, 0 height).
*/
CBox() {
w = 0;
h = 0;
}
/**
* @brief Constructs a CBox with uniform dimensions.
* @param d Dimensions to apply uniformly (x, y, width, height).
*/
CBox(const double d) {
x = d;
y = d;
w = d;
h = d;
}
/**
* @brief Constructs a CBox from a position and size vector.
* @param pos Position vector representing the top-left corner.
* @param size Size vector representing width and height.
*/
CBox(const Vector2D& pos, const Vector2D& size) {
x = pos.x;
y = pos.y;
w = size.x;
h = size.y;
}
// Geometric operations
CBox& applyFromWlr();
CBox& scale(double scale);
CBox& scaleFromCenter(double scale);
CBox& scale(const Vector2D& scale);
CBox& translate(const Vector2D& vec);
CBox& round();
CBox& transform(const eTransform t, double w, double h);
CBox& addExtents(const SBoxExtents& e);
CBox& expand(const double& value);
CBox& noNegativeSize();
CBox copy() const;
CBox intersection(const CBox& other) const;
bool overlaps(const CBox& other) const;
bool inside(const CBox& bound) const;
/**
* @brief Computes the extents of the box relative to another box.
* @param small Another CBox to compare against.
* @return SBoxExtents representing the extents of the box relative to 'small'.
*/
SBoxExtents extentsFrom(const CBox&); // this is the big box
/**
* @brief Calculates the middle point of the box.
* @return Vector2D representing the middle point.
*/
Vector2D middle() const;
/**
* @brief Retrieves the position of the top-left corner of the box.
* @return Vector2D representing the position.
*/
Vector2D pos() const;
/**
* @brief Retrieves the size (width and height) of the box.
* @return Vector2D representing the size.
*/
Vector2D size() const;
/**
* @brief Retrieves the size of the box offset by its position.
* @return Vector2D representing the bottom right extent of the box.
*/
Vector2D extent() const;
/**
* @brief Finds the closest point within the box to a given vector.
* @param vec Vector from which to find the closest point.
* @return Vector2D representing the closest point within the box.
*/
Vector2D closestPoint(const Vector2D& vec) const;
/**
* @brief Checks if a given point is inside the box.
* @param vec Vector representing the point to check.
* @return True if the point is inside the box, false otherwise.
*/
bool containsPoint(const Vector2D& vec) const;
/**
* @brief Checks if the box is empty (zero width or height).
* @return True if the box is empty, false otherwise.
*/
bool empty() const;
double x = 0, y = 0; // Position of the top-left corner of the box.
union {
double w;
double width;
};
union {
double h;
double height;
};
double rot = 0; //< Rotation angle of the box in radians (counterclockwise).
/**
* @brief Checks equality between two CBox objects.
* @param rhs Another CBox object to compare.
* @return True if both CBox objects are equal, false otherwise.
*/
bool operator==(const CBox& rhs) const {
return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h;
}
private:
CBox roundInternal();
};
}

View file

@ -0,0 +1,112 @@
#pragma once
#include "hyprutils/memory/Casts.hpp"
#include <cstdint>
namespace Hyprutils::Math {
/**
* @brief Flag set of box edges
*/
class CEdges {
public:
enum eEdges : uint8_t {
NONE = 0,
TOP = 1,
LEFT = 2,
BOTTOM = 4,
RIGHT = 8,
};
CEdges() = default;
CEdges(eEdges edges) : m_edges(edges) {}
CEdges(uint8_t edges) : m_edges(Memory::sc<eEdges>(edges)) {}
bool operator==(const CEdges& other) {
return m_edges == other.m_edges;
}
CEdges operator|(const CEdges& other) {
return m_edges | other.m_edges;
}
CEdges operator&(const CEdges& other) {
return m_edges & other.m_edges;
}
CEdges operator^(const CEdges& other) {
return m_edges ^ other.m_edges;
}
void operator|=(const CEdges& other) {
m_edges = (*this | other).m_edges;
}
void operator&=(const CEdges& other) {
m_edges = (*this & other).m_edges;
}
void operator^=(const CEdges& other) {
m_edges = (*this ^ other).m_edges;
}
/**
* @return if the edge set contains the top edge.
*/
bool top() {
return m_edges & TOP;
}
/**
* @return if the edge set contains the left edge.
*/
bool left() {
return m_edges & LEFT;
}
/**
* @return if the edge set contains the bottom edge.
*/
bool bottom() {
return m_edges & BOTTOM;
}
/**
* @return if the edge set contains the right edge.
*/
bool right() {
return m_edges & RIGHT;
}
/**
* @param top The state the top edge should be set to.
*/
void setTop(bool top) {
m_edges = Memory::sc<eEdges>((m_edges & ~TOP) | (TOP * top));
}
/**
* @param left The state the left edge should be set to.
*/
void setLeft(bool left) {
m_edges = Memory::sc<eEdges>((m_edges & ~LEFT) | (LEFT * left));
}
/**
* @param bottom The state the bottom edge should be set to.
*/
void setBottom(bool bottom) {
m_edges = Memory::sc<eEdges>((m_edges & ~BOTTOM) | (BOTTOM * bottom));
}
/**
* @param right The state the right edge should be set to.
*/
void setRight(bool right) {
m_edges = Memory::sc<eEdges>((m_edges & ~RIGHT) | (RIGHT * right));
}
eEdges m_edges = NONE;
};
}

View file

@ -0,0 +1,59 @@
#pragma once
#include <array>
#include <vector>
#include <string>
#include <ostream>
#include "./Misc.hpp"
namespace Hyprutils {
namespace Math {
class CBox;
class Vector2D;
class Mat3x3 {
public:
Mat3x3();
Mat3x3(std::array<float, 9>);
Mat3x3(std::vector<float>);
/* create an identity 3x3 matrix */
static Mat3x3 identity();
/* create an output projection matrix */
static Mat3x3 outputProjection(const Vector2D& size, eTransform transform);
/* get the matrix as an array, in a row-major order. */
std::array<float, 9> getMatrix() const;
/* create a box projection matrix */
Mat3x3 projectBox(const CBox& box, eTransform transform, float rot = 0.F /* rad, CCW */) const;
/* in-place functions */
Mat3x3& transform(eTransform transform);
Mat3x3& rotate(float rot /* rad, CCW */);
Mat3x3& scale(const Vector2D& scale);
Mat3x3& scale(const float scale);
Mat3x3& translate(const Vector2D& offset);
Mat3x3& transpose();
Mat3x3& multiply(const Mat3x3& other);
/* misc utils */
Mat3x3 copy() const;
std::string toString() const;
bool operator==(const Mat3x3& other) const {
return other.matrix == matrix;
}
friend std::ostream& operator<<(std::ostream& os, const Mat3x3& mat) {
os << mat.toString();
return os;
}
private:
std::array<float, 9> matrix;
};
}
}

View file

@ -0,0 +1,16 @@
#pragma once
namespace Hyprutils {
namespace Math {
enum eTransform {
HYPRUTILS_TRANSFORM_NORMAL = 0,
HYPRUTILS_TRANSFORM_90 = 1,
HYPRUTILS_TRANSFORM_180 = 2,
HYPRUTILS_TRANSFORM_270 = 3,
HYPRUTILS_TRANSFORM_FLIPPED = 4,
HYPRUTILS_TRANSFORM_FLIPPED_90 = 5,
HYPRUTILS_TRANSFORM_FLIPPED_180 = 6,
HYPRUTILS_TRANSFORM_FLIPPED_270 = 7,
};
}
}

View file

@ -0,0 +1,87 @@
#pragma once
#include <pixman.h>
#include <vector>
#include "Vector2D.hpp"
#include "Box.hpp"
namespace Hyprutils {
namespace Math {
class CRegion {
public:
/* Create an empty region */
CRegion();
/* Create from a reference. Copies, does not own. */
CRegion(const pixman_region32_t* const ref);
/* Create from a box */
CRegion(double x, double y, double w, double h);
/* Create from a CBox */
CRegion(const CBox& box);
/* Create from a pixman_box32_t */
CRegion(pixman_box32_t* box);
CRegion(const CRegion&);
CRegion(CRegion&&) noexcept;
~CRegion();
CRegion& operator=(CRegion&& other) noexcept {
if (this != &other)
pixman_region32_copy(&m_rRegion, other.pixman());
return *this;
}
CRegion& operator=(const CRegion& other) {
if (this != &other)
pixman_region32_copy(&m_rRegion, other.pixman());
return *this;
}
CRegion& clear();
CRegion& set(const CRegion& other);
CRegion& add(const CRegion& other);
CRegion& add(double x, double y, double w, double h);
CRegion& add(const CBox& other);
CRegion& subtract(const CRegion& other);
CRegion& intersect(const CRegion& other);
CRegion& intersect(double x, double y, double w, double h);
CRegion& translate(const Vector2D& vec);
CRegion& transform(const eTransform t, double w, double h);
CRegion& invert(pixman_box32_t* box);
CRegion& invert(const CBox& box);
CRegion& scale(float scale);
CRegion& scale(const Vector2D& scale);
CRegion& expand(double units);
CRegion& rationalize();
CBox getExtents();
bool containsPoint(const Vector2D& vec) const;
bool empty() const;
Vector2D closestPoint(const Vector2D& vec) const;
CRegion copy() const;
std::vector<pixman_box32_t> getRects() const;
template <typename T>
void forEachRect(T&& cb) const {
int rectsNum = 0;
const auto* rects = pixman_region32_rectangles(&m_rRegion, &rectsNum);
for (int i = 0; i < rectsNum; ++i) {
std::forward<T>(cb)(rects[i]);
}
}
//
pixman_region32_t* pixman() {
return &m_rRegion;
}
const pixman_region32_t* pixman() const {
return &m_rRegion;
}
private:
pixman_region32_t m_rRegion;
};
}
}

View file

@ -0,0 +1,170 @@
#pragma once
#include <hyprutils/memory/Casts.hpp>
#include <hyprutils/math/Misc.hpp>
#include <format>
#include <string>
namespace Hyprutils {
namespace Math {
class Vector2D {
public:
constexpr Vector2D(double xx, double yy) : x(xx), y(yy) {
;
}
constexpr Vector2D(int xx, int yy) : x(Hyprutils::Memory::sc<double>(xx)), y(Hyprutils::Memory::sc<double>(yy)) {
;
}
constexpr Vector2D() = default;
~Vector2D() = default;
double x = 0;
double y = 0;
// returns the scale
double normalize();
constexpr Vector2D operator+(const Vector2D& a) const {
return Vector2D(this->x + a.x, this->y + a.y);
}
constexpr Vector2D operator-(const Vector2D& a) const {
return Vector2D(this->x - a.x, this->y - a.y);
}
constexpr Vector2D operator-() const {
return Vector2D(-this->x, -this->y);
}
constexpr Vector2D operator*(const double& a) const {
return Vector2D(this->x * a, this->y * a);
}
constexpr Vector2D operator/(const double& a) const {
return Vector2D(this->x / a, this->y / a);
}
constexpr bool operator==(const Vector2D& a) const {
return a.x == x && a.y == y;
}
constexpr bool operator!=(const Vector2D& a) const {
return a.x != x || a.y != y;
}
constexpr Vector2D operator*(const Vector2D& a) const {
return Vector2D(this->x * a.x, this->y * a.y);
}
constexpr Vector2D operator/(const Vector2D& a) const {
return Vector2D(this->x / a.x, this->y / a.y);
}
constexpr bool operator>(const Vector2D& a) const {
return this->x > a.x && this->y > a.y;
}
constexpr bool operator<(const Vector2D& a) const {
return this->x < a.x && this->y < a.y;
}
constexpr Vector2D& operator+=(const Vector2D& a) {
this->x += a.x;
this->y += a.y;
return *this;
}
constexpr Vector2D& operator-=(const Vector2D& a) {
this->x -= a.x;
this->y -= a.y;
return *this;
}
constexpr Vector2D& operator*=(const Vector2D& a) {
this->x *= a.x;
this->y *= a.y;
return *this;
}
constexpr Vector2D& operator/=(const Vector2D& a) {
this->x /= a.x;
this->y /= a.y;
return *this;
}
constexpr Vector2D& operator*=(const double& a) {
this->x *= a;
this->y *= a;
return *this;
}
constexpr Vector2D& operator/=(const double& a) {
this->x /= a;
this->y /= a;
return *this;
}
double distance(const Vector2D& other) const;
double distanceSq(const Vector2D& other) const;
double size() const;
Vector2D clamp(const Vector2D& min, const Vector2D& max = Vector2D{-1, -1}) const;
Vector2D floor() const;
Vector2D round() const;
Vector2D getComponentMax(const Vector2D& other) const;
Vector2D transform(eTransform transform, const Vector2D& monitorSize) const;
};
}
}
// absolutely ridiculous formatter spec parsing
#define AQ_FORMAT_PARSE(specs__, type__) \
template <typename FormatContext> \
constexpr auto parse(FormatContext& ctx) { \
auto it = ctx.begin(); \
for (; it != ctx.end() && *it != '}'; it++) { \
switch (*it) { specs__ default : throw std::format_error("invalid format specification"); } \
} \
return it; \
}
#define AQ_FORMAT_FLAG(spec__, flag__) \
case spec__: (flag__) = true; break;
#define AQ_FORMAT_NUMBER(buf__) \
case '0': \
case '1': \
case '2': \
case '3': \
case '4': \
case '5': \
case '6': \
case '7': \
case '8': \
case '9': (buf__).push_back(*it); break;
/**
format specification
- 'j', as json array
- 'X', same as std::format("{}x{}", vec.x, vec.y)
- number, floating point precision, use `0` to format as integer
*/
template <typename CharT>
struct std::formatter<Hyprutils::Math::Vector2D, CharT> : std::formatter<CharT> {
bool formatJson = false;
bool formatX = false;
std::string precision = "";
AQ_FORMAT_PARSE(AQ_FORMAT_FLAG('j', formatJson) //
AQ_FORMAT_FLAG('X', formatX) //
AQ_FORMAT_NUMBER(precision),
Hyprutils::Math::Vector2D)
template <typename FormatContext>
auto format(const Hyprutils::Math::Vector2D& vec, FormatContext& ctx) const {
std::string formatString = precision.empty() ? "{}" : std::format("{{:.{}f}}", precision);
if (formatJson)
formatString = std::format("[{0}, {0}]", formatString);
else if (formatX)
formatString = std::format("{0}x{0}", formatString);
else
formatString = std::format("[Vector2D: x: {0}, y: {0}]", formatString);
try {
string buf = std::vformat(formatString, std::make_format_args(vec.x, vec.y));
return std::format_to(ctx.out(), "{}", buf);
} catch (std::format_error& e) { return std::format_to(ctx.out(), "[{}, {}]", vec.x, vec.y); }
}
};

View file

@ -0,0 +1,437 @@
#pragma once
#include "./ImplBase.hpp"
#include "./SharedPtr.hpp"
#include "./WeakPtr.hpp"
#include <mutex>
/*
This header provides a thread-safe wrapper for Hyprutils shared pointer implementations.
Like with STL shared pointers, that does not mean that individual SP/WP objects can be shared across threads without synchronization.
It only means that the refcounting of the data is thread-safe.
Should an Atomic SP/WP be shared across threads, calling a non-const member leads to a data race.
To avoid that, each thread should have thread-local SP/WP objects.
Example:
We have a CAtomicSharedPointer member in a class. Suppose this member is accessed by multiple threads and is not constant.
In such a case we need external synchronization to ensure valid data access.
However, if we create a copy of this CAtomicWeakPointer member for each thread that accesses it,
then the references to the object will be counted in a thread-safe manner and it will be safe to lock a WP and to access the data in case of an SP.
In such an example, the inner data would need its own synchronization mechanism if it isn't constant itself.
*/
namespace Hyprutils::Memory {
namespace Atomic_ {
class impl : public Impl_::impl_base {
std::recursive_mutex m_mutex;
public:
impl(void* data, DeleteFn deleter) noexcept : Impl_::impl_base(data, deleter) {
;
}
std::lock_guard<std::recursive_mutex> lockGuard() {
return std::lock_guard<std::recursive_mutex>(m_mutex);
}
// Needed when unlock order or mutex lifetime matters.
std::recursive_mutex& getMutex() {
return m_mutex;
}
};
}
// Forward declaration for friend
template <typename T>
class CAtomicWeakPointer;
template <typename T>
class CAtomicSharedPointer {
template <typename X>
using isConstructible = std::enable_if_t<std::is_constructible_v<T&, X&>>;
template <typename X>
using validHierarchy = std::enable_if_t<std::is_assignable_v<CAtomicSharedPointer<T>&, X>, CAtomicSharedPointer&>;
public:
explicit CAtomicSharedPointer(T* object) noexcept : m_ptr(new Atomic_::impl(sc<void*>(object), _delete), sc<void*>(object)) {
;
}
CAtomicSharedPointer(Impl_::impl_base* impl, void* data) noexcept : m_ptr(impl, data) {
;
}
CAtomicSharedPointer(const CAtomicSharedPointer<T>& ref) {
if (!ref.m_ptr.impl_)
return;
auto lg = ref.implLockGuard();
m_ptr = ref.m_ptr;
}
template <typename U, typename = isConstructible<U>>
CAtomicSharedPointer(const CAtomicSharedPointer<U>& ref) {
if (!ref.m_ptr.impl_)
return;
auto lg = ref.implLockGuard();
m_ptr = ref.m_ptr;
}
template <typename U, typename = isConstructible<U>>
CAtomicSharedPointer(CAtomicSharedPointer<U>&& ref) noexcept {
std::swap(m_ptr, ref.m_ptr);
}
CAtomicSharedPointer(CAtomicSharedPointer&& ref) noexcept {
std::swap(m_ptr, ref.m_ptr);
}
CAtomicSharedPointer() noexcept = default;
CAtomicSharedPointer(std::nullptr_t) noexcept {
; // empty
}
~CAtomicSharedPointer() {
reset();
}
template <typename U>
validHierarchy<const CAtomicSharedPointer<U>&> operator=(const CAtomicSharedPointer<U>& rhs) {
reset();
if (!rhs.m_ptr.impl_)
return *this;
auto lg = rhs.implLockGuard();
m_ptr = rhs.m_ptr;
return *this;
}
CAtomicSharedPointer& operator=(const CAtomicSharedPointer& rhs) {
if (this == &rhs)
return *this;
reset();
if (!rhs.m_ptr.impl_)
return *this;
auto lg = rhs.implLockGuard();
m_ptr = rhs.m_ptr;
return *this;
}
template <typename U>
validHierarchy<const CAtomicSharedPointer<U>&> operator=(CAtomicSharedPointer<U>&& rhs) noexcept {
std::swap(m_ptr, rhs.m_ptr);
return *this;
}
CAtomicSharedPointer& operator=(CAtomicSharedPointer&& rhs) noexcept {
if (this == &rhs)
return *this;
std::swap(m_ptr, rhs.m_ptr);
return *this;
}
void reset() {
if (!m_ptr.impl_)
return;
// last ref and last wref?
// -> must unlock BEFORE reset
// not last ref?
// -> must unlock AFTER reset
auto& mutex = sc<Atomic_::impl*>(m_ptr.impl_)->getMutex();
mutex.lock();
if (m_ptr.impl_->ref() > 1) {
m_ptr.reset();
mutex.unlock();
return;
}
if (m_ptr.impl_->wref() == 0) {
mutex.unlock(); // Don't hold the mutex when destroying it
m_ptr.impl_->destroy();
delete sc<Atomic_::impl*>(m_ptr.impl_);
m_ptr.impl_ = nullptr;
// mutex invalid
return;
} else {
// When the control block gets destroyed, the mutex is destroyed with it.
// Thus we must avoid attempting an unlock after impl_ has been destroyed.
// Without the workaround is no safe way of checking whether it has been destroyed or not.
//
// To avoid this altogether, keep a weak pointer here.
// This guarantees that impl_ is still valid after the reset.
CWeakPointer<T> guard = m_ptr;
m_ptr.reset(); // destroys the data
// Now we can safely check if guard is the last wref.
if (guard.impl_->wref() == 1) {
mutex.unlock();
// destroy impl_ (includes the mutex)
delete sc<Atomic_::impl*>(guard.impl_);
guard.impl_ = nullptr;
// mutex invalid
return;
}
guard.reset();
mutex.unlock();
}
}
T& operator*() const {
return *m_ptr;
}
T* operator->() const {
return m_ptr.get();
}
T* get() const {
return m_ptr.get();
}
operator bool() const {
return m_ptr;
}
bool operator==(const CAtomicSharedPointer& rhs) const {
return m_ptr == rhs.m_ptr;
}
bool operator()(const CAtomicSharedPointer& lhs, const CAtomicSharedPointer& rhs) const {
return lhs.m_ptr == rhs.m_ptr;
}
unsigned int strongRef() const {
return m_ptr.impl_ ? m_ptr.impl_->ref() : 0;
}
Atomic_::impl* impl() const {
return sc<Atomic_::impl*>(m_ptr.impl_);
}
private:
static void _delete(void* p) {
std::default_delete<T>{}(sc<T*>(p));
}
std::lock_guard<std::recursive_mutex> implLockGuard() const {
return impl()->lockGuard();
}
CSharedPointer<T> m_ptr;
template <typename U>
friend class CAtomicWeakPointer;
template <typename U>
friend class CAtomicSharedPointer;
};
template <typename T>
class CAtomicWeakPointer {
template <typename X>
using isConstructible = std::enable_if_t<std::is_constructible_v<T&, X&>>;
template <typename X>
using validHierarchy = std::enable_if_t<std::is_assignable_v<CAtomicWeakPointer<T>&, X>, CAtomicWeakPointer&>;
public:
CAtomicWeakPointer(const CAtomicWeakPointer<T>& ref) {
if (!ref.m_ptr.impl_)
return;
auto lg = ref.implLockGuard();
m_ptr = ref.m_ptr;
}
template <typename U, typename = isConstructible<U>>
CAtomicWeakPointer(const CAtomicWeakPointer<U>& ref) {
if (!ref.m_ptr.impl_)
return;
auto lg = ref.implLockGuard();
m_ptr = ref.m_ptr;
}
template <typename U, typename = isConstructible<U>>
CAtomicWeakPointer(CAtomicWeakPointer<U>&& ref) noexcept {
std::swap(m_ptr, ref.m_ptr);
}
CAtomicWeakPointer(CAtomicWeakPointer&& ref) noexcept {
std::swap(m_ptr, ref.m_ptr);
}
CAtomicWeakPointer(const CAtomicSharedPointer<T>& ref) {
if (!ref.m_ptr.impl_)
return;
auto lg = ref.implLockGuard();
m_ptr = ref.m_ptr;
}
CAtomicWeakPointer() noexcept = default;
CAtomicWeakPointer(std::nullptr_t) noexcept {
; // empty
}
~CAtomicWeakPointer() {
reset();
}
template <typename U>
validHierarchy<const CAtomicWeakPointer<U>&> operator=(const CAtomicWeakPointer<U>& rhs) {
reset();
auto lg = rhs.implLockGuard();
m_ptr = rhs.m_ptr;
return *this;
}
CAtomicWeakPointer& operator=(const CAtomicWeakPointer& rhs) {
if (this == &rhs)
return *this;
reset();
auto lg = rhs.implLockGuard();
m_ptr = rhs.m_ptr;
return *this;
}
template <typename U>
validHierarchy<const CAtomicWeakPointer<U>&> operator=(CAtomicWeakPointer<U>&& rhs) noexcept {
std::swap(m_ptr, rhs.m_ptr);
return *this;
}
CAtomicWeakPointer& operator=(CAtomicWeakPointer&& rhs) noexcept {
if (this == &rhs)
return *this;
std::swap(m_ptr, rhs.m_ptr);
return *this;
}
void reset() {
if (!m_ptr.impl_)
return;
// last ref and last wref?
// -> must unlock BEFORE reset
// not last ref?
// -> must unlock AFTER reset
auto& mutex = sc<Atomic_::impl*>(m_ptr.impl_)->getMutex();
mutex.lock();
if (m_ptr.impl_->ref() == 0 && m_ptr.impl_->wref() == 1) {
mutex.unlock();
delete sc<Atomic_::impl*>(m_ptr.impl_);
m_ptr.impl_ = nullptr;
// mutex invalid
return;
}
m_ptr.reset();
mutex.unlock();
}
T& operator*() const {
return *m_ptr;
}
T* operator->() const {
return m_ptr.get();
}
T* get() const {
return m_ptr.get();
}
operator bool() const {
return m_ptr;
}
bool operator==(const CAtomicWeakPointer& rhs) const {
return m_ptr == rhs.m_ptr;
}
bool operator==(const CAtomicSharedPointer<T>& rhs) const {
return m_ptr == rhs.m_ptr;
}
bool operator()(const CAtomicWeakPointer& lhs, const CAtomicWeakPointer& rhs) const {
return lhs.m_ptr == rhs.m_ptr;
}
bool expired() {
return m_ptr.expired();
}
bool valid() {
return m_ptr.valid();
}
CAtomicSharedPointer<T> lock() const {
if (!m_ptr.impl_)
return {};
auto lg = implLockGuard();
if (!m_ptr.impl_->dataNonNull() || m_ptr.impl_->destroying() || !m_ptr.impl_->lockable())
return {};
return CAtomicSharedPointer<T>(m_ptr.impl_, m_ptr.m_data);
}
Atomic_::impl* impl() const {
return sc<Atomic_::impl*>(m_ptr.impl_);
}
private:
std::lock_guard<std::recursive_mutex> implLockGuard() const {
return impl()->lockGuard();
}
CWeakPointer<T> m_ptr;
template <typename U>
friend class CAtomicWeakPointer;
template <typename U>
friend class CAtomicSharedPointer;
};
template <typename U, typename... Args>
[[nodiscard]] inline CAtomicSharedPointer<U> makeAtomicShared(Args&&... args) {
return CAtomicSharedPointer<U>(new U(std::forward<Args>(args)...));
}
template <typename T, typename U>
CAtomicSharedPointer<T> reinterpretPointerCast(const CAtomicSharedPointer<U>& ref) {
return CAtomicSharedPointer<T>(ref.impl(), ref.m_data);
}
template <typename T, typename U>
CAtomicSharedPointer<T> dynamicPointerCast(const CAtomicSharedPointer<U>& ref) {
if (!ref)
return nullptr;
T* newPtr = dynamic_cast<T*>(sc<U*>(ref.impl()->getData()));
if (!newPtr)
return nullptr;
return CAtomicSharedPointer<T>(ref.impl(), newPtr);
}
}

View file

@ -0,0 +1,29 @@
#pragma once
#include <bit>
#include <utility>
namespace Hyprutils::Memory {
template <typename To, typename From>
constexpr To sc(From&& from) noexcept {
return static_cast<To>(std::forward<From>(from));
}
template <typename To, typename From>
constexpr To cc(From&& from) noexcept {
return const_cast<To>(std::forward<From>(from));
}
template <typename To, typename From>
constexpr To rc(From&& from) noexcept {
return reinterpret_cast<To>(std::forward<From>(from));
}
template <typename To, typename From>
constexpr To dc(From&& from) {
return dynamic_cast<To>(std::forward<From>(from));
}
template <typename To, typename From>
constexpr To bc(const From& from) noexcept {
return std::bit_cast<To>(from);
}
}

View file

@ -0,0 +1,98 @@
#pragma once
#include <cstdint>
#include <memory>
namespace Hyprutils {
namespace Memory {
namespace Impl_ {
class impl_base {
public:
using DeleteFn = void (*)(void*);
impl_base(void* data, DeleteFn deleter, bool lock = true) noexcept : _lockable(lock), _data(data), _deleter(deleter) {
;
}
void inc() noexcept {
_ref++;
}
void dec() noexcept {
_ref--;
}
void incWeak() noexcept {
_weak++;
}
void decWeak() noexcept {
_weak--;
}
unsigned int ref() noexcept {
return _ref;
}
unsigned int wref() noexcept {
return _weak;
}
void destroy() noexcept {
_destroy();
}
bool destroying() noexcept {
return _destroying;
}
bool lockable() noexcept {
return _lockable;
}
bool dataNonNull() noexcept {
return _data != nullptr;
}
void* getData() noexcept {
return _data;
}
~impl_base() {
destroy();
}
private:
/* strong refcount */
unsigned int _ref = 0;
/* weak refcount */
unsigned int _weak = 0;
/* if this is lockable (shared) */
bool _lockable = true;
void* _data = nullptr;
/* if the destructor was called,
creating shared_ptrs is no longer valid */
bool _destroying = false;
void _destroy() {
if (!_data || _destroying)
return;
// first, we destroy the data, but keep the pointer.
// this way, weak pointers will still be able to
// reference and use, but no longer create shared ones.
_destroying = true;
_deleter(_data);
// now, we can reset the data and call it a day.
_data = nullptr;
_destroying = false;
}
DeleteFn _deleter = nullptr;
};
}
}
}

View file

@ -1,8 +1,8 @@
#pragma once #pragma once
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <memory>
#include "ImplBase.hpp"
#include "Casts.hpp"
/* /*
This is a custom impl of std::shared_ptr. This is a custom impl of std::shared_ptr.
@ -17,151 +17,49 @@
namespace Hyprutils { namespace Hyprutils {
namespace Memory { namespace Memory {
namespace CSharedPointer_ {
class impl_base {
public:
virtual ~impl_base(){};
virtual void inc() noexcept = 0;
virtual void dec() noexcept = 0;
virtual void incWeak() noexcept = 0;
virtual void decWeak() noexcept = 0;
virtual unsigned int ref() noexcept = 0;
virtual unsigned int wref() noexcept = 0;
virtual void destroy() noexcept = 0;
virtual bool destroying() noexcept = 0;
virtual bool dataNonNull() noexcept = 0;
};
template <typename T>
class impl : public impl_base {
public:
impl(T* data) noexcept : _data(data) {
;
}
/* strong refcount */
unsigned int _ref = 0;
/* weak refcount */
unsigned int _weak = 0;
T* _data = nullptr;
friend void swap(impl*& a, impl*& b) {
impl* tmp = a;
a = b;
b = tmp;
}
/* if the destructor was called,
creating shared_ptrs is no longer valid */
bool _destroying = false;
void _destroy() {
if (!_data || _destroying)
return;
// first, we destroy the data, but keep the pointer.
// this way, weak pointers will still be able to
// reference and use, but no longer create shared ones.
_destroying = true;
__deleter(_data);
// now, we can reset the data and call it a day.
_data = nullptr;
_destroying = false;
}
std::default_delete<T> __deleter{};
//
virtual void inc() noexcept {
_ref++;
}
virtual void dec() noexcept {
_ref--;
}
virtual void incWeak() noexcept {
_weak++;
}
virtual void decWeak() noexcept {
_weak--;
}
virtual unsigned int ref() noexcept {
return _ref;
}
virtual unsigned int wref() noexcept {
return _weak;
}
virtual void destroy() noexcept {
_destroy();
}
virtual bool destroying() noexcept {
return _destroying;
}
virtual bool dataNonNull() noexcept {
return _data;
}
virtual ~impl() {
destroy();
}
};
};
template <typename T> template <typename T>
class CSharedPointer { class CSharedPointer {
public: public:
template <typename X> template <typename X>
using validHierarchy = typename std::enable_if<std::is_assignable<CSharedPointer<T>&, X>::value, CSharedPointer&>::type; using validHierarchy = std::enable_if_t<std::is_assignable_v<CSharedPointer<T>&, X>, CSharedPointer&>;
template <typename X> template <typename X>
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type; using isConstructible = std::enable_if_t<std::is_constructible_v<T&, X&>>;
/* creates a new shared pointer managing a resource /* creates a new shared pointer managing a resource
avoid calling. Could duplicate ownership. Prefer makeShared */ avoid calling. Could duplicate ownership. Prefer makeShared */
explicit CSharedPointer(T* object) noexcept { explicit CSharedPointer(T* object) noexcept : impl_(new Impl_::impl_base(sc<void*>(object), _delete)), m_data(sc<void*>(object)) {
impl_ = new CSharedPointer_::impl<T>(object);
increment(); increment();
} }
/* creates a shared pointer from a reference */ /* creates a shared pointer from a reference */
template <typename U, typename = isConstructible<U>> template <typename U, typename = isConstructible<U>>
CSharedPointer(const CSharedPointer<U>& ref) noexcept { CSharedPointer(const CSharedPointer<U>& ref) noexcept : impl_(ref.impl_), m_data(ref.m_data) {
impl_ = ref.impl_;
increment(); increment();
} }
CSharedPointer(const CSharedPointer& ref) noexcept { CSharedPointer(const CSharedPointer& ref) noexcept : impl_(ref.impl_), m_data(ref.m_data) {
impl_ = ref.impl_;
increment(); increment();
} }
template <typename U, typename = isConstructible<U>> template <typename U, typename = isConstructible<U>>
CSharedPointer(CSharedPointer<U>&& ref) noexcept { CSharedPointer(CSharedPointer<U>&& ref) noexcept {
std::swap(impl_, ref.impl_); std::swap(impl_, ref.impl_);
std::swap(m_data, ref.m_data);
} }
CSharedPointer(CSharedPointer&& ref) noexcept { CSharedPointer(CSharedPointer&& ref) noexcept {
std::swap(impl_, ref.impl_); std::swap(impl_, ref.impl_);
std::swap(m_data, ref.m_data);
} }
/* allows weakPointer to create from an impl */ /* allows weakPointer to create from an impl */
CSharedPointer(CSharedPointer_::impl_base* implementation) noexcept { CSharedPointer(Impl_::impl_base* implementation, void* data) noexcept : impl_(implementation), m_data(data) {
impl_ = implementation;
increment(); increment();
} }
/* creates an empty shared pointer with no implementation */ /* creates an empty shared pointer with no implementation */
CSharedPointer() noexcept { CSharedPointer() noexcept = default;
; // empty
}
/* creates an empty shared pointer with no implementation */ /* creates an empty shared pointer with no implementation */
CSharedPointer(std::nullptr_t) noexcept { CSharedPointer(std::nullptr_t) noexcept {
@ -169,13 +67,7 @@ namespace Hyprutils {
} }
~CSharedPointer() { ~CSharedPointer() {
// we do not decrement here, decrement();
// because we want to preserve the pointer
// in case this is the last owner.
if (impl_ && impl_->ref() == 1)
destroyImpl();
else
decrement();
} }
template <typename U> template <typename U>
@ -184,7 +76,8 @@ namespace Hyprutils {
return *this; return *this;
decrement(); decrement();
impl_ = rhs.impl_; impl_ = rhs.impl_;
m_data = rhs.m_data;
increment(); increment();
return *this; return *this;
} }
@ -194,7 +87,8 @@ namespace Hyprutils {
return *this; return *this;
decrement(); decrement();
impl_ = rhs.impl_; impl_ = rhs.impl_;
m_data = rhs.m_data;
increment(); increment();
return *this; return *this;
} }
@ -202,11 +96,13 @@ namespace Hyprutils {
template <typename U> template <typename U>
validHierarchy<const CSharedPointer<U>&> operator=(CSharedPointer<U>&& rhs) { validHierarchy<const CSharedPointer<U>&> operator=(CSharedPointer<U>&& rhs) {
std::swap(impl_, rhs.impl_); std::swap(impl_, rhs.impl_);
std::swap(m_data, rhs.m_data);
return *this; return *this;
} }
CSharedPointer& operator=(CSharedPointer&& rhs) { CSharedPointer& operator=(CSharedPointer&& rhs) noexcept {
std::swap(impl_, rhs.impl_); std::swap(impl_, rhs.impl_);
std::swap(m_data, rhs.m_data);
return *this; return *this;
} }
@ -214,16 +110,18 @@ namespace Hyprutils {
return impl_ && impl_->dataNonNull(); return impl_ && impl_->dataNonNull();
} }
// this compares that the pointed-to object is the same, but in multiple inheritance,
// different typed pointers can be equal if the object is the same
bool operator==(const CSharedPointer& rhs) const { bool operator==(const CSharedPointer& rhs) const {
return impl_ == rhs.impl_; return impl_ == rhs.impl_;
} }
bool operator()(const CSharedPointer& lhs, const CSharedPointer& rhs) const { bool operator()(const CSharedPointer& lhs, const CSharedPointer& rhs) const {
return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_; return rc<uintptr_t>(lhs.impl_) < rc<uintptr_t>(rhs.impl_);
} }
bool operator<(const CSharedPointer& rhs) const { bool operator<(const CSharedPointer& rhs) const {
return (uintptr_t)impl_ < (uintptr_t)rhs.impl_; return rc<uintptr_t>(impl_) < rc<uintptr_t>(rhs.impl_);
} }
T* operator->() const { T* operator->() const {
@ -236,21 +134,29 @@ namespace Hyprutils {
void reset() { void reset() {
decrement(); decrement();
impl_ = nullptr; impl_ = nullptr;
m_data = nullptr;
} }
T* get() const { T* get() const {
return (T*)(impl_ ? static_cast<CSharedPointer_::impl<T>*>(impl_)->_data : nullptr); return impl_ && impl_->dataNonNull() ? sc<T*>(m_data) : nullptr;
} }
unsigned int strongRef() const { unsigned int strongRef() const {
return impl_ ? impl_->ref() : 0; return impl_ ? impl_->ref() : 0;
} }
CSharedPointer_::impl_base* impl_ = nullptr; Impl_::impl_base* impl_ = nullptr;
// Never use directly: raw data ptr, could be UAF
void* m_data = nullptr;
private: private:
/* static void _delete(void* p) {
std::default_delete<T>{}(sc<T*>(p));
}
/*
no-op if there is no impl_ no-op if there is no impl_
may delete the stored object if ref == 0 may delete the stored object if ref == 0
may delete and reset impl_ if ref == 0 and weak == 0 may delete and reset impl_ if ref == 0 and weak == 0
@ -273,7 +179,7 @@ namespace Hyprutils {
impl_->inc(); impl_->inc();
} }
/* destroy the pointed-to object /* destroy the pointed-to object
if able, will also destroy impl */ if able, will also destroy impl */
void destroyImpl() { void destroyImpl() {
// destroy the impl contents // destroy the impl contents
@ -288,9 +194,24 @@ namespace Hyprutils {
}; };
template <typename U, typename... Args> template <typename U, typename... Args>
static CSharedPointer<U> makeShared(Args&&... args) { [[nodiscard]] inline CSharedPointer<U> makeShared(Args&&... args) {
return CSharedPointer<U>(new U(std::forward<Args>(args)...)); return CSharedPointer<U>(new U(std::forward<Args>(args)...));
} }
template <typename T, typename U>
CSharedPointer<T> reinterpretPointerCast(const CSharedPointer<U>& ref) {
return CSharedPointer<T>(ref.impl_, ref.m_data);
}
template <typename T, typename U>
CSharedPointer<T> dynamicPointerCast(const CSharedPointer<U>& ref) {
if (!ref)
return nullptr;
T* newPtr = dynamic_cast<T*>(sc<U*>(ref.impl_->getData()));
if (!newPtr)
return nullptr;
return CSharedPointer<T>(ref.impl_, newPtr);
}
} }
} }
@ -299,4 +220,4 @@ struct std::hash<Hyprutils::Memory::CSharedPointer<T>> {
std::size_t operator()(const Hyprutils::Memory::CSharedPointer<T>& p) const noexcept { std::size_t operator()(const Hyprutils::Memory::CSharedPointer<T>& p) const noexcept {
return std::hash<void*>{}(p.impl_); return std::hash<void*>{}(p.impl_);
} }
}; };

View file

@ -0,0 +1,147 @@
#pragma once
#include "ImplBase.hpp"
#include "Casts.hpp"
/*
This is a custom impl of std::unique_ptr.
In contrast to the STL one, it allows for
creation of a weak_ptr, that will then be unable
to be locked.
*/
namespace Hyprutils {
namespace Memory {
template <typename T>
class CUniquePointer {
public:
template <typename X>
using validHierarchy = std::enable_if_t<std::is_assignable_v<CUniquePointer<T>&, X>, CUniquePointer&>;
template <typename X>
using isConstructible = std::enable_if_t<std::is_constructible_v<T&, X&>>;
/* creates a new unique pointer managing a resource
avoid calling. Could duplicate ownership. Prefer makeUnique */
explicit CUniquePointer(T* object) noexcept : impl_(new Impl_::impl_base(sc<void*>(object), [](void* p) { std::default_delete<T>{}(sc<T*>(p)); }, false)) {
increment();
}
/* creates a shared pointer from a reference */
template <typename U, typename = isConstructible<U>>
CUniquePointer(const CUniquePointer<U>& ref) = delete;
CUniquePointer(const CUniquePointer& ref) = delete;
template <typename U, typename = isConstructible<U>>
CUniquePointer(CUniquePointer<U>&& ref) noexcept {
std::swap(impl_, ref.impl_);
}
CUniquePointer(CUniquePointer&& ref) noexcept {
std::swap(impl_, ref.impl_);
}
/* creates an empty unique pointer with no implementation */
CUniquePointer() noexcept = default;
/* creates an empty unique pointer with no implementation */
CUniquePointer(std::nullptr_t) noexcept {
; // empty
}
~CUniquePointer() {
decrement();
}
template <typename U>
validHierarchy<const CUniquePointer<U>&> operator=(const CUniquePointer<U>& rhs) = delete;
CUniquePointer& operator=(const CUniquePointer& rhs) = delete;
template <typename U>
validHierarchy<const CUniquePointer<U>&> operator=(CUniquePointer<U>&& rhs) {
std::swap(impl_, rhs.impl_);
return *this;
}
CUniquePointer& operator=(CUniquePointer&& rhs) noexcept {
std::swap(impl_, rhs.impl_);
return *this;
}
operator bool() const {
return impl_;
}
bool operator()(const CUniquePointer& lhs, const CUniquePointer& rhs) const {
return rc<uintptr_t>(lhs.impl_) < rc<uintptr_t>(rhs.impl_);
}
T* operator->() const {
return get();
}
T& operator*() const {
return *get();
}
void reset() {
decrement();
impl_ = nullptr;
}
T* get() const {
return impl_ ? sc<T*>(impl_->getData()) : nullptr;
}
Impl_::impl_base* impl_ = nullptr;
private:
/*
no-op if there is no impl_
may delete the stored object if ref == 0
may delete and reset impl_ if ref == 0 and weak == 0
*/
void decrement() {
if (!impl_)
return;
impl_->dec();
// if ref == 0, we can destroy impl
if (impl_->ref() == 0)
destroyImpl();
}
/* no-op if there is no impl_ */
void increment() {
if (!impl_)
return;
impl_->inc();
}
/* destroy the pointed-to object
if able, will also destroy impl */
void destroyImpl() {
// destroy the impl contents
impl_->destroy();
// check for weak refs, if zero, we can also delete impl_
if (impl_->wref() == 0) {
delete impl_;
impl_ = nullptr;
}
}
};
template <typename U, typename... Args>
[[nodiscard]] inline CUniquePointer<U> makeUnique(Args&&... args) {
return CUniquePointer<U>(new U(std::forward<Args>(args)...));
}
}
}
template <typename T>
struct std::hash<Hyprutils::Memory::CUniquePointer<T>> {
std::size_t operator()(const Hyprutils::Memory::CUniquePointer<T>& p) const noexcept {
return std::hash<void*>{}(p.impl_);
}
};

View file

@ -1,6 +1,8 @@
#pragma once #pragma once
#include "./SharedPtr.hpp" #include "./SharedPtr.hpp"
#include "./UniquePtr.hpp"
#include "./Casts.hpp"
/* /*
This is a Hyprland implementation of std::weak_ptr. This is a Hyprland implementation of std::weak_ptr.
@ -14,9 +16,9 @@ namespace Hyprutils {
class CWeakPointer { class CWeakPointer {
public: public:
template <typename X> template <typename X>
using validHierarchy = typename std::enable_if<std::is_assignable<CWeakPointer<T>&, X>::value, CWeakPointer&>::type; using validHierarchy = std::enable_if_t<std::is_assignable_v<CWeakPointer<T>&, X>, CWeakPointer&>;
template <typename X> template <typename X>
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type; using isConstructible = std::enable_if_t<std::is_constructible_v<T&, X&>>;
/* create a weak ptr from a reference */ /* create a weak ptr from a reference */
template <typename U, typename = isConstructible<U>> template <typename U, typename = isConstructible<U>>
@ -24,7 +26,19 @@ namespace Hyprutils {
if (!ref.impl_) if (!ref.impl_)
return; return;
impl_ = ref.impl_; impl_ = ref.impl_;
m_data = ref.m_data;
incrementWeak();
}
/* create a weak ptr from a reference */
template <typename U, typename = isConstructible<U>>
CWeakPointer(const CUniquePointer<U>& ref) noexcept {
if (!ref.impl_)
return;
impl_ = ref.impl_;
m_data = ref.impl_->getData();
incrementWeak(); incrementWeak();
} }
@ -34,7 +48,8 @@ namespace Hyprutils {
if (!ref.impl_) if (!ref.impl_)
return; return;
impl_ = ref.impl_; impl_ = ref.impl_;
m_data = ref.m_data;
incrementWeak(); incrementWeak();
} }
@ -42,17 +57,20 @@ namespace Hyprutils {
if (!ref.impl_) if (!ref.impl_)
return; return;
impl_ = ref.impl_; impl_ = ref.impl_;
m_data = ref.m_data;
incrementWeak(); incrementWeak();
} }
template <typename U, typename = isConstructible<U>> template <typename U, typename = isConstructible<U>>
CWeakPointer(CWeakPointer<U>&& ref) noexcept { CWeakPointer(CWeakPointer<U>&& ref) noexcept {
std::swap(impl_, ref.impl_); std::swap(impl_, ref.impl_);
std::swap(m_data, ref.m_data);
} }
CWeakPointer(CWeakPointer&& ref) noexcept { CWeakPointer(CWeakPointer&& ref) noexcept {
std::swap(impl_, ref.impl_); std::swap(impl_, ref.impl_);
std::swap(m_data, ref.m_data);
} }
/* create a weak ptr from another weak ptr with assignment */ /* create a weak ptr from another weak ptr with assignment */
@ -62,7 +80,8 @@ namespace Hyprutils {
return *this; return *this;
decrementWeak(); decrementWeak();
impl_ = rhs.impl_; impl_ = rhs.impl_;
m_data = rhs.m_data;
incrementWeak(); incrementWeak();
return *this; return *this;
} }
@ -72,7 +91,8 @@ namespace Hyprutils {
return *this; return *this;
decrementWeak(); decrementWeak();
impl_ = rhs.impl_; impl_ = rhs.impl_;
m_data = rhs.m_data;
incrementWeak(); incrementWeak();
return *this; return *this;
} }
@ -80,19 +100,18 @@ namespace Hyprutils {
/* create a weak ptr from a shared ptr with assignment */ /* create a weak ptr from a shared ptr with assignment */
template <typename U> template <typename U>
validHierarchy<const CWeakPointer<U>&> operator=(const CSharedPointer<U>& rhs) { validHierarchy<const CWeakPointer<U>&> operator=(const CSharedPointer<U>& rhs) {
if ((uintptr_t)impl_ == (uintptr_t)rhs.impl_) if (rc<uintptr_t>(impl_) == rc<uintptr_t>(rhs.impl_))
return *this; return *this;
decrementWeak(); decrementWeak();
impl_ = rhs.impl_; impl_ = rhs.impl_;
m_data = rhs.m_data;
incrementWeak(); incrementWeak();
return *this; return *this;
} }
/* create an empty weak ptr */ /* create an empty weak ptr */
CWeakPointer() { CWeakPointer() noexcept = default;
;
}
~CWeakPointer() { ~CWeakPointer() {
decrementWeak(); decrementWeak();
@ -115,14 +134,15 @@ namespace Hyprutils {
void reset() { void reset() {
decrementWeak(); decrementWeak();
impl_ = nullptr; impl_ = nullptr;
m_data = nullptr;
} }
CSharedPointer<T> lock() const { CSharedPointer<T> lock() const {
if (!impl_ || !impl_->dataNonNull() || impl_->destroying()) if (!impl_ || !impl_->dataNonNull() || impl_->destroying() || !impl_->lockable())
return {}; return {};
return CSharedPointer<T>(impl_); return CSharedPointer<T>(impl_, m_data);
} }
/* this returns valid() */ /* this returns valid() */
@ -138,23 +158,42 @@ namespace Hyprutils {
return impl_ == rhs.impl_; return impl_ == rhs.impl_;
} }
bool operator==(const CUniquePointer<T>& rhs) const {
return impl_ == rhs.impl_;
}
bool operator==(std::nullptr_t) const {
return !valid();
}
bool operator!=(std::nullptr_t) const {
return valid();
}
bool operator()(const CWeakPointer& lhs, const CWeakPointer& rhs) const { bool operator()(const CWeakPointer& lhs, const CWeakPointer& rhs) const {
return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_; return rc<uintptr_t>(lhs.impl_) < rc<uintptr_t>(rhs.impl_);
} }
bool operator<(const CWeakPointer& rhs) const { bool operator<(const CWeakPointer& rhs) const {
return (uintptr_t)impl_ < (uintptr_t)rhs.impl_; return rc<uintptr_t>(impl_) < rc<uintptr_t>(rhs.impl_);
} }
T* get() const { T* get() const {
return (T*)(impl_ ? static_cast<CSharedPointer_::impl<T>*>(impl_)->_data : nullptr); return impl_ && impl_->dataNonNull() ? sc<T*>(m_data) : nullptr;
} }
T* operator->() const { T* operator->() const {
return get(); return get();
} }
CSharedPointer_::impl_base* impl_ = nullptr; T& operator*() const {
return *get();
}
Impl_::impl_base* impl_ = nullptr;
// Never use directly: raw data ptr, could be UAF
void* m_data = nullptr;
private: private:
/* no-op if there is no impl_ */ /* no-op if there is no impl_ */
@ -181,6 +220,16 @@ namespace Hyprutils {
impl_->incWeak(); impl_->incWeak();
} }
}; };
template <typename T, typename U>
CWeakPointer<T> dynamicPointerCast(const CWeakPointer<U>& ref) {
if (!ref)
return nullptr;
T* newPtr = dynamic_cast<T*>(sc<U*>(ref.impl_->getData()));
if (!newPtr)
return nullptr;
return CWeakPointer<T>(ref.impl_, newPtr);
}
} }
} }

View file

@ -0,0 +1,9 @@
#pragma once
#include <string>
#include <expected>
#include <string_view>
namespace Hyprutils::File {
std::expected<std::string, std::string> readFileAsString(const std::string_view& path);
}

View file

@ -0,0 +1,39 @@
#pragma once
#include <fcntl.h>
namespace Hyprutils {
namespace OS {
class CFileDescriptor {
public:
CFileDescriptor() = default;
explicit CFileDescriptor(int const fd);
CFileDescriptor(CFileDescriptor&&);
CFileDescriptor& operator=(CFileDescriptor&&);
~CFileDescriptor();
CFileDescriptor(const CFileDescriptor&) = delete;
CFileDescriptor& operator=(const CFileDescriptor&) = delete;
bool operator==(const CFileDescriptor& rhs) const {
return m_fd == rhs.m_fd;
}
bool isValid() const;
int get() const;
int getFlags() const;
bool setFlags(int flags);
int take();
void reset();
CFileDescriptor duplicate(int flags = F_DUPFD_CLOEXEC) const;
bool isReadable() const;
bool isClosed() const;
static bool isReadable(int fd);
static bool isClosed(int fd);
private:
int m_fd = -1;
};
};
};

View file

@ -0,0 +1,52 @@
#pragma once
#include <string>
#include <vector>
#include <utility>
#include <sys/types.h>
namespace Hyprutils {
namespace OS {
class CProcess {
public:
/* Creates a process object, doesn't run yet */
CProcess(const std::string& binary_, const std::vector<std::string>& args_);
~CProcess();
CProcess(CProcess&) = delete;
CProcess(CProcess&&) = delete;
CProcess(const CProcess&&) = delete;
CProcess(const CProcess&) = delete;
CProcess& operator=(const CProcess&) = delete;
CProcess& operator=(CProcess&&) = delete;
void addEnv(const std::string& name, const std::string& value);
// only for async, sync doesn't make sense
void setStdinFD(int fd);
// only for async, sync doesn't make sense
void setStdoutFD(int fd);
// only for async, sync doesn't make sense
void setStderrFD(int fd);
/* Run the process, synchronously, get the stdout and stderr. False on fail */
bool runSync();
/* Run the process, asynchronously. This will detach the process from this object (and process) and let it live a happy life. False on fail. */
bool runAsync();
// only populated when ran sync
const std::string& stdOut();
const std::string& stdErr();
pid_t pid();
// only for sync
int exitCode();
private:
struct impl;
impl* m_impl;
};
}
}

View file

@ -0,0 +1,42 @@
#pragma once
#include "../string/VarList.hpp"
#include <string>
#include <optional>
#include <utility>
namespace Hyprutils {
namespace Path {
/** Check whether a config in the form basePath/hypr/programName.conf exists.
@param basePath the path where the config will be searched
@param programName name of the program (and config file) to search for
*/
bool checkConfigExists(const std::string basePath, const std::string programName);
/** Constructs a full config path given the basePath and programName.
@param basePath the path where the config hypr/programName.conf is located
@param programName name of the program (and config file)
*/
std::string fullConfigPath(const std::string basePath, const std::string programName);
/** Retrieves the absolute path of the $HOME env variable.
*/
std::optional<std::string> getHome();
/** Retrieves a CVarList of paths from the $XDG_CONFIG_DIRS env variable.
*/
std::optional<String::CVarList> getXdgConfigDirs();
/** Retrieves the absolute path of the $XDG_CONFIG_HOME env variable.
*/
std::optional<std::string> getXdgConfigHome();
/** Searches for a config according to the XDG Base Directory specification.
Returns a pair of the full path to a config and the base path.
Returns std::nullopt in case of a non-existent value.
@param programName name of the program (and config file)
*/
using T = std::optional<std::string>;
std::pair<T, T> findConfig(const std::string programName);
}
}

View file

@ -6,39 +6,27 @@
namespace Hyprutils { namespace Hyprutils {
namespace Signal { namespace Signal {
class CSignal; class CSignalBase;
class CSignalListener { class CSignalListener {
public: public:
CSignalListener(std::function<void(std::any)> handler);
CSignalListener(CSignalListener&&) = delete; CSignalListener(CSignalListener&&) = delete;
CSignalListener(CSignalListener&) = delete; CSignalListener(CSignalListener&) = delete;
CSignalListener(const CSignalListener&) = delete; CSignalListener(const CSignalListener&) = delete;
CSignalListener(const CSignalListener&&) = delete; CSignalListener(const CSignalListener&&) = delete;
void emit(std::any data); [[deprecated("Relic of the legacy untyped signal API. Using this with CSignalT is undefined behavior.")]] void emit(std::any data);
private: private:
std::function<void(std::any)> m_fHandler; CSignalListener(std::function<void(void*)> handler);
void emitInternal(void* args);
std::function<void(void*)> m_fHandler;
friend class CSignalBase;
}; };
typedef Hyprutils::Memory::CSharedPointer<CSignalListener> CHyprSignalListener; typedef Hyprutils::Memory::CSharedPointer<CSignalListener> CHyprSignalListener;
class CStaticSignalListener {
public:
CStaticSignalListener(std::function<void(void*, std::any)> handler, void* owner);
CStaticSignalListener(CStaticSignalListener&&) = delete;
CStaticSignalListener(CStaticSignalListener&) = delete;
CStaticSignalListener(const CStaticSignalListener&) = delete;
CStaticSignalListener(const CStaticSignalListener&&) = delete;
void emit(std::any data);
private:
void* m_pOwner = nullptr;
std::function<void(void*, std::any)> m_fHandler;
};
} }
} }

View file

@ -2,27 +2,112 @@
#include <functional> #include <functional>
#include <any> #include <any>
#include <type_traits>
#include <utility>
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <tuple>
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/memory/WeakPtr.hpp> #include <hyprutils/memory/WeakPtr.hpp>
#include "./Listener.hpp" #include "./Listener.hpp"
namespace Hyprutils { namespace Hyprutils {
namespace Signal { namespace Signal {
class CSignal { class CSignalBase {
public: protected:
void emit(std::any data = {}); CHyprSignalListener registerListenerInternal(std::function<void(void*)> handler);
void registerStaticListenerInternal(std::function<void(void*)> handler);
void emitInternal(void* args);
// std::vector<Hyprutils::Memory::CWeakPointer<CSignalListener>> m_vListeners;
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener registerListener(std::function<void(std::any)> handler); std::vector<Hyprutils::Memory::CSharedPointer<CSignalListener>> m_vStaticListeners;
};
template <typename... Args>
class CSignalT : public CSignalBase {
template <typename T>
using RefArg = std::conditional_t<std::is_trivially_copyable_v<T>, T, const T&>;
public:
void emit(RefArg<Args>... args) {
if (m_vListeners.empty() && m_vStaticListeners.empty())
return;
if constexpr (sizeof...(Args) == 0)
emitInternal(nullptr);
else {
auto argsTuple = std::tuple<RefArg<Args>...>(args...);
if constexpr (sizeof...(Args) == 1)
// NOLINTNEXTLINE: const is reapplied by handler invocation if required
emitInternal(Memory::cc<void*>(Memory::sc<const void*>(&std::get<0>(argsTuple))));
else
emitInternal(&argsTuple);
}
}
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener listen(std::function<void(RefArg<Args>...)> handler) {
return registerListenerInternal(mkHandler(handler));
}
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener listen(std::function<void()> handler)
requires(sizeof...(Args) != 0)
{
return listen([handler](RefArg<Args>... args) { handler(); });
}
template <typename... OtherArgs>
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener forward(CSignalT<OtherArgs...>& signal) {
if constexpr (sizeof...(OtherArgs) == 0)
return listen([&signal](RefArg<Args>... args) { signal.emit(); });
else
return listen([&signal](RefArg<Args>... args) { signal.emit(args...); });
}
// deprecated, use listen()
CHyprSignalListener registerListener(std::function<void(std::any d)> handler) {
return listen([handler](const Args&... args) {
constexpr auto mkAny = [](std::any d = {}) { return d; };
handler(mkAny(args...));
});
}
// this is for static listeners. They die with this signal. // this is for static listeners. They die with this signal.
// TODO: can we somehow rid of the void* data and make it a custom this? void listenStatic(std::function<void(RefArg<Args>...)> handler) {
void registerStaticListener(std::function<void(void*, std::any)> handler, void* owner); registerStaticListenerInternal(mkHandler(handler));
}
void listenStatic(std::function<void()> handler)
requires(sizeof...(Args) != 0)
{
return listenStatic([handler](RefArg<Args>... args) { handler(); });
}
// Deprecated: use listenStatic()
void registerStaticListener(std::function<void(void*, std::any)> handler, void* owner) {
return listenStatic([handler, owner](const RefArg<Args>&... args) {
constexpr auto mkAny = [](std::any d = {}) { return d; };
handler(owner, mkAny(args...));
});
}
private: private:
std::vector<Hyprutils::Memory::CWeakPointer<CSignalListener>> m_vListeners; std::function<void(void*)> mkHandler(std::function<void(RefArg<Args>...)> handler) {
std::vector<std::unique_ptr<CStaticSignalListener>> m_vStaticListeners; return [handler](void* args) {
if constexpr (sizeof...(Args) == 0)
handler();
else if constexpr (sizeof...(Args) == 1)
handler(*Memory::sc<std::remove_reference_t<std::tuple_element_t<0, std::tuple<RefArg<Args>...>>>*>(args));
else
std::apply(handler, *Memory::sc<std::tuple<RefArg<Args>...>*>(args));
};
}
};
// compat. Deprecated.
class CSignal : public CSignalT<std::any> {
public:
void emit(std::any data = {});
}; };
} }
} }

View file

@ -0,0 +1,64 @@
#pragma once
#include <functional>
#include <vector>
#include <string>
namespace Hyprutils {
namespace String {
class CConstVarList {
public:
/** Split string into an immutable arg list
@param lastArgNo stop splitting after argv reaches maximum size, last arg will contain rest of unsplit args
@param delim if delimiter is 's', use std::isspace
@param removeEmpty remove empty args from argv
*/
CConstVarList(const std::string& in, const size_t lastArgNo = 0, const char delim = ',', const bool removeEmpty = false);
~CConstVarList() = default;
size_t size() const {
return m_args.size();
}
std::string join(const std::string& joiner, size_t from = 0, size_t to = 0) const;
void map(std::function<void(const std::string_view&)> func) {
for (auto& s : m_args)
func(s);
}
std::string_view operator[](const size_t& idx) const {
if (idx >= m_args.size())
return "";
return m_args[idx];
}
// for range-based loops
std::vector<std::string_view>::iterator begin() {
return m_args.begin();
}
std::vector<std::string_view>::const_iterator begin() const {
return m_args.begin();
}
std::vector<std::string_view>::iterator end() {
return m_args.end();
}
std::vector<std::string_view>::const_iterator end() const {
return m_args.end();
}
bool contains(const std::string_view& el) {
for (auto& a : m_args) {
if (a == el)
return true;
}
return false;
}
private:
std::string m_str;
std::vector<std::string_view> m_args;
};
}
}

View file

@ -4,7 +4,11 @@
namespace Hyprutils { namespace Hyprutils {
namespace String { namespace String {
// trims beginning and end of whitespace characters // trims beginning and end of whitespace characters
std::string trim(const std::string& in); std::string trim(const char* in);
bool isNumber(const std::string& str, bool allowfloat = false); std::string trim(const std::string& in);
std::string_view trim(const std::string_view& in);
bool isNumber(const std::string& str, bool allowfloat = false);
void replaceInString(std::string& string, const std::string& what, const std::string& to);
bool truthy(const std::string_view& in);
}; };
}; };

View file

@ -12,7 +12,7 @@ namespace Hyprutils {
@param delim if delimiter is 's', use std::isspace @param delim if delimiter is 's', use std::isspace
@param removeEmpty remove empty args from argv @param removeEmpty remove empty args from argv
*/ */
CVarList(const std::string& in, const size_t maxSize = 0, const char delim = ',', const bool removeEmpty = false); CVarList(const std::string& in, const size_t lastArgNo = 0, const char delim = ',', const bool removeEmpty = false);
~CVarList() = default; ~CVarList() = default;

View file

@ -0,0 +1,50 @@
#pragma once
#include <functional>
#include <vector>
#include <string>
namespace Hyprutils {
namespace String {
class CVarList2 {
public:
/** Split string into arg list
Prefer this over CConstVarList / CVarList, this is better.
@param lastArgNo stop splitting after argv reaches maximum size, last arg will contain rest of unsplit args
@param delim if delimiter is 's', use std::isspace
@param removeEmpty remove empty args from argv
@param allowEscape whether to allow escaping the delimiter
*/
CVarList2(std::string&& in, const size_t lastArgNo = 0, const char delim = ',', const bool removeEmpty = false, const bool allowEscape = true);
~CVarList2() = default;
size_t size() const {
return m_args.size();
}
std::string join(const std::string& joiner, size_t from = 0, size_t to = 0) const;
void append(std::string&& arg);
bool contains(const std::string& el);
std::string_view operator[](const size_t& idx) const {
if (idx >= m_args.size())
return "";
return m_args[idx];
}
// for range-based loops
std::vector<std::string_view>::const_iterator begin() const {
return m_args.begin();
}
std::vector<std::string_view>::const_iterator end() const {
return m_args.end();
}
private:
std::string m_inString;
std::vector<std::string> m_copyStrings;
std::vector<std::string_view> m_args;
};
}
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <functional>
namespace Hyprutils {
namespace Utils {
// calls a function when it goes out of scope
class CScopeGuard {
public:
CScopeGuard(const std::function<void()>& fn_);
~CScopeGuard();
private:
std::function<void()> fn;
};
};
};

57
nix/default.nix Normal file
View file

@ -0,0 +1,57 @@
{
lib,
stdenv,
stdenvAdapters,
cmake,
pkg-config,
gtest,
pixman,
version ? "git",
debug ? false,
# whether to use the mold linker
# disable this for older machines without SSE4_2 and AVX2 support
withMold ? true,
}:
let
inherit (builtins) foldl';
inherit (lib.lists) flatten optional;
inherit (lib.strings) optionalString;
adapters = flatten [
(lib.optional withMold stdenvAdapters.useMoldLinker)
(lib.optional debug stdenvAdapters.keepDebugInfo)
];
customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters;
in
customStdenv.mkDerivation {
pname = "hyprutils" + optionalString debug "-debug";
inherit version;
src = ../.;
doCheck = debug;
nativeBuildInputs = [
cmake
pkg-config
];
buildInputs = flatten [
(optional debug gtest)
pixman
];
outputs = [
"out"
"dev"
];
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
meta = with lib; {
homepage = "https://github.com/hyprwm/hyprutils";
description = "Small C++ library for utilities used across the Hypr* ecosystem";
license = licenses.bsd3;
platforms = platforms.linux;
};
}

23
nix/overlays.nix Normal file
View file

@ -0,0 +1,23 @@
{
self,
lib,
}: let
mkDate = longDate: (lib.concatStringsSep "-" [
(builtins.substring 0 4 longDate)
(builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
ver = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
version = ver + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
in {
default = self.overlays.hyprutils;
hyprutils = final: prev: {
hyprutils = final.callPackage ./default.nix {
stdenv = final.gcc15Stdenv;
inherit version;
};
hyprutils-debug = final.hyprutils.override {debug = true;};
hyprutils-with-tests = final.hyprutils-debug;
};
}

View file

@ -0,0 +1,155 @@
#include <hyprutils/animation/AnimatedVariable.hpp>
#include <hyprutils/animation/AnimationManager.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
using namespace Hyprutils::Animation;
using namespace Hyprutils::Memory;
static const std::string DEFAULTBEZIERNAME = "default";
static const std::string DEFAULTSTYLE = "";
#define SP CSharedPointer
#define WP CWeakPointer
void CBaseAnimatedVariable::create(CAnimationManager* pManager, int typeInfo, SP<CBaseAnimatedVariable> pSelf) {
m_Type = typeInfo;
m_pSelf = std::move(pSelf);
m_pAnimationManager = pManager;
m_pSignals = pManager->getSignals();
m_bDummy = false;
}
void CBaseAnimatedVariable::create2(CAnimationManager* pManager, int typeInfo, WP<CBaseAnimatedVariable> pSelf) {
m_Type = typeInfo;
m_pSelf = std::move(pSelf);
m_pAnimationManager = pManager;
m_pSignals = pManager->getSignals();
m_bDummy = false;
}
void CBaseAnimatedVariable::connectToActive() {
if (m_bDummy || m_bIsConnectedToActive || isAnimationManagerDead())
return;
m_pSignals->connect.emit(m_pSelf);
m_bIsConnectedToActive = true;
}
void CBaseAnimatedVariable::disconnectFromActive() {
if (isAnimationManagerDead())
return;
m_pSignals->disconnect.emit(m_pSelf);
m_bIsConnectedToActive = false;
}
bool Hyprutils::Animation::CBaseAnimatedVariable::enabled() const {
if (m_pConfig && m_pConfig->pValues)
return m_pConfig->pValues->internalEnabled;
return false;
}
const std::string& CBaseAnimatedVariable::getBezierName() const {
if (m_pConfig && m_pConfig->pValues)
return m_pConfig->pValues->internalBezier;
return DEFAULTBEZIERNAME;
}
const std::string& CBaseAnimatedVariable::getStyle() const {
if (m_pConfig && m_pConfig->pValues)
return m_pConfig->pValues->internalStyle;
return DEFAULTSTYLE;
}
float CBaseAnimatedVariable::getPercent() const {
const auto DURATIONPASSED = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - animationBegin).count();
if (m_pConfig && m_pConfig->pValues)
return std::clamp((DURATIONPASSED / 100.f) / m_pConfig->pValues->internalSpeed, 0.f, 1.f);
return 1.f;
}
float CBaseAnimatedVariable::getCurveValue() const {
if (!m_bIsBeingAnimated || isAnimationManagerDead())
return 1.f;
const auto BEZIER = m_pAnimationManager->getBezier(getBezierName());
if (!BEZIER)
return 1.f;
const auto SPENT = getPercent();
if (SPENT >= 1.f)
return 1.f;
return BEZIER->getYForPoint(SPENT);
}
bool CBaseAnimatedVariable::ok() const {
return m_pConfig && !m_bDummy && !isAnimationManagerDead();
}
void CBaseAnimatedVariable::onUpdate() {
if (m_bIsBeingAnimated && m_fUpdateCallback)
m_fUpdateCallback(m_pSelf);
}
void CBaseAnimatedVariable::setCallbackOnEnd(CallbackFun func, bool remove) {
m_fEndCallback = std::move(func);
m_bRemoveEndAfterRan = remove;
if (!isBeingAnimated())
onAnimationEnd();
}
void CBaseAnimatedVariable::setCallbackOnBegin(CallbackFun func, bool remove) {
m_fBeginCallback = std::move(func);
m_bRemoveBeginAfterRan = remove;
}
void CBaseAnimatedVariable::setUpdateCallback(CallbackFun func) {
m_fUpdateCallback = std::move(func);
}
void CBaseAnimatedVariable::resetAllCallbacks() {
m_fBeginCallback = nullptr;
m_fEndCallback = nullptr;
m_fUpdateCallback = nullptr;
m_bRemoveBeginAfterRan = false;
m_bRemoveEndAfterRan = false;
}
void CBaseAnimatedVariable::onAnimationEnd() {
m_bIsBeingAnimated = false;
/* We do not call disconnectFromActive here. The animation manager will remove it on a call to tickDone. */
if (m_fEndCallback) {
CallbackFun cb = nullptr;
m_fEndCallback.swap(cb);
cb(m_pSelf);
if (!m_bRemoveEndAfterRan && /* callback did not set a new one by itself */ !m_fEndCallback)
m_fEndCallback = cb; // restore
}
}
void CBaseAnimatedVariable::onAnimationBegin() {
m_bIsBeingAnimated = true;
animationBegin = std::chrono::steady_clock::now();
connectToActive();
if (m_fBeginCallback) {
m_fBeginCallback(m_pSelf);
if (m_bRemoveBeginAfterRan)
m_fBeginCallback = nullptr; // reset
}
}
bool CBaseAnimatedVariable::isAnimationManagerDead() const {
return m_pSignals.expired();
}

View file

@ -0,0 +1,70 @@
#include <hyprutils/animation/AnimationConfig.hpp>
using namespace Hyprutils::Animation;
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
void CAnimationConfigTree::createNode(const std::string& nodeName, const std::string& parent) {
auto pConfig = m_mAnimationConfig[nodeName];
if (!pConfig)
pConfig = makeShared<SAnimationPropertyConfig>();
WP<SAnimationPropertyConfig> parentRef;
if (!parent.empty() && m_mAnimationConfig.find(parent) != m_mAnimationConfig.end())
parentRef = m_mAnimationConfig[parent];
*pConfig = {
.overridden = false,
.internalBezier = "",
.internalStyle = "",
.internalSpeed = 0.f,
.internalEnabled = -1,
.pValues = (parentRef) ? parentRef->pValues : pConfig,
.pParentAnimation = (parentRef) ? parentRef : pConfig,
};
m_mAnimationConfig[nodeName] = pConfig;
}
bool CAnimationConfigTree::nodeExists(const std::string& nodeName) const {
return m_mAnimationConfig.find(nodeName) != m_mAnimationConfig.end();
}
void CAnimationConfigTree::setConfigForNode(const std::string& nodeName, int enabled, float speed, const std::string& bezier, const std::string& style) {
auto pConfig = m_mAnimationConfig[nodeName];
if (!pConfig)
return;
*pConfig = {
.overridden = true,
.internalBezier = bezier,
.internalStyle = style,
.internalSpeed = speed,
.internalEnabled = enabled,
.pValues = pConfig,
.pParentAnimation = pConfig->pParentAnimation, // keep the parent!
};
setAnimForChildren(pConfig);
}
SP<SAnimationPropertyConfig> CAnimationConfigTree::getConfig(const std::string& name) const {
return m_mAnimationConfig.at(name);
}
const std::unordered_map<std::string, SP<SAnimationPropertyConfig>>& CAnimationConfigTree::getFullConfig() const {
return m_mAnimationConfig;
}
void CAnimationConfigTree::setAnimForChildren(SP<SAnimationPropertyConfig> PANIM) {
for (auto& [name, anim] : m_mAnimationConfig) {
if (anim->pParentAnimation == PANIM && !anim->overridden) {
// if a child isnt overridden, set the values of the parent
anim->pValues = PANIM->pValues;
setAnimForChildren(anim);
}
}
}

View file

@ -0,0 +1,100 @@
#include <algorithm>
#include <hyprutils/animation/AnimationManager.hpp>
#include <hyprutils/animation/AnimatedVariable.hpp>
using namespace Hyprutils::Animation;
using namespace Hyprutils::Math;
using namespace Hyprutils::Memory;
using namespace Hyprutils::Signal;
#define SP CSharedPointer
#define WP CWeakPointer
const std::array<Vector2D, 2> DEFAULTBEZIERPOINTS = {Vector2D(0.0, 0.75), Vector2D(0.15, 1.0)};
CAnimationManager::CAnimationManager() {
const auto BEZIER = makeShared<CBezierCurve>();
BEZIER->setup(DEFAULTBEZIERPOINTS);
m_mBezierCurves["default"] = BEZIER;
m_events = makeUnique<SAnimationManagerSignals>();
m_listeners = makeUnique<SAnimVarListeners>();
m_listeners->connect = m_events->connect.listen([this](const WP<CBaseAnimatedVariable>& animVar) {
if (!m_bTickScheduled)
scheduleTick();
if (animVar)
m_vActiveAnimatedVariables.emplace_back(animVar);
});
m_listeners->disconnect = m_events->disconnect.listen([this](const WP<CBaseAnimatedVariable>& animVar) {
if (animVar)
std::erase_if(m_vActiveAnimatedVariables, [&](const auto& other) { return !other || other == animVar; });
});
}
void CAnimationManager::removeAllBeziers() {
m_mBezierCurves.clear();
// add the default one
const auto BEZIER = makeShared<CBezierCurve>();
BEZIER->setup(DEFAULTBEZIERPOINTS);
m_mBezierCurves["default"] = BEZIER;
}
void CAnimationManager::addBezierWithName(std::string name, const Vector2D& p1, const Vector2D& p2) {
const auto BEZIER = makeShared<CBezierCurve>();
BEZIER->setup({
p1,
p2,
});
m_mBezierCurves[name] = BEZIER;
}
bool CAnimationManager::shouldTickForNext() {
return !m_vActiveAnimatedVariables.empty();
}
void CAnimationManager::tickDone() {
rotateActive();
}
void CAnimationManager::rotateActive() {
std::vector<CWeakPointer<CBaseAnimatedVariable>> active;
active.reserve(m_vActiveAnimatedVariables.size()); // avoid reallocations
for (auto const& av : m_vActiveAnimatedVariables) {
if (!av)
continue;
if (av->ok() && av->isBeingAnimated())
active.emplace_back(av);
else
av->m_bIsConnectedToActive = false;
}
m_vActiveAnimatedVariables = std::move(active);
}
bool CAnimationManager::bezierExists(const std::string& bezier) {
for (auto const& [bc, bz] : m_mBezierCurves) {
if (bc == bezier)
return true;
}
return false;
}
SP<CBezierCurve> CAnimationManager::getBezier(const std::string& name) {
const auto BEZIER = std::ranges::find_if(m_mBezierCurves, [&](const auto& other) { return other.first == name; });
return BEZIER == m_mBezierCurves.end() ? m_mBezierCurves["default"] : BEZIER->second;
}
const std::unordered_map<std::string, SP<CBezierCurve>>& CAnimationManager::getAllBeziers() {
return m_mBezierCurves;
}
CWeakPointer<CAnimationManager::SAnimationManagerSignals> CAnimationManager::getSignals() const {
return m_events;
}

View file

@ -0,0 +1,107 @@
#include <hyprutils/animation/BezierCurve.hpp>
#include <hyprutils/memory/Casts.hpp>
#include <array>
#include <cmath>
using namespace Hyprutils::Animation;
using namespace Hyprutils::Math;
using namespace Hyprutils::Memory;
void CBezierCurve::setup(const std::array<Vector2D, 2>& pVec) {
setup4(std::array<Vector2D, 4>{
Vector2D(0, 0), // Start point
pVec[0], pVec[1], // Control points
Vector2D(1, 1) // End point
});
}
void CBezierCurve::setup4(const std::array<Vector2D, 4>& pVec) {
// Avoid reallocations by reserving enough memory upfront
m_vPoints.resize(4);
m_vPoints = {
pVec[0],
pVec[1],
pVec[2],
pVec[3],
};
// Pre-bake curve
//
// We start baking at t=(i+1)/n not at t=0
// That means the first baked x can be > 0 if curve itself starts at x>0
for (int i = 0; i < BAKEDPOINTS; ++i) {
// When i=0 -> t=1/255
const float t = (i + 1) * INVBAKEDPOINTS;
m_aPointsBaked[i] = Vector2D(getXForT(t), getYForT(t));
}
}
float CBezierCurve::getXForT(float const& t) const {
float t2 = t * t;
float t3 = t2 * t;
return ((1 - t) * (1 - t) * (1 - t) * m_vPoints[0].x) + (3 * t * (1 - t) * (1 - t) * m_vPoints[1].x) + (3 * t2 * (1 - t) * m_vPoints[2].x) + (t3 * m_vPoints[3].x);
}
float CBezierCurve::getYForT(float const& t) const {
float t2 = t * t;
float t3 = t2 * t;
return ((1 - t) * (1 - t) * (1 - t) * m_vPoints[0].y) + (3 * t * (1 - t) * (1 - t) * m_vPoints[1].y) + (3 * t2 * (1 - t) * m_vPoints[2].y) + (t3 * m_vPoints[3].y);
}
// Todo: this probably can be done better and faster
float CBezierCurve::getYForPoint(float const& x) const {
if (x >= 1.f)
return 1.f;
if (x <= 0.f)
return 0.f;
int index = 0;
bool below = true;
for (int step = (BAKEDPOINTS + 1) / 2; step > 0; step /= 2) {
if (below)
index += step;
else
index -= step;
// Clamp to avoid index walking off
if (index < 0)
index = 0;
else if (index > BAKEDPOINTS - 1)
index = BAKEDPOINTS - 1;
below = m_aPointsBaked[index].x < x;
}
int lowerIndex = index - (!below || index == BAKEDPOINTS - 1);
// Clamp final indices
if (lowerIndex < 0)
lowerIndex = 0;
else if (lowerIndex > BAKEDPOINTS - 2)
lowerIndex = BAKEDPOINTS - 2;
// In the name of performance I shall make a hack
const auto& LOWERPOINT = m_aPointsBaked[lowerIndex];
const auto& UPPERPOINT = m_aPointsBaked[lowerIndex + 1];
const float dx = (UPPERPOINT.x - LOWERPOINT.x);
// If two baked points have almost the same x
// just return the lower one
if (dx <= 1e-6f)
return LOWERPOINT.y;
const auto PERCINDELTA = (x - LOWERPOINT.x) / dx;
// Can sometimes happen for VERY small x
if (std::isnan(PERCINDELTA) || std::isinf(PERCINDELTA))
return LOWERPOINT.y;
return LOWERPOINT.y + ((UPPERPOINT.y - LOWERPOINT.y) * PERCINDELTA);
}
const std::vector<Hyprutils::Math::Vector2D>& CBezierCurve::getControlPoints() const {
return m_vPoints;
}

298
src/cli/ArgumentParser.cpp Normal file
View file

@ -0,0 +1,298 @@
#include "ArgumentParser.hpp"
#include <format>
#include <vector>
#include <algorithm>
#include <hyprutils/string/String.hpp>
#include <hyprutils/memory/Casts.hpp>
using namespace Hyprutils::CLI;
using namespace Hyprutils::Memory;
using namespace Hyprutils::String;
using namespace Hyprutils;
CArgumentParser::CArgumentParser(const std::span<const char*>& args) : m_impl(makeUnique<CArgumentParserImpl>(args)) {
;
}
std::expected<void, std::string> CArgumentParser::registerBoolOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description) {
return m_impl->registerOption(name, abbrev, description, ARG_TYPE_BOOL);
}
std::expected<void, std::string> CArgumentParser::registerIntOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description) {
return m_impl->registerOption(name, abbrev, description, ARG_TYPE_INT);
}
std::expected<void, std::string> CArgumentParser::registerFloatOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description) {
return m_impl->registerOption(name, abbrev, description, ARG_TYPE_FLOAT);
}
std::expected<void, std::string> CArgumentParser::registerStringOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description) {
return m_impl->registerOption(name, abbrev, description, ARG_TYPE_STR);
}
std::optional<bool> CArgumentParser::getBool(const std::string_view& name) {
auto ref = m_impl->getValue(name);
if (ref == m_impl->m_values.end())
return std::nullopt;
if (const auto pval = std::get_if<bool>(&ref->val); pval)
return *pval;
return std::nullopt;
}
std::optional<int> CArgumentParser::getInt(const std::string_view& name) {
auto ref = m_impl->getValue(name);
if (ref == m_impl->m_values.end())
return std::nullopt;
if (const auto pval = std::get_if<int>(&ref->val); pval)
return *pval;
return std::nullopt;
}
std::optional<float> CArgumentParser::getFloat(const std::string_view& name) {
auto ref = m_impl->getValue(name);
if (ref == m_impl->m_values.end())
return std::nullopt;
if (const auto pval = std::get_if<float>(&ref->val); pval)
return *pval;
return std::nullopt;
}
std::optional<std::string_view> CArgumentParser::getString(const std::string_view& name) {
auto ref = m_impl->getValue(name);
if (ref == m_impl->m_values.end())
return std::nullopt;
if (const auto pval = std::get_if<std::string>(&ref->val); pval)
return *pval;
return std::nullopt;
}
std::string CArgumentParser::getDescription(const std::string_view& header, std::optional<size_t> maxWidth) {
return m_impl->getDescription(header, maxWidth);
}
std::expected<void, std::string> CArgumentParser::parse() {
return m_impl->parse();
}
CArgumentParserImpl::CArgumentParserImpl(const std::span<const char*>& args) {
m_argv.reserve(args.size());
for (const auto& a : args) {
m_argv.emplace_back(a);
}
}
std::vector<SArgumentKey>::iterator CArgumentParserImpl::getValue(const std::string_view& sv) {
if (sv.empty())
return m_values.end();
auto it = std::ranges::find_if(m_values, [&sv](const auto& e) { return e.full == sv || e.abbrev == sv; });
return it;
}
std::expected<void, std::string> CArgumentParserImpl::registerOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description,
eArgumentType type) {
if (name.empty())
return std::unexpected("Name cannot be empty");
if (getValue(name) != m_values.end() || getValue(abbrev) != m_values.end())
return std::unexpected("Value already exists");
m_values.emplace_back(SArgumentKey{
.full = std::string{name},
.abbrev = std::string{abbrev},
.desc = std::string{description},
.argType = type,
.val = std::monostate{},
});
return {};
}
std::expected<void, std::string> CArgumentParserImpl::parse() {
// walk the args
for (size_t i = 1; i < m_argv.size(); ++i) {
auto val = m_values.end();
const auto& arg = m_argv.at(i);
if (arg.starts_with("--"))
val = getValue(std::string_view{arg}.substr(2));
else if (arg.starts_with('-'))
val = getValue(std::string_view{arg}.substr(1));
else
return std::unexpected(std::format("Invalid element found while parsing: {}", arg));
if (val == m_values.end())
return std::unexpected(std::format("Invalid argument found while parsing: {}", arg));
switch (val->argType) {
case ARG_TYPE_BOOL: {
val->val = true;
break;
}
case ARG_TYPE_INT: {
if (i + 1 >= m_argv.size())
return std::unexpected(std::format("Failed parsing argument {}, no value supplied", arg));
const auto& next = std::string{m_argv.at(++i)};
if (!isNumber(next))
return std::unexpected(std::format("Failed parsing argument {}, value {} is not an int", arg, next));
try {
val->val = sc<int>(std::stoi(next));
} catch (...) { return std::unexpected(std::format("Failed parsing argument {}, value {} is not an int", arg, next)); }
break;
}
case ARG_TYPE_FLOAT: {
if (i + 1 >= m_argv.size())
return std::unexpected(std::format("Failed parsing argument {}, no value supplied", arg));
const auto& next = std::string{m_argv.at(++i)};
if (!isNumber(next, true))
return std::unexpected(std::format("Failed parsing argument {}, value {} is not a float", arg, next));
try {
val->val = sc<float>(std::stof(next));
} catch (...) { return std::unexpected(std::format("Failed parsing argument {}, value {} is not a float", arg, next)); }
break;
}
case ARG_TYPE_STR: {
if (i + 1 >= m_argv.size())
return std::unexpected(std::format("Failed parsing argument {}, no value supplied", arg));
val->val = std::string{m_argv.at(++i)};
break;
}
case ARG_TYPE_END: break;
}
}
return {};
}
std::string CArgumentParserImpl::getDescription(const std::string_view& header, std::optional<size_t> maxWidth) {
const size_t MAX_COLS = maxWidth.value_or(80);
const std::string PAD_STR = " ";
constexpr const std::array<const char*, ARG_TYPE_END> TYPE_STRS = {
"", // bool
"[int]", // int
"[float]", // float
"[str]", // str
};
//
auto wrap = [](const std::string_view& str, size_t maxW) -> std::vector<std::string_view> {
std::vector<std::string_view> result;
// walk word by word
size_t nextSpacePos = 0, lastBreakPos = 0;
while (true) {
size_t lastSpacePos = nextSpacePos;
nextSpacePos = str.find(' ', nextSpacePos + 1);
if (nextSpacePos == std::string::npos)
break;
if (nextSpacePos - lastBreakPos > maxW) {
if (lastSpacePos - lastBreakPos <= maxW) {
// break
result.emplace_back(str.substr(lastBreakPos, lastSpacePos - lastBreakPos));
lastBreakPos = lastSpacePos + 1;
} else {
while (lastSpacePos - lastBreakPos > maxW) {
// break
result.emplace_back(str.substr(lastBreakPos, maxW));
lastBreakPos += maxW;
}
}
continue;
}
}
result.emplace_back(str.substr(lastBreakPos));
return result;
};
auto pad = [&PAD_STR](size_t len) -> std::string_view {
if (len >= PAD_STR.size())
return PAD_STR;
return std::string_view{PAD_STR}.substr(0, len);
};
std::string rolling;
rolling += std::format("┏ {}\n", header);
rolling += "";
for (size_t i = 0; i < MAX_COLS; ++i) {
rolling += "";
}
rolling += "\n";
// get max widths
size_t maxArgWidth = 0, maxShortWidth = 0;
for (const auto& v : m_values) {
maxShortWidth = std::max(maxShortWidth, v.abbrev.size() + 4 + std::string_view{TYPE_STRS[v.argType]}.length());
maxArgWidth = std::max(maxArgWidth, v.full.size() + 3);
}
// write the table
for (const auto& v : m_values) {
size_t lenUsed = 0;
rolling += "┣ --" + v.full;
lenUsed += 3 + v.full.size();
rolling += pad(maxArgWidth - lenUsed);
lenUsed = maxArgWidth;
if (!v.abbrev.empty()) {
rolling += " -" + v.abbrev;
lenUsed += 2 + v.abbrev.size();
rolling += " ";
rolling += TYPE_STRS[v.argType];
lenUsed += std::string_view{TYPE_STRS[v.argType]}.length() + 1;
rolling += pad(maxArgWidth + maxShortWidth - lenUsed);
} else
rolling += pad(maxShortWidth);
lenUsed = maxArgWidth + maxShortWidth;
rolling += " | ";
lenUsed += 3;
const auto ROWS = wrap(v.desc, MAX_COLS - lenUsed);
const auto LEN_START_DESC = lenUsed;
rolling += ROWS[0];
lenUsed += ROWS[0].size();
rolling += pad(MAX_COLS - lenUsed);
rolling += "\n";
for (size_t i = 1; i < ROWS.size(); ++i) {
lenUsed = LEN_START_DESC;
rolling += "";
rolling += pad(LEN_START_DESC);
rolling += ROWS[i];
lenUsed += ROWS[i].size();
rolling += pad(MAX_COLS - lenUsed);
rolling += "\n";
}
}
rolling += "";
for (size_t i = 0; i < MAX_COLS; ++i) {
rolling += "";
}
rolling += "\n";
return rolling;
}

View file

@ -0,0 +1,39 @@
#include <hyprutils/cli/ArgumentParser.hpp>
#include <map>
#include <variant>
#include <vector>
namespace Hyprutils::CLI {
enum eArgumentType : uint8_t {
ARG_TYPE_BOOL = 0,
ARG_TYPE_INT,
ARG_TYPE_FLOAT,
ARG_TYPE_STR,
ARG_TYPE_END,
};
struct SArgumentKey {
using Value = std::variant<std::monostate, bool, int, float, std::string>;
std::string full, abbrev, desc;
eArgumentType argType = ARG_TYPE_BOOL;
Value val;
};
class CArgumentParserImpl {
public:
CArgumentParserImpl(const std::span<const char*>& args);
~CArgumentParserImpl() = default;
std::string getDescription(const std::string_view& header, std::optional<size_t> maxWidth = {});
std::expected<void, std::string> parse();
std::vector<SArgumentKey>::iterator getValue(const std::string_view& sv);
std::expected<void, std::string> registerOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description, eArgumentType type);
std::vector<SArgumentKey> m_values;
std::vector<std::string_view> m_argv;
};
}

199
src/cli/Logger.cpp Normal file
View file

@ -0,0 +1,199 @@
#include "Logger.hpp"
#include <chrono>
#include <print>
using namespace Hyprutils;
using namespace Hyprutils::CLI;
CLogger::CLogger() {
m_impl = Memory::makeUnique<CLoggerImpl>(this);
}
CLogger::~CLogger() = default;
void CLogger::setLogLevel(eLogLevel level) {
m_logLevel = level;
}
void CLogger::setTime(bool enabled) {
m_impl->m_timeEnabled = enabled;
}
void CLogger::setEnableStdout(bool enabled) {
m_impl->m_stdoutEnabled = enabled;
m_impl->updateParentShouldLog();
}
void CLogger::setEnableColor(bool enabled) {
m_impl->m_colorEnabled = enabled;
}
void CLogger::setEnableRolling(bool enabled) {
m_impl->m_rollingEnabled = enabled;
}
std::expected<void, std::string> CLogger::setOutputFile(const std::string_view& file) {
if (file.empty()) {
m_impl->m_fileEnabled = false;
m_impl->m_logOfs = {};
return {};
}
std::filesystem::path filePath{file};
std::error_code ec;
if (!filePath.has_parent_path())
return std::unexpected("Path has no parent");
auto dir = filePath.parent_path();
if (!std::filesystem::exists(dir, ec) || ec)
return std::unexpected("Parent path is inaccessible, or doesn't exist");
m_impl->m_logOfs = std::ofstream{filePath, std::ios::trunc};
m_impl->m_logFilePath = filePath;
if (!m_impl->m_logOfs.good())
return std::unexpected("Failed to open a write stream");
m_impl->m_fileEnabled = true;
m_impl->updateParentShouldLog();
return {};
}
void CLogger::log(eLogLevel level, const std::string_view& msg) {
if (!m_shouldLogAtAll)
return;
if (level < m_logLevel)
return;
m_impl->log(level, msg);
}
const std::string& CLogger::rollingLog() {
return m_impl->m_rollingLog;
}
CLoggerImpl::CLoggerImpl(CLogger* parent) : m_parent(parent) {
updateParentShouldLog();
}
void CLoggerImpl::log(eLogLevel level, const std::string_view& msg, const std::string_view& from) {
std::lock_guard<std::mutex> lg(m_logMtx);
std::string logPrefix = "", logPrefixColor = "";
std::string logMsg = "";
switch (level) {
case LOG_TRACE:
logPrefix += "TRACE ";
logPrefixColor += "\033[1;34mTRACE \033[0m";
break;
case LOG_DEBUG:
logPrefix += "DEBUG ";
logPrefixColor += "\033[1;32mDEBUG \033[0m";
break;
case LOG_WARN:
logPrefix += "WARN ";
logPrefixColor += "\033[1;33mWARN \033[0m";
break;
case LOG_ERR:
logPrefix += "ERR ";
logPrefixColor += "\033[1;31mERR \033[0m";
break;
case LOG_CRIT:
logPrefix += "CRIT ";
logPrefixColor += "\033[1;35mCRIT \033[0m";
break;
}
if (m_timeEnabled) {
#ifndef _LIBCPP_VERSION
static auto current_zone = std::chrono::current_zone();
const auto zt = std::chrono::zoned_time{current_zone, std::chrono::system_clock::now()};
const auto hms = std::chrono::hh_mm_ss{zt.get_local_time() - std::chrono::floor<std::chrono::days>(zt.get_local_time())};
#else
// TODO: current clang 17 does not support `zoned_time`, remove this once clang 19 is ready
const auto hms = std::chrono::hh_mm_ss{std::chrono::system_clock::now() - std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now())};
#endif
logMsg += std::format("@ {} ", hms);
}
if (!from.empty()) {
logMsg += "from ";
logMsg += from;
logMsg += " ";
}
logMsg += "]: ";
logMsg += msg;
if (m_stdoutEnabled) {
try {
std::println("{}{}", m_colorEnabled ? logPrefixColor : logPrefix, logMsg);
std::fflush(stdout);
} catch (std::exception& e) {
; // this could be e.g. stdout closed
}
}
if (m_fileEnabled)
m_logOfs << logPrefix << logMsg << "\n";
if (m_rollingEnabled)
appendToRolling(logPrefix + logMsg);
}
void CLoggerImpl::updateParentShouldLog() {
m_parent->m_shouldLogAtAll = m_fileEnabled || m_stdoutEnabled;
}
void CLoggerImpl::appendToRolling(const std::string& s) {
constexpr const size_t ROLLING_LOG_SIZE = 4096;
if (!m_rollingLog.empty())
m_rollingLog += "\n";
m_rollingLog += s;
if (m_rollingLog.size() > ROLLING_LOG_SIZE)
m_rollingLog = m_rollingLog.substr(m_rollingLog.find('\n', m_rollingLog.size() - ROLLING_LOG_SIZE) + 1);
}
CLoggerConnection::CLoggerConnection(CLogger& logger) : m_impl(logger.m_impl), m_logger(&logger), m_logLevel(logger.m_logLevel) {
;
}
CLoggerConnection::~CLoggerConnection() = default;
void CLoggerConnection::setName(const std::string_view& name) {
m_name = name;
}
void CLoggerConnection::setLogLevel(eLogLevel level) {
m_logLevel = level;
}
void CLoggerConnection::log(eLogLevel level, const std::string_view& msg) {
if (!m_impl || !m_logger)
return;
if (!m_logger->m_shouldLogAtAll)
return;
if (level < m_logLevel)
return;
m_impl->log(level, msg, m_name);
}
CLogger* CLoggerConnection::getLogger() {
if (!m_impl)
return nullptr;
return m_logger;
}
void CLoggerConnection::redirect(CLogger& logger) {
m_impl = logger.m_impl;
m_logger = &logger;
}

35
src/cli/Logger.hpp Normal file
View file

@ -0,0 +1,35 @@
#include <hyprutils/cli/Logger.hpp>
#include <fstream>
#include <filesystem>
#include <mutex>
namespace Hyprutils::CLI {
class CLoggerImpl {
public:
CLoggerImpl(CLogger*);
~CLoggerImpl() = default;
CLoggerImpl(const CLoggerImpl&) = delete;
CLoggerImpl(CLoggerImpl&) = delete;
CLoggerImpl(CLoggerImpl&&) = delete;
void updateParentShouldLog();
void appendToRolling(const std::string& s);
void log(eLogLevel level, const std::string_view& msg, const std::string_view& from = "");
std::string m_rollingLog;
std::ofstream m_logOfs;
std::filesystem::path m_logFilePath;
bool m_timeEnabled = false;
bool m_stdoutEnabled = true;
bool m_fileEnabled = false;
bool m_colorEnabled = true;
bool m_rollingEnabled = false;
std::mutex m_logMtx;
// this is fine because CLogger is NOMOVE and NOCOPY
CLogger* m_parent = nullptr;
};
}

165
src/i18n/I18nEngine.cpp Normal file
View file

@ -0,0 +1,165 @@
#include "I18nEngine.hpp"
#include <algorithm>
#include <format>
#include <locale>
#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Hyprutils::I18n;
using namespace Hyprutils;
using namespace Hyprutils::Utils;
CI18nEngine::CI18nEngine() : m_impl(Memory::makeUnique<SI18nEngineImpl>()) {
;
}
CI18nEngine::~CI18nEngine() = default;
void CI18nEngine::registerEntry(const std::string& locale, uint64_t key, std::string&& translationUTF8) {
auto& entryVec = m_impl->entries[locale];
if (entryVec.size() <= key)
entryVec.resize(key + 1);
entryVec[key].entry = std::move(translationUTF8);
entryVec[key].exists = true;
}
void CI18nEngine::registerEntry(const std::string& locale, uint64_t key, translationFn&& translationFn) {
auto& entryVec = m_impl->entries[locale];
if (entryVec.size() <= key)
entryVec.resize(key + 1);
entryVec[key].fn = std::move(translationFn);
entryVec[key].exists = true;
}
void CI18nEngine::setFallbackLocale(const std::string& locale) {
m_impl->fallbackLocale = locale;
}
std::string CI18nEngine::localizeEntry(const std::string& locale, uint64_t key, const translationVarMap& map) {
SI18nTranslationEntry* entry = nullptr;
if (m_impl->entries.contains(locale) && m_impl->entries[locale].size() > key)
entry = &m_impl->entries[locale][key];
if (locale.contains('_')) {
if (!entry || !entry->exists) {
// try to fall back to lang_LANG
auto stem = locale.substr(0, locale.find('_'));
auto stemUpper = stem;
std::ranges::transform(stemUpper, stemUpper.begin(), ::toupper);
auto newLocale = std::format("{}_{}", stem, stemUpper);
if (m_impl->entries.contains(newLocale) && m_impl->entries[newLocale].size() > key)
entry = &m_impl->entries[newLocale][key];
}
if (!entry || !entry->exists) {
// try to fall back to any lang prefixed with our prefix
const auto stem = locale.substr(0, locale.find('_') + 1);
const auto stemRaw = locale.substr(0, locale.find('_'));
for (const auto& [k, v] : m_impl->entries) {
if (k.starts_with(stem) || k == stemRaw) {
if (m_impl->entries.contains(k) && m_impl->entries[k].size() > key)
entry = &m_impl->entries[k][key];
if (entry && entry->exists)
break;
}
}
}
} else {
// locale doesn't have a _, e.g. pl
// find any locale that has the same stem
for (const auto& [k, v] : m_impl->entries) {
if (k.starts_with(locale + "_") || k == locale) {
if (m_impl->entries.contains(k) && m_impl->entries[k].size() > key)
entry = &m_impl->entries[k][key];
if (entry && entry->exists)
break;
}
}
}
if (!entry || !entry->exists) {
// fall back to general fallback
if (m_impl->entries.contains(m_impl->fallbackLocale) && m_impl->entries[m_impl->fallbackLocale].size() > key)
entry = &m_impl->entries[m_impl->fallbackLocale][key];
}
if (!entry || !entry->exists)
return "";
std::string_view rawStr = entry->entry;
std::string fnStringContainer;
if (entry->fn) {
fnStringContainer = entry->fn(map);
rawStr = fnStringContainer;
}
struct SRange {
size_t begin = 0;
size_t end = 0;
const std::string* val = nullptr;
};
std::vector<SRange> rangesFound;
// discover all replacable ranges
for (const auto& [k, v] : map) {
size_t start = rawStr.find(k, 0);
while (start != std::string::npos) {
if (start == 0 || start + k.size() >= rawStr.size())
break;
// always move the pointer
CScopeGuard x([&start, &rawStr, &k] { start = rawStr.find(k, start + 1); });
if (rawStr[start - 1] != '{' || rawStr[start + k.size()] != '}')
continue;
// add range
rangesFound.emplace_back(SRange{.begin = start - 1, .end = start + k.size() + 1, .val = &v});
}
}
if (rangesFound.empty())
return std::string{rawStr};
// build the new string. First, sort our entries
std::ranges::sort(rangesFound, [](const auto& a, const auto& b) { return a.begin < b.begin; });
// calc the size
size_t stringLen = 0;
size_t lastBegin = 0;
for (const auto& r : rangesFound) {
stringLen += r.begin - lastBegin + r.val->size();
lastBegin = r.end;
}
stringLen += rawStr.size() - lastBegin;
lastBegin = 0;
const auto ORIGINAL_STR = std::string_view{rawStr};
std::string newStr;
newStr.reserve(stringLen);
for (const auto& r : rangesFound) {
newStr += ORIGINAL_STR.substr(lastBegin, r.begin - lastBegin);
newStr += *r.val;
lastBegin = r.end;
}
newStr += ORIGINAL_STR.substr(lastBegin);
return newStr;
}
CI18nLocale CI18nEngine::getSystemLocale() {
try {
return CI18nLocale(std::locale("").name());
} catch (...) { return CI18nLocale("en_US.UTF-8"); }
}

18
src/i18n/I18nEngine.hpp Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include <hyprutils/i18n/I18nEngine.hpp>
namespace Hyprutils::I18n {
struct SI18nTranslationEntry {
bool exists = false;
std::string entry = "";
translationFn fn = nullptr;
};
struct SI18nEngineImpl {
std::unordered_map<std::string, std::vector<SI18nTranslationEntry>> entries;
std::string fallbackLocale = "en_US";
};
std::string extractLocale(std::string locale);
};

44
src/i18n/I18nLocale.cpp Normal file
View file

@ -0,0 +1,44 @@
#include "I18nEngine.hpp"
using namespace Hyprutils::I18n;
std::string Hyprutils::I18n::extractLocale(std::string locale) {
// localeStr is very arbitrary... from my testing, it can be:
// en_US.UTF-8
// LC_CTYPE=en_US
// POSIX
// *
//
// We only return e.g. en_US or pl_PL, or pl
if (locale == "POSIX")
return "en_US";
if (locale == "*")
return "en_US";
if (locale.contains('='))
locale = locale.substr(locale.find('=') + 1);
if (locale.contains('.'))
locale = locale.substr(0, locale.find('.'));
return locale;
}
CI18nLocale::CI18nLocale(std::string fullLocale) : m_rawFullLocale(std::move(fullLocale)) {
m_locale = extractLocale(m_rawFullLocale);
}
std::string CI18nLocale::locale() {
return m_locale;
}
std::string CI18nLocale::stem() {
if (m_locale.contains('_'))
return m_locale.substr(0, m_locale.find('_'));
return m_locale;
}
std::string CI18nLocale::full() {
return m_rawFullLocale;
}

237
src/math/Box.cpp Normal file
View file

@ -0,0 +1,237 @@
#include <hyprutils/math/Box.hpp>
#include <limits>
#include <algorithm>
#include <cmath>
#define VECINRECT(vec, x1, y1, x2, y2) ((vec).x >= (x1) && (vec).x < (x2) && (vec).y >= (y1) && (vec).y < (y2))
using namespace Hyprutils::Math;
constexpr double HALF = 0.5;
constexpr double DOUBLE = 2.0;
constexpr double EPSILON = 1e-9;
CBox& Hyprutils::Math::CBox::scale(double scale) {
x *= scale;
y *= scale;
w *= scale;
h *= scale;
return *this;
}
CBox& Hyprutils::Math::CBox::scale(const Vector2D& scale) {
x *= scale.x;
y *= scale.y;
w *= scale.x;
h *= scale.y;
return *this;
}
CBox& Hyprutils::Math::CBox::translate(const Vector2D& vec) {
x += vec.x;
y += vec.y;
return *this;
}
Vector2D Hyprutils::Math::CBox::middle() const {
return Vector2D{x + (w * HALF), y + (h * HALF)};
}
bool Hyprutils::Math::CBox::containsPoint(const Vector2D& vec) const {
return VECINRECT(vec, x, y, x + w, y + h);
}
bool Hyprutils::Math::CBox::empty() const {
return std::fabs(w) < EPSILON || std::fabs(h) < EPSILON;
}
CBox& Hyprutils::Math::CBox::round() {
double roundedX = std::round(x);
double roundedY = std::round(y);
double newW = x + w - roundedX;
double newH = y + h - roundedY;
x = roundedX;
y = roundedY;
w = std::round(newW);
h = std::round(newH);
return *this;
}
CBox& Hyprutils::Math::CBox::transform(const eTransform t, double w, double h) {
CBox temp = *this;
if (t % 2 == 0) {
width = temp.width;
height = temp.height;
} else {
width = temp.height;
height = temp.width;
}
switch (t) {
case HYPRUTILS_TRANSFORM_NORMAL:
x = temp.x;
y = temp.y;
break;
case HYPRUTILS_TRANSFORM_90:
x = h - temp.y - temp.height;
y = temp.x;
break;
case HYPRUTILS_TRANSFORM_180:
x = w - temp.x - temp.width;
y = h - temp.y - temp.height;
break;
case HYPRUTILS_TRANSFORM_270:
x = temp.y;
y = w - temp.x - temp.width;
break;
case HYPRUTILS_TRANSFORM_FLIPPED:
x = w - temp.x - temp.width;
y = temp.y;
break;
case HYPRUTILS_TRANSFORM_FLIPPED_90:
x = temp.y;
y = temp.x;
break;
case HYPRUTILS_TRANSFORM_FLIPPED_180:
x = temp.x;
y = h - temp.y - temp.height;
break;
case HYPRUTILS_TRANSFORM_FLIPPED_270:
x = h - temp.y - temp.height;
y = w - temp.x - temp.width;
break;
}
return *this;
}
CBox& Hyprutils::Math::CBox::addExtents(const SBoxExtents& e) {
x -= e.topLeft.x;
y -= e.topLeft.y;
w += e.topLeft.x + e.bottomRight.x;
h += e.topLeft.y + e.bottomRight.y;
return *this;
}
CBox& Hyprutils::Math::CBox::scaleFromCenter(double scale) {
double oldW = w, oldH = h;
w *= scale;
h *= scale;
x -= (w - oldW) * HALF;
y -= (h - oldH) * HALF;
return *this;
}
CBox& Hyprutils::Math::CBox::expand(const double& value) {
x -= value;
y -= value;
w += value * DOUBLE;
h += value * DOUBLE;
if (w <= EPSILON || h <= EPSILON) {
w = 0;
h = 0;
}
return *this;
}
CBox& Hyprutils::Math::CBox::noNegativeSize() {
w = std::clamp(w, 0.0, std::numeric_limits<double>::infinity());
h = std::clamp(h, 0.0, std::numeric_limits<double>::infinity());
return *this;
}
CBox Hyprutils::Math::CBox::intersection(const CBox& other) const {
const double newX = std::max(x, other.x);
const double newY = std::max(y, other.y);
const double newBottom = std::min(y + h, other.y + other.h);
const double newRight = std::min(x + w, other.x + other.w);
double newW = newRight - newX;
double newH = newBottom - newY;
if (newW <= EPSILON || newH <= EPSILON) {
newW = 0;
newH = 0;
}
return {newX, newY, newW, newH};
}
bool Hyprutils::Math::CBox::overlaps(const CBox& other) const {
return (other.x + other.w >= x) && (x + w >= other.x) && (other.y + other.h >= y) && (y + h >= other.y);
}
bool Hyprutils::Math::CBox::inside(const CBox& bound) const {
return bound.x < x && bound.y < y && x + w < bound.x + bound.w && y + h < bound.y + bound.h;
}
CBox Hyprutils::Math::CBox::roundInternal() {
double flooredX = std::floor(x);
double flooredY = std::floor(y);
double newW = x + w - flooredX;
double newH = y + h - flooredY;
return CBox{flooredX, flooredY, std::floor(newW), std::floor(newH)};
}
CBox Hyprutils::Math::CBox::copy() const {
return CBox{*this};
}
Vector2D Hyprutils::Math::CBox::pos() const {
return {x, y};
}
Vector2D Hyprutils::Math::CBox::size() const {
return {w, h};
}
Vector2D Hyprutils::Math::CBox::extent() const {
return pos() + size();
}
Vector2D Hyprutils::Math::CBox::closestPoint(const Vector2D& vec) const {
if (containsPoint(vec))
return vec;
Vector2D nv = vec;
Vector2D maxPoint = {x + w - EPSILON, y + h - EPSILON};
if (x < maxPoint.x)
nv.x = std::clamp(nv.x, x, maxPoint.x);
else
nv.x = x;
if (y < maxPoint.y)
nv.y = std::clamp(nv.y, y, maxPoint.y);
else
nv.y = y;
if (std::fabs(nv.x - x) < EPSILON)
nv.x = x;
else if (std::fabs(nv.x - (maxPoint.x)) < EPSILON)
nv.x = maxPoint.x;
if (std::fabs(nv.y - y) < EPSILON)
nv.y = y;
else if (std::fabs(nv.y - (maxPoint.y)) < EPSILON)
nv.y = maxPoint.y;
return nv;
}
SBoxExtents Hyprutils::Math::CBox::extentsFrom(const CBox& small) {
return {.topLeft = {small.x - x, small.y - y}, .bottomRight = {w - small.w - (small.x - x), h - small.h - (small.y - y)}};
}

155
src/math/Mat3x3.cpp Normal file
View file

@ -0,0 +1,155 @@
#include <hyprutils/math/Mat3x3.hpp>
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/math/Box.hpp>
#include <hyprutils/memory/Casts.hpp>
#include <cmath>
#include <unordered_map>
#include <format>
using namespace Hyprutils::Math;
using namespace Hyprutils::Memory;
static std::unordered_map<eTransform, Mat3x3> transforms = {
{HYPRUTILS_TRANSFORM_NORMAL, std::array<float, 9>{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
{HYPRUTILS_TRANSFORM_90, std::array<float, 9>{0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
{HYPRUTILS_TRANSFORM_180, std::array<float, 9>{-1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
{HYPRUTILS_TRANSFORM_270, std::array<float, 9>{0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
{HYPRUTILS_TRANSFORM_FLIPPED, std::array<float, 9>{-1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
{HYPRUTILS_TRANSFORM_FLIPPED_90, std::array<float, 9>{0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
{HYPRUTILS_TRANSFORM_FLIPPED_180, std::array<float, 9>{1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
{HYPRUTILS_TRANSFORM_FLIPPED_270, std::array<float, 9>{0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
};
Mat3x3::Mat3x3() {
matrix = {0};
}
Mat3x3::Mat3x3(std::array<float, 9> mat) : matrix(mat) {
;
}
Mat3x3::Mat3x3(std::vector<float> mat) {
for (size_t i = 0; i < 9; ++i) {
matrix.at(i) = mat.size() < i ? mat.at(i) : 0.F;
}
}
Mat3x3 Mat3x3::identity() {
return Mat3x3(std::array<float, 9>{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f});
}
Mat3x3 Mat3x3::outputProjection(const Vector2D& size, eTransform transform) {
Mat3x3 mat;
const auto& t = transforms.at(transform);
float x = 2.0f / size.x;
float y = 2.0f / size.y;
// Rotation + reflection
mat.matrix[0] = x * t.matrix[0];
mat.matrix[1] = x * t.matrix[1];
mat.matrix[3] = y * t.matrix[3];
mat.matrix[4] = y * t.matrix[4];
// Translation
mat.matrix[2] = -copysign(1.0f, mat.matrix[0] + mat.matrix[1]);
mat.matrix[5] = -copysign(1.0f, mat.matrix[3] + mat.matrix[4]);
// Identity
mat.matrix[8] = 1.0f;
return mat;
}
std::array<float, 9> Mat3x3::getMatrix() const {
return matrix;
}
Mat3x3 Mat3x3::projectBox(const CBox& box, eTransform transform, float rot /* rad, CCW */) const {
Mat3x3 mat = Mat3x3::identity();
const auto boxSize = box.size();
mat.translate(box.pos());
if (rot != 0) {
mat.translate(boxSize / 2);
mat.rotate(rot);
mat.translate(-boxSize / 2);
}
mat.scale(boxSize);
if (transform != HYPRUTILS_TRANSFORM_NORMAL) {
mat.translate({0.5, 0.5});
mat.transform(transform);
mat.translate({-0.5, -0.5});
}
return this->copy().multiply(mat);
}
Mat3x3& Mat3x3::transform(eTransform transform) {
multiply(transforms.at(transform));
return *this;
}
Mat3x3& Mat3x3::rotate(float rot) {
multiply(std::array<float, 9>{cosf(rot), -sinf(rot), 0.0f, sinf(rot), cosf(rot), 0.0f, 0.0f, 0.0f, 1.0f});
return *this;
}
Mat3x3& Mat3x3::scale(const Vector2D& scale_) {
multiply(std::array<float, 9>{sc<float>(scale_.x), 0.0f, 0.0f, 0.0f, sc<float>(scale_.y), 0.0f, 0.0f, 0.0f, 1.0f});
return *this;
}
Mat3x3& Mat3x3::scale(const float scale_) {
return scale({scale_, scale_});
}
Mat3x3& Mat3x3::translate(const Vector2D& offset) {
multiply(std::array<float, 9>{1.0f, 0.0f, sc<float>(offset.x), 0.0f, 1.0f, sc<float>(offset.y), 0.0f, 0.0f, 1.0f});
return *this;
}
Mat3x3& Mat3x3::transpose() {
matrix = std::array<float, 9>{matrix[0], matrix[3], matrix[6], matrix[1], matrix[4], matrix[7], matrix[2], matrix[5], matrix[8]};
return *this;
}
Mat3x3& Mat3x3::multiply(const Mat3x3& other) {
const float* m1 = matrix.data(); // Pointer to current matrix
const float* m2 = other.matrix.data(); // Pointer to the other matrix
std::array<float, 9> product;
product[0] = m1[0] * m2[0] + m1[1] * m2[3] + m1[2] * m2[6];
product[1] = m1[0] * m2[1] + m1[1] * m2[4] + m1[2] * m2[7];
product[2] = m1[0] * m2[2] + m1[1] * m2[5] + m1[2] * m2[8];
product[3] = m1[3] * m2[0] + m1[4] * m2[3] + m1[5] * m2[6];
product[4] = m1[3] * m2[1] + m1[4] * m2[4] + m1[5] * m2[7];
product[5] = m1[3] * m2[2] + m1[4] * m2[5] + m1[5] * m2[8];
product[6] = m1[6] * m2[0] + m1[7] * m2[3] + m1[8] * m2[6];
product[7] = m1[6] * m2[1] + m1[7] * m2[4] + m1[8] * m2[7];
product[8] = m1[6] * m2[2] + m1[7] * m2[5] + m1[8] * m2[8];
matrix = product;
return *this;
}
Mat3x3 Mat3x3::copy() const {
return *this;
}
std::string Mat3x3::toString() const {
for (const auto& m : matrix) {
if (!std::isfinite(m))
return "[mat3x3: invalid values]";
}
return std::format("[mat3x3: {}, {}, {}, {}, {}, {}, {}, {}, {}]", matrix.at(0), matrix.at(1), matrix.at(2), matrix.at(3), matrix.at(4), matrix.at(5), matrix.at(6),
matrix.at(7), matrix.at(8));
}

222
src/math/Region.cpp Normal file
View file

@ -0,0 +1,222 @@
#include "hyprutils/memory/Casts.hpp"
#include <hyprutils/math/Region.hpp>
#include <cmath>
using namespace Hyprutils::Math;
using namespace Hyprutils::Memory;
constexpr const int64_t MAX_REGION_SIDE = 10000000;
Hyprutils::Math::CRegion::CRegion() {
pixman_region32_init(&m_rRegion);
}
Hyprutils::Math::CRegion::CRegion(const pixman_region32_t* const ref) {
pixman_region32_init(&m_rRegion);
pixman_region32_copy(&m_rRegion, ref);
}
Hyprutils::Math::CRegion::CRegion(double x, double y, double w, double h) {
pixman_region32_init_rect(&m_rRegion, x, y, w, h);
}
Hyprutils::Math::CRegion::CRegion(const CBox& box) {
pixman_region32_init_rect(&m_rRegion, box.x, box.y, box.w, box.h);
}
Hyprutils::Math::CRegion::CRegion(pixman_box32_t* box) {
pixman_region32_init_rect(&m_rRegion, box->x1, box->y1, box->x2 - box->x1, box->y2 - box->y1);
}
Hyprutils::Math::CRegion::CRegion(const CRegion& other) {
pixman_region32_init(&m_rRegion);
pixman_region32_copy(&m_rRegion, other.pixman());
}
Hyprutils::Math::CRegion::CRegion(CRegion&& other) noexcept {
pixman_region32_init(&m_rRegion);
pixman_region32_copy(&m_rRegion, other.pixman());
}
Hyprutils::Math::CRegion::~CRegion() {
pixman_region32_fini(&m_rRegion);
}
CRegion& Hyprutils::Math::CRegion::clear() {
pixman_region32_clear(&m_rRegion);
return *this;
}
CRegion& Hyprutils::Math::CRegion::set(const CRegion& other) {
pixman_region32_copy(&m_rRegion, other.pixman());
return *this;
}
CRegion& Hyprutils::Math::CRegion::add(const CRegion& other) {
pixman_region32_union(&m_rRegion, &m_rRegion, other.pixman());
return *this;
}
CRegion& Hyprutils::Math::CRegion::add(double x, double y, double w, double h) {
pixman_region32_union_rect(&m_rRegion, &m_rRegion, x, y, w, h);
return *this;
}
CRegion& Hyprutils::Math::CRegion::add(const CBox& other) {
pixman_region32_union_rect(&m_rRegion, &m_rRegion, other.x, other.y, other.w, other.h);
return *this;
}
CRegion& Hyprutils::Math::CRegion::subtract(const CRegion& other) {
pixman_region32_subtract(&m_rRegion, &m_rRegion, other.pixman());
return *this;
}
CRegion& Hyprutils::Math::CRegion::intersect(const CRegion& other) {
pixman_region32_intersect(&m_rRegion, &m_rRegion, other.pixman());
return *this;
}
CRegion& Hyprutils::Math::CRegion::intersect(double x, double y, double w, double h) {
pixman_region32_intersect_rect(&m_rRegion, &m_rRegion, x, y, w, h);
return *this;
}
CRegion& Hyprutils::Math::CRegion::invert(pixman_box32_t* box) {
pixman_region32_inverse(&m_rRegion, &m_rRegion, box);
return *this;
}
CRegion& Hyprutils::Math::CRegion::invert(const CBox& box) {
pixman_box32 pixmanBox = {.x1 = sc<int32_t>(box.x), .y1 = sc<int32_t>(box.y), .x2 = sc<int32_t>(box.w) + sc<int32_t>(box.x), .y2 = sc<int32_t>(box.h) + sc<int32_t>(box.y)};
return this->invert(&pixmanBox);
}
CRegion& Hyprutils::Math::CRegion::translate(const Vector2D& vec) {
pixman_region32_translate(&m_rRegion, vec.x, vec.y);
return *this;
}
CRegion& Hyprutils::Math::CRegion::transform(const eTransform t, double w, double h) {
if (t == HYPRUTILS_TRANSFORM_NORMAL)
return *this;
auto rects = getRects();
clear();
for (auto& r : rects) {
CBox xfmd{r.x1, r.y1, r.x2 - r.x1, r.y2 - r.y1};
xfmd.transform(t, w, h);
add(xfmd);
}
return *this;
}
CRegion& Hyprutils::Math::CRegion::expand(double units) {
auto rects = getRects();
clear();
for (auto& r : rects) {
CBox b{sc<double>(r.x1) - units, sc<double>(r.y1) - units, sc<double>(r.x2) - r.x1 + (units * 2), sc<double>(r.y2) - r.y1 + (units * 2)};
add(b);
}
return *this;
}
CRegion& Hyprutils::Math::CRegion::rationalize() {
intersect(CBox{-MAX_REGION_SIDE, -MAX_REGION_SIDE, MAX_REGION_SIDE * 2, MAX_REGION_SIDE * 2});
return *this;
}
CRegion Hyprutils::Math::CRegion::copy() const {
return CRegion(*this);
}
CRegion& Hyprutils::Math::CRegion::scale(float scale_) {
scale({scale_, scale_});
return *this;
}
CRegion& Hyprutils::Math::CRegion::scale(const Vector2D& scale) {
if (scale == Vector2D{1, 1})
return *this;
int rectsNum = 0;
auto RECTSARR = pixman_region32_rectangles(&m_rRegion, &rectsNum);
std::vector<pixman_box32_t> boxes;
boxes.resize(rectsNum);
for (int i = 0; i < rectsNum; ++i) {
boxes[i].x1 = std::floor(RECTSARR[i].x1 * scale.x);
boxes[i].x2 = std::ceil(RECTSARR[i].x2 * scale.x);
boxes[i].y1 = std::floor(RECTSARR[i].y1 * scale.y);
boxes[i].y2 = std::ceil(RECTSARR[i].y2 * scale.y);
}
pixman_region32_fini(&m_rRegion);
pixman_region32_init_rects(&m_rRegion, boxes.data(), boxes.size());
return *this;
}
std::vector<pixman_box32_t> Hyprutils::Math::CRegion::getRects() const {
std::vector<pixman_box32_t> result;
int rectsNum = 0;
const auto RECTSARR = pixman_region32_rectangles(&m_rRegion, &rectsNum);
result.assign(RECTSARR, RECTSARR + rectsNum);
return result;
}
CBox Hyprutils::Math::CRegion::getExtents() {
pixman_box32_t* box = pixman_region32_extents(&m_rRegion);
return {sc<double>(box->x1), sc<double>(box->y1), sc<double>(box->x2) - box->x1, sc<double>(box->y2) - box->y1};
}
bool Hyprutils::Math::CRegion::containsPoint(const Vector2D& vec) const {
return pixman_region32_contains_point(&m_rRegion, vec.x, vec.y, nullptr);
}
bool Hyprutils::Math::CRegion::empty() const {
return !pixman_region32_not_empty(&m_rRegion);
}
Vector2D Hyprutils::Math::CRegion::closestPoint(const Vector2D& vec) const {
if (containsPoint(vec))
return vec;
double bestDist = __FLT_MAX__;
Vector2D leader = vec;
for (auto& box : getRects()) {
double x = 0, y = 0;
if (vec.x >= box.x2)
x = box.x2 - 1;
else if (vec.x < box.x1)
x = box.x1;
else
x = vec.x;
if (vec.y >= box.y2)
y = box.y2 - 1;
else if (vec.y < box.y1)
y = box.y1;
else
y = vec.y;
double distance = pow(x, 2) + pow(y, 2);
if (distance < bestDist) {
bestDist = distance;
leader = {x, y};
}
}
return leader;
}

59
src/math/Vector2D.cpp Normal file
View file

@ -0,0 +1,59 @@
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/memory/Casts.hpp>
#include <hyprutils/math/Misc.hpp>
#include <algorithm>
#include <cmath>
using namespace Hyprutils::Math;
double Hyprutils::Math::Vector2D::normalize() {
// get max abs
const auto max = std::abs(x) > std::abs(y) ? std::abs(x) : std::abs(y);
x /= max;
y /= max;
return max;
}
Vector2D Hyprutils::Math::Vector2D::floor() const {
return Vector2D(std::floor(x), std::floor(y));
}
Vector2D Hyprutils::Math::Vector2D::round() const {
return Vector2D(std::round(x), std::round(y));
}
Vector2D Hyprutils::Math::Vector2D::clamp(const Vector2D& min, const Vector2D& max) const {
return Vector2D(std::clamp(this->x, min.x, max.x < min.x ? INFINITY : max.x), std::clamp(this->y, min.y, max.y < min.y ? INFINITY : max.y));
}
double Hyprutils::Math::Vector2D::distance(const Vector2D& other) const {
return std::sqrt(distanceSq(other));
}
double Hyprutils::Math::Vector2D::distanceSq(const Vector2D& other) const {
return ((x - other.x) * (x - other.x)) + ((y - other.y) * (y - other.y));
}
double Hyprutils::Math::Vector2D::size() const {
return std::sqrt((x * x) + (y * y));
}
Vector2D Hyprutils::Math::Vector2D::getComponentMax(const Vector2D& other) const {
return Vector2D(std::max(this->x, other.x), std::max(this->y, other.y));
}
Vector2D Hyprutils::Math::Vector2D::transform(eTransform transform, const Vector2D& monitorSize) const {
switch (transform) {
case HYPRUTILS_TRANSFORM_NORMAL: return *this;
case HYPRUTILS_TRANSFORM_90: return Vector2D(y, monitorSize.y - x);
case HYPRUTILS_TRANSFORM_180: return Vector2D(monitorSize.x - x, monitorSize.y - y);
case HYPRUTILS_TRANSFORM_270: return Vector2D(monitorSize.x - y, x);
case HYPRUTILS_TRANSFORM_FLIPPED: return Vector2D(monitorSize.x - x, y);
case HYPRUTILS_TRANSFORM_FLIPPED_90: return Vector2D(y, x);
case HYPRUTILS_TRANSFORM_FLIPPED_180: return Vector2D(x, monitorSize.y - y);
case HYPRUTILS_TRANSFORM_FLIPPED_270: return Vector2D(monitorSize.x - y, monitorSize.y - x);
default: return *this;
}
}

20
src/os/File.cpp Normal file
View file

@ -0,0 +1,20 @@
#include <hyprutils/os/File.hpp>
#include <filesystem>
#include <fstream>
using namespace Hyprutils;
using namespace Hyprutils::File;
std::expected<std::string, std::string> File::readFileAsString(const std::string_view& path) {
std::error_code ec;
if (!std::filesystem::exists(path, ec) || ec)
return std::unexpected("File not found");
std::ifstream file(std::string{path});
if (!file.good())
return std::unexpected("Failed to open file");
return std::string((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>()));
}

86
src/os/FileDescriptor.cpp Normal file
View file

@ -0,0 +1,86 @@
#include <cstdlib>
#include <hyprutils/os/FileDescriptor.hpp>
#include <fcntl.h>
#include <sys/poll.h>
#include <unistd.h>
#include <utility>
using namespace Hyprutils::OS;
CFileDescriptor::CFileDescriptor(int const fd) : m_fd(fd) {}
CFileDescriptor::CFileDescriptor(CFileDescriptor&& other) : m_fd(std::exchange(other.m_fd, -1)) {}
CFileDescriptor& CFileDescriptor::operator=(CFileDescriptor&& other) {
if (this == &other) // Shit will go haywire if there is duplicate ownership
abort();
reset();
m_fd = std::exchange(other.m_fd, -1);
return *this;
}
CFileDescriptor::~CFileDescriptor() {
reset();
}
bool CFileDescriptor::isValid() const {
return m_fd != -1;
}
int CFileDescriptor::get() const {
return m_fd;
}
int CFileDescriptor::getFlags() const {
return fcntl(m_fd, F_GETFD);
}
bool CFileDescriptor::setFlags(int flags) {
return fcntl(m_fd, F_SETFD, flags) != -1;
}
int CFileDescriptor::take() {
return std::exchange(m_fd, -1);
}
void CFileDescriptor::reset() {
if (m_fd != -1) {
close(m_fd);
m_fd = -1;
}
}
CFileDescriptor CFileDescriptor::duplicate(int flags) const {
if (m_fd == -1)
return {};
return CFileDescriptor{fcntl(m_fd, flags, 0)};
}
bool CFileDescriptor::isClosed() const {
return isClosed(m_fd);
}
bool CFileDescriptor::isReadable() const {
return isReadable(m_fd);
}
bool CFileDescriptor::isClosed(int fd) {
pollfd pfd = {
.fd = fd,
.events = POLLIN,
.revents = 0,
};
if (poll(&pfd, 1, 0) < 0)
return true;
return pfd.revents & (POLLHUP | POLLERR);
}
bool CFileDescriptor::isReadable(int fd) {
pollfd pfd = {.fd = fd, .events = POLLIN, .revents = 0};
return poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN);
}

285
src/os/Process.cpp Normal file
View file

@ -0,0 +1,285 @@
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/Casts.hpp>
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
#include <csignal>
#include <cstdio>
#include <unistd.h>
#include <cstring>
#include <array>
#include <thread>
#include <sys/fcntl.h>
#include <sys/wait.h>
#include <sys/poll.h>
struct Hyprutils::OS::CProcess::impl {
std::string binary, out, err;
std::vector<std::string> args;
std::vector<std::pair<std::string, std::string>> env;
pid_t grandchildPid = 0;
int stdoutFD = -1, stderrFD = -1, exitCode = 0, stdinFD = -1;
};
Hyprutils::OS::CProcess::CProcess(const std::string& binary, const std::vector<std::string>& args) : m_impl(new impl()) {
m_impl->binary = binary;
m_impl->args = args;
}
Hyprutils::OS::CProcess::~CProcess() {
delete m_impl;
}
void Hyprutils::OS::CProcess::addEnv(const std::string& name, const std::string& value) {
m_impl->env.emplace_back(std::make_pair<>(name, value));
}
bool Hyprutils::OS::CProcess::runSync() {
int outPipe[2], errPipe[2];
if (pipe(outPipe))
return false;
if (pipe(errPipe)) {
close(outPipe[0]);
close(outPipe[1]);
return false;
}
int pid = fork();
if (pid == -1) {
close(outPipe[0]);
close(outPipe[1]);
close(outPipe[0]);
close(outPipe[1]);
return false;
}
if (!pid) {
// child
close(outPipe[0]);
close(errPipe[0]);
dup2(outPipe[1], 1 /* stdout */);
dup2(errPipe[1], 2 /* stderr */);
// build argv
std::vector<char*> argsC;
argsC.emplace_back(strdup(m_impl->binary.c_str()));
for (auto& arg : m_impl->args) {
// TODO: does this leak? Can we just pipe c_str() as the strings won't be realloc'd?
argsC.emplace_back(strdup(arg.c_str()));
}
argsC.emplace_back(nullptr);
// pass env
for (auto& [n, v] : m_impl->env) {
setenv(n.c_str(), v.c_str(), 1);
}
execvp(m_impl->binary.c_str(), argsC.data());
exit(1);
} else {
// parent
close(outPipe[1]);
close(errPipe[1]);
m_impl->out = "";
m_impl->err = "";
m_impl->grandchildPid = pid;
std::array<char, 1024> buf;
buf.fill(0);
// wait for read
ssize_t ret = 0;
int fdFlags = fcntl(outPipe[0], F_GETFL, 0);
if (fcntl(outPipe[0], F_SETFL, fdFlags | O_NONBLOCK) < 0)
return false;
fdFlags = fcntl(errPipe[0], F_GETFL, 0);
if (fcntl(errPipe[0], F_SETFL, fdFlags | O_NONBLOCK) < 0)
return false;
pollfd pollfds[2] = {
{.fd = outPipe[0], .events = POLLIN, .revents = 0},
{.fd = errPipe[0], .events = POLLIN, .revents = 0},
};
while (1337) {
int ret = poll(pollfds, 2, 5000);
if (ret < 0) {
if (errno == EINTR)
continue;
return false;
}
bool hupd = false;
for (size_t i = 0; i < 2; ++i) {
if (pollfds[i].revents & POLLHUP) {
hupd = true;
break;
}
}
if (hupd)
break;
if (pollfds[0].revents & POLLIN) {
while ((ret = read(outPipe[0], buf.data(), 1023)) > 0) {
m_impl->out += std::string_view{buf.data(), sc<size_t>(ret)};
}
buf.fill(0);
}
if (pollfds[1].revents & POLLIN) {
while ((ret = read(errPipe[0], buf.data(), 1023)) > 0) {
m_impl->err += std::string_view{buf.data(), sc<size_t>(ret)};
}
buf.fill(0);
}
}
// Final reads. Nonblock, so its ok.
while ((ret = read(outPipe[0], buf.data(), 1023)) > 0) {
m_impl->out += std::string_view{buf.data(), sc<size_t>(ret)};
}
buf.fill(0);
while ((ret = read(errPipe[0], buf.data(), 1023)) > 0) {
m_impl->err += std::string_view{buf.data(), sc<size_t>(ret)};
}
buf.fill(0);
close(outPipe[0]);
close(errPipe[0]);
// reap child
int status = 0;
waitpid(pid, &status, 0);
if (WIFEXITED(status))
m_impl->exitCode = WEXITSTATUS(status);
return true;
}
return true;
}
bool Hyprutils::OS::CProcess::runAsync() {
int socket[2];
if (pipe(socket) != 0)
return false;
pid_t child, grandchild;
child = fork();
if (child < 0) {
close(socket[0]);
close(socket[1]);
return false;
}
if (child == 0) {
// run in child
sigset_t set;
sigemptyset(&set);
sigprocmask(SIG_SETMASK, &set, nullptr);
grandchild = fork();
if (grandchild == 0) {
// run in grandchild
close(socket[0]);
close(socket[1]);
// build argv
std::vector<char*> argsC;
argsC.emplace_back(strdup(m_impl->binary.c_str()));
for (auto& arg : m_impl->args) {
argsC.emplace_back(strdup(arg.c_str()));
}
argsC.emplace_back(nullptr);
// pass env
for (auto& [n, v] : m_impl->env) {
setenv(n.c_str(), v.c_str(), 1);
}
if (m_impl->stdinFD != -1) {
dup2(m_impl->stdinFD, STDIN_FILENO);
close(m_impl->stdinFD);
}
if (m_impl->stdoutFD != -1) {
dup2(m_impl->stdoutFD, STDOUT_FILENO);
close(m_impl->stdoutFD);
}
if (m_impl->stderrFD != -1) {
dup2(m_impl->stderrFD, STDERR_FILENO);
close(m_impl->stderrFD);
}
execvp(m_impl->binary.c_str(), argsC.data());
_exit(0);
}
close(socket[0]);
if (write(socket[1], &grandchild, sizeof(grandchild)) != sizeof(grandchild)) {
close(socket[1]);
_exit(1);
}
close(socket[1]);
_exit(0);
}
// run in parent
close(socket[1]);
ssize_t bytesRead = read(socket[0], &grandchild, sizeof(grandchild));
close(socket[0]);
if (bytesRead != sizeof(grandchild)) {
waitpid(child, nullptr, 0);
return false;
}
// clear child and leave grandchild to init
waitpid(child, nullptr, 0);
m_impl->grandchildPid = grandchild;
return true;
}
const std::string& Hyprutils::OS::CProcess::stdOut() {
return m_impl->out;
}
const std::string& Hyprutils::OS::CProcess::stdErr() {
return m_impl->err;
}
pid_t Hyprutils::OS::CProcess::pid() {
return m_impl->grandchildPid;
}
int Hyprutils::OS::CProcess::exitCode() {
return m_impl->exitCode;
}
void Hyprutils::OS::CProcess::setStdinFD(int fd) {
m_impl->stdinFD = fd;
}
void Hyprutils::OS::CProcess::setStdoutFD(int fd) {
m_impl->stdoutFD = fd;
}
void Hyprutils::OS::CProcess::setStderrFD(int fd) {
m_impl->stderrFD = fd;
}

81
src/path/Path.cpp Normal file
View file

@ -0,0 +1,81 @@
#include <hyprutils/path/Path.hpp>
#include <hyprutils/string/VarList.hpp>
#include <filesystem>
using namespace Hyprutils;
namespace Hyprutils::Path {
std::string fullConfigPath(std::string basePath, std::string programName) {
return basePath + "/hypr/" + programName + ".conf";
}
bool checkConfigExists(std::string basePath, std::string programName) {
return std::filesystem::exists(fullConfigPath(basePath, programName));
}
std::optional<std::string> getHome() {
static const auto homeDir = getenv("HOME");
if (!homeDir || !std::filesystem::path(homeDir).is_absolute())
return std::nullopt;
return std::string(homeDir).append("/.config");
}
std::optional<String::CVarList> getXdgConfigDirs() {
static const auto xdgConfigDirs = getenv("XDG_CONFIG_DIRS");
if (!xdgConfigDirs)
return std::nullopt;
static const auto xdgConfigDirsList = String::CVarList(xdgConfigDirs, 0, ':');
return xdgConfigDirsList;
}
std::optional<std::string> getXdgConfigHome() {
static const auto xdgConfigHome = getenv("XDG_CONFIG_HOME");
if (!xdgConfigHome || !std::filesystem::path(xdgConfigHome).is_absolute())
return std::nullopt;
return xdgConfigHome;
}
using T = std::optional<std::string>;
std::pair<T, T> findConfig(std::string programName) {
bool xdgConfigHomeExists = false;
static const auto xdgConfigHome = getXdgConfigHome();
if (xdgConfigHome.has_value()) {
xdgConfigHomeExists = true;
if (checkConfigExists(xdgConfigHome.value(), programName))
return std::make_pair(fullConfigPath(xdgConfigHome.value(), programName), xdgConfigHome);
}
bool homeExists = false;
static const auto home = getHome();
if (home.has_value()) {
homeExists = true;
if (checkConfigExists(home.value(), programName))
return std::make_pair(fullConfigPath(home.value(), programName), home);
}
static const auto xdgConfigDirs = getXdgConfigDirs();
if (xdgConfigDirs.has_value()) {
for (auto& dir : xdgConfigDirs.value()) {
if (checkConfigExists(dir, programName))
return std::make_pair(fullConfigPath(dir, programName), std::nullopt);
}
}
if (checkConfigExists("/etc/xdg", programName))
return std::make_pair(fullConfigPath("/etc/xdg", programName), std::nullopt);
if (xdgConfigHomeExists)
return std::make_pair(std::nullopt, xdgConfigHome);
else if (homeExists)
return std::make_pair(std::nullopt, home);
return std::make_pair(std::nullopt, std::nullopt);
}
}

View file

@ -1,22 +1,20 @@
#include <hyprutils/signal/Listener.hpp> #include <hyprutils/signal/Listener.hpp>
#include <tuple>
using namespace Hyprutils::Signal; using namespace Hyprutils::Signal;
Hyprutils::Signal::CSignalListener::CSignalListener(std::function<void(std::any)> handler) : m_fHandler(handler) { Hyprutils::Signal::CSignalListener::CSignalListener(std::function<void(void*)> handler) : m_fHandler(handler) {
; ;
} }
void Hyprutils::Signal::CSignalListener::emit(std::any data) { void Hyprutils::Signal::CSignalListener::emitInternal(void* data) {
if (!m_fHandler) if (!m_fHandler)
return; return;
m_fHandler(data); m_fHandler(data);
} }
Hyprutils::Signal::CStaticSignalListener::CStaticSignalListener(std::function<void(void*, std::any)> handler, void* owner) : m_pOwner(owner), m_fHandler(handler) { void Hyprutils::Signal::CSignalListener::emit(std::any data) {
; auto dataTuple = std::tuple<std::any>(data);
} emitInternal(&dataTuple);
void Hyprutils::Signal::CStaticSignalListener::emit(std::any data) {
m_fHandler(m_pOwner, data);
} }

View file

@ -1,3 +1,4 @@
#include "hyprutils/memory/SharedPtr.hpp"
#include <hyprutils/signal/Signal.hpp> #include <hyprutils/signal/Signal.hpp>
#include <hyprutils/memory/WeakPtr.hpp> #include <hyprutils/memory/WeakPtr.hpp>
#include <algorithm> #include <algorithm>
@ -8,51 +9,52 @@ using namespace Hyprutils::Memory;
#define SP CSharedPointer #define SP CSharedPointer
#define WP CWeakPointer #define WP CWeakPointer
void Hyprutils::Signal::CSignal::emit(std::any data) { void Hyprutils::Signal::CSignalBase::emitInternal(void* args) {
bool dirty = false;
std::vector<SP<CSignalListener>> listeners; std::vector<SP<CSignalListener>> listeners;
listeners.reserve(m_vListeners.size());
for (auto& l : m_vListeners) { for (auto& l : m_vListeners) {
if (l.expired()) { if (l.expired())
dirty = true;
continue; continue;
}
listeners.emplace_back(l.lock()); listeners.emplace_back(l.lock());
} }
std::vector<CStaticSignalListener*> statics; auto statics = m_vStaticListeners;
for (auto& l : m_vStaticListeners) {
statics.emplace_back(l.get());
}
for (auto& l : listeners) { for (auto& l : listeners) {
// if there is only one lock, it means the event is only held by the listeners // if there is only one lock, it means the event is only held by the listeners
// vector and was removed during our iteration // vector and was removed during our iteration
if (l.strongRef() == 1) { if (l.strongRef() == 1)
dirty = true;
continue; continue;
}
l->emit(data); l->emitInternal(args);
} }
for (auto& l : statics) { for (auto& l : statics) {
l->emit(data); l->emitInternal(args);
} }
// release SPs // release SPs
listeners.clear(); listeners.clear();
if (dirty) // we cannot release any expired refs here as one of the listeners could've removed this object and
std::erase_if(m_vListeners, [](const auto& other) { return other.expired(); }); // as such we'd be doing a UAF
} }
CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function<void(std::any)> handler) { CHyprSignalListener Hyprutils::Signal::CSignalBase::registerListenerInternal(std::function<void(void*)> handler) {
CHyprSignalListener listener = makeShared<CSignalListener>(handler); CHyprSignalListener listener = SP<CSignalListener>(new CSignalListener(handler));
m_vListeners.emplace_back(listener); m_vListeners.emplace_back(listener);
// housekeeping: remove any stale listeners
std::erase_if(m_vListeners, [](const auto& other) { return other.expired(); });
return listener; return listener;
} }
void Hyprutils::Signal::CSignal::registerStaticListener(std::function<void(void*, std::any)> handler, void* owner) { void Hyprutils::Signal::CSignalBase::registerStaticListenerInternal(std::function<void(void*)> handler) {
m_vStaticListeners.emplace_back(std::make_unique<CStaticSignalListener>(handler, owner)); m_vStaticListeners.emplace_back(SP<CSignalListener>(new CSignalListener(handler)));
} }
void Hyprutils::Signal::CSignal::emit(std::any data) {
CSignalT::emit(data);
}

View file

@ -0,0 +1,38 @@
#include <ranges>
#include <algorithm>
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/ConstVarList.hpp>
using namespace Hyprutils::String;
CConstVarList::CConstVarList(const std::string& in, const size_t lastArgNo, const char delim, const bool removeEmpty) : m_str(in) {
if (in.empty())
return;
size_t idx = 0;
size_t pos = 0;
std::ranges::replace_if(m_str, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0);
for (const auto& s : m_str | std::views::split(0)) {
if (removeEmpty && s.empty())
continue;
if (++idx == lastArgNo) {
m_args.emplace_back(trim(std::string_view{m_str}.substr(pos)));
break;
}
pos += s.size() + 1;
m_args.emplace_back(trim(std::string_view{s.data()}));
}
}
std::string CConstVarList::join(const std::string& joiner, size_t from, size_t to) const {
size_t last = to == 0 ? size() : to;
std::string rolling;
for (size_t i = from; i < last; ++i) {
// cast can be removed once C++26's change to allow this is supported
rolling += std::string{m_args[i]} + (i + 1 < last ? joiner : "");
}
return rolling;
}

View file

@ -7,12 +7,12 @@ std::string Hyprutils::String::trim(const std::string& in) {
if (in.empty()) if (in.empty())
return in; return in;
int countBefore = 0; size_t countBefore = 0;
while (countBefore < in.length() && std::isspace(in.at(countBefore))) { while (countBefore < in.length() && std::isspace(in.at(countBefore))) {
countBefore++; countBefore++;
} }
int countAfter = 0; size_t countAfter = 0;
while (countAfter < in.length() - countBefore && std::isspace(in.at(in.length() - countAfter - 1))) { while (countAfter < in.length() - countBefore && std::isspace(in.at(in.length() - countAfter - 1))) {
countAfter++; countAfter++;
} }
@ -22,10 +22,33 @@ std::string Hyprutils::String::trim(const std::string& in) {
return result; return result;
} }
std::string_view Hyprutils::String::trim(const std::string_view& sv) {
if (sv.empty())
return sv;
size_t countBefore = 0;
while (countBefore < sv.length() && std::isspace(sv.at(countBefore))) {
countBefore++;
}
size_t countAfter = 0;
while (countAfter < sv.length() - countBefore && std::isspace(sv.at(sv.length() - countAfter - 1))) {
countAfter++;
}
return sv.substr(countBefore, sv.length() - countBefore - countAfter);
}
std::string Hyprutils::String::trim(const char* in) {
return trim(std::string{in});
}
bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) { bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) {
if (str.empty()) if (str.empty())
return false; return false;
bool decimalParsed = false;
for (size_t i = 0; i < str.length(); ++i) { for (size_t i = 0; i < str.length(); ++i) {
const char& c = str.at(i); const char& c = str.at(i);
@ -44,15 +67,37 @@ bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) {
if (i == 0) if (i == 0)
return false; return false;
if (str.at(0) == '-') if (decimalParsed)
return false; return false;
decimalParsed = true;
continue; continue;
} }
} }
if (str.back() == '.') return isdigit(str.back()) != 0;
}
void Hyprutils::String::replaceInString(std::string& string, const std::string& what, const std::string& to) {
if (string.empty())
return;
size_t pos = 0;
while ((pos = string.find(what, pos)) != std::string::npos) {
string.replace(pos, what.length(), to);
pos += to.length();
}
}
bool Hyprutils::String::truthy(const std::string_view& in) {
if (in == "1")
return true;
if (in == "0")
return false; return false;
return true; std::string lower = std::string{in};
std::ranges::transform(lower, lower.begin(), ::tolower);
return lower.starts_with("true") || lower.starts_with("yes") || lower.starts_with("on");
} }

View file

@ -6,14 +6,13 @@
using namespace Hyprutils::String; using namespace Hyprutils::String;
Hyprutils::String::CVarList::CVarList(const std::string& in, const size_t lastArgNo, const char delim, const bool removeEmpty) { Hyprutils::String::CVarList::CVarList(const std::string& in, const size_t lastArgNo, const char delim, const bool removeEmpty) {
if (in.empty()) if (!removeEmpty && in.empty())
m_vArgs.emplace_back(""); m_vArgs.emplace_back("");
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())
@ -23,7 +22,7 @@ Hyprutils::String::CVarList::CVarList(const std::string& in, const size_t lastAr
break; break;
} }
pos += s.size() + 1; pos += s.size() + 1;
m_vArgs.emplace_back(trim(std::string_view{s}.data())); m_vArgs.emplace_back(trim(std::string{s.data()}));
} }
} }
@ -36,4 +35,4 @@ std::string Hyprutils::String::CVarList::join(const std::string& joiner, size_t
} }
return rolling; return rolling;
} }

113
src/string/VarList2.cpp Normal file
View file

@ -0,0 +1,113 @@
#include <algorithm>
#include <hyprutils/string/VarList2.hpp>
#include <hyprutils/string/String.hpp>
using namespace Hyprutils::String;
CVarList2::CVarList2(std::string&& in, const size_t lastArgNo, const char delim, const bool removeEmpty, const bool allowEscape) : m_inString(std::move(in)) {
if (m_inString.empty())
return;
auto isDelimiter = [&delim](const char& c) { return delim == 's' ? std::isspace(c) : delim == c; };
size_t argBegin = 0;
std::vector<size_t> escapedIndices; // local to the current arg
for (size_t i = 0; i < m_inString.size(); ++i) {
const char& c = m_inString[i];
if (!isDelimiter(c))
continue;
if (allowEscape) {
// we allow escape, so this might be escaped. Check first
if (i - argBegin != 0) {
const char& previousC = m_inString[i - 1];
if (i - argBegin == 1) {
if (previousC == '\\') {
escapedIndices.emplace_back(i - argBegin - 1);
continue; // escaped
}
// fall to breaking, not escaped
} else {
const char& prevPreviousC = m_inString[i - 2];
if (previousC == '\\') {
// whether or not escaped, pop char
escapedIndices.emplace_back(i - argBegin - 1);
if (prevPreviousC != '\\') {
// escaped
continue;
}
}
// fall to breaking, not escaped, but mark the \\ to be popped
}
// fall to breaking, couldn't be escaped
}
}
// here we found a delimiter and need to break up the string (not escaped)
if (escapedIndices.empty()) {
// we didn't escape anything, so we can use inString
const auto ARG = trim(std::string_view{m_inString}.substr(argBegin, i - argBegin));
if (!ARG.empty() || !removeEmpty)
m_args.emplace_back(ARG);
} else {
// we escaped something, fixup the string, add to copies, then emplace
std::string cpy = m_inString.substr(argBegin, i - argBegin);
for (size_t i = 0; i < escapedIndices.size(); ++i) {
cpy = cpy.substr(0, escapedIndices[i] - i) + cpy.substr(escapedIndices[i] - i + 1);
}
m_copyStrings.emplace_back(std::move(cpy));
m_args.emplace_back(trim(std::string_view{m_copyStrings.back()}));
}
// update next argBegin
argBegin = i + 1;
escapedIndices.clear();
}
// append anything left
if (argBegin < m_inString.size()) {
if (escapedIndices.empty()) {
// we didn't escape anything, so we can use inString
const auto ARG = trim(std::string_view{m_inString}.substr(argBegin, m_inString.size() - argBegin));
if (!ARG.empty() || !removeEmpty)
m_args.emplace_back(ARG);
} else {
// we escaped something, fixup the string, add to copies, then emplace
std::string cpy = m_inString.substr(argBegin, m_inString.size() - argBegin);
for (size_t i = 0; i < escapedIndices.size(); ++i) {
cpy = cpy.substr(0, escapedIndices[i] - i) + cpy.substr(escapedIndices[i] - i + 1);
}
m_copyStrings.emplace_back(std::move(cpy));
m_args.emplace_back(trim(std::string_view{m_copyStrings.back()}));
}
}
}
std::string CVarList2::join(const std::string& joiner, size_t from, size_t to) const {
if (to == 0 || to <= from)
to = m_args.size();
std::string roll;
for (size_t i = from; i < to && i < m_args.size(); ++i) {
roll += m_args[i];
if (i + 1 < to && i + 1 < m_args.size())
roll += joiner;
}
return roll;
}
void CVarList2::append(std::string&& arg) {
m_copyStrings.emplace_back(std::move(arg));
m_args.emplace_back(m_copyStrings.back());
}
bool CVarList2::contains(const std::string& el) {
return std::ranges::any_of(m_args, [&el](const auto& e) { return e == el; });
}

12
src/utils/ScopeGuard.cpp Normal file
View file

@ -0,0 +1,12 @@
#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Hyprutils::Utils;
Hyprutils::Utils::CScopeGuard::CScopeGuard(const std::function<void()>& fn_) : fn(fn_) {
;
}
Hyprutils::Utils::CScopeGuard::~CScopeGuard() {
if (fn)
fn();
}

View file

@ -0,0 +1,395 @@
#include <gtest/gtest.h>
#include <hyprutils/animation/AnimationConfig.hpp>
#include <hyprutils/animation/AnimationManager.hpp>
#include <hyprutils/animation/AnimatedVariable.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#define SP CSharedPointer
#define WP CWeakPointer
#define UP CUniquePointer
using namespace Hyprutils::Animation;
using namespace Hyprutils::Math;
using namespace Hyprutils::Memory;
class EmtpyContext {};
template <typename VarType>
using CAnimatedVariable = CGenericAnimatedVariable<VarType, EmtpyContext>;
template <typename VarType>
using PANIMVAR = UP<CAnimatedVariable<VarType>>;
template <typename VarType>
using PANIMVARREF = WP<CAnimatedVariable<VarType>>;
enum eAVTypes {
INT = 1,
TEST,
};
struct SomeTestType {
bool done = false;
bool operator==(const SomeTestType& other) const {
return done == other.done;
}
SomeTestType& operator=(const SomeTestType& other) {
done = other.done;
return *this;
}
};
CAnimationConfigTree animationTree;
class CMyAnimationManager : public CAnimationManager {
public:
void tick() {
for (const auto& PAV : m_vActiveAnimatedVariables) {
if (!PAV || !PAV->ok() || !PAV->isBeingAnimated())
continue;
const auto SPENT = PAV->getPercent();
const auto PBEZIER = getBezier(PAV->getBezierName());
if (SPENT >= 1.f || !PAV->enabled()) {
PAV->warp(true, false);
continue;
}
const auto POINTY = PBEZIER->getYForPoint(SPENT);
switch (PAV->m_Type) {
case eAVTypes::INT: {
auto avInt = dc<CAnimatedVariable<int>*>(PAV.get());
if (!avInt)
std::cout << "Dynamic cast upcast failed\n";
const auto DELTA = avInt->goal() - avInt->begun();
avInt->value() = avInt->begun() + (DELTA * POINTY);
} break;
case eAVTypes::TEST: {
auto avCustom = dc<CAnimatedVariable<SomeTestType>*>(PAV.get());
if (!avCustom)
std::cout << "Dynamic cast upcast failed\n";
if (SPENT >= 1.f)
avCustom->value().done = true;
} break;
default: {
std::cout << "What are we even doing?\n";
} break;
}
PAV->onUpdate();
}
tickDone();
}
template <typename VarType>
void createAnimation(const VarType& v, PANIMVAR<VarType>& av, const std::string& animationConfigName) {
constexpr const eAVTypes EAVTYPE = std::is_same_v<VarType, int> ? eAVTypes::INT : eAVTypes::TEST;
av = makeUnique<CGenericAnimatedVariable<VarType, EmtpyContext>>();
av->create2(EAVTYPE, sc<CAnimationManager*>(this), av, v);
av->setConfig(animationTree.getConfig(animationConfigName));
}
virtual void scheduleTick() {
;
}
virtual void onTicked() {
;
}
};
UP<CMyAnimationManager> pAnimationManager;
class Subject {
public:
Subject(const int& a, const int& b) {
pAnimationManager->createAnimation(a, m_iA, "default");
pAnimationManager->createAnimation(b, m_iB, "internal");
pAnimationManager->createAnimation({}, m_iC, "default");
}
PANIMVAR<int> m_iA;
PANIMVAR<int> m_iB;
PANIMVAR<SomeTestType> m_iC;
};
static int config() {
pAnimationManager = makeUnique<CMyAnimationManager>();
int ret = 0;
animationTree.createNode("global");
animationTree.createNode("internal");
animationTree.createNode("foo", "internal");
animationTree.createNode("default", "global");
animationTree.createNode("bar", "default");
/*
internal
foo
global
default
bar
*/
auto barCfg = animationTree.getConfig("bar");
auto internalCfg = animationTree.getConfig("internal");
// internal is a root node and should point to itself
EXPECT_EQ(internalCfg->pParentAnimation.get(), internalCfg.get());
EXPECT_EQ(internalCfg->pValues.get(), internalCfg.get());
animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf");
EXPECT_EQ(barCfg->internalEnabled, -1);
{
const auto PVALUES = barCfg->pValues.lock();
EXPECT_EQ(PVALUES->internalEnabled, 1);
EXPECT_EQ(PVALUES->internalBezier, "default");
EXPECT_EQ(PVALUES->internalStyle, "asdf");
EXPECT_EQ(PVALUES->internalSpeed, 4.0);
}
EXPECT_EQ(barCfg->pParentAnimation.get(), animationTree.getConfig("default").get());
// Overwrite our own values
animationTree.setConfigForNode("bar", 1, 4.2, "test", "qwer");
{
const auto PVALUES = barCfg->pValues.lock();
EXPECT_EQ(PVALUES->internalEnabled, 1);
EXPECT_EQ(PVALUES->internalBezier, "test");
EXPECT_EQ(PVALUES->internalStyle, "qwer");
EXPECT_EQ(PVALUES->internalSpeed, 4.2f);
}
// Now overwrite the parent
animationTree.setConfigForNode("default", 0, 0.0, "zxcv", "foo");
{
// Expecting no change
const auto PVALUES = barCfg->pValues.lock();
EXPECT_EQ(PVALUES->internalEnabled, 1);
EXPECT_EQ(PVALUES->internalBezier, "test");
EXPECT_EQ(PVALUES->internalStyle, "qwer");
EXPECT_EQ(PVALUES->internalSpeed, 4.2f);
}
return ret;
}
TEST(Animation, animation) {
config();
animationTree.createNode("global");
animationTree.createNode("internal");
animationTree.createNode("default", "global");
animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf");
Subject s(0, 0);
EXPECT_EQ(s.m_iA->value(), 0);
EXPECT_EQ(s.m_iB->value(), 0);
// Test destruction of a CAnimatedVariable
{
Subject s2(10, 10);
// Adds them to active
*s2.m_iA = 1;
*s2.m_iB = 2;
// We deliberately do not tick here, to make sure the destructor removes active animated variables
}
EXPECT_EQ(pAnimationManager->shouldTickForNext(), false);
EXPECT_EQ(s.m_iC->value().done, false);
*s.m_iA = 10;
*s.m_iB = 100;
*s.m_iC = SomeTestType(true);
EXPECT_EQ(s.m_iC->value().done, false);
while (pAnimationManager->shouldTickForNext()) {
pAnimationManager->tick();
}
EXPECT_EQ(s.m_iA->value(), 10);
EXPECT_EQ(s.m_iB->value(), 100);
EXPECT_EQ(s.m_iC->value().done, true);
s.m_iA->setValue(0);
s.m_iB->setValue(0);
while (pAnimationManager->shouldTickForNext()) {
pAnimationManager->tick();
}
EXPECT_EQ(s.m_iA->value(), 10);
EXPECT_EQ(s.m_iB->value(), 100);
// Test config stuff
EXPECT_EQ(s.m_iA->getBezierName(), "default");
EXPECT_EQ(s.m_iA->getStyle(), "asdf");
EXPECT_EQ(s.m_iA->enabled(), true);
animationTree.getConfig("global")->internalEnabled = 0;
EXPECT_EQ(s.m_iA->enabled(), false);
*s.m_iA = 50;
pAnimationManager->tick(); // Expecting a warp
EXPECT_EQ(s.m_iA->value(), 50);
// Test missing pValues
animationTree.getConfig("global")->internalEnabled = 0;
animationTree.getConfig("default")->pValues.reset();
EXPECT_EQ(s.m_iA->enabled(), false);
EXPECT_EQ(s.m_iA->getBezierName(), "default");
EXPECT_EQ(s.m_iA->getStyle(), "");
EXPECT_EQ(s.m_iA->getPercent(), 1.f);
// Reset
animationTree.setConfigForNode("default", 1, 1, "default");
//
// Test callbacks
//
int beginCallbackRan = 0;
int updateCallbackRan = 0;
int endCallbackRan = 0;
s.m_iA->setCallbackOnBegin([&beginCallbackRan](WP<CBaseAnimatedVariable> pav) { beginCallbackRan++; });
s.m_iA->setUpdateCallback([&updateCallbackRan](WP<CBaseAnimatedVariable> pav) { updateCallbackRan++; });
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> pav) { endCallbackRan++; }, false);
s.m_iA->setValueAndWarp(42);
EXPECT_EQ(beginCallbackRan, 0);
EXPECT_EQ(updateCallbackRan, 1);
EXPECT_EQ(endCallbackRan, 2); // first called when setting the callback, then when warping.
*s.m_iA = 1337;
while (pAnimationManager->shouldTickForNext()) {
pAnimationManager->tick();
}
EXPECT_EQ(beginCallbackRan, 1);
EXPECT_EQ(updateCallbackRan > 2, true);
EXPECT_EQ(endCallbackRan, 3);
std::vector<PANIMVAR<int>> vars;
for (int i = 0; i < 10; i++) {
vars.resize(vars.size() + 1);
pAnimationManager->createAnimation(1, vars.back(), "default");
*vars.back() = 1337;
}
// test adding / removing vars during a tick
s.m_iA->resetAllCallbacks();
s.m_iA->setUpdateCallback([&vars](WP<CBaseAnimatedVariable> v) {
if (v.get() != vars.back().get())
vars.back()->warp();
});
s.m_iA->setCallbackOnEnd([&s, &vars](auto) {
vars.resize(vars.size() + 1);
pAnimationManager->createAnimation(1, vars.back(), "default");
*vars.back() = 1337;
});
*s.m_iA = 1000000;
while (pAnimationManager->shouldTickForNext()) {
pAnimationManager->tick();
}
EXPECT_EQ(s.m_iA->value(), 1000000);
// all vars should be set to 1337
EXPECT_EQ(std::find_if(vars.begin(), vars.end(), [](const auto& v) { return v->value() != 1337; }) == vars.end(), true);
// test one-time callbacks
s.m_iA->resetAllCallbacks();
s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, true);
EXPECT_EQ(endCallbackRan, 4);
s.m_iA->setValueAndWarp(10);
EXPECT_EQ(endCallbackRan, 4);
EXPECT_EQ(s.m_iA->value(), 10);
// test warp
*s.m_iA = 3;
s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, false);
s.m_iA->warp(false);
EXPECT_EQ(endCallbackRan, 4);
*s.m_iA = 4;
s.m_iA->warp(true);
EXPECT_EQ(endCallbackRan, 5);
// test getCurveValue
*s.m_iA = 0;
EXPECT_EQ(s.m_iA->getCurveValue(), 0.f);
s.m_iA->warp();
EXPECT_EQ(s.m_iA->getCurveValue(), 1.f);
EXPECT_EQ(endCallbackRan, 6);
// test end callback readding the var
*s.m_iA = 5;
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) {
endCallbackRan++;
const auto PAV = dc<CAnimatedVariable<int>*>(v.get());
*PAV = 10;
PAV->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) { endCallbackRan++; });
});
while (pAnimationManager->shouldTickForNext()) {
pAnimationManager->tick();
}
EXPECT_EQ(endCallbackRan, 8);
EXPECT_EQ(s.m_iA->value(), 10);
// Test duplicate active anim vars are not allowed
{
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
PANIMVAR<int> a;
pAnimationManager->createAnimation(1, a, "default");
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
*a = 10;
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
*a = 20;
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
a->warp();
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
EXPECT_EQ(a->value(), 20);
}
// Test no crash when animation manager gets destroyed
{
PANIMVAR<int> a;
pAnimationManager->createAnimation(1, a, "default");
*a = 10;
pAnimationManager.reset();
EXPECT_EQ(a->isAnimationManagerDead(), true);
a->setValueAndWarp(11);
EXPECT_EQ(a->value(), 11);
*a = 12;
a->warp();
EXPECT_EQ(a->value(), 12);
*a = 13;
} // a gets destroyed
EXPECT_EQ(pAnimationManager.get(), nullptr);
}

View file

@ -0,0 +1,79 @@
#include <cmath>
#include <hyprutils/animation/BezierCurve.hpp>
#include <gtest/gtest.h>
using Hyprutils::Animation::CBezierCurve;
using Hyprutils::Math::Vector2D;
static void test_nonmonotonic4_clamps_out_of_range() {
// Non-monotonic curve in X
// This used to drive the step-halving search to OOB. It should now clamp
CBezierCurve curve;
std::array<Vector2D, 4> pts = {
Vector2D{0.5f, 1.0f}, // P0
Vector2D{1.0f, 1.0f}, // P1
Vector2D{0.0f, 0.0f}, // P2
Vector2D{0.5f, 0.0f} // P3
};
curve.setup4(pts);
// x > last baked x
EXPECT_EQ(std::isfinite(curve.getYForPoint(0.6f)), true);
// Far beyond range
EXPECT_EQ(std::isfinite(curve.getYForPoint(std::numeric_limits<float>::max())), true);
EXPECT_EQ(std::isfinite(curve.getYForPoint(-std::numeric_limits<float>::max())), true);
}
static void test_adjacent_baked_x_equal() {
// Curve with flat tail (X=1, Y=1)
CBezierCurve curve;
std::array<Vector2D, 4> pts = {
Vector2D{0.0f, 0.0f}, // P0
Vector2D{0.2f, 0.2f}, // P1
Vector2D{1.0f, 1.0f}, // P2
Vector2D{1.0f, 1.0f} // P3
};
curve.setup4(pts);
// Exactly at last baked X
const float y_at_end = curve.getYForPoint(1.0f);
// Slightly beyond last baked X
const float y_past_end = curve.getYForPoint(1.0001f);
EXPECT_EQ(y_at_end, 1.0f);
EXPECT_EQ(y_past_end, y_at_end);
}
static void test_all_baked_x_equal() {
// Extreme case: X is constant along the whole curve
CBezierCurve curve;
std::array<Vector2D, 4> pts = {
Vector2D{0.0f, 0.0f}, // P0
Vector2D{0.0f, 0.3f}, // P1
Vector2D{0.0f, 0.7f}, // P2
Vector2D{0.0f, 1.0f} // P3
};
curve.setup4(pts);
// Below any baked X
const float y_lo = curve.getYForPoint(-100.0f);
const float y_0 = curve.getYForPoint(0.0f);
// Above any baked X
const float y_hi = curve.getYForPoint(100.0f);
EXPECT_EQ(std::isfinite(y_lo), true);
EXPECT_EQ(std::isfinite(y_0), true);
EXPECT_EQ(std::isfinite(y_hi), true);
// For this curve Y should stay within [0,1]
EXPECT_EQ((y_lo >= 0.0f && y_lo <= 1.0f), true);
EXPECT_EQ((y_0 >= 0.0f && y_0 <= 1.0f), true);
EXPECT_EQ((y_hi >= 0.0f && y_hi <= 1.0f), true);
}
TEST(Animation, beziercurve) {
test_nonmonotonic4_clamps_out_of_range();
test_adjacent_baked_x_equal();
test_all_baked_x_equal();
}

View file

@ -0,0 +1,105 @@
#include <cli/ArgumentParser.hpp>
#include <gtest/gtest.h>
#include <print>
using namespace Hyprutils::CLI;
using namespace Hyprutils;
constexpr const char* DESC_TEST = R"#(┏ My description
--hello -h | Says hello
--hello2 | Says hello 2
--value -v [float] | Sets a valueeeeeee
--longlonglonglongintopt -l [int] | Long long
maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaan maaan man maaan man maaan
man maaan man
)#";
TEST(CLI, ArgumentParser) {
std::vector<const char*> argv = {"app", "--hello", "--value", "0.2"};
CArgumentParser parser(argv);
EXPECT_TRUE(parser.registerBoolOption("hello", "h", "Says hello"));
EXPECT_TRUE(parser.registerBoolOption("hello2", "", "Says hello 2"));
EXPECT_TRUE(parser.registerFloatOption("value", "v", "Sets a valueeeeeee"));
EXPECT_TRUE(parser.registerIntOption("longlonglonglongintopt", "l", "Long long maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaan maaan man maaan man maaan man maaan man"));
auto result = parser.parse();
EXPECT_TRUE(result.has_value());
std::println("{}", parser.getDescription("My description"));
if (!result.has_value())
std::println("Error: {}", result.error());
EXPECT_EQ(parser.getBool("hello").value_or(false), true);
EXPECT_EQ(parser.getBool("hello2").value_or(false), false);
EXPECT_EQ(parser.getFloat("value").value_or(0.F), 0.2F);
EXPECT_EQ(parser.getDescription("My description"), DESC_TEST);
CArgumentParser parser2(argv);
EXPECT_TRUE(parser2.registerBoolOption("hello2", "e", "Says hello 2"));
EXPECT_TRUE(parser2.registerFloatOption("value", "v", "Sets a valueeeeeee"));
EXPECT_TRUE(parser2.registerIntOption("longlonglonglongintopt", "l", "Long long maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaan maaan man maaan man maaan man maaan man"));
EXPECT_TRUE(parser2.registerFloatOption("value2", "", "Sets a valueeeeeee 2"));
EXPECT_TRUE(!parser2.registerFloatOption("", "a", "Sets a valueeeeeee 2"));
auto result2 = parser2.parse();
EXPECT_TRUE(!result2.has_value());
std::vector<const char*> argv3 = {"app", "--hello", "--value"};
CArgumentParser parser3(argv3);
EXPECT_TRUE(parser3.registerBoolOption("hello2", "e", "Says hello 2"));
EXPECT_TRUE(parser3.registerFloatOption("value", "v", "Sets a valueeeeeee"));
EXPECT_TRUE(parser3.registerIntOption("longlonglonglongintopt", "l", "Long long maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaan maaan man maaan man maaan man maaan man"));
auto result3 = parser3.parse();
EXPECT_TRUE(!result3.has_value());
std::vector<const char*> argv4 = {"app", "--value", "hi", "-w", "2"};
CArgumentParser parser4(argv4);
EXPECT_TRUE(parser4.registerStringOption("value", "v", "Sets a valueeeeeee"));
EXPECT_TRUE(parser4.registerIntOption("value2", "w", "Sets a valueeeeeee 2"));
auto result4 = parser4.parse();
EXPECT_TRUE(result4.has_value());
EXPECT_EQ(parser4.getString("value").value_or(""), "hi");
EXPECT_EQ(parser4.getInt("value2").value_or(0), 2);
std::vector<const char*> argv5 = {
"app",
"e",
};
CArgumentParser parser5(argv5);
EXPECT_TRUE(parser5.registerStringOption("value", "v", "Sets a valueeeeeee"));
EXPECT_TRUE(parser5.registerStringOption("value2", "w", "Sets a valueeeeeee 2"));
auto result5 = parser5.parse();
EXPECT_TRUE(!result5.has_value());
CArgumentParser parser6(argv5);
EXPECT_TRUE(parser6.registerStringOption("aa", "v", "Sets a valueeeeeee"));
EXPECT_TRUE(!parser6.registerStringOption("aa", "w", "Sets a valueeeeeee 2"));
EXPECT_TRUE(parser6.registerStringOption("bb", "b", "Sets a valueeeeeee"));
EXPECT_TRUE(!parser6.registerStringOption("cc", "b", "Sets a valueeeeeee 2"));
}

104
tests/cli/Logger.cpp Normal file
View file

@ -0,0 +1,104 @@
#include <cli/Logger.hpp>
#include <hyprutils/os/File.hpp>
#include <gtest/gtest.h>
#include <filesystem>
using namespace Hyprutils::CLI;
using namespace Hyprutils;
TEST(CLI, Logger) {
CLogger logger;
logger.setEnableRolling(true);
logger.log(Hyprutils::CLI::LOG_DEBUG, "Hello!");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!");
logger.log(Hyprutils::CLI::LOG_TRACE, "Hello!");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!");
logger.setLogLevel(LOG_TRACE);
logger.log(Hyprutils::CLI::LOG_TRACE, "Hello, {}!", "Trace");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!");
CLoggerConnection connection(logger);
connection.setName("conn");
connection.log(Hyprutils::CLI::LOG_TRACE, "Hello from connection!");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!\nTRACE from conn ]: Hello from connection!");
connection.setLogLevel(Hyprutils::CLI::LOG_WARN);
connection.log(Hyprutils::CLI::LOG_DEBUG, "Hello from connection!");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!\nTRACE from conn ]: Hello from connection!");
logger.setEnableRolling(false);
connection.log(Hyprutils::CLI::LOG_ERR, "Err!");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!\nTRACE from conn ]: Hello from connection!");
logger.setEnableStdout(false);
logger.log(Hyprutils::CLI::LOG_ERR, "Error");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!\nTRACE from conn ]: Hello from connection!");
auto res = logger.setOutputFile("./loggerFile.log");
EXPECT_TRUE(res);
logger.log(LOG_DEBUG, "Hi file!");
res = logger.setOutputFile(""); // clear
EXPECT_TRUE(res);
auto fileRead = File::readFileAsString("./loggerFile.log");
EXPECT_TRUE(fileRead);
EXPECT_EQ(fileRead.value_or(""), "DEBUG ]: Hi file!\n");
std::error_code ec;
std::filesystem::remove("./loggerFile.log", ec);
// TODO: maybe find a way to test the times and color?
logger.setEnableStdout(true);
logger.setTime(true);
logger.log(Hyprutils::CLI::LOG_WARN, "Timed warning!");
logger.setEnableColor(false);
logger.log(Hyprutils::CLI::LOG_CRIT, "rip");
logger.setEnableRolling(true);
// spam some logs to check rolling
for (size_t i = 0; i < 200; ++i) {
logger.log(LOG_ERR, "Oh noes!!!");
}
EXPECT_TRUE(logger.rollingLog().size() < 4096);
EXPECT_TRUE(logger.rollingLog().starts_with("ERR")); // test the breaking is done correctly
// test scoping
CLogger* pLogger = new CLogger();
CLoggerConnection* pConnection = new CLoggerConnection(*pLogger);
pLogger->setEnableStdout(false);
pConnection->log(LOG_DEBUG, "This shouldn't log anything.");
EXPECT_TRUE(pLogger->rollingLog().empty());
delete pLogger;
pConnection->log(LOG_DEBUG, "This shouldn't do anything, or crash.");
}

80
tests/i18n/Engine.cpp Normal file
View file

@ -0,0 +1,80 @@
#include <i18n/I18nEngine.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::I18n;
enum eTxtKeys : uint64_t {
TXT_KEY_HELLO,
TXT_KEY_I_HAVE_APPLES,
TXT_KEY_FALLBACK,
};
TEST(I18n, Engine) {
CI18nEngine engine;
engine.setFallbackLocale("en_US");
engine.registerEntry("en_US", TXT_KEY_HELLO, "Hello World!");
engine.registerEntry("en_US", TXT_KEY_I_HAVE_APPLES, [](const translationVarMap& m) {
if (std::stoi(m.at("count")) == 1)
return "I have {count} apple.";
else
return "I have {count} apples.";
});
engine.registerEntry("en_US", TXT_KEY_FALLBACK, "Fallback string!");
engine.registerEntry("pl_PL", TXT_KEY_HELLO, "Witaj świecie!");
engine.registerEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, [](const translationVarMap& m) {
const auto COUNT = std::stoi(m.at("count"));
if (COUNT == 1)
return "Mam {count} jabłko.";
else if (COUNT < 5)
return "Mam {count} jabłka.";
else
return "Mam {count} jabłek.";
});
engine.registerEntry("es_XX", TXT_KEY_FALLBACK, "I don't speak spanish");
engine.registerEntry("es_ES", TXT_KEY_FALLBACK, "I don't speak spanish here either");
engine.registerEntry("ts_TST", TXT_KEY_FALLBACK, "Hello {var1} world {var2}");
engine.registerEntry("am", TXT_KEY_FALLBACK, "Amongus!");
EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_HELLO, {}), "Hello World!");
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_HELLO, {}), "Witaj świecie!");
EXPECT_EQ(engine.localizeEntry("de_DE", TXT_KEY_HELLO, {}), "Hello World!");
EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_I_HAVE_APPLES, {{"count", "1"}}), "I have 1 apple.");
EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "I have 2 apples.");
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "1"}}), "Mam 1 jabłko.");
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "Mam 2 jabłka.");
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek.");
EXPECT_EQ(engine.localizeEntry("pl", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek.");
EXPECT_EQ(engine.localizeEntry("pl_XX", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek.");
EXPECT_EQ(engine.localizeEntry("en_XX", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "I have 2 apples.");
EXPECT_EQ(engine.localizeEntry("es_YY", TXT_KEY_FALLBACK, {}), "I don't speak spanish here either");
EXPECT_EQ(engine.localizeEntry("es_XX", TXT_KEY_FALLBACK, {}), "I don't speak spanish");
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_FALLBACK, {}), "Fallback string!");
EXPECT_EQ(engine.localizeEntry("am_AM", TXT_KEY_FALLBACK, {}), "Amongus!");
// test weird translations
engine.registerEntry("ts", TXT_KEY_HELLO, "count}");
EXPECT_EQ(engine.localizeEntry("ts", TXT_KEY_HELLO, {{"count", "1"}}), "count}");
engine.registerEntry("ts", TXT_KEY_HELLO, "{count");
EXPECT_EQ(engine.localizeEntry("ts", TXT_KEY_HELLO, {{"count", "1"}}), "{count");
EXPECT_EQ(engine.localizeEntry("ts", 42069 /* invalid key */, {{"count", "1"}}), "");
EXPECT_EQ(engine.localizeEntry("ts_TST", TXT_KEY_FALLBACK, {{"var1", "hi"}, {"var2", "!"}}), "Hello hi world !");
// Order shouldn't matter
EXPECT_EQ(engine.localizeEntry("ts_TST", TXT_KEY_FALLBACK, {{"var2", "!"}, {"var1", "hi"}}), "Hello hi world !");
}

11
tests/i18n/Locale.cpp Normal file
View file

@ -0,0 +1,11 @@
#include <gtest/gtest.h>
#include <i18n/I18nEngine.hpp>
using namespace Hyprutils::I18n;
TEST(I18n, Locale) {
EXPECT_EQ(extractLocale("pl_PL.UTF-8"), "pl_PL");
EXPECT_EQ(extractLocale("POSIX"), "en_US");
EXPECT_EQ(extractLocale("*"), "en_US");
EXPECT_EQ(extractLocale("LC_CTYPE=pl_PL.UTF-8"), "pl_PL");
}

73
tests/math/Box.cpp Normal file
View file

@ -0,0 +1,73 @@
#include <hyprutils/math/Box.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::Math;
TEST(Math, box) {
// Test default constructor and accessors
{
CBox box1;
EXPECT_EQ(box1.x, 0);
EXPECT_EQ(box1.y, 0);
EXPECT_EQ(box1.width, 0);
EXPECT_EQ(box1.height, 0);
// Test parameterized constructor and accessors
CBox box2(10, 20, 30, 40);
EXPECT_EQ(box2.x, 10);
EXPECT_EQ(box2.y, 20);
EXPECT_EQ(box2.width, 30);
EXPECT_EQ(box2.height, 40);
// Test setters and getters
box2.translate(Vector2D(5, -5));
EXPECT_EQ(box2.pos(), Vector2D(15, 15));
}
//Test Scaling and Transformation
{
CBox box(10, 10, 20, 30);
// Test scaling
box.scale(2.0);
EXPECT_EQ(box.size(), Vector2D(40, 60));
EXPECT_EQ(box.pos(), Vector2D(20, 20));
// Test scaling from center
box.scaleFromCenter(0.5);
EXPECT_EQ(box.size(), Vector2D(20, 30));
EXPECT_EQ(box.pos(), Vector2D(30, 35));
// Test transformation
box.transform(HYPRUTILS_TRANSFORM_90, 100, 200);
EXPECT_EQ(box.pos(), Vector2D(135, 30));
EXPECT_EQ(box.size(), Vector2D(30, 20));
// Test Intersection and Extents
}
{
CBox box1(0, 0, 100, 100);
CBox box2(50, 50, 100, 100);
CBox intersection = box1.intersection(box2);
EXPECT_EQ(intersection.pos(), Vector2D(50, 50));
EXPECT_EQ(intersection.size(), Vector2D(50, 50));
SBoxExtents extents = box1.extentsFrom(box2);
EXPECT_EQ(extents.topLeft, Vector2D(50, 50));
EXPECT_EQ(extents.bottomRight, Vector2D(-50, -50));
}
// Test Boundary Conditions and Special Cases
{
CBox box(0, 0, 50, 50);
EXPECT_EQ(box.empty(), false);
EXPECT_EQ(box.containsPoint(Vector2D(25, 25)), true);
EXPECT_EQ(box.containsPoint(Vector2D(60, 60)), false);
EXPECT_EQ(box.overlaps(CBox(25, 25, 50, 50)), true);
EXPECT_EQ(box.inside(CBox(0, 0, 100, 100)), false);
}
}

23
tests/math/Mat3x3.cpp Normal file
View file

@ -0,0 +1,23 @@
#include <hyprutils/math/Mat3x3.hpp>
#include <hyprutils/math/Box.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::Math;
TEST(Math, mat3x3) {
Mat3x3 jeremy = Mat3x3::outputProjection({1920, 1080}, HYPRUTILS_TRANSFORM_FLIPPED_90);
Mat3x3 matrixBox = jeremy.projectBox(CBox{10, 10, 200, 200}, HYPRUTILS_TRANSFORM_NORMAL).translate({100, 100}).scale({1.25F, 1.5F}).transpose();
Mat3x3 expected = std::array<float, 9>{0, 0.46296296, 0, 0.3125, 0, 0, 19.84375, 36.055557, 1};
// we need to do this to avoid precision errors on 32-bit archs
EXPECT_EQ(std::abs(expected.getMatrix().at(0) - matrixBox.getMatrix().at(0)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(1) - matrixBox.getMatrix().at(1)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(2) - matrixBox.getMatrix().at(2)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(3) - matrixBox.getMatrix().at(3)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(4) - matrixBox.getMatrix().at(4)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(5) - matrixBox.getMatrix().at(5)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(6) - matrixBox.getMatrix().at(6)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(7) - matrixBox.getMatrix().at(7)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(8) - matrixBox.getMatrix().at(8)) < 0.1, true);
}

18
tests/math/Region.cpp Normal file
View file

@ -0,0 +1,18 @@
#include <hyprutils/math/Region.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::Math;
TEST(Math, region) {
CRegion rg(CBox{{20, 20}, {40, 40}});
auto extents = rg.getExtents();
EXPECT_EQ(extents.pos(), Vector2D(20, 20));
EXPECT_EQ(extents.size(), Vector2D(40, 40));
rg.scale(2);
extents = rg.getExtents();
EXPECT_EQ(extents.pos(), Vector2D(40, 40));
EXPECT_EQ(extents.size(), Vector2D(80, 80));
}

19
tests/math/Vector2D.cpp Normal file
View file

@ -0,0 +1,19 @@
#include <hyprutils/math/Vector2D.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::Math;
TEST(Math, vector2d) {
Vector2D original(30, 40);
Vector2D monitorSize(100, 200);
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_NORMAL, monitorSize), Vector2D(30, 40));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_90, monitorSize), Vector2D(40, 200 - 30));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_180, monitorSize), Vector2D(100 - 30, 200 - 40));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_270, monitorSize), Vector2D(100 - 40, 30));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED, monitorSize), Vector2D(100 - 30, 40));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_90, monitorSize), Vector2D(40, 30));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_180, monitorSize), Vector2D(30, 200 - 40));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_270, monitorSize), Vector2D(100 - 40, 200 - 30));
}

View file

@ -1,29 +0,0 @@
#include <hyprutils/memory/WeakPtr.hpp>
#include "shared.hpp"
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
int main(int argc, char** argv, char** envp) {
SP<int> intPtr = makeShared<int>(10);
int ret = 0;
EXPECT(*intPtr, 10);
EXPECT(intPtr.strongRef(), 1);
WP<int> weak = intPtr;
EXPECT(*intPtr, 10);
EXPECT(intPtr.strongRef(), 1);
EXPECT(*weak.get(), 10);
EXPECT(weak.expired(), false);
intPtr = {};
EXPECT(weak.expired(), true);
return ret;
}

294
tests/memory/Memory.cpp Normal file
View file

@ -0,0 +1,294 @@
#include <hyprutils/memory/Atomic.hpp>
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <gtest/gtest.h>
#include <chrono>
#include <thread>
#include <vector>
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
#define UP CUniquePointer
#define ASP CAtomicSharedPointer
#define AWP CAtomicWeakPointer
#define NTHREADS 8
#define ITERATIONS 10000
static void testAtomicImpl() {
{
// Using makeShared here could lead to invalid refcounts.
ASP<int> shared = makeAtomicShared<int>(0);
std::vector<std::thread> threads;
threads.reserve(NTHREADS);
for (size_t i = 0; i < NTHREADS; i++) {
threads.emplace_back([shared]() {
for (size_t j = 0; j < ITERATIONS; j++) {
ASP<int> strongRef = shared;
(*shared)++;
strongRef.reset();
}
});
}
for (auto& thread : threads) {
thread.join();
}
// Actual count is not incremented in a thread-safe manner here, so we can't check it.
// We just want to check that the concurent refcounting doesn't cause any memory corruption.
shared.reset();
EXPECT_EQ(shared, false);
}
{
ASP<int> shared = makeAtomicShared<int>(0);
AWP<int> weak = shared;
std::vector<std::thread> threads;
threads.reserve(NTHREADS);
for (size_t i = 0; i < NTHREADS; i++) {
threads.emplace_back([weak]() {
for (size_t j = 0; j < ITERATIONS; j++) {
if (auto s = weak.lock(); s) {
(*s)++;
}
}
});
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
shared.reset();
for (auto& thread : threads) {
thread.join();
}
EXPECT_EQ(shared.strongRef(), 0);
EXPECT_EQ(weak.valid(), false);
auto shared2 = weak.lock();
EXPECT_EQ(shared, false);
EXPECT_EQ(shared2.get(), nullptr);
EXPECT_EQ(shared.strongRef(), 0);
EXPECT_EQ(weak.valid(), false);
EXPECT_EQ(weak.expired(), true);
}
{ // This tests recursive deletion. When foo will be deleted, bar will be deleted within the foo dtor.
class CFoo {
public:
AWP<CFoo> bar;
};
ASP<CFoo> foo = makeAtomicShared<CFoo>();
foo->bar = foo;
}
{ // This tests destroying the data when storing the base class of a type
class ITest {
public:
size_t num = 0;
ITest() : num(1234) {};
};
class CA : public ITest {
public:
size_t num2 = 0;
CA() : ITest(), num2(4321) {};
};
class CB : public ITest {
public:
int num2 = 0;
CB() : ITest(), num2(-1) {};
};
ASP<ITest> genericAtomic = nullptr;
SP<ITest> genericNormal = nullptr;
{
auto derivedAtomic = makeAtomicShared<CA>();
auto derivedNormal = makeShared<CA>();
genericAtomic = derivedAtomic;
genericNormal = derivedNormal;
}
EXPECT_EQ(!!genericAtomic, true);
EXPECT_EQ(!!genericNormal, true);
}
}
class InterfaceA {
public:
virtual ~InterfaceA() = default;
int m_ifaceAInt = 69;
int m_ifaceAShit = 1;
};
class InterfaceB {
public:
virtual ~InterfaceB() = default;
int m_ifaceBInt = 2;
int m_ifaceBShit = 3;
};
class CChild : public InterfaceA, public InterfaceB {
public:
virtual ~CChild() = default;
int m_childInt = 4;
};
class CChildA : public InterfaceA {
public:
int m_childAInt = 4;
};
static void testHierarchy() {
// Same test for atomic and non-atomic
{
SP<CChildA> childA = makeShared<CChildA>();
auto ifaceA = SP<InterfaceA>(childA);
EXPECT_TRUE(ifaceA);
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
auto ifaceB = dynamicPointerCast<InterfaceA>(SP<CChildA>{});
EXPECT_TRUE(!ifaceB);
}
{
SP<CChild> child = makeShared<CChild>();
SP<InterfaceA> ifaceA = dynamicPointerCast<InterfaceA>(child);
SP<InterfaceB> ifaceB = dynamicPointerCast<InterfaceB>(child);
EXPECT_TRUE(ifaceA);
EXPECT_TRUE(ifaceB);
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
EXPECT_EQ(ifaceB->m_ifaceBInt, 2);
WP<InterfaceA> ifaceAWeak = ifaceA;
child.reset();
EXPECT_TRUE(ifaceAWeak);
EXPECT_TRUE(ifaceA);
EXPECT_EQ(ifaceAWeak->m_ifaceAInt, 69);
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
ifaceA.reset();
EXPECT_TRUE(ifaceAWeak);
EXPECT_EQ(ifaceAWeak->m_ifaceAInt, 69);
EXPECT_TRUE(ifaceB);
EXPECT_EQ(ifaceB->m_ifaceBInt, 2);
ifaceB.reset();
EXPECT_TRUE(!ifaceAWeak);
}
//
{
ASP<CChildA> childA = makeAtomicShared<CChildA>();
auto ifaceA = ASP<InterfaceA>(childA);
EXPECT_TRUE(ifaceA);
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
auto ifaceB = dynamicPointerCast<InterfaceA>(ASP<CChildA>{});
EXPECT_TRUE(!ifaceB);
}
{
ASP<CChild> child = makeAtomicShared<CChild>();
ASP<InterfaceA> ifaceA = dynamicPointerCast<InterfaceA>(child);
ASP<InterfaceB> ifaceB = dynamicPointerCast<InterfaceB>(child);
EXPECT_TRUE(ifaceA);
EXPECT_TRUE(ifaceB);
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
EXPECT_EQ(ifaceB->m_ifaceBInt, 2);
AWP<InterfaceA> ifaceAWeak = ifaceA;
AWP<InterfaceB> ifaceBWeak = dynamicPointerCast<InterfaceB>(ifaceA);
child.reset();
EXPECT_TRUE(ifaceAWeak);
EXPECT_TRUE(ifaceBWeak);
EXPECT_TRUE(ifaceA);
EXPECT_EQ(ifaceAWeak->m_ifaceAInt, 69);
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
EXPECT_EQ(ifaceBWeak->m_ifaceBInt, 2);
ifaceA.reset();
EXPECT_TRUE(ifaceAWeak);
EXPECT_EQ(ifaceAWeak->m_ifaceAInt, 69);
EXPECT_TRUE(ifaceB);
EXPECT_EQ(ifaceB->m_ifaceBInt, 2);
EXPECT_EQ(ifaceBWeak->m_ifaceBInt, 2);
ifaceB.reset();
EXPECT_TRUE(!ifaceAWeak);
EXPECT_TRUE(!ifaceBWeak);
}
// test for leaks
for (size_t i = 0; i < 10000; ++i) {
auto child = makeAtomicShared<CChild>();
auto child2 = makeShared<CChild>();
}
}
TEST(Memory, memory) {
SP<int> intPtr = makeShared<int>(10);
SP<int> intPtr2 = makeShared<int>(-1337);
UP<int> intUnique = makeUnique<int>(420);
EXPECT_EQ(*intPtr, 10);
EXPECT_EQ(intPtr.strongRef(), 1);
EXPECT_EQ(*intUnique, 420);
WP<int> weak = intPtr;
WP<int> weakUnique = intUnique;
EXPECT_EQ(*intPtr, 10);
EXPECT_EQ(intPtr.strongRef(), 1);
EXPECT_EQ(*weak, 10);
EXPECT_EQ(weak.expired(), false);
EXPECT_EQ(!!weak.lock(), true);
EXPECT_EQ(*weakUnique, 420);
EXPECT_EQ(weakUnique.expired(), false);
EXPECT_EQ(intUnique.impl_->wref(), 1);
SP<int> sharedFromUnique = weakUnique.lock();
EXPECT_EQ(sharedFromUnique, nullptr);
std::vector<SP<int>> sps;
sps.push_back(intPtr);
sps.emplace_back(intPtr);
sps.push_back(intPtr2);
sps.emplace_back(intPtr2);
std::erase_if(sps, [intPtr](const auto& e) { return e == intPtr; });
intPtr.reset();
intUnique.reset();
EXPECT_EQ(weak.impl_->ref(), 0);
EXPECT_EQ(weakUnique.impl_->ref(), 0);
EXPECT_EQ(weakUnique.impl_->wref(), 1);
EXPECT_EQ(intPtr2.strongRef(), 3);
EXPECT_EQ(weak.expired(), true);
EXPECT_EQ(weakUnique.expired(), true);
auto intPtr2AsUint = reinterpretPointerCast<unsigned int>(intPtr2);
EXPECT_EQ(intPtr2.strongRef(), 4);
EXPECT_EQ(intPtr2AsUint.strongRef(), 4);
EXPECT_EQ(*intPtr2AsUint > 0, true);
EXPECT_EQ(*intPtr2AsUint, (unsigned int)(int)-1337);
*intPtr2AsUint = 10;
EXPECT_EQ(*intPtr2AsUint, 10);
EXPECT_EQ(*intPtr2, 10);
testAtomicImpl();
testHierarchy();
}

45
tests/os/Fd.cpp Normal file
View file

@ -0,0 +1,45 @@
#include <hyprutils/os/FileDescriptor.hpp>
#include <gtest/gtest.h>
#include <sys/mman.h>
using namespace Hyprutils::OS;
TEST(OS, fd) {
std::string name = "/test_filedescriptors";
CFileDescriptor fd(shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600));
EXPECT_EQ(fd.isValid(), true);
EXPECT_EQ(fd.isReadable(), true);
int flags = fd.getFlags();
EXPECT_EQ(fd.getFlags(), FD_CLOEXEC);
flags &= ~FD_CLOEXEC;
fd.setFlags(flags);
EXPECT_EQ(fd.getFlags(), !FD_CLOEXEC);
CFileDescriptor fd2 = fd.duplicate();
EXPECT_EQ(fd.isValid(), true);
EXPECT_EQ(fd.isReadable(), true);
EXPECT_EQ(fd2.isValid(), true);
EXPECT_EQ(fd2.isReadable(), true);
CFileDescriptor fd3(fd2.take());
EXPECT_EQ(fd.isValid(), true);
EXPECT_EQ(fd.isReadable(), true);
EXPECT_EQ(fd2.isValid(), false);
EXPECT_EQ(fd2.isReadable(), false);
// .duplicate default flags is FD_CLOEXEC
EXPECT_EQ(fd3.getFlags(), FD_CLOEXEC);
fd.reset();
fd2.reset();
fd3.reset();
EXPECT_EQ(fd.isReadable(), false);
EXPECT_EQ(fd2.isReadable(), false);
EXPECT_EQ(fd3.isReadable(), false);
shm_unlink(name.c_str());
}

28
tests/os/Process.cpp Normal file
View file

@ -0,0 +1,28 @@
#include <hyprutils/os/Process.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::OS;
TEST(OS, process) {
CProcess process("sh", {"-c", "echo \"Hello $WORLD!\""});
process.addEnv("WORLD", "World");
EXPECT_EQ(process.runAsync(), true);
EXPECT_EQ(process.runSync(), true);
EXPECT_EQ(process.stdOut(), std::string{"Hello World!\n"});
EXPECT_EQ(process.stdErr(), std::string{""});
EXPECT_EQ(process.exitCode(), 0);
CProcess process2("sh", {"-c", "while true; do sleep 1; done;"});
EXPECT_EQ(process2.runAsync(), true);
EXPECT_EQ(getpgid(process2.pid()) >= 0, true);
kill(process2.pid(), SIGKILL);
CProcess process3("sh", {"-c", "cat /geryueruggbuergheruger/reugiheruygyuerghuryeghyer/eruihgyuerguyerghyuerghuyergerguyer/NON_EXISTENT"});
EXPECT_EQ(process3.runSync(), true);
EXPECT_EQ(process3.exitCode(), 1);
}

View file

@ -1,20 +0,0 @@
#pragma once
#include <iostream>
namespace Colors {
constexpr const char* RED = "\x1b[31m";
constexpr const char* GREEN = "\x1b[32m";
constexpr const char* YELLOW = "\x1b[33m";
constexpr const char* BLUE = "\x1b[34m";
constexpr const char* MAGENTA = "\x1b[35m";
constexpr const char* CYAN = "\x1b[36m";
constexpr const char* RESET = "\x1b[0m";
};
#define EXPECT(expr, val) \
if (const auto RESULT = expr; RESULT != (val)) { \
std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected " << #val << " but got " << RESULT << "\n"; \
ret = 1; \
} else { \
std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got " << val << "\n"; \
}

View file

@ -1,30 +0,0 @@
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include "shared.hpp"
using namespace Hyprutils::Signal;
using namespace Hyprutils::Memory;
int main(int argc, char** argv, char** envp) {
int ret = 0;
CSignal signal;
int data = 0;
auto listener = signal.registerListener([&] (std::any d) {
data = 1;
});
signal.emit();
EXPECT(data, 1);
data = 0;
listener.reset();
signal.emit();
EXPECT(data, 0);
return ret;
}

369
tests/signal/Signal.cpp Normal file
View file

@ -0,0 +1,369 @@
#include <gtest/gtest.h>
#include <any>
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/signal/Listener.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <memory>
using namespace Hyprutils::Signal;
using namespace Hyprutils::Memory;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
//
static void legacy() {
CSignal signal;
int data = 0;
auto listener = signal.registerListener([&]([[maybe_unused]] std::any d) { data = 1; });
signal.emit();
EXPECT_EQ(data, 1);
data = 0;
listener.reset();
signal.emit();
EXPECT_EQ(data, 0);
}
static void legacyListenerEmit() {
int data = 0;
CSignal signal;
auto listener = signal.registerListener([&](std::any d) { data = std::any_cast<int>(d); });
listener->emit(1); // not a typo
EXPECT_EQ(data, 1);
}
static void legacyListeners() {
int data = 0;
CSignalT<> signal0;
CSignalT<int> signal1;
auto listener0 = signal0.registerListener([&](std::any d) { data += 1; });
auto listener1 = signal1.registerListener([&](std::any d) { data += std::any_cast<int>(d); });
signal0.registerStaticListener([&](void* o, std::any d) { data += 10; }, nullptr);
signal1.registerStaticListener([&](void* o, std::any d) { data += std::any_cast<int>(d) * 10; }, nullptr);
signal0.emit();
signal1.emit(2);
EXPECT_EQ(data, 33);
}
#pragma GCC diagnostic pop
//
static void empty() {
int data = 0;
CSignalT<> signal;
auto listener = signal.listen([&] { data = 1; });
signal.emit();
EXPECT_EQ(data, 1);
data = 0;
listener.reset();
signal.emit();
EXPECT_EQ(data, 0);
}
static void typed() {
int data = 0;
CSignalT<int> signal;
auto listener = signal.listen([&](int newData) { data = newData; });
signal.emit(1);
EXPECT_EQ(data, 1);
}
static void ignoreParams() {
int data = 0;
CSignalT<int> signal;
auto listener = signal.listen([&] { data += 1; });
signal.listenStatic([&] { data += 1; });
signal.emit(2);
EXPECT_EQ(data, 2);
}
static void typedMany() {
int data1 = 0;
int data2 = 0;
int data3 = 0;
CSignalT<int, int, int> signal;
auto listener = signal.listen([&](int d1, int d2, int d3) {
data1 = d1;
data2 = d2;
data3 = d3;
});
signal.emit(1, 2, 3);
EXPECT_EQ(data1, 1);
EXPECT_EQ(data2, 2);
EXPECT_EQ(data3, 3);
}
static void ref() {
int count = 0;
int data = 0;
CSignalT<int&> signal;
auto l1 = signal.listen([&](int& v) { v += 1; });
auto l2 = signal.listen([&](int v) { count += v; });
signal.emit(data);
CSignalT<const int&> constSignal;
auto l3 = constSignal.listen([&](const int& v) { count += v; });
auto l4 = constSignal.listen([&](int v) { count += v; });
constSignal.emit(data);
EXPECT_EQ(data, 1);
EXPECT_EQ(count, 3);
}
static void refMany() {
int count = 0;
int data1 = 0;
int data2 = 10;
CSignalT<int&, const int&> signal;
auto l1 = signal.listen([&](int& v, const int&) { v += 1; });
auto l2 = signal.listen([&](int v1, int v2) { count += v1 + v2; });
signal.emit(data1, data2);
EXPECT_EQ(data1, 1);
EXPECT_EQ(count, 11);
}
static void autoRefTypes() {
class CCopyCounter {
public:
CCopyCounter(int& createCount, int& destroyCount) : createCount(createCount), destroyCount(destroyCount) {
createCount += 1;
}
CCopyCounter(CCopyCounter&& other) noexcept : CCopyCounter(other.createCount, other.destroyCount) {}
CCopyCounter(const CCopyCounter& other) noexcept : CCopyCounter(other.createCount, other.destroyCount) {}
~CCopyCounter() {
destroyCount += 1;
}
private:
int& createCount;
int& destroyCount;
};
auto createCount = 0;
auto destroyCount = 0;
CSignalT<CCopyCounter> signal;
auto listener = signal.listen([](const CCopyCounter& counter) {});
signal.emit(CCopyCounter(createCount, destroyCount));
EXPECT_EQ(createCount, 1);
EXPECT_EQ(destroyCount, 1);
}
static void forward() {
int count = 0;
CSignalT<int> sig;
CSignalT<int> connected1;
CSignalT<> connected2;
auto conn1 = sig.forward(connected1);
auto conn2 = sig.forward(connected2);
auto listener1 = connected1.listen([&](int v) { count += v; });
auto listener2 = connected2.listen([&] { count += 1; });
sig.emit(2);
EXPECT_EQ(count, 3);
}
static void listenerAdded() {
int count = 0;
CSignalT<> signal;
CHyprSignalListener secondListener;
auto listener = signal.listen([&] {
count += 1;
if (!secondListener)
secondListener = signal.listen([&] { count += 1; });
});
signal.emit();
EXPECT_EQ(count, 1); // second should NOT be invoked as it was registed during emit
signal.emit();
EXPECT_EQ(count, 3); // second should be invoked
}
static void lastListenerSwapped() {
int count = 0;
CSignalT<> signal;
CHyprSignalListener removedListener;
CHyprSignalListener addedListener;
auto firstListener = signal.listen([&] {
removedListener.reset(); // dropped and should NOT be invoked
if (!addedListener)
addedListener = signal.listen([&] { count += 2; });
});
removedListener = signal.listen([&] { count += 1; });
signal.emit();
EXPECT_EQ(count, 0); // neither the removed nor added listeners should fire
signal.emit();
EXPECT_EQ(count, 2); // only the new listener should fire
}
static void signalDestroyed() {
int count = 0;
auto signal = std::make_unique<CSignalT<>>();
// This ensures a destructor of a listener called before signal reset is safe.
auto preListener = signal->listen([&] { count += 1; });
auto listener = signal->listen([&] { signal.reset(); });
// This ensures a destructor of a listener called after signal reset is safe
// and gets called.
auto postListener = signal->listen([&] { count += 1; });
signal->emit();
EXPECT_EQ(count, 2); // all listeners should fire regardless of signal deletion
}
// purely an asan test
static void signalDestroyedBeforeListener() {
CHyprSignalListener listener1;
CHyprSignalListener listener2;
CSignalT<> signal;
listener1 = signal.listen([] {});
listener2 = signal.listen([] {});
}
static void signalDestroyedWithAddedListener() {
int count = 0;
auto signal = std::make_unique<CSignalT<>>();
CHyprSignalListener shouldNotRun;
auto listener = signal->listen([&] {
shouldNotRun = signal->listen([&] { count += 2; });
signal.reset();
});
signal->emit();
EXPECT_EQ(count, 0);
}
static void signalDestroyedWithRemovedAndAddedListener() {
int count = 0;
auto signal = std::make_unique<CSignalT<>>();
CHyprSignalListener removed;
CHyprSignalListener shouldNotRun;
auto listener = signal->listen([&] {
removed.reset();
shouldNotRun = signal->listen([&] { count += 2; });
signal.reset();
});
removed = signal->listen([&] { count += 1; });
signal->emit();
EXPECT_EQ(count, 0);
}
static void staticListener() {
int data = 0;
CSignalT<int> signal;
signal.listenStatic([&](int newData) { data = newData; });
signal.emit(1);
EXPECT_EQ(data, 1);
}
static void staticListenerDestroy() {
int count = 0;
auto signal = makeShared<CSignalT<>>();
signal->listenStatic([&] { count += 1; });
signal->listenStatic([&] {
// should not fire but SHOULD be freed
signal->listenStatic([&] { count += 3; });
signal.reset();
});
signal->listenStatic([&] { count += 1; });
signal->emit();
EXPECT_EQ(count, 2);
}
// purely an asan test
static void listenerDestroysSelf() {
CSignalT<> signal;
CHyprSignalListener listener;
listener = signal.listen([&] { listener.reset(); });
// the static signal case is taken care of above
signal.emit();
}
TEST(Signal, signal) {
legacy();
legacyListenerEmit();
legacyListeners();
empty();
typed();
ignoreParams();
typedMany();
ref();
refMany();
autoRefTypes();
forward();
listenerAdded();
lastListenerSwapped();
signalDestroyed();
signalDestroyedBeforeListener();
signalDestroyedWithAddedListener();
signalDestroyedWithRemovedAndAddedListener();
staticListener();
staticListenerDestroy();
signalDestroyed();
listenerDestroysSelf();
}

View file

@ -1,38 +0,0 @@
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/VarList.hpp>
#include "shared.hpp"
using namespace Hyprutils::String;
int main(int argc, char** argv, char** envp) {
int ret = 0;
EXPECT(trim(" a "), "a");
EXPECT(trim(" a a "), "a a");
EXPECT(trim("a"), "a");
EXPECT(trim(" "), "");
EXPECT(isNumber("99214123434"), true);
EXPECT(isNumber("-35252345234"), true);
EXPECT(isNumber("---3423--432"), false);
EXPECT(isNumber("s---3423--432"), false);
EXPECT(isNumber("---3423--432s"), false);
EXPECT(isNumber("1s"), false);
EXPECT(isNumber(""), false);
EXPECT(isNumber("--0"), false);
EXPECT(isNumber("abc"), false);
EXPECT(isNumber("0.0", true), true);
EXPECT(isNumber("0.2", true), true);
EXPECT(isNumber("0.", true), false);
EXPECT(isNumber(".0", true), false);
EXPECT(isNumber("", true), false);
EXPECT(isNumber("vvss", true), false);
EXPECT(isNumber("0.9999s", true), false);
EXPECT(isNumber("s0.9999", true), false);
CVarList list("hello world!", 0, 's', true);
EXPECT(list[0], "hello");
EXPECT(list[1], "world!");
return ret;
}

View file

@ -0,0 +1,15 @@
#include <hyprutils/string/ConstVarList.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::String;
TEST(String, constvarlist) {
CConstVarList listConst("hello world!", 0, 's', true);
EXPECT_EQ(listConst[0], "hello");
EXPECT_EQ(listConst[1], "world!");
CConstVarList listConst2("0 set", 2, ' ');
EXPECT_EQ(listConst2[0], "0");
EXPECT_EQ(listConst2[1], "set");
}

47
tests/string/String.cpp Normal file
View file

@ -0,0 +1,47 @@
#include <hyprutils/string/String.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::String;
TEST(String, string) {
EXPECT_EQ(trim(" a "), "a");
EXPECT_EQ(trim(" a a "), "a a");
EXPECT_EQ(trim("a"), "a");
EXPECT_EQ(trim(" "), "");
EXPECT_EQ(isNumber("99214123434"), true);
EXPECT_EQ(isNumber("-35252345234"), true);
EXPECT_EQ(isNumber("---3423--432"), false);
EXPECT_EQ(isNumber("s---3423--432"), false);
EXPECT_EQ(isNumber("---3423--432s"), false);
EXPECT_EQ(isNumber("1s"), false);
EXPECT_EQ(isNumber(""), false);
EXPECT_EQ(isNumber("-"), false);
EXPECT_EQ(isNumber("--0"), false);
EXPECT_EQ(isNumber("abc"), false);
EXPECT_EQ(isNumber("0.0", true), true);
EXPECT_EQ(isNumber("0.2", true), true);
EXPECT_EQ(isNumber("0.", true), false);
EXPECT_EQ(isNumber(".0", true), false);
EXPECT_EQ(isNumber("", true), false);
EXPECT_EQ(isNumber("vvss", true), false);
EXPECT_EQ(isNumber("0.9999s", true), false);
EXPECT_EQ(isNumber("s0.9999", true), false);
EXPECT_EQ(isNumber("-1.0", true), true);
EXPECT_EQ(isNumber("-1..0", true), false);
EXPECT_EQ(isNumber("-10.0000000001", true), true);
EXPECT_EQ(truthy("frgeujgeruibger"), false);
EXPECT_EQ(truthy("false"), false);
EXPECT_EQ(truthy("0"), false);
EXPECT_EQ(truthy("yees"), false);
EXPECT_EQ(truthy("naa"), false);
EXPECT_EQ(truthy("-1"), false);
EXPECT_EQ(truthy("true"), true);
EXPECT_EQ(truthy("true eeee ee"), true);
EXPECT_EQ(truthy("yesss"), true);
EXPECT_EQ(truthy("1"), true);
EXPECT_EQ(truthy("on"), true);
EXPECT_EQ(truthy("onn"), true);
}

11
tests/string/VarList.cpp Normal file
View file

@ -0,0 +1,11 @@
#include <hyprutils/string/VarList.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::String;
TEST(String, varlist) {
CVarList list("hello world!", 0, 's', true);
EXPECT_EQ(list[0], "hello");
EXPECT_EQ(list[1], "world!");
}

67
tests/string/VarList2.cpp Normal file
View file

@ -0,0 +1,67 @@
#include <hyprutils/string/VarList2.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::String;
TEST(String, varlist2) {
CVarList2 varList2("0 set", 2, ' ');
EXPECT_EQ(varList2[0], "0");
EXPECT_EQ(varList2[1], "set");
varList2.append("Hello");
EXPECT_EQ(varList2[1], "set");
EXPECT_EQ(varList2[2], "Hello");
EXPECT_EQ(varList2[3], "");
EXPECT_EQ(varList2.contains("set"), true);
EXPECT_EQ(varList2.contains("sett"), false);
EXPECT_EQ(varList2.contains(""), false);
EXPECT_EQ(varList2.size(), 3);
CVarList2 varList2B("hello, world\\, ok?", 0, ',', true, true);
EXPECT_EQ(varList2B[0], "hello");
EXPECT_EQ(varList2B[1], "world, ok?");
CVarList2 varList2C("hello, , ok?", 0, ',', true, true);
EXPECT_EQ(varList2C[0], "hello");
EXPECT_EQ(varList2C[1], "ok?");
CVarList2 varList2D("\\\\, , ok?", 0, ',', true, true);
EXPECT_EQ(varList2D[0], "\\");
EXPECT_EQ(varList2D[1], "ok?");
CVarList2 varList2E("\\, , ok?", 0, ',', true, true);
EXPECT_EQ(varList2E[0], ",");
EXPECT_EQ(varList2E[1], "ok?");
CVarList2 varList2F("Hello, world\\\\, ok?", 0, ',', true, true);
EXPECT_EQ(varList2F[0], "Hello");
EXPECT_EQ(varList2F[1], "world\\");
EXPECT_EQ(varList2F[2], "ok?");
CVarList2 varList2G("Hello,\\, ok?", 0, ',', true, true);
EXPECT_EQ(varList2G[0], "Hello");
EXPECT_EQ(varList2G[1], ", ok?");
CVarList2 varList2H("Hello,\\\\, ok?", 0, ',', true, true);
EXPECT_EQ(varList2H[0], "Hello");
EXPECT_EQ(varList2H[1], "\\");
EXPECT_EQ(varList2H[2], "ok?");
CVarList2 varList2I("Hello,\\, ok?", 0, ',', true, false);
EXPECT_EQ(varList2I[0], "Hello");
EXPECT_EQ(varList2I[1], "\\");
EXPECT_EQ(varList2I[2], "ok?");
CVarList2 varList2J("", 0, ',', true, false);
EXPECT_EQ(varList2J[0], "");
CVarList2 varList2K(",\\, ok?", 0, ',', true);
EXPECT_EQ(varList2K[0], ", ok?");
CVarList2 varList2L("Hello, world", 0, ',', true);
EXPECT_EQ(varList2L.join(" "), "Hello world");
EXPECT_EQ(varList2L.join(" ", 0, 1000), "Hello world");
EXPECT_EQ(varList2L.join(" ", 0, 1), "Hello");
}