From aa8b6130dd3790b72b0f279b78a29e94b75313b5 Mon Sep 17 00:00:00 2001 From: "alan (NyxTrail)" Date: Sun, 22 Sep 2024 04:28:52 +0000 Subject: [PATCH] New upstream version 0.2.2 --- .gitignore | 10 +++ CMakeLists.txt | 56 ++++++++----- VERSION | 1 + flake.lock | 6 +- flake.nix | 4 +- include/hyprutils/math/Box.hpp | 8 +- include/hyprutils/math/Edges.hpp | 110 +++++++++++++++++++++++++ include/hyprutils/math/Region.hpp | 1 + include/hyprutils/memory/SharedPtr.hpp | 13 ++- include/hyprutils/memory/WeakPtr.hpp | 8 +- include/hyprutils/path/Path.hpp | 42 ++++++++++ include/hyprutils/string/VarList.hpp | 2 +- include/hyprutils/utils/ScopeGuard.hpp | 17 ++++ src/math/Box.cpp | 26 ++++-- src/math/Region.cpp | 13 +++ src/path/Path.cpp | 81 ++++++++++++++++++ src/signal/Signal.cpp | 19 ++--- src/string/String.cpp | 6 +- src/string/VarList.cpp | 2 +- src/utils/ScopeGuard.cpp | 12 +++ tests/string.cpp | 3 + 21 files changed, 385 insertions(+), 55 deletions(-) create mode 100644 VERSION create mode 100644 include/hyprutils/math/Edges.hpp create mode 100644 include/hyprutils/path/Path.hpp create mode 100644 include/hyprutils/utils/ScopeGuard.hpp create mode 100644 src/path/Path.cpp create mode 100644 src/utils/ScopeGuard.cpp diff --git a/.gitignore b/.gitignore index 2c633ed..041d4c5 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,13 @@ build/ .vscode/ .cache/ + +.cmake/ +CMakeCache.txt +CMakeFiles/ +CTestTestfile.cmake +DartConfiguration.tcl +Makefile +cmake_install.cmake +compile_commands.json +hyprutils.pc diff --git a/CMakeLists.txt b/CMakeLists.txt index fa1f426..9089c54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,13 @@ cmake_minimum_required(VERSION 3.19) -set(HYPRUTILS_VERSION "0.1.5") +file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) +string(STRIP ${VER_RAW} HYPRUTILS_VERSION) add_compile_definitions(HYPRUTILS_VERSION="${HYPRUTILS_VERSION}") -project(hyprutils - VERSION ${HYPRUTILS_VERSION} - DESCRIPTION "Small C++ library for utilities used across the Hypr* ecosystem" -) +project( + hyprutils + VERSION ${HYPRUTILS_VERSION} + DESCRIPTION "Small C++ library for utilities used across the Hypr* ecosystem") include(CTest) include(GNUInstallDirs) @@ -20,11 +21,11 @@ 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) + message(STATUS "Configuring hyprutils in Debug") + add_compile_definitions(HYPRLAND_DEBUG) else() - add_compile_options(-O3) - message(STATUS "Configuring hyprutils in Release") + add_compile_options(-O3) + message(STATUS "Configuring hyprutils in Release") endif() file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp") @@ -34,14 +35,12 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(deps REQUIRED IMPORTED_TARGET pixman-1) add_library(hyprutils SHARED ${SRCFILES}) -target_include_directories( hyprutils - PUBLIC "./include" - PRIVATE "./src" -) -set_target_properties(hyprutils PROPERTIES - VERSION ${hyprutils_VERSION} - SOVERSION 0 -) +target_include_directories( + hyprutils + PUBLIC "./include" + PRIVATE "./src") +set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION} + SOVERSION 1) target_link_libraries(hyprutils PkgConfig::deps) # tests @@ -49,25 +48,38 @@ add_custom_target(tests) add_executable(hyprutils_memory "tests/memory.cpp") target_link_libraries(hyprutils_memory PRIVATE hyprutils PkgConfig::deps) -add_test(NAME "Memory" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_memory "memory") +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 PkgConfig::deps) -add_test(NAME "String" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_string "string") +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 PkgConfig::deps) -add_test(NAME "Signal" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_signal "signal") +add_test( + NAME "Signal" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests + COMMAND hyprutils_signal "signal") add_dependencies(tests hyprutils_signal) add_executable(hyprutils_math "tests/math.cpp") target_link_libraries(hyprutils_math PRIVATE hyprutils PkgConfig::deps) -add_test(NAME "Math" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND hyprutils_math "math") +add_test( + NAME "Math" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests + COMMAND hyprutils_math "math") add_dependencies(tests hyprutils_math) # Installation install(TARGETS hyprutils) install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -install(FILES ${CMAKE_BINARY_DIR}/hyprutils.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +install(FILES ${CMAKE_BINARY_DIR}/hyprutils.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..ee1372d --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.2.2 diff --git a/flake.lock b/flake.lock index 62b5934..aeb7fc2 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1717602782, - "narHash": "sha256-pL9jeus5QpX5R+9rsp3hhZ+uplVHscNJh8n8VpqscM0=", + "lastModified": 1721138476, + "narHash": "sha256-+W5eZOhhemLQxelojLxETfbFbc19NWawsXBlapYpqIA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e8057b67ebf307f01bdcc8fba94d94f75039d1f6", + "rev": "ad0b5eed1b6031efaed382844806550c3dcb4206", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 8ef8886..6550f66 100644 --- a/flake.nix +++ b/flake.nix @@ -23,13 +23,15 @@ (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.gcc13Stdenv; - version = "0.pre" + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); + version = version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); }; hyprutils-with-tests = final.hyprutils.override {doCheck = true;}; }; diff --git a/include/hyprutils/math/Box.hpp b/include/hyprutils/math/Box.hpp index db10428..b59c312 100644 --- a/include/hyprutils/math/Box.hpp +++ b/include/hyprutils/math/Box.hpp @@ -135,6 +135,12 @@ namespace Hyprutils::Math { */ Vector2D size() const; + /** + * @brief Retrieves the size of the box offset by its position. + * @return Vector2D representing the bottom right extent of the box. + */ + Vector2D extent() const; + /** * @brief Finds the closest point within the box to a given vector. * @param vec Vector from which to find the closest point. @@ -179,4 +185,4 @@ namespace Hyprutils::Math { private: CBox roundInternal(); }; -} \ No newline at end of file +} diff --git a/include/hyprutils/math/Edges.hpp b/include/hyprutils/math/Edges.hpp new file mode 100644 index 0000000..a1eaef9 --- /dev/null +++ b/include/hyprutils/math/Edges.hpp @@ -0,0 +1,110 @@ +#pragma once +#include + +namespace Hyprutils::Math { + + /** + * @brief Flag set of box edges + */ + class CEdges { + public: + enum eEdges : uint8_t { + NONE = 0, + TOP = 1, + LEFT = 2, + BOTTOM = 4, + RIGHT = 8, + }; + + CEdges() = default; + CEdges(eEdges edges) : m_edges(edges) {} + CEdges(uint8_t edges) : m_edges(static_cast(edges)) {} + + bool operator==(const CEdges& other) { + return m_edges == other.m_edges; + } + + CEdges operator|(const CEdges& other) { + return m_edges | other.m_edges; + } + + CEdges operator&(const CEdges& other) { + return m_edges & other.m_edges; + } + + CEdges operator^(const CEdges& other) { + return m_edges ^ other.m_edges; + } + + void operator|=(const CEdges& other) { + m_edges = (*this | other).m_edges; + } + + void operator&=(const CEdges& other) { + m_edges = (*this & other).m_edges; + } + + void operator^=(const CEdges& other) { + m_edges = (*this ^ other).m_edges; + } + + /** + * @return if the edge set contains the top edge. + */ + bool top() { + return m_edges & TOP; + } + + /** + * @return if the edge set contains the left edge. + */ + bool left() { + return m_edges & LEFT; + } + + /** + * @return if the edge set contains the bottom edge. + */ + bool bottom() { + return m_edges & BOTTOM; + } + + /** + * @return if the edge set contains the right edge. + */ + bool right() { + return m_edges & RIGHT; + } + + /** + * @param top The state the top edge should be set to. + */ + void setTop(bool top) { + m_edges = static_cast((m_edges & ~TOP) | (TOP * top)); + } + + /** + * @param left The state the left edge should be set to. + */ + void setLeft(bool left) { + m_edges = static_cast((m_edges & ~LEFT) | (LEFT * left)); + } + + /** + * @param bottom The state the bottom edge should be set to. + */ + void setBottom(bool bottom) { + m_edges = static_cast((m_edges & ~BOTTOM) | (BOTTOM * bottom)); + } + + /** + * @param right The state the right edge should be set to. + */ + void setRight(bool right) { + m_edges = static_cast((m_edges & ~RIGHT) | (RIGHT * right)); + } + + eEdges m_edges = NONE; + }; + +} diff --git a/include/hyprutils/math/Region.hpp b/include/hyprutils/math/Region.hpp index 856489b..f7589f2 100644 --- a/include/hyprutils/math/Region.hpp +++ b/include/hyprutils/math/Region.hpp @@ -49,6 +49,7 @@ namespace Hyprutils { CRegion& invert(const CBox& box); CRegion& scale(float scale); CRegion& scale(const Vector2D& scale); + CRegion& expand(double units); CRegion& rationalize(); CBox getExtents(); bool containsPoint(const Vector2D& vec) const; diff --git a/include/hyprutils/memory/SharedPtr.hpp b/include/hyprutils/memory/SharedPtr.hpp index 8cc8ee2..81ea705 100644 --- a/include/hyprutils/memory/SharedPtr.hpp +++ b/include/hyprutils/memory/SharedPtr.hpp @@ -20,7 +20,7 @@ namespace Hyprutils { namespace CSharedPointer_ { class impl_base { public: - virtual ~impl_base(){}; + virtual ~impl_base() {}; virtual void inc() noexcept = 0; virtual void dec() noexcept = 0; @@ -31,6 +31,7 @@ namespace Hyprutils { virtual void destroy() noexcept = 0; virtual bool destroying() noexcept = 0; virtual bool dataNonNull() noexcept = 0; + virtual void* getData() noexcept = 0; }; template @@ -107,6 +108,10 @@ namespace Hyprutils { } virtual bool dataNonNull() noexcept { + return _data != nullptr; + } + + virtual void* getData() noexcept { return _data; } @@ -213,11 +218,11 @@ namespace Hyprutils { } bool operator()(const CSharedPointer& lhs, const CSharedPointer& rhs) const { - return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_; + return reinterpret_cast(lhs.impl_) < reinterpret_cast(rhs.impl_); } bool operator<(const CSharedPointer& rhs) const { - return (uintptr_t)impl_ < (uintptr_t)rhs.impl_; + return reinterpret_cast(impl_) < reinterpret_cast(rhs.impl_); } T* operator->() const { @@ -234,7 +239,7 @@ namespace Hyprutils { } T* get() const { - return (T*)(impl_ ? static_cast*>(impl_)->_data : nullptr); + return impl_ ? static_cast(impl_->getData()) : nullptr; } unsigned int strongRef() const { diff --git a/include/hyprutils/memory/WeakPtr.hpp b/include/hyprutils/memory/WeakPtr.hpp index 8c62fa4..cd0d4bd 100644 --- a/include/hyprutils/memory/WeakPtr.hpp +++ b/include/hyprutils/memory/WeakPtr.hpp @@ -80,7 +80,7 @@ namespace Hyprutils { /* create a weak ptr from a shared ptr with assignment */ template validHierarchy&> operator=(const CSharedPointer& rhs) { - if ((uintptr_t)impl_ == (uintptr_t)rhs.impl_) + if (reinterpret_cast(impl_) == reinterpret_cast(rhs.impl_)) return *this; decrementWeak(); @@ -139,15 +139,15 @@ namespace Hyprutils { } bool operator()(const CWeakPointer& lhs, const CWeakPointer& rhs) const { - return (uintptr_t)lhs.impl_ < (uintptr_t)rhs.impl_; + return reinterpret_cast(lhs.impl_) < reinterpret_cast(rhs.impl_); } bool operator<(const CWeakPointer& rhs) const { - return (uintptr_t)impl_ < (uintptr_t)rhs.impl_; + return reinterpret_cast(impl_) < reinterpret_cast(rhs.impl_); } T* get() const { - return (T*)(impl_ ? static_cast*>(impl_)->_data : nullptr); + return impl_ ? static_cast(impl_->getData()) : nullptr; } T* operator->() const { diff --git a/include/hyprutils/path/Path.hpp b/include/hyprutils/path/Path.hpp new file mode 100644 index 0000000..8634f20 --- /dev/null +++ b/include/hyprutils/path/Path.hpp @@ -0,0 +1,42 @@ +#pragma once +#include "../string/VarList.hpp" +#include +#include +#include + +namespace Hyprutils { + namespace Path { + /** Check whether a config in the form basePath/hypr/programName.conf exists. + @param basePath the path where the config will be searched + @param programName name of the program (and config file) to search for + */ + bool checkConfigExists(const std::string basePath, const std::string programName); + + /** Constructs a full config path given the basePath and programName. + @param basePath the path where the config hypr/programName.conf is located + @param programName name of the program (and config file) + */ + std::string fullConfigPath(const std::string basePath, const std::string programName); + + /** Retrieves the absolute path of the $HOME env variable. + */ + std::optional getHome(); + + /** Retrieves a CVarList of paths from the $XDG_CONFIG_DIRS env variable. + */ + std::optional getXdgConfigDirs(); + + /** Retrieves the absolute path of the $XDG_CONFIG_HOME env variable. + */ + std::optional getXdgConfigHome(); + + /** Searches for a config according to the XDG Base Directory specification. + Returns a pair of the full path to a config and the base path. + Returns std::nullopt in case of a non-existent value. + @param programName name of the program (and config file) + */ + + using T = std::optional; + std::pair findConfig(const std::string programName); + } +} diff --git a/include/hyprutils/string/VarList.hpp b/include/hyprutils/string/VarList.hpp index a876111..697b51e 100644 --- a/include/hyprutils/string/VarList.hpp +++ b/include/hyprutils/string/VarList.hpp @@ -12,7 +12,7 @@ namespace Hyprutils { @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(const std::string& in, const size_t lastArgNo = 0, const char delim = ',', const bool removeEmpty = false); ~CVarList() = default; diff --git a/include/hyprutils/utils/ScopeGuard.hpp b/include/hyprutils/utils/ScopeGuard.hpp new file mode 100644 index 0000000..af4feb7 --- /dev/null +++ b/include/hyprutils/utils/ScopeGuard.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace Hyprutils { + namespace Utils { + // calls a function when it goes out of scope + class CScopeGuard { + public: + CScopeGuard(const std::function& fn_); + ~CScopeGuard(); + + private: + std::function fn; + }; + }; +}; diff --git a/src/math/Box.cpp b/src/math/Box.cpp index 21e8fe5..fcd1eda 100644 --- a/src/math/Box.cpp +++ b/src/math/Box.cpp @@ -199,23 +199,35 @@ Vector2D Hyprutils::Math::CBox::size() const { return {w, h}; } +Vector2D Hyprutils::Math::CBox::extent() const { + return pos() + size(); +} + Vector2D Hyprutils::Math::CBox::closestPoint(const Vector2D& vec) const { if (containsPoint(vec)) return vec; - Vector2D nv = vec; - nv.x = std::clamp(nv.x, x, x + w); - nv.y = std::clamp(nv.y, y, y + h); + Vector2D nv = vec; + Vector2D maxPoint = {x + w - EPSILON, y + h - EPSILON}; + + if (x < maxPoint.x) + nv.x = std::clamp(nv.x, x, maxPoint.x); + else + nv.x = x; + if (y < maxPoint.y) + nv.y = std::clamp(nv.y, y, maxPoint.y); + else + nv.y = y; if (std::fabs(nv.x - x) < EPSILON) nv.x = x; - else if (std::fabs(nv.x - (x + w)) < EPSILON) - nv.x = x + w; + else if (std::fabs(nv.x - (maxPoint.x)) < EPSILON) + nv.x = maxPoint.x; if (std::fabs(nv.y - y) < EPSILON) nv.y = y; - else if (std::fabs(nv.y - (y + h)) < EPSILON) - nv.y = y + h; + else if (std::fabs(nv.y - (maxPoint.y)) < EPSILON) + nv.y = maxPoint.y; return nv; } diff --git a/src/math/Region.cpp b/src/math/Region.cpp index 81eda8b..e69ddb8 100644 --- a/src/math/Region.cpp +++ b/src/math/Region.cpp @@ -112,6 +112,19 @@ CRegion& Hyprutils::Math::CRegion::transform(const eTransform t, double w, doubl return *this; } +CRegion& Hyprutils::Math::CRegion::expand(double units) { + auto rects = getRects(); + + 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}; + add(b); + } + + return *this; +} + CRegion& Hyprutils::Math::CRegion::rationalize() { intersect(CBox{-MAX_REGION_SIDE, -MAX_REGION_SIDE, MAX_REGION_SIDE * 2, MAX_REGION_SIDE * 2}); return *this; diff --git a/src/path/Path.cpp b/src/path/Path.cpp new file mode 100644 index 0000000..b6f9997 --- /dev/null +++ b/src/path/Path.cpp @@ -0,0 +1,81 @@ +#include +#include +#include + +using namespace Hyprutils; + +namespace Hyprutils::Path { + std::string fullConfigPath(std::string basePath, std::string programName) { + return basePath + "/hypr/" + programName + ".conf"; + } + + bool checkConfigExists(std::string basePath, std::string programName) { + return std::filesystem::exists(fullConfigPath(basePath, programName)); + } + + std::optional getHome() { + static const auto homeDir = getenv("HOME"); + + if (!homeDir || !std::filesystem::path(homeDir).is_absolute()) + return std::nullopt; + + return std::string(homeDir).append("/.config"); + } + + std::optional getXdgConfigDirs() { + static const auto xdgConfigDirs = getenv("XDG_CONFIG_DIRS"); + + if (!xdgConfigDirs) + return std::nullopt; + + static const auto xdgConfigDirsList = String::CVarList(xdgConfigDirs, 0, ':'); + + return xdgConfigDirsList; + } + + std::optional getXdgConfigHome() { + static const auto xdgConfigHome = getenv("XDG_CONFIG_HOME"); + + if (!xdgConfigHome || !std::filesystem::path(xdgConfigHome).is_absolute()) + return std::nullopt; + + return xdgConfigHome; + } + + using T = std::optional; + std::pair findConfig(std::string programName) { + bool xdgConfigHomeExists = false; + static const auto xdgConfigHome = getXdgConfigHome(); + if (xdgConfigHome.has_value()) { + xdgConfigHomeExists = true; + if (checkConfigExists(xdgConfigHome.value(), programName)) + return std::make_pair(fullConfigPath(xdgConfigHome.value(), programName), xdgConfigHome); + } + + bool homeExists = false; + static const auto home = getHome(); + if (home.has_value()) { + homeExists = true; + if (checkConfigExists(home.value(), programName)) + return std::make_pair(fullConfigPath(home.value(), programName), home); + } + + static const auto xdgConfigDirs = getXdgConfigDirs(); + if (xdgConfigDirs.has_value()) { + for (auto dir : xdgConfigDirs.value()) { + if (checkConfigExists(dir, programName)) + return std::make_pair(fullConfigPath(dir, programName), std::nullopt); + } + } + + if (checkConfigExists("/etc/xdg", programName)) + return std::make_pair(fullConfigPath("/etc/xdg", programName), std::nullopt); + + if (xdgConfigHomeExists) + return std::make_pair(std::nullopt, xdgConfigHome); + else if (homeExists) + return std::make_pair(std::nullopt, home); + + return std::make_pair(std::nullopt, std::nullopt); + } +} diff --git a/src/signal/Signal.cpp b/src/signal/Signal.cpp index 698d16a..3442fe7 100644 --- a/src/signal/Signal.cpp +++ b/src/signal/Signal.cpp @@ -9,14 +9,10 @@ using namespace Hyprutils::Memory; #define WP CWeakPointer void Hyprutils::Signal::CSignal::emit(std::any data) { - bool dirty = false; - std::vector> listeners; for (auto& l : m_vListeners) { - if (l.expired()) { - dirty = true; + if (l.expired()) continue; - } listeners.emplace_back(l.lock()); } @@ -29,10 +25,9 @@ void Hyprutils::Signal::CSignal::emit(std::any data) { 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; + if (l.strongRef() == 1) continue; - } + l->emit(data); } @@ -43,13 +38,17 @@ void Hyprutils::Signal::CSignal::emit(std::any data) { // release SPs listeners.clear(); - if (dirty) - std::erase_if(m_vListeners, [](const auto& other) { return other.expired(); }); + // we cannot release any expired refs here as one of the listeners could've removed this object and + // as such we'd be doing a UAF } CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function handler) { CHyprSignalListener listener = makeShared(handler); m_vListeners.emplace_back(listener); + + // housekeeping: remove any stale listeners + std::erase_if(m_vListeners, [](const auto& other) { return other.expired(); }); + return listener; } diff --git a/src/string/String.cpp b/src/string/String.cpp index 17d9b33..2ff2dac 100644 --- a/src/string/String.cpp +++ b/src/string/String.cpp @@ -26,6 +26,8 @@ bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) { if (str.empty()) return false; + bool decimalParsed = false; + for (size_t i = 0; i < str.length(); ++i) { const char& c = str.at(i); @@ -44,9 +46,11 @@ bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) { if (i == 0) return false; - if (str.at(0) == '-') + if (decimalParsed) return false; + decimalParsed = true; + continue; } } diff --git a/src/string/VarList.cpp b/src/string/VarList.cpp index 0256087..925430c 100644 --- a/src/string/VarList.cpp +++ b/src/string/VarList.cpp @@ -6,7 +6,7 @@ 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()) + if (!removeEmpty && in.empty()) m_vArgs.emplace_back(""); std::string args{in}; diff --git a/src/utils/ScopeGuard.cpp b/src/utils/ScopeGuard.cpp new file mode 100644 index 0000000..fa8bbd8 --- /dev/null +++ b/src/utils/ScopeGuard.cpp @@ -0,0 +1,12 @@ +#include + +using namespace Hyprutils::Utils; + +Hyprutils::Utils::CScopeGuard::CScopeGuard(const std::function& fn_) : fn(fn_) { + ; +} + +Hyprutils::Utils::CScopeGuard::~CScopeGuard() { + if (fn) + fn(); +} diff --git a/tests/string.cpp b/tests/string.cpp index 67060ed..51cb3ad 100644 --- a/tests/string.cpp +++ b/tests/string.cpp @@ -30,6 +30,9 @@ int main(int argc, char** argv, char** envp) { EXPECT(isNumber("vvss", true), false); EXPECT(isNumber("0.9999s", true), false); EXPECT(isNumber("s0.9999", true), false); + EXPECT(isNumber("-1.0", true), true); + EXPECT(isNumber("-1..0", true), false); + EXPECT(isNumber("-10.0000000001", true), true); CVarList list("hello world!", 0, 's', true); EXPECT(list[0], "hello");