New upstream version 0.1.2

This commit is contained in:
alan (NyxTrail) 2024-06-14 04:52:37 +00:00
commit 1bd6d73aa6
25 changed files with 1358 additions and 0 deletions

65
.clang-format Normal file
View File

@ -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
.github/workflows/arch.yml vendored Normal file
View File

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

27
.github/workflows/nix.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: Build & Test
on: [push, pull_request, workflow_dispatch]
jobs:
nix:
strategy:
matrix:
package:
- hyprutils
- hyprutils-with-tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v26
- uses: DeterminateSystems/magic-nix-cache-action@main
# not needed (yet)
# - uses: cachix/cachix-action@v12
# with:
# name: hyprland
# authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build & Test
run: nix build .#${{ matrix.package }} --print-build-logs

36
.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
build/
.vscode/
.cache/

64
CMakeLists.txt Normal file
View File

@ -0,0 +1,64 @@
cmake_minimum_required(VERSION 3.19)
set(HYPRUTILS_VERSION "0.1.2")
add_compile_definitions(HYPRUTILS_VERSION="${HYPRUTILS_VERSION}")
project(hyprutils
VERSION ${HYPRUTILS_VERSION}
DESCRIPTION "Small C++ library for utilities used across the Hypr* ecosystem"
)
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")
file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "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
)
# 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})
install(FILES ${CMAKE_BINARY_DIR}/hyprutils.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)

28
LICENSE Normal file
View File

@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2024, Hypr Development
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

14
README.md Normal file
View File

@ -0,0 +1,14 @@
# hyprutils
Hyprutils is a small C++ library for utilities used across the Hypr* ecosystem.
## Stability
Hyprutils depends on the ABI stability of the stdlib implementation of your compiler. Sover bumps will be done only for hyprutils ABI breaks, not stdlib.
## Building
```sh
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF`
```

43
flake.lock Normal file
View File

@ -0,0 +1,43 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1717602782,
"narHash": "sha256-pL9jeus5QpX5R+9rsp3hhZ+uplVHscNJh8n8VpqscM0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e8057b67ebf307f01bdcc8fba94d94f75039d1f6",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"systems": "systems"
}
},
"systems": {
"locked": {
"lastModified": 1689347949,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
"owner": "nix-systems",
"repo": "default-linux",
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default-linux",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

45
flake.nix Normal file
View File

@ -0,0 +1,45 @@
{
description = "Small C++ library for utilities used across the Hypr* ecosystem";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
systems.url = "github:nix-systems/default-linux";
};
outputs = {
self,
nixpkgs,
systems,
}: let
inherit (nixpkgs) lib;
eachSystem = lib.genAttrs (import systems);
pkgsFor = eachSystem (system:
import nixpkgs {
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)
]);
in {
overlays = {
default = self.overlays.hyprutils;
hyprutils = final: prev: {
hyprutils = final.callPackage ./nix/default.nix {
stdenv = final.gcc13Stdenv;
version = "0.pre" + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
};
hyprutils-with-tests = final.hyprutils.override {doCheck = true;};
};
};
packages = eachSystem (system: {
default = self.packages.${system}.hyprutils;
inherit (pkgsFor.${system}) hyprutils hyprutils-with-tests;
});
formatter = eachSystem (system: pkgsFor.${system}.alejandra);
};
}

10
hyprutils.pc.in Normal file
View File

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

View File

@ -0,0 +1,296 @@
#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() {
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_);
}
};

View File

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

View File

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

View File

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

View File

@ -0,0 +1,11 @@
#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);
void replaceInString(std::string& string, const std::string& what, const std::string& to);
};
};

View File

@ -0,0 +1,67 @@
#pragma once
#include <functional>
#include <vector>
#include <string>
namespace Hyprutils {
namespace String {
class CVarList {
public:
/** Split string into 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
*/
CVarList(const std::string& in, const size_t maxSize = 0, const char delim = ',', const bool removeEmpty = false);
~CVarList() = default;
size_t size() const {
return m_vArgs.size();
}
std::string join(const std::string& joiner, size_t from = 0, size_t to = 0) const;
void map(std::function<void(std::string&)> func) {
for (auto& s : m_vArgs)
func(s);
}
void append(const std::string arg) {
m_vArgs.emplace_back(arg);
}
std::string operator[](const size_t& idx) const {
if (idx >= m_vArgs.size())
return "";
return m_vArgs[idx];
}
// for range-based loops
std::vector<std::string>::iterator begin() {
return m_vArgs.begin();
}
std::vector<std::string>::const_iterator begin() const {
return m_vArgs.begin();
}
std::vector<std::string>::iterator end() {
return m_vArgs.end();
}
std::vector<std::string>::const_iterator end() const {
return m_vArgs.end();
}
bool contains(const std::string& el) {
for (auto& a : m_vArgs) {
if (a == el)
return true;
}
return false;
}
private:
std::vector<std::string> m_vArgs;
};
}
}

23
nix/default.nix Normal file
View File

@ -0,0 +1,23 @@
{
lib,
stdenv,
cmake,
version ? "git",
doCheck ? false,
}:
stdenv.mkDerivation {
pname = "hyprutils";
inherit version doCheck;
src = ../.;
nativeBuildInputs = [cmake];
outputs = ["out" "dev"];
meta = with lib; {
homepage = "https://github.com/hyprwm/hyprutils";
description = "Small C++ library for utilities used across the Hypr* ecosystem";
license = licenses.bsd3;
platforms = platforms.linux;
};
}

22
src/signal/Listener.cpp Normal file
View File

@ -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
src/signal/Signal.cpp Normal file
View File

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

68
src/string/String.cpp Normal file
View File

@ -0,0 +1,68 @@
#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;
}
void Hyprutils::String::replaceInString(std::string& string, const std::string& what, const std::string& to) {
if (string.empty())
return;
size_t pos = 0;
while ((pos = string.find(what, pos)) != std::string::npos) {
string.replace(pos, what.length(), to);
pos += to.length();
}
}

39
src/string/VarList.cpp Normal file
View File

@ -0,0 +1,39 @@
#include <ranges>
#include <algorithm>
#include <hyprutils/string/VarList.hpp>
#include <hyprutils/string/String.hpp>
using namespace Hyprutils::String;
Hyprutils::String::CVarList::CVarList(const std::string& in, const size_t lastArgNo, const char delim, const bool removeEmpty) {
if (in.empty())
m_vArgs.emplace_back("");
std::string args{in};
size_t idx = 0;
size_t pos = 0;
std::ranges::replace_if(
args, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0);
for (const auto& s : args | std::views::split(0)) {
if (removeEmpty && s.empty())
continue;
if (++idx == lastArgNo) {
m_vArgs.emplace_back(trim(in.substr(pos)));
break;
}
pos += s.size() + 1;
m_vArgs.emplace_back(trim(std::string_view{s}.data()));
}
}
std::string Hyprutils::String::CVarList::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) {
rolling += m_vArgs[i] + (i + 1 < last ? joiner : "");
}
return rolling;
}

29
tests/memory.cpp Normal file
View File

@ -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
tests/shared.hpp Normal file
View File

@ -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
tests/signal.cpp Normal file
View File

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

42
tests/string.cpp Normal file
View File

@ -0,0 +1,42 @@
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/VarList.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);
CVarList list("hello world!", 0, 's', true);
EXPECT(list[0], "hello");
EXPECT(list[1], "world!");
std::string hello = "hello world!";
replaceInString(hello, "hello", "hi");
EXPECT(hello, "hi world!");
return ret;
}