Skip to content

Commit

Permalink
utils: Initial Commit
Browse files Browse the repository at this point in the history
vaxerski committed Jun 8, 2024
1 parent cbc7c2d commit bf73356
Showing 17 changed files with 1,025 additions and 0 deletions.
65 changes: 65 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
Language: Cpp
BasedOnStyle: LLVM

AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: true
AlignConsecutiveAssignments: true
AlignEscapedNewlines: Right
AlignOperands: false
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: AfterColon
ColumnLimit: 180
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
IncludeBlocks: Preserve
IndentCaseLabels: true
IndentWidth: 4
PointerAlignment: Left
ReflowComments: false
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 4
UseTab: Never

AllowShortEnumsOnASingleLine: false

BraceWrapping:
AfterEnum: false

AlignConsecutiveDeclarations: AcrossEmptyLines

NamespaceIndentation: All
57 changes: 57 additions & 0 deletions .github/workflows/arch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Build & Test (Arch)

on: [push, pull_request, workflow_dispatch]
jobs:
gcc:
name: "Arch: Build and Test (gcc)"
runs-on: ubuntu-latest
container:
image: archlinux
steps:
- name: Checkout repository actions
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions

- name: Get required pkgs
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++
- name: Build hyprutils with gcc
run: |
CC="/usr/bin/gcc" CXX="/usr/bin/g++" cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
CC="/usr/bin/gcc" CXX="/usr/bin/g++" cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
cmake --install ./build
- name: Run tests
run: |
cd ./build && ctest --output-on-failure
clang:
name: "Arch: Build and Test (clang)"
runs-on: ubuntu-latest
container:
image: archlinux
steps:
- name: Checkout repository actions
uses: actions/checkout@v4
with:
sparse-checkout: .github/actions

- name: Get required pkgs
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++
- name: Build hyprutils with clang
run: |
CC="/usr/bin/clang" CXX="/usr/bin/clang++" cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
CC="/usr/bin/clang" CXX="/usr/bin/clang++" cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
cmake --install ./build
- name: Run tests
run: |
cd ./build && ctest --output-on-failure
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -30,3 +30,7 @@
*.exe
*.out
*.app

build/
.vscode/
.cache/
63 changes: 63 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
cmake_minimum_required(VERSION 3.19)

set(HYPRUTILS_VERSION "0.1.0")
add_compile_definitions(HYPRUTILS_VERSION="${HYPRUTILS_VERSION}")

project(hyprutils
VERSION ${HYPRUTILS_VERSION}
DESCRIPTION "A library and toolkit for the Hyprland cursor format"
)

include(CTest)
include(GNUInstallDirs)

set(PREFIX ${CMAKE_INSTALL_PREFIX})
set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR})
set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR})

configure_file(hyprutils.pc.in hyprutils.pc @ONLY)

set(CMAKE_CXX_STANDARD 23)

if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Configuring hyprutils in Debug")
add_compile_definitions(HYPRLAND_DEBUG)
else()
add_compile_options(-O3)
message(STATUS "Configuring hyprutils in Release")
endif()

file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp")

add_library(hyprutils SHARED ${SRCFILES})
target_include_directories( hyprutils
PUBLIC "./include"
PRIVATE "./src"
)
set_target_properties(hyprutils PROPERTIES
VERSION ${hyprutils_VERSION}
SOVERSION 0
PUBLIC_HEADER include/hyprutils/hyprutils.hpp include/hyprutils/hyprutils.h include/hyprutils/shared.h
)

# tests
add_custom_target(tests)

add_executable(hyprutils_memory "tests/memory.cpp")
target_link_libraries(hyprutils_memory PRIVATE hyprutils)
add_test(NAME "Memory" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_memory "memory")
add_dependencies(tests hyprutils_memory)

add_executable(hyprutils_string "tests/string.cpp")
target_link_libraries(hyprutils_string PRIVATE hyprutils)
add_test(NAME "String" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_string "string")
add_dependencies(tests hyprutils_string)

add_executable(hyprutils_signal "tests/signal.cpp")
target_link_libraries(hyprutils_signal PRIVATE hyprutils)
add_test(NAME "Signal" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_signal "signal")
add_dependencies(tests hyprutils_signal)

# Installation
install(TARGETS hyprutils)
install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
10 changes: 10 additions & 0 deletions hyprutils.pc.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
prefix=@PREFIX@
includedir=@INCLUDE@
libdir=@LIBDIR@

Name: hyprutils
URL: https://github.com/hyprwm/hyprutils
Description: Hyprland utilities library used across the ecosystem
Version: @HYPRUTILS_VERSION@
Cflags: -I${includedir}
Libs: -L${libdir} -lhyprutils
302 changes: 302 additions & 0 deletions include/hyprutils/memory/SharedPtr.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
#pragma once

#include <cstddef>
#include <cstdint>
#include <memory>

/*
This is a custom impl of std::shared_ptr.
It is not thread-safe like the STL one,
but Hyprland is single-threaded anyways.
It differs a bit from how the STL one works,
namely in the fact that it keeps the T* inside the
control block, and that you can still make a CWeakPtr
or deref an existing one inside the destructor.
*/

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

template <typename T>
class impl : public impl_base {
public:
impl(T* data) noexcept : _data(data) {
;
}

/* strong refcount */
unsigned int _ref = 0;
/* weak refcount */
unsigned int _weak = 0;

T* _data = nullptr;

friend void swap(impl*& a, impl*& b) {
impl* tmp = a;
a = b;
b = tmp;
}

/* if the destructor was called,
creating shared_ptrs is no longer valid */
bool _destroying = false;

void _destroy() {
if (!_data || _destroying)
return;

// first, we destroy the data, but keep the pointer.
// this way, weak pointers will still be able to
// reference and use, but no longer create shared ones.
_destroying = true;
__deleter(_data);
// now, we can reset the data and call it a day.
_data = nullptr;
_destroying = false;
}

std::default_delete<T> __deleter{};

//
virtual void inc() noexcept {
_ref++;
}

virtual void dec() noexcept {
_ref--;
}

virtual void incWeak() noexcept {
_weak++;
}

virtual void decWeak() noexcept {
_weak--;
}

virtual unsigned int ref() noexcept {
return _ref;
}

virtual unsigned int wref() noexcept {
return _weak;
}

virtual void destroy() noexcept {
_destroy();
}

virtual bool destroying() noexcept {
return _destroying;
}

virtual bool dataNonNull() noexcept {
return _data;
}

virtual ~impl() {
destroy();
}
};
};

template <typename T>
class CSharedPointer {
public:
template <typename X>
using validHierarchy = typename std::enable_if<std::is_assignable<CSharedPointer<T>&, X>::value, CSharedPointer&>::type;
template <typename X>
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type;

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

/* creates a shared pointer from a reference */
template <typename U, typename = isConstructible<U>>
CSharedPointer(const CSharedPointer<U>& ref) noexcept {
impl_ = ref.impl_;
increment();
}

CSharedPointer(const CSharedPointer& ref) noexcept {
impl_ = ref.impl_;
increment();
}

template <typename U, typename = isConstructible<U>>
CSharedPointer(CSharedPointer<U>&& ref) noexcept {
std::swap(impl_, ref.impl_);
}

CSharedPointer(CSharedPointer&& ref) noexcept {
std::swap(impl_, ref.impl_);
}

/* allows weakPointer to create from an impl */
CSharedPointer(CSharedPointer_::impl_base* implementation) noexcept {
impl_ = implementation;
increment();
}

/* creates an empty shared pointer with no implementation */
CSharedPointer() noexcept {
; // empty
}

/* creates an empty shared pointer with no implementation */
CSharedPointer(std::nullptr_t) noexcept {
; // empty
}

~CSharedPointer() {
// we do not decrement here,
// because we want to preserve the pointer
// in case this is the last owner.
if (impl_ && impl_->ref() == 1)
destroyImpl();
else
decrement();
}

template <typename U>
validHierarchy<const CSharedPointer<U>&> operator=(const CSharedPointer<U>& rhs) {
if (impl_ == rhs.impl_)
return *this;

decrement();
impl_ = rhs.impl_;
increment();
return *this;
}

CSharedPointer& operator=(const CSharedPointer& rhs) {
if (impl_ == rhs.impl_)
return *this;

decrement();
impl_ = rhs.impl_;
increment();
return *this;
}

template <typename U>
validHierarchy<const CSharedPointer<U>&> operator=(CSharedPointer<U>&& rhs) {
std::swap(impl_, rhs.impl_);
return *this;
}

CSharedPointer& operator=(CSharedPointer&& rhs) {
std::swap(impl_, rhs.impl_);
return *this;
}

operator bool() const {
return impl_ && impl_->dataNonNull();
}

bool operator==(const CSharedPointer& rhs) const {
return impl_ == rhs.impl_;
}

bool operator()(const CSharedPointer& lhs, const CSharedPointer& rhs) const {
return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_;
}

bool operator<(const CSharedPointer& rhs) const {
return (uintptr_t)impl_ < (uintptr_t)rhs.impl_;
}

T* operator->() const {
return get();
}

T& operator*() const {
return *get();
}

void reset() {
decrement();
impl_ = nullptr;
}

T* get() const {
return (T*)(impl_ ? static_cast<CSharedPointer_::impl<T>*>(impl_)->_data : nullptr);
}

unsigned int strongRef() const {
return impl_ ? impl_->ref() : 0;
}

CSharedPointer_::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 CSharedPointer<U> makeShared(Args&&... args) {
return CSharedPointer<U>(new U(std::forward<Args>(args)...));
}
}
}

template <typename T>
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_);
}
};
192 changes: 192 additions & 0 deletions include/hyprutils/memory/WeakPtr.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#pragma once

#include "./SharedPtr.hpp"

/*
This is a Hyprland implementation of std::weak_ptr.
See SharedPtr.hpp for more info on how it's different.
*/

namespace Hyprutils {
namespace Memory {
template <typename T>
class CWeakPointer {
public:
template <typename X>
using validHierarchy = typename std::enable_if<std::is_assignable<CWeakPointer<T>&, X>::value, CWeakPointer&>::type;
template <typename X>
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type;

/* create a weak ptr from a reference */
template <typename U, typename = isConstructible<U>>
CWeakPointer(const CSharedPointer<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 {
if (!ref.impl_)
return;

impl_ = ref.impl_;
incrementWeak();
}

CWeakPointer(const CWeakPointer& ref) noexcept {
if (!ref.impl_)
return;

impl_ = ref.impl_;
incrementWeak();
}

template <typename U, typename = isConstructible<U>>
CWeakPointer(CWeakPointer<U>&& ref) noexcept {
std::swap(impl_, ref.impl_);
}

CWeakPointer(CWeakPointer&& ref) noexcept {
std::swap(impl_, ref.impl_);
}

/* create a weak ptr from another weak ptr with assignment */
template <typename U>
validHierarchy<const CWeakPointer<U>&> operator=(const CWeakPointer<U>& rhs) {
if (impl_ == rhs.impl_)
return *this;

decrementWeak();
impl_ = rhs.impl_;
incrementWeak();
return *this;
}

CWeakPointer<T>& operator=(const CWeakPointer& rhs) {
if (impl_ == rhs.impl_)
return *this;

decrementWeak();
impl_ = rhs.impl_;
incrementWeak();
return *this;
}

/* create a weak ptr from a shared ptr with assignment */
template <typename U>
validHierarchy<const CWeakPointer<U>&> operator=(const CSharedPointer<U>& rhs) {
if ((uintptr_t)impl_ == (uintptr_t)rhs.impl_)
return *this;

decrementWeak();
impl_ = rhs.impl_;
incrementWeak();
return *this;
}

/* create an empty weak ptr */
CWeakPointer() {
;
}

~CWeakPointer() {
decrementWeak();
}

/* expired MAY return true even if the pointer is still stored.
the situation would be e.g. self-weak pointer in a destructor.
for pointer validity, use valid() */
bool expired() const {
return !impl_ || !impl_->dataNonNull() || impl_->destroying();
}

/* this means the pointed-to object is not yet deleted and can still be
referenced, but it might be in the process of being deleted.
check !expired() if you want to check whether it's valid and
assignable to a SP. */
bool valid() const {
return impl_ && impl_->dataNonNull();
}

void reset() {
decrementWeak();
impl_ = nullptr;
}

CSharedPointer<T> lock() const {
if (!impl_ || !impl_->dataNonNull() || impl_->destroying())
return {};

return CSharedPointer<T>(impl_);
}

/* this returns valid() */
operator bool() const {
return valid();
}

bool operator==(const CWeakPointer<T>& rhs) const {
return impl_ == rhs.impl_;
}

bool operator==(const CSharedPointer<T>& rhs) const {
return impl_ == rhs.impl_;
}

bool operator()(const CWeakPointer& lhs, const CWeakPointer& rhs) const {
return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_;
}

bool operator<(const CWeakPointer& rhs) const {
return (uintptr_t)impl_ < (uintptr_t)rhs.impl_;
}

T* get() const {
return (T*)(impl_ ? static_cast<CSharedPointer_::impl<T>*>(impl_)->_data : nullptr);
}

T* operator->() const {
return get();
}

CSharedPointer_::impl_base* impl_ = nullptr;

private:
/* no-op if there is no impl_ */
void decrementWeak() {
if (!impl_)
return;

impl_->decWeak();

// we need to check for ->destroying,
// because otherwise we could destroy here
// and have a shared_ptr destroy the same thing
// later (in situations where we have a weak_ptr to self)
if (impl_->wref() == 0 && impl_->ref() == 0 && !impl_->destroying()) {
delete impl_;
impl_ = nullptr;
}
}
/* no-op if there is no impl_ */
void incrementWeak() {
if (!impl_)
return;

impl_->incWeak();
}
};
}
}

template <typename T>
struct std::hash<Hyprutils::Memory::CWeakPointer<T>> {
std::size_t operator()(const Hyprutils::Memory::CWeakPointer<T>& p) const noexcept {
return std::hash<void*>{}(p.impl_);
}
};
44 changes: 44 additions & 0 deletions include/hyprutils/signal/Listener.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#pragma once

#include <any>
#include <functional>
#include <hyprutils/memory/SharedPtr.hpp>

namespace Hyprutils {
namespace Signal {
class CSignal;

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

private:
std::function<void(std::any)> m_fHandler;
};

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;
};
}
}
28 changes: 28 additions & 0 deletions include/hyprutils/signal/Signal.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <functional>
#include <any>
#include <vector>
#include <memory>
#include <hyprutils/memory/WeakPtr.hpp>
#include "./Listener.hpp"

namespace Hyprutils {
namespace Signal {
class CSignal {
public:
void emit(std::any data = {});

//
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener registerListener(std::function<void(std::any)> handler);

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

private:
std::vector<Hyprutils::Memory::CWeakPointer<CSignalListener>> m_vListeners;
std::vector<std::unique_ptr<CStaticSignalListener>> m_vStaticListeners;
};
}
}
10 changes: 10 additions & 0 deletions include/hyprutils/string/String.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once
#include <string>

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);
};
};
22 changes: 22 additions & 0 deletions src/signal/Listener.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <hyprutils/signal/Listener.hpp>

using namespace Hyprutils::Signal;

Hyprutils::Signal::CSignalListener::CSignalListener(std::function<void(std::any)> handler) : m_fHandler(handler) {
;
}

void Hyprutils::Signal::CSignalListener::emit(std::any 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);
}
58 changes: 58 additions & 0 deletions src/signal/Signal.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <algorithm>

using namespace Hyprutils::Signal;
using namespace Hyprutils::Memory;

#define SP CSharedPointer
#define WP CWeakPointer

void Hyprutils::Signal::CSignal::emit(std::any data) {
bool dirty = false;

std::vector<SP<CSignalListener>> listeners;
for (auto& l : m_vListeners) {
if (l.expired()) {
dirty = true;
continue;
}

listeners.emplace_back(l.lock());
}

std::vector<CStaticSignalListener*> statics;
for (auto& l : m_vStaticListeners) {
statics.emplace_back(l.get());
}

for (auto& l : listeners) {
// if there is only one lock, it means the event is only held by the listeners
// vector and was removed during our iteration
if (l.strongRef() == 1) {
dirty = true;
continue;
}
l->emit(data);
}

for (auto& l : statics) {
l->emit(data);
}

// release SPs
listeners.clear();

if (dirty)
std::erase_if(m_vListeners, [](const auto& other) { return other.expired(); });
}

CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function<void(std::any)> handler) {
CHyprSignalListener listener = makeShared<CSignalListener>(handler);
m_vListeners.emplace_back(listener);
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));
}
58 changes: 58 additions & 0 deletions src/string/String.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include <algorithm>
#include <hyprutils/string/String.hpp>

using namespace Hyprutils::String;

std::string Hyprutils::String::trim(const std::string& in) {
if (in.empty())
return in;

int countBefore = 0;
while (countBefore < in.length() && std::isspace(in.at(countBefore))) {
countBefore++;
}

int countAfter = 0;
while (countAfter < in.length() - countBefore && std::isspace(in.at(in.length() - countAfter - 1))) {
countAfter++;
}

std::string result = in.substr(countBefore, in.length() - countBefore - countAfter);

return result;
}

bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) {
if (str.empty())
return false;

for (size_t i = 0; i < str.length(); ++i) {
const char& c = str.at(i);

if (i == 0 && str.at(i) == '-') {
// only place where we allow -
continue;
}

if (!isdigit(c)) {
if (!allowfloat)
return false;

if (c != '.')
return false;

if (i == 0)
return false;

if (str.at(0) == '-')
return false;

continue;
}
}

if (str.back() == '.')
return false;

return true;
}
29 changes: 29 additions & 0 deletions tests/memory.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include <hyprutils/memory/WeakPtr.hpp>
#include "shared.hpp"

using namespace Hyprutils::Memory;

#define SP CSharedPointer
#define WP CWeakPointer

int main(int argc, char** argv, char** envp) {
SP<int> intPtr = makeShared<int>(10);

int ret = 0;

EXPECT(*intPtr, 10);
EXPECT(intPtr.strongRef(), 1);

WP<int> weak = intPtr;

EXPECT(*intPtr, 10);
EXPECT(intPtr.strongRef(), 1);
EXPECT(*weak.get(), 10);
EXPECT(weak.expired(), false);

intPtr = {};

EXPECT(weak.expired(), true);

return ret;
}
20 changes: 20 additions & 0 deletions tests/shared.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once
#include <iostream>

namespace Colors {
constexpr const char* RED = "\x1b[31m";
constexpr const char* GREEN = "\x1b[32m";
constexpr const char* YELLOW = "\x1b[33m";
constexpr const char* BLUE = "\x1b[34m";
constexpr const char* MAGENTA = "\x1b[35m";
constexpr const char* CYAN = "\x1b[36m";
constexpr const char* RESET = "\x1b[0m";
};

#define EXPECT(expr, val) \
if (const auto RESULT = expr; RESULT != (val)) { \
std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected " << #val << " but got " << RESULT << "\n"; \
ret = 1; \
} else { \
std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got " << val << "\n"; \
}
30 changes: 30 additions & 0 deletions tests/signal.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include "shared.hpp"

using namespace Hyprutils::Signal;
using namespace Hyprutils::Memory;

int main(int argc, char** argv, char** envp) {
int ret = 0;

CSignal signal;
int data = 0;
auto listener = signal.registerListener([&] (std::any d) {
data = 1;
});

signal.emit();

EXPECT(data, 1);

data = 0;

listener.reset();

signal.emit();

EXPECT(data, 0);

return ret;
}
33 changes: 33 additions & 0 deletions tests/string.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <hyprutils/string/String.hpp>
#include "shared.hpp"

using namespace Hyprutils::String;

int main(int argc, char** argv, char** envp) {
int ret = 0;

EXPECT(trim(" a "), "a");
EXPECT(trim(" a a "), "a a");
EXPECT(trim("a"), "a");
EXPECT(trim(" "), "");

EXPECT(isNumber("99214123434"), true);
EXPECT(isNumber("-35252345234"), true);
EXPECT(isNumber("---3423--432"), false);
EXPECT(isNumber("s---3423--432"), false);
EXPECT(isNumber("---3423--432s"), false);
EXPECT(isNumber("1s"), false);
EXPECT(isNumber(""), false);
EXPECT(isNumber("--0"), false);
EXPECT(isNumber("abc"), false);
EXPECT(isNumber("0.0", true), true);
EXPECT(isNumber("0.2", true), true);
EXPECT(isNumber("0.", true), false);
EXPECT(isNumber(".0", true), false);
EXPECT(isNumber("", true), false);
EXPECT(isNumber("vvss", true), false);
EXPECT(isNumber("0.9999s", true), false);
EXPECT(isNumber("s0.9999", true), false);

return ret;
}

0 comments on commit bf73356

Please sign in to comment.