Compare commits

..

No commits in common. "main" and "v0.10.0" have entirely different histories.

55 changed files with 828 additions and 2633 deletions

View file

@ -17,7 +17,7 @@ jobs:
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy gcc gtest base-devel cmake clang libc++ pixman
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman
- name: Build hyprutils with gcc
run: |
@ -44,7 +44,7 @@ jobs:
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu
pacman --noconfirm --noprogressbar -Sy gcc gtest base-devel cmake clang libc++ pixman
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman
- name: Build hyprutils with clang
run: |

View file

@ -33,11 +33,9 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Configuring hyprutils in Debug")
add_compile_definitions(HYPRLAND_DEBUG)
set(BUILD_TESTING ON)
else()
add_compile_options(-O3)
message(STATUS "Configuring hyprutils in Release")
set(BUILD_TESTING OFF)
endif()
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp")
@ -52,31 +50,67 @@ target_include_directories(
PUBLIC "./include"
PRIVATE "./src")
set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION}
SOVERSION 10)
SOVERSION 9)
target_link_libraries(hyprutils PkgConfig::deps)
if(BUILD_TESTING)
# GTest
find_package(GTest CONFIG REQUIRED)
include(GoogleTest)
file(GLOB_RECURSE TESTFILES CONFIGURE_DEPENDS "tests/*.cpp")
add_executable(hyprutils_tests ${TESTFILES})
# tests
add_custom_target(tests)
target_compile_options(hyprutils_tests PRIVATE --coverage)
target_link_options(hyprutils_tests PRIVATE --coverage)
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_dependencies(tests hyprutils_memory)
target_include_directories(
hyprutils_tests
PUBLIC "./include"
PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}")
target_link_libraries(hyprutils_tests PRIVATE hyprutils GTest::gtest_main
PkgConfig::deps)
gtest_discover_tests(hyprutils_tests)
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_dependencies(tests hyprutils_string)
# Add coverage to hyprutils for test builds
target_compile_options(hyprutils PRIVATE --coverage)
target_link_options(hyprutils PRIVATE --coverage)
endif()
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_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_dependencies(tests hyprutils_math)
add_executable(hyprutils_os "tests/os.cpp")
target_link_libraries(hyprutils_os PRIVATE hyprutils PkgConfig::deps)
add_test(
NAME "OS"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_os "os")
add_dependencies(tests hyprutils_os)
add_executable(hyprutils_filedescriptor "tests/filedescriptor.cpp")
target_link_libraries(hyprutils_filedescriptor PRIVATE hyprutils PkgConfig::deps)
add_test(
NAME "Filedescriptor"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_filedescriptor "filedescriptor")
add_dependencies(tests hyprutils_filedescriptor)
add_executable(hyprutils_animation "tests/animation.cpp")
target_link_libraries(hyprutils_animation PRIVATE hyprutils PkgConfig::deps)
add_test(
NAME "Animation"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprutils_animation "utils")
add_dependencies(tests hyprutils_animation)
# Installation
install(TARGETS hyprutils)

View file

@ -1 +1 @@
0.11.0
0.10.0

View file

@ -22,7 +22,6 @@ namespace Hyprutils {
};
void create(CAnimationManager*, int, Memory::CSharedPointer<CBaseAnimatedVariable>);
void create2(CAnimationManager*, int, Memory::CWeakPointer<CBaseAnimatedVariable>);
void connectToActive();
void disconnectFromActive();
@ -137,7 +136,6 @@ namespace Hyprutils {
public:
CGenericAnimatedVariable() = default;
/* Deprecated: use create2 */
void create(const int typeInfo, CAnimationManager* pAnimationManager, Memory::CSharedPointer<CGenericAnimatedVariable<VarType, AnimationContext>> pSelf,
const VarType& initialValue) {
m_Begun = initialValue;
@ -147,16 +145,6 @@ namespace Hyprutils {
CBaseAnimatedVariable::create(pAnimationManager, typeInfo, pSelf);
}
/* Equivalent to create, except that it allows animated variables to be UP's */
void create2(const int typeInfo, CAnimationManager* pAnimationManager, Memory::CWeakPointer<CGenericAnimatedVariable<VarType, AnimationContext>> pSelf,
const VarType& initialValue) {
m_Begun = initialValue;
m_Value = initialValue;
m_Goal = initialValue;
CBaseAnimatedVariable::create2(pAnimationManager, typeInfo, pSelf);
}
CGenericAnimatedVariable(const CGenericAnimatedVariable&) = delete;
CGenericAnimatedVariable(CGenericAnimatedVariable&&) = delete;
CGenericAnimatedVariable& operator=(const CGenericAnimatedVariable&) = delete;

View file

@ -1,36 +0,0 @@
#pragma once
#include <span>
#include <string>
#include <string_view>
#include <optional>
#include <expected>
#include "../memory/UniquePtr.hpp"
namespace Hyprutils::CLI {
class CArgumentParserImpl;
class CArgumentParser {
public:
CArgumentParser(const std::span<const char*>& args);
~CArgumentParser() = default;
std::expected<void, std::string> registerBoolOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description);
std::expected<void, std::string> registerIntOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description);
std::expected<void, std::string> registerFloatOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description);
std::expected<void, std::string> registerStringOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description);
std::optional<bool> getBool(const std::string_view& name);
std::optional<int> getInt(const std::string_view& name);
std::optional<float> getFloat(const std::string_view& name);
std::optional<std::string_view> getString(const std::string_view& name);
// commence the parsing after registering
std::expected<void, std::string> parse();
std::string getDescription(const std::string_view& header, std::optional<size_t> maxWidth = {});
private:
Memory::CUniquePointer<CArgumentParserImpl> m_impl;
};
};

View file

@ -1,116 +0,0 @@
#pragma once
#include <format>
#include <expected>
#include <string_view>
#include "../memory/UniquePtr.hpp"
#include "../memory/WeakPtr.hpp"
namespace Hyprutils::CLI {
class CLoggerImpl;
enum eLogLevel : uint8_t {
LOG_TRACE = 0,
LOG_DEBUG,
LOG_WARN,
LOG_ERR,
LOG_CRIT,
};
// CLogger is a thread-safe, general purpose logger.
// the logger's stdout is enabled by default.
// color is enabled by default, it's only for stdout.
// everything else is disabled.
class CLogger {
public:
CLogger();
~CLogger();
CLogger(const CLogger&) = delete;
CLogger(CLogger&) = delete;
CLogger(CLogger&&) = delete;
void setLogLevel(eLogLevel level);
void setTime(bool enabled);
void setEnableStdout(bool enabled);
void setEnableColor(bool enabled);
void setEnableRolling(bool enabled);
std::expected<void, std::string> setOutputFile(const std::string_view& file);
const std::string& rollingLog();
void log(eLogLevel level, const std::string_view& msg);
template <typename... Args>
// NOLINTNEXTLINE
void log(eLogLevel level, std::format_string<Args...> fmt, Args&&... args) {
if (!m_shouldLogAtAll)
return;
if (level < m_logLevel)
return;
std::string logMsg = std::vformat(fmt.get(), std::make_format_args(args...));
log(level, logMsg);
}
private:
Memory::CUniquePointer<CLoggerImpl> m_impl;
// this has to be here as part of important optimization of trace logs
eLogLevel m_logLevel = LOG_DEBUG;
// this has to be here as part of important optimization of disabled logging
bool m_shouldLogAtAll = false;
friend class CLoggerImpl;
friend class CLoggerConnection;
};
// CLoggerConnection is a "handle" to a logger, that can be created from a logger and
// allows to send messages to a logger via a proxy
// this does not allow for any changes to the logger itself, only sending logs.
// Logger connections keep their own logLevel. They inherit it at creation, but can be changed
class CLoggerConnection {
public:
CLoggerConnection(CLogger& logger);
~CLoggerConnection();
CLoggerConnection(const CLoggerConnection&) = delete;
CLoggerConnection(CLoggerConnection&) = delete;
// Allow move
CLoggerConnection(CLoggerConnection&&) = default;
void setName(const std::string_view& name);
void setLogLevel(eLogLevel level);
void log(eLogLevel level, const std::string_view& msg);
CLogger* getLogger();
void redirect(CLogger& logger);
template <typename... Args>
// NOLINTNEXTLINE
void log(eLogLevel level, std::format_string<Args...> fmt, Args&&... args) {
if (!m_impl || !m_logger)
return;
if (!m_logger->m_shouldLogAtAll)
return;
if (level < m_logLevel)
return;
std::string logMsg = std::vformat(fmt.get(), std::make_format_args(args...));
log(level, logMsg);
}
private:
Memory::CWeakPointer<CLoggerImpl> m_impl;
CLogger* m_logger = nullptr;
eLogLevel m_logLevel = LOG_DEBUG;
std::string m_name = "";
};
};

View file

@ -1,55 +0,0 @@
#pragma once
#include "../memory/UniquePtr.hpp"
#include <cstdint>
#include <string>
#include <unordered_map>
#include <functional>
namespace Hyprutils::I18n {
struct SI18nEngineImpl;
typedef std::unordered_map<std::string, std::string> translationVarMap;
typedef std::function<std::string(const translationVarMap&)> translationFn;
class CI18nLocale {
public:
~CI18nLocale() = default;
std::string locale();
std::string stem();
std::string full();
private:
CI18nLocale(std::string fullLocale);
std::string m_locale, m_rawFullLocale;
friend class CI18nEngine;
};
class CI18nEngine {
public:
CI18nEngine();
~CI18nEngine();
/*
Register a translation entry. The internal translation db is kept as a vector,
so make sure your keys are linear, don't use e.g. 2 billion as that will call
.resize() on the vec to 2 billion.
If you pass a Fn, you can do logic, e.g. "1 point" vs "2 points".
*/
void registerEntry(const std::string& locale, uint64_t key, std::string&& translationUTF8);
void registerEntry(const std::string& locale, uint64_t key, translationFn&& translationFn);
void setFallbackLocale(const std::string& locale);
std::string localizeEntry(const std::string& locale, uint64_t key, const translationVarMap& map);
CI18nLocale getSystemLocale();
private:
Memory::CUniquePointer<SI18nEngineImpl> m_impl;
};
}

View file

@ -54,11 +54,11 @@ namespace Hyprutils::Memory {
using validHierarchy = std::enable_if_t<std::is_assignable_v<CAtomicSharedPointer<T>&, X>, CAtomicSharedPointer&>;
public:
explicit CAtomicSharedPointer(T* object) noexcept : m_ptr(new Atomic_::impl(sc<void*>(object), _delete), sc<void*>(object)) {
explicit CAtomicSharedPointer(T* object) noexcept : m_ptr(new Atomic_::impl(sc<void*>(object), _delete)) {
;
}
CAtomicSharedPointer(Impl_::impl_base* impl, void* data) noexcept : m_ptr(impl, data) {
CAtomicSharedPointer(Impl_::impl_base* impl) noexcept : m_ptr(impl) {
;
}
@ -219,17 +219,13 @@ namespace Hyprutils::Memory {
return m_ptr.impl_ ? m_ptr.impl_->ref() : 0;
}
Atomic_::impl* impl() const {
return sc<Atomic_::impl*>(m_ptr.impl_);
}
private:
static void _delete(void* p) {
std::default_delete<T>{}(sc<T*>(p));
}
std::lock_guard<std::recursive_mutex> implLockGuard() const {
return impl()->lockGuard();
return sc<Atomic_::impl*>(m_ptr.impl_)->lockGuard();
}
CSharedPointer<T> m_ptr;
@ -395,16 +391,12 @@ namespace Hyprutils::Memory {
if (!m_ptr.impl_->dataNonNull() || m_ptr.impl_->destroying() || !m_ptr.impl_->lockable())
return {};
return CAtomicSharedPointer<T>(m_ptr.impl_, m_ptr.m_data);
}
Atomic_::impl* impl() const {
return sc<Atomic_::impl*>(m_ptr.impl_);
return CAtomicSharedPointer<T>(m_ptr.impl_);
}
private:
std::lock_guard<std::recursive_mutex> implLockGuard() const {
return impl()->lockGuard();
return sc<Atomic_::impl*>(m_ptr.impl_)->lockGuard();
}
CWeakPointer<T> m_ptr;
@ -419,19 +411,4 @@ namespace Hyprutils::Memory {
[[nodiscard]] inline CAtomicSharedPointer<U> makeAtomicShared(Args&&... args) {
return CAtomicSharedPointer<U>(new U(std::forward<Args>(args)...));
}
template <typename T, typename U>
CAtomicSharedPointer<T> reinterpretPointerCast(const CAtomicSharedPointer<U>& ref) {
return CAtomicSharedPointer<T>(ref.impl(), ref.m_data);
}
template <typename T, typename U>
CAtomicSharedPointer<T> dynamicPointerCast(const CAtomicSharedPointer<U>& ref) {
if (!ref)
return nullptr;
T* newPtr = dynamic_cast<T*>(sc<U*>(ref.impl()->getData()));
if (!newPtr)
return nullptr;
return CAtomicSharedPointer<T>(ref.impl(), newPtr);
}
}

View file

@ -28,33 +28,31 @@ namespace Hyprutils {
/* creates a new shared pointer managing a resource
avoid calling. Could duplicate ownership. Prefer makeShared */
explicit CSharedPointer(T* object) noexcept : impl_(new Impl_::impl_base(sc<void*>(object), _delete)), m_data(sc<void*>(object)) {
explicit CSharedPointer(T* object) noexcept : impl_(new Impl_::impl_base(sc<void*>(object), _delete)) {
increment();
}
/* creates a shared pointer from a reference */
template <typename U, typename = isConstructible<U>>
CSharedPointer(const CSharedPointer<U>& ref) noexcept : impl_(ref.impl_), m_data(ref.m_data) {
CSharedPointer(const CSharedPointer<U>& ref) noexcept : impl_(ref.impl_) {
increment();
}
CSharedPointer(const CSharedPointer& ref) noexcept : impl_(ref.impl_), m_data(ref.m_data) {
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_);
std::swap(m_data, ref.m_data);
}
CSharedPointer(CSharedPointer&& ref) noexcept {
std::swap(impl_, ref.impl_);
std::swap(m_data, ref.m_data);
}
/* allows weakPointer to create from an impl */
CSharedPointer(Impl_::impl_base* implementation, void* data) noexcept : impl_(implementation), m_data(data) {
CSharedPointer(Impl_::impl_base* implementation) noexcept : impl_(implementation) {
increment();
}
@ -77,7 +75,6 @@ namespace Hyprutils {
decrement();
impl_ = rhs.impl_;
m_data = rhs.m_data;
increment();
return *this;
}
@ -88,7 +85,6 @@ namespace Hyprutils {
decrement();
impl_ = rhs.impl_;
m_data = rhs.m_data;
increment();
return *this;
}
@ -96,13 +92,11 @@ namespace Hyprutils {
template <typename U>
validHierarchy<const CSharedPointer<U>&> operator=(CSharedPointer<U>&& rhs) {
std::swap(impl_, rhs.impl_);
std::swap(m_data, rhs.m_data);
return *this;
}
CSharedPointer& operator=(CSharedPointer&& rhs) noexcept {
std::swap(impl_, rhs.impl_);
std::swap(m_data, rhs.m_data);
return *this;
}
@ -110,8 +104,6 @@ namespace Hyprutils {
return impl_ && impl_->dataNonNull();
}
// this compares that the pointed-to object is the same, but in multiple inheritance,
// different typed pointers can be equal if the object is the same
bool operator==(const CSharedPointer& rhs) const {
return impl_ == rhs.impl_;
}
@ -135,11 +127,10 @@ namespace Hyprutils {
void reset() {
decrement();
impl_ = nullptr;
m_data = nullptr;
}
T* get() const {
return impl_ && impl_->dataNonNull() ? sc<T*>(m_data) : nullptr;
return impl_ ? sc<T*>(impl_->getData()) : nullptr;
}
unsigned int strongRef() const {
@ -148,9 +139,6 @@ namespace Hyprutils {
Impl_::impl_base* impl_ = nullptr;
// Never use directly: raw data ptr, could be UAF
void* m_data = nullptr;
private:
static void _delete(void* p) {
std::default_delete<T>{}(sc<T*>(p));
@ -200,17 +188,7 @@ namespace Hyprutils {
template <typename T, typename U>
CSharedPointer<T> reinterpretPointerCast(const CSharedPointer<U>& ref) {
return CSharedPointer<T>(ref.impl_, ref.m_data);
}
template <typename T, typename U>
CSharedPointer<T> dynamicPointerCast(const CSharedPointer<U>& ref) {
if (!ref)
return nullptr;
T* newPtr = dynamic_cast<T*>(sc<U*>(ref.impl_->getData()));
if (!newPtr)
return nullptr;
return CSharedPointer<T>(ref.impl_, newPtr);
return CSharedPointer<T>(ref.impl_);
}
}
}

View file

@ -27,7 +27,6 @@ namespace Hyprutils {
return;
impl_ = ref.impl_;
m_data = ref.m_data;
incrementWeak();
}
@ -38,7 +37,6 @@ namespace Hyprutils {
return;
impl_ = ref.impl_;
m_data = ref.impl_->getData();
incrementWeak();
}
@ -49,7 +47,6 @@ namespace Hyprutils {
return;
impl_ = ref.impl_;
m_data = ref.m_data;
incrementWeak();
}
@ -58,19 +55,16 @@ namespace Hyprutils {
return;
impl_ = ref.impl_;
m_data = ref.m_data;
incrementWeak();
}
template <typename U, typename = isConstructible<U>>
CWeakPointer(CWeakPointer<U>&& ref) noexcept {
std::swap(impl_, ref.impl_);
std::swap(m_data, ref.m_data);
}
CWeakPointer(CWeakPointer&& ref) noexcept {
std::swap(impl_, ref.impl_);
std::swap(m_data, ref.m_data);
}
/* create a weak ptr from another weak ptr with assignment */
@ -81,7 +75,6 @@ namespace Hyprutils {
decrementWeak();
impl_ = rhs.impl_;
m_data = rhs.m_data;
incrementWeak();
return *this;
}
@ -92,7 +85,6 @@ namespace Hyprutils {
decrementWeak();
impl_ = rhs.impl_;
m_data = rhs.m_data;
incrementWeak();
return *this;
}
@ -105,7 +97,6 @@ namespace Hyprutils {
decrementWeak();
impl_ = rhs.impl_;
m_data = rhs.m_data;
incrementWeak();
return *this;
}
@ -135,14 +126,13 @@ namespace Hyprutils {
void reset() {
decrementWeak();
impl_ = nullptr;
m_data = nullptr;
}
CSharedPointer<T> lock() const {
if (!impl_ || !impl_->dataNonNull() || impl_->destroying() || !impl_->lockable())
return {};
return CSharedPointer<T>(impl_, m_data);
return CSharedPointer<T>(impl_);
}
/* this returns valid() */
@ -179,7 +169,7 @@ namespace Hyprutils {
}
T* get() const {
return impl_ && impl_->dataNonNull() ? sc<T*>(m_data) : nullptr;
return impl_ ? sc<T*>(impl_->getData()) : nullptr;
}
T* operator->() const {
@ -192,9 +182,6 @@ namespace Hyprutils {
Impl_::impl_base* impl_ = nullptr;
// Never use directly: raw data ptr, could be UAF
void* m_data = nullptr;
private:
/* no-op if there is no impl_ */
void decrementWeak() {
@ -220,16 +207,6 @@ namespace Hyprutils {
impl_->incWeak();
}
};
template <typename T, typename U>
CWeakPointer<T> dynamicPointerCast(const CWeakPointer<U>& ref) {
if (!ref)
return nullptr;
T* newPtr = dynamic_cast<T*>(sc<U*>(ref.impl_->getData()));
if (!newPtr)
return nullptr;
return CWeakPointer<T>(ref.impl_, newPtr);
}
}
}

View file

@ -1,9 +0,0 @@
#pragma once
#include <string>
#include <expected>
#include <string_view>
namespace Hyprutils::File {
std::expected<std::string, std::string> readFileAsString(const std::string_view& path);
}

View file

@ -26,13 +26,10 @@ namespace Hyprutils {
template <typename... Args>
class CSignalT : public CSignalBase {
template <typename T>
using RefArg = std::conditional_t<std::is_trivially_copyable_v<T>, T, const 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 (m_vListeners.empty() && m_vStaticListeners.empty())
return;
if constexpr (sizeof...(Args) == 0)
emitInternal(nullptr);
else {

View file

@ -4,11 +4,8 @@
namespace Hyprutils {
namespace String {
// trims beginning and end of whitespace characters
std::string trim(const char* in);
std::string trim(const std::string& in);
std::string_view trim(const std::string_view& in);
bool isNumber(const std::string& str, bool allowfloat = false);
void replaceInString(std::string& string, const std::string& what, const std::string& to);
bool truthy(const std::string_view& in);
};
};

View file

@ -1,50 +0,0 @@
#pragma once
#include <functional>
#include <vector>
#include <string>
namespace Hyprutils {
namespace String {
class CVarList2 {
public:
/** Split string into arg list
Prefer this over CConstVarList / CVarList, this is better.
@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
@param allowEscape whether to allow escaping the delimiter
*/
CVarList2(std::string&& in, const size_t lastArgNo = 0, const char delim = ',', const bool removeEmpty = false, const bool allowEscape = true);
~CVarList2() = 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 append(std::string&& arg);
bool contains(const std::string& el);
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>::const_iterator begin() const {
return m_args.begin();
}
std::vector<std::string_view>::const_iterator end() const {
return m_args.end();
}
private:
std::string m_inString;
std::vector<std::string> m_copyStrings;
std::vector<std::string_view> m_args;
};
}
}

View file

@ -4,17 +4,16 @@
stdenvAdapters,
cmake,
pkg-config,
gtest,
pixman,
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
}: let
inherit (builtins) foldl';
inherit (lib.lists) flatten optional;
inherit (lib.lists) flatten;
inherit (lib.strings) optionalString;
adapters = flatten [
@ -24,29 +23,26 @@ let
customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters;
in
customStdenv.mkDerivation {
customStdenv.mkDerivation {
pname = "hyprutils" + optionalString debug "-debug";
inherit version;
inherit version doCheck;
src = ../.;
doCheck = debug;
nativeBuildInputs = [
cmake
pkg-config
];
buildInputs = flatten [
(optional debug gtest)
buildInputs = [
pixman
];
outputs = [
"out"
"dev"
];
outputs = ["out" "dev"];
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
cmakeBuildType =
if debug
then "Debug"
else "RelWithDebInfo";
meta = with lib; {
homepage = "https://github.com/hyprwm/hyprutils";
@ -54,4 +50,4 @@ customStdenv.mkDerivation {
license = licenses.bsd3;
platforms = platforms.linux;
};
}
}

View file

@ -18,6 +18,6 @@ in {
inherit version;
};
hyprutils-debug = final.hyprutils.override {debug = true;};
hyprutils-with-tests = final.hyprutils-debug;
hyprutils-with-tests = final.hyprutils.override {doCheck = true;};
};
}

View file

@ -13,16 +13,7 @@ static const std::string DEFAULTSTYLE = "";
void CBaseAnimatedVariable::create(CAnimationManager* pManager, int typeInfo, SP<CBaseAnimatedVariable> pSelf) {
m_Type = typeInfo;
m_pSelf = std::move(pSelf);
m_pAnimationManager = pManager;
m_pSignals = pManager->getSignals();
m_bDummy = false;
}
void CBaseAnimatedVariable::create2(CAnimationManager* pManager, int typeInfo, WP<CBaseAnimatedVariable> pSelf) {
m_Type = typeInfo;
m_pSelf = std::move(pSelf);
m_pSelf = pSelf;
m_pAnimationManager = pManager;
m_pSignals = pManager->getSignals();
@ -46,22 +37,28 @@ void CBaseAnimatedVariable::disconnectFromActive() {
}
bool Hyprutils::Animation::CBaseAnimatedVariable::enabled() const {
if (m_pConfig && m_pConfig->pValues)
return m_pConfig->pValues->internalEnabled;
if (const auto PCONFIG = m_pConfig.lock()) {
const auto PVALUES = PCONFIG->pValues.lock();
return PVALUES ? PVALUES->internalEnabled : false;
}
return false;
}
const std::string& CBaseAnimatedVariable::getBezierName() const {
if (m_pConfig && m_pConfig->pValues)
return m_pConfig->pValues->internalBezier;
if (const auto PCONFIG = m_pConfig.lock()) {
const auto PVALUES = PCONFIG->pValues.lock();
return PVALUES ? PVALUES->internalBezier : DEFAULTBEZIERNAME;
}
return DEFAULTBEZIERNAME;
}
const std::string& CBaseAnimatedVariable::getStyle() const {
if (m_pConfig && m_pConfig->pValues)
return m_pConfig->pValues->internalStyle;
if (const auto PCONFIG = m_pConfig.lock()) {
const auto PVALUES = PCONFIG->pValues.lock();
return PVALUES ? PVALUES->internalStyle : DEFAULTSTYLE;
}
return DEFAULTSTYLE;
}
@ -69,8 +66,10 @@ const std::string& CBaseAnimatedVariable::getStyle() const {
float CBaseAnimatedVariable::getPercent() const {
const auto DURATIONPASSED = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - animationBegin).count();
if (m_pConfig && m_pConfig->pValues)
return std::clamp((DURATIONPASSED / 100.f) / m_pConfig->pValues->internalSpeed, 0.f, 1.f);
if (const auto PCONFIG = m_pConfig.lock()) {
const auto PVALUES = PCONFIG->pValues.lock();
return PVALUES ? std::clamp((DURATIONPASSED / 100.f) / PVALUES->internalSpeed, 0.f, 1.f) : 1.f;
}
return 1.f;
}
@ -79,7 +78,14 @@ float CBaseAnimatedVariable::getCurveValue() const {
if (!m_bIsBeingAnimated || isAnimationManagerDead())
return 1.f;
const auto BEZIER = m_pAnimationManager->getBezier(getBezierName());
std::string bezierName = "";
if (const auto PCONFIG = m_pConfig.lock()) {
const auto PVALUES = PCONFIG->pValues.lock();
if (PVALUES)
bezierName = PVALUES->internalBezier;
}
const auto BEZIER = m_pAnimationManager->getBezier(bezierName);
if (!BEZIER)
return 1.f;

View file

@ -64,13 +64,14 @@ void CAnimationManager::rotateActive() {
std::vector<CWeakPointer<CBaseAnimatedVariable>> active;
active.reserve(m_vActiveAnimatedVariables.size()); // avoid reallocations
for (auto const& av : m_vActiveAnimatedVariables) {
if (!av)
const auto PAV = av.lock();
if (!PAV)
continue;
if (av->ok() && av->isBeingAnimated())
if (PAV->ok() && PAV->isBeingAnimated())
active.emplace_back(av);
else
av->m_bIsConnectedToActive = false;
PAV->m_bIsConnectedToActive = false;
}
m_vActiveAnimatedVariables = std::move(active);

View file

@ -26,15 +26,20 @@ void CBezierCurve::setup4(const std::array<Vector2D, 4>& pVec) {
pVec[3],
};
// Pre-bake curve
//
// We start baking at t=(i+1)/n not at t=0
// That means the first baked x can be > 0 if curve itself starts at x>0
if (m_vPoints.size() != 4)
std::abort();
// bake BAKEDPOINTS points for faster lookups
// T -> X ( / BAKEDPOINTS )
for (int i = 0; i < BAKEDPOINTS; ++i) {
// When i=0 -> t=1/255
const float t = (i + 1) * INVBAKEDPOINTS;
float const t = (i + 1) / sc<float>(BAKEDPOINTS);
m_aPointsBaked[i] = Vector2D(getXForT(t), getYForT(t));
}
for (int j = 1; j < 10; ++j) {
float i = j / 10.0f;
getYForPoint(i);
}
}
float CBezierCurve::getXForT(float const& t) const {
@ -48,7 +53,7 @@ float CBezierCurve::getYForT(float const& t) const {
float t2 = t * t;
float t3 = t2 * t;
return ((1 - t) * (1 - t) * (1 - t) * m_vPoints[0].y) + (3 * t * (1 - t) * (1 - t) * m_vPoints[1].y) + (3 * t2 * (1 - t) * m_vPoints[2].y) + (t3 * m_vPoints[3].y);
return ((1 - t) * (1 - t) * (1 - t) * m_vPoints[0].y) +(3 * t * (1 - t) * (1 - t) * m_vPoints[1].y) + (3 * t2 * (1 - t) * m_vPoints[2].y) + (t3 * m_vPoints[3].y);
}
// Todo: this probably can be done better and faster
@ -66,40 +71,21 @@ float CBezierCurve::getYForPoint(float const& x) const {
else
index -= step;
// Clamp to avoid index walking off
if (index < 0)
index = 0;
else if (index > BAKEDPOINTS - 1)
index = BAKEDPOINTS - 1;
below = m_aPointsBaked[index].x < x;
}
int lowerIndex = index - (!below || index == BAKEDPOINTS - 1);
// Clamp final indices
if (lowerIndex < 0)
lowerIndex = 0;
else if (lowerIndex > BAKEDPOINTS - 2)
lowerIndex = BAKEDPOINTS - 2;
// in the name of performance i shall make a hack
const auto LOWERPOINT = &m_aPointsBaked[lowerIndex];
const auto UPPERPOINT = &m_aPointsBaked[lowerIndex + 1];
// In the name of performance I shall make a hack
const auto& LOWERPOINT = m_aPointsBaked[lowerIndex];
const auto& UPPERPOINT = m_aPointsBaked[lowerIndex + 1];
const auto PERCINDELTA = (x - LOWERPOINT->x) / (UPPERPOINT->x - LOWERPOINT->x);
const float dx = (UPPERPOINT.x - LOWERPOINT.x);
// If two baked points have almost the same x
// just return the lower one
if (dx <= 1e-6f)
return LOWERPOINT.y;
if (std::isnan(PERCINDELTA) || std::isinf(PERCINDELTA)) // can sometimes happen for VERY small x
return 0.f;
const auto PERCINDELTA = (x - LOWERPOINT.x) / dx;
// Can sometimes happen for VERY small x
if (std::isnan(PERCINDELTA) || std::isinf(PERCINDELTA))
return LOWERPOINT.y;
return LOWERPOINT.y + ((UPPERPOINT.y - LOWERPOINT.y) * PERCINDELTA);
return LOWERPOINT->y + ((UPPERPOINT->y - LOWERPOINT->y) * PERCINDELTA);
}
const std::vector<Hyprutils::Math::Vector2D>& CBezierCurve::getControlPoints() const {

View file

@ -1,298 +0,0 @@
#include "ArgumentParser.hpp"
#include <format>
#include <vector>
#include <algorithm>
#include <hyprutils/string/String.hpp>
#include <hyprutils/memory/Casts.hpp>
using namespace Hyprutils::CLI;
using namespace Hyprutils::Memory;
using namespace Hyprutils::String;
using namespace Hyprutils;
CArgumentParser::CArgumentParser(const std::span<const char*>& args) : m_impl(makeUnique<CArgumentParserImpl>(args)) {
;
}
std::expected<void, std::string> CArgumentParser::registerBoolOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description) {
return m_impl->registerOption(name, abbrev, description, ARG_TYPE_BOOL);
}
std::expected<void, std::string> CArgumentParser::registerIntOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description) {
return m_impl->registerOption(name, abbrev, description, ARG_TYPE_INT);
}
std::expected<void, std::string> CArgumentParser::registerFloatOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description) {
return m_impl->registerOption(name, abbrev, description, ARG_TYPE_FLOAT);
}
std::expected<void, std::string> CArgumentParser::registerStringOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description) {
return m_impl->registerOption(name, abbrev, description, ARG_TYPE_STR);
}
std::optional<bool> CArgumentParser::getBool(const std::string_view& name) {
auto ref = m_impl->getValue(name);
if (ref == m_impl->m_values.end())
return std::nullopt;
if (const auto pval = std::get_if<bool>(&ref->val); pval)
return *pval;
return std::nullopt;
}
std::optional<int> CArgumentParser::getInt(const std::string_view& name) {
auto ref = m_impl->getValue(name);
if (ref == m_impl->m_values.end())
return std::nullopt;
if (const auto pval = std::get_if<int>(&ref->val); pval)
return *pval;
return std::nullopt;
}
std::optional<float> CArgumentParser::getFloat(const std::string_view& name) {
auto ref = m_impl->getValue(name);
if (ref == m_impl->m_values.end())
return std::nullopt;
if (const auto pval = std::get_if<float>(&ref->val); pval)
return *pval;
return std::nullopt;
}
std::optional<std::string_view> CArgumentParser::getString(const std::string_view& name) {
auto ref = m_impl->getValue(name);
if (ref == m_impl->m_values.end())
return std::nullopt;
if (const auto pval = std::get_if<std::string>(&ref->val); pval)
return *pval;
return std::nullopt;
}
std::string CArgumentParser::getDescription(const std::string_view& header, std::optional<size_t> maxWidth) {
return m_impl->getDescription(header, maxWidth);
}
std::expected<void, std::string> CArgumentParser::parse() {
return m_impl->parse();
}
CArgumentParserImpl::CArgumentParserImpl(const std::span<const char*>& args) {
m_argv.reserve(args.size());
for (const auto& a : args) {
m_argv.emplace_back(a);
}
}
std::vector<SArgumentKey>::iterator CArgumentParserImpl::getValue(const std::string_view& sv) {
if (sv.empty())
return m_values.end();
auto it = std::ranges::find_if(m_values, [&sv](const auto& e) { return e.full == sv || e.abbrev == sv; });
return it;
}
std::expected<void, std::string> CArgumentParserImpl::registerOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description,
eArgumentType type) {
if (name.empty())
return std::unexpected("Name cannot be empty");
if (getValue(name) != m_values.end() || getValue(abbrev) != m_values.end())
return std::unexpected("Value already exists");
m_values.emplace_back(SArgumentKey{
.full = std::string{name},
.abbrev = std::string{abbrev},
.desc = std::string{description},
.argType = type,
.val = std::monostate{},
});
return {};
}
std::expected<void, std::string> CArgumentParserImpl::parse() {
// walk the args
for (size_t i = 1; i < m_argv.size(); ++i) {
auto val = m_values.end();
const auto& arg = m_argv.at(i);
if (arg.starts_with("--"))
val = getValue(std::string_view{arg}.substr(2));
else if (arg.starts_with('-'))
val = getValue(std::string_view{arg}.substr(1));
else
return std::unexpected(std::format("Invalid element found while parsing: {}", arg));
if (val == m_values.end())
return std::unexpected(std::format("Invalid argument found while parsing: {}", arg));
switch (val->argType) {
case ARG_TYPE_BOOL: {
val->val = true;
break;
}
case ARG_TYPE_INT: {
if (i + 1 >= m_argv.size())
return std::unexpected(std::format("Failed parsing argument {}, no value supplied", arg));
const auto& next = std::string{m_argv.at(++i)};
if (!isNumber(next))
return std::unexpected(std::format("Failed parsing argument {}, value {} is not an int", arg, next));
try {
val->val = sc<int>(std::stoi(next));
} catch (...) { return std::unexpected(std::format("Failed parsing argument {}, value {} is not an int", arg, next)); }
break;
}
case ARG_TYPE_FLOAT: {
if (i + 1 >= m_argv.size())
return std::unexpected(std::format("Failed parsing argument {}, no value supplied", arg));
const auto& next = std::string{m_argv.at(++i)};
if (!isNumber(next, true))
return std::unexpected(std::format("Failed parsing argument {}, value {} is not a float", arg, next));
try {
val->val = sc<float>(std::stof(next));
} catch (...) { return std::unexpected(std::format("Failed parsing argument {}, value {} is not a float", arg, next)); }
break;
}
case ARG_TYPE_STR: {
if (i + 1 >= m_argv.size())
return std::unexpected(std::format("Failed parsing argument {}, no value supplied", arg));
val->val = std::string{m_argv.at(++i)};
break;
}
case ARG_TYPE_END: break;
}
}
return {};
}
std::string CArgumentParserImpl::getDescription(const std::string_view& header, std::optional<size_t> maxWidth) {
const size_t MAX_COLS = maxWidth.value_or(80);
const std::string PAD_STR = " ";
constexpr const std::array<const char*, ARG_TYPE_END> TYPE_STRS = {
"", // bool
"[int]", // int
"[float]", // float
"[str]", // str
};
//
auto wrap = [](const std::string_view& str, size_t maxW) -> std::vector<std::string_view> {
std::vector<std::string_view> result;
// walk word by word
size_t nextSpacePos = 0, lastBreakPos = 0;
while (true) {
size_t lastSpacePos = nextSpacePos;
nextSpacePos = str.find(' ', nextSpacePos + 1);
if (nextSpacePos == std::string::npos)
break;
if (nextSpacePos - lastBreakPos > maxW) {
if (lastSpacePos - lastBreakPos <= maxW) {
// break
result.emplace_back(str.substr(lastBreakPos, lastSpacePos - lastBreakPos));
lastBreakPos = lastSpacePos + 1;
} else {
while (lastSpacePos - lastBreakPos > maxW) {
// break
result.emplace_back(str.substr(lastBreakPos, maxW));
lastBreakPos += maxW;
}
}
continue;
}
}
result.emplace_back(str.substr(lastBreakPos));
return result;
};
auto pad = [&PAD_STR](size_t len) -> std::string_view {
if (len >= PAD_STR.size())
return PAD_STR;
return std::string_view{PAD_STR}.substr(0, len);
};
std::string rolling;
rolling += std::format("┏ {}\n", header);
rolling += "";
for (size_t i = 0; i < MAX_COLS; ++i) {
rolling += "";
}
rolling += "\n";
// get max widths
size_t maxArgWidth = 0, maxShortWidth = 0;
for (const auto& v : m_values) {
maxShortWidth = std::max(maxShortWidth, v.abbrev.size() + 4 + std::string_view{TYPE_STRS[v.argType]}.length());
maxArgWidth = std::max(maxArgWidth, v.full.size() + 3);
}
// write the table
for (const auto& v : m_values) {
size_t lenUsed = 0;
rolling += "┣ --" + v.full;
lenUsed += 3 + v.full.size();
rolling += pad(maxArgWidth - lenUsed);
lenUsed = maxArgWidth;
if (!v.abbrev.empty()) {
rolling += " -" + v.abbrev;
lenUsed += 2 + v.abbrev.size();
rolling += " ";
rolling += TYPE_STRS[v.argType];
lenUsed += std::string_view{TYPE_STRS[v.argType]}.length() + 1;
rolling += pad(maxArgWidth + maxShortWidth - lenUsed);
} else
rolling += pad(maxShortWidth);
lenUsed = maxArgWidth + maxShortWidth;
rolling += " | ";
lenUsed += 3;
const auto ROWS = wrap(v.desc, MAX_COLS - lenUsed);
const auto LEN_START_DESC = lenUsed;
rolling += ROWS[0];
lenUsed += ROWS[0].size();
rolling += pad(MAX_COLS - lenUsed);
rolling += "\n";
for (size_t i = 1; i < ROWS.size(); ++i) {
lenUsed = LEN_START_DESC;
rolling += "";
rolling += pad(LEN_START_DESC);
rolling += ROWS[i];
lenUsed += ROWS[i].size();
rolling += pad(MAX_COLS - lenUsed);
rolling += "\n";
}
}
rolling += "";
for (size_t i = 0; i < MAX_COLS; ++i) {
rolling += "";
}
rolling += "\n";
return rolling;
}

View file

@ -1,39 +0,0 @@
#include <hyprutils/cli/ArgumentParser.hpp>
#include <map>
#include <variant>
#include <vector>
namespace Hyprutils::CLI {
enum eArgumentType : uint8_t {
ARG_TYPE_BOOL = 0,
ARG_TYPE_INT,
ARG_TYPE_FLOAT,
ARG_TYPE_STR,
ARG_TYPE_END,
};
struct SArgumentKey {
using Value = std::variant<std::monostate, bool, int, float, std::string>;
std::string full, abbrev, desc;
eArgumentType argType = ARG_TYPE_BOOL;
Value val;
};
class CArgumentParserImpl {
public:
CArgumentParserImpl(const std::span<const char*>& args);
~CArgumentParserImpl() = default;
std::string getDescription(const std::string_view& header, std::optional<size_t> maxWidth = {});
std::expected<void, std::string> parse();
std::vector<SArgumentKey>::iterator getValue(const std::string_view& sv);
std::expected<void, std::string> registerOption(const std::string_view& name, const std::string_view& abbrev, const std::string_view& description, eArgumentType type);
std::vector<SArgumentKey> m_values;
std::vector<std::string_view> m_argv;
};
}

View file

@ -1,199 +0,0 @@
#include "Logger.hpp"
#include <chrono>
#include <print>
using namespace Hyprutils;
using namespace Hyprutils::CLI;
CLogger::CLogger() {
m_impl = Memory::makeUnique<CLoggerImpl>(this);
}
CLogger::~CLogger() = default;
void CLogger::setLogLevel(eLogLevel level) {
m_logLevel = level;
}
void CLogger::setTime(bool enabled) {
m_impl->m_timeEnabled = enabled;
}
void CLogger::setEnableStdout(bool enabled) {
m_impl->m_stdoutEnabled = enabled;
m_impl->updateParentShouldLog();
}
void CLogger::setEnableColor(bool enabled) {
m_impl->m_colorEnabled = enabled;
}
void CLogger::setEnableRolling(bool enabled) {
m_impl->m_rollingEnabled = enabled;
}
std::expected<void, std::string> CLogger::setOutputFile(const std::string_view& file) {
if (file.empty()) {
m_impl->m_fileEnabled = false;
m_impl->m_logOfs = {};
return {};
}
std::filesystem::path filePath{file};
std::error_code ec;
if (!filePath.has_parent_path())
return std::unexpected("Path has no parent");
auto dir = filePath.parent_path();
if (!std::filesystem::exists(dir, ec) || ec)
return std::unexpected("Parent path is inaccessible, or doesn't exist");
m_impl->m_logOfs = std::ofstream{filePath, std::ios::trunc};
m_impl->m_logFilePath = filePath;
if (!m_impl->m_logOfs.good())
return std::unexpected("Failed to open a write stream");
m_impl->m_fileEnabled = true;
m_impl->updateParentShouldLog();
return {};
}
void CLogger::log(eLogLevel level, const std::string_view& msg) {
if (!m_shouldLogAtAll)
return;
if (level < m_logLevel)
return;
m_impl->log(level, msg);
}
const std::string& CLogger::rollingLog() {
return m_impl->m_rollingLog;
}
CLoggerImpl::CLoggerImpl(CLogger* parent) : m_parent(parent) {
updateParentShouldLog();
}
void CLoggerImpl::log(eLogLevel level, const std::string_view& msg, const std::string_view& from) {
std::lock_guard<std::mutex> lg(m_logMtx);
std::string logPrefix = "", logPrefixColor = "";
std::string logMsg = "";
switch (level) {
case LOG_TRACE:
logPrefix += "TRACE ";
logPrefixColor += "\033[1;34mTRACE \033[0m";
break;
case LOG_DEBUG:
logPrefix += "DEBUG ";
logPrefixColor += "\033[1;32mDEBUG \033[0m";
break;
case LOG_WARN:
logPrefix += "WARN ";
logPrefixColor += "\033[1;33mWARN \033[0m";
break;
case LOG_ERR:
logPrefix += "ERR ";
logPrefixColor += "\033[1;31mERR \033[0m";
break;
case LOG_CRIT:
logPrefix += "CRIT ";
logPrefixColor += "\033[1;35mCRIT \033[0m";
break;
}
if (m_timeEnabled) {
#ifndef _LIBCPP_VERSION
static auto current_zone = std::chrono::current_zone();
const auto zt = std::chrono::zoned_time{current_zone, std::chrono::system_clock::now()};
const auto hms = std::chrono::hh_mm_ss{zt.get_local_time() - std::chrono::floor<std::chrono::days>(zt.get_local_time())};
#else
// TODO: current clang 17 does not support `zoned_time`, remove this once clang 19 is ready
const auto hms = std::chrono::hh_mm_ss{std::chrono::system_clock::now() - std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now())};
#endif
logMsg += std::format("@ {} ", hms);
}
if (!from.empty()) {
logMsg += "from ";
logMsg += from;
logMsg += " ";
}
logMsg += "]: ";
logMsg += msg;
if (m_stdoutEnabled) {
try {
std::println("{}{}", m_colorEnabled ? logPrefixColor : logPrefix, logMsg);
std::fflush(stdout);
} catch (std::exception& e) {
; // this could be e.g. stdout closed
}
}
if (m_fileEnabled)
m_logOfs << logPrefix << logMsg << "\n";
if (m_rollingEnabled)
appendToRolling(logPrefix + logMsg);
}
void CLoggerImpl::updateParentShouldLog() {
m_parent->m_shouldLogAtAll = m_fileEnabled || m_stdoutEnabled;
}
void CLoggerImpl::appendToRolling(const std::string& s) {
constexpr const size_t ROLLING_LOG_SIZE = 4096;
if (!m_rollingLog.empty())
m_rollingLog += "\n";
m_rollingLog += s;
if (m_rollingLog.size() > ROLLING_LOG_SIZE)
m_rollingLog = m_rollingLog.substr(m_rollingLog.find('\n', m_rollingLog.size() - ROLLING_LOG_SIZE) + 1);
}
CLoggerConnection::CLoggerConnection(CLogger& logger) : m_impl(logger.m_impl), m_logger(&logger), m_logLevel(logger.m_logLevel) {
;
}
CLoggerConnection::~CLoggerConnection() = default;
void CLoggerConnection::setName(const std::string_view& name) {
m_name = name;
}
void CLoggerConnection::setLogLevel(eLogLevel level) {
m_logLevel = level;
}
void CLoggerConnection::log(eLogLevel level, const std::string_view& msg) {
if (!m_impl || !m_logger)
return;
if (!m_logger->m_shouldLogAtAll)
return;
if (level < m_logLevel)
return;
m_impl->log(level, msg, m_name);
}
CLogger* CLoggerConnection::getLogger() {
if (!m_impl)
return nullptr;
return m_logger;
}
void CLoggerConnection::redirect(CLogger& logger) {
m_impl = logger.m_impl;
m_logger = &logger;
}

View file

@ -1,35 +0,0 @@
#include <hyprutils/cli/Logger.hpp>
#include <fstream>
#include <filesystem>
#include <mutex>
namespace Hyprutils::CLI {
class CLoggerImpl {
public:
CLoggerImpl(CLogger*);
~CLoggerImpl() = default;
CLoggerImpl(const CLoggerImpl&) = delete;
CLoggerImpl(CLoggerImpl&) = delete;
CLoggerImpl(CLoggerImpl&&) = delete;
void updateParentShouldLog();
void appendToRolling(const std::string& s);
void log(eLogLevel level, const std::string_view& msg, const std::string_view& from = "");
std::string m_rollingLog;
std::ofstream m_logOfs;
std::filesystem::path m_logFilePath;
bool m_timeEnabled = false;
bool m_stdoutEnabled = true;
bool m_fileEnabled = false;
bool m_colorEnabled = true;
bool m_rollingEnabled = false;
std::mutex m_logMtx;
// this is fine because CLogger is NOMOVE and NOCOPY
CLogger* m_parent = nullptr;
};
}

View file

@ -1,165 +0,0 @@
#include "I18nEngine.hpp"
#include <algorithm>
#include <format>
#include <locale>
#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Hyprutils::I18n;
using namespace Hyprutils;
using namespace Hyprutils::Utils;
CI18nEngine::CI18nEngine() : m_impl(Memory::makeUnique<SI18nEngineImpl>()) {
;
}
CI18nEngine::~CI18nEngine() = default;
void CI18nEngine::registerEntry(const std::string& locale, uint64_t key, std::string&& translationUTF8) {
auto& entryVec = m_impl->entries[locale];
if (entryVec.size() <= key)
entryVec.resize(key + 1);
entryVec[key].entry = std::move(translationUTF8);
entryVec[key].exists = true;
}
void CI18nEngine::registerEntry(const std::string& locale, uint64_t key, translationFn&& translationFn) {
auto& entryVec = m_impl->entries[locale];
if (entryVec.size() <= key)
entryVec.resize(key + 1);
entryVec[key].fn = std::move(translationFn);
entryVec[key].exists = true;
}
void CI18nEngine::setFallbackLocale(const std::string& locale) {
m_impl->fallbackLocale = locale;
}
std::string CI18nEngine::localizeEntry(const std::string& locale, uint64_t key, const translationVarMap& map) {
SI18nTranslationEntry* entry = nullptr;
if (m_impl->entries.contains(locale) && m_impl->entries[locale].size() > key)
entry = &m_impl->entries[locale][key];
if (locale.contains('_')) {
if (!entry || !entry->exists) {
// try to fall back to lang_LANG
auto stem = locale.substr(0, locale.find('_'));
auto stemUpper = stem;
std::ranges::transform(stemUpper, stemUpper.begin(), ::toupper);
auto newLocale = std::format("{}_{}", stem, stemUpper);
if (m_impl->entries.contains(newLocale) && m_impl->entries[newLocale].size() > key)
entry = &m_impl->entries[newLocale][key];
}
if (!entry || !entry->exists) {
// try to fall back to any lang prefixed with our prefix
const auto stem = locale.substr(0, locale.find('_') + 1);
const auto stemRaw = locale.substr(0, locale.find('_'));
for (const auto& [k, v] : m_impl->entries) {
if (k.starts_with(stem) || k == stemRaw) {
if (m_impl->entries.contains(k) && m_impl->entries[k].size() > key)
entry = &m_impl->entries[k][key];
if (entry && entry->exists)
break;
}
}
}
} else {
// locale doesn't have a _, e.g. pl
// find any locale that has the same stem
for (const auto& [k, v] : m_impl->entries) {
if (k.starts_with(locale + "_") || k == locale) {
if (m_impl->entries.contains(k) && m_impl->entries[k].size() > key)
entry = &m_impl->entries[k][key];
if (entry && entry->exists)
break;
}
}
}
if (!entry || !entry->exists) {
// fall back to general fallback
if (m_impl->entries.contains(m_impl->fallbackLocale) && m_impl->entries[m_impl->fallbackLocale].size() > key)
entry = &m_impl->entries[m_impl->fallbackLocale][key];
}
if (!entry || !entry->exists)
return "";
std::string_view rawStr = entry->entry;
std::string fnStringContainer;
if (entry->fn) {
fnStringContainer = entry->fn(map);
rawStr = fnStringContainer;
}
struct SRange {
size_t begin = 0;
size_t end = 0;
const std::string* val = nullptr;
};
std::vector<SRange> rangesFound;
// discover all replacable ranges
for (const auto& [k, v] : map) {
size_t start = rawStr.find(k, 0);
while (start != std::string::npos) {
if (start == 0 || start + k.size() >= rawStr.size())
break;
// always move the pointer
CScopeGuard x([&start, &rawStr, &k] { start = rawStr.find(k, start + 1); });
if (rawStr[start - 1] != '{' || rawStr[start + k.size()] != '}')
continue;
// add range
rangesFound.emplace_back(SRange{.begin = start - 1, .end = start + k.size() + 1, .val = &v});
}
}
if (rangesFound.empty())
return std::string{rawStr};
// build the new string. First, sort our entries
std::ranges::sort(rangesFound, [](const auto& a, const auto& b) { return a.begin < b.begin; });
// calc the size
size_t stringLen = 0;
size_t lastBegin = 0;
for (const auto& r : rangesFound) {
stringLen += r.begin - lastBegin + r.val->size();
lastBegin = r.end;
}
stringLen += rawStr.size() - lastBegin;
lastBegin = 0;
const auto ORIGINAL_STR = std::string_view{rawStr};
std::string newStr;
newStr.reserve(stringLen);
for (const auto& r : rangesFound) {
newStr += ORIGINAL_STR.substr(lastBegin, r.begin - lastBegin);
newStr += *r.val;
lastBegin = r.end;
}
newStr += ORIGINAL_STR.substr(lastBegin);
return newStr;
}
CI18nLocale CI18nEngine::getSystemLocale() {
try {
return CI18nLocale(std::locale("").name());
} catch (...) { return CI18nLocale("en_US.UTF-8"); }
}

View file

@ -1,18 +0,0 @@
#pragma once
#include <hyprutils/i18n/I18nEngine.hpp>
namespace Hyprutils::I18n {
struct SI18nTranslationEntry {
bool exists = false;
std::string entry = "";
translationFn fn = nullptr;
};
struct SI18nEngineImpl {
std::unordered_map<std::string, std::vector<SI18nTranslationEntry>> entries;
std::string fallbackLocale = "en_US";
};
std::string extractLocale(std::string locale);
};

View file

@ -1,44 +0,0 @@
#include "I18nEngine.hpp"
using namespace Hyprutils::I18n;
std::string Hyprutils::I18n::extractLocale(std::string locale) {
// localeStr is very arbitrary... from my testing, it can be:
// en_US.UTF-8
// LC_CTYPE=en_US
// POSIX
// *
//
// We only return e.g. en_US or pl_PL, or pl
if (locale == "POSIX")
return "en_US";
if (locale == "*")
return "en_US";
if (locale.contains('='))
locale = locale.substr(locale.find('=') + 1);
if (locale.contains('.'))
locale = locale.substr(0, locale.find('.'));
return locale;
}
CI18nLocale::CI18nLocale(std::string fullLocale) : m_rawFullLocale(std::move(fullLocale)) {
m_locale = extractLocale(m_rawFullLocale);
}
std::string CI18nLocale::locale() {
return m_locale;
}
std::string CI18nLocale::stem() {
if (m_locale.contains('_'))
return m_locale.substr(0, m_locale.find('_'));
return m_locale;
}
std::string CI18nLocale::full() {
return m_rawFullLocale;
}

View file

@ -1,20 +0,0 @@
#include <hyprutils/os/File.hpp>
#include <filesystem>
#include <fstream>
using namespace Hyprutils;
using namespace Hyprutils::File;
std::expected<std::string, std::string> File::readFileAsString(const std::string_view& path) {
std::error_code ec;
if (!std::filesystem::exists(path, ec) || ec)
return std::unexpected("File not found");
std::ifstream file(std::string{path});
if (!file.good())
return std::unexpected("Failed to open file");
return std::string((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>()));
}

View file

@ -1,10 +1,26 @@
#include <ranges>
#include <algorithm>
#include <hyprutils/string/String.hpp>
#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;
@ -17,11 +33,11 @@ CConstVarList::CConstVarList(const std::string& in, const size_t lastArgNo, cons
if (removeEmpty && s.empty())
continue;
if (++idx == lastArgNo) {
m_args.emplace_back(trim(std::string_view{m_str}.substr(pos)));
m_args.emplace_back(trim(in.substr(pos)));
break;
}
pos += s.size() + 1;
m_args.emplace_back(trim(std::string_view{s.data()}));
m_args.emplace_back(trim(s.data()));
}
}

View file

@ -22,27 +22,6 @@ std::string Hyprutils::String::trim(const std::string& in) {
return result;
}
std::string_view Hyprutils::String::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);
}
std::string Hyprutils::String::trim(const char* in) {
return trim(std::string{in});
}
bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) {
if (str.empty())
return false;
@ -88,16 +67,3 @@ void Hyprutils::String::replaceInString(std::string& string, const std::string&
pos += to.length();
}
}
bool Hyprutils::String::truthy(const std::string_view& in) {
if (in == "1")
return true;
if (in == "0")
return false;
std::string lower = std::string{in};
std::ranges::transform(lower, lower.begin(), ::tolower);
return lower.starts_with("true") || lower.starts_with("yes") || lower.starts_with("on");
}

View file

@ -22,7 +22,7 @@ Hyprutils::String::CVarList::CVarList(const std::string& in, const size_t lastAr
break;
}
pos += s.size() + 1;
m_vArgs.emplace_back(trim(std::string{s.data()}));
m_vArgs.emplace_back(trim(s.data()));
}
}

View file

@ -1,113 +0,0 @@
#include <algorithm>
#include <hyprutils/string/VarList2.hpp>
#include <hyprutils/string/String.hpp>
using namespace Hyprutils::String;
CVarList2::CVarList2(std::string&& in, const size_t lastArgNo, const char delim, const bool removeEmpty, const bool allowEscape) : m_inString(std::move(in)) {
if (m_inString.empty())
return;
auto isDelimiter = [&delim](const char& c) { return delim == 's' ? std::isspace(c) : delim == c; };
size_t argBegin = 0;
std::vector<size_t> escapedIndices; // local to the current arg
for (size_t i = 0; i < m_inString.size(); ++i) {
const char& c = m_inString[i];
if (!isDelimiter(c))
continue;
if (allowEscape) {
// we allow escape, so this might be escaped. Check first
if (i - argBegin != 0) {
const char& previousC = m_inString[i - 1];
if (i - argBegin == 1) {
if (previousC == '\\') {
escapedIndices.emplace_back(i - argBegin - 1);
continue; // escaped
}
// fall to breaking, not escaped
} else {
const char& prevPreviousC = m_inString[i - 2];
if (previousC == '\\') {
// whether or not escaped, pop char
escapedIndices.emplace_back(i - argBegin - 1);
if (prevPreviousC != '\\') {
// escaped
continue;
}
}
// fall to breaking, not escaped, but mark the \\ to be popped
}
// fall to breaking, couldn't be escaped
}
}
// here we found a delimiter and need to break up the string (not escaped)
if (escapedIndices.empty()) {
// we didn't escape anything, so we can use inString
const auto ARG = trim(std::string_view{m_inString}.substr(argBegin, i - argBegin));
if (!ARG.empty() || !removeEmpty)
m_args.emplace_back(ARG);
} else {
// we escaped something, fixup the string, add to copies, then emplace
std::string cpy = m_inString.substr(argBegin, i - argBegin);
for (size_t i = 0; i < escapedIndices.size(); ++i) {
cpy = cpy.substr(0, escapedIndices[i] - i) + cpy.substr(escapedIndices[i] - i + 1);
}
m_copyStrings.emplace_back(std::move(cpy));
m_args.emplace_back(trim(std::string_view{m_copyStrings.back()}));
}
// update next argBegin
argBegin = i + 1;
escapedIndices.clear();
}
// append anything left
if (argBegin < m_inString.size()) {
if (escapedIndices.empty()) {
// we didn't escape anything, so we can use inString
const auto ARG = trim(std::string_view{m_inString}.substr(argBegin, m_inString.size() - argBegin));
if (!ARG.empty() || !removeEmpty)
m_args.emplace_back(ARG);
} else {
// we escaped something, fixup the string, add to copies, then emplace
std::string cpy = m_inString.substr(argBegin, m_inString.size() - argBegin);
for (size_t i = 0; i < escapedIndices.size(); ++i) {
cpy = cpy.substr(0, escapedIndices[i] - i) + cpy.substr(escapedIndices[i] - i + 1);
}
m_copyStrings.emplace_back(std::move(cpy));
m_args.emplace_back(trim(std::string_view{m_copyStrings.back()}));
}
}
}
std::string CVarList2::join(const std::string& joiner, size_t from, size_t to) const {
if (to == 0 || to <= from)
to = m_args.size();
std::string roll;
for (size_t i = from; i < to && i < m_args.size(); ++i) {
roll += m_args[i];
if (i + 1 < to && i + 1 < m_args.size())
roll += joiner;
}
return roll;
}
void CVarList2::append(std::string&& arg) {
m_copyStrings.emplace_back(std::move(arg));
m_args.emplace_back(m_copyStrings.back());
}
bool CVarList2::contains(const std::string& el) {
return std::ranges::any_of(m_args, [&el](const auto& e) { return e == el; });
}

View file

@ -1,11 +1,9 @@
#include <gtest/gtest.h>
#include <hyprutils/animation/AnimationConfig.hpp>
#include <hyprutils/animation/AnimationManager.hpp>
#include <hyprutils/animation/AnimatedVariable.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#include "shared.hpp"
#define SP CSharedPointer
#define WP CWeakPointer
@ -21,7 +19,7 @@ template <typename VarType>
using CAnimatedVariable = CGenericAnimatedVariable<VarType, EmtpyContext>;
template <typename VarType>
using PANIMVAR = UP<CAnimatedVariable<VarType>>;
using PANIMVAR = SP<CAnimatedVariable<VarType>>;
template <typename VarType>
using PANIMVARREF = WP<CAnimatedVariable<VarType>>;
@ -47,7 +45,8 @@ CAnimationConfigTree animationTree;
class CMyAnimationManager : public CAnimationManager {
public:
void tick() {
for (const auto& PAV : m_vActiveAnimatedVariables) {
for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) {
const auto PAV = m_vActiveAnimatedVariables[i].lock();
if (!PAV || !PAV->ok() || !PAV->isBeingAnimated())
continue;
@ -65,21 +64,21 @@ class CMyAnimationManager : public CAnimationManager {
case eAVTypes::INT: {
auto avInt = dc<CAnimatedVariable<int>*>(PAV.get());
if (!avInt)
std::cout << "Dynamic cast upcast failed\n";
std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET;
const auto DELTA = avInt->goal() - avInt->begun();
const auto DELTA = avInt->goal() - avInt->value();
avInt->value() = avInt->begun() + (DELTA * POINTY);
} break;
case eAVTypes::TEST: {
auto avCustom = dc<CAnimatedVariable<SomeTestType>*>(PAV.get());
if (!avCustom)
std::cout << "Dynamic cast upcast failed\n";
std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET;
if (SPENT >= 1.f)
avCustom->value().done = true;
} break;
default: {
std::cout << "What are we even doing?\n";
std::cout << Colors::RED << "What are we even doing?" << Colors::RESET;
} break;
}
@ -92,10 +91,11 @@ class CMyAnimationManager : public CAnimationManager {
template <typename VarType>
void createAnimation(const VarType& v, PANIMVAR<VarType>& av, const std::string& animationConfigName) {
constexpr const eAVTypes EAVTYPE = std::is_same_v<VarType, int> ? eAVTypes::INT : eAVTypes::TEST;
av = makeUnique<CGenericAnimatedVariable<VarType, EmtpyContext>>();
const auto PAV = makeShared<CGenericAnimatedVariable<VarType, EmtpyContext>>();
av->create2(EAVTYPE, sc<CAnimationManager*>(this), av, v);
av->setConfig(animationTree.getConfig(animationConfigName));
PAV->create(EAVTYPE, sc<CAnimationManager*>(this), PAV, v);
PAV->setConfig(animationTree.getConfig(animationConfigName));
av = std::move(PAV);
}
virtual void scheduleTick() {
@ -121,7 +121,7 @@ class Subject {
PANIMVAR<SomeTestType> m_iC;
};
static int config() {
int config() {
pAnimationManager = makeUnique<CMyAnimationManager>();
int ret = 0;
@ -145,30 +145,30 @@ static int config() {
auto internalCfg = animationTree.getConfig("internal");
// internal is a root node and should point to itself
EXPECT_EQ(internalCfg->pParentAnimation.get(), internalCfg.get());
EXPECT_EQ(internalCfg->pValues.get(), internalCfg.get());
EXPECT(internalCfg->pParentAnimation.get(), internalCfg.get());
EXPECT(internalCfg->pValues.get(), internalCfg.get());
animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf");
EXPECT_EQ(barCfg->internalEnabled, -1);
EXPECT(barCfg->internalEnabled, -1);
{
const auto PVALUES = barCfg->pValues.lock();
EXPECT_EQ(PVALUES->internalEnabled, 1);
EXPECT_EQ(PVALUES->internalBezier, "default");
EXPECT_EQ(PVALUES->internalStyle, "asdf");
EXPECT_EQ(PVALUES->internalSpeed, 4.0);
EXPECT(PVALUES->internalEnabled, 1);
EXPECT(PVALUES->internalBezier, "default");
EXPECT(PVALUES->internalStyle, "asdf");
EXPECT(PVALUES->internalSpeed, 4.0);
}
EXPECT_EQ(barCfg->pParentAnimation.get(), animationTree.getConfig("default").get());
EXPECT(barCfg->pParentAnimation.get(), animationTree.getConfig("default").get());
// Overwrite our own values
animationTree.setConfigForNode("bar", 1, 4.2, "test", "qwer");
{
const auto PVALUES = barCfg->pValues.lock();
EXPECT_EQ(PVALUES->internalEnabled, 1);
EXPECT_EQ(PVALUES->internalBezier, "test");
EXPECT_EQ(PVALUES->internalStyle, "qwer");
EXPECT_EQ(PVALUES->internalSpeed, 4.2f);
EXPECT(PVALUES->internalEnabled, 1);
EXPECT(PVALUES->internalBezier, "test");
EXPECT(PVALUES->internalStyle, "qwer");
EXPECT(PVALUES->internalSpeed, 4.2f);
}
// Now overwrite the parent
@ -177,17 +177,17 @@ static int config() {
{
// Expecting no change
const auto PVALUES = barCfg->pValues.lock();
EXPECT_EQ(PVALUES->internalEnabled, 1);
EXPECT_EQ(PVALUES->internalBezier, "test");
EXPECT_EQ(PVALUES->internalStyle, "qwer");
EXPECT_EQ(PVALUES->internalSpeed, 4.2f);
EXPECT(PVALUES->internalEnabled, 1);
EXPECT(PVALUES->internalBezier, "test");
EXPECT(PVALUES->internalStyle, "qwer");
EXPECT(PVALUES->internalSpeed, 4.2f);
}
return ret;
}
TEST(Animation, animation) {
config();
int main(int argc, char** argv, char** envp) {
int ret = config();
animationTree.createNode("global");
animationTree.createNode("internal");
@ -197,8 +197,8 @@ TEST(Animation, animation) {
Subject s(0, 0);
EXPECT_EQ(s.m_iA->value(), 0);
EXPECT_EQ(s.m_iB->value(), 0);
EXPECT(s.m_iA->value(), 0);
EXPECT(s.m_iB->value(), 0);
// Test destruction of a CAnimatedVariable
{
@ -209,22 +209,22 @@ TEST(Animation, animation) {
// We deliberately do not tick here, to make sure the destructor removes active animated variables
}
EXPECT_EQ(pAnimationManager->shouldTickForNext(), false);
EXPECT_EQ(s.m_iC->value().done, false);
EXPECT(pAnimationManager->shouldTickForNext(), false);
EXPECT(s.m_iC->value().done, false);
*s.m_iA = 10;
*s.m_iB = 100;
*s.m_iC = SomeTestType(true);
EXPECT_EQ(s.m_iC->value().done, false);
EXPECT(s.m_iC->value().done, false);
while (pAnimationManager->shouldTickForNext()) {
pAnimationManager->tick();
}
EXPECT_EQ(s.m_iA->value(), 10);
EXPECT_EQ(s.m_iB->value(), 100);
EXPECT_EQ(s.m_iC->value().done, true);
EXPECT(s.m_iA->value(), 10);
EXPECT(s.m_iB->value(), 100);
EXPECT(s.m_iC->value().done, true);
s.m_iA->setValue(0);
s.m_iB->setValue(0);
@ -233,30 +233,30 @@ TEST(Animation, animation) {
pAnimationManager->tick();
}
EXPECT_EQ(s.m_iA->value(), 10);
EXPECT_EQ(s.m_iB->value(), 100);
EXPECT(s.m_iA->value(), 10);
EXPECT(s.m_iB->value(), 100);
// Test config stuff
EXPECT_EQ(s.m_iA->getBezierName(), "default");
EXPECT_EQ(s.m_iA->getStyle(), "asdf");
EXPECT_EQ(s.m_iA->enabled(), true);
EXPECT(s.m_iA->getBezierName(), "default");
EXPECT(s.m_iA->getStyle(), "asdf");
EXPECT(s.m_iA->enabled(), true);
animationTree.getConfig("global")->internalEnabled = 0;
EXPECT_EQ(s.m_iA->enabled(), false);
EXPECT(s.m_iA->enabled(), false);
*s.m_iA = 50;
pAnimationManager->tick(); // Expecting a warp
EXPECT_EQ(s.m_iA->value(), 50);
EXPECT(s.m_iA->value(), 50);
// Test missing pValues
animationTree.getConfig("global")->internalEnabled = 0;
animationTree.getConfig("default")->pValues.reset();
EXPECT_EQ(s.m_iA->enabled(), false);
EXPECT_EQ(s.m_iA->getBezierName(), "default");
EXPECT_EQ(s.m_iA->getStyle(), "");
EXPECT_EQ(s.m_iA->getPercent(), 1.f);
EXPECT(s.m_iA->enabled(), false);
EXPECT(s.m_iA->getBezierName(), "default");
EXPECT(s.m_iA->getStyle(), "");
EXPECT(s.m_iA->getPercent(), 1.f);
// Reset
animationTree.setConfigForNode("default", 1, 1, "default");
@ -273,18 +273,18 @@ TEST(Animation, animation) {
s.m_iA->setValueAndWarp(42);
EXPECT_EQ(beginCallbackRan, 0);
EXPECT_EQ(updateCallbackRan, 1);
EXPECT_EQ(endCallbackRan, 2); // first called when setting the callback, then when warping.
EXPECT(beginCallbackRan, 0);
EXPECT(updateCallbackRan, 1);
EXPECT(endCallbackRan, 2); // first called when setting the callback, then when warping.
*s.m_iA = 1337;
while (pAnimationManager->shouldTickForNext()) {
pAnimationManager->tick();
}
EXPECT_EQ(beginCallbackRan, 1);
EXPECT_EQ(updateCallbackRan > 2, true);
EXPECT_EQ(endCallbackRan, 3);
EXPECT(beginCallbackRan, 1);
EXPECT(updateCallbackRan > 2, true);
EXPECT(endCallbackRan, 3);
std::vector<PANIMVAR<int>> vars;
for (int i = 0; i < 10; i++) {
@ -296,7 +296,7 @@ TEST(Animation, animation) {
// test adding / removing vars during a tick
s.m_iA->resetAllCallbacks();
s.m_iA->setUpdateCallback([&vars](WP<CBaseAnimatedVariable> v) {
if (v.get() != vars.back().get())
if (v.lock() != vars.back())
vars.back()->warp();
});
s.m_iA->setCallbackOnEnd([&s, &vars](auto) {
@ -311,44 +311,44 @@ TEST(Animation, animation) {
pAnimationManager->tick();
}
EXPECT_EQ(s.m_iA->value(), 1000000);
EXPECT(s.m_iA->value(), 1000000);
// all vars should be set to 1337
EXPECT_EQ(std::find_if(vars.begin(), vars.end(), [](const auto& v) { return v->value() != 1337; }) == vars.end(), true);
EXPECT(std::find_if(vars.begin(), vars.end(), [](const auto& v) { return v->value() != 1337; }) == vars.end(), true);
// test one-time callbacks
s.m_iA->resetAllCallbacks();
s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, true);
EXPECT_EQ(endCallbackRan, 4);
EXPECT(endCallbackRan, 4);
s.m_iA->setValueAndWarp(10);
EXPECT_EQ(endCallbackRan, 4);
EXPECT_EQ(s.m_iA->value(), 10);
EXPECT(endCallbackRan, 4);
EXPECT(s.m_iA->value(), 10);
// test warp
*s.m_iA = 3;
s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, false);
s.m_iA->warp(false);
EXPECT_EQ(endCallbackRan, 4);
EXPECT(endCallbackRan, 4);
*s.m_iA = 4;
s.m_iA->warp(true);
EXPECT_EQ(endCallbackRan, 5);
EXPECT(endCallbackRan, 5);
// test getCurveValue
*s.m_iA = 0;
EXPECT_EQ(s.m_iA->getCurveValue(), 0.f);
EXPECT(s.m_iA->getCurveValue(), 0.f);
s.m_iA->warp();
EXPECT_EQ(s.m_iA->getCurveValue(), 1.f);
EXPECT_EQ(endCallbackRan, 6);
EXPECT(s.m_iA->getCurveValue(), 1.f);
EXPECT(endCallbackRan, 6);
// test end callback readding the var
*s.m_iA = 5;
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) {
endCallbackRan++;
const auto PAV = dc<CAnimatedVariable<int>*>(v.get());
const auto PAV = dc<CAnimatedVariable<int>*>(v.lock().get());
*PAV = 10;
PAV->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) { endCallbackRan++; });
@ -358,22 +358,22 @@ TEST(Animation, animation) {
pAnimationManager->tick();
}
EXPECT_EQ(endCallbackRan, 8);
EXPECT_EQ(s.m_iA->value(), 10);
EXPECT(endCallbackRan, 8);
EXPECT(s.m_iA->value(), 10);
// Test duplicate active anim vars are not allowed
{
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
PANIMVAR<int> a;
pAnimationManager->createAnimation(1, a, "default");
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
*a = 10;
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
*a = 20;
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
a->warp();
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
EXPECT_EQ(a->value(), 20);
EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
EXPECT(a->value(), 20);
}
// Test no crash when animation manager gets destroyed
@ -382,14 +382,16 @@ TEST(Animation, animation) {
pAnimationManager->createAnimation(1, a, "default");
*a = 10;
pAnimationManager.reset();
EXPECT_EQ(a->isAnimationManagerDead(), true);
EXPECT(a->isAnimationManagerDead(), true);
a->setValueAndWarp(11);
EXPECT_EQ(a->value(), 11);
EXPECT(a->value(), 11);
*a = 12;
a->warp();
EXPECT_EQ(a->value(), 12);
EXPECT(a->value(), 12);
*a = 13;
} // a gets destroyed
EXPECT_EQ(pAnimationManager.get(), nullptr);
EXPECT(pAnimationManager.get(), nullptr);
return ret;
}

View file

@ -1,79 +0,0 @@
#include <cmath>
#include <hyprutils/animation/BezierCurve.hpp>
#include <gtest/gtest.h>
using Hyprutils::Animation::CBezierCurve;
using Hyprutils::Math::Vector2D;
static void test_nonmonotonic4_clamps_out_of_range() {
// Non-monotonic curve in X
// This used to drive the step-halving search to OOB. It should now clamp
CBezierCurve curve;
std::array<Vector2D, 4> pts = {
Vector2D{0.5f, 1.0f}, // P0
Vector2D{1.0f, 1.0f}, // P1
Vector2D{0.0f, 0.0f}, // P2
Vector2D{0.5f, 0.0f} // P3
};
curve.setup4(pts);
// x > last baked x
EXPECT_EQ(std::isfinite(curve.getYForPoint(0.6f)), true);
// Far beyond range
EXPECT_EQ(std::isfinite(curve.getYForPoint(std::numeric_limits<float>::max())), true);
EXPECT_EQ(std::isfinite(curve.getYForPoint(-std::numeric_limits<float>::max())), true);
}
static void test_adjacent_baked_x_equal() {
// Curve with flat tail (X=1, Y=1)
CBezierCurve curve;
std::array<Vector2D, 4> pts = {
Vector2D{0.0f, 0.0f}, // P0
Vector2D{0.2f, 0.2f}, // P1
Vector2D{1.0f, 1.0f}, // P2
Vector2D{1.0f, 1.0f} // P3
};
curve.setup4(pts);
// Exactly at last baked X
const float y_at_end = curve.getYForPoint(1.0f);
// Slightly beyond last baked X
const float y_past_end = curve.getYForPoint(1.0001f);
EXPECT_EQ(y_at_end, 1.0f);
EXPECT_EQ(y_past_end, y_at_end);
}
static void test_all_baked_x_equal() {
// Extreme case: X is constant along the whole curve
CBezierCurve curve;
std::array<Vector2D, 4> pts = {
Vector2D{0.0f, 0.0f}, // P0
Vector2D{0.0f, 0.3f}, // P1
Vector2D{0.0f, 0.7f}, // P2
Vector2D{0.0f, 1.0f} // P3
};
curve.setup4(pts);
// Below any baked X
const float y_lo = curve.getYForPoint(-100.0f);
const float y_0 = curve.getYForPoint(0.0f);
// Above any baked X
const float y_hi = curve.getYForPoint(100.0f);
EXPECT_EQ(std::isfinite(y_lo), true);
EXPECT_EQ(std::isfinite(y_0), true);
EXPECT_EQ(std::isfinite(y_hi), true);
// For this curve Y should stay within [0,1]
EXPECT_EQ((y_lo >= 0.0f && y_lo <= 1.0f), true);
EXPECT_EQ((y_0 >= 0.0f && y_0 <= 1.0f), true);
EXPECT_EQ((y_hi >= 0.0f && y_hi <= 1.0f), true);
}
TEST(Animation, beziercurve) {
test_nonmonotonic4_clamps_out_of_range();
test_adjacent_baked_x_equal();
test_all_baked_x_equal();
}

View file

@ -1,105 +0,0 @@
#include <cli/ArgumentParser.hpp>
#include <gtest/gtest.h>
#include <print>
using namespace Hyprutils::CLI;
using namespace Hyprutils;
constexpr const char* DESC_TEST = R"#(┏ My description
--hello -h | Says hello
--hello2 | Says hello 2
--value -v [float] | Sets a valueeeeeee
--longlonglonglongintopt -l [int] | Long long
maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaan maaan man maaan man maaan
man maaan man
)#";
TEST(CLI, ArgumentParser) {
std::vector<const char*> argv = {"app", "--hello", "--value", "0.2"};
CArgumentParser parser(argv);
EXPECT_TRUE(parser.registerBoolOption("hello", "h", "Says hello"));
EXPECT_TRUE(parser.registerBoolOption("hello2", "", "Says hello 2"));
EXPECT_TRUE(parser.registerFloatOption("value", "v", "Sets a valueeeeeee"));
EXPECT_TRUE(parser.registerIntOption("longlonglonglongintopt", "l", "Long long maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaan maaan man maaan man maaan man maaan man"));
auto result = parser.parse();
EXPECT_TRUE(result.has_value());
std::println("{}", parser.getDescription("My description"));
if (!result.has_value())
std::println("Error: {}", result.error());
EXPECT_EQ(parser.getBool("hello").value_or(false), true);
EXPECT_EQ(parser.getBool("hello2").value_or(false), false);
EXPECT_EQ(parser.getFloat("value").value_or(0.F), 0.2F);
EXPECT_EQ(parser.getDescription("My description"), DESC_TEST);
CArgumentParser parser2(argv);
EXPECT_TRUE(parser2.registerBoolOption("hello2", "e", "Says hello 2"));
EXPECT_TRUE(parser2.registerFloatOption("value", "v", "Sets a valueeeeeee"));
EXPECT_TRUE(parser2.registerIntOption("longlonglonglongintopt", "l", "Long long maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaan maaan man maaan man maaan man maaan man"));
EXPECT_TRUE(parser2.registerFloatOption("value2", "", "Sets a valueeeeeee 2"));
EXPECT_TRUE(!parser2.registerFloatOption("", "a", "Sets a valueeeeeee 2"));
auto result2 = parser2.parse();
EXPECT_TRUE(!result2.has_value());
std::vector<const char*> argv3 = {"app", "--hello", "--value"};
CArgumentParser parser3(argv3);
EXPECT_TRUE(parser3.registerBoolOption("hello2", "e", "Says hello 2"));
EXPECT_TRUE(parser3.registerFloatOption("value", "v", "Sets a valueeeeeee"));
EXPECT_TRUE(parser3.registerIntOption("longlonglonglongintopt", "l", "Long long maaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaan maaan man maaan man maaan man maaan man"));
auto result3 = parser3.parse();
EXPECT_TRUE(!result3.has_value());
std::vector<const char*> argv4 = {"app", "--value", "hi", "-w", "2"};
CArgumentParser parser4(argv4);
EXPECT_TRUE(parser4.registerStringOption("value", "v", "Sets a valueeeeeee"));
EXPECT_TRUE(parser4.registerIntOption("value2", "w", "Sets a valueeeeeee 2"));
auto result4 = parser4.parse();
EXPECT_TRUE(result4.has_value());
EXPECT_EQ(parser4.getString("value").value_or(""), "hi");
EXPECT_EQ(parser4.getInt("value2").value_or(0), 2);
std::vector<const char*> argv5 = {
"app",
"e",
};
CArgumentParser parser5(argv5);
EXPECT_TRUE(parser5.registerStringOption("value", "v", "Sets a valueeeeeee"));
EXPECT_TRUE(parser5.registerStringOption("value2", "w", "Sets a valueeeeeee 2"));
auto result5 = parser5.parse();
EXPECT_TRUE(!result5.has_value());
CArgumentParser parser6(argv5);
EXPECT_TRUE(parser6.registerStringOption("aa", "v", "Sets a valueeeeeee"));
EXPECT_TRUE(!parser6.registerStringOption("aa", "w", "Sets a valueeeeeee 2"));
EXPECT_TRUE(parser6.registerStringOption("bb", "b", "Sets a valueeeeeee"));
EXPECT_TRUE(!parser6.registerStringOption("cc", "b", "Sets a valueeeeeee 2"));
}

View file

@ -1,104 +0,0 @@
#include <cli/Logger.hpp>
#include <hyprutils/os/File.hpp>
#include <gtest/gtest.h>
#include <filesystem>
using namespace Hyprutils::CLI;
using namespace Hyprutils;
TEST(CLI, Logger) {
CLogger logger;
logger.setEnableRolling(true);
logger.log(Hyprutils::CLI::LOG_DEBUG, "Hello!");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!");
logger.log(Hyprutils::CLI::LOG_TRACE, "Hello!");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!");
logger.setLogLevel(LOG_TRACE);
logger.log(Hyprutils::CLI::LOG_TRACE, "Hello, {}!", "Trace");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!");
CLoggerConnection connection(logger);
connection.setName("conn");
connection.log(Hyprutils::CLI::LOG_TRACE, "Hello from connection!");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!\nTRACE from conn ]: Hello from connection!");
connection.setLogLevel(Hyprutils::CLI::LOG_WARN);
connection.log(Hyprutils::CLI::LOG_DEBUG, "Hello from connection!");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!\nTRACE from conn ]: Hello from connection!");
logger.setEnableRolling(false);
connection.log(Hyprutils::CLI::LOG_ERR, "Err!");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!\nTRACE from conn ]: Hello from connection!");
logger.setEnableStdout(false);
logger.log(Hyprutils::CLI::LOG_ERR, "Error");
EXPECT_EQ(logger.rollingLog(), "DEBUG ]: Hello!\nTRACE ]: Hello, Trace!\nTRACE from conn ]: Hello from connection!");
auto res = logger.setOutputFile("./loggerFile.log");
EXPECT_TRUE(res);
logger.log(LOG_DEBUG, "Hi file!");
res = logger.setOutputFile(""); // clear
EXPECT_TRUE(res);
auto fileRead = File::readFileAsString("./loggerFile.log");
EXPECT_TRUE(fileRead);
EXPECT_EQ(fileRead.value_or(""), "DEBUG ]: Hi file!\n");
std::error_code ec;
std::filesystem::remove("./loggerFile.log", ec);
// TODO: maybe find a way to test the times and color?
logger.setEnableStdout(true);
logger.setTime(true);
logger.log(Hyprutils::CLI::LOG_WARN, "Timed warning!");
logger.setEnableColor(false);
logger.log(Hyprutils::CLI::LOG_CRIT, "rip");
logger.setEnableRolling(true);
// spam some logs to check rolling
for (size_t i = 0; i < 200; ++i) {
logger.log(LOG_ERR, "Oh noes!!!");
}
EXPECT_TRUE(logger.rollingLog().size() < 4096);
EXPECT_TRUE(logger.rollingLog().starts_with("ERR")); // test the breaking is done correctly
// test scoping
CLogger* pLogger = new CLogger();
CLoggerConnection* pConnection = new CLoggerConnection(*pLogger);
pLogger->setEnableStdout(false);
pConnection->log(LOG_DEBUG, "This shouldn't log anything.");
EXPECT_TRUE(pLogger->rollingLog().empty());
delete pLogger;
pConnection->log(LOG_DEBUG, "This shouldn't do anything, or crash.");
}

49
tests/filedescriptor.cpp Normal file
View file

@ -0,0 +1,49 @@
#include <hyprutils/os/FileDescriptor.hpp>
#include "shared.hpp"
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
using namespace Hyprutils::OS;
int main(int argc, char** argv, char** envp) {
std::string name = "/test_filedescriptors";
CFileDescriptor fd(shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600));
int ret = 0;
EXPECT(fd.isValid(), true);
EXPECT(fd.isReadable(), true);
int flags = fd.getFlags();
EXPECT(fd.getFlags(), FD_CLOEXEC);
flags &= ~FD_CLOEXEC;
fd.setFlags(flags);
EXPECT(fd.getFlags(), !FD_CLOEXEC);
CFileDescriptor fd2 = fd.duplicate();
EXPECT(fd.isValid(), true);
EXPECT(fd.isReadable(), true);
EXPECT(fd2.isValid(), true);
EXPECT(fd2.isReadable(), true);
CFileDescriptor fd3(fd2.take());
EXPECT(fd.isValid(), true);
EXPECT(fd.isReadable(), true);
EXPECT(fd2.isValid(), false);
EXPECT(fd2.isReadable(), false);
// .duplicate default flags is FD_CLOEXEC
EXPECT(fd3.getFlags(), FD_CLOEXEC);
fd.reset();
fd2.reset();
fd3.reset();
EXPECT(fd.isReadable(), false);
EXPECT(fd2.isReadable(), false);
EXPECT(fd3.isReadable(), false);
shm_unlink(name.c_str());
return ret;
}

View file

@ -1,80 +0,0 @@
#include <i18n/I18nEngine.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::I18n;
enum eTxtKeys : uint64_t {
TXT_KEY_HELLO,
TXT_KEY_I_HAVE_APPLES,
TXT_KEY_FALLBACK,
};
TEST(I18n, Engine) {
CI18nEngine engine;
engine.setFallbackLocale("en_US");
engine.registerEntry("en_US", TXT_KEY_HELLO, "Hello World!");
engine.registerEntry("en_US", TXT_KEY_I_HAVE_APPLES, [](const translationVarMap& m) {
if (std::stoi(m.at("count")) == 1)
return "I have {count} apple.";
else
return "I have {count} apples.";
});
engine.registerEntry("en_US", TXT_KEY_FALLBACK, "Fallback string!");
engine.registerEntry("pl_PL", TXT_KEY_HELLO, "Witaj świecie!");
engine.registerEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, [](const translationVarMap& m) {
const auto COUNT = std::stoi(m.at("count"));
if (COUNT == 1)
return "Mam {count} jabłko.";
else if (COUNT < 5)
return "Mam {count} jabłka.";
else
return "Mam {count} jabłek.";
});
engine.registerEntry("es_XX", TXT_KEY_FALLBACK, "I don't speak spanish");
engine.registerEntry("es_ES", TXT_KEY_FALLBACK, "I don't speak spanish here either");
engine.registerEntry("ts_TST", TXT_KEY_FALLBACK, "Hello {var1} world {var2}");
engine.registerEntry("am", TXT_KEY_FALLBACK, "Amongus!");
EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_HELLO, {}), "Hello World!");
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_HELLO, {}), "Witaj świecie!");
EXPECT_EQ(engine.localizeEntry("de_DE", TXT_KEY_HELLO, {}), "Hello World!");
EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_I_HAVE_APPLES, {{"count", "1"}}), "I have 1 apple.");
EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "I have 2 apples.");
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "1"}}), "Mam 1 jabłko.");
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "Mam 2 jabłka.");
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek.");
EXPECT_EQ(engine.localizeEntry("pl", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek.");
EXPECT_EQ(engine.localizeEntry("pl_XX", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek.");
EXPECT_EQ(engine.localizeEntry("en_XX", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "I have 2 apples.");
EXPECT_EQ(engine.localizeEntry("es_YY", TXT_KEY_FALLBACK, {}), "I don't speak spanish here either");
EXPECT_EQ(engine.localizeEntry("es_XX", TXT_KEY_FALLBACK, {}), "I don't speak spanish");
EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_FALLBACK, {}), "Fallback string!");
EXPECT_EQ(engine.localizeEntry("am_AM", TXT_KEY_FALLBACK, {}), "Amongus!");
// test weird translations
engine.registerEntry("ts", TXT_KEY_HELLO, "count}");
EXPECT_EQ(engine.localizeEntry("ts", TXT_KEY_HELLO, {{"count", "1"}}), "count}");
engine.registerEntry("ts", TXT_KEY_HELLO, "{count");
EXPECT_EQ(engine.localizeEntry("ts", TXT_KEY_HELLO, {{"count", "1"}}), "{count");
EXPECT_EQ(engine.localizeEntry("ts", 42069 /* invalid key */, {{"count", "1"}}), "");
EXPECT_EQ(engine.localizeEntry("ts_TST", TXT_KEY_FALLBACK, {{"var1", "hi"}, {"var2", "!"}}), "Hello hi world !");
// Order shouldn't matter
EXPECT_EQ(engine.localizeEntry("ts_TST", TXT_KEY_FALLBACK, {{"var2", "!"}, {"var1", "hi"}}), "Hello hi world !");
}

View file

@ -1,11 +0,0 @@
#include <gtest/gtest.h>
#include <i18n/I18nEngine.hpp>
using namespace Hyprutils::I18n;
TEST(I18n, Locale) {
EXPECT_EQ(extractLocale("pl_PL.UTF-8"), "pl_PL");
EXPECT_EQ(extractLocale("POSIX"), "en_US");
EXPECT_EQ(extractLocale("*"), "en_US");
EXPECT_EQ(extractLocale("LC_CTYPE=pl_PL.UTF-8"), "pl_PL");
}

136
tests/math.cpp Normal file
View file

@ -0,0 +1,136 @@
#include <hyprutils/math/Region.hpp>
#include <hyprutils/math/Mat3x3.hpp>
#include "shared.hpp"
using namespace Hyprutils::Math;
int main(int argc, char** argv, char** envp) {
CRegion rg = {0, 0, 100, 100};
rg.add(CBox{{}, {20, 200}});
int ret = 0;
EXPECT(rg.getExtents().height, 200);
EXPECT(rg.getExtents().width, 100);
rg.intersect(CBox{10, 10, 300, 300});
EXPECT(rg.getExtents().width, 90);
EXPECT(rg.getExtents().height, 190);
/*Box.cpp test cases*/
// Test default constructor and accessors
{
CBox box1;
EXPECT(box1.x, 0);
EXPECT(box1.y, 0);
EXPECT(box1.width, 0);
EXPECT(box1.height, 0);
// Test parameterized constructor and accessors
CBox box2(10, 20, 30, 40);
EXPECT(box2.x, 10);
EXPECT(box2.y, 20);
EXPECT(box2.width, 30);
EXPECT(box2.height, 40);
// Test setters and getters
box2.translate(Vector2D(5, -5));
EXPECT_VECTOR2D(box2.pos(), Vector2D(15, 15));
}
//Test Scaling and Transformation
{
CBox box(10, 10, 20, 30);
// Test scaling
box.scale(2.0);
EXPECT_VECTOR2D(box.size(), Vector2D(40, 60));
EXPECT_VECTOR2D(box.pos(), Vector2D(20, 20));
// Test scaling from center
box.scaleFromCenter(0.5);
EXPECT_VECTOR2D(box.size(), Vector2D(20, 30));
EXPECT_VECTOR2D(box.pos(), Vector2D(30, 35));
// Test transformation
box.transform(HYPRUTILS_TRANSFORM_90, 100, 200);
EXPECT_VECTOR2D(box.pos(), Vector2D(135, 30));
EXPECT_VECTOR2D(box.size(), Vector2D(30, 20));
// Test Intersection and Extents
}
{
CBox box1(0, 0, 100, 100);
CBox box2(50, 50, 100, 100);
CBox intersection = box1.intersection(box2);
EXPECT_VECTOR2D(intersection.pos(), Vector2D(50, 50));
EXPECT_VECTOR2D(intersection.size(), Vector2D(50, 50));
SBoxExtents extents = box1.extentsFrom(box2);
EXPECT_VECTOR2D(extents.topLeft, Vector2D(50, 50));
EXPECT_VECTOR2D(extents.bottomRight, Vector2D(-50, -50));
}
// Test Boundary Conditions and Special Cases
{
CBox box(0, 0, 50, 50);
EXPECT(box.empty(), false);
EXPECT(box.containsPoint(Vector2D(25, 25)), true);
EXPECT(box.containsPoint(Vector2D(60, 60)), false);
EXPECT(box.overlaps(CBox(25, 25, 50, 50)), true);
EXPECT(box.inside(CBox(0, 0, 100, 100)), false);
}
// Test matrices
{
Mat3x3 jeremy = Mat3x3::outputProjection({1920, 1080}, HYPRUTILS_TRANSFORM_FLIPPED_90);
Mat3x3 matrixBox = jeremy.projectBox(CBox{10, 10, 200, 200}, HYPRUTILS_TRANSFORM_NORMAL).translate({100, 100}).scale({1.25F, 1.5F}).transpose();
Mat3x3 expected = std::array<float, 9>{0, 0.46296296, 0, 0.3125, 0, 0, 19.84375, 36.055557, 1};
// 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);
}
// Test Region Scaling
{
CRegion rg(CBox{{20, 20}, {40, 40}});
auto extents = rg.getExtents();
EXPECT_VECTOR2D(extents.pos(), Vector2D(20, 20));
EXPECT_VECTOR2D(extents.size(), Vector2D(40, 40));
rg.scale(2);
extents = rg.getExtents();
EXPECT_VECTOR2D(extents.pos(), Vector2D(40, 40));
EXPECT_VECTOR2D(extents.size(), Vector2D(80, 80));
}
{
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,73 +0,0 @@
#include <hyprutils/math/Box.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::Math;
TEST(Math, box) {
// Test default constructor and accessors
{
CBox box1;
EXPECT_EQ(box1.x, 0);
EXPECT_EQ(box1.y, 0);
EXPECT_EQ(box1.width, 0);
EXPECT_EQ(box1.height, 0);
// Test parameterized constructor and accessors
CBox box2(10, 20, 30, 40);
EXPECT_EQ(box2.x, 10);
EXPECT_EQ(box2.y, 20);
EXPECT_EQ(box2.width, 30);
EXPECT_EQ(box2.height, 40);
// Test setters and getters
box2.translate(Vector2D(5, -5));
EXPECT_EQ(box2.pos(), Vector2D(15, 15));
}
//Test Scaling and Transformation
{
CBox box(10, 10, 20, 30);
// Test scaling
box.scale(2.0);
EXPECT_EQ(box.size(), Vector2D(40, 60));
EXPECT_EQ(box.pos(), Vector2D(20, 20));
// Test scaling from center
box.scaleFromCenter(0.5);
EXPECT_EQ(box.size(), Vector2D(20, 30));
EXPECT_EQ(box.pos(), Vector2D(30, 35));
// Test transformation
box.transform(HYPRUTILS_TRANSFORM_90, 100, 200);
EXPECT_EQ(box.pos(), Vector2D(135, 30));
EXPECT_EQ(box.size(), Vector2D(30, 20));
// Test Intersection and Extents
}
{
CBox box1(0, 0, 100, 100);
CBox box2(50, 50, 100, 100);
CBox intersection = box1.intersection(box2);
EXPECT_EQ(intersection.pos(), Vector2D(50, 50));
EXPECT_EQ(intersection.size(), Vector2D(50, 50));
SBoxExtents extents = box1.extentsFrom(box2);
EXPECT_EQ(extents.topLeft, Vector2D(50, 50));
EXPECT_EQ(extents.bottomRight, Vector2D(-50, -50));
}
// Test Boundary Conditions and Special Cases
{
CBox box(0, 0, 50, 50);
EXPECT_EQ(box.empty(), false);
EXPECT_EQ(box.containsPoint(Vector2D(25, 25)), true);
EXPECT_EQ(box.containsPoint(Vector2D(60, 60)), false);
EXPECT_EQ(box.overlaps(CBox(25, 25, 50, 50)), true);
EXPECT_EQ(box.inside(CBox(0, 0, 100, 100)), false);
}
}

View file

@ -1,23 +0,0 @@
#include <hyprutils/math/Mat3x3.hpp>
#include <hyprutils/math/Box.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::Math;
TEST(Math, mat3x3) {
Mat3x3 jeremy = Mat3x3::outputProjection({1920, 1080}, HYPRUTILS_TRANSFORM_FLIPPED_90);
Mat3x3 matrixBox = jeremy.projectBox(CBox{10, 10, 200, 200}, HYPRUTILS_TRANSFORM_NORMAL).translate({100, 100}).scale({1.25F, 1.5F}).transpose();
Mat3x3 expected = std::array<float, 9>{0, 0.46296296, 0, 0.3125, 0, 0, 19.84375, 36.055557, 1};
// we need to do this to avoid precision errors on 32-bit archs
EXPECT_EQ(std::abs(expected.getMatrix().at(0) - matrixBox.getMatrix().at(0)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(1) - matrixBox.getMatrix().at(1)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(2) - matrixBox.getMatrix().at(2)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(3) - matrixBox.getMatrix().at(3)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(4) - matrixBox.getMatrix().at(4)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(5) - matrixBox.getMatrix().at(5)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(6) - matrixBox.getMatrix().at(6)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(7) - matrixBox.getMatrix().at(7)) < 0.1, true);
EXPECT_EQ(std::abs(expected.getMatrix().at(8) - matrixBox.getMatrix().at(8)) < 0.1, true);
}

View file

@ -1,18 +0,0 @@
#include <hyprutils/math/Region.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::Math;
TEST(Math, region) {
CRegion rg(CBox{{20, 20}, {40, 40}});
auto extents = rg.getExtents();
EXPECT_EQ(extents.pos(), Vector2D(20, 20));
EXPECT_EQ(extents.size(), Vector2D(40, 40));
rg.scale(2);
extents = rg.getExtents();
EXPECT_EQ(extents.pos(), Vector2D(40, 40));
EXPECT_EQ(extents.size(), Vector2D(80, 80));
}

View file

@ -1,19 +0,0 @@
#include <hyprutils/math/Vector2D.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::Math;
TEST(Math, vector2d) {
Vector2D original(30, 40);
Vector2D monitorSize(100, 200);
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_NORMAL, monitorSize), Vector2D(30, 40));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_90, monitorSize), Vector2D(40, 200 - 30));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_180, monitorSize), Vector2D(100 - 30, 200 - 40));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_270, monitorSize), Vector2D(100 - 40, 30));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED, monitorSize), Vector2D(100 - 30, 40));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_90, monitorSize), Vector2D(40, 30));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_180, monitorSize), Vector2D(30, 200 - 40));
EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_270, monitorSize), Vector2D(100 - 40, 200 - 30));
}

186
tests/memory.cpp Normal file
View file

@ -0,0 +1,186 @@
#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;
#define SP CSharedPointer
#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;
}
{ // This tests destroying the data when storing the base class of a type
class ITest {
public:
size_t num = 0;
ITest() : num(1234) {};
};
class CA : public ITest {
public:
size_t num2 = 0;
CA() : ITest(), num2(4321) {};
};
class CB : public ITest {
public:
int num2 = 0;
CB() : ITest(), num2(-1) {};
};
ASP<ITest> genericAtomic = nullptr;
SP<ITest> genericNormal = nullptr;
{
auto derivedAtomic = makeAtomicShared<CA>();
auto derivedNormal = makeShared<CA>();
genericAtomic = derivedAtomic;
genericNormal = derivedNormal;
}
EXPECT(!!genericAtomic, true);
EXPECT(!!genericNormal, true);
}
return ret;
}
int main(int argc, char** argv, char** envp) {
SP<int> intPtr = makeShared<int>(10);
SP<int> intPtr2 = makeShared<int>(-1337);
UP<int> intUnique = makeUnique<int>(420);
int ret = 0;
EXPECT(*intPtr, 10);
EXPECT(intPtr.strongRef(), 1);
EXPECT(*intUnique, 420);
WP<int> weak = intPtr;
WP<int> weakUnique = intUnique;
EXPECT(*intPtr, 10);
EXPECT(intPtr.strongRef(), 1);
EXPECT(*weak, 10);
EXPECT(weak.expired(), false);
EXPECT(!!weak.lock(), true);
EXPECT(*weakUnique, 420);
EXPECT(weakUnique.expired(), false);
EXPECT(intUnique.impl_->wref(), 1);
SP<int> sharedFromUnique = weakUnique.lock();
EXPECT(sharedFromUnique, nullptr);
std::vector<SP<int>> sps;
sps.push_back(intPtr);
sps.emplace_back(intPtr);
sps.push_back(intPtr2);
sps.emplace_back(intPtr2);
std::erase_if(sps, [intPtr](const auto& e) { return e == intPtr; });
intPtr.reset();
intUnique.reset();
EXPECT(weak.impl_->ref(), 0);
EXPECT(weakUnique.impl_->ref(), 0);
EXPECT(weakUnique.impl_->wref(), 1);
EXPECT(intPtr2.strongRef(), 3);
EXPECT(weak.expired(), true);
EXPECT(weakUnique.expired(), true);
auto intPtr2AsUint = reinterpretPointerCast<unsigned int>(intPtr2);
EXPECT(intPtr2.strongRef(), 4);
EXPECT(intPtr2AsUint.strongRef(), 4);
EXPECT(*intPtr2AsUint > 0, true);
EXPECT(*intPtr2AsUint, (unsigned int)(int)-1337);
*intPtr2AsUint = 10;
EXPECT(*intPtr2AsUint, 10);
EXPECT(*intPtr2, 10);
EXPECT(testAtomicImpl(), 0);
return ret;
}

View file

@ -1,294 +0,0 @@
#include <hyprutils/memory/Atomic.hpp>
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <gtest/gtest.h>
#include <chrono>
#include <thread>
#include <vector>
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
#define UP CUniquePointer
#define ASP CAtomicSharedPointer
#define AWP CAtomicWeakPointer
#define NTHREADS 8
#define ITERATIONS 10000
static void testAtomicImpl() {
{
// 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_EQ(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_EQ(shared.strongRef(), 0);
EXPECT_EQ(weak.valid(), false);
auto shared2 = weak.lock();
EXPECT_EQ(shared, false);
EXPECT_EQ(shared2.get(), nullptr);
EXPECT_EQ(shared.strongRef(), 0);
EXPECT_EQ(weak.valid(), false);
EXPECT_EQ(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;
}
{ // This tests destroying the data when storing the base class of a type
class ITest {
public:
size_t num = 0;
ITest() : num(1234) {};
};
class CA : public ITest {
public:
size_t num2 = 0;
CA() : ITest(), num2(4321) {};
};
class CB : public ITest {
public:
int num2 = 0;
CB() : ITest(), num2(-1) {};
};
ASP<ITest> genericAtomic = nullptr;
SP<ITest> genericNormal = nullptr;
{
auto derivedAtomic = makeAtomicShared<CA>();
auto derivedNormal = makeShared<CA>();
genericAtomic = derivedAtomic;
genericNormal = derivedNormal;
}
EXPECT_EQ(!!genericAtomic, true);
EXPECT_EQ(!!genericNormal, true);
}
}
class InterfaceA {
public:
virtual ~InterfaceA() = default;
int m_ifaceAInt = 69;
int m_ifaceAShit = 1;
};
class InterfaceB {
public:
virtual ~InterfaceB() = default;
int m_ifaceBInt = 2;
int m_ifaceBShit = 3;
};
class CChild : public InterfaceA, public InterfaceB {
public:
virtual ~CChild() = default;
int m_childInt = 4;
};
class CChildA : public InterfaceA {
public:
int m_childAInt = 4;
};
static void testHierarchy() {
// Same test for atomic and non-atomic
{
SP<CChildA> childA = makeShared<CChildA>();
auto ifaceA = SP<InterfaceA>(childA);
EXPECT_TRUE(ifaceA);
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
auto ifaceB = dynamicPointerCast<InterfaceA>(SP<CChildA>{});
EXPECT_TRUE(!ifaceB);
}
{
SP<CChild> child = makeShared<CChild>();
SP<InterfaceA> ifaceA = dynamicPointerCast<InterfaceA>(child);
SP<InterfaceB> ifaceB = dynamicPointerCast<InterfaceB>(child);
EXPECT_TRUE(ifaceA);
EXPECT_TRUE(ifaceB);
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
EXPECT_EQ(ifaceB->m_ifaceBInt, 2);
WP<InterfaceA> ifaceAWeak = ifaceA;
child.reset();
EXPECT_TRUE(ifaceAWeak);
EXPECT_TRUE(ifaceA);
EXPECT_EQ(ifaceAWeak->m_ifaceAInt, 69);
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
ifaceA.reset();
EXPECT_TRUE(ifaceAWeak);
EXPECT_EQ(ifaceAWeak->m_ifaceAInt, 69);
EXPECT_TRUE(ifaceB);
EXPECT_EQ(ifaceB->m_ifaceBInt, 2);
ifaceB.reset();
EXPECT_TRUE(!ifaceAWeak);
}
//
{
ASP<CChildA> childA = makeAtomicShared<CChildA>();
auto ifaceA = ASP<InterfaceA>(childA);
EXPECT_TRUE(ifaceA);
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
auto ifaceB = dynamicPointerCast<InterfaceA>(ASP<CChildA>{});
EXPECT_TRUE(!ifaceB);
}
{
ASP<CChild> child = makeAtomicShared<CChild>();
ASP<InterfaceA> ifaceA = dynamicPointerCast<InterfaceA>(child);
ASP<InterfaceB> ifaceB = dynamicPointerCast<InterfaceB>(child);
EXPECT_TRUE(ifaceA);
EXPECT_TRUE(ifaceB);
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
EXPECT_EQ(ifaceB->m_ifaceBInt, 2);
AWP<InterfaceA> ifaceAWeak = ifaceA;
AWP<InterfaceB> ifaceBWeak = dynamicPointerCast<InterfaceB>(ifaceA);
child.reset();
EXPECT_TRUE(ifaceAWeak);
EXPECT_TRUE(ifaceBWeak);
EXPECT_TRUE(ifaceA);
EXPECT_EQ(ifaceAWeak->m_ifaceAInt, 69);
EXPECT_EQ(ifaceA->m_ifaceAInt, 69);
EXPECT_EQ(ifaceBWeak->m_ifaceBInt, 2);
ifaceA.reset();
EXPECT_TRUE(ifaceAWeak);
EXPECT_EQ(ifaceAWeak->m_ifaceAInt, 69);
EXPECT_TRUE(ifaceB);
EXPECT_EQ(ifaceB->m_ifaceBInt, 2);
EXPECT_EQ(ifaceBWeak->m_ifaceBInt, 2);
ifaceB.reset();
EXPECT_TRUE(!ifaceAWeak);
EXPECT_TRUE(!ifaceBWeak);
}
// test for leaks
for (size_t i = 0; i < 10000; ++i) {
auto child = makeAtomicShared<CChild>();
auto child2 = makeShared<CChild>();
}
}
TEST(Memory, memory) {
SP<int> intPtr = makeShared<int>(10);
SP<int> intPtr2 = makeShared<int>(-1337);
UP<int> intUnique = makeUnique<int>(420);
EXPECT_EQ(*intPtr, 10);
EXPECT_EQ(intPtr.strongRef(), 1);
EXPECT_EQ(*intUnique, 420);
WP<int> weak = intPtr;
WP<int> weakUnique = intUnique;
EXPECT_EQ(*intPtr, 10);
EXPECT_EQ(intPtr.strongRef(), 1);
EXPECT_EQ(*weak, 10);
EXPECT_EQ(weak.expired(), false);
EXPECT_EQ(!!weak.lock(), true);
EXPECT_EQ(*weakUnique, 420);
EXPECT_EQ(weakUnique.expired(), false);
EXPECT_EQ(intUnique.impl_->wref(), 1);
SP<int> sharedFromUnique = weakUnique.lock();
EXPECT_EQ(sharedFromUnique, nullptr);
std::vector<SP<int>> sps;
sps.push_back(intPtr);
sps.emplace_back(intPtr);
sps.push_back(intPtr2);
sps.emplace_back(intPtr2);
std::erase_if(sps, [intPtr](const auto& e) { return e == intPtr; });
intPtr.reset();
intUnique.reset();
EXPECT_EQ(weak.impl_->ref(), 0);
EXPECT_EQ(weakUnique.impl_->ref(), 0);
EXPECT_EQ(weakUnique.impl_->wref(), 1);
EXPECT_EQ(intPtr2.strongRef(), 3);
EXPECT_EQ(weak.expired(), true);
EXPECT_EQ(weakUnique.expired(), true);
auto intPtr2AsUint = reinterpretPointerCast<unsigned int>(intPtr2);
EXPECT_EQ(intPtr2.strongRef(), 4);
EXPECT_EQ(intPtr2AsUint.strongRef(), 4);
EXPECT_EQ(*intPtr2AsUint > 0, true);
EXPECT_EQ(*intPtr2AsUint, (unsigned int)(int)-1337);
*intPtr2AsUint = 10;
EXPECT_EQ(*intPtr2AsUint, 10);
EXPECT_EQ(*intPtr2, 10);
testAtomicImpl();
testHierarchy();
}

35
tests/os.cpp Normal file
View file

@ -0,0 +1,35 @@
#include <csignal>
#include <cerrno>
#include <cstdlib>
#include <unistd.h>
#include <hyprutils/os/Process.hpp>
#include "shared.hpp"
using namespace Hyprutils::OS;
int main(int argc, char** argv, char** envp) {
int ret = 0;
CProcess process("sh", {"-c", "echo \"Hello $WORLD!\""});
process.addEnv("WORLD", "World");
EXPECT(process.runAsync(), true);
EXPECT(process.runSync(), true);
EXPECT(process.stdOut(), std::string{"Hello World!\n"});
EXPECT(process.stdErr(), std::string{""});
EXPECT(process.exitCode(), 0);
CProcess process2("sh", {"-c", "while true; do sleep 1; done;"});
EXPECT(process2.runAsync(), true);
EXPECT(getpgid(process2.pid()) >= 0, true);
kill(process2.pid(), SIGKILL);
CProcess process3("sh", {"-c", "cat /geryueruggbuergheruger/reugiheruygyuerghuryeghyer/eruihgyuerguyerghyuerghuyergerguyer/NON_EXISTENT"});
EXPECT(process3.runSync(), true);
EXPECT(process3.exitCode(), 1);
return ret;
}

View file

@ -1,45 +0,0 @@
#include <hyprutils/os/FileDescriptor.hpp>
#include <gtest/gtest.h>
#include <sys/mman.h>
using namespace Hyprutils::OS;
TEST(OS, fd) {
std::string name = "/test_filedescriptors";
CFileDescriptor fd(shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600));
EXPECT_EQ(fd.isValid(), true);
EXPECT_EQ(fd.isReadable(), true);
int flags = fd.getFlags();
EXPECT_EQ(fd.getFlags(), FD_CLOEXEC);
flags &= ~FD_CLOEXEC;
fd.setFlags(flags);
EXPECT_EQ(fd.getFlags(), !FD_CLOEXEC);
CFileDescriptor fd2 = fd.duplicate();
EXPECT_EQ(fd.isValid(), true);
EXPECT_EQ(fd.isReadable(), true);
EXPECT_EQ(fd2.isValid(), true);
EXPECT_EQ(fd2.isReadable(), true);
CFileDescriptor fd3(fd2.take());
EXPECT_EQ(fd.isValid(), true);
EXPECT_EQ(fd.isReadable(), true);
EXPECT_EQ(fd2.isValid(), false);
EXPECT_EQ(fd2.isReadable(), false);
// .duplicate default flags is FD_CLOEXEC
EXPECT_EQ(fd3.getFlags(), FD_CLOEXEC);
fd.reset();
fd2.reset();
fd3.reset();
EXPECT_EQ(fd.isReadable(), false);
EXPECT_EQ(fd2.isReadable(), false);
EXPECT_EQ(fd3.isReadable(), false);
shm_unlink(name.c_str());
}

View file

@ -1,28 +0,0 @@
#include <hyprutils/os/Process.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::OS;
TEST(OS, process) {
CProcess process("sh", {"-c", "echo \"Hello $WORLD!\""});
process.addEnv("WORLD", "World");
EXPECT_EQ(process.runAsync(), true);
EXPECT_EQ(process.runSync(), true);
EXPECT_EQ(process.stdOut(), std::string{"Hello World!\n"});
EXPECT_EQ(process.stdErr(), std::string{""});
EXPECT_EQ(process.exitCode(), 0);
CProcess process2("sh", {"-c", "while true; do sleep 1; done;"});
EXPECT_EQ(process2.runAsync(), true);
EXPECT_EQ(getpgid(process2.pid()) >= 0, true);
kill(process2.pid(), SIGKILL);
CProcess process3("sh", {"-c", "cat /geryueruggbuergheruger/reugiheruygyuerghuryeghyer/eruihgyuerguyerghyuerghuyergerguyer/NON_EXISTENT"});
EXPECT_EQ(process3.runSync(), true);
EXPECT_EQ(process3.exitCode(), 1);
}

32
tests/shared.hpp Normal file
View file

@ -0,0 +1,32 @@
#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"; \
}
#define EXPECT_VECTOR2D(expr, val) \
do { \
const auto& RESULT = expr; \
const auto& EXPECTED = val; \
if (!(std::abs(RESULT.x - EXPECTED.x) < 1e-6 && std::abs(RESULT.y - EXPECTED.y) < 1e-6)) { \
std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected (" << EXPECTED.x << ", " << EXPECTED.y << ") but got (" << RESULT.x << ", " \
<< RESULT.y << ")\n"; \
ret = 1; \
} else { \
std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got (" << RESULT.x << ", " << RESULT.y << ")\n"; \
} \
} while (0)

View file

@ -1,11 +1,10 @@
#include <gtest/gtest.h>
#include <any>
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/signal/Listener.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;
@ -14,14 +13,14 @@ using namespace Hyprutils::Memory;
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
//
static void legacy() {
void legacy(int& ret) {
CSignal signal;
int data = 0;
auto listener = signal.registerListener([&]([[maybe_unused]] std::any d) { data = 1; });
signal.emit();
EXPECT_EQ(data, 1);
EXPECT(data, 1);
data = 0;
@ -29,19 +28,19 @@ static void legacy() {
signal.emit();
EXPECT_EQ(data, 0);
EXPECT(data, 0);
}
static void legacyListenerEmit() {
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_EQ(data, 1);
EXPECT(data, 1);
}
static void legacyListeners() {
void legacyListeners(int& ret) {
int data = 0;
CSignalT<> signal0;
@ -56,38 +55,38 @@ static void legacyListeners() {
signal0.emit();
signal1.emit(2);
EXPECT_EQ(data, 33);
EXPECT(data, 33);
}
#pragma GCC diagnostic pop
//
static void empty() {
void empty(int& ret) {
int data = 0;
CSignalT<> signal;
auto listener = signal.listen([&] { data = 1; });
signal.emit();
EXPECT_EQ(data, 1);
EXPECT(data, 1);
data = 0;
listener.reset();
signal.emit();
EXPECT_EQ(data, 0);
EXPECT(data, 0);
}
static void typed() {
void typed(int& ret) {
int data = 0;
CSignalT<int> signal;
auto listener = signal.listen([&](int newData) { data = newData; });
signal.emit(1);
EXPECT_EQ(data, 1);
EXPECT(data, 1);
}
static void ignoreParams() {
void ignoreParams(int& ret) {
int data = 0;
CSignalT<int> signal;
@ -96,10 +95,10 @@ static void ignoreParams() {
signal.listenStatic([&] { data += 1; });
signal.emit(2);
EXPECT_EQ(data, 2);
EXPECT(data, 2);
}
static void typedMany() {
void typedMany(int& ret) {
int data1 = 0;
int data2 = 0;
int data3 = 0;
@ -112,12 +111,12 @@ static void typedMany() {
});
signal.emit(1, 2, 3);
EXPECT_EQ(data1, 1);
EXPECT_EQ(data2, 2);
EXPECT_EQ(data3, 3);
EXPECT(data1, 1);
EXPECT(data2, 2);
EXPECT(data3, 3);
}
static void ref() {
void ref(int& ret) {
int count = 0;
int data = 0;
@ -131,11 +130,11 @@ static void ref() {
auto l4 = constSignal.listen([&](int v) { count += v; });
constSignal.emit(data);
EXPECT_EQ(data, 1);
EXPECT_EQ(count, 3);
EXPECT(data, 1);
EXPECT(count, 3);
}
static void refMany() {
void refMany(int& ret) {
int count = 0;
int data1 = 0;
int data2 = 10;
@ -145,11 +144,11 @@ static void refMany() {
auto l2 = signal.listen([&](int v1, int v2) { count += v1 + v2; });
signal.emit(data1, data2);
EXPECT_EQ(data1, 1);
EXPECT_EQ(count, 11);
EXPECT(data1, 1);
EXPECT(count, 11);
}
static void autoRefTypes() {
void autoRefTypes(int& ret) {
class CCopyCounter {
public:
CCopyCounter(int& createCount, int& destroyCount) : createCount(createCount), destroyCount(destroyCount) {
@ -175,11 +174,11 @@ static void autoRefTypes() {
auto listener = signal.listen([](const CCopyCounter& counter) {});
signal.emit(CCopyCounter(createCount, destroyCount));
EXPECT_EQ(createCount, 1);
EXPECT_EQ(destroyCount, 1);
EXPECT(createCount, 1);
EXPECT(destroyCount, 1);
}
static void forward() {
void forward(int& ret) {
int count = 0;
CSignalT<int> sig;
@ -194,10 +193,10 @@ static void forward() {
sig.emit(2);
EXPECT_EQ(count, 3);
EXPECT(count, 3);
}
static void listenerAdded() {
void listenerAdded(int& ret) {
int count = 0;
CSignalT<> signal;
@ -211,13 +210,13 @@ static void listenerAdded() {
});
signal.emit();
EXPECT_EQ(count, 1); // second should NOT be invoked as it was registed during emit
EXPECT(count, 1); // second should NOT be invoked as it was registed during emit
signal.emit();
EXPECT_EQ(count, 3); // second should be invoked
EXPECT(count, 3); // second should be invoked
}
static void lastListenerSwapped() {
void lastListenerSwapped(int& ret) {
int count = 0;
CSignalT<> signal;
@ -234,13 +233,13 @@ static void lastListenerSwapped() {
removedListener = signal.listen([&] { count += 1; });
signal.emit();
EXPECT_EQ(count, 0); // neither the removed nor added listeners should fire
EXPECT(count, 0); // neither the removed nor added listeners should fire
signal.emit();
EXPECT_EQ(count, 2); // only the new listener should fire
EXPECT(count, 2); // only the new listener should fire
}
static void signalDestroyed() {
void signalDestroyed(int& ret) {
int count = 0;
auto signal = std::make_unique<CSignalT<>>();
@ -255,11 +254,11 @@ static void signalDestroyed() {
auto postListener = signal->listen([&] { count += 1; });
signal->emit();
EXPECT_EQ(count, 2); // all listeners should fire regardless of signal deletion
EXPECT(count, 2); // all listeners should fire regardless of signal deletion
}
// purely an asan test
static void signalDestroyedBeforeListener() {
void signalDestroyedBeforeListener() {
CHyprSignalListener listener1;
CHyprSignalListener listener2;
@ -269,7 +268,7 @@ static void signalDestroyedBeforeListener() {
listener2 = signal.listen([] {});
}
static void signalDestroyedWithAddedListener() {
void signalDestroyedWithAddedListener(int& ret) {
int count = 0;
auto signal = std::make_unique<CSignalT<>>();
@ -281,10 +280,10 @@ static void signalDestroyedWithAddedListener() {
});
signal->emit();
EXPECT_EQ(count, 0);
EXPECT(count, 0);
}
static void signalDestroyedWithRemovedAndAddedListener() {
void signalDestroyedWithRemovedAndAddedListener(int& ret) {
int count = 0;
auto signal = std::make_unique<CSignalT<>>();
@ -300,20 +299,20 @@ static void signalDestroyedWithRemovedAndAddedListener() {
removed = signal->listen([&] { count += 1; });
signal->emit();
EXPECT_EQ(count, 0);
EXPECT(count, 0);
}
static void staticListener() {
void staticListener(int& ret) {
int data = 0;
CSignalT<int> signal;
signal.listenStatic([&](int newData) { data = newData; });
signal.emit(1);
EXPECT_EQ(data, 1);
EXPECT(data, 1);
}
static void staticListenerDestroy() {
void staticListenerDestroy(int& ret) {
int count = 0;
auto signal = makeShared<CSignalT<>>();
@ -329,11 +328,11 @@ static void staticListenerDestroy() {
signal->listenStatic([&] { count += 1; });
signal->emit();
EXPECT_EQ(count, 2);
EXPECT(count, 2);
}
// purely an asan test
static void listenerDestroysSelf() {
void listenerDestroysSelf() {
CSignalT<> signal;
CHyprSignalListener listener;
@ -344,26 +343,28 @@ static void listenerDestroysSelf() {
signal.emit();
}
TEST(Signal, signal) {
legacy();
legacyListenerEmit();
legacyListeners();
empty();
typed();
ignoreParams();
typedMany();
ref();
refMany();
autoRefTypes();
forward();
listenerAdded();
lastListenerSwapped();
signalDestroyed();
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();
signalDestroyedWithRemovedAndAddedListener();
staticListener();
staticListenerDestroy();
signalDestroyed();
signalDestroyedWithAddedListener(ret);
signalDestroyedWithRemovedAndAddedListener(ret);
staticListener(ret);
staticListenerDestroy(ret);
signalDestroyed(ret);
listenerDestroysSelf();
return ret;
}

51
tests/string.cpp Normal file
View file

@ -0,0 +1,51 @@
#include <hyprutils/string/String.hpp>
#include <hyprutils/string/VarList.hpp>
#include <hyprutils/string/ConstVarList.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("-"), 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);
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");
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!");
return ret;
}

View file

@ -1,15 +0,0 @@
#include <hyprutils/string/ConstVarList.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::String;
TEST(String, constvarlist) {
CConstVarList listConst("hello world!", 0, 's', true);
EXPECT_EQ(listConst[0], "hello");
EXPECT_EQ(listConst[1], "world!");
CConstVarList listConst2("0 set", 2, ' ');
EXPECT_EQ(listConst2[0], "0");
EXPECT_EQ(listConst2[1], "set");
}

View file

@ -1,47 +0,0 @@
#include <hyprutils/string/String.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::String;
TEST(String, string) {
EXPECT_EQ(trim(" a "), "a");
EXPECT_EQ(trim(" a a "), "a a");
EXPECT_EQ(trim("a"), "a");
EXPECT_EQ(trim(" "), "");
EXPECT_EQ(isNumber("99214123434"), true);
EXPECT_EQ(isNumber("-35252345234"), true);
EXPECT_EQ(isNumber("---3423--432"), false);
EXPECT_EQ(isNumber("s---3423--432"), false);
EXPECT_EQ(isNumber("---3423--432s"), false);
EXPECT_EQ(isNumber("1s"), false);
EXPECT_EQ(isNumber(""), false);
EXPECT_EQ(isNumber("-"), false);
EXPECT_EQ(isNumber("--0"), false);
EXPECT_EQ(isNumber("abc"), false);
EXPECT_EQ(isNumber("0.0", true), true);
EXPECT_EQ(isNumber("0.2", true), true);
EXPECT_EQ(isNumber("0.", true), false);
EXPECT_EQ(isNumber(".0", true), false);
EXPECT_EQ(isNumber("", true), false);
EXPECT_EQ(isNumber("vvss", true), false);
EXPECT_EQ(isNumber("0.9999s", true), false);
EXPECT_EQ(isNumber("s0.9999", true), false);
EXPECT_EQ(isNumber("-1.0", true), true);
EXPECT_EQ(isNumber("-1..0", true), false);
EXPECT_EQ(isNumber("-10.0000000001", true), true);
EXPECT_EQ(truthy("frgeujgeruibger"), false);
EXPECT_EQ(truthy("false"), false);
EXPECT_EQ(truthy("0"), false);
EXPECT_EQ(truthy("yees"), false);
EXPECT_EQ(truthy("naa"), false);
EXPECT_EQ(truthy("-1"), false);
EXPECT_EQ(truthy("true"), true);
EXPECT_EQ(truthy("true eeee ee"), true);
EXPECT_EQ(truthy("yesss"), true);
EXPECT_EQ(truthy("1"), true);
EXPECT_EQ(truthy("on"), true);
EXPECT_EQ(truthy("onn"), true);
}

View file

@ -1,11 +0,0 @@
#include <hyprutils/string/VarList.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::String;
TEST(String, varlist) {
CVarList list("hello world!", 0, 's', true);
EXPECT_EQ(list[0], "hello");
EXPECT_EQ(list[1], "world!");
}

View file

@ -1,67 +0,0 @@
#include <hyprutils/string/VarList2.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::String;
TEST(String, varlist2) {
CVarList2 varList2("0 set", 2, ' ');
EXPECT_EQ(varList2[0], "0");
EXPECT_EQ(varList2[1], "set");
varList2.append("Hello");
EXPECT_EQ(varList2[1], "set");
EXPECT_EQ(varList2[2], "Hello");
EXPECT_EQ(varList2[3], "");
EXPECT_EQ(varList2.contains("set"), true);
EXPECT_EQ(varList2.contains("sett"), false);
EXPECT_EQ(varList2.contains(""), false);
EXPECT_EQ(varList2.size(), 3);
CVarList2 varList2B("hello, world\\, ok?", 0, ',', true, true);
EXPECT_EQ(varList2B[0], "hello");
EXPECT_EQ(varList2B[1], "world, ok?");
CVarList2 varList2C("hello, , ok?", 0, ',', true, true);
EXPECT_EQ(varList2C[0], "hello");
EXPECT_EQ(varList2C[1], "ok?");
CVarList2 varList2D("\\\\, , ok?", 0, ',', true, true);
EXPECT_EQ(varList2D[0], "\\");
EXPECT_EQ(varList2D[1], "ok?");
CVarList2 varList2E("\\, , ok?", 0, ',', true, true);
EXPECT_EQ(varList2E[0], ",");
EXPECT_EQ(varList2E[1], "ok?");
CVarList2 varList2F("Hello, world\\\\, ok?", 0, ',', true, true);
EXPECT_EQ(varList2F[0], "Hello");
EXPECT_EQ(varList2F[1], "world\\");
EXPECT_EQ(varList2F[2], "ok?");
CVarList2 varList2G("Hello,\\, ok?", 0, ',', true, true);
EXPECT_EQ(varList2G[0], "Hello");
EXPECT_EQ(varList2G[1], ", ok?");
CVarList2 varList2H("Hello,\\\\, ok?", 0, ',', true, true);
EXPECT_EQ(varList2H[0], "Hello");
EXPECT_EQ(varList2H[1], "\\");
EXPECT_EQ(varList2H[2], "ok?");
CVarList2 varList2I("Hello,\\, ok?", 0, ',', true, false);
EXPECT_EQ(varList2I[0], "Hello");
EXPECT_EQ(varList2I[1], "\\");
EXPECT_EQ(varList2I[2], "ok?");
CVarList2 varList2J("", 0, ',', true, false);
EXPECT_EQ(varList2J[0], "");
CVarList2 varList2K(",\\, ok?", 0, ',', true);
EXPECT_EQ(varList2K[0], ", ok?");
CVarList2 varList2L("Hello, world", 0, ',', true);
EXPECT_EQ(varList2L.join(" "), "Hello world");
EXPECT_EQ(varList2L.join(" ", 0, 1000), "Hello world");
EXPECT_EQ(varList2L.join(" ", 0, 1), "Hello");
}