diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..f0dfdf1 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,101 @@ +WarningsAsErrors: '*' +HeaderFilterRegex: '.*\.hpp' +FormatStyle: file +Checks: > + -*, + bugprone-*, + -bugprone-easily-swappable-parameters, + -bugprone-forward-declararion-namespace, + -bugprone-forward-declararion-namespace, + -bugprone-macro-parentheses, + -bugprone-narrowing-conversions, + -bugprone-branch-clone, + -bugprone-assignment-in-if-condition, + concurrency-*, + -concurrency-mt-unsafe, + cppcoreguidelines-*, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-avoid-const-or-ref-data-members, + -cppcoreguidelines-non-private-member-variables-in-classes, + -cppcoreguidelines-avoid-goto, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-avoid-do-while, + -cppcoreguidelines-avoid-non-const-global-variables, + -cppcoreguidelines-special-member-functions, + -cppcoreguidelines-explicit-virtual-functions, + -cppcoreguidelines-avoid-c-arrays, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-narrowing-conversions, + -cppcoreguidelines-pro-type-union-access, + -cppcoreguidelines-pro-type-member-init, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-macro-to-enum, + -cppcoreguidelines-init-variables, + -cppcoreguidelines-pro-type-cstyle-cast, + -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-pro-type-reinterpret-cast, + google-global-names-in-headers, + -google-readability-casting, + google-runtime-operator, + misc-*, + -misc-unused-parameters, + -misc-no-recursion, + -misc-non-private-member-variables-in-classes, + -misc-include-cleaner, + -misc-use-anonymous-namespace, + -misc-const-correctness, + modernize-*, + -modernize-return-braced-init-list, + -modernize-use-trailing-return-type, + -modernize-use-using, + -modernize-use-override, + -modernize-avoid-c-arrays, + -modernize-macro-to-enum, + -modernize-loop-convert, + -modernize-use-nodiscard, + -modernize-pass-by-value, + -modernize-use-auto, + performance-*, + -performance-avoid-endl, + -performance-unnecessary-value-param, + portability-std-allocator-const, + readability-*, + -readability-function-cognitive-complexity, + -readability-function-size, + -readability-identifier-length, + -readability-magic-numbers, + -readability-uppercase-literal-suffix, + -readability-braces-around-statements, + -readability-redundant-access-specifiers, + -readability-else-after-return, + -readability-container-data-pointer, + -readability-implicit-bool-conversion, + -readability-avoid-nested-conditional-operator, + -readability-redundant-member-init, + -readability-redundant-string-init, + -readability-avoid-const-params-in-decls, + -readability-named-parameter, + -readability-convert-member-functions-to-static, + -readability-qualified-auto, + -readability-make-member-function-const, + -readability-isolate-declaration, + -readability-inconsistent-declaration-parameter-name, + -clang-diagnostic-error, + +CheckOptions: + performance-for-range-copy.WarnOnAllAutoCopies: true + performance-inefficient-string-concatenation.StrictMode: true + readability-braces-around-statements.ShortStatementLines: 0 + readability-identifier-naming.ClassCase: CamelCase + readability-identifier-naming.ClassIgnoredRegexp: I.* + readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!? + readability-identifier-naming.EnumCase: CamelCase + readability-identifier-naming.EnumPrefix: e + readability-identifier-naming.EnumConstantCase: UPPER_CASE + readability-identifier-naming.FunctionCase: camelBack + readability-identifier-naming.NamespaceCase: CamelCase + readability-identifier-naming.NamespacePrefix: N + readability-identifier-naming.StructPrefix: S + readability-identifier-naming.StructCase: CamelCase diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 0f0b75d..c5b1b46 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -14,7 +14,6 @@ jobs: - uses: actions/checkout@v3 - uses: cachix/install-nix-action@v26 - - uses: DeterminateSystems/magic-nix-cache-action@main # not needed (yet) # - uses: cachix/cachix-action@v12 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9089c54..ca2591c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,16 @@ set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}) configure_file(hyprutils.pc.in hyprutils.pc @ONLY) set(CMAKE_CXX_STANDARD 23) +add_compile_options( + -Wall + -Wextra + -Wpedantic + -Wno-unused-parameter + -Wno-unused-value + -Wno-missing-field-initializers + -Wno-narrowing + -Wno-pointer-arith) +set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) message(STATUS "Configuring hyprutils in Debug") @@ -40,7 +50,7 @@ target_include_directories( PUBLIC "./include" PRIVATE "./src") set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION} - SOVERSION 1) + SOVERSION 4) target_link_libraries(hyprutils PkgConfig::deps) # tests @@ -78,6 +88,30 @@ add_test( 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 install(TARGETS hyprutils) install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/README.md b/README.md index b4c6335..3bb4851 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,6 @@ Hyprutils depends on the ABI stability of the stdlib implementation of your comp git clone https://github.com/hyprwm/hyprutils.git cd hyprutils/ cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build -cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF` +cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` sudo cmake --install build ``` diff --git a/VERSION b/VERSION index 7179039..4b9fcbe 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.3 +0.5.1 diff --git a/flake.lock b/flake.lock index aeb7fc2..184825a 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1721138476, - "narHash": "sha256-+W5eZOhhemLQxelojLxETfbFbc19NWawsXBlapYpqIA=", + "lastModified": 1734119587, + "narHash": "sha256-AKU6qqskl0yf2+JdRdD0cfxX4b9x3KKV5RqA6wijmPM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ad0b5eed1b6031efaed382844806550c3dcb4206", + "rev": "3566ab7246670a43abd2ffa913cc62dad9cdf7d5", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 6550f66..833f77f 100644 --- a/flake.nix +++ b/flake.nix @@ -30,7 +30,7 @@ default = self.overlays.hyprutils; hyprutils = final: prev: { hyprutils = final.callPackage ./nix/default.nix { - stdenv = final.gcc13Stdenv; + stdenv = final.gcc14Stdenv; version = version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); }; hyprutils-with-tests = final.hyprutils.override {doCheck = true;}; diff --git a/include/hyprutils/animation/AnimatedVariable.hpp b/include/hyprutils/animation/AnimatedVariable.hpp new file mode 100644 index 0000000..113793f --- /dev/null +++ b/include/hyprutils/animation/AnimatedVariable.hpp @@ -0,0 +1,226 @@ +#pragma once + +#include "AnimationConfig.hpp" +#include "../memory/WeakPtr.hpp" +#include "../memory/SharedPtr.hpp" +#include "../signal/Signal.hpp" +#include "AnimationManager.hpp" + +#include +#include + +namespace Hyprutils { + namespace Animation { + + /* A base class for animated variables. */ + class CBaseAnimatedVariable { + public: + using CallbackFun = std::function thisptr)>; + + CBaseAnimatedVariable() { + ; // m_bDummy = true; + }; + + void create(CAnimationManager*, int, Memory::CSharedPointer); + void connectToActive(); + void disconnectFromActive(); + + /* Needs to call disconnectFromActive to remove `m_pSelf` from the active animation list */ + virtual ~CBaseAnimatedVariable() { + disconnectFromActive(); + }; + + virtual void warp(bool endCallback = true, bool forceDisconnect = true) = 0; + + CBaseAnimatedVariable(const CBaseAnimatedVariable&) = delete; + CBaseAnimatedVariable(CBaseAnimatedVariable&&) = delete; + CBaseAnimatedVariable& operator=(const CBaseAnimatedVariable&) = delete; + CBaseAnimatedVariable& operator=(CBaseAnimatedVariable&&) = delete; + + // + void setConfig(Memory::CSharedPointer pConfig) { + m_pConfig = pConfig; + } + + Memory::CWeakPointer getConfig() const { + return m_pConfig; + } + + bool enabled() const; + const std::string& getBezierName() const; + const std::string& getStyle() const; + + /* returns the spent (completion) % */ + float getPercent() const; + + /* returns the current curve value. */ + float getCurveValue() const; + + /* checks if an animation is in progress */ + bool isBeingAnimated() const { + return m_bIsBeingAnimated; + } + + /* checks m_bDummy and m_pAnimationManager */ + bool ok() const; + + /* calls the update callback */ + void onUpdate(); + + /* sets a function to be ran when an animation ended. + if "remove" is set to true, it will remove the callback when ran. */ + void setCallbackOnEnd(CallbackFun func, bool remove = true); + + /* sets a function to be ran when an animation is started. + if "remove" is set to true, it will remove the callback when ran. */ + void setCallbackOnBegin(CallbackFun func, bool remove = true); + + /* sets the update callback, called every time the value is animated and a step is done + Warning: calling unregisterVar/registerVar in this handler will cause UB */ + void setUpdateCallback(CallbackFun func); + + /* resets all callbacks. Does not call any. */ + void resetAllCallbacks(); + + void onAnimationEnd(); + void onAnimationBegin(); + + /* returns whether the parent CAnimationManager is dead */ + bool isAnimationManagerDead() const; + + int m_Type = -1; + + protected: + friend class CAnimationManager; + + CAnimationManager* m_pAnimationManager = nullptr; + + bool m_bIsConnectedToActive = false; + bool m_bIsBeingAnimated = false; + + Memory::CWeakPointer m_pSelf; + + Memory::CWeakPointer m_pSignals; + + private: + Memory::CWeakPointer m_pConfig; + + std::chrono::steady_clock::time_point animationBegin; + + bool m_bDummy = true; + + bool m_bRemoveEndAfterRan = true; + bool m_bRemoveBeginAfterRan = true; + + CallbackFun m_fEndCallback; + CallbackFun m_fBeginCallback; + CallbackFun m_fUpdateCallback; + }; + + /* This concept represents the minimum requirement for a type to be used with CGenericAnimatedVariable */ + template + concept AnimatedType = requires(ValueImpl val) { + requires std::is_copy_constructible_v; + { val == val } -> std::same_as; // requires operator== + { val = val }; // requires operator= + }; + + /* + A generic class for variables. + VarType is the type of the variable to be animated. + AnimationContext is there to attach additional data to the animation. + In Hyprland that struct would contain a reference to window, workspace or layer for example. + */ + template + class CGenericAnimatedVariable : public CBaseAnimatedVariable { + public: + CGenericAnimatedVariable() = default; + + void create(const int typeInfo, CAnimationManager* pAnimationManager, Memory::CSharedPointer> pSelf, + const VarType& initialValue) { + m_Begun = initialValue; + m_Value = initialValue; + m_Goal = initialValue; + + CBaseAnimatedVariable::create(pAnimationManager, typeInfo, pSelf); + } + + CGenericAnimatedVariable(const CGenericAnimatedVariable&) = delete; + CGenericAnimatedVariable(CGenericAnimatedVariable&&) = delete; + CGenericAnimatedVariable& operator=(const CGenericAnimatedVariable&) = delete; + CGenericAnimatedVariable& operator=(CGenericAnimatedVariable&&) = delete; + + virtual void warp(bool endCallback = true, bool forceDisconnect = true) { + if (!m_bIsBeingAnimated) + return; + + m_Value = m_Goal; + + onUpdate(); + + m_bIsBeingAnimated = false; + + if (forceDisconnect) + disconnectFromActive(); + + if (endCallback) + onAnimationEnd(); + } + + const VarType& value() const { + return m_Value; + } + + /* used to update the value each tick via the AnimationManager */ + VarType& value() { + return m_Value; + } + + const VarType& goal() const { + return m_Goal; + } + + const VarType& begun() const { + return m_Begun; + } + + CGenericAnimatedVariable& operator=(const VarType& v) { + if (v == m_Goal) + return *this; + + m_Goal = v; + m_Begun = m_Value; + + onAnimationBegin(); + + return *this; + } + + /* Sets the actual stored value, without affecting the goal, but resets the timer*/ + void setValue(const VarType& v) { + if (v == m_Value) + return; + + m_Value = v; + m_Begun = m_Value; + + onAnimationBegin(); + } + + /* Sets the actual value and goal*/ + void setValueAndWarp(const VarType& v) { + m_Goal = v; + m_bIsBeingAnimated = true; + + warp(); + } + + AnimationContext m_Context; + + private: + VarType m_Value{}; + VarType m_Goal{}; + VarType m_Begun{}; + }; + } +} diff --git a/include/hyprutils/animation/AnimationConfig.hpp b/include/hyprutils/animation/AnimationConfig.hpp new file mode 100644 index 0000000..f639938 --- /dev/null +++ b/include/hyprutils/animation/AnimationConfig.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "../memory/WeakPtr.hpp" + +#include +#include + +namespace Hyprutils { + namespace Animation { + /* + Structure for animation properties. + Config properties need to have a static lifetime to allow for config reload. + */ + struct SAnimationPropertyConfig { + bool overridden = false; + + std::string internalBezier = ""; + std::string internalStyle = ""; + float internalSpeed = 0.f; + int internalEnabled = -1; + + Memory::CWeakPointer pValues; + Memory::CWeakPointer pParentAnimation; + }; + + /* A class to manage SAnimationPropertyConfig objects in a tree structure */ + class CAnimationConfigTree { + public: + CAnimationConfigTree() = default; + ~CAnimationConfigTree() = default; + + /* Add a new animation node inheriting from a parent. + If parent is empty, a root node will be created that references it's own values. + Make sure the parent node has already been created through this interface. */ + void createNode(const std::string& nodeName, const std::string& parent = ""); + + /* check if a node name has been created using createNode */ + bool nodeExists(const std::string& nodeName) const; + + /* Override the values of a node. The root node can also be overriden. */ + void setConfigForNode(const std::string& nodeName, int enabled, float speed, const std::string& bezier, const std::string& style = ""); + + Memory::CSharedPointer getConfig(const std::string& name) const; + const std::unordered_map>& getFullConfig() const; + + CAnimationConfigTree(const CAnimationConfigTree&) = delete; + CAnimationConfigTree(CAnimationConfigTree&&) = delete; + CAnimationConfigTree& operator=(const CAnimationConfigTree&) = delete; + CAnimationConfigTree& operator=(CAnimationConfigTree&&) = delete; + + private: + void setAnimForChildren(Memory::CSharedPointer PANIM); + std::unordered_map> m_mAnimationConfig; + }; + } +} diff --git a/include/hyprutils/animation/AnimationManager.hpp b/include/hyprutils/animation/AnimationManager.hpp new file mode 100644 index 0000000..1266e2d --- /dev/null +++ b/include/hyprutils/animation/AnimationManager.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "./BezierCurve.hpp" +#include "../math/Vector2D.hpp" +#include "../memory/WeakPtr.hpp" +#include "../signal/Signal.hpp" + +#include +#include +#include + +namespace Hyprutils { + namespace Animation { + class CBaseAnimatedVariable; + + /* A class for managing bezier curves and variables that are being animated. */ + class CAnimationManager { + public: + CAnimationManager(); + virtual ~CAnimationManager() = default; + + void tickDone(); + void rotateActive(); + bool shouldTickForNext(); + + virtual void scheduleTick() = 0; + virtual void onTicked() = 0; + + void addBezierWithName(std::string, const Math::Vector2D&, const Math::Vector2D&); + void removeAllBeziers(); + + bool bezierExists(const std::string&); + Memory::CSharedPointer getBezier(const std::string&); + + const std::unordered_map>& getAllBeziers(); + + struct SAnimationManagerSignals { + Signal::CSignal connect; // WP + Signal::CSignal disconnect; // WP + }; + + Memory::CWeakPointer getSignals() const; + + std::vector> m_vActiveAnimatedVariables; + + private: + std::unordered_map> m_mBezierCurves; + + bool m_bTickScheduled = false; + + void onConnect(std::any data); + void onDisconnect(std::any data); + + struct SAnimVarListeners { + Signal::CHyprSignalListener connect; + Signal::CHyprSignalListener disconnect; + }; + + Memory::CUniquePointer m_listeners; + Memory::CUniquePointer m_events; + }; + } +} diff --git a/include/hyprutils/animation/BezierCurve.hpp b/include/hyprutils/animation/BezierCurve.hpp new file mode 100644 index 0000000..b1647df --- /dev/null +++ b/include/hyprutils/animation/BezierCurve.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "../math/Vector2D.hpp" + +namespace Hyprutils { + namespace Animation { + constexpr int BAKEDPOINTS = 255; + constexpr float INVBAKEDPOINTS = 1.f / BAKEDPOINTS; + + /* An implementation of a cubic bezier curve. */ + class CBezierCurve { + public: + /* Calculates a cubic bezier curve based on 2 control points (EXCLUDES the 0,0 and 1,1 points). */ + void setup(const std::array& points); + + float getYForT(float const& t) const; + float getXForT(float const& t) const; + float getYForPoint(float const& x) const; + + private: + /* this INCLUDES the 0,0 and 1,1 points. */ + std::vector m_vPoints; + + std::array m_aPointsBaked; + }; + } +} diff --git a/include/hyprutils/math/Mat3x3.hpp b/include/hyprutils/math/Mat3x3.hpp index 72f8e87..040ee8e 100644 --- a/include/hyprutils/math/Mat3x3.hpp +++ b/include/hyprutils/math/Mat3x3.hpp @@ -24,7 +24,7 @@ namespace Hyprutils { /* create an output projection matrix */ static Mat3x3 outputProjection(const Vector2D& size, eTransform transform); - /* get the matrix as an array, in a RTL TTB order. */ + /* get the matrix as an array, in a row-major order. */ std::array getMatrix() const; /* create a box projection matrix */ diff --git a/include/hyprutils/math/Vector2D.hpp b/include/hyprutils/math/Vector2D.hpp index 4cc0402..f59e1e6 100644 --- a/include/hyprutils/math/Vector2D.hpp +++ b/include/hyprutils/math/Vector2D.hpp @@ -102,7 +102,7 @@ namespace Hyprutils { } // absolutely ridiculous formatter spec parsing -#define AQ_FORMAT_PARSE(specs__, type__) \ +#define AQ_FORMAT_PARSE(specs__, type__) \ template \ constexpr auto parse(FormatContext& ctx) { \ auto it = ctx.begin(); \ @@ -112,10 +112,10 @@ namespace Hyprutils { return it; \ } -#define AQ_FORMAT_FLAG(spec__, flag__) \ +#define AQ_FORMAT_FLAG(spec__, flag__) \ case spec__: (flag__) = true; break; -#define AQ_FORMAT_NUMBER(buf__) \ +#define AQ_FORMAT_NUMBER(buf__) \ case '0': \ case '1': \ case '2': \ @@ -139,9 +139,9 @@ struct std::formatter : std::formatter bool formatX = false; std::string precision = ""; AQ_FORMAT_PARSE(AQ_FORMAT_FLAG('j', formatJson) // - AQ_FORMAT_FLAG('X', formatX) // - AQ_FORMAT_NUMBER(precision), - Hyprutils::Math::Vector2D) + AQ_FORMAT_FLAG('X', formatX) // + AQ_FORMAT_NUMBER(precision), + Hyprutils::Math::Vector2D) template auto format(const Hyprutils::Math::Vector2D& vec, FormatContext& ctx) const { diff --git a/include/hyprutils/memory/ImplBase.hpp b/include/hyprutils/memory/ImplBase.hpp new file mode 100644 index 0000000..8c32540 --- /dev/null +++ b/include/hyprutils/memory/ImplBase.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include + +namespace Hyprutils { + namespace Memory { + namespace Impl_ { + class impl_base { + public: + virtual ~impl_base() {}; + + virtual void inc() noexcept = 0; + virtual void dec() noexcept = 0; + virtual void incWeak() noexcept = 0; + virtual void decWeak() noexcept = 0; + virtual unsigned int ref() noexcept = 0; + virtual unsigned int wref() noexcept = 0; + virtual void destroy() noexcept = 0; + virtual bool destroying() noexcept = 0; + virtual bool dataNonNull() noexcept = 0; + virtual bool lockable() noexcept = 0; + virtual void* getData() noexcept = 0; + }; + + template + class impl : public impl_base { + public: + impl(T* data, bool lock = true) noexcept : _lockable(lock), _data(data) { + ; + } + + /* strong refcount */ + unsigned int _ref = 0; + /* weak refcount */ + unsigned int _weak = 0; + /* if this is lockable (shared) */ + bool _lockable = true; + + T* _data = nullptr; + + friend void swap(impl*& a, impl*& b) { + impl* tmp = a; + a = b; + b = tmp; + } + + /* if the destructor was called, + creating shared_ptrs is no longer valid */ + bool _destroying = false; + + void _destroy() { + if (!_data || _destroying) + return; + + // first, we destroy the data, but keep the pointer. + // this way, weak pointers will still be able to + // reference and use, but no longer create shared ones. + _destroying = true; + __deleter(_data); + // now, we can reset the data and call it a day. + _data = nullptr; + _destroying = false; + } + + std::default_delete __deleter{}; + + // + virtual void inc() noexcept { + _ref++; + } + + virtual void dec() noexcept { + _ref--; + } + + virtual void incWeak() noexcept { + _weak++; + } + + virtual void decWeak() noexcept { + _weak--; + } + + virtual unsigned int ref() noexcept { + return _ref; + } + + virtual unsigned int wref() noexcept { + return _weak; + } + + virtual void destroy() noexcept { + _destroy(); + } + + virtual bool destroying() noexcept { + return _destroying; + } + + virtual bool lockable() noexcept { + return _lockable; + } + + virtual bool dataNonNull() noexcept { + return _data != nullptr; + } + + virtual void* getData() noexcept { + return _data; + } + + virtual ~impl() { + destroy(); + } + }; + } + } + +} diff --git a/include/hyprutils/memory/SharedPtr.hpp b/include/hyprutils/memory/SharedPtr.hpp index 81ea705..d2c5995 100644 --- a/include/hyprutils/memory/SharedPtr.hpp +++ b/include/hyprutils/memory/SharedPtr.hpp @@ -1,8 +1,6 @@ #pragma once -#include -#include -#include +#include "ImplBase.hpp" /* This is a custom impl of std::shared_ptr. @@ -17,109 +15,6 @@ namespace Hyprutils { namespace Memory { - namespace CSharedPointer_ { - class impl_base { - public: - virtual ~impl_base() {}; - - virtual void inc() noexcept = 0; - virtual void dec() noexcept = 0; - virtual void incWeak() noexcept = 0; - virtual void decWeak() noexcept = 0; - virtual unsigned int ref() noexcept = 0; - virtual unsigned int wref() noexcept = 0; - virtual void destroy() noexcept = 0; - virtual bool destroying() noexcept = 0; - virtual bool dataNonNull() noexcept = 0; - virtual void* getData() noexcept = 0; - }; - - template - class impl : public impl_base { - public: - impl(T* data) noexcept : _data(data) { - ; - } - - /* strong refcount */ - unsigned int _ref = 0; - /* weak refcount */ - unsigned int _weak = 0; - - T* _data = nullptr; - - friend void swap(impl*& a, impl*& b) { - impl* tmp = a; - a = b; - b = tmp; - } - - /* if the destructor was called, - creating shared_ptrs is no longer valid */ - bool _destroying = false; - - void _destroy() { - if (!_data || _destroying) - return; - - // first, we destroy the data, but keep the pointer. - // this way, weak pointers will still be able to - // reference and use, but no longer create shared ones. - _destroying = true; - __deleter(_data); - // now, we can reset the data and call it a day. - _data = nullptr; - _destroying = false; - } - - std::default_delete __deleter{}; - - // - virtual void inc() noexcept { - _ref++; - } - - virtual void dec() noexcept { - _ref--; - } - - virtual void incWeak() noexcept { - _weak++; - } - - virtual void decWeak() noexcept { - _weak--; - } - - virtual unsigned int ref() noexcept { - return _ref; - } - - virtual unsigned int wref() noexcept { - return _weak; - } - - virtual void destroy() noexcept { - _destroy(); - } - - virtual bool destroying() noexcept { - return _destroying; - } - - virtual bool dataNonNull() noexcept { - return _data != nullptr; - } - - virtual void* getData() noexcept { - return _data; - } - - virtual ~impl() { - destroy(); - } - }; - }; template class CSharedPointer { @@ -132,7 +27,7 @@ namespace Hyprutils { /* creates a new shared pointer managing a resource avoid calling. Could duplicate ownership. Prefer makeShared */ explicit CSharedPointer(T* object) noexcept { - impl_ = new CSharedPointer_::impl(object); + impl_ = new Impl_::impl(object); increment(); } @@ -158,7 +53,7 @@ namespace Hyprutils { } /* allows weakPointer to create from an impl */ - CSharedPointer(CSharedPointer_::impl_base* implementation) noexcept { + CSharedPointer(Impl_::impl_base* implementation) noexcept { impl_ = implementation; increment(); } @@ -246,7 +141,7 @@ namespace Hyprutils { return impl_ ? impl_->ref() : 0; } - CSharedPointer_::impl_base* impl_ = nullptr; + Impl_::impl_base* impl_ = nullptr; private: /* diff --git a/include/hyprutils/memory/UniquePtr.hpp b/include/hyprutils/memory/UniquePtr.hpp new file mode 100644 index 0000000..8588560 --- /dev/null +++ b/include/hyprutils/memory/UniquePtr.hpp @@ -0,0 +1,149 @@ +#pragma once + +#include "ImplBase.hpp" + +/* + This is a custom impl of std::unique_ptr. + In contrast to the STL one, it allows for + creation of a weak_ptr, that will then be unable + to be locked. +*/ + +namespace Hyprutils { + namespace Memory { + template + class CUniquePointer { + public: + template + using validHierarchy = typename std::enable_if&, X>::value, CUniquePointer&>::type; + template + using isConstructible = typename std::enable_if::value>::type; + + /* creates a new unique pointer managing a resource + avoid calling. Could duplicate ownership. Prefer makeUnique */ + explicit CUniquePointer(T* object) noexcept { + impl_ = new Impl_::impl(object, false); + increment(); + } + + /* creates a shared pointer from a reference */ + template > + CUniquePointer(const CUniquePointer& ref) = delete; + CUniquePointer(const CUniquePointer& ref) = delete; + + template > + CUniquePointer(CUniquePointer&& ref) noexcept { + std::swap(impl_, ref.impl_); + } + + CUniquePointer(CUniquePointer&& ref) noexcept { + std::swap(impl_, ref.impl_); + } + + /* creates an empty unique pointer with no implementation */ + CUniquePointer() noexcept { + ; // empty + } + + /* creates an empty unique pointer with no implementation */ + CUniquePointer(std::nullptr_t) noexcept { + ; // empty + } + + ~CUniquePointer() { + decrement(); + } + + template + validHierarchy&> operator=(const CUniquePointer& rhs) = delete; + CUniquePointer& operator=(const CUniquePointer& rhs) = delete; + + template + validHierarchy&> operator=(CUniquePointer&& rhs) { + std::swap(impl_, rhs.impl_); + return *this; + } + + CUniquePointer& operator=(CUniquePointer&& rhs) { + std::swap(impl_, rhs.impl_); + return *this; + } + + operator bool() const { + return impl_; + } + + bool operator()(const CUniquePointer& lhs, const CUniquePointer& rhs) const { + return reinterpret_cast(lhs.impl_) < reinterpret_cast(rhs.impl_); + } + + T* operator->() const { + return get(); + } + + T& operator*() const { + return *get(); + } + + void reset() { + decrement(); + impl_ = nullptr; + } + + T* get() const { + return impl_ ? static_cast(impl_->getData()) : nullptr; + } + + Impl_::impl_base* impl_ = nullptr; + + private: + /* + no-op if there is no impl_ + may delete the stored object if ref == 0 + may delete and reset impl_ if ref == 0 and weak == 0 + */ + void decrement() { + if (!impl_) + return; + + impl_->dec(); + + // if ref == 0, we can destroy impl + if (impl_->ref() == 0) + destroyImpl(); + } + /* no-op if there is no impl_ */ + void increment() { + if (!impl_) + return; + + impl_->inc(); + } + + /* destroy the pointed-to object + if able, will also destroy impl */ + void destroyImpl() { + // destroy the impl contents + impl_->destroy(); + + // check for weak refs, if zero, we can also delete impl_ + if (impl_->wref() == 0) { + delete impl_; + impl_ = nullptr; + } + } + }; + + template + static CUniquePointer makeUnique(Args&&... args) { + return CUniquePointer(new U(std::forward(args)...)); + } + } +} + +template +struct std::hash> { + std::size_t operator()(const Hyprutils::Memory::CUniquePointer& p) const noexcept { + return std::hash{}(p.impl_); + } +}; \ No newline at end of file diff --git a/include/hyprutils/memory/WeakPtr.hpp b/include/hyprutils/memory/WeakPtr.hpp index cd0d4bd..023bf78 100644 --- a/include/hyprutils/memory/WeakPtr.hpp +++ b/include/hyprutils/memory/WeakPtr.hpp @@ -1,6 +1,7 @@ #pragma once #include "./SharedPtr.hpp" +#include "./UniquePtr.hpp" /* This is a Hyprland implementation of std::weak_ptr. @@ -28,6 +29,16 @@ namespace Hyprutils { incrementWeak(); } + /* create a weak ptr from a reference */ + template > + CWeakPointer(const CUniquePointer& ref) noexcept { + if (!ref.impl_) + return; + + impl_ = ref.impl_; + incrementWeak(); + } + /* create a weak ptr from another weak ptr */ template > CWeakPointer(const CWeakPointer& ref) noexcept { @@ -119,7 +130,7 @@ namespace Hyprutils { } CSharedPointer lock() const { - if (!impl_ || !impl_->dataNonNull() || impl_->destroying()) + if (!impl_ || !impl_->dataNonNull() || impl_->destroying() || !impl_->lockable()) return {}; return CSharedPointer(impl_); @@ -138,6 +149,18 @@ namespace Hyprutils { return impl_ == rhs.impl_; } + bool operator==(const CUniquePointer& rhs) const { + return impl_ == rhs.impl_; + } + + bool operator==(std::nullptr_t) const { + return !valid(); + } + + bool operator!=(std::nullptr_t) const { + return valid(); + } + bool operator()(const CWeakPointer& lhs, const CWeakPointer& rhs) const { return reinterpret_cast(lhs.impl_) < reinterpret_cast(rhs.impl_); } @@ -154,7 +177,11 @@ namespace Hyprutils { return get(); } - CSharedPointer_::impl_base* impl_ = nullptr; + T& operator*() const { + return *get(); + } + + Impl_::impl_base* impl_ = nullptr; private: /* no-op if there is no impl_ */ diff --git a/include/hyprutils/os/FileDescriptor.hpp b/include/hyprutils/os/FileDescriptor.hpp new file mode 100644 index 0000000..96761d0 --- /dev/null +++ b/include/hyprutils/os/FileDescriptor.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +namespace Hyprutils { + namespace OS { + class CFileDescriptor { + public: + CFileDescriptor() = default; + explicit CFileDescriptor(int const fd); + CFileDescriptor(CFileDescriptor&&); + CFileDescriptor& operator=(CFileDescriptor&&); + ~CFileDescriptor(); + + CFileDescriptor(const CFileDescriptor&) = delete; + CFileDescriptor& operator=(const CFileDescriptor&) = delete; + + bool operator==(const CFileDescriptor& rhs) const { + return m_fd == rhs.m_fd; + } + + bool isValid() const; + int get() const; + int getFlags() const; + bool setFlags(int flags); + int take(); + void reset(); + CFileDescriptor duplicate(int flags = F_DUPFD_CLOEXEC) const; + + bool isReadable() const; + bool isClosed() const; + + static bool isReadable(int fd); + static bool isClosed(int fd); + + private: + int m_fd = -1; + }; + }; +}; diff --git a/include/hyprutils/os/Process.hpp b/include/hyprutils/os/Process.hpp new file mode 100644 index 0000000..47842ce --- /dev/null +++ b/include/hyprutils/os/Process.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include + +namespace Hyprutils { + namespace OS { + class CProcess { + public: + /* Creates a process object, doesn't run yet */ + CProcess(const std::string& binary_, const std::vector& args_); + + void addEnv(const std::string& name, const std::string& value); + + /* Run the process, synchronously, get the stdout and stderr. False on fail */ + bool runSync(); + + /* Run the process, asynchronously. This will detach the process from this object (and process) and let it live a happy life. False on fail. */ + bool runAsync(); + + // only populated when ran sync + const std::string& stdOut(); + const std::string& stdErr(); + + const pid_t pid(); + + private: + std::string binary, out, err; + std::vector args; + std::vector> env; + pid_t grandchildPid = 0; + }; + } +} \ No newline at end of file diff --git a/include/hyprutils/string/String.hpp b/include/hyprutils/string/String.hpp index 6490d07..cdc5c39 100644 --- a/include/hyprutils/string/String.hpp +++ b/include/hyprutils/string/String.hpp @@ -5,7 +5,7 @@ namespace Hyprutils { namespace String { // trims beginning and end of whitespace characters std::string trim(const std::string& in); - bool isNumber(const std::string& str, bool allowfloat = false); - void replaceInString(std::string& string, const std::string& what, const std::string& to); + bool isNumber(const std::string& str, bool allowfloat = false); + void replaceInString(std::string& string, const std::string& what, const std::string& to); }; }; \ No newline at end of file diff --git a/nix/default.nix b/nix/default.nix index 99b53bc..ea575ac 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1,36 +1,49 @@ { lib, stdenv, + stdenvAdapters, cmake, pkg-config, pixman, version ? "git", doCheck ? false, -}: -stdenv.mkDerivation { - pname = "hyprutils"; - inherit version doCheck; - src = ../.; + debug ? false, +}: let + inherit (builtins) foldl'; + inherit (lib.lists) flatten; - nativeBuildInputs = [ - cmake - pkg-config + adapters = flatten [ + stdenvAdapters.useMoldLinker + (lib.optional debug stdenvAdapters.keepDebugInfo) ]; - buildInputs = [ - pixman - ]; + customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters; +in + customStdenv.mkDerivation { + pname = "hyprutils"; + inherit version doCheck; + src = ../.; - outputs = ["out" "dev"]; + nativeBuildInputs = [ + cmake + pkg-config + ]; - cmakeBuildType = "RelWithDebInfo"; + buildInputs = [ + pixman + ]; - dontStrip = true; + outputs = ["out" "dev"]; - meta = with lib; { - homepage = "https://github.com/hyprwm/hyprutils"; - description = "Small C++ library for utilities used across the Hypr* ecosystem"; - license = licenses.bsd3; - platforms = platforms.linux; - }; -} + cmakeBuildType = + if debug + then "Debug" + else "RelWithDebInfo"; + + meta = with lib; { + homepage = "https://github.com/hyprwm/hyprutils"; + description = "Small C++ library for utilities used across the Hypr* ecosystem"; + license = licenses.bsd3; + platforms = platforms.linux; + }; + } diff --git a/src/animation/AnimatedVariable.cpp b/src/animation/AnimatedVariable.cpp new file mode 100644 index 0000000..ad63a8b --- /dev/null +++ b/src/animation/AnimatedVariable.cpp @@ -0,0 +1,162 @@ +#include +#include +#include + +using namespace Hyprutils::Animation; +using namespace Hyprutils::Memory; + +#define SP CSharedPointer +#define WP CWeakPointer + +void CBaseAnimatedVariable::create(CAnimationManager* pManager, int typeInfo, SP pSelf) { + m_Type = typeInfo; + m_pSelf = pSelf; + + m_pAnimationManager = pManager; + m_pSignals = pManager->getSignals(); + m_bDummy = false; +} + +void CBaseAnimatedVariable::connectToActive() { + if (m_bDummy || m_bIsConnectedToActive || isAnimationManagerDead()) + return; + + m_pSignals->connect.emit(m_pSelf); + m_bIsConnectedToActive = true; +} + +void CBaseAnimatedVariable::disconnectFromActive() { + if (isAnimationManagerDead()) + return; + + m_pSignals->disconnect.emit(m_pSelf); + m_bIsConnectedToActive = false; +} + +bool Hyprutils::Animation::CBaseAnimatedVariable::enabled() const { + if (const auto PCONFIG = m_pConfig.lock()) { + const auto PVALUES = PCONFIG->pValues.lock(); + return PVALUES ? PVALUES->internalEnabled : false; + } + + return false; +} + +const std::string& CBaseAnimatedVariable::getBezierName() const { + static constexpr const std::string DEFAULTBEZIERNAME = "default"; + + if (const auto PCONFIG = m_pConfig.lock()) { + const auto PVALUES = PCONFIG->pValues.lock(); + return PVALUES ? PVALUES->internalBezier : DEFAULTBEZIERNAME; + } + + return DEFAULTBEZIERNAME; +} + +const std::string& CBaseAnimatedVariable::getStyle() const { + static constexpr const std::string DEFAULTSTYLE = ""; + + if (const auto PCONFIG = m_pConfig.lock()) { + const auto PVALUES = PCONFIG->pValues.lock(); + return PVALUES ? PVALUES->internalStyle : DEFAULTSTYLE; + } + + return DEFAULTSTYLE; +} + +float CBaseAnimatedVariable::getPercent() const { + const auto DURATIONPASSED = std::chrono::duration_cast(std::chrono::steady_clock::now() - animationBegin).count(); + + if (const auto PCONFIG = m_pConfig.lock()) { + const auto PVALUES = PCONFIG->pValues.lock(); + return PVALUES ? std::clamp((DURATIONPASSED / 100.f) / PVALUES->internalSpeed, 0.f, 1.f) : 1.f; + } + + return 1.f; +} + +float CBaseAnimatedVariable::getCurveValue() const { + if (!m_bIsBeingAnimated || isAnimationManagerDead()) + return 1.f; + + std::string bezierName = ""; + 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) + return 1.f; + + const auto SPENT = getPercent(); + if (SPENT >= 1.f) + return 1.f; + + return BEZIER->getYForPoint(SPENT); +} + +bool CBaseAnimatedVariable::ok() const { + return m_pConfig && !m_bDummy && !isAnimationManagerDead(); +} + +void CBaseAnimatedVariable::onUpdate() { + if (m_bIsBeingAnimated && m_fUpdateCallback) + m_fUpdateCallback(m_pSelf); +} + +void CBaseAnimatedVariable::setCallbackOnEnd(CallbackFun func, bool remove) { + m_fEndCallback = std::move(func); + m_bRemoveEndAfterRan = remove; + + if (!isBeingAnimated()) + onAnimationEnd(); +} + +void CBaseAnimatedVariable::setCallbackOnBegin(CallbackFun func, bool remove) { + m_fBeginCallback = std::move(func); + m_bRemoveBeginAfterRan = remove; +} + +void CBaseAnimatedVariable::setUpdateCallback(CallbackFun func) { + m_fUpdateCallback = std::move(func); +} + +void CBaseAnimatedVariable::resetAllCallbacks() { + m_fBeginCallback = nullptr; + m_fEndCallback = nullptr; + m_fUpdateCallback = nullptr; + m_bRemoveBeginAfterRan = false; + m_bRemoveEndAfterRan = false; +} + +void CBaseAnimatedVariable::onAnimationEnd() { + m_bIsBeingAnimated = false; + /* We do not call disconnectFromActive here. The animation manager will remove it on a call to tickDone. */ + + if (m_fEndCallback) { + CallbackFun cb = nullptr; + m_fEndCallback.swap(cb); + + cb(m_pSelf); + if (!m_bRemoveEndAfterRan && /* callback did not set a new one by itself */ !m_fEndCallback) + m_fEndCallback = cb; // restore + } +} + +void CBaseAnimatedVariable::onAnimationBegin() { + m_bIsBeingAnimated = true; + animationBegin = std::chrono::steady_clock::now(); + connectToActive(); + + if (m_fBeginCallback) { + m_fBeginCallback(m_pSelf); + if (m_bRemoveBeginAfterRan) + m_fBeginCallback = nullptr; // reset + } +} + +bool CBaseAnimatedVariable::isAnimationManagerDead() const { + return m_pSignals.expired(); +} diff --git a/src/animation/AnimationConfig.cpp b/src/animation/AnimationConfig.cpp new file mode 100644 index 0000000..e158bca --- /dev/null +++ b/src/animation/AnimationConfig.cpp @@ -0,0 +1,70 @@ +#include + +using namespace Hyprutils::Animation; +using namespace Hyprutils::Memory; + +#define SP CSharedPointer +#define WP CWeakPointer + +void CAnimationConfigTree::createNode(const std::string& nodeName, const std::string& parent) { + auto pConfig = m_mAnimationConfig[nodeName]; + if (!pConfig) + pConfig = makeShared(); + + WP parentRef; + if (!parent.empty() && m_mAnimationConfig.find(parent) != m_mAnimationConfig.end()) + parentRef = m_mAnimationConfig[parent]; + + *pConfig = { + .overridden = false, + .internalBezier = "", + .internalStyle = "", + .internalSpeed = 0.f, + .internalEnabled = -1, + .pValues = (parentRef) ? parentRef->pValues : pConfig, + .pParentAnimation = (parentRef) ? parentRef : pConfig, + }; + + m_mAnimationConfig[nodeName] = pConfig; +} + +bool CAnimationConfigTree::nodeExists(const std::string& nodeName) const { + return m_mAnimationConfig.find(nodeName) != m_mAnimationConfig.end(); +} + +void CAnimationConfigTree::setConfigForNode(const std::string& nodeName, int enabled, float speed, const std::string& bezier, const std::string& style) { + auto pConfig = m_mAnimationConfig[nodeName]; + if (!pConfig) + return; + + *pConfig = { + .overridden = true, + .internalBezier = bezier, + .internalStyle = style, + .internalSpeed = speed, + .internalEnabled = enabled, + .pValues = pConfig, + .pParentAnimation = pConfig->pParentAnimation, // keep the parent! + }; + + setAnimForChildren(pConfig); +} + +SP CAnimationConfigTree::getConfig(const std::string& name) const { + return m_mAnimationConfig.at(name); +} + +const std::unordered_map>& CAnimationConfigTree::getFullConfig() const { + return m_mAnimationConfig; +} + +void CAnimationConfigTree::setAnimForChildren(SP PANIM) { + for (auto& [name, anim] : m_mAnimationConfig) { + if (anim->pParentAnimation == PANIM && !anim->overridden) { + // if a child isnt overridden, set the values of the parent + anim->pValues = PANIM->pValues; + + setAnimForChildren(anim); + } + } +} diff --git a/src/animation/AnimationManager.cpp b/src/animation/AnimationManager.cpp new file mode 100644 index 0000000..cc62616 --- /dev/null +++ b/src/animation/AnimationManager.cpp @@ -0,0 +1,114 @@ +#include +#include +#include + +using namespace Hyprutils::Animation; +using namespace Hyprutils::Math; +using namespace Hyprutils::Memory; +using namespace Hyprutils::Signal; + +#define SP CSharedPointer +#define WP CWeakPointer + +const std::array DEFAULTBEZIERPOINTS = {Vector2D(0.0, 0.75), Vector2D(0.15, 1.0)}; + +CAnimationManager::CAnimationManager() { + const auto BEZIER = makeShared(); + BEZIER->setup(DEFAULTBEZIERPOINTS); + m_mBezierCurves["default"] = BEZIER; + + m_events = makeUnique(); + m_listeners = makeUnique(); + + m_listeners->connect = m_events->connect.registerListener([this](std::any data) { onConnect(data); }); + m_listeners->disconnect = m_events->disconnect.registerListener([this](std::any data) { onDisconnect(data); }); +} + +void CAnimationManager::onConnect(std::any data) { + if (!m_bTickScheduled) + scheduleTick(); + + try { + const auto PAV = std::any_cast>(data); + if (!PAV) + return; + + m_vActiveAnimatedVariables.emplace_back(PAV); + } catch (const std::bad_any_cast&) { return; } +} + +void CAnimationManager::onDisconnect(std::any data) { + try { + const auto PAV = std::any_cast>(data); + if (!PAV) + return; + + std::erase_if(m_vActiveAnimatedVariables, [&](const auto& other) { return !other || other == PAV; }); + } catch (const std::bad_any_cast&) { return; } +} + +void CAnimationManager::removeAllBeziers() { + m_mBezierCurves.clear(); + + // add the default one + const auto BEZIER = makeShared(); + BEZIER->setup(DEFAULTBEZIERPOINTS); + m_mBezierCurves["default"] = BEZIER; +} + +void CAnimationManager::addBezierWithName(std::string name, const Vector2D& p1, const Vector2D& p2) { + const auto BEZIER = makeShared(); + BEZIER->setup({ + p1, + p2, + }); + m_mBezierCurves[name] = BEZIER; +} + +bool CAnimationManager::shouldTickForNext() { + return !m_vActiveAnimatedVariables.empty(); +} + +void CAnimationManager::tickDone() { + rotateActive(); +} + +void CAnimationManager::rotateActive() { + std::vector> active; + active.reserve(m_vActiveAnimatedVariables.size()); // avoid reallocations + for (auto const& av : m_vActiveAnimatedVariables) { + const auto PAV = av.lock(); + if (!PAV) + continue; + + if (PAV->ok() && PAV->isBeingAnimated()) + active.emplace_back(av); + else + PAV->m_bIsConnectedToActive = false; + } + + m_vActiveAnimatedVariables = std::move(active); +} + +bool CAnimationManager::bezierExists(const std::string& bezier) { + for (auto const& [bc, bz] : m_mBezierCurves) { + if (bc == bezier) + return true; + } + + return false; +} + +SP CAnimationManager::getBezier(const std::string& name) { + const auto BEZIER = std::ranges::find_if(m_mBezierCurves, [&](const auto& other) { return other.first == name; }); + + return BEZIER == m_mBezierCurves.end() ? m_mBezierCurves["default"] : BEZIER->second; +} + +const std::unordered_map>& CAnimationManager::getAllBeziers() { + return m_mBezierCurves; +} + +CWeakPointer CAnimationManager::getSignals() const { + return m_events; +} diff --git a/src/animation/BezierCurve.cpp b/src/animation/BezierCurve.cpp new file mode 100644 index 0000000..850cf3a --- /dev/null +++ b/src/animation/BezierCurve.cpp @@ -0,0 +1,78 @@ +#include + +#include +#include + +using namespace Hyprutils::Animation; +using namespace Hyprutils::Math; + +void CBezierCurve::setup(const std::array& pVec) { + // Avoid reallocations by reserving enough memory upfront + m_vPoints.resize(pVec.size() + 2); + m_vPoints = { + Vector2D(0, 0), // Start point + pVec[0], pVec[1], // Control points + Vector2D(1, 1) // End point + }; + + if (m_vPoints.size() != 4) + std::abort(); + + // bake BAKEDPOINTS points for faster lookups + // T -> X ( / BAKEDPOINTS ) + for (int i = 0; i < BAKEDPOINTS; ++i) { + float const t = (i + 1) / (float)BAKEDPOINTS; + 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 t2 = t * 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); +} + +float CBezierCurve::getYForT(float const& t) const { + float t2 = t * 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); +} + +// Todo: this probably can be done better and faster +float CBezierCurve::getYForPoint(float const& x) const { + if (x >= 1.f) + return 1.f; + if (x <= 0.f) + return 0.f; + + int index = 0; + bool below = true; + for (int step = (BAKEDPOINTS + 1) / 2; step > 0; step /= 2) { + if (below) + index += step; + else + index -= step; + + below = m_aPointsBaked[index].x < x; + } + + int lowerIndex = index - (!below || index == BAKEDPOINTS - 1); + + // in the name of performance i shall make a hack + const auto LOWERPOINT = &m_aPointsBaked[lowerIndex]; + const auto UPPERPOINT = &m_aPointsBaked[lowerIndex + 1]; + + const auto PERCINDELTA = (x - LOWERPOINT->x) / (UPPERPOINT->x - LOWERPOINT->x); + + if (std::isnan(PERCINDELTA) || std::isinf(PERCINDELTA)) // can sometimes happen for VERY small x + return 0.f; + + return LOWERPOINT->y + ((UPPERPOINT->y - LOWERPOINT->y) * PERCINDELTA); +} diff --git a/src/math/Box.cpp b/src/math/Box.cpp index fcd1eda..16fadfb 100644 --- a/src/math/Box.cpp +++ b/src/math/Box.cpp @@ -37,7 +37,7 @@ CBox& Hyprutils::Math::CBox::translate(const Vector2D& vec) { } Vector2D Hyprutils::Math::CBox::middle() const { - return Vector2D{x + w * HALF, y + h * HALF}; + return Vector2D{x + (w * HALF), y + (h * HALF)}; } bool Hyprutils::Math::CBox::containsPoint(const Vector2D& vec) const { @@ -200,7 +200,7 @@ Vector2D Hyprutils::Math::CBox::size() const { } Vector2D Hyprutils::Math::CBox::extent() const { - return pos() + size(); + return pos() + size(); } Vector2D Hyprutils::Math::CBox::closestPoint(const Vector2D& vec) const { @@ -233,5 +233,5 @@ Vector2D Hyprutils::Math::CBox::closestPoint(const Vector2D& vec) const { } SBoxExtents Hyprutils::Math::CBox::extentsFrom(const CBox& small) { - return {{small.x - x, small.y - y}, {w - small.w - (small.x - x), h - small.h - (small.y - y)}}; + return {.topLeft = {small.x - x, small.y - y}, .bottomRight = {w - small.w - (small.x - x), h - small.h - (small.y - y)}}; } diff --git a/src/math/Region.cpp b/src/math/Region.cpp index e69ddb8..6260b88 100644 --- a/src/math/Region.cpp +++ b/src/math/Region.cpp @@ -86,7 +86,7 @@ CRegion& Hyprutils::Math::CRegion::invert(pixman_box32_t* box) { } CRegion& Hyprutils::Math::CRegion::invert(const CBox& box) { - pixman_box32 pixmanBox = {(int32_t)box.x, (int32_t)box.y, (int32_t)box.w + (int32_t)box.x, (int32_t)box.h + (int32_t)box.y}; + pixman_box32 pixmanBox = {.x1 = (int32_t)box.x, .y1 = (int32_t)box.y, .x2 = (int32_t)box.w + (int32_t)box.x, .y2 = (int32_t)box.h + (int32_t)box.y}; return this->invert(&pixmanBox); } @@ -118,7 +118,7 @@ CRegion& Hyprutils::Math::CRegion::expand(double units) { clear(); for (auto& r : rects) { - CBox b{(double)r.x1 - units, (double)r.y1 - units, (double)r.x2 - r.x1 + units * 2, (double)r.y2 - r.y1 + units * 2}; + CBox b{(double)r.x1 - units, (double)r.y1 - units, (double)r.x2 - r.x1 + (units * 2), (double)r.y2 - r.y1 + (units * 2)}; add(b); } @@ -149,9 +149,9 @@ CRegion& Hyprutils::Math::CRegion::scale(const Vector2D& scale) { for (auto& r : rects) { r.x1 = std::floor(r.x1 * scale.x); - r.y1 = std::floor(r.y1 * scale.x); + r.y1 = std::floor(r.y1 * scale.y); r.x2 = std::ceil(r.x2 * scale.x); - r.y2 = std::ceil(r.y2 * scale.x); + r.y2 = std::ceil(r.y2 * scale.y); add(&r); } @@ -214,4 +214,4 @@ Vector2D Hyprutils::Math::CRegion::closestPoint(const Vector2D& vec) const { } return leader; -} \ No newline at end of file +} diff --git a/src/math/Vector2D.cpp b/src/math/Vector2D.cpp index a7f709a..6f0da5f 100644 --- a/src/math/Vector2D.cpp +++ b/src/math/Vector2D.cpp @@ -4,19 +4,16 @@ using namespace Hyprutils::Math; -Hyprutils::Math::Vector2D::Vector2D(double xx, double yy) { - x = xx; - y = yy; +Hyprutils::Math::Vector2D::Vector2D(double xx, double yy) : x(xx), y(yy) { + ; } -Hyprutils::Math::Vector2D::Vector2D(int xx, int yy) { - x = (double)xx; - y = (double)yy; +Hyprutils::Math::Vector2D::Vector2D(int xx, int yy) : x((double)xx), y((double)yy) { + ; } -Hyprutils::Math::Vector2D::Vector2D() { - x = 0; - y = 0; +Hyprutils::Math::Vector2D::Vector2D() : x(0), y(0) { + ; } Hyprutils::Math::Vector2D::~Vector2D() {} @@ -48,11 +45,11 @@ double Hyprutils::Math::Vector2D::distance(const Vector2D& other) const { } double Hyprutils::Math::Vector2D::distanceSq(const Vector2D& other) const { - return (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y); + return ((x - other.x) * (x - other.x)) + ((y - other.y) * (y - other.y)); } double Hyprutils::Math::Vector2D::size() const { - return std::sqrt(x * x + y * y); + return std::sqrt((x * x) + (y * y)); } Vector2D Hyprutils::Math::Vector2D::getComponentMax(const Vector2D& other) const { diff --git a/src/os/FileDescriptor.cpp b/src/os/FileDescriptor.cpp new file mode 100644 index 0000000..f13a3ee --- /dev/null +++ b/src/os/FileDescriptor.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include + +using namespace Hyprutils::OS; + +CFileDescriptor::CFileDescriptor(int const fd) : m_fd(fd) {} + +CFileDescriptor::CFileDescriptor(CFileDescriptor&& other) : m_fd(std::exchange(other.m_fd, -1)) {} + +CFileDescriptor& CFileDescriptor::operator=(CFileDescriptor&& other) { + if (this == &other) // Shit will go haywire if there is duplicate ownership + abort(); + + reset(); + m_fd = std::exchange(other.m_fd, -1); + return *this; +} + +CFileDescriptor::~CFileDescriptor() { + reset(); +} + +bool CFileDescriptor::isValid() const { + return m_fd != -1; +} + +int CFileDescriptor::get() const { + return m_fd; +} + +int CFileDescriptor::getFlags() const { + return fcntl(m_fd, F_GETFD); +} + +bool CFileDescriptor::setFlags(int flags) { + return fcntl(m_fd, F_SETFD, flags) != -1; +} + +int CFileDescriptor::take() { + return std::exchange(m_fd, -1); +} + +void CFileDescriptor::reset() { + if (m_fd != -1) { + close(m_fd); + m_fd = -1; + } +} + +CFileDescriptor CFileDescriptor::duplicate(int flags) const { + if (m_fd == -1) + return {}; + + return CFileDescriptor{fcntl(m_fd, flags, 0)}; +} + +bool CFileDescriptor::isClosed() const { + return isClosed(m_fd); +} + +bool CFileDescriptor::isReadable() const { + return isReadable(m_fd); +} + +bool CFileDescriptor::isClosed(int fd) { + pollfd pfd = { + .fd = fd, + .events = POLLIN, + .revents = 0, + }; + + if (poll(&pfd, 1, 0) < 0) + return true; + + return pfd.revents & (POLLHUP | POLLERR); +} + +bool CFileDescriptor::isReadable(int fd) { + pollfd pfd = {.fd = fd, .events = POLLIN, .revents = 0}; + + return poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN); +} diff --git a/src/os/Process.cpp b/src/os/Process.cpp new file mode 100644 index 0000000..7f08dc9 --- /dev/null +++ b/src/os/Process.cpp @@ -0,0 +1,232 @@ +#include +using namespace Hyprutils::OS; + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +Hyprutils::OS::CProcess::CProcess(const std::string& binary_, const std::vector& args_) : binary(binary_), args(args_) { + ; +} + +void Hyprutils::OS::CProcess::addEnv(const std::string& name, const std::string& value) { + env.emplace_back(std::make_pair<>(name, value)); +} + +bool Hyprutils::OS::CProcess::runSync() { + int outPipe[2], errPipe[2]; + if (pipe(outPipe)) + return false; + if (pipe(errPipe)) { + close(outPipe[0]); + close(outPipe[1]); + return false; + } + + int pid = fork(); + if (pid == -1) { + close(outPipe[0]); + close(outPipe[1]); + close(outPipe[0]); + close(outPipe[1]); + return false; + } + + if (!pid) { + // child + close(outPipe[0]); + close(errPipe[0]); + + dup2(outPipe[1], 1 /* stdout */); + dup2(errPipe[1], 2 /* stderr */); + + // build argv + std::vector argsC; + argsC.emplace_back(strdup(binary.c_str())); + for (auto& arg : args) { + // TODO: does this leak? Can we just pipe c_str() as the strings won't be realloc'd? + argsC.emplace_back(strdup(arg.c_str())); + } + + argsC.emplace_back(nullptr); + + // pass env + for (auto& [n, v] : env) { + setenv(n.c_str(), v.c_str(), 1); + } + + execvp(binary.c_str(), (char* const*)argsC.data()); + exit(1); + } else { + // parent + close(outPipe[1]); + close(errPipe[1]); + + out = ""; + err = ""; + + grandchildPid = pid; + + std::array buf; + buf.fill(0); + + // wait for read + ssize_t ret = 0; + + int fdFlags = fcntl(outPipe[0], F_GETFL, 0); + if (fcntl(outPipe[0], F_SETFL, fdFlags | O_NONBLOCK) < 0) + return false; + fdFlags = fcntl(errPipe[0], F_GETFL, 0); + if (fcntl(errPipe[0], F_SETFL, fdFlags | O_NONBLOCK) < 0) + return false; + + pollfd pollfds[2] = { + {.fd = outPipe[0], .events = POLLIN, .revents = 0}, + {.fd = errPipe[0], .events = POLLIN, .revents = 0}, + }; + + while (1337) { + int ret = poll(pollfds, 2, 5000); + + if (ret < 0) { + if (errno == EINTR) + continue; + + return false; + } + + bool hupd = false; + + for (size_t i = 0; i < 2; ++i) { + if (pollfds[i].revents & POLLHUP) { + hupd = true; + break; + } + } + + if (hupd) + break; + + if (pollfds[0].revents & POLLIN) { + while ((ret = read(outPipe[0], buf.data(), 1023)) > 0) { + out += std::string_view{(char*)buf.data(), (size_t)ret}; + } + + buf.fill(0); + } + + if (pollfds[1].revents & POLLIN) { + while ((ret = read(errPipe[0], buf.data(), 1023)) > 0) { + err += std::string_view{(char*)buf.data(), (size_t)ret}; + } + + buf.fill(0); + } + } + + // Final reads. Nonblock, so its ok. + while ((ret = read(outPipe[0], buf.data(), 1023)) > 0) { + out += std::string_view{(char*)buf.data(), (size_t)ret}; + } + + buf.fill(0); + + while ((ret = read(errPipe[0], buf.data(), 1023)) > 0) { + err += std::string_view{(char*)buf.data(), (size_t)ret}; + } + + buf.fill(0); + + close(outPipe[0]); + close(errPipe[0]); + + // reap child + waitpid(pid, nullptr, 0); + + return true; + } + + return true; +} + +bool Hyprutils::OS::CProcess::runAsync() { + int socket[2]; + if (pipe(socket) != 0) + return false; + + pid_t child, grandchild; + child = fork(); + if (child < 0) { + close(socket[0]); + close(socket[1]); + return false; + } + + if (child == 0) { + // run in child + sigset_t set; + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, nullptr); + + grandchild = fork(); + if (grandchild == 0) { + // run in grandchild + close(socket[0]); + close(socket[1]); + // build argv + std::vector argsC; + argsC.emplace_back(strdup(binary.c_str())); + for (auto& arg : args) { + argsC.emplace_back(strdup(arg.c_str())); + } + + argsC.emplace_back(nullptr); + + execvp(binary.c_str(), (char* const*)argsC.data()); + _exit(0); + } + close(socket[0]); + if (write(socket[1], &grandchild, sizeof(grandchild)) != sizeof(grandchild)) { + close(socket[1]); + _exit(1); + } + close(socket[1]); + _exit(0); + } + + // run in parent + close(socket[1]); + ssize_t bytesRead = read(socket[0], &grandchild, sizeof(grandchild)); + close(socket[0]); + + if (bytesRead != sizeof(grandchild)) { + waitpid(child, nullptr, 0); + return false; + } + + // clear child and leave grandchild to init + waitpid(child, nullptr, 0); + + grandchildPid = grandchild; + + return true; +} + +const std::string& Hyprutils::OS::CProcess::stdOut() { + return out; +} + +const std::string& Hyprutils::OS::CProcess::stdErr() { + return err; +} + +const pid_t Hyprutils::OS::CProcess::pid() { + return grandchildPid; +} diff --git a/src/path/Path.cpp b/src/path/Path.cpp index b6f9997..cad9e49 100644 --- a/src/path/Path.cpp +++ b/src/path/Path.cpp @@ -62,7 +62,7 @@ namespace Hyprutils::Path { static const auto xdgConfigDirs = getXdgConfigDirs(); if (xdgConfigDirs.has_value()) { - for (auto dir : xdgConfigDirs.value()) { + for (auto& dir : xdgConfigDirs.value()) { if (checkConfigExists(dir, programName)) return std::make_pair(fullConfigPath(dir, programName), std::nullopt); } diff --git a/src/signal/Signal.cpp b/src/signal/Signal.cpp index 3442fe7..75417d9 100644 --- a/src/signal/Signal.cpp +++ b/src/signal/Signal.cpp @@ -18,6 +18,7 @@ void Hyprutils::Signal::CSignal::emit(std::any data) { } std::vector statics; + statics.reserve(m_vStaticListeners.size()); for (auto& l : m_vStaticListeners) { statics.emplace_back(l.get()); } @@ -27,7 +28,7 @@ void Hyprutils::Signal::CSignal::emit(std::any data) { // vector and was removed during our iteration if (l.strongRef() == 1) continue; - + l->emit(data); } @@ -38,7 +39,7 @@ void Hyprutils::Signal::CSignal::emit(std::any data) { // release SPs listeners.clear(); - // we cannot release any expired refs here as one of the listeners could've removed this object and + // we cannot release any expired refs here as one of the listeners could've removed this object and // as such we'd be doing a UAF } @@ -48,10 +49,10 @@ CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function handler, void* owner) { m_vStaticListeners.emplace_back(std::make_unique(handler, owner)); -} \ No newline at end of file +} diff --git a/src/string/String.cpp b/src/string/String.cpp index 2ff2dac..3018128 100644 --- a/src/string/String.cpp +++ b/src/string/String.cpp @@ -7,12 +7,12 @@ std::string Hyprutils::String::trim(const std::string& in) { if (in.empty()) return in; - int countBefore = 0; + size_t countBefore = 0; while (countBefore < in.length() && std::isspace(in.at(countBefore))) { countBefore++; } - int countAfter = 0; + size_t countAfter = 0; while (countAfter < in.length() - countBefore && std::isspace(in.at(in.length() - countAfter - 1))) { countAfter++; } @@ -55,10 +55,7 @@ bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) { } } - if (!isdigit(str.back())) - return false; - - return true; + return isdigit(str.back()) != 0; } void Hyprutils::String::replaceInString(std::string& string, const std::string& what, const std::string& to) { diff --git a/src/string/VarList.cpp b/src/string/VarList.cpp index 925430c..8a93559 100644 --- a/src/string/VarList.cpp +++ b/src/string/VarList.cpp @@ -12,8 +12,7 @@ Hyprutils::String::CVarList::CVarList(const std::string& in, const size_t lastAr std::string args{in}; size_t idx = 0; size_t pos = 0; - std::ranges::replace_if( - args, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0); + std::ranges::replace_if(args, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0); for (const auto& s : args | std::views::split(0)) { if (removeEmpty && s.empty()) @@ -23,7 +22,7 @@ Hyprutils::String::CVarList::CVarList(const std::string& in, const size_t lastAr break; } pos += s.size() + 1; - m_vArgs.emplace_back(trim(std::string_view{s}.data())); + m_vArgs.emplace_back(trim(s.data())); } } @@ -36,4 +35,4 @@ std::string Hyprutils::String::CVarList::join(const std::string& joiner, size_t } return rolling; -} \ No newline at end of file +} diff --git a/tests/animation.cpp b/tests/animation.cpp new file mode 100644 index 0000000..112f379 --- /dev/null +++ b/tests/animation.cpp @@ -0,0 +1,397 @@ +#include +#include +#include +#include +#include +#include "shared.hpp" + +#define SP CSharedPointer +#define WP CWeakPointer +#define UP CUniquePointer + +using namespace Hyprutils::Animation; +using namespace Hyprutils::Math; +using namespace Hyprutils::Memory; + +class EmtpyContext {}; + +template +using CAnimatedVariable = CGenericAnimatedVariable; + +template +using PANIMVAR = SP>; + +template +using PANIMVARREF = WP>; + +enum eAVTypes { + INT = 1, + TEST, +}; + +struct SomeTestType { + bool done = false; + bool operator==(const SomeTestType& other) const { + return done == other.done; + } + SomeTestType& operator=(const SomeTestType& other) { + done = other.done; + return *this; + } +}; + +CAnimationConfigTree animationTree; + +class CMyAnimationManager : public CAnimationManager { + public: + void tick() { + for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) { + const auto PAV = m_vActiveAnimatedVariables[i].lock(); + if (!PAV || !PAV->ok() || !PAV->isBeingAnimated()) + continue; + + const auto SPENT = PAV->getPercent(); + const auto PBEZIER = getBezier(PAV->getBezierName()); + + if (SPENT >= 1.f || !PAV->enabled()) { + PAV->warp(true, false); + continue; + } + + const auto POINTY = PBEZIER->getYForPoint(SPENT); + + switch (PAV->m_Type) { + case eAVTypes::INT: { + auto avInt = dynamic_cast*>(PAV.get()); + if (!avInt) + std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET; + + const auto DELTA = avInt->goal() - avInt->value(); + avInt->value() = avInt->begun() + (DELTA * POINTY); + } break; + case eAVTypes::TEST: { + auto avCustom = dynamic_cast*>(PAV.get()); + if (!avCustom) + std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET; + + if (SPENT >= 1.f) + avCustom->value().done = true; + } break; + default: { + std::cout << Colors::RED << "What are we even doing?" << Colors::RESET; + } break; + } + + PAV->onUpdate(); + } + + tickDone(); + } + + template + void createAnimation(const VarType& v, PANIMVAR& av, const std::string& animationConfigName) { + constexpr const eAVTypes EAVTYPE = std::is_same_v ? eAVTypes::INT : eAVTypes::TEST; + const auto PAV = makeShared>(); + + PAV->create(EAVTYPE, static_cast(this), PAV, v); + PAV->setConfig(animationTree.getConfig(animationConfigName)); + av = std::move(PAV); + } + + virtual void scheduleTick() { + ; + } + + virtual void onTicked() { + ; + } +}; + +UP pAnimationManager; + +class Subject { + public: + Subject(const int& a, const int& b) { + pAnimationManager->createAnimation(a, m_iA, "default"); + pAnimationManager->createAnimation(b, m_iB, "internal"); + pAnimationManager->createAnimation({}, m_iC, "default"); + } + PANIMVAR m_iA; + PANIMVAR m_iB; + PANIMVAR m_iC; +}; + +int config() { + pAnimationManager = makeUnique(); + + int ret = 0; + + animationTree.createNode("global"); + animationTree.createNode("internal"); + + animationTree.createNode("foo", "internal"); + animationTree.createNode("default", "global"); + animationTree.createNode("bar", "default"); + + /* + internal + ↳ foo + global + ↳ default + ↳ bar + */ + + auto barCfg = animationTree.getConfig("bar"); + auto internalCfg = animationTree.getConfig("internal"); + + // internal is a root node and should point to itself + EXPECT(internalCfg->pParentAnimation.get(), internalCfg.get()); + EXPECT(internalCfg->pValues.get(), internalCfg.get()); + + animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf"); + + EXPECT(barCfg->internalEnabled, -1); + { + const auto PVALUES = barCfg->pValues.lock(); + EXPECT(PVALUES->internalEnabled, 1); + EXPECT(PVALUES->internalBezier, "default"); + EXPECT(PVALUES->internalStyle, "asdf"); + EXPECT(PVALUES->internalSpeed, 4.0); + } + EXPECT(barCfg->pParentAnimation.get(), animationTree.getConfig("default").get()); + + // Overwrite our own values + animationTree.setConfigForNode("bar", 1, 4.2, "test", "qwer"); + + { + const auto PVALUES = barCfg->pValues.lock(); + EXPECT(PVALUES->internalEnabled, 1); + EXPECT(PVALUES->internalBezier, "test"); + EXPECT(PVALUES->internalStyle, "qwer"); + EXPECT(PVALUES->internalSpeed, 4.2f); + } + + // Now overwrite the parent + animationTree.setConfigForNode("default", 0, 0.0, "zxcv", "foo"); + + { + // Expecting no change + const auto PVALUES = barCfg->pValues.lock(); + EXPECT(PVALUES->internalEnabled, 1); + EXPECT(PVALUES->internalBezier, "test"); + EXPECT(PVALUES->internalStyle, "qwer"); + EXPECT(PVALUES->internalSpeed, 4.2f); + } + + return ret; +} + +int main(int argc, char** argv, char** envp) { + int ret = config(); + + animationTree.createNode("global"); + animationTree.createNode("internal"); + + animationTree.createNode("default", "global"); + animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf"); + + Subject s(0, 0); + + EXPECT(s.m_iA->value(), 0); + EXPECT(s.m_iB->value(), 0); + + // Test destruction of a CAnimatedVariable + { + Subject s2(10, 10); + // Adds them to active + *s2.m_iA = 1; + *s2.m_iB = 2; + // We deliberately do not tick here, to make sure the destructor removes active animated variables + } + + EXPECT(pAnimationManager->shouldTickForNext(), false); + EXPECT(s.m_iC->value().done, false); + + *s.m_iA = 10; + *s.m_iB = 100; + *s.m_iC = SomeTestType(true); + + EXPECT(s.m_iC->value().done, false); + + while (pAnimationManager->shouldTickForNext()) { + pAnimationManager->tick(); + } + + EXPECT(s.m_iA->value(), 10); + EXPECT(s.m_iB->value(), 100); + EXPECT(s.m_iC->value().done, true); + + s.m_iA->setValue(0); + s.m_iB->setValue(0); + + while (pAnimationManager->shouldTickForNext()) { + pAnimationManager->tick(); + } + + EXPECT(s.m_iA->value(), 10); + EXPECT(s.m_iB->value(), 100); + + // Test config stuff + EXPECT(s.m_iA->getBezierName(), "default"); + EXPECT(s.m_iA->getStyle(), "asdf"); + EXPECT(s.m_iA->enabled(), true); + + animationTree.getConfig("global")->internalEnabled = 0; + + EXPECT(s.m_iA->enabled(), false); + + *s.m_iA = 50; + pAnimationManager->tick(); // Expecting a warp + EXPECT(s.m_iA->value(), 50); + + // Test missing pValues + animationTree.getConfig("global")->internalEnabled = 0; + animationTree.getConfig("default")->pValues.reset(); + + EXPECT(s.m_iA->enabled(), false); + EXPECT(s.m_iA->getBezierName(), "default"); + EXPECT(s.m_iA->getStyle(), ""); + EXPECT(s.m_iA->getPercent(), 1.f); + + // Reset + animationTree.setConfigForNode("default", 1, 1, "default"); + + // + // Test callbacks + // + int beginCallbackRan = 0; + int updateCallbackRan = 0; + int endCallbackRan = 0; + s.m_iA->setCallbackOnBegin([&beginCallbackRan](WP pav) { beginCallbackRan++; }); + s.m_iA->setUpdateCallback([&updateCallbackRan](WP pav) { updateCallbackRan++; }); + s.m_iA->setCallbackOnEnd([&endCallbackRan](WP pav) { endCallbackRan++; }, false); + + s.m_iA->setValueAndWarp(42); + + EXPECT(beginCallbackRan, 0); + EXPECT(updateCallbackRan, 1); + EXPECT(endCallbackRan, 2); // first called when setting the callback, then when warping. + + *s.m_iA = 1337; + while (pAnimationManager->shouldTickForNext()) { + pAnimationManager->tick(); + } + + EXPECT(beginCallbackRan, 1); + EXPECT(updateCallbackRan > 2, true); + EXPECT(endCallbackRan, 3); + + std::vector> vars; + for (int i = 0; i < 10; i++) { + vars.resize(vars.size() + 1); + pAnimationManager->createAnimation(1, vars.back(), "default"); + *vars.back() = 1337; + } + + // test adding / removing vars during a tick + s.m_iA->resetAllCallbacks(); + s.m_iA->setUpdateCallback([&vars](WP v) { + if (v.lock() != vars.back()) + vars.back()->warp(); + }); + s.m_iA->setCallbackOnEnd([&s, &vars](auto) { + vars.resize(vars.size() + 1); + pAnimationManager->createAnimation(1, vars.back(), "default"); + *vars.back() = 1337; + }); + + *s.m_iA = 1000000; + + while (pAnimationManager->shouldTickForNext()) { + pAnimationManager->tick(); + } + + EXPECT(s.m_iA->value(), 1000000); + // 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); + + // test one-time callbacks + s.m_iA->resetAllCallbacks(); + s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, true); + + EXPECT(endCallbackRan, 4); + + s.m_iA->setValueAndWarp(10); + + EXPECT(endCallbackRan, 4); + EXPECT(s.m_iA->value(), 10); + + // test warp + *s.m_iA = 3; + s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, false); + + s.m_iA->warp(false); + EXPECT(endCallbackRan, 4); + + *s.m_iA = 4; + s.m_iA->warp(true); + EXPECT(endCallbackRan, 5); + + // test getCurveValue + *s.m_iA = 0; + EXPECT(s.m_iA->getCurveValue(), 0.f); + s.m_iA->warp(); + EXPECT(s.m_iA->getCurveValue(), 1.f); + EXPECT(endCallbackRan, 6); + + // test end callback readding the var + *s.m_iA = 5; + s.m_iA->setCallbackOnEnd([&endCallbackRan](WP v) { + endCallbackRan++; + const auto PAV = dynamic_cast*>(v.lock().get()); + + *PAV = 10; + PAV->setCallbackOnEnd([&endCallbackRan](WP v) { endCallbackRan++; }); + }); + + while (pAnimationManager->shouldTickForNext()) { + pAnimationManager->tick(); + } + + EXPECT(endCallbackRan, 8); + EXPECT(s.m_iA->value(), 10); + + // Test duplicate active anim vars are not allowed + { + EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); + PANIMVAR a; + pAnimationManager->createAnimation(1, a, "default"); + EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); + *a = 10; + EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 1); + *a = 20; + EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 1); + a->warp(); + EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); + EXPECT(a->value(), 20); + } + + // Test no crash when animation manager gets destroyed + { + PANIMVAR a; + pAnimationManager->createAnimation(1, a, "default"); + *a = 10; + pAnimationManager.reset(); + EXPECT(a->isAnimationManagerDead(), true); + a->setValueAndWarp(11); + EXPECT(a->value(), 11); + *a = 12; + a->warp(); + EXPECT(a->value(), 12); + *a = 13; + } // a gets destroyed + + EXPECT(pAnimationManager.get(), nullptr); + + return ret; +} diff --git a/tests/filedescriptor.cpp b/tests/filedescriptor.cpp new file mode 100644 index 0000000..c2a2c79 --- /dev/null +++ b/tests/filedescriptor.cpp @@ -0,0 +1,49 @@ +#include +#include "shared.hpp" +#include +#include +#include + +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; +} diff --git a/tests/math.cpp b/tests/math.cpp index 6c80151..a7b8090 100644 --- a/tests/math.cpp +++ b/tests/math.cpp @@ -88,7 +88,7 @@ int main(int argc, char** argv, char** envp) { // Test matrices { - Mat3x3 jeremy = Mat3x3::outputProjection({1920, 1080}, HYPRUTILS_TRANSFORM_FLIPPED_90); + 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{0, 0.46296296, 0, 0.3125, 0, 0, 19.84375, 36.055557, 1}; diff --git a/tests/memory.cpp b/tests/memory.cpp index 805ef42..2c43303 100644 --- a/tests/memory.cpp +++ b/tests/memory.cpp @@ -1,29 +1,56 @@ #include +#include #include "shared.hpp" +#include using namespace Hyprutils::Memory; #define SP CSharedPointer #define WP CWeakPointer +#define UP CUniquePointer int main(int argc, char** argv, char** envp) { - SP intPtr = makeShared(10); + SP intPtr = makeShared(10); + SP intPtr2 = makeShared(1337); + UP intUnique = makeUnique(420); - int ret = 0; + int ret = 0; EXPECT(*intPtr, 10); EXPECT(intPtr.strongRef(), 1); + EXPECT(*intUnique, 420); - WP weak = intPtr; + WP weak = intPtr; + WP weakUnique = intUnique; EXPECT(*intPtr, 10); EXPECT(intPtr.strongRef(), 1); - EXPECT(*weak.get(), 10); + EXPECT(*weak, 10); EXPECT(weak.expired(), false); + EXPECT(*weakUnique, 420); + EXPECT(weakUnique.expired(), false); + EXPECT(intUnique.impl_->wref(), 1); - intPtr = {}; + SP sharedFromUnique = weakUnique.lock(); + EXPECT(sharedFromUnique, nullptr); + + std::vector> 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); return ret; } \ No newline at end of file diff --git a/tests/os.cpp b/tests/os.cpp new file mode 100644 index 0000000..d6582dd --- /dev/null +++ b/tests/os.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include +#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{""}); + + CProcess process2("sh", {"-c", "while true; do sleep 1; done;"}); + + EXPECT(process2.runAsync(), true); + EXPECT(getpgid(process2.pid()) >= 0, true); + + kill(process2.pid(), SIGKILL); + + return ret; +} \ No newline at end of file diff --git a/tests/shared.hpp b/tests/shared.hpp index 131764c..33109f8 100644 --- a/tests/shared.hpp +++ b/tests/shared.hpp @@ -13,7 +13,7 @@ namespace Colors { #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"; \ + 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"; \ diff --git a/tests/signal.cpp b/tests/signal.cpp index e76e427..3d42588 100644 --- a/tests/signal.cpp +++ b/tests/signal.cpp @@ -6,13 +6,11 @@ using namespace Hyprutils::Signal; using namespace Hyprutils::Memory; int main(int argc, char** argv, char** envp) { - int ret = 0; + int ret = 0; CSignal signal; - int data = 0; - auto listener = signal.registerListener([&] (std::any d) { - data = 1; - }); + int data = 0; + auto listener = signal.registerListener([&]([[maybe_unused]] std::any d) { data = 1; }); signal.emit();