Merge branch 'debian/latest' into 'debian/latest'

new upstream version 0.8.4

See merge request NyxTrail/hyprutils!3
This commit is contained in:
Carl Keinath 2025-09-17 21:25:31 +00:00
commit 3ea49697a9
47 changed files with 1487 additions and 198 deletions

11
.editorconfig Normal file
View File

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

View File

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

2
.gitignore vendored
View File

@ -34,6 +34,7 @@
build/
.vscode/
.cache/
.direnv/
.cmake/
CMakeCache.txt
@ -44,3 +45,4 @@ Makefile
cmake_install.cmake
compile_commands.json
hyprutils.pc
.envrc

View File

@ -50,7 +50,7 @@ target_include_directories(
PUBLIC "./include"
PRIVATE "./src")
set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION}
SOVERSION 6)
SOVERSION 7)
target_link_libraries(hyprutils PkgConfig::deps)
# tests

View File

@ -1 +1 @@
0.7.0
0.8.4

10
debian/changelog vendored
View File

@ -1,3 +1,13 @@
hyprutils (0.8.4-0.1) unstable; urgency=medium
* Non-maintainer upload.
* New upstream version 0.8.4. (Closes: #1090839)
* Breaking ABI changes: updated soversion to 7.
* Added autopkgtests to run upstream tests.
* d/watch: migrated to Version 5.
-- Carl Keinath <carl.keinath@gmail.com> Wed, 17 Sep 2025 23:04:34 +0200
hyprutils (0.7.0-1) unstable; urgency=medium
[ alan (NyxTrail) ]

5
debian/control vendored
View File

@ -2,7 +2,6 @@ Source: hyprutils
Section: libs
Priority: optional
Maintainer: Alan M Varghese (NyxTrail) <alan@digistorm.in>
Rules-Requires-Root: no
Build-Depends:
debhelper-compat (= 13),
cmake,
@ -18,7 +17,7 @@ Section: libdevel
Architecture: any
Multi-Arch: same
Depends:
libhyprutils6 (= ${binary:Version}),
libhyprutils7 (= ${binary:Version}),
${misc:Depends},
Description: Utilities used across the Hyprland ecosystem (development files)
Hyprutils is a small C++ library for utilities used across the Hyprland
@ -26,7 +25,7 @@ Description: Utilities used across the Hyprland ecosystem (development files)
.
This package contains the development files.
Package: libhyprutils6
Package: libhyprutils7
Architecture: any
Multi-Arch: same
Depends:

4
debian/rules vendored
View File

@ -2,9 +2,5 @@
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
ifeq ($(DEB_HOST_ARCH),i386)
export DEB_CXXFLAGS_MAINT_APPEND += -ffloat-store
endif
%:
dh $@

74
debian/tests/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,74 @@
cmake_minimum_required(VERSION 3.19)
project(hyprutils)
include(CTest)
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
)
enable_testing()
find_package(PkgConfig REQUIRED)
pkg_check_modules(deps REQUIRED IMPORTED_TARGET pixman-1)
find_library(HYPRUTILS_LIB hyprutils REQUIRED)
add_executable(hyprutils_memory "tests/memory.cpp")
target_link_libraries(hyprutils_memory PRIVATE ${HYPRUTILS_LIB} PkgConfig::deps)
add_test(
NAME "Memory"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_memory "memory")
add_executable(hyprutils_string "tests/string.cpp")
target_link_libraries(hyprutils_string PRIVATE ${HYPRUTILS_LIB} PkgConfig::deps)
add_test(
NAME "String"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_string "string")
add_executable(hyprutils_signal "tests/signal.cpp")
target_link_libraries(hyprutils_signal PRIVATE ${HYPRUTILS_LIB} PkgConfig::deps)
add_test(
NAME "Signal"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_signal "signal")
add_executable(hyprutils_math "tests/math.cpp")
target_link_libraries(hyprutils_math PRIVATE ${HYPRUTILS_LIB} PkgConfig::deps)
add_test(
NAME "Math"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_math "math")
add_executable(hyprutils_os "tests/os.cpp")
target_link_libraries(hyprutils_os PRIVATE ${HYPRUTILS_LIB} PkgConfig::deps)
add_test(
NAME "OS"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_os "os")
add_executable(hyprutils_filedescriptor "tests/filedescriptor.cpp")
target_link_libraries(hyprutils_filedescriptor PRIVATE ${HYPRUTILS_LIB} PkgConfig::deps)
add_test(
NAME "Filedescriptor"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_filedescriptor "filedescriptor")
add_executable(hyprutils_animation "tests/animation.cpp")
target_link_libraries(hyprutils_animation PRIVATE ${HYPRUTILS_LIB} PkgConfig::deps)
add_test(
NAME "Animation"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_animation "utils")

2
debian/tests/control vendored Normal file
View File

@ -0,0 +1,2 @@
Tests: upstream-tests
Depends: @, @builddeps@

14
debian/tests/upstream-tests vendored Normal file
View File

@ -0,0 +1,14 @@
#!/bin/sh
set -eu
mkdir -p "$AUTOPKGTEST_TMP"
cp -r tests "$AUTOPKGTEST_TMP"/tests
cp debian/tests/CMakeLists.txt "$AUTOPKGTEST_TMP"/CMakeLists.txt
mkdir -p "$AUTOPKGTEST_TMP"/build
cd "$AUTOPKGTEST_TMP"/build
cmake ..
cmake --build .
ctest --output-on-failure

9
debian/watch vendored
View File

@ -1,4 +1,5 @@
version=4
opts="filenamemangle=s%(?:.*?)?v?(\d[\d.]*)\.tar\.gz%@PACKAGE@-$1.tar.gz%" \
https://github.com/hyprwm/hyprutils/tags \
(?:.*?/)?v?(\d[\d.]*)\.tar\.gz
Version: 5
Template: Github
Owner: hyprwm
Project: hyprutils

View File

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1734119587,
"narHash": "sha256-AKU6qqskl0yf2+JdRdD0cfxX4b9x3KKV5RqA6wijmPM=",
"lastModified": 1748929857,
"narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3566ab7246670a43abd2ffa913cc62dad9cdf7d5",
"rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4",
"type": "github"
},
"original": {

View File

@ -18,28 +18,12 @@
localSystem.system = system;
overlays = with self.overlays; [hyprutils];
});
mkDate = longDate: (lib.concatStringsSep "-" [
(builtins.substring 0 4 longDate)
(builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
version = lib.removeSuffix "\n" (builtins.readFile ./VERSION);
in {
overlays = {
default = self.overlays.hyprutils;
hyprutils = final: prev: {
hyprutils = final.callPackage ./nix/default.nix {
stdenv = final.gcc14Stdenv;
version = version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
};
hyprutils-with-tests = final.hyprutils.override {doCheck = true;};
};
};
overlays = import ./nix/overlays.nix {inherit self lib;};
packages = eachSystem (system: {
default = self.packages.${system}.hyprutils;
inherit (pkgsFor.${system}) hyprutils hyprutils-with-tests;
inherit (pkgsFor.${system}) hyprutils hyprutils-debug hyprutils-with-tests;
});
formatter = eachSystem (system: pkgsFor.${system}.alejandra);

View File

@ -35,8 +35,8 @@ namespace Hyprutils {
const std::unordered_map<std::string, Memory::CSharedPointer<CBezierCurve>>& getAllBeziers();
struct SAnimationManagerSignals {
Signal::CSignal connect; // WP<CBaseAnimatedVariable>
Signal::CSignal disconnect; // WP<CBaseAnimatedVariable>
Signal::CSignalT<Memory::CWeakPointer<CBaseAnimatedVariable>> connect;
Signal::CSignalT<Memory::CWeakPointer<CBaseAnimatedVariable>> disconnect;
};
Memory::CWeakPointer<SAnimationManagerSignals> getSignals() const;
@ -48,9 +48,6 @@ namespace Hyprutils {
bool m_bTickScheduled = false;
void onConnect(std::any data);
void onDisconnect(std::any data);
struct SAnimVarListeners {
Signal::CHyprSignalListener connect;
Signal::CHyprSignalListener disconnect;

View File

@ -20,6 +20,9 @@ namespace Hyprutils {
float getXForT(float const& t) const;
float getYForPoint(float const& x) const;
/* this INCLUDES the 0,0 and 1,1 points. */
const std::vector<Hyprutils::Math::Vector2D>& getControlPoints() const;
private:
/* this INCLUDES the 0,0 and 1,1 points. */
std::vector<Hyprutils::Math::Vector2D> m_vPoints;

View File

@ -1,4 +1,6 @@
#pragma once
#include "hyprutils/memory/Casts.hpp"
#include <cstdint>
namespace Hyprutils::Math {
@ -18,7 +20,7 @@ namespace Hyprutils::Math {
CEdges() = default;
CEdges(eEdges edges) : m_edges(edges) {}
CEdges(uint8_t edges) : m_edges(static_cast<eEdges>(edges)) {}
CEdges(uint8_t edges) : m_edges(Memory::sc<eEdges>(edges)) {}
bool operator==(const CEdges& other) {
return m_edges == other.m_edges;
@ -80,28 +82,28 @@ namespace Hyprutils::Math {
* @param top The state the top edge should be set to.
*/
void setTop(bool top) {
m_edges = static_cast<eEdges>((m_edges & ~TOP) | (TOP * top));
m_edges = Memory::sc<eEdges>((m_edges & ~TOP) | (TOP * top));
}
/**
* @param left The state the left edge should be set to.
*/
void setLeft(bool left) {
m_edges = static_cast<eEdges>((m_edges & ~LEFT) | (LEFT * left));
m_edges = Memory::sc<eEdges>((m_edges & ~LEFT) | (LEFT * left));
}
/**
* @param bottom The state the bottom edge should be set to.
*/
void setBottom(bool bottom) {
m_edges = static_cast<eEdges>((m_edges & ~BOTTOM) | (BOTTOM * bottom));
m_edges = Memory::sc<eEdges>((m_edges & ~BOTTOM) | (BOTTOM * bottom));
}
/**
* @param right The state the right edge should be set to.
*/
void setRight(bool right) {
m_edges = static_cast<eEdges>((m_edges & ~RIGHT) | (RIGHT * right));
m_edges = Memory::sc<eEdges>((m_edges & ~RIGHT) | (RIGHT * right));
}
eEdges m_edges = NONE;

View File

@ -21,17 +21,21 @@ namespace Hyprutils {
CRegion(pixman_box32_t* box);
CRegion(const CRegion&);
CRegion(CRegion&&);
CRegion(CRegion&&) noexcept;
~CRegion();
CRegion& operator=(CRegion&& other) {
pixman_region32_copy(&m_rRegion, other.pixman());
CRegion& operator=(CRegion&& other) noexcept {
if (this != &other)
pixman_region32_copy(&m_rRegion, other.pixman());
return *this;
}
CRegion& operator=(CRegion& other) {
pixman_region32_copy(&m_rRegion, other.pixman());
CRegion& operator=(const CRegion& other) {
if (this != &other)
pixman_region32_copy(&m_rRegion, other.pixman());
return *this;
}
@ -58,12 +62,24 @@ namespace Hyprutils {
CRegion copy() const;
std::vector<pixman_box32_t> getRects() const;
template <typename T>
void forEachRect(T&& cb) const {
int rectsNum = 0;
const auto* rects = pixman_region32_rectangles(&m_rRegion, &rectsNum);
for (int i = 0; i < rectsNum; ++i) {
std::forward<T>(cb)(rects[i]);
}
}
//
pixman_region32_t* pixman() {
return &m_rRegion;
}
const pixman_region32_t* pixman() const {
return &m_rRegion;
}
private:
pixman_region32_t m_rRegion;
};

View File

@ -1,5 +1,7 @@
#pragma once
#include <hyprutils/math/Misc.hpp>
#include <format>
#include <string>
@ -97,6 +99,8 @@ namespace Hyprutils {
Vector2D round() const;
Vector2D getComponentMax(const Vector2D& other) const;
Vector2D transform(eTransform transform, const Vector2D& monitorSize) const;
};
}
}

View File

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

View File

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

View File

@ -1,5 +1,6 @@
#pragma once
#include <cstdint>
#include <memory>
namespace Hyprutils {

View File

@ -1,6 +1,8 @@
#pragma once
#include <cstdint>
#include "ImplBase.hpp"
#include "Casts.hpp"
/*
This is a custom impl of std::shared_ptr.
@ -59,9 +61,7 @@ namespace Hyprutils {
}
/* creates an empty shared pointer with no implementation */
CSharedPointer() noexcept {
; // empty
}
CSharedPointer() noexcept = default;
/* creates an empty shared pointer with no implementation */
CSharedPointer(std::nullptr_t) noexcept {
@ -113,11 +113,11 @@ namespace Hyprutils {
}
bool operator()(const CSharedPointer& lhs, const CSharedPointer& rhs) const {
return reinterpret_cast<uintptr_t>(lhs.impl_) < reinterpret_cast<uintptr_t>(rhs.impl_);
return rc<uintptr_t>(lhs.impl_) < rc<uintptr_t>(rhs.impl_);
}
bool operator<(const CSharedPointer& rhs) const {
return reinterpret_cast<uintptr_t>(impl_) < reinterpret_cast<uintptr_t>(rhs.impl_);
return rc<uintptr_t>(impl_) < rc<uintptr_t>(rhs.impl_);
}
T* operator->() const {
@ -134,7 +134,7 @@ namespace Hyprutils {
}
T* get() const {
return impl_ ? static_cast<T*>(impl_->getData()) : nullptr;
return impl_ ? sc<T*>(impl_->getData()) : nullptr;
}
unsigned int strongRef() const {
@ -182,7 +182,7 @@ namespace Hyprutils {
};
template <typename U, typename... Args>
static CSharedPointer<U> makeShared(Args&&... args) {
[[nodiscard]] inline CSharedPointer<U> makeShared(Args&&... args) {
return CSharedPointer<U>(new U(std::forward<Args>(args)...));
}
@ -198,4 +198,4 @@ struct std::hash<Hyprutils::Memory::CSharedPointer<T>> {
std::size_t operator()(const Hyprutils::Memory::CSharedPointer<T>& p) const noexcept {
return std::hash<void*>{}(p.impl_);
}
};
};

View File

@ -1,6 +1,7 @@
#pragma once
#include "ImplBase.hpp"
#include "Casts.hpp"
/*
This is a custom impl of std::unique_ptr.
@ -41,9 +42,7 @@ namespace Hyprutils {
}
/* creates an empty unique pointer with no implementation */
CUniquePointer() noexcept {
; // empty
}
CUniquePointer() noexcept = default;
/* creates an empty unique pointer with no implementation */
CUniquePointer(std::nullptr_t) noexcept {
@ -74,7 +73,7 @@ namespace Hyprutils {
}
bool operator()(const CUniquePointer& lhs, const CUniquePointer& rhs) const {
return reinterpret_cast<uintptr_t>(lhs.impl_) < reinterpret_cast<uintptr_t>(rhs.impl_);
return rc<uintptr_t>(lhs.impl_) < rc<uintptr_t>(rhs.impl_);
}
T* operator->() const {
@ -91,7 +90,7 @@ namespace Hyprutils {
}
T* get() const {
return impl_ ? static_cast<T*>(impl_->getData()) : nullptr;
return impl_ ? sc<T*>(impl_->getData()) : nullptr;
}
Impl_::impl_base* impl_ = nullptr;
@ -135,7 +134,7 @@ namespace Hyprutils {
};
template <typename U, typename... Args>
static CUniquePointer<U> makeUnique(Args&&... args) {
[[nodiscard]] inline CUniquePointer<U> makeUnique(Args&&... args) {
return CUniquePointer<U>(new U(std::forward<Args>(args)...));
}
}
@ -146,4 +145,4 @@ 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

@ -2,6 +2,7 @@
#include "./SharedPtr.hpp"
#include "./UniquePtr.hpp"
#include "./Casts.hpp"
/*
This is a Hyprland implementation of std::weak_ptr.
@ -91,7 +92,7 @@ namespace Hyprutils {
/* create a weak ptr from a shared ptr with assignment */
template <typename U>
validHierarchy<const CWeakPointer<U>&> operator=(const CSharedPointer<U>& rhs) {
if (reinterpret_cast<uintptr_t>(impl_) == reinterpret_cast<uintptr_t>(rhs.impl_))
if (rc<uintptr_t>(impl_) == rc<uintptr_t>(rhs.impl_))
return *this;
decrementWeak();
@ -101,9 +102,7 @@ namespace Hyprutils {
}
/* create an empty weak ptr */
CWeakPointer() {
;
}
CWeakPointer() noexcept = default;
~CWeakPointer() {
decrementWeak();
@ -162,15 +161,15 @@ namespace Hyprutils {
}
bool operator()(const CWeakPointer& lhs, const CWeakPointer& rhs) const {
return reinterpret_cast<uintptr_t>(lhs.impl_) < reinterpret_cast<uintptr_t>(rhs.impl_);
return rc<uintptr_t>(lhs.impl_) < rc<uintptr_t>(rhs.impl_);
}
bool operator<(const CWeakPointer& rhs) const {
return reinterpret_cast<uintptr_t>(impl_) < reinterpret_cast<uintptr_t>(rhs.impl_);
return rc<uintptr_t>(impl_) < rc<uintptr_t>(rhs.impl_);
}
T* get() const {
return impl_ ? static_cast<T*>(impl_->getData()) : nullptr;
return impl_ ? sc<T*>(impl_->getData()) : nullptr;
}
T* operator->() const {

View File

@ -13,8 +13,17 @@ namespace Hyprutils {
CProcess(const std::string& binary_, const std::vector<std::string>& args_);
~CProcess();
void addEnv(const std::string& name, const std::string& value);
CProcess(CProcess&) = delete;
CProcess(CProcess&&) = delete;
CProcess(const CProcess&&) = delete;
CProcess(const CProcess&) = delete;
CProcess& operator=(const CProcess&) = delete;
CProcess& operator=(CProcess&&) = delete;
void addEnv(const std::string& name, const std::string& value);
// only for async, sync doesn't make sense
void setStdinFD(int fd);
// only for async, sync doesn't make sense
void setStdoutFD(int fd);
// only for async, sync doesn't make sense
@ -40,4 +49,4 @@ namespace Hyprutils {
impl* m_impl;
};
}
}
}

View File

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

View File

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

View File

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

View File

@ -8,19 +8,23 @@
version ? "git",
doCheck ? false,
debug ? false,
# whether to use the mold linker
# disable this for older machines without SSE4_2 and AVX2 support
withMold ? true,
}: let
inherit (builtins) foldl';
inherit (lib.lists) flatten;
inherit (lib.strings) optionalString;
adapters = flatten [
stdenvAdapters.useMoldLinker
(lib.optional withMold stdenvAdapters.useMoldLinker)
(lib.optional debug stdenvAdapters.keepDebugInfo)
];
customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters;
in
customStdenv.mkDerivation {
pname = "hyprutils";
pname = "hyprutils" + optionalString debug "-debug";
inherit version doCheck;
src = ../.;

23
nix/overlays.nix Normal file
View File

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

View File

@ -20,31 +20,18 @@ CAnimationManager::CAnimationManager() {
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); });
}
m_listeners->connect = m_events->connect.listen([this](const WP<CBaseAnimatedVariable>& animVar) {
if (!m_bTickScheduled)
scheduleTick();
void CAnimationManager::onConnect(std::any data) {
if (!m_bTickScheduled)
scheduleTick();
if (animVar)
m_vActiveAnimatedVariables.emplace_back(animVar);
});
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; }
m_listeners->disconnect = m_events->disconnect.listen([this](const WP<CBaseAnimatedVariable>& animVar) {
if (animVar)
std::erase_if(m_vActiveAnimatedVariables, [&](const auto& other) { return !other || other == animVar; });
});
}
void CAnimationManager::removeAllBeziers() {

View File

@ -1,10 +1,12 @@
#include <hyprutils/animation/BezierCurve.hpp>
#include <hyprutils/memory/Casts.hpp>
#include <array>
#include <cmath>
using namespace Hyprutils::Animation;
using namespace Hyprutils::Math;
using namespace Hyprutils::Memory;
void CBezierCurve::setup(const std::array<Vector2D, 2>& pVec) {
// Avoid reallocations by reserving enough memory upfront
@ -21,7 +23,7 @@ void CBezierCurve::setup(const std::array<Vector2D, 2>& pVec) {
// bake BAKEDPOINTS points for faster lookups
// T -> X ( / BAKEDPOINTS )
for (int i = 0; i < BAKEDPOINTS; ++i) {
float const t = (i + 1) / (float)BAKEDPOINTS;
float const t = (i + 1) / sc<float>(BAKEDPOINTS);
m_aPointsBaked[i] = Vector2D(getXForT(t), getYForT(t));
}
@ -76,3 +78,7 @@ float CBezierCurve::getYForPoint(float const& x) const {
return LOWERPOINT->y + ((UPPERPOINT->y - LOWERPOINT->y) * PERCINDELTA);
}
const std::vector<Hyprutils::Math::Vector2D>& CBezierCurve::getControlPoints() const {
return m_vPoints;
}

View File

@ -1,11 +1,13 @@
#include <hyprutils/math/Mat3x3.hpp>
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/math/Box.hpp>
#include <hyprutils/memory/Casts.hpp>
#include <cmath>
#include <unordered_map>
#include <format>
using namespace Hyprutils::Math;
using namespace Hyprutils::Memory;
static std::unordered_map<eTransform, Mat3x3> transforms = {
{HYPRUTILS_TRANSFORM_NORMAL, std::array<float, 9>{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
@ -93,12 +95,12 @@ Mat3x3& Mat3x3::transform(eTransform transform) {
}
Mat3x3& Mat3x3::rotate(float rot) {
multiply(std::array<float, 9>{(float)cos(rot), (float)-sin(rot), 0.0f, (float)sin(rot), (float)cos(rot), 0.0f, 0.0f, 0.0f, 1.0f});
multiply(std::array<float, 9>{cosf(rot), -sinf(rot), 0.0f, sinf(rot), cosf(rot), 0.0f, 0.0f, 0.0f, 1.0f});
return *this;
}
Mat3x3& Mat3x3::scale(const Vector2D& scale_) {
multiply(std::array<float, 9>{(float)scale_.x, 0.0f, 0.0f, 0.0f, (float)scale_.y, 0.0f, 0.0f, 0.0f, 1.0f});
multiply(std::array<float, 9>{sc<float>(scale_.x), 0.0f, 0.0f, 0.0f, sc<float>(scale_.y), 0.0f, 0.0f, 0.0f, 1.0f});
return *this;
}
@ -107,7 +109,7 @@ Mat3x3& Mat3x3::scale(const float scale_) {
}
Mat3x3& Mat3x3::translate(const Vector2D& offset) {
multiply(std::array<float, 9>{1.0f, 0.0f, (float)offset.x, 0.0f, 1.0f, (float)offset.y, 0.0f, 0.0f, 1.0f});
multiply(std::array<float, 9>{1.0f, 0.0f, sc<float>(offset.x), 0.0f, 1.0f, sc<float>(offset.y), 0.0f, 0.0f, 1.0f});
return *this;
}
@ -117,19 +119,22 @@ Mat3x3& Mat3x3::transpose() {
}
Mat3x3& Mat3x3::multiply(const Mat3x3& other) {
const float* m1 = matrix.data(); // Pointer to current matrix
const float* m2 = other.matrix.data(); // Pointer to the other matrix
std::array<float, 9> product;
product[0] = matrix[0] * other.matrix[0] + matrix[1] * other.matrix[3] + matrix[2] * other.matrix[6];
product[1] = matrix[0] * other.matrix[1] + matrix[1] * other.matrix[4] + matrix[2] * other.matrix[7];
product[2] = matrix[0] * other.matrix[2] + matrix[1] * other.matrix[5] + matrix[2] * other.matrix[8];
product[0] = m1[0] * m2[0] + m1[1] * m2[3] + m1[2] * m2[6];
product[1] = m1[0] * m2[1] + m1[1] * m2[4] + m1[2] * m2[7];
product[2] = m1[0] * m2[2] + m1[1] * m2[5] + m1[2] * m2[8];
product[3] = matrix[3] * other.matrix[0] + matrix[4] * other.matrix[3] + matrix[5] * other.matrix[6];
product[4] = matrix[3] * other.matrix[1] + matrix[4] * other.matrix[4] + matrix[5] * other.matrix[7];
product[5] = matrix[3] * other.matrix[2] + matrix[4] * other.matrix[5] + matrix[5] * other.matrix[8];
product[3] = m1[3] * m2[0] + m1[4] * m2[3] + m1[5] * m2[6];
product[4] = m1[3] * m2[1] + m1[4] * m2[4] + m1[5] * m2[7];
product[5] = m1[3] * m2[2] + m1[4] * m2[5] + m1[5] * m2[8];
product[6] = matrix[6] * other.matrix[0] + matrix[7] * other.matrix[3] + matrix[8] * other.matrix[6];
product[7] = matrix[6] * other.matrix[1] + matrix[7] * other.matrix[4] + matrix[8] * other.matrix[7];
product[8] = matrix[6] * other.matrix[2] + matrix[7] * other.matrix[5] + matrix[8] * other.matrix[8];
product[6] = m1[6] * m2[0] + m1[7] * m2[3] + m1[8] * m2[6];
product[7] = m1[6] * m2[1] + m1[7] * m2[4] + m1[8] * m2[7];
product[8] = m1[6] * m2[2] + m1[7] * m2[5] + m1[8] * m2[8];
matrix = product;
return *this;
@ -140,6 +145,11 @@ Mat3x3 Mat3x3::copy() const {
}
std::string Mat3x3::toString() const {
for (const auto& m : matrix) {
if (!std::isfinite(m))
return "[mat3x3: invalid values]";
}
return std::format("[mat3x3: {}, {}, {}, {}, {}, {}, {}, {}, {}]", matrix.at(0), matrix.at(1), matrix.at(2), matrix.at(3), matrix.at(4), matrix.at(5), matrix.at(6),
matrix.at(7), matrix.at(8));
}

View File

@ -1,7 +1,9 @@
#include "hyprutils/memory/Casts.hpp"
#include <hyprutils/math/Region.hpp>
#include <cmath>
using namespace Hyprutils::Math;
using namespace Hyprutils::Memory;
constexpr const int64_t MAX_REGION_SIDE = 10000000;
@ -28,10 +30,10 @@ Hyprutils::Math::CRegion::CRegion(pixman_box32_t* box) {
Hyprutils::Math::CRegion::CRegion(const CRegion& other) {
pixman_region32_init(&m_rRegion);
pixman_region32_copy(&m_rRegion, const_cast<CRegion*>(&other)->pixman());
pixman_region32_copy(&m_rRegion, other.pixman());
}
Hyprutils::Math::CRegion::CRegion(CRegion&& other) {
Hyprutils::Math::CRegion::CRegion(CRegion&& other) noexcept {
pixman_region32_init(&m_rRegion);
pixman_region32_copy(&m_rRegion, other.pixman());
}
@ -46,12 +48,12 @@ CRegion& Hyprutils::Math::CRegion::clear() {
}
CRegion& Hyprutils::Math::CRegion::set(const CRegion& other) {
pixman_region32_copy(&m_rRegion, const_cast<CRegion*>(&other)->pixman());
pixman_region32_copy(&m_rRegion, other.pixman());
return *this;
}
CRegion& Hyprutils::Math::CRegion::add(const CRegion& other) {
pixman_region32_union(&m_rRegion, &m_rRegion, const_cast<CRegion*>(&other)->pixman());
pixman_region32_union(&m_rRegion, &m_rRegion, other.pixman());
return *this;
}
@ -66,12 +68,12 @@ CRegion& Hyprutils::Math::CRegion::add(const CBox& other) {
}
CRegion& Hyprutils::Math::CRegion::subtract(const CRegion& other) {
pixman_region32_subtract(&m_rRegion, &m_rRegion, const_cast<CRegion*>(&other)->pixman());
pixman_region32_subtract(&m_rRegion, &m_rRegion, other.pixman());
return *this;
}
CRegion& Hyprutils::Math::CRegion::intersect(const CRegion& other) {
pixman_region32_intersect(&m_rRegion, &m_rRegion, const_cast<CRegion*>(&other)->pixman());
pixman_region32_intersect(&m_rRegion, &m_rRegion, other.pixman());
return *this;
}
@ -86,7 +88,7 @@ CRegion& Hyprutils::Math::CRegion::invert(pixman_box32_t* box) {
}
CRegion& Hyprutils::Math::CRegion::invert(const CBox& box) {
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};
pixman_box32 pixmanBox = {.x1 = sc<int32_t>(box.x), .y1 = sc<int32_t>(box.y), .x2 = sc<int32_t>(box.w) + sc<int32_t>(box.x), .y2 = sc<int32_t>(box.h) + sc<int32_t>(box.y)};
return this->invert(&pixmanBox);
}
@ -104,7 +106,7 @@ CRegion& Hyprutils::Math::CRegion::transform(const eTransform t, double w, doubl
clear();
for (auto& r : rects) {
CBox xfmd{(double)r.x1, (double)r.y1, (double)r.x2 - r.x1, (double)r.y2 - r.y1};
CBox xfmd{r.x1, r.y1, r.x2 - r.x1, r.y2 - r.y1};
xfmd.transform(t, w, h);
add(xfmd);
}
@ -118,7 +120,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{sc<double>(r.x1) - units, sc<double>(r.y1) - units, sc<double>(r.x2) - r.x1 + (units * 2), sc<double>(r.y2) - r.y1 + (units * 2)};
add(b);
}
@ -171,7 +173,7 @@ std::vector<pixman_box32_t> Hyprutils::Math::CRegion::getRects() const {
CBox Hyprutils::Math::CRegion::getExtents() {
pixman_box32_t* box = pixman_region32_extents(&m_rRegion);
return {(double)box->x1, (double)box->y1, (double)box->x2 - box->x1, (double)box->y2 - box->y1};
return {sc<double>(box->x1), sc<double>(box->y1), sc<double>(box->x2) - box->x1, sc<double>(box->y2) - box->y1};
}
bool Hyprutils::Math::CRegion::containsPoint(const Vector2D& vec) const {

View File

@ -1,14 +1,17 @@
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/memory/Casts.hpp>
#include <hyprutils/math/Misc.hpp>
#include <algorithm>
#include <cmath>
using namespace Hyprutils::Math;
using namespace Hyprutils::Memory;
Hyprutils::Math::Vector2D::Vector2D(double xx, double yy) : x(xx), y(yy) {
;
}
Hyprutils::Math::Vector2D::Vector2D(int xx, int yy) : x((double)xx), y((double)yy) {
Hyprutils::Math::Vector2D::Vector2D(int xx, int yy) : x(sc<double>(xx)), y(sc<double>(yy)) {
;
}
@ -55,3 +58,17 @@ double Hyprutils::Math::Vector2D::size() const {
Vector2D Hyprutils::Math::Vector2D::getComponentMax(const Vector2D& other) const {
return Vector2D(std::max(this->x, other.x), std::max(this->y, other.y));
}
Vector2D Hyprutils::Math::Vector2D::transform(eTransform transform, const Vector2D& monitorSize) const {
switch (transform) {
case HYPRUTILS_TRANSFORM_NORMAL: return *this;
case HYPRUTILS_TRANSFORM_90: return Vector2D(y, monitorSize.y - x);
case HYPRUTILS_TRANSFORM_180: return Vector2D(monitorSize.x - x, monitorSize.y - y);
case HYPRUTILS_TRANSFORM_270: return Vector2D(monitorSize.x - y, x);
case HYPRUTILS_TRANSFORM_FLIPPED: return Vector2D(monitorSize.x - x, y);
case HYPRUTILS_TRANSFORM_FLIPPED_90: return Vector2D(y, x);
case HYPRUTILS_TRANSFORM_FLIPPED_180: return Vector2D(x, monitorSize.y - y);
case HYPRUTILS_TRANSFORM_FLIPPED_270: return Vector2D(monitorSize.x - y, monitorSize.y - x);
default: return *this;
}
}

View File

@ -1,5 +1,7 @@
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/Casts.hpp>
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
#include <csignal>
#include <cstdio>
@ -17,7 +19,7 @@ struct Hyprutils::OS::CProcess::impl {
std::vector<std::string> args;
std::vector<std::pair<std::string, std::string>> env;
pid_t grandchildPid = 0;
int stdoutFD = -1, stderrFD = -1, exitCode = 0;
int stdoutFD = -1, stderrFD = -1, exitCode = 0, stdinFD = -1;
};
Hyprutils::OS::CProcess::CProcess(const std::string& binary, const std::vector<std::string>& args) : m_impl(new impl()) {
@ -61,7 +63,7 @@ bool Hyprutils::OS::CProcess::runSync() {
dup2(errPipe[1], 2 /* stderr */);
// build argv
std::vector<const char*> argsC;
std::vector<char*> argsC;
argsC.emplace_back(strdup(m_impl->binary.c_str()));
for (auto& arg : m_impl->args) {
// TODO: does this leak? Can we just pipe c_str() as the strings won't be realloc'd?
@ -75,7 +77,7 @@ bool Hyprutils::OS::CProcess::runSync() {
setenv(n.c_str(), v.c_str(), 1);
}
execvp(m_impl->binary.c_str(), (char* const*)argsC.data());
execvp(m_impl->binary.c_str(), argsC.data());
exit(1);
} else {
// parent
@ -129,7 +131,7 @@ bool Hyprutils::OS::CProcess::runSync() {
if (pollfds[0].revents & POLLIN) {
while ((ret = read(outPipe[0], buf.data(), 1023)) > 0) {
m_impl->out += std::string_view{(char*)buf.data(), (size_t)ret};
m_impl->out += std::string_view{buf.data(), sc<size_t>(ret)};
}
buf.fill(0);
@ -137,7 +139,7 @@ bool Hyprutils::OS::CProcess::runSync() {
if (pollfds[1].revents & POLLIN) {
while ((ret = read(errPipe[0], buf.data(), 1023)) > 0) {
m_impl->err += std::string_view{(char*)buf.data(), (size_t)ret};
m_impl->err += std::string_view{buf.data(), sc<size_t>(ret)};
}
buf.fill(0);
@ -146,13 +148,13 @@ bool Hyprutils::OS::CProcess::runSync() {
// Final reads. Nonblock, so its ok.
while ((ret = read(outPipe[0], buf.data(), 1023)) > 0) {
m_impl->out += std::string_view{(char*)buf.data(), (size_t)ret};
m_impl->out += std::string_view{buf.data(), sc<size_t>(ret)};
}
buf.fill(0);
while ((ret = read(errPipe[0], buf.data(), 1023)) > 0) {
m_impl->err += std::string_view{(char*)buf.data(), (size_t)ret};
m_impl->err += std::string_view{buf.data(), sc<size_t>(ret)};
}
buf.fill(0);
@ -198,7 +200,7 @@ bool Hyprutils::OS::CProcess::runAsync() {
close(socket[0]);
close(socket[1]);
// build argv
std::vector<const char*> argsC;
std::vector<char*> argsC;
argsC.emplace_back(strdup(m_impl->binary.c_str()));
for (auto& arg : m_impl->args) {
argsC.emplace_back(strdup(arg.c_str()));
@ -206,12 +208,25 @@ bool Hyprutils::OS::CProcess::runAsync() {
argsC.emplace_back(nullptr);
if (m_impl->stdoutFD != -1)
dup2(m_impl->stdoutFD, 1);
if (m_impl->stderrFD != -1)
dup2(m_impl->stderrFD, 2);
// pass env
for (auto& [n, v] : m_impl->env) {
setenv(n.c_str(), v.c_str(), 1);
}
execvp(m_impl->binary.c_str(), (char* const*)argsC.data());
if (m_impl->stdinFD != -1) {
dup2(m_impl->stdinFD, STDIN_FILENO);
close(m_impl->stdinFD);
}
if (m_impl->stdoutFD != -1) {
dup2(m_impl->stdoutFD, STDOUT_FILENO);
close(m_impl->stdoutFD);
}
if (m_impl->stderrFD != -1) {
dup2(m_impl->stderrFD, STDERR_FILENO);
close(m_impl->stderrFD);
}
execvp(m_impl->binary.c_str(), argsC.data());
_exit(0);
}
close(socket[0]);
@ -257,6 +272,10 @@ int Hyprutils::OS::CProcess::exitCode() {
return m_impl->exitCode;
}
void Hyprutils::OS::CProcess::setStdinFD(int fd) {
m_impl->stdinFD = fd;
}
void Hyprutils::OS::CProcess::setStdoutFD(int fd) {
m_impl->stdoutFD = fd;
}

View File

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

View File

@ -1,3 +1,4 @@
#include "hyprutils/memory/SharedPtr.hpp"
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <algorithm>
@ -8,8 +9,9 @@ using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
void Hyprutils::Signal::CSignal::emit(std::any data) {
void Hyprutils::Signal::CSignalBase::emitInternal(void* args) {
std::vector<SP<CSignalListener>> listeners;
listeners.reserve(m_vListeners.size());
for (auto& l : m_vListeners) {
if (l.expired())
continue;
@ -17,11 +19,7 @@ void Hyprutils::Signal::CSignal::emit(std::any data) {
listeners.emplace_back(l.lock());
}
std::vector<CStaticSignalListener*> statics;
statics.reserve(m_vStaticListeners.size());
for (auto& l : m_vStaticListeners) {
statics.emplace_back(l.get());
}
auto statics = m_vStaticListeners;
for (auto& l : listeners) {
// if there is only one lock, it means the event is only held by the listeners
@ -29,11 +27,11 @@ void Hyprutils::Signal::CSignal::emit(std::any data) {
if (l.strongRef() == 1)
continue;
l->emit(data);
l->emitInternal(args);
}
for (auto& l : statics) {
l->emit(data);
l->emitInternal(args);
}
// release SPs
@ -43,8 +41,8 @@ void Hyprutils::Signal::CSignal::emit(std::any data) {
// as such we'd be doing a UAF
}
CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function<void(std::any)> handler) {
CHyprSignalListener listener = makeShared<CSignalListener>(handler);
CHyprSignalListener Hyprutils::Signal::CSignalBase::registerListenerInternal(std::function<void(void*)> handler) {
CHyprSignalListener listener = SP<CSignalListener>(new CSignalListener(handler));
m_vListeners.emplace_back(listener);
// housekeeping: remove any stale listeners
@ -53,6 +51,10 @@ CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function<v
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));
void Hyprutils::Signal::CSignalBase::registerStaticListenerInternal(std::function<void(void*)> handler) {
m_vStaticListeners.emplace_back(SP<CSignalListener>(new CSignalListener(handler)));
}
void Hyprutils::Signal::CSignal::emit(std::any data) {
CSignalT::emit(data);
}

View File

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

View File

@ -62,7 +62,7 @@ class CMyAnimationManager : public CAnimationManager {
switch (PAV->m_Type) {
case eAVTypes::INT: {
auto avInt = dynamic_cast<CAnimatedVariable<int>*>(PAV.get());
auto avInt = dc<CAnimatedVariable<int>*>(PAV.get());
if (!avInt)
std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET;
@ -70,7 +70,7 @@ class CMyAnimationManager : public CAnimationManager {
avInt->value() = avInt->begun() + (DELTA * POINTY);
} break;
case eAVTypes::TEST: {
auto avCustom = dynamic_cast<CAnimatedVariable<SomeTestType>*>(PAV.get());
auto avCustom = dc<CAnimatedVariable<SomeTestType>*>(PAV.get());
if (!avCustom)
std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET;
@ -93,7 +93,7 @@ class CMyAnimationManager : public CAnimationManager {
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->create(EAVTYPE, sc<CAnimationManager*>(this), PAV, v);
PAV->setConfig(animationTree.getConfig(animationConfigName));
av = std::move(PAV);
}
@ -348,7 +348,7 @@ int main(int argc, char** argv, char** envp) {
*s.m_iA = 5;
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) {
endCallbackRan++;
const auto PAV = dynamic_cast<CAnimatedVariable<int>*>(v.lock().get());
const auto PAV = dc<CAnimatedVariable<int>*>(v.lock().get());
*PAV = 10;
PAV->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) { endCallbackRan++; });

View File

@ -92,8 +92,31 @@ int main(int argc, char** argv, char** envp) {
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};
EXPECT(matrixBox, expected);
// we need to do this to avoid precision errors on 32-bit archs
EXPECT(std::abs(expected.getMatrix().at(0) - matrixBox.getMatrix().at(0)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(1) - matrixBox.getMatrix().at(1)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(2) - matrixBox.getMatrix().at(2)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(3) - matrixBox.getMatrix().at(3)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(4) - matrixBox.getMatrix().at(4)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(5) - matrixBox.getMatrix().at(5)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(6) - matrixBox.getMatrix().at(6)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(7) - matrixBox.getMatrix().at(7)) < 0.1, true);
EXPECT(std::abs(expected.getMatrix().at(8) - matrixBox.getMatrix().at(8)) < 0.1, true);
}
{
Vector2D original(30, 40);
Vector2D monitorSize(100, 200);
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_NORMAL, monitorSize), Vector2D(30, 40 ));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_90, monitorSize), Vector2D(40, 200 - 30));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_180, monitorSize), Vector2D(100 - 30, 200 - 40));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_270, monitorSize), Vector2D(100 - 40, 30 ));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_FLIPPED, monitorSize), Vector2D(100 - 30, 40 ));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_90, monitorSize), Vector2D(40, 30 ));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_180, monitorSize), Vector2D(30, 200 - 40));
EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_270, monitorSize), Vector2D(100 - 40, 200 - 30));
}
return ret;
}
}

View File

@ -1,6 +1,11 @@
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/Atomic.hpp>
#include "shared.hpp"
#include <chrono>
#include <print>
#include <thread>
#include <vector>
using namespace Hyprutils::Memory;
@ -9,6 +14,87 @@ using namespace Hyprutils::Memory;
#define WP CWeakPointer
#define UP CUniquePointer
#define ASP CAtomicSharedPointer
#define AWP CAtomicWeakPointer
#define NTHREADS 8
#define ITERATIONS 10000
static int testAtomicImpl() {
int ret = 0;
{
// Using makeShared here could lead to invalid refcounts.
ASP<int> shared = makeAtomicShared<int>(0);
std::vector<std::thread> threads;
threads.reserve(NTHREADS);
for (size_t i = 0; i < NTHREADS; i++) {
threads.emplace_back([shared]() {
for (size_t j = 0; j < ITERATIONS; j++) {
ASP<int> strongRef = shared;
(*shared)++;
strongRef.reset();
}
});
}
for (auto& thread : threads) {
thread.join();
}
// Actual count is not incremented in a thread-safe manner here, so we can't check it.
// We just want to check that the concurent refcounting doesn't cause any memory corruption.
shared.reset();
EXPECT(shared, false);
}
{
ASP<int> shared = makeAtomicShared<int>(0);
AWP<int> weak = shared;
std::vector<std::thread> threads;
threads.reserve(NTHREADS);
for (size_t i = 0; i < NTHREADS; i++) {
threads.emplace_back([weak]() {
for (size_t j = 0; j < ITERATIONS; j++) {
if (auto s = weak.lock(); s) {
(*s)++;
}
}
});
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
shared.reset();
for (auto& thread : threads) {
thread.join();
}
EXPECT(shared.strongRef(), 0);
EXPECT(weak.valid(), false);
auto shared2 = weak.lock();
EXPECT(shared, false);
EXPECT(shared2.get(), nullptr);
EXPECT(shared.strongRef(), 0);
EXPECT(weak.valid(), false);
EXPECT(weak.expired(), true);
}
{ // This tests recursive deletion. When foo will be deleted, bar will be deleted within the foo dtor.
class CFoo {
public:
AWP<CFoo> bar;
};
ASP<CFoo> foo = makeAtomicShared<CFoo>();
foo->bar = foo;
}
return ret;
}
int main(int argc, char** argv, char** envp) {
SP<int> intPtr = makeShared<int>(10);
SP<int> intPtr2 = makeShared<int>(-1337);
@ -62,5 +148,7 @@ int main(int argc, char** argv, char** envp) {
EXPECT(*intPtr2AsUint, 10);
EXPECT(*intPtr2, 10);
EXPECT(testAtomicImpl(), 0);
return ret;
}
}

View File

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

View File

@ -1,5 +1,6 @@
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/VarList.hpp>
#include <hyprutils/string/ConstVarList.hpp>
#include "shared.hpp"
using namespace Hyprutils::String;
@ -38,6 +39,10 @@ int main(int argc, char** argv, char** envp) {
EXPECT(list[0], "hello");
EXPECT(list[1], "world!");
CConstVarList listConst("hello world!", 0, 's', true);
EXPECT(listConst[0], "hello");
EXPECT(listConst[1], "world!");
std::string hello = "hello world!";
replaceInString(hello, "hello", "hi");
EXPECT(hello, "hi world!");