Update upstream source from tag 'upstream/0.5.1'

Update to upstream version '0.5.1'
with Debian dir 89b2508bdd
This commit is contained in:
alan (NyxTrail) 2025-03-02 09:46:21 +00:00
commit a2ebf2d72c
41 changed files with 2219 additions and 195 deletions

101
.clang-tidy Normal file
View File

@ -0,0 +1,101 @@
WarningsAsErrors: '*'
HeaderFilterRegex: '.*\.hpp'
FormatStyle: file
Checks: >
-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-forward-declararion-namespace,
-bugprone-forward-declararion-namespace,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
-bugprone-branch-clone,
-bugprone-assignment-in-if-condition,
concurrency-*,
-concurrency-mt-unsafe,
cppcoreguidelines-*,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-explicit-virtual-functions,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-macro-to-enum,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-pro-type-reinterpret-cast,
google-global-names-in-headers,
-google-readability-casting,
google-runtime-operator,
misc-*,
-misc-unused-parameters,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-include-cleaner,
-misc-use-anonymous-namespace,
-misc-const-correctness,
modernize-*,
-modernize-return-braced-init-list,
-modernize-use-trailing-return-type,
-modernize-use-using,
-modernize-use-override,
-modernize-avoid-c-arrays,
-modernize-macro-to-enum,
-modernize-loop-convert,
-modernize-use-nodiscard,
-modernize-pass-by-value,
-modernize-use-auto,
performance-*,
-performance-avoid-endl,
-performance-unnecessary-value-param,
portability-std-allocator-const,
readability-*,
-readability-function-cognitive-complexity,
-readability-function-size,
-readability-identifier-length,
-readability-magic-numbers,
-readability-uppercase-literal-suffix,
-readability-braces-around-statements,
-readability-redundant-access-specifiers,
-readability-else-after-return,
-readability-container-data-pointer,
-readability-implicit-bool-conversion,
-readability-avoid-nested-conditional-operator,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-avoid-const-params-in-decls,
-readability-named-parameter,
-readability-convert-member-functions-to-static,
-readability-qualified-auto,
-readability-make-member-function-const,
-readability-isolate-declaration,
-readability-inconsistent-declaration-parameter-name,
-clang-diagnostic-error,
CheckOptions:
performance-for-range-copy.WarnOnAllAutoCopies: true
performance-inefficient-string-concatenation.StrictMode: true
readability-braces-around-statements.ShortStatementLines: 0
readability-identifier-naming.ClassCase: CamelCase
readability-identifier-naming.ClassIgnoredRegexp: I.*
readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!?
readability-identifier-naming.EnumCase: CamelCase
readability-identifier-naming.EnumPrefix: e
readability-identifier-naming.EnumConstantCase: UPPER_CASE
readability-identifier-naming.FunctionCase: camelBack
readability-identifier-naming.NamespaceCase: CamelCase
readability-identifier-naming.NamespacePrefix: N
readability-identifier-naming.StructPrefix: S
readability-identifier-naming.StructCase: CamelCase

View File

@ -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

View File

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

View File

@ -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
```

View File

@ -1 +1 @@
0.2.3
0.5.1

View File

@ -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": {

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,30 @@
#pragma once
#include <array>
#include <vector>
#include "../math/Vector2D.hpp"
namespace Hyprutils {
namespace Animation {
constexpr int BAKEDPOINTS = 255;
constexpr float INVBAKEDPOINTS = 1.f / BAKEDPOINTS;
/* An implementation of a cubic bezier curve. */
class CBezierCurve {
public:
/* Calculates a cubic bezier curve based on 2 control points (EXCLUDES the 0,0 and 1,1 points). */
void setup(const std::array<Hyprutils::Math::Vector2D, 2>& points);
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<Hyprutils::Math::Vector2D> m_vPoints;
std::array<Hyprutils::Math::Vector2D, BAKEDPOINTS> m_aPointsBaked;
};
}
}

View File

@ -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<float, 9> getMatrix() const;
/* create a box projection matrix */

View File

@ -102,7 +102,7 @@ namespace Hyprutils {
}
// absolutely ridiculous formatter spec parsing
#define AQ_FORMAT_PARSE(specs__, type__) \
#define AQ_FORMAT_PARSE(specs__, type__) \
template <typename FormatContext> \
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<Hyprutils::Math::Vector2D, CharT> : std::formatter<CharT>
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 <typename FormatContext>
auto format(const Hyprutils::Math::Vector2D& vec, FormatContext& ctx) const {

View File

@ -0,0 +1,119 @@
#pragma once
#include <memory>
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 <typename T>
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<T> __deleter{};
//
virtual void inc() noexcept {
_ref++;
}
virtual void dec() noexcept {
_ref--;
}
virtual void incWeak() noexcept {
_weak++;
}
virtual void decWeak() noexcept {
_weak--;
}
virtual unsigned int ref() noexcept {
return _ref;
}
virtual unsigned int wref() noexcept {
return _weak;
}
virtual void destroy() noexcept {
_destroy();
}
virtual bool destroying() noexcept {
return _destroying;
}
virtual bool lockable() noexcept {
return _lockable;
}
virtual bool dataNonNull() noexcept {
return _data != nullptr;
}
virtual void* getData() noexcept {
return _data;
}
virtual ~impl() {
destroy();
}
};
}
}
}

View File

@ -1,8 +1,6 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#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 <typename T>
class impl : public impl_base {
public:
impl(T* data) noexcept : _data(data) {
;
}
/* strong refcount */
unsigned int _ref = 0;
/* weak refcount */
unsigned int _weak = 0;
T* _data = nullptr;
friend void swap(impl*& a, impl*& b) {
impl* tmp = a;
a = b;
b = tmp;
}
/* if the destructor was called,
creating shared_ptrs is no longer valid */
bool _destroying = false;
void _destroy() {
if (!_data || _destroying)
return;
// first, we destroy the data, but keep the pointer.
// this way, weak pointers will still be able to
// reference and use, but no longer create shared ones.
_destroying = true;
__deleter(_data);
// now, we can reset the data and call it a day.
_data = nullptr;
_destroying = false;
}
std::default_delete<T> __deleter{};
//
virtual void inc() noexcept {
_ref++;
}
virtual void dec() noexcept {
_ref--;
}
virtual void incWeak() noexcept {
_weak++;
}
virtual void decWeak() noexcept {
_weak--;
}
virtual unsigned int ref() noexcept {
return _ref;
}
virtual unsigned int wref() noexcept {
return _weak;
}
virtual void destroy() noexcept {
_destroy();
}
virtual bool destroying() noexcept {
return _destroying;
}
virtual bool dataNonNull() noexcept {
return _data != nullptr;
}
virtual void* getData() noexcept {
return _data;
}
virtual ~impl() {
destroy();
}
};
};
template <typename T>
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<T>(object);
impl_ = new Impl_::impl<T>(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:
/*

View File

@ -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 <typename T>
class CUniquePointer {
public:
template <typename X>
using validHierarchy = typename std::enable_if<std::is_assignable<CUniquePointer<T>&, X>::value, CUniquePointer&>::type;
template <typename X>
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::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<T>(object, false);
increment();
}
/* creates a shared pointer from a reference */
template <typename U, typename = isConstructible<U>>
CUniquePointer(const CUniquePointer<U>& ref) = delete;
CUniquePointer(const CUniquePointer& ref) = delete;
template <typename U, typename = isConstructible<U>>
CUniquePointer(CUniquePointer<U>&& ref) noexcept {
std::swap(impl_, ref.impl_);
}
CUniquePointer(CUniquePointer&& ref) noexcept {
std::swap(impl_, ref.impl_);
}
/* creates an empty unique pointer with no implementation */
CUniquePointer() noexcept {
; // empty
}
/* creates an empty unique pointer with no implementation */
CUniquePointer(std::nullptr_t) noexcept {
; // empty
}
~CUniquePointer() {
decrement();
}
template <typename U>
validHierarchy<const CUniquePointer<U>&> operator=(const CUniquePointer<U>& rhs) = delete;
CUniquePointer& operator=(const CUniquePointer& rhs) = delete;
template <typename U>
validHierarchy<const CUniquePointer<U>&> operator=(CUniquePointer<U>&& rhs) {
std::swap(impl_, rhs.impl_);
return *this;
}
CUniquePointer& operator=(CUniquePointer&& rhs) {
std::swap(impl_, rhs.impl_);
return *this;
}
operator bool() const {
return impl_;
}
bool operator()(const CUniquePointer& lhs, const CUniquePointer& rhs) const {
return reinterpret_cast<uintptr_t>(lhs.impl_) < reinterpret_cast<uintptr_t>(rhs.impl_);
}
T* operator->() const {
return get();
}
T& operator*() const {
return *get();
}
void reset() {
decrement();
impl_ = nullptr;
}
T* get() const {
return impl_ ? static_cast<T*>(impl_->getData()) : nullptr;
}
Impl_::impl_base* impl_ = nullptr;
private:
/*
no-op if there is no impl_
may delete the stored object if ref == 0
may delete and reset impl_ if ref == 0 and weak == 0
*/
void decrement() {
if (!impl_)
return;
impl_->dec();
// if ref == 0, we can destroy impl
if (impl_->ref() == 0)
destroyImpl();
}
/* no-op if there is no impl_ */
void increment() {
if (!impl_)
return;
impl_->inc();
}
/* destroy the pointed-to object
if able, will also destroy impl */
void destroyImpl() {
// destroy the impl contents
impl_->destroy();
// check for weak refs, if zero, we can also delete impl_
if (impl_->wref() == 0) {
delete impl_;
impl_ = nullptr;
}
}
};
template <typename U, typename... Args>
static CUniquePointer<U> makeUnique(Args&&... args) {
return CUniquePointer<U>(new U(std::forward<Args>(args)...));
}
}
}
template <typename T>
struct std::hash<Hyprutils::Memory::CUniquePointer<T>> {
std::size_t operator()(const Hyprutils::Memory::CUniquePointer<T>& p) const noexcept {
return std::hash<void*>{}(p.impl_);
}
};

View File

@ -1,6 +1,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 <typename U, typename = isConstructible<U>>
CWeakPointer(const CUniquePointer<U>& ref) noexcept {
if (!ref.impl_)
return;
impl_ = ref.impl_;
incrementWeak();
}
/* create a weak ptr from another weak ptr */
template <typename U, typename = isConstructible<U>>
CWeakPointer(const CWeakPointer<U>& ref) noexcept {
@ -119,7 +130,7 @@ namespace Hyprutils {
}
CSharedPointer<T> lock() const {
if (!impl_ || !impl_->dataNonNull() || impl_->destroying())
if (!impl_ || !impl_->dataNonNull() || impl_->destroying() || !impl_->lockable())
return {};
return CSharedPointer<T>(impl_);
@ -138,6 +149,18 @@ namespace Hyprutils {
return impl_ == rhs.impl_;
}
bool operator==(const CUniquePointer<T>& rhs) const {
return impl_ == rhs.impl_;
}
bool operator==(std::nullptr_t) const {
return !valid();
}
bool operator!=(std::nullptr_t) const {
return valid();
}
bool operator()(const CWeakPointer& lhs, const CWeakPointer& rhs) const {
return reinterpret_cast<uintptr_t>(lhs.impl_) < reinterpret_cast<uintptr_t>(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_ */

View File

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

View File

@ -0,0 +1,36 @@
#pragma once
#include <string>
#include <vector>
#include <utility>
#include <sys/types.h>
namespace Hyprutils {
namespace OS {
class CProcess {
public:
/* Creates a process object, doesn't run yet */
CProcess(const std::string& binary_, const std::vector<std::string>& args_);
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<std::string> args;
std::vector<std::pair<std::string, std::string>> env;
pid_t grandchildPid = 0;
};
}
}

View File

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

View File

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

View File

@ -0,0 +1,162 @@
#include <hyprutils/animation/AnimatedVariable.hpp>
#include <hyprutils/animation/AnimationManager.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
using namespace Hyprutils::Animation;
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
void CBaseAnimatedVariable::create(CAnimationManager* pManager, int typeInfo, SP<CBaseAnimatedVariable> 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::milliseconds>(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();
}

View File

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

View File

@ -0,0 +1,114 @@
#include <algorithm>
#include <hyprutils/animation/AnimationManager.hpp>
#include <hyprutils/animation/AnimatedVariable.hpp>
using namespace Hyprutils::Animation;
using namespace Hyprutils::Math;
using namespace Hyprutils::Memory;
using namespace Hyprutils::Signal;
#define SP CSharedPointer
#define WP CWeakPointer
const std::array<Vector2D, 2> DEFAULTBEZIERPOINTS = {Vector2D(0.0, 0.75), Vector2D(0.15, 1.0)};
CAnimationManager::CAnimationManager() {
const auto BEZIER = makeShared<CBezierCurve>();
BEZIER->setup(DEFAULTBEZIERPOINTS);
m_mBezierCurves["default"] = BEZIER;
m_events = makeUnique<SAnimationManagerSignals>();
m_listeners = makeUnique<SAnimVarListeners>();
m_listeners->connect = m_events->connect.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<WP<CBaseAnimatedVariable>>(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<WP<CBaseAnimatedVariable>>(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<CBezierCurve>();
BEZIER->setup(DEFAULTBEZIERPOINTS);
m_mBezierCurves["default"] = BEZIER;
}
void CAnimationManager::addBezierWithName(std::string name, const Vector2D& p1, const Vector2D& p2) {
const auto BEZIER = makeShared<CBezierCurve>();
BEZIER->setup({
p1,
p2,
});
m_mBezierCurves[name] = BEZIER;
}
bool CAnimationManager::shouldTickForNext() {
return !m_vActiveAnimatedVariables.empty();
}
void CAnimationManager::tickDone() {
rotateActive();
}
void CAnimationManager::rotateActive() {
std::vector<CWeakPointer<CBaseAnimatedVariable>> active;
active.reserve(m_vActiveAnimatedVariables.size()); // avoid reallocations
for (auto const& av : m_vActiveAnimatedVariables) {
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<CBezierCurve> CAnimationManager::getBezier(const std::string& name) {
const auto BEZIER = std::ranges::find_if(m_mBezierCurves, [&](const auto& other) { return other.first == name; });
return BEZIER == m_mBezierCurves.end() ? m_mBezierCurves["default"] : BEZIER->second;
}
const std::unordered_map<std::string, SP<CBezierCurve>>& CAnimationManager::getAllBeziers() {
return m_mBezierCurves;
}
CWeakPointer<CAnimationManager::SAnimationManagerSignals> CAnimationManager::getSignals() const {
return m_events;
}

View File

@ -0,0 +1,78 @@
#include <hyprutils/animation/BezierCurve.hpp>
#include <array>
#include <cmath>
using namespace Hyprutils::Animation;
using namespace Hyprutils::Math;
void CBezierCurve::setup(const std::array<Vector2D, 2>& 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);
}

View File

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

View File

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

View File

@ -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 {

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

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

232
src/os/Process.cpp Normal file
View File

@ -0,0 +1,232 @@
#include <hyprutils/os/Process.hpp>
using namespace Hyprutils::OS;
#include <csignal>
#include <cstdio>
#include <unistd.h>
#include <cstring>
#include <array>
#include <thread>
#include <sys/fcntl.h>
#include <sys/wait.h>
#include <sys/poll.h>
Hyprutils::OS::CProcess::CProcess(const std::string& binary_, const std::vector<std::string>& 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<const char*> 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<char, 1024> buf;
buf.fill(0);
// wait for read
ssize_t ret = 0;
int fdFlags = fcntl(outPipe[0], F_GETFL, 0);
if (fcntl(outPipe[0], F_SETFL, fdFlags | O_NONBLOCK) < 0)
return false;
fdFlags = fcntl(errPipe[0], F_GETFL, 0);
if (fcntl(errPipe[0], F_SETFL, fdFlags | O_NONBLOCK) < 0)
return false;
pollfd pollfds[2] = {
{.fd = outPipe[0], .events = POLLIN, .revents = 0},
{.fd = errPipe[0], .events = POLLIN, .revents = 0},
};
while (1337) {
int ret = poll(pollfds, 2, 5000);
if (ret < 0) {
if (errno == EINTR)
continue;
return false;
}
bool hupd = false;
for (size_t i = 0; i < 2; ++i) {
if (pollfds[i].revents & POLLHUP) {
hupd = true;
break;
}
}
if (hupd)
break;
if (pollfds[0].revents & POLLIN) {
while ((ret = read(outPipe[0], buf.data(), 1023)) > 0) {
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<const char*> 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;
}

View File

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

View File

@ -18,6 +18,7 @@ void Hyprutils::Signal::CSignal::emit(std::any data) {
}
std::vector<CStaticSignalListener*> 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<v
// housekeeping: remove any stale listeners
std::erase_if(m_vListeners, [](const auto& other) { return other.expired(); });
return listener;
}
void Hyprutils::Signal::CSignal::registerStaticListener(std::function<void(void*, std::any)> handler, void* owner) {
m_vStaticListeners.emplace_back(std::make_unique<CStaticSignalListener>(handler, owner));
}
}

View File

@ -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) {

View File

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

397
tests/animation.cpp Normal file
View File

@ -0,0 +1,397 @@
#include <hyprutils/animation/AnimationConfig.hpp>
#include <hyprutils/animation/AnimationManager.hpp>
#include <hyprutils/animation/AnimatedVariable.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#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 <typename VarType>
using CAnimatedVariable = CGenericAnimatedVariable<VarType, EmtpyContext>;
template <typename VarType>
using PANIMVAR = SP<CAnimatedVariable<VarType>>;
template <typename VarType>
using PANIMVARREF = WP<CAnimatedVariable<VarType>>;
enum eAVTypes {
INT = 1,
TEST,
};
struct SomeTestType {
bool done = false;
bool operator==(const SomeTestType& other) const {
return done == other.done;
}
SomeTestType& operator=(const SomeTestType& other) {
done = other.done;
return *this;
}
};
CAnimationConfigTree animationTree;
class CMyAnimationManager : public CAnimationManager {
public:
void tick() {
for (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<CAnimatedVariable<int>*>(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<CAnimatedVariable<SomeTestType>*>(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 <typename VarType>
void createAnimation(const VarType& v, PANIMVAR<VarType>& av, const std::string& animationConfigName) {
constexpr const eAVTypes EAVTYPE = std::is_same_v<VarType, int> ? eAVTypes::INT : eAVTypes::TEST;
const auto PAV = makeShared<CGenericAnimatedVariable<VarType, EmtpyContext>>();
PAV->create(EAVTYPE, static_cast<CAnimationManager*>(this), PAV, v);
PAV->setConfig(animationTree.getConfig(animationConfigName));
av = std::move(PAV);
}
virtual void scheduleTick() {
;
}
virtual void onTicked() {
;
}
};
UP<CMyAnimationManager> pAnimationManager;
class Subject {
public:
Subject(const int& a, const int& b) {
pAnimationManager->createAnimation(a, m_iA, "default");
pAnimationManager->createAnimation(b, m_iB, "internal");
pAnimationManager->createAnimation({}, m_iC, "default");
}
PANIMVAR<int> m_iA;
PANIMVAR<int> m_iB;
PANIMVAR<SomeTestType> m_iC;
};
int config() {
pAnimationManager = makeUnique<CMyAnimationManager>();
int ret = 0;
animationTree.createNode("global");
animationTree.createNode("internal");
animationTree.createNode("foo", "internal");
animationTree.createNode("default", "global");
animationTree.createNode("bar", "default");
/*
internal
foo
global
default
bar
*/
auto barCfg = animationTree.getConfig("bar");
auto internalCfg = animationTree.getConfig("internal");
// internal is a root node and should point to itself
EXPECT(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<CBaseAnimatedVariable> pav) { beginCallbackRan++; });
s.m_iA->setUpdateCallback([&updateCallbackRan](WP<CBaseAnimatedVariable> pav) { updateCallbackRan++; });
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> pav) { endCallbackRan++; }, false);
s.m_iA->setValueAndWarp(42);
EXPECT(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<PANIMVAR<int>> vars;
for (int i = 0; i < 10; i++) {
vars.resize(vars.size() + 1);
pAnimationManager->createAnimation(1, vars.back(), "default");
*vars.back() = 1337;
}
// test adding / removing vars during a tick
s.m_iA->resetAllCallbacks();
s.m_iA->setUpdateCallback([&vars](WP<CBaseAnimatedVariable> v) {
if (v.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<CBaseAnimatedVariable> v) {
endCallbackRan++;
const auto PAV = dynamic_cast<CAnimatedVariable<int>*>(v.lock().get());
*PAV = 10;
PAV->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> 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<int> 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<int> 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;
}

49
tests/filedescriptor.cpp Normal file
View File

@ -0,0 +1,49 @@
#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;
}

View File

@ -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<float, 9>{0, 0.46296296, 0, 0.3125, 0, 0, 19.84375, 36.055557, 1};

View File

@ -1,29 +1,56 @@
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#include "shared.hpp"
#include <vector>
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
#define UP CUniquePointer
int main(int argc, char** argv, char** envp) {
SP<int> intPtr = makeShared<int>(10);
SP<int> intPtr = makeShared<int>(10);
SP<int> intPtr2 = makeShared<int>(1337);
UP<int> intUnique = makeUnique<int>(420);
int ret = 0;
int ret = 0;
EXPECT(*intPtr, 10);
EXPECT(intPtr.strongRef(), 1);
EXPECT(*intUnique, 420);
WP<int> weak = intPtr;
WP<int> weak = intPtr;
WP<int> 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<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);
return ret;
}

30
tests/os.cpp Normal file
View File

@ -0,0 +1,30 @@
#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{""});
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;
}

View File

@ -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"; \

View File

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