Compare commits

...

40 commits
v0.8.4 ... 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
62 changed files with 2794 additions and 943 deletions

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++ pixman 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++ pixman pacman --noconfirm --noprogressbar -Sy gcc gtest base-devel cmake clang libc++ pixman
- name: Build hyprutils with clang - name: Build hyprutils with clang
run: | run: |

View file

@ -33,9 +33,11 @@ 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")
@ -50,67 +52,31 @@ target_include_directories(
PUBLIC "./include" PUBLIC "./include"
PRIVATE "./src") PRIVATE "./src")
set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION} set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION}
SOVERSION 7) SOVERSION 10)
target_link_libraries(hyprutils PkgConfig::deps) target_link_libraries(hyprutils PkgConfig::deps)
# tests if(BUILD_TESTING)
add_custom_target(tests) # GTest
find_package(GTest CONFIG REQUIRED)
include(GoogleTest)
file(GLOB_RECURSE TESTFILES CONFIGURE_DEPENDS "tests/*.cpp")
add_executable(hyprutils_tests ${TESTFILES})
add_executable(hyprutils_memory "tests/memory.cpp") target_compile_options(hyprutils_tests PRIVATE --coverage)
target_link_libraries(hyprutils_memory PRIVATE hyprutils PkgConfig::deps) target_link_options(hyprutils_tests PRIVATE --coverage)
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_include_directories(
target_link_libraries(hyprutils_string PRIVATE hyprutils PkgConfig::deps) hyprutils_tests
add_test( PUBLIC "./include"
NAME "String" PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}")
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests target_link_libraries(hyprutils_tests PRIVATE hyprutils GTest::gtest_main
COMMAND hyprutils_string "string") PkgConfig::deps)
add_dependencies(tests hyprutils_string) gtest_discover_tests(hyprutils_tests)
add_executable(hyprutils_signal "tests/signal.cpp") # Add coverage to hyprutils for test builds
target_link_libraries(hyprutils_signal PRIVATE hyprutils PkgConfig::deps) target_compile_options(hyprutils PRIVATE --coverage)
add_test( target_link_options(hyprutils PRIVATE --coverage)
NAME "Signal" endif()
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_signal "signal")
add_dependencies(tests hyprutils_signal)
add_executable(hyprutils_math "tests/math.cpp")
target_link_libraries(hyprutils_math PRIVATE hyprutils PkgConfig::deps)
add_test(
NAME "Math"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_math "math")
add_dependencies(tests hyprutils_math)
add_executable(hyprutils_os "tests/os.cpp")
target_link_libraries(hyprutils_os PRIVATE hyprutils PkgConfig::deps)
add_test(
NAME "OS"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_os "os")
add_dependencies(tests hyprutils_os)
add_executable(hyprutils_filedescriptor "tests/filedescriptor.cpp")
target_link_libraries(hyprutils_filedescriptor PRIVATE hyprutils PkgConfig::deps)
add_test(
NAME "Filedescriptor"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_filedescriptor "filedescriptor")
add_dependencies(tests hyprutils_filedescriptor)
add_executable(hyprutils_animation "tests/animation.cpp")
target_link_libraries(hyprutils_animation PRIVATE hyprutils PkgConfig::deps)
add_test(
NAME "Animation"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_animation "utils")
add_dependencies(tests hyprutils_animation)
# Installation # Installation
install(TARGETS hyprutils) install(TARGETS hyprutils)

View file

@ -1 +1 @@
0.8.4 0.11.0

View file

@ -22,6 +22,7 @@ namespace Hyprutils {
}; };
void create(CAnimationManager*, int, Memory::CSharedPointer<CBaseAnimatedVariable>); void create(CAnimationManager*, int, Memory::CSharedPointer<CBaseAnimatedVariable>);
void create2(CAnimationManager*, int, Memory::CWeakPointer<CBaseAnimatedVariable>);
void connectToActive(); void connectToActive();
void disconnectFromActive(); void disconnectFromActive();
@ -136,6 +137,7 @@ namespace Hyprutils {
public: public:
CGenericAnimatedVariable() = default; CGenericAnimatedVariable() = default;
/* Deprecated: use create2 */
void create(const int typeInfo, CAnimationManager* pAnimationManager, Memory::CSharedPointer<CGenericAnimatedVariable<VarType, AnimationContext>> pSelf, void create(const int typeInfo, CAnimationManager* pAnimationManager, Memory::CSharedPointer<CGenericAnimatedVariable<VarType, AnimationContext>> pSelf,
const VarType& initialValue) { const VarType& initialValue) {
m_Begun = initialValue; m_Begun = initialValue;
@ -145,6 +147,16 @@ namespace Hyprutils {
CBaseAnimatedVariable::create(pAnimationManager, typeInfo, pSelf); 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(const CGenericAnimatedVariable&) = delete;
CGenericAnimatedVariable(CGenericAnimatedVariable&&) = delete; CGenericAnimatedVariable(CGenericAnimatedVariable&&) = delete;
CGenericAnimatedVariable& operator=(const CGenericAnimatedVariable&) = delete; CGenericAnimatedVariable& operator=(const CGenericAnimatedVariable&) = delete;

View file

@ -15,6 +15,8 @@ namespace Hyprutils {
public: public:
/* Calculates a cubic bezier curve based on 2 control points (EXCLUDES the 0,0 and 1,1 points). */ /* 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); 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 getYForT(float const& t) const;
float getXForT(float const& t) const; float getXForT(float const& t) const;

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

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <hyprutils/memory/Casts.hpp>
#include <hyprutils/math/Misc.hpp> #include <hyprutils/math/Misc.hpp>
#include <format> #include <format>
@ -9,10 +10,14 @@ namespace Hyprutils {
namespace Math { namespace Math {
class Vector2D { class Vector2D {
public: public:
Vector2D(double, double); constexpr Vector2D(double xx, double yy) : x(xx), y(yy) {
Vector2D(int, int); ;
Vector2D(); }
~Vector2D(); 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 x = 0;
double y = 0; double y = 0;
@ -20,71 +25,71 @@ namespace Hyprutils {
// returns the scale // returns the scale
double normalize(); double normalize();
Vector2D operator+(const Vector2D& a) const { constexpr Vector2D operator+(const Vector2D& a) const {
return Vector2D(this->x + a.x, this->y + a.y); return Vector2D(this->x + a.x, this->y + a.y);
} }
Vector2D operator-(const Vector2D& a) const { constexpr Vector2D operator-(const Vector2D& a) const {
return Vector2D(this->x - a.x, this->y - a.y); return Vector2D(this->x - a.x, this->y - a.y);
} }
Vector2D operator-() const { constexpr Vector2D operator-() const {
return Vector2D(-this->x, -this->y); return Vector2D(-this->x, -this->y);
} }
Vector2D operator*(const double& a) const { constexpr Vector2D operator*(const double& a) const {
return Vector2D(this->x * a, this->y * a); return Vector2D(this->x * a, this->y * a);
} }
Vector2D operator/(const double& a) const { constexpr Vector2D operator/(const double& a) const {
return Vector2D(this->x / a, this->y / a); return Vector2D(this->x / a, this->y / a);
} }
bool operator==(const Vector2D& a) const { constexpr bool operator==(const Vector2D& a) const {
return a.x == x && a.y == y; return a.x == x && a.y == y;
} }
bool operator!=(const Vector2D& a) const { constexpr bool operator!=(const Vector2D& a) const {
return a.x != x || a.y != y; return a.x != x || a.y != y;
} }
Vector2D operator*(const Vector2D& a) const { constexpr Vector2D operator*(const Vector2D& a) const {
return Vector2D(this->x * a.x, this->y * a.y); return Vector2D(this->x * a.x, this->y * a.y);
} }
Vector2D operator/(const Vector2D& a) const { constexpr Vector2D operator/(const Vector2D& a) const {
return Vector2D(this->x / a.x, this->y / a.y); return Vector2D(this->x / a.x, this->y / a.y);
} }
bool operator>(const Vector2D& a) const { constexpr bool operator>(const Vector2D& a) const {
return this->x > a.x && this->y > a.y; return this->x > a.x && this->y > a.y;
} }
bool operator<(const Vector2D& a) const { constexpr bool operator<(const Vector2D& a) const {
return this->x < a.x && this->y < a.y; return this->x < a.x && this->y < a.y;
} }
Vector2D& operator+=(const Vector2D& a) { constexpr Vector2D& operator+=(const Vector2D& a) {
this->x += a.x; this->x += a.x;
this->y += a.y; this->y += a.y;
return *this; return *this;
} }
Vector2D& operator-=(const Vector2D& a) { constexpr Vector2D& operator-=(const Vector2D& a) {
this->x -= a.x; this->x -= a.x;
this->y -= a.y; this->y -= a.y;
return *this; return *this;
} }
Vector2D& operator*=(const Vector2D& a) { constexpr Vector2D& operator*=(const Vector2D& a) {
this->x *= a.x; this->x *= a.x;
this->y *= a.y; this->y *= a.y;
return *this; return *this;
} }
Vector2D& operator/=(const Vector2D& a) { constexpr Vector2D& operator/=(const Vector2D& a) {
this->x /= a.x; this->x /= a.x;
this->y /= a.y; this->y /= a.y;
return *this; return *this;
} }
Vector2D& operator*=(const double& a) { constexpr Vector2D& operator*=(const double& a) {
this->x *= a; this->x *= a;
this->y *= a; this->y *= a;
return *this; return *this;
} }
Vector2D& operator/=(const double& a) { constexpr Vector2D& operator/=(const double& a) {
this->x /= a; this->x /= a;
this->y /= a; this->y /= a;
return *this; return *this;

View file

@ -23,12 +23,11 @@
namespace Hyprutils::Memory { namespace Hyprutils::Memory {
namespace Atomic_ { namespace Atomic_ {
template <typename T> class impl : public Impl_::impl_base {
class impl : public Impl_::impl<T> {
std::recursive_mutex m_mutex; std::recursive_mutex m_mutex;
public: public:
impl(T* data, bool lock = true) noexcept : Impl_::impl<T>(data, lock) { impl(void* data, DeleteFn deleter) noexcept : Impl_::impl_base(data, deleter) {
; ;
} }
@ -50,12 +49,18 @@ namespace Hyprutils::Memory {
template <typename T> template <typename T>
class CAtomicSharedPointer { class CAtomicSharedPointer {
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&>>;
template <typename X> template <typename X>
using validHierarchy = typename std::enable_if<std::is_assignable<CAtomicSharedPointer<T>&, X>::value, CAtomicSharedPointer&>::type; using validHierarchy = std::enable_if_t<std::is_assignable_v<CAtomicSharedPointer<T>&, X>, CAtomicSharedPointer&>;
public: public:
explicit CAtomicSharedPointer(Impl_::impl_base* impl) noexcept : m_ptr(impl) {} 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) { CAtomicSharedPointer(const CAtomicSharedPointer<T>& ref) {
if (!ref.m_ptr.impl_) if (!ref.m_ptr.impl_)
@ -141,7 +146,7 @@ namespace Hyprutils::Memory {
// -> must unlock BEFORE reset // -> must unlock BEFORE reset
// not last ref? // not last ref?
// -> must unlock AFTER reset // -> must unlock AFTER reset
auto& mutex = sc<Atomic_::impl<T>*>(m_ptr.impl_)->getMutex(); auto& mutex = sc<Atomic_::impl*>(m_ptr.impl_)->getMutex();
mutex.lock(); mutex.lock();
if (m_ptr.impl_->ref() > 1) { if (m_ptr.impl_->ref() > 1) {
@ -152,7 +157,11 @@ namespace Hyprutils::Memory {
if (m_ptr.impl_->wref() == 0) { if (m_ptr.impl_->wref() == 0) {
mutex.unlock(); // Don't hold the mutex when destroying it mutex.unlock(); // Don't hold the mutex when destroying it
m_ptr.reset();
m_ptr.impl_->destroy();
delete sc<Atomic_::impl*>(m_ptr.impl_);
m_ptr.impl_ = nullptr;
// mutex invalid // mutex invalid
return; return;
} else { } else {
@ -163,12 +172,18 @@ namespace Hyprutils::Memory {
// To avoid this altogether, keep a weak pointer here. // To avoid this altogether, keep a weak pointer here.
// This guarantees that impl_ is still valid after the reset. // This guarantees that impl_ is still valid after the reset.
CWeakPointer<T> guard = m_ptr; CWeakPointer<T> guard = m_ptr;
m_ptr.reset(); m_ptr.reset(); // destroys the data
// Now we can safely check if guard is the last wref. // Now we can safely check if guard is the last wref.
if (guard.impl_->wref() == 1) { if (guard.impl_->wref() == 1) {
mutex.unlock(); mutex.unlock();
return; // ~guard destroys impl_ and mutex
// destroy impl_ (includes the mutex)
delete sc<Atomic_::impl*>(guard.impl_);
guard.impl_ = nullptr;
// mutex invalid
return;
} }
guard.reset(); guard.reset();
@ -204,9 +219,17 @@ namespace Hyprutils::Memory {
return m_ptr.impl_ ? m_ptr.impl_->ref() : 0; return m_ptr.impl_ ? m_ptr.impl_->ref() : 0;
} }
Atomic_::impl* impl() const {
return sc<Atomic_::impl*>(m_ptr.impl_);
}
private: private:
static void _delete(void* p) {
std::default_delete<T>{}(sc<T*>(p));
}
std::lock_guard<std::recursive_mutex> implLockGuard() const { std::lock_guard<std::recursive_mutex> implLockGuard() const {
return sc<Atomic_::impl<T>*>(m_ptr.impl_)->lockGuard(); return impl()->lockGuard();
} }
CSharedPointer<T> m_ptr; CSharedPointer<T> m_ptr;
@ -221,9 +244,9 @@ namespace Hyprutils::Memory {
class CAtomicWeakPointer { class CAtomicWeakPointer {
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&>>;
template <typename X> template <typename X>
using validHierarchy = typename std::enable_if<std::is_assignable<CAtomicWeakPointer<T>&, X>::value, CAtomicWeakPointer&>::type; using validHierarchy = std::enable_if_t<std::is_assignable_v<CAtomicWeakPointer<T>&, X>, CAtomicWeakPointer&>;
public: public:
CAtomicWeakPointer(const CAtomicWeakPointer<T>& ref) { CAtomicWeakPointer(const CAtomicWeakPointer<T>& ref) {
@ -312,11 +335,13 @@ namespace Hyprutils::Memory {
// -> must unlock BEFORE reset // -> must unlock BEFORE reset
// not last ref? // not last ref?
// -> must unlock AFTER reset // -> must unlock AFTER reset
auto& mutex = sc<Atomic_::impl<T>*>(m_ptr.impl_)->getMutex(); auto& mutex = sc<Atomic_::impl*>(m_ptr.impl_)->getMutex();
mutex.lock(); mutex.lock();
if (m_ptr.impl_->ref() == 0 && m_ptr.impl_->wref() == 1) { if (m_ptr.impl_->ref() == 0 && m_ptr.impl_->wref() == 1) {
mutex.unlock(); mutex.unlock();
m_ptr.reset();
delete sc<Atomic_::impl*>(m_ptr.impl_);
m_ptr.impl_ = nullptr;
// mutex invalid // mutex invalid
return; return;
} }
@ -370,12 +395,16 @@ namespace Hyprutils::Memory {
if (!m_ptr.impl_->dataNonNull() || m_ptr.impl_->destroying() || !m_ptr.impl_->lockable()) if (!m_ptr.impl_->dataNonNull() || m_ptr.impl_->destroying() || !m_ptr.impl_->lockable())
return {}; return {};
return CAtomicSharedPointer<T>(m_ptr.impl_); return CAtomicSharedPointer<T>(m_ptr.impl_, m_ptr.m_data);
}
Atomic_::impl* impl() const {
return sc<Atomic_::impl*>(m_ptr.impl_);
} }
private: private:
std::lock_guard<std::recursive_mutex> implLockGuard() const { std::lock_guard<std::recursive_mutex> implLockGuard() const {
return sc<Atomic_::impl<T>*>(m_ptr.impl_)->lockGuard(); return impl()->lockGuard();
} }
CWeakPointer<T> m_ptr; CWeakPointer<T> m_ptr;
@ -387,7 +416,22 @@ namespace Hyprutils::Memory {
}; };
template <typename U, typename... Args> template <typename U, typename... Args>
static CAtomicSharedPointer<U> makeAtomicShared(Args&&... args) { [[nodiscard]] inline CAtomicSharedPointer<U> makeAtomicShared(Args&&... args) {
return CAtomicSharedPointer<U>(new Atomic_::impl<U>(new U(std::forward<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

@ -27,5 +27,3 @@ namespace Hyprutils::Memory {
return std::bit_cast<To>(from); return std::bit_cast<To>(from);
} }
} }

View file

@ -8,28 +8,61 @@ namespace Hyprutils {
namespace Impl_ { namespace Impl_ {
class impl_base { class impl_base {
public: public:
virtual ~impl_base() {}; using DeleteFn = void (*)(void*);
virtual void inc() noexcept = 0; impl_base(void* data, DeleteFn deleter, bool lock = true) noexcept : _lockable(lock), _data(data), _deleter(deleter) {
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;
virtual bool lockable() noexcept = 0;
virtual void* getData() noexcept = 0;
};
template <typename T>
class impl : public impl_base {
public:
impl(T* data, bool lock = true) noexcept : _lockable(lock), _data(data) {
; ;
} }
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 */ /* strong refcount */
unsigned int _ref = 0; unsigned int _ref = 0;
/* weak refcount */ /* weak refcount */
@ -37,13 +70,7 @@ namespace Hyprutils {
/* if this is lockable (shared) */ /* if this is lockable (shared) */
bool _lockable = true; bool _lockable = true;
T* _data = nullptr; void* _data = nullptr;
friend void swap(impl*& a, impl*& b) {
impl* tmp = a;
a = b;
b = tmp;
}
/* if the destructor was called, /* if the destructor was called,
creating shared_ptrs is no longer valid */ creating shared_ptrs is no longer valid */
@ -57,62 +84,13 @@ namespace Hyprutils {
// this way, weak pointers will still be able to // this way, weak pointers will still be able to
// reference and use, but no longer create shared ones. // reference and use, but no longer create shared ones.
_destroying = true; _destroying = true;
__deleter(_data); _deleter(_data);
// now, we can reset the data and call it a day. // now, we can reset the data and call it a day.
_data = nullptr; _data = nullptr;
_destroying = false; _destroying = false;
} }
std::default_delete<T> __deleter{}; DeleteFn _deleter = nullptr;
//
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 lockable() noexcept {
return _lockable;
}
virtual bool dataNonNull() noexcept {
return _data != nullptr;
}
virtual void* getData() noexcept {
return _data;
}
virtual ~impl() {
destroy();
}
}; };
} }
} }

View file

@ -22,41 +22,39 @@ namespace Hyprutils {
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 Impl_::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(Impl_::impl_base* implementation) noexcept { CSharedPointer(Impl_::impl_base* implementation, void* data) noexcept : impl_(implementation), m_data(data) {
impl_ = implementation;
increment(); increment();
} }
@ -79,6 +77,7 @@ namespace Hyprutils {
decrement(); decrement();
impl_ = rhs.impl_; impl_ = rhs.impl_;
m_data = rhs.m_data;
increment(); increment();
return *this; return *this;
} }
@ -89,6 +88,7 @@ namespace Hyprutils {
decrement(); decrement();
impl_ = rhs.impl_; impl_ = rhs.impl_;
m_data = rhs.m_data;
increment(); increment();
return *this; return *this;
} }
@ -96,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;
} }
@ -108,6 +110,8 @@ 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_;
} }
@ -131,10 +135,11 @@ namespace Hyprutils {
void reset() { void reset() {
decrement(); decrement();
impl_ = nullptr; impl_ = nullptr;
m_data = nullptr;
} }
T* get() const { T* get() const {
return impl_ ? sc<T*>(impl_->getData()) : nullptr; return impl_ && impl_->dataNonNull() ? sc<T*>(m_data) : nullptr;
} }
unsigned int strongRef() const { unsigned int strongRef() const {
@ -143,7 +148,14 @@ namespace Hyprutils {
Impl_::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
@ -188,7 +200,17 @@ namespace Hyprutils {
template <typename T, typename U> template <typename T, typename U>
CSharedPointer<T> reinterpretPointerCast(const CSharedPointer<U>& ref) { CSharedPointer<T> reinterpretPointerCast(const CSharedPointer<U>& ref) {
return CSharedPointer<T>(ref.impl_); 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);
} }
} }
} }

View file

@ -16,14 +16,13 @@ namespace Hyprutils {
class CUniquePointer { class CUniquePointer {
public: public:
template <typename X> template <typename X>
using validHierarchy = typename std::enable_if<std::is_assignable<CUniquePointer<T>&, X>::value, CUniquePointer&>::type; using validHierarchy = std::enable_if_t<std::is_assignable_v<CUniquePointer<T>&, X>, CUniquePointer&>;
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 unique pointer managing a resource /* creates a new unique pointer managing a resource
avoid calling. Could duplicate ownership. Prefer makeUnique */ avoid calling. Could duplicate ownership. Prefer makeUnique */
explicit CUniquePointer(T* object) noexcept { explicit CUniquePointer(T* object) noexcept : impl_(new Impl_::impl_base(sc<void*>(object), [](void* p) { std::default_delete<T>{}(sc<T*>(p)); }, false)) {
impl_ = new Impl_::impl<T>(object, false);
increment(); increment();
} }
@ -63,7 +62,7 @@ namespace Hyprutils {
return *this; return *this;
} }
CUniquePointer& operator=(CUniquePointer&& rhs) { CUniquePointer& operator=(CUniquePointer&& rhs) noexcept {
std::swap(impl_, rhs.impl_); std::swap(impl_, rhs.impl_);
return *this; return *this;
} }

View file

@ -16,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>>
@ -27,6 +27,7 @@ namespace Hyprutils {
return; return;
impl_ = ref.impl_; impl_ = ref.impl_;
m_data = ref.m_data;
incrementWeak(); incrementWeak();
} }
@ -37,6 +38,7 @@ namespace Hyprutils {
return; return;
impl_ = ref.impl_; impl_ = ref.impl_;
m_data = ref.impl_->getData();
incrementWeak(); incrementWeak();
} }
@ -47,6 +49,7 @@ namespace Hyprutils {
return; return;
impl_ = ref.impl_; impl_ = ref.impl_;
m_data = ref.m_data;
incrementWeak(); incrementWeak();
} }
@ -55,16 +58,19 @@ namespace Hyprutils {
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 */
@ -75,6 +81,7 @@ namespace Hyprutils {
decrementWeak(); decrementWeak();
impl_ = rhs.impl_; impl_ = rhs.impl_;
m_data = rhs.m_data;
incrementWeak(); incrementWeak();
return *this; return *this;
} }
@ -85,6 +92,7 @@ namespace Hyprutils {
decrementWeak(); decrementWeak();
impl_ = rhs.impl_; impl_ = rhs.impl_;
m_data = rhs.m_data;
incrementWeak(); incrementWeak();
return *this; return *this;
} }
@ -97,6 +105,7 @@ namespace Hyprutils {
decrementWeak(); decrementWeak();
impl_ = rhs.impl_; impl_ = rhs.impl_;
m_data = rhs.m_data;
incrementWeak(); incrementWeak();
return *this; return *this;
} }
@ -126,13 +135,14 @@ 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() || !impl_->lockable()) 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() */
@ -169,7 +179,7 @@ namespace Hyprutils {
} }
T* get() const { T* get() const {
return impl_ ? sc<T*>(impl_->getData()) : nullptr; return impl_ && impl_->dataNonNull() ? sc<T*>(m_data) : nullptr;
} }
T* operator->() const { T* operator->() const {
@ -182,6 +192,9 @@ namespace Hyprutils {
Impl_::impl_base* impl_ = nullptr; 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_ */
void decrementWeak() { void decrementWeak() {
@ -207,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

@ -26,10 +26,13 @@ namespace Hyprutils {
template <typename... Args> template <typename... Args>
class CSignalT : public CSignalBase { class CSignalT : public CSignalBase {
template <typename T> template <typename T>
using RefArg = std::conditional_t<std::is_reference_v<T> || std::is_arithmetic_v<T>, T, const T&>; using RefArg = std::conditional_t<std::is_trivially_copyable_v<T>, T, const T&>;
public: public:
void emit(RefArg<Args>... args) { void emit(RefArg<Args>... args) {
if (m_vListeners.empty() && m_vStaticListeners.empty())
return;
if constexpr (sizeof...(Args) == 0) if constexpr (sizeof...(Args) == 0)
emitInternal(nullptr); emitInternal(nullptr);
else { else {

View file

@ -4,8 +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 char* in);
std::string trim(const std::string& in); 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); bool isNumber(const std::string& str, bool allowfloat = false);
void replaceInString(std::string& string, const std::string& what, const std::string& to); void replaceInString(std::string& string, const std::string& what, const std::string& to);
bool truthy(const std::string_view& in);
}; };
}; };

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

@ -4,16 +4,17 @@
stdenvAdapters, stdenvAdapters,
cmake, cmake,
pkg-config, pkg-config,
gtest,
pixman, pixman,
version ? "git", version ? "git",
doCheck ? false,
debug ? false, debug ? false,
# whether to use the mold linker # whether to use the mold linker
# disable this for older machines without SSE4_2 and AVX2 support # disable this for older machines without SSE4_2 and AVX2 support
withMold ? true, withMold ? true,
}: let }:
let
inherit (builtins) foldl'; inherit (builtins) foldl';
inherit (lib.lists) flatten; inherit (lib.lists) flatten optional;
inherit (lib.strings) optionalString; inherit (lib.strings) optionalString;
adapters = flatten [ adapters = flatten [
@ -23,26 +24,29 @@
customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters; customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters;
in in
customStdenv.mkDerivation { customStdenv.mkDerivation {
pname = "hyprutils" + optionalString debug "-debug"; pname = "hyprutils" + optionalString debug "-debug";
inherit version doCheck; inherit version;
src = ../.; src = ../.;
doCheck = debug;
nativeBuildInputs = [ nativeBuildInputs = [
cmake cmake
pkg-config pkg-config
]; ];
buildInputs = [ buildInputs = flatten [
(optional debug gtest)
pixman pixman
]; ];
outputs = ["out" "dev"]; outputs = [
"out"
"dev"
];
cmakeBuildType = cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
if debug
then "Debug"
else "RelWithDebInfo";
meta = with lib; { meta = with lib; {
homepage = "https://github.com/hyprwm/hyprutils"; homepage = "https://github.com/hyprwm/hyprutils";
@ -50,4 +54,4 @@ in
license = licenses.bsd3; license = licenses.bsd3;
platforms = platforms.linux; platforms = platforms.linux;
}; };
} }

View file

@ -18,6 +18,6 @@ in {
inherit version; inherit version;
}; };
hyprutils-debug = final.hyprutils.override {debug = true;}; hyprutils-debug = final.hyprutils.override {debug = true;};
hyprutils-with-tests = final.hyprutils.override {doCheck = true;}; hyprutils-with-tests = final.hyprutils-debug;
}; };
} }

View file

@ -13,7 +13,16 @@ static const std::string DEFAULTSTYLE = "";
void CBaseAnimatedVariable::create(CAnimationManager* pManager, int typeInfo, SP<CBaseAnimatedVariable> pSelf) { void CBaseAnimatedVariable::create(CAnimationManager* pManager, int typeInfo, SP<CBaseAnimatedVariable> pSelf) {
m_Type = typeInfo; m_Type = typeInfo;
m_pSelf = pSelf; 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_pAnimationManager = pManager;
m_pSignals = pManager->getSignals(); m_pSignals = pManager->getSignals();
@ -37,28 +46,22 @@ void CBaseAnimatedVariable::disconnectFromActive() {
} }
bool Hyprutils::Animation::CBaseAnimatedVariable::enabled() const { bool Hyprutils::Animation::CBaseAnimatedVariable::enabled() const {
if (const auto PCONFIG = m_pConfig.lock()) { if (m_pConfig && m_pConfig->pValues)
const auto PVALUES = PCONFIG->pValues.lock(); return m_pConfig->pValues->internalEnabled;
return PVALUES ? PVALUES->internalEnabled : false;
}
return false; return false;
} }
const std::string& CBaseAnimatedVariable::getBezierName() const { const std::string& CBaseAnimatedVariable::getBezierName() const {
if (const auto PCONFIG = m_pConfig.lock()) { if (m_pConfig && m_pConfig->pValues)
const auto PVALUES = PCONFIG->pValues.lock(); return m_pConfig->pValues->internalBezier;
return PVALUES ? PVALUES->internalBezier : DEFAULTBEZIERNAME;
}
return DEFAULTBEZIERNAME; return DEFAULTBEZIERNAME;
} }
const std::string& CBaseAnimatedVariable::getStyle() const { const std::string& CBaseAnimatedVariable::getStyle() const {
if (const auto PCONFIG = m_pConfig.lock()) { if (m_pConfig && m_pConfig->pValues)
const auto PVALUES = PCONFIG->pValues.lock(); return m_pConfig->pValues->internalStyle;
return PVALUES ? PVALUES->internalStyle : DEFAULTSTYLE;
}
return DEFAULTSTYLE; return DEFAULTSTYLE;
} }
@ -66,10 +69,8 @@ const std::string& CBaseAnimatedVariable::getStyle() const {
float CBaseAnimatedVariable::getPercent() const { float CBaseAnimatedVariable::getPercent() const {
const auto DURATIONPASSED = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - animationBegin).count(); const auto DURATIONPASSED = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - animationBegin).count();
if (const auto PCONFIG = m_pConfig.lock()) { if (m_pConfig && m_pConfig->pValues)
const auto PVALUES = PCONFIG->pValues.lock(); return std::clamp((DURATIONPASSED / 100.f) / m_pConfig->pValues->internalSpeed, 0.f, 1.f);
return PVALUES ? std::clamp((DURATIONPASSED / 100.f) / PVALUES->internalSpeed, 0.f, 1.f) : 1.f;
}
return 1.f; return 1.f;
} }
@ -78,14 +79,7 @@ float CBaseAnimatedVariable::getCurveValue() const {
if (!m_bIsBeingAnimated || isAnimationManagerDead()) if (!m_bIsBeingAnimated || isAnimationManagerDead())
return 1.f; return 1.f;
std::string bezierName = ""; const auto BEZIER = m_pAnimationManager->getBezier(getBezierName());
if (const auto PCONFIG = m_pConfig.lock()) {
const auto PVALUES = PCONFIG->pValues.lock();
if (PVALUES)
bezierName = PVALUES->internalBezier;
}
const auto BEZIER = m_pAnimationManager->getBezier(bezierName);
if (!BEZIER) if (!BEZIER)
return 1.f; return 1.f;

View file

@ -64,14 +64,13 @@ void CAnimationManager::rotateActive() {
std::vector<CWeakPointer<CBaseAnimatedVariable>> active; std::vector<CWeakPointer<CBaseAnimatedVariable>> active;
active.reserve(m_vActiveAnimatedVariables.size()); // avoid reallocations active.reserve(m_vActiveAnimatedVariables.size()); // avoid reallocations
for (auto const& av : m_vActiveAnimatedVariables) { for (auto const& av : m_vActiveAnimatedVariables) {
const auto PAV = av.lock(); if (!av)
if (!PAV)
continue; continue;
if (PAV->ok() && PAV->isBeingAnimated()) if (av->ok() && av->isBeingAnimated())
active.emplace_back(av); active.emplace_back(av);
else else
PAV->m_bIsConnectedToActive = false; av->m_bIsConnectedToActive = false;
} }
m_vActiveAnimatedVariables = std::move(active); m_vActiveAnimatedVariables = std::move(active);

View file

@ -9,42 +9,46 @@ using namespace Hyprutils::Math;
using namespace Hyprutils::Memory; using namespace Hyprutils::Memory;
void CBezierCurve::setup(const std::array<Vector2D, 2>& pVec) { void CBezierCurve::setup(const std::array<Vector2D, 2>& pVec) {
// Avoid reallocations by reserving enough memory upfront setup4(std::array<Vector2D, 4>{
m_vPoints.resize(pVec.size() + 2);
m_vPoints = {
Vector2D(0, 0), // Start point Vector2D(0, 0), // Start point
pVec[0], pVec[1], // Control points pVec[0], pVec[1], // Control points
Vector2D(1, 1) // End point 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],
}; };
if (m_vPoints.size() != 4) // Pre-bake curve
std::abort(); //
// We start baking at t=(i+1)/n not at t=0
// bake BAKEDPOINTS points for faster lookups // That means the first baked x can be > 0 if curve itself starts at x>0
// T -> X ( / BAKEDPOINTS )
for (int i = 0; i < BAKEDPOINTS; ++i) { for (int i = 0; i < BAKEDPOINTS; ++i) {
float const t = (i + 1) / sc<float>(BAKEDPOINTS); // When i=0 -> t=1/255
const float t = (i + 1) * INVBAKEDPOINTS;
m_aPointsBaked[i] = Vector2D(getXForT(t), getYForT(t)); m_aPointsBaked[i] = Vector2D(getXForT(t), getYForT(t));
} }
for (int j = 1; j < 10; ++j) {
float i = j / 10.0f;
getYForPoint(i);
}
} }
float CBezierCurve::getXForT(float const& t) const { float CBezierCurve::getXForT(float const& t) const {
float t2 = t * t; float t2 = t * t;
float t3 = t2 * t; float t3 = t2 * t;
return (3 * t * (1 - t) * (1 - t) * m_vPoints[1].x) + (3 * t2 * (1 - t) * m_vPoints[2].x) + (t3 * m_vPoints[3].x); 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 CBezierCurve::getYForT(float const& t) const {
float t2 = t * t; float t2 = t * t;
float t3 = t2 * t; float t3 = t2 * t;
return (3 * t * (1 - t) * (1 - t) * m_vPoints[1].y) + (3 * t2 * (1 - t) * m_vPoints[2].y) + (t3 * m_vPoints[3].y); 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 // Todo: this probably can be done better and faster
@ -62,21 +66,40 @@ float CBezierCurve::getYForPoint(float const& x) const {
else else
index -= step; 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; below = m_aPointsBaked[index].x < x;
} }
int lowerIndex = index - (!below || index == BAKEDPOINTS - 1); int lowerIndex = index - (!below || index == BAKEDPOINTS - 1);
// in the name of performance i shall make a hack // Clamp final indices
const auto LOWERPOINT = &m_aPointsBaked[lowerIndex]; if (lowerIndex < 0)
const auto UPPERPOINT = &m_aPointsBaked[lowerIndex + 1]; lowerIndex = 0;
else if (lowerIndex > BAKEDPOINTS - 2)
lowerIndex = BAKEDPOINTS - 2;
const auto PERCINDELTA = (x - LOWERPOINT->x) / (UPPERPOINT->x - LOWERPOINT->x); // In the name of performance I shall make a hack
const auto& LOWERPOINT = m_aPointsBaked[lowerIndex];
const auto& UPPERPOINT = m_aPointsBaked[lowerIndex + 1];
if (std::isnan(PERCINDELTA) || std::isinf(PERCINDELTA)) // can sometimes happen for VERY small x const float dx = (UPPERPOINT.x - LOWERPOINT.x);
return 0.f; // If two baked points have almost the same x
// just return the lower one
if (dx <= 1e-6f)
return LOWERPOINT.y;
return LOWERPOINT->y + ((UPPERPOINT->y - LOWERPOINT->y) * PERCINDELTA); 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 { const std::vector<Hyprutils::Math::Vector2D>& CBezierCurve::getControlPoints() const {

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;
}

View file

@ -145,18 +145,21 @@ CRegion& Hyprutils::Math::CRegion::scale(const Vector2D& scale) {
if (scale == Vector2D{1, 1}) if (scale == Vector2D{1, 1})
return *this; return *this;
auto rects = getRects(); int rectsNum = 0;
auto RECTSARR = pixman_region32_rectangles(&m_rRegion, &rectsNum);
clear(); std::vector<pixman_box32_t> boxes;
boxes.resize(rectsNum);
for (auto& r : rects) { for (int i = 0; i < rectsNum; ++i) {
r.x1 = std::floor(r.x1 * scale.x); boxes[i].x1 = std::floor(RECTSARR[i].x1 * scale.x);
r.y1 = std::floor(r.y1 * scale.y); boxes[i].x2 = std::ceil(RECTSARR[i].x2 * scale.x);
r.x2 = std::ceil(r.x2 * scale.x); boxes[i].y1 = std::floor(RECTSARR[i].y1 * scale.y);
r.y2 = std::ceil(r.y2 * scale.y); boxes[i].y2 = std::ceil(RECTSARR[i].y2 * scale.y);
add(&r);
} }
pixman_region32_fini(&m_rRegion);
pixman_region32_init_rects(&m_rRegion, boxes.data(), boxes.size());
return *this; return *this;
} }

View file

@ -5,21 +5,6 @@
#include <cmath> #include <cmath>
using namespace Hyprutils::Math; using namespace Hyprutils::Math;
using namespace Hyprutils::Memory;
Hyprutils::Math::Vector2D::Vector2D(double xx, double yy) : x(xx), y(yy) {
;
}
Hyprutils::Math::Vector2D::Vector2D(int xx, int yy) : x(sc<double>(xx)), y(sc<double>(yy)) {
;
}
Hyprutils::Math::Vector2D::Vector2D() : x(0), y(0) {
;
}
Hyprutils::Math::Vector2D::~Vector2D() {}
double Hyprutils::Math::Vector2D::normalize() { double Hyprutils::Math::Vector2D::normalize() {
// get max abs // get max abs

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>()));
}

View file

@ -1,26 +1,10 @@
#include <ranges> #include <ranges>
#include <algorithm> #include <algorithm>
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/ConstVarList.hpp> #include <hyprutils/string/ConstVarList.hpp>
using namespace Hyprutils::String; using namespace Hyprutils::String;
static std::string_view 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);
}
CConstVarList::CConstVarList(const std::string& in, const size_t lastArgNo, const char delim, const bool removeEmpty) : m_str(in) { CConstVarList::CConstVarList(const std::string& in, const size_t lastArgNo, const char delim, const bool removeEmpty) : m_str(in) {
if (in.empty()) if (in.empty())
return; return;
@ -33,11 +17,11 @@ CConstVarList::CConstVarList(const std::string& in, const size_t lastArgNo, cons
if (removeEmpty && s.empty()) if (removeEmpty && s.empty())
continue; continue;
if (++idx == lastArgNo) { if (++idx == lastArgNo) {
m_args.emplace_back(trim(in.substr(pos))); m_args.emplace_back(trim(std::string_view{m_str}.substr(pos)));
break; break;
} }
pos += s.size() + 1; pos += s.size() + 1;
m_args.emplace_back(trim(s.data())); m_args.emplace_back(trim(std::string_view{s.data()}));
} }
} }

View file

@ -22,6 +22,27 @@ 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;
@ -67,3 +88,16 @@ void Hyprutils::String::replaceInString(std::string& string, const std::string&
pos += to.length(); pos += to.length();
} }
} }
bool Hyprutils::String::truthy(const std::string_view& in) {
if (in == "1")
return true;
if (in == "0")
return false;
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

@ -22,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(s.data())); m_vArgs.emplace_back(trim(std::string{s.data()}));
} }
} }

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; });
}

View file

@ -1,9 +1,11 @@
#include <gtest/gtest.h>
#include <hyprutils/animation/AnimationConfig.hpp> #include <hyprutils/animation/AnimationConfig.hpp>
#include <hyprutils/animation/AnimationManager.hpp> #include <hyprutils/animation/AnimationManager.hpp>
#include <hyprutils/animation/AnimatedVariable.hpp> #include <hyprutils/animation/AnimatedVariable.hpp>
#include <hyprutils/memory/WeakPtr.hpp> #include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp> #include <hyprutils/memory/UniquePtr.hpp>
#include "shared.hpp"
#define SP CSharedPointer #define SP CSharedPointer
#define WP CWeakPointer #define WP CWeakPointer
@ -19,7 +21,7 @@ template <typename VarType>
using CAnimatedVariable = CGenericAnimatedVariable<VarType, EmtpyContext>; using CAnimatedVariable = CGenericAnimatedVariable<VarType, EmtpyContext>;
template <typename VarType> template <typename VarType>
using PANIMVAR = SP<CAnimatedVariable<VarType>>; using PANIMVAR = UP<CAnimatedVariable<VarType>>;
template <typename VarType> template <typename VarType>
using PANIMVARREF = WP<CAnimatedVariable<VarType>>; using PANIMVARREF = WP<CAnimatedVariable<VarType>>;
@ -45,8 +47,7 @@ CAnimationConfigTree animationTree;
class CMyAnimationManager : public CAnimationManager { class CMyAnimationManager : public CAnimationManager {
public: public:
void tick() { void tick() {
for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) { for (const auto& PAV : m_vActiveAnimatedVariables) {
const auto PAV = m_vActiveAnimatedVariables[i].lock();
if (!PAV || !PAV->ok() || !PAV->isBeingAnimated()) if (!PAV || !PAV->ok() || !PAV->isBeingAnimated())
continue; continue;
@ -64,21 +65,21 @@ class CMyAnimationManager : public CAnimationManager {
case eAVTypes::INT: { case eAVTypes::INT: {
auto avInt = dc<CAnimatedVariable<int>*>(PAV.get()); auto avInt = dc<CAnimatedVariable<int>*>(PAV.get());
if (!avInt) if (!avInt)
std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET; std::cout << "Dynamic cast upcast failed\n";
const auto DELTA = avInt->goal() - avInt->value(); const auto DELTA = avInt->goal() - avInt->begun();
avInt->value() = avInt->begun() + (DELTA * POINTY); avInt->value() = avInt->begun() + (DELTA * POINTY);
} break; } break;
case eAVTypes::TEST: { case eAVTypes::TEST: {
auto avCustom = dc<CAnimatedVariable<SomeTestType>*>(PAV.get()); auto avCustom = dc<CAnimatedVariable<SomeTestType>*>(PAV.get());
if (!avCustom) if (!avCustom)
std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET; std::cout << "Dynamic cast upcast failed\n";
if (SPENT >= 1.f) if (SPENT >= 1.f)
avCustom->value().done = true; avCustom->value().done = true;
} break; } break;
default: { default: {
std::cout << Colors::RED << "What are we even doing?" << Colors::RESET; std::cout << "What are we even doing?\n";
} break; } break;
} }
@ -91,11 +92,10 @@ class CMyAnimationManager : public CAnimationManager {
template <typename VarType> template <typename VarType>
void createAnimation(const VarType& v, PANIMVAR<VarType>& av, const std::string& animationConfigName) { 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; constexpr const eAVTypes EAVTYPE = std::is_same_v<VarType, int> ? eAVTypes::INT : eAVTypes::TEST;
const auto PAV = makeShared<CGenericAnimatedVariable<VarType, EmtpyContext>>(); av = makeUnique<CGenericAnimatedVariable<VarType, EmtpyContext>>();
PAV->create(EAVTYPE, sc<CAnimationManager*>(this), PAV, v); av->create2(EAVTYPE, sc<CAnimationManager*>(this), av, v);
PAV->setConfig(animationTree.getConfig(animationConfigName)); av->setConfig(animationTree.getConfig(animationConfigName));
av = std::move(PAV);
} }
virtual void scheduleTick() { virtual void scheduleTick() {
@ -121,7 +121,7 @@ class Subject {
PANIMVAR<SomeTestType> m_iC; PANIMVAR<SomeTestType> m_iC;
}; };
int config() { static int config() {
pAnimationManager = makeUnique<CMyAnimationManager>(); pAnimationManager = makeUnique<CMyAnimationManager>();
int ret = 0; int ret = 0;
@ -145,30 +145,30 @@ int config() {
auto internalCfg = animationTree.getConfig("internal"); auto internalCfg = animationTree.getConfig("internal");
// internal is a root node and should point to itself // internal is a root node and should point to itself
EXPECT(internalCfg->pParentAnimation.get(), internalCfg.get()); EXPECT_EQ(internalCfg->pParentAnimation.get(), internalCfg.get());
EXPECT(internalCfg->pValues.get(), internalCfg.get()); EXPECT_EQ(internalCfg->pValues.get(), internalCfg.get());
animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf"); animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf");
EXPECT(barCfg->internalEnabled, -1); EXPECT_EQ(barCfg->internalEnabled, -1);
{ {
const auto PVALUES = barCfg->pValues.lock(); const auto PVALUES = barCfg->pValues.lock();
EXPECT(PVALUES->internalEnabled, 1); EXPECT_EQ(PVALUES->internalEnabled, 1);
EXPECT(PVALUES->internalBezier, "default"); EXPECT_EQ(PVALUES->internalBezier, "default");
EXPECT(PVALUES->internalStyle, "asdf"); EXPECT_EQ(PVALUES->internalStyle, "asdf");
EXPECT(PVALUES->internalSpeed, 4.0); EXPECT_EQ(PVALUES->internalSpeed, 4.0);
} }
EXPECT(barCfg->pParentAnimation.get(), animationTree.getConfig("default").get()); EXPECT_EQ(barCfg->pParentAnimation.get(), animationTree.getConfig("default").get());
// Overwrite our own values // Overwrite our own values
animationTree.setConfigForNode("bar", 1, 4.2, "test", "qwer"); animationTree.setConfigForNode("bar", 1, 4.2, "test", "qwer");
{ {
const auto PVALUES = barCfg->pValues.lock(); const auto PVALUES = barCfg->pValues.lock();
EXPECT(PVALUES->internalEnabled, 1); EXPECT_EQ(PVALUES->internalEnabled, 1);
EXPECT(PVALUES->internalBezier, "test"); EXPECT_EQ(PVALUES->internalBezier, "test");
EXPECT(PVALUES->internalStyle, "qwer"); EXPECT_EQ(PVALUES->internalStyle, "qwer");
EXPECT(PVALUES->internalSpeed, 4.2f); EXPECT_EQ(PVALUES->internalSpeed, 4.2f);
} }
// Now overwrite the parent // Now overwrite the parent
@ -177,17 +177,17 @@ int config() {
{ {
// Expecting no change // Expecting no change
const auto PVALUES = barCfg->pValues.lock(); const auto PVALUES = barCfg->pValues.lock();
EXPECT(PVALUES->internalEnabled, 1); EXPECT_EQ(PVALUES->internalEnabled, 1);
EXPECT(PVALUES->internalBezier, "test"); EXPECT_EQ(PVALUES->internalBezier, "test");
EXPECT(PVALUES->internalStyle, "qwer"); EXPECT_EQ(PVALUES->internalStyle, "qwer");
EXPECT(PVALUES->internalSpeed, 4.2f); EXPECT_EQ(PVALUES->internalSpeed, 4.2f);
} }
return ret; return ret;
} }
int main(int argc, char** argv, char** envp) { TEST(Animation, animation) {
int ret = config(); config();
animationTree.createNode("global"); animationTree.createNode("global");
animationTree.createNode("internal"); animationTree.createNode("internal");
@ -197,8 +197,8 @@ int main(int argc, char** argv, char** envp) {
Subject s(0, 0); Subject s(0, 0);
EXPECT(s.m_iA->value(), 0); EXPECT_EQ(s.m_iA->value(), 0);
EXPECT(s.m_iB->value(), 0); EXPECT_EQ(s.m_iB->value(), 0);
// Test destruction of a CAnimatedVariable // Test destruction of a CAnimatedVariable
{ {
@ -209,22 +209,22 @@ int main(int argc, char** argv, char** envp) {
// We deliberately do not tick here, to make sure the destructor removes active animated variables // We deliberately do not tick here, to make sure the destructor removes active animated variables
} }
EXPECT(pAnimationManager->shouldTickForNext(), false); EXPECT_EQ(pAnimationManager->shouldTickForNext(), false);
EXPECT(s.m_iC->value().done, false); EXPECT_EQ(s.m_iC->value().done, false);
*s.m_iA = 10; *s.m_iA = 10;
*s.m_iB = 100; *s.m_iB = 100;
*s.m_iC = SomeTestType(true); *s.m_iC = SomeTestType(true);
EXPECT(s.m_iC->value().done, false); EXPECT_EQ(s.m_iC->value().done, false);
while (pAnimationManager->shouldTickForNext()) { while (pAnimationManager->shouldTickForNext()) {
pAnimationManager->tick(); pAnimationManager->tick();
} }
EXPECT(s.m_iA->value(), 10); EXPECT_EQ(s.m_iA->value(), 10);
EXPECT(s.m_iB->value(), 100); EXPECT_EQ(s.m_iB->value(), 100);
EXPECT(s.m_iC->value().done, true); EXPECT_EQ(s.m_iC->value().done, true);
s.m_iA->setValue(0); s.m_iA->setValue(0);
s.m_iB->setValue(0); s.m_iB->setValue(0);
@ -233,30 +233,30 @@ int main(int argc, char** argv, char** envp) {
pAnimationManager->tick(); pAnimationManager->tick();
} }
EXPECT(s.m_iA->value(), 10); EXPECT_EQ(s.m_iA->value(), 10);
EXPECT(s.m_iB->value(), 100); EXPECT_EQ(s.m_iB->value(), 100);
// Test config stuff // Test config stuff
EXPECT(s.m_iA->getBezierName(), "default"); EXPECT_EQ(s.m_iA->getBezierName(), "default");
EXPECT(s.m_iA->getStyle(), "asdf"); EXPECT_EQ(s.m_iA->getStyle(), "asdf");
EXPECT(s.m_iA->enabled(), true); EXPECT_EQ(s.m_iA->enabled(), true);
animationTree.getConfig("global")->internalEnabled = 0; animationTree.getConfig("global")->internalEnabled = 0;
EXPECT(s.m_iA->enabled(), false); EXPECT_EQ(s.m_iA->enabled(), false);
*s.m_iA = 50; *s.m_iA = 50;
pAnimationManager->tick(); // Expecting a warp pAnimationManager->tick(); // Expecting a warp
EXPECT(s.m_iA->value(), 50); EXPECT_EQ(s.m_iA->value(), 50);
// Test missing pValues // Test missing pValues
animationTree.getConfig("global")->internalEnabled = 0; animationTree.getConfig("global")->internalEnabled = 0;
animationTree.getConfig("default")->pValues.reset(); animationTree.getConfig("default")->pValues.reset();
EXPECT(s.m_iA->enabled(), false); EXPECT_EQ(s.m_iA->enabled(), false);
EXPECT(s.m_iA->getBezierName(), "default"); EXPECT_EQ(s.m_iA->getBezierName(), "default");
EXPECT(s.m_iA->getStyle(), ""); EXPECT_EQ(s.m_iA->getStyle(), "");
EXPECT(s.m_iA->getPercent(), 1.f); EXPECT_EQ(s.m_iA->getPercent(), 1.f);
// Reset // Reset
animationTree.setConfigForNode("default", 1, 1, "default"); animationTree.setConfigForNode("default", 1, 1, "default");
@ -273,18 +273,18 @@ int main(int argc, char** argv, char** envp) {
s.m_iA->setValueAndWarp(42); s.m_iA->setValueAndWarp(42);
EXPECT(beginCallbackRan, 0); EXPECT_EQ(beginCallbackRan, 0);
EXPECT(updateCallbackRan, 1); EXPECT_EQ(updateCallbackRan, 1);
EXPECT(endCallbackRan, 2); // first called when setting the callback, then when warping. EXPECT_EQ(endCallbackRan, 2); // first called when setting the callback, then when warping.
*s.m_iA = 1337; *s.m_iA = 1337;
while (pAnimationManager->shouldTickForNext()) { while (pAnimationManager->shouldTickForNext()) {
pAnimationManager->tick(); pAnimationManager->tick();
} }
EXPECT(beginCallbackRan, 1); EXPECT_EQ(beginCallbackRan, 1);
EXPECT(updateCallbackRan > 2, true); EXPECT_EQ(updateCallbackRan > 2, true);
EXPECT(endCallbackRan, 3); EXPECT_EQ(endCallbackRan, 3);
std::vector<PANIMVAR<int>> vars; std::vector<PANIMVAR<int>> vars;
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
@ -296,7 +296,7 @@ int main(int argc, char** argv, char** envp) {
// test adding / removing vars during a tick // test adding / removing vars during a tick
s.m_iA->resetAllCallbacks(); s.m_iA->resetAllCallbacks();
s.m_iA->setUpdateCallback([&vars](WP<CBaseAnimatedVariable> v) { s.m_iA->setUpdateCallback([&vars](WP<CBaseAnimatedVariable> v) {
if (v.lock() != vars.back()) if (v.get() != vars.back().get())
vars.back()->warp(); vars.back()->warp();
}); });
s.m_iA->setCallbackOnEnd([&s, &vars](auto) { s.m_iA->setCallbackOnEnd([&s, &vars](auto) {
@ -311,44 +311,44 @@ int main(int argc, char** argv, char** envp) {
pAnimationManager->tick(); pAnimationManager->tick();
} }
EXPECT(s.m_iA->value(), 1000000); EXPECT_EQ(s.m_iA->value(), 1000000);
// all vars should be set to 1337 // all vars should be set to 1337
EXPECT(std::find_if(vars.begin(), vars.end(), [](const auto& v) { return v->value() != 1337; }) == vars.end(), true); EXPECT_EQ(std::find_if(vars.begin(), vars.end(), [](const auto& v) { return v->value() != 1337; }) == vars.end(), true);
// test one-time callbacks // test one-time callbacks
s.m_iA->resetAllCallbacks(); s.m_iA->resetAllCallbacks();
s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, true); s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, true);
EXPECT(endCallbackRan, 4); EXPECT_EQ(endCallbackRan, 4);
s.m_iA->setValueAndWarp(10); s.m_iA->setValueAndWarp(10);
EXPECT(endCallbackRan, 4); EXPECT_EQ(endCallbackRan, 4);
EXPECT(s.m_iA->value(), 10); EXPECT_EQ(s.m_iA->value(), 10);
// test warp // test warp
*s.m_iA = 3; *s.m_iA = 3;
s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, false); s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, false);
s.m_iA->warp(false); s.m_iA->warp(false);
EXPECT(endCallbackRan, 4); EXPECT_EQ(endCallbackRan, 4);
*s.m_iA = 4; *s.m_iA = 4;
s.m_iA->warp(true); s.m_iA->warp(true);
EXPECT(endCallbackRan, 5); EXPECT_EQ(endCallbackRan, 5);
// test getCurveValue // test getCurveValue
*s.m_iA = 0; *s.m_iA = 0;
EXPECT(s.m_iA->getCurveValue(), 0.f); EXPECT_EQ(s.m_iA->getCurveValue(), 0.f);
s.m_iA->warp(); s.m_iA->warp();
EXPECT(s.m_iA->getCurveValue(), 1.f); EXPECT_EQ(s.m_iA->getCurveValue(), 1.f);
EXPECT(endCallbackRan, 6); EXPECT_EQ(endCallbackRan, 6);
// test end callback readding the var // test end callback readding the var
*s.m_iA = 5; *s.m_iA = 5;
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) { s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) {
endCallbackRan++; endCallbackRan++;
const auto PAV = dc<CAnimatedVariable<int>*>(v.lock().get()); const auto PAV = dc<CAnimatedVariable<int>*>(v.get());
*PAV = 10; *PAV = 10;
PAV->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) { endCallbackRan++; }); PAV->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) { endCallbackRan++; });
@ -358,22 +358,22 @@ int main(int argc, char** argv, char** envp) {
pAnimationManager->tick(); pAnimationManager->tick();
} }
EXPECT(endCallbackRan, 8); EXPECT_EQ(endCallbackRan, 8);
EXPECT(s.m_iA->value(), 10); EXPECT_EQ(s.m_iA->value(), 10);
// Test duplicate active anim vars are not allowed // Test duplicate active anim vars are not allowed
{ {
EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
PANIMVAR<int> a; PANIMVAR<int> a;
pAnimationManager->createAnimation(1, a, "default"); pAnimationManager->createAnimation(1, a, "default");
EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
*a = 10; *a = 10;
EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 1); EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
*a = 20; *a = 20;
EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 1); EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
a->warp(); a->warp();
EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
EXPECT(a->value(), 20); EXPECT_EQ(a->value(), 20);
} }
// Test no crash when animation manager gets destroyed // Test no crash when animation manager gets destroyed
@ -382,16 +382,14 @@ int main(int argc, char** argv, char** envp) {
pAnimationManager->createAnimation(1, a, "default"); pAnimationManager->createAnimation(1, a, "default");
*a = 10; *a = 10;
pAnimationManager.reset(); pAnimationManager.reset();
EXPECT(a->isAnimationManagerDead(), true); EXPECT_EQ(a->isAnimationManagerDead(), true);
a->setValueAndWarp(11); a->setValueAndWarp(11);
EXPECT(a->value(), 11); EXPECT_EQ(a->value(), 11);
*a = 12; *a = 12;
a->warp(); a->warp();
EXPECT(a->value(), 12); EXPECT_EQ(a->value(), 12);
*a = 13; *a = 13;
} // a gets destroyed } // a gets destroyed
EXPECT(pAnimationManager.get(), nullptr); EXPECT_EQ(pAnimationManager.get(), nullptr);
return ret;
} }

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.");
}

View file

@ -1,49 +0,0 @@
#include <hyprutils/os/FileDescriptor.hpp>
#include "shared.hpp"
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
using namespace Hyprutils::OS;
int main(int argc, char** argv, char** envp) {
std::string name = "/test_filedescriptors";
CFileDescriptor fd(shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600));
int ret = 0;
EXPECT(fd.isValid(), true);
EXPECT(fd.isReadable(), true);
int flags = fd.getFlags();
EXPECT(fd.getFlags(), FD_CLOEXEC);
flags &= ~FD_CLOEXEC;
fd.setFlags(flags);
EXPECT(fd.getFlags(), !FD_CLOEXEC);
CFileDescriptor fd2 = fd.duplicate();
EXPECT(fd.isValid(), true);
EXPECT(fd.isReadable(), true);
EXPECT(fd2.isValid(), true);
EXPECT(fd2.isReadable(), true);
CFileDescriptor fd3(fd2.take());
EXPECT(fd.isValid(), true);
EXPECT(fd.isReadable(), true);
EXPECT(fd2.isValid(), false);
EXPECT(fd2.isReadable(), false);
// .duplicate default flags is FD_CLOEXEC
EXPECT(fd3.getFlags(), FD_CLOEXEC);
fd.reset();
fd2.reset();
fd3.reset();
EXPECT(fd.isReadable(), false);
EXPECT(fd2.isReadable(), false);
EXPECT(fd3.isReadable(), false);
shm_unlink(name.c_str());
return ret;
}

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");
}

View file

@ -1,122 +0,0 @@
#include <hyprutils/math/Region.hpp>
#include <hyprutils/math/Mat3x3.hpp>
#include "shared.hpp"
using namespace Hyprutils::Math;
int main(int argc, char** argv, char** envp) {
CRegion rg = {0, 0, 100, 100};
rg.add(CBox{{}, {20, 200}});
int ret = 0;
EXPECT(rg.getExtents().height, 200);
EXPECT(rg.getExtents().width, 100);
rg.intersect(CBox{10, 10, 300, 300});
EXPECT(rg.getExtents().width, 90);
EXPECT(rg.getExtents().height, 190);
/*Box.cpp test cases*/
// Test default constructor and accessors
{
CBox box1;
EXPECT(box1.x, 0);
EXPECT(box1.y, 0);
EXPECT(box1.width, 0);
EXPECT(box1.height, 0);
// Test parameterized constructor and accessors
CBox box2(10, 20, 30, 40);
EXPECT(box2.x, 10);
EXPECT(box2.y, 20);
EXPECT(box2.width, 30);
EXPECT(box2.height, 40);
// Test setters and getters
box2.translate(Vector2D(5, -5));
EXPECT_VECTOR2D(box2.pos(), Vector2D(15, 15));
}
//Test Scaling and Transformation
{
CBox box(10, 10, 20, 30);
// Test scaling
box.scale(2.0);
EXPECT_VECTOR2D(box.size(), Vector2D(40, 60));
EXPECT_VECTOR2D(box.pos(), Vector2D(20, 20));
// Test scaling from center
box.scaleFromCenter(0.5);
EXPECT_VECTOR2D(box.size(), Vector2D(20, 30));
EXPECT_VECTOR2D(box.pos(), Vector2D(30, 35));
// Test transformation
box.transform(HYPRUTILS_TRANSFORM_90, 100, 200);
EXPECT_VECTOR2D(box.pos(), Vector2D(135, 30));
EXPECT_VECTOR2D(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_VECTOR2D(intersection.pos(), Vector2D(50, 50));
EXPECT_VECTOR2D(intersection.size(), Vector2D(50, 50));
SBoxExtents extents = box1.extentsFrom(box2);
EXPECT_VECTOR2D(extents.topLeft, Vector2D(50, 50));
EXPECT_VECTOR2D(extents.bottomRight, Vector2D(-50, -50));
}
// Test Boundary Conditions and Special Cases
{
CBox box(0, 0, 50, 50);
EXPECT(box.empty(), false);
EXPECT(box.containsPoint(Vector2D(25, 25)), true);
EXPECT(box.containsPoint(Vector2D(60, 60)), false);
EXPECT(box.overlaps(CBox(25, 25, 50, 50)), true);
EXPECT(box.inside(CBox(0, 0, 100, 100)), false);
}
// Test matrices
{
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(std::abs(expected.getMatrix().at(0) - matrixBox.getMatrix().at(0)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(1) - matrixBox.getMatrix().at(1)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(2) - matrixBox.getMatrix().at(2)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(3) - matrixBox.getMatrix().at(3)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(4) - matrixBox.getMatrix().at(4)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(5) - matrixBox.getMatrix().at(5)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(6) - matrixBox.getMatrix().at(6)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(7) - matrixBox.getMatrix().at(7)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(8) - matrixBox.getMatrix().at(8)) < 0.1, true);
}
{
Vector2D original(30, 40);
Vector2D monitorSize(100, 200);
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_NORMAL, monitorSize), Vector2D(30, 40 ));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_90, monitorSize), Vector2D(40, 200 - 30));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_180, monitorSize), Vector2D(100 - 30, 200 - 40));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_270, monitorSize), Vector2D(100 - 40, 30 ));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_FLIPPED, monitorSize), Vector2D(100 - 30, 40 ));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_90, monitorSize), Vector2D(40, 30 ));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_180, monitorSize), Vector2D(30, 200 - 40));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_270, monitorSize), Vector2D(100 - 40, 200 - 30));
}
return ret;
}

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,154 +0,0 @@
#include <hyprutils/memory/UniquePtr.hpp>
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/Atomic.hpp>
#include "shared.hpp"
#include <chrono>
#include <print>
#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 int testAtomicImpl() {
int ret = 0;
{
// 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(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(shared.strongRef(), 0);
EXPECT(weak.valid(), false);
auto shared2 = weak.lock();
EXPECT(shared, false);
EXPECT(shared2.get(), nullptr);
EXPECT(shared.strongRef(), 0);
EXPECT(weak.valid(), false);
EXPECT(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;
}
return ret;
}
int main(int argc, char** argv, char** envp) {
SP<int> intPtr = makeShared<int>(10);
SP<int> intPtr2 = makeShared<int>(-1337);
UP<int> intUnique = makeUnique<int>(420);
int ret = 0;
EXPECT(*intPtr, 10);
EXPECT(intPtr.strongRef(), 1);
EXPECT(*intUnique, 420);
WP<int> weak = intPtr;
WP<int> weakUnique = intUnique;
EXPECT(*intPtr, 10);
EXPECT(intPtr.strongRef(), 1);
EXPECT(*weak, 10);
EXPECT(weak.expired(), false);
EXPECT(*weakUnique, 420);
EXPECT(weakUnique.expired(), false);
EXPECT(intUnique.impl_->wref(), 1);
SP<int> sharedFromUnique = weakUnique.lock();
EXPECT(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(weak.impl_->ref(), 0);
EXPECT(weakUnique.impl_->ref(), 0);
EXPECT(weakUnique.impl_->wref(), 1);
EXPECT(intPtr2.strongRef(), 3);
EXPECT(weak.expired(), true);
EXPECT(weakUnique.expired(), true);
auto intPtr2AsUint = reinterpretPointerCast<unsigned int>(intPtr2);
EXPECT(intPtr2.strongRef(), 4);
EXPECT(intPtr2AsUint.strongRef(), 4);
EXPECT(*intPtr2AsUint > 0, true);
EXPECT(*intPtr2AsUint, (unsigned int)(int)-1337);
*intPtr2AsUint = 10;
EXPECT(*intPtr2AsUint, 10);
EXPECT(*intPtr2, 10);
EXPECT(testAtomicImpl(), 0);
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();
}

View file

@ -1,35 +0,0 @@
#include <csignal>
#include <cerrno>
#include <cstdlib>
#include <unistd.h>
#include <hyprutils/os/Process.hpp>
#include "shared.hpp"
using namespace Hyprutils::OS;
int main(int argc, char** argv, char** envp) {
int ret = 0;
CProcess process("sh", {"-c", "echo \"Hello $WORLD!\""});
process.addEnv("WORLD", "World");
EXPECT(process.runAsync(), true);
EXPECT(process.runSync(), true);
EXPECT(process.stdOut(), std::string{"Hello World!\n"});
EXPECT(process.stdErr(), std::string{""});
EXPECT(process.exitCode(), 0);
CProcess process2("sh", {"-c", "while true; do sleep 1; done;"});
EXPECT(process2.runAsync(), true);
EXPECT(getpgid(process2.pid()) >= 0, true);
kill(process2.pid(), SIGKILL);
CProcess process3("sh", {"-c", "cat /geryueruggbuergheruger/reugiheruygyuerghuryeghyer/eruihgyuerguyerghyuerghuyergerguyer/NON_EXISTENT"});
EXPECT(process3.runSync(), true);
EXPECT(process3.exitCode(), 1);
return ret;
}

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,32 +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"; \
}
#define EXPECT_VECTOR2D(expr, val) \
do { \
const auto& RESULT = expr; \
const auto& EXPECTED = val; \
if (!(std::abs(RESULT.x - EXPECTED.x) < 1e-6 && std::abs(RESULT.y - EXPECTED.y) < 1e-6)) { \
std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected (" << EXPECTED.x << ", " << EXPECTED.y << ") but got (" << RESULT.x << ", " \
<< RESULT.y << ")\n"; \
ret = 1; \
} else { \
std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got (" << RESULT.x << ", " << RESULT.y << ")\n"; \
} \
} while (0)

View file

@ -1,10 +1,11 @@
#include <gtest/gtest.h>
#include <any> #include <any>
#include <hyprutils/signal/Signal.hpp> #include <hyprutils/signal/Signal.hpp>
#include <hyprutils/signal/Listener.hpp>
#include <hyprutils/memory/WeakPtr.hpp> #include <hyprutils/memory/WeakPtr.hpp>
#include <memory> #include <memory>
#include "hyprutils/memory/SharedPtr.hpp"
#include "hyprutils/signal/Listener.hpp"
#include "shared.hpp"
using namespace Hyprutils::Signal; using namespace Hyprutils::Signal;
using namespace Hyprutils::Memory; using namespace Hyprutils::Memory;
@ -13,14 +14,14 @@ using namespace Hyprutils::Memory;
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
// //
void legacy(int& ret) { static void legacy() {
CSignal signal; CSignal signal;
int data = 0; int data = 0;
auto listener = signal.registerListener([&]([[maybe_unused]] std::any d) { data = 1; }); auto listener = signal.registerListener([&]([[maybe_unused]] std::any d) { data = 1; });
signal.emit(); signal.emit();
EXPECT(data, 1); EXPECT_EQ(data, 1);
data = 0; data = 0;
@ -28,19 +29,19 @@ void legacy(int& ret) {
signal.emit(); signal.emit();
EXPECT(data, 0); EXPECT_EQ(data, 0);
} }
void legacyListenerEmit(int& ret) { static void legacyListenerEmit() {
int data = 0; int data = 0;
CSignal signal; CSignal signal;
auto listener = signal.registerListener([&](std::any d) { data = std::any_cast<int>(d); }); auto listener = signal.registerListener([&](std::any d) { data = std::any_cast<int>(d); });
listener->emit(1); // not a typo listener->emit(1); // not a typo
EXPECT(data, 1); EXPECT_EQ(data, 1);
} }
void legacyListeners(int& ret) { static void legacyListeners() {
int data = 0; int data = 0;
CSignalT<> signal0; CSignalT<> signal0;
@ -55,38 +56,38 @@ void legacyListeners(int& ret) {
signal0.emit(); signal0.emit();
signal1.emit(2); signal1.emit(2);
EXPECT(data, 33); EXPECT_EQ(data, 33);
} }
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
// //
void empty(int& ret) { static void empty() {
int data = 0; int data = 0;
CSignalT<> signal; CSignalT<> signal;
auto listener = signal.listen([&] { data = 1; }); auto listener = signal.listen([&] { data = 1; });
signal.emit(); signal.emit();
EXPECT(data, 1); EXPECT_EQ(data, 1);
data = 0; data = 0;
listener.reset(); listener.reset();
signal.emit(); signal.emit();
EXPECT(data, 0); EXPECT_EQ(data, 0);
} }
void typed(int& ret) { static void typed() {
int data = 0; int data = 0;
CSignalT<int> signal; CSignalT<int> signal;
auto listener = signal.listen([&](int newData) { data = newData; }); auto listener = signal.listen([&](int newData) { data = newData; });
signal.emit(1); signal.emit(1);
EXPECT(data, 1); EXPECT_EQ(data, 1);
} }
void ignoreParams(int& ret) { static void ignoreParams() {
int data = 0; int data = 0;
CSignalT<int> signal; CSignalT<int> signal;
@ -95,10 +96,10 @@ void ignoreParams(int& ret) {
signal.listenStatic([&] { data += 1; }); signal.listenStatic([&] { data += 1; });
signal.emit(2); signal.emit(2);
EXPECT(data, 2); EXPECT_EQ(data, 2);
} }
void typedMany(int& ret) { static void typedMany() {
int data1 = 0; int data1 = 0;
int data2 = 0; int data2 = 0;
int data3 = 0; int data3 = 0;
@ -111,12 +112,12 @@ void typedMany(int& ret) {
}); });
signal.emit(1, 2, 3); signal.emit(1, 2, 3);
EXPECT(data1, 1); EXPECT_EQ(data1, 1);
EXPECT(data2, 2); EXPECT_EQ(data2, 2);
EXPECT(data3, 3); EXPECT_EQ(data3, 3);
} }
void ref(int& ret) { static void ref() {
int count = 0; int count = 0;
int data = 0; int data = 0;
@ -130,11 +131,11 @@ void ref(int& ret) {
auto l4 = constSignal.listen([&](int v) { count += v; }); auto l4 = constSignal.listen([&](int v) { count += v; });
constSignal.emit(data); constSignal.emit(data);
EXPECT(data, 1); EXPECT_EQ(data, 1);
EXPECT(count, 3); EXPECT_EQ(count, 3);
} }
void refMany(int& ret) { static void refMany() {
int count = 0; int count = 0;
int data1 = 0; int data1 = 0;
int data2 = 10; int data2 = 10;
@ -144,11 +145,11 @@ void refMany(int& ret) {
auto l2 = signal.listen([&](int v1, int v2) { count += v1 + v2; }); auto l2 = signal.listen([&](int v1, int v2) { count += v1 + v2; });
signal.emit(data1, data2); signal.emit(data1, data2);
EXPECT(data1, 1); EXPECT_EQ(data1, 1);
EXPECT(count, 11); EXPECT_EQ(count, 11);
} }
void autoRefTypes(int& ret) { static void autoRefTypes() {
class CCopyCounter { class CCopyCounter {
public: public:
CCopyCounter(int& createCount, int& destroyCount) : createCount(createCount), destroyCount(destroyCount) { CCopyCounter(int& createCount, int& destroyCount) : createCount(createCount), destroyCount(destroyCount) {
@ -174,11 +175,11 @@ void autoRefTypes(int& ret) {
auto listener = signal.listen([](const CCopyCounter& counter) {}); auto listener = signal.listen([](const CCopyCounter& counter) {});
signal.emit(CCopyCounter(createCount, destroyCount)); signal.emit(CCopyCounter(createCount, destroyCount));
EXPECT(createCount, 1); EXPECT_EQ(createCount, 1);
EXPECT(destroyCount, 1); EXPECT_EQ(destroyCount, 1);
} }
void forward(int& ret) { static void forward() {
int count = 0; int count = 0;
CSignalT<int> sig; CSignalT<int> sig;
@ -193,10 +194,10 @@ void forward(int& ret) {
sig.emit(2); sig.emit(2);
EXPECT(count, 3); EXPECT_EQ(count, 3);
} }
void listenerAdded(int& ret) { static void listenerAdded() {
int count = 0; int count = 0;
CSignalT<> signal; CSignalT<> signal;
@ -210,13 +211,13 @@ void listenerAdded(int& ret) {
}); });
signal.emit(); signal.emit();
EXPECT(count, 1); // second should NOT be invoked as it was registed during emit EXPECT_EQ(count, 1); // second should NOT be invoked as it was registed during emit
signal.emit(); signal.emit();
EXPECT(count, 3); // second should be invoked EXPECT_EQ(count, 3); // second should be invoked
} }
void lastListenerSwapped(int& ret) { static void lastListenerSwapped() {
int count = 0; int count = 0;
CSignalT<> signal; CSignalT<> signal;
@ -233,13 +234,13 @@ void lastListenerSwapped(int& ret) {
removedListener = signal.listen([&] { count += 1; }); removedListener = signal.listen([&] { count += 1; });
signal.emit(); signal.emit();
EXPECT(count, 0); // neither the removed nor added listeners should fire EXPECT_EQ(count, 0); // neither the removed nor added listeners should fire
signal.emit(); signal.emit();
EXPECT(count, 2); // only the new listener should fire EXPECT_EQ(count, 2); // only the new listener should fire
} }
void signalDestroyed(int& ret) { static void signalDestroyed() {
int count = 0; int count = 0;
auto signal = std::make_unique<CSignalT<>>(); auto signal = std::make_unique<CSignalT<>>();
@ -254,11 +255,11 @@ void signalDestroyed(int& ret) {
auto postListener = signal->listen([&] { count += 1; }); auto postListener = signal->listen([&] { count += 1; });
signal->emit(); signal->emit();
EXPECT(count, 2); // all listeners should fire regardless of signal deletion EXPECT_EQ(count, 2); // all listeners should fire regardless of signal deletion
} }
// purely an asan test // purely an asan test
void signalDestroyedBeforeListener() { static void signalDestroyedBeforeListener() {
CHyprSignalListener listener1; CHyprSignalListener listener1;
CHyprSignalListener listener2; CHyprSignalListener listener2;
@ -268,7 +269,7 @@ void signalDestroyedBeforeListener() {
listener2 = signal.listen([] {}); listener2 = signal.listen([] {});
} }
void signalDestroyedWithAddedListener(int& ret) { static void signalDestroyedWithAddedListener() {
int count = 0; int count = 0;
auto signal = std::make_unique<CSignalT<>>(); auto signal = std::make_unique<CSignalT<>>();
@ -280,10 +281,10 @@ void signalDestroyedWithAddedListener(int& ret) {
}); });
signal->emit(); signal->emit();
EXPECT(count, 0); EXPECT_EQ(count, 0);
} }
void signalDestroyedWithRemovedAndAddedListener(int& ret) { static void signalDestroyedWithRemovedAndAddedListener() {
int count = 0; int count = 0;
auto signal = std::make_unique<CSignalT<>>(); auto signal = std::make_unique<CSignalT<>>();
@ -299,20 +300,20 @@ void signalDestroyedWithRemovedAndAddedListener(int& ret) {
removed = signal->listen([&] { count += 1; }); removed = signal->listen([&] { count += 1; });
signal->emit(); signal->emit();
EXPECT(count, 0); EXPECT_EQ(count, 0);
} }
void staticListener(int& ret) { static void staticListener() {
int data = 0; int data = 0;
CSignalT<int> signal; CSignalT<int> signal;
signal.listenStatic([&](int newData) { data = newData; }); signal.listenStatic([&](int newData) { data = newData; });
signal.emit(1); signal.emit(1);
EXPECT(data, 1); EXPECT_EQ(data, 1);
} }
void staticListenerDestroy(int& ret) { static void staticListenerDestroy() {
int count = 0; int count = 0;
auto signal = makeShared<CSignalT<>>(); auto signal = makeShared<CSignalT<>>();
@ -328,11 +329,11 @@ void staticListenerDestroy(int& ret) {
signal->listenStatic([&] { count += 1; }); signal->listenStatic([&] { count += 1; });
signal->emit(); signal->emit();
EXPECT(count, 2); EXPECT_EQ(count, 2);
} }
// purely an asan test // purely an asan test
void listenerDestroysSelf() { static void listenerDestroysSelf() {
CSignalT<> signal; CSignalT<> signal;
CHyprSignalListener listener; CHyprSignalListener listener;
@ -343,28 +344,26 @@ void listenerDestroysSelf() {
signal.emit(); signal.emit();
} }
int main(int argc, char** argv, char** envp) { TEST(Signal, signal) {
int ret = 0; legacy();
legacy(ret); legacyListenerEmit();
legacyListenerEmit(ret); legacyListeners();
legacyListeners(ret); empty();
empty(ret); typed();
typed(ret); ignoreParams();
ignoreParams(ret); typedMany();
typedMany(ret); ref();
ref(ret); refMany();
refMany(ret); autoRefTypes();
autoRefTypes(ret); forward();
forward(ret); listenerAdded();
listenerAdded(ret); lastListenerSwapped();
lastListenerSwapped(ret); signalDestroyed();
signalDestroyed(ret);
signalDestroyedBeforeListener(); signalDestroyedBeforeListener();
signalDestroyedWithAddedListener(ret); signalDestroyedWithAddedListener();
signalDestroyedWithRemovedAndAddedListener(ret); signalDestroyedWithRemovedAndAddedListener();
staticListener(ret); staticListener();
staticListenerDestroy(ret); staticListenerDestroy();
signalDestroyed(ret); signalDestroyed();
listenerDestroysSelf(); listenerDestroysSelf();
return ret;
} }

View file

@ -1,51 +0,0 @@
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/VarList.hpp>
#include <hyprutils/string/ConstVarList.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("-"), 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);
EXPECT(isNumber("-1.0", true), true);
EXPECT(isNumber("-1..0", true), false);
EXPECT(isNumber("-10.0000000001", true), true);
CVarList list("hello world!", 0, 's', true);
EXPECT(list[0], "hello");
EXPECT(list[1], "world!");
CConstVarList listConst("hello world!", 0, 's', true);
EXPECT(listConst[0], "hello");
EXPECT(listConst[1], "world!");
std::string hello = "hello world!";
replaceInString(hello, "hello", "hi");
EXPECT(hello, "hi 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");
}