mirror of
https://github.com/hyprwm/hyprutils.git
synced 2025-12-20 09:20:08 +01:00
Compare commits
133 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ac060bfcf | ||
| 1c527b30fe | |||
| fe686486ac | |||
| 2f2413801b | |||
|
|
9f8e158dbd | ||
|
|
7e6346f84b | ||
|
|
a64517c236 | ||
| 0168583075 | |||
| 5e1a14bc29 | |||
| bc9803c4b8 | |||
|
|
44c2ba0354 | ||
| 96df6f6535 | |||
| a9fe9748ae | |||
|
|
b311dc90dc | ||
|
|
16a7fe760d | ||
| 31f29957df | |||
| 1454845751 | |||
|
|
2698ac1194 | ||
| 671792bcfe | |||
| 0c6411851c | |||
| e3cae692f6 | |||
|
|
cb3e797fde | ||
| 0a28f35c00 | |||
|
|
01afe9245b | ||
|
|
9a9745d7aa | ||
|
|
968f881222 | ||
| 926689ddb9 | |||
| 9637961a55 | |||
|
|
164a30b3d8 | ||
| 3df7bde01e | |||
| 9ab64319e9 | |||
|
|
feaaf44d59 | ||
| 94cce79434 | |||
| c1f541256e | |||
| 1f80045da1 | |||
| a20932e200 | |||
|
|
64446e1a4c | ||
|
|
05f0fb2774 | ||
|
|
427332a7ca | ||
| 61e295340d | |||
| b2ae320484 | |||
|
|
bc193efa4b | ||
| b364dcb739 | |||
| e631ea36dd | |||
|
|
69efb6291c | ||
|
|
df6b8820c4 | ||
| c65d41d4f4 | |||
|
|
8dd20c73e0 | ||
|
|
18fbac5a98 | ||
|
|
b074d4abc7 | ||
|
|
bcabcbada9 | ||
|
|
86905e2590 | ||
|
|
e21b18ff8f | ||
| a8229739cf | |||
|
|
e89269578a | ||
|
|
4737241eaf | ||
|
|
1eb6759ae7 | ||
| d844a08d83 | |||
| 2cd5e4fcd5 | |||
| 376d0209c8 | |||
| 93246269d4 | |||
| 6ee59e4eb8 | |||
|
|
925f26633f | ||
| 1b8090e5d8 | |||
| d46bd32da5 | |||
| 38f3a21165 | |||
| 15df5e39af | |||
| c9cd5f153c | |||
| 57ab2a867d | |||
|
|
e36db00dfb | ||
|
|
f1d0879444 | ||
| 674ea57373 | |||
| 7f00411949 | |||
| 05878d9470 | |||
| 5f51dcea4a | |||
| f2dc70e448 | |||
| 966d0c0b6a | |||
|
|
7248194a2c | ||
| fbd02eb032 | |||
| dd1f720cbc | |||
|
|
6b0154b183 | ||
| 61a5382f4b | |||
| 77d7ea3498 | |||
| e4e018a2ca | |||
|
|
3d9ae75886 | ||
|
|
9aad80acd4 | ||
|
|
dd790b90d7 | ||
| 6a8bc9d2a4 | |||
|
|
de58286a21 | ||
|
|
fb0c2d1de3 | ||
| 3411e5b42d | |||
| 006620eb29 | |||
|
|
ca1253c626 | ||
| 423c69d697 | |||
| 59414c4cee | |||
|
|
72dfbf5296 | ||
| 5dd4f4a9f7 | |||
| 3c895da64b | |||
|
|
fb9a816cb9 | ||
|
|
6a26d08bac | ||
| c42ce87eb3 | |||
|
|
8af7e4b9de | ||
| 8f15d45b12 | |||
|
|
9be03a8562 | ||
| 5e45b1a1b9 | |||
| c3331116eb | |||
|
|
e6cf45cd18 | ||
| 104117aed6 | |||
| b26f33cc1c | |||
|
|
2e21319c8e | ||
| e911361a68 | |||
| e74177e025 | |||
| 315fba5d21 | |||
| 91c1634727 | |||
| 4c5f18d06b | |||
| 8d21d1dfa9 | |||
|
|
d504d45114 | ||
| 60d3dece30 | |||
| 3ce0cde870 | |||
| fd4be8b9ca | |||
| 3f5293432b | |||
| db956287d3 | |||
| 7373e87215 | |||
|
|
a9f85a4bca | ||
| d97af4f6bd | |||
| 8976e3f6a5 | |||
| aadf9a27dd | |||
| 0252fd13e7 | |||
| 5dcbbc1e3d | |||
| a950624669 | |||
| 962582a090 | |||
| 04697c69ab | |||
| 928d9b4ee9 |
88 changed files with 6110 additions and 587 deletions
101
.clang-tidy
Normal file
101
.clang-tidy
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
WarningsAsErrors: '*'
|
||||
HeaderFilterRegex: '.*\.hpp'
|
||||
FormatStyle: file
|
||||
Checks: >
|
||||
-*,
|
||||
bugprone-*,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-forward-declararion-namespace,
|
||||
-bugprone-forward-declararion-namespace,
|
||||
-bugprone-macro-parentheses,
|
||||
-bugprone-narrowing-conversions,
|
||||
-bugprone-branch-clone,
|
||||
-bugprone-assignment-in-if-condition,
|
||||
concurrency-*,
|
||||
-concurrency-mt-unsafe,
|
||||
cppcoreguidelines-*,
|
||||
-cppcoreguidelines-owning-memory,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||
-cppcoreguidelines-avoid-const-or-ref-data-members,
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||
-cppcoreguidelines-avoid-goto,
|
||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||
-cppcoreguidelines-avoid-do-while,
|
||||
-cppcoreguidelines-avoid-non-const-global-variables,
|
||||
-cppcoreguidelines-special-member-functions,
|
||||
-cppcoreguidelines-explicit-virtual-functions,
|
||||
-cppcoreguidelines-avoid-c-arrays,
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||
-cppcoreguidelines-narrowing-conversions,
|
||||
-cppcoreguidelines-pro-type-union-access,
|
||||
-cppcoreguidelines-pro-type-member-init,
|
||||
-cppcoreguidelines-macro-usage,
|
||||
-cppcoreguidelines-macro-to-enum,
|
||||
-cppcoreguidelines-init-variables,
|
||||
-cppcoreguidelines-pro-type-cstyle-cast,
|
||||
-cppcoreguidelines-pro-type-vararg,
|
||||
-cppcoreguidelines-pro-type-reinterpret-cast,
|
||||
google-global-names-in-headers,
|
||||
-google-readability-casting,
|
||||
google-runtime-operator,
|
||||
misc-*,
|
||||
-misc-unused-parameters,
|
||||
-misc-no-recursion,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-misc-include-cleaner,
|
||||
-misc-use-anonymous-namespace,
|
||||
-misc-const-correctness,
|
||||
modernize-*,
|
||||
-modernize-return-braced-init-list,
|
||||
-modernize-use-trailing-return-type,
|
||||
-modernize-use-using,
|
||||
-modernize-use-override,
|
||||
-modernize-avoid-c-arrays,
|
||||
-modernize-macro-to-enum,
|
||||
-modernize-loop-convert,
|
||||
-modernize-use-nodiscard,
|
||||
-modernize-pass-by-value,
|
||||
-modernize-use-auto,
|
||||
performance-*,
|
||||
-performance-avoid-endl,
|
||||
-performance-unnecessary-value-param,
|
||||
portability-std-allocator-const,
|
||||
readability-*,
|
||||
-readability-function-cognitive-complexity,
|
||||
-readability-function-size,
|
||||
-readability-identifier-length,
|
||||
-readability-magic-numbers,
|
||||
-readability-uppercase-literal-suffix,
|
||||
-readability-braces-around-statements,
|
||||
-readability-redundant-access-specifiers,
|
||||
-readability-else-after-return,
|
||||
-readability-container-data-pointer,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-avoid-nested-conditional-operator,
|
||||
-readability-redundant-member-init,
|
||||
-readability-redundant-string-init,
|
||||
-readability-avoid-const-params-in-decls,
|
||||
-readability-named-parameter,
|
||||
-readability-convert-member-functions-to-static,
|
||||
-readability-qualified-auto,
|
||||
-readability-make-member-function-const,
|
||||
-readability-isolate-declaration,
|
||||
-readability-inconsistent-declaration-parameter-name,
|
||||
-clang-diagnostic-error,
|
||||
|
||||
CheckOptions:
|
||||
performance-for-range-copy.WarnOnAllAutoCopies: true
|
||||
performance-inefficient-string-concatenation.StrictMode: true
|
||||
readability-braces-around-statements.ShortStatementLines: 0
|
||||
readability-identifier-naming.ClassCase: CamelCase
|
||||
readability-identifier-naming.ClassIgnoredRegexp: I.*
|
||||
readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!?
|
||||
readability-identifier-naming.EnumCase: CamelCase
|
||||
readability-identifier-naming.EnumPrefix: e
|
||||
readability-identifier-naming.EnumConstantCase: UPPER_CASE
|
||||
readability-identifier-naming.FunctionCase: camelBack
|
||||
readability-identifier-naming.NamespaceCase: CamelCase
|
||||
readability-identifier-naming.NamespacePrefix: N
|
||||
readability-identifier-naming.StructPrefix: S
|
||||
readability-identifier-naming.StructCase: CamelCase
|
||||
11
.editorconfig
Normal file
11
.editorconfig
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{cmake,nix,yml,yaml},CMakeLists.txt]
|
||||
indent_size = 2
|
||||
4
.github/workflows/arch.yml
vendored
4
.github/workflows/arch.yml
vendored
|
|
@ -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 base-devel cmake clang libc++ pixman
|
||||
pacman --noconfirm --noprogressbar -Sy gcc gtest 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 base-devel cmake clang libc++ pixman
|
||||
pacman --noconfirm --noprogressbar -Sy gcc gtest base-devel cmake clang libc++ pixman
|
||||
|
||||
- name: Build hyprutils with clang
|
||||
run: |
|
||||
|
|
|
|||
31
.github/workflows/nix.yml
vendored
31
.github/workflows/nix.yml
vendored
|
|
@ -13,8 +13,35 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: cachix/install-nix-action@v26
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- name: Install Nix
|
||||
uses: nixbuild/nix-quick-install-action@v31
|
||||
with:
|
||||
nix_conf: |
|
||||
keep-env-derivations = true
|
||||
keep-outputs = true
|
||||
|
||||
- name: Restore and save Nix store
|
||||
uses: nix-community/cache-nix-action@v6
|
||||
with:
|
||||
# restore and save a cache using this key
|
||||
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
||||
# if there's no cache hit, restore a cache by this prefix
|
||||
restore-prefixes-first-match: nix-${{ runner.os }}-
|
||||
# collect garbage until the Nix store size (in bytes) is at most this number
|
||||
# before trying to save a new cache
|
||||
# 1G = 1073741824
|
||||
gc-max-store-size-linux: 1G
|
||||
# do purge caches
|
||||
purge: true
|
||||
# purge all versions of the cache
|
||||
purge-prefixes: nix-${{ runner.os }}-
|
||||
# created more than this number of seconds ago
|
||||
purge-created: 0
|
||||
# or, last accessed more than this number of seconds ago
|
||||
# relative to the start of the `Post Restore and save Nix store` phase
|
||||
purge-last-accessed: 0
|
||||
# except any version with the key that is the same as the `primary-key`
|
||||
purge-primary-key: never
|
||||
|
||||
# not needed (yet)
|
||||
# - uses: cachix/cachix-action@v12
|
||||
|
|
|
|||
12
.gitignore
vendored
12
.gitignore
vendored
|
|
@ -34,3 +34,15 @@
|
|||
build/
|
||||
.vscode/
|
||||
.cache/
|
||||
.direnv/
|
||||
|
||||
.cmake/
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
CTestTestfile.cmake
|
||||
DartConfiguration.tcl
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
compile_commands.json
|
||||
hyprutils.pc
|
||||
.envrc
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
cmake_minimum_required(VERSION 3.19)
|
||||
|
||||
set(HYPRUTILS_VERSION "0.2.0")
|
||||
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
|
||||
string(STRIP ${VER_RAW} HYPRUTILS_VERSION)
|
||||
add_compile_definitions(HYPRUTILS_VERSION="${HYPRUTILS_VERSION}")
|
||||
|
||||
project(hyprutils
|
||||
VERSION ${HYPRUTILS_VERSION}
|
||||
DESCRIPTION "Small C++ library for utilities used across the Hypr* ecosystem"
|
||||
)
|
||||
project(
|
||||
hyprutils
|
||||
VERSION ${HYPRUTILS_VERSION}
|
||||
DESCRIPTION "Small C++ library for utilities used across the Hypr* ecosystem")
|
||||
|
||||
include(CTest)
|
||||
include(GNUInstallDirs)
|
||||
|
|
@ -18,13 +19,25 @@ set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR})
|
|||
configure_file(hyprutils.pc.in hyprutils.pc @ONLY)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
add_compile_options(
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wpedantic
|
||||
-Wno-unused-parameter
|
||||
-Wno-unused-value
|
||||
-Wno-missing-field-initializers
|
||||
-Wno-narrowing
|
||||
-Wno-pointer-arith)
|
||||
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)
|
||||
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")
|
||||
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")
|
||||
|
|
@ -34,40 +47,39 @@ find_package(PkgConfig REQUIRED)
|
|||
pkg_check_modules(deps REQUIRED IMPORTED_TARGET pixman-1)
|
||||
|
||||
add_library(hyprutils SHARED ${SRCFILES})
|
||||
target_include_directories( hyprutils
|
||||
PUBLIC "./include"
|
||||
PRIVATE "./src"
|
||||
)
|
||||
set_target_properties(hyprutils PROPERTIES
|
||||
VERSION ${hyprutils_VERSION}
|
||||
SOVERSION 1
|
||||
)
|
||||
target_include_directories(
|
||||
hyprutils
|
||||
PUBLIC "./include"
|
||||
PRIVATE "./src")
|
||||
set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION}
|
||||
SOVERSION 10)
|
||||
target_link_libraries(hyprutils PkgConfig::deps)
|
||||
|
||||
# tests
|
||||
add_custom_target(tests)
|
||||
if(BUILD_TESTING)
|
||||
# GTest
|
||||
find_package(GTest CONFIG REQUIRED)
|
||||
include(GoogleTest)
|
||||
file(GLOB_RECURSE TESTFILES CONFIGURE_DEPENDS "tests/*.cpp")
|
||||
add_executable(hyprutils_tests ${TESTFILES})
|
||||
|
||||
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_compile_options(hyprutils_tests PRIVATE --coverage)
|
||||
target_link_options(hyprutils_tests PRIVATE --coverage)
|
||||
|
||||
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_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 coverage to hyprutils for test builds
|
||||
target_compile_options(hyprutils PRIVATE --coverage)
|
||||
target_link_options(hyprutils PRIVATE --coverage)
|
||||
endif()
|
||||
|
||||
# Installation
|
||||
install(TARGETS hyprutils)
|
||||
install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(FILES ${CMAKE_BINARY_DIR}/hyprutils.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
install(FILES ${CMAKE_BINARY_DIR}/hyprutils.pc
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@ Hyprutils depends on the ABI stability of the stdlib implementation of your comp
|
|||
git clone https://github.com/hyprwm/hyprutils.git
|
||||
cd hyprutils/
|
||||
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
|
||||
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF`
|
||||
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF`
|
||||
sudo cmake --install build
|
||||
```
|
||||
|
|
|
|||
1
VERSION
Normal file
1
VERSION
Normal file
|
|
@ -0,0 +1 @@
|
|||
0.11.0
|
||||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -2,11 +2,11 @@
|
|||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1717602782,
|
||||
"narHash": "sha256-pL9jeus5QpX5R+9rsp3hhZ+uplVHscNJh8n8VpqscM0=",
|
||||
"lastModified": 1748929857,
|
||||
"narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e8057b67ebf307f01bdcc8fba94d94f75039d1f6",
|
||||
"rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
18
flake.nix
18
flake.nix
|
|
@ -18,26 +18,12 @@
|
|||
localSystem.system = system;
|
||||
overlays = with self.overlays; [hyprutils];
|
||||
});
|
||||
mkDate = longDate: (lib.concatStringsSep "-" [
|
||||
(builtins.substring 0 4 longDate)
|
||||
(builtins.substring 4 2 longDate)
|
||||
(builtins.substring 6 2 longDate)
|
||||
]);
|
||||
in {
|
||||
overlays = {
|
||||
default = self.overlays.hyprutils;
|
||||
hyprutils = final: prev: {
|
||||
hyprutils = final.callPackage ./nix/default.nix {
|
||||
stdenv = final.gcc13Stdenv;
|
||||
version = "0.pre" + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
|
||||
};
|
||||
hyprutils-with-tests = final.hyprutils.override {doCheck = true;};
|
||||
};
|
||||
};
|
||||
overlays = import ./nix/overlays.nix {inherit self lib;};
|
||||
|
||||
packages = eachSystem (system: {
|
||||
default = self.packages.${system}.hyprutils;
|
||||
inherit (pkgsFor.${system}) hyprutils hyprutils-with-tests;
|
||||
inherit (pkgsFor.${system}) hyprutils hyprutils-debug hyprutils-with-tests;
|
||||
});
|
||||
|
||||
formatter = eachSystem (system: pkgsFor.${system}.alejandra);
|
||||
|
|
|
|||
238
include/hyprutils/animation/AnimatedVariable.hpp
Normal file
238
include/hyprutils/animation/AnimatedVariable.hpp
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
#pragma once
|
||||
|
||||
#include "AnimationConfig.hpp"
|
||||
#include "../memory/WeakPtr.hpp"
|
||||
#include "../memory/SharedPtr.hpp"
|
||||
#include "../signal/Signal.hpp"
|
||||
#include "AnimationManager.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace Animation {
|
||||
|
||||
/* A base class for animated variables. */
|
||||
class CBaseAnimatedVariable {
|
||||
public:
|
||||
using CallbackFun = std::function<void(Memory::CWeakPointer<CBaseAnimatedVariable> thisptr)>;
|
||||
|
||||
CBaseAnimatedVariable() {
|
||||
; // m_bDummy = true;
|
||||
};
|
||||
|
||||
void create(CAnimationManager*, int, Memory::CSharedPointer<CBaseAnimatedVariable>);
|
||||
void create2(CAnimationManager*, int, Memory::CWeakPointer<CBaseAnimatedVariable>);
|
||||
void connectToActive();
|
||||
void disconnectFromActive();
|
||||
|
||||
/* Needs to call disconnectFromActive to remove `m_pSelf` from the active animation list */
|
||||
virtual ~CBaseAnimatedVariable() {
|
||||
disconnectFromActive();
|
||||
};
|
||||
|
||||
virtual void warp(bool endCallback = true, bool forceDisconnect = true) = 0;
|
||||
|
||||
CBaseAnimatedVariable(const CBaseAnimatedVariable&) = delete;
|
||||
CBaseAnimatedVariable(CBaseAnimatedVariable&&) = delete;
|
||||
CBaseAnimatedVariable& operator=(const CBaseAnimatedVariable&) = delete;
|
||||
CBaseAnimatedVariable& operator=(CBaseAnimatedVariable&&) = delete;
|
||||
|
||||
//
|
||||
void setConfig(Memory::CSharedPointer<SAnimationPropertyConfig> pConfig) {
|
||||
m_pConfig = pConfig;
|
||||
}
|
||||
|
||||
Memory::CWeakPointer<SAnimationPropertyConfig> getConfig() const {
|
||||
return m_pConfig;
|
||||
}
|
||||
|
||||
bool enabled() const;
|
||||
const std::string& getBezierName() const;
|
||||
const std::string& getStyle() const;
|
||||
|
||||
/* returns the spent (completion) % */
|
||||
float getPercent() const;
|
||||
|
||||
/* returns the current curve value. */
|
||||
float getCurveValue() const;
|
||||
|
||||
/* checks if an animation is in progress */
|
||||
bool isBeingAnimated() const {
|
||||
return m_bIsBeingAnimated;
|
||||
}
|
||||
|
||||
/* checks m_bDummy and m_pAnimationManager */
|
||||
bool ok() const;
|
||||
|
||||
/* calls the update callback */
|
||||
void onUpdate();
|
||||
|
||||
/* sets a function to be ran when an animation ended.
|
||||
if "remove" is set to true, it will remove the callback when ran. */
|
||||
void setCallbackOnEnd(CallbackFun func, bool remove = true);
|
||||
|
||||
/* sets a function to be ran when an animation is started.
|
||||
if "remove" is set to true, it will remove the callback when ran. */
|
||||
void setCallbackOnBegin(CallbackFun func, bool remove = true);
|
||||
|
||||
/* sets the update callback, called every time the value is animated and a step is done
|
||||
Warning: calling unregisterVar/registerVar in this handler will cause UB */
|
||||
void setUpdateCallback(CallbackFun func);
|
||||
|
||||
/* resets all callbacks. Does not call any. */
|
||||
void resetAllCallbacks();
|
||||
|
||||
void onAnimationEnd();
|
||||
void onAnimationBegin();
|
||||
|
||||
/* returns whether the parent CAnimationManager is dead */
|
||||
bool isAnimationManagerDead() const;
|
||||
|
||||
int m_Type = -1;
|
||||
|
||||
protected:
|
||||
friend class CAnimationManager;
|
||||
|
||||
CAnimationManager* m_pAnimationManager = nullptr;
|
||||
|
||||
bool m_bIsConnectedToActive = false;
|
||||
bool m_bIsBeingAnimated = false;
|
||||
|
||||
Memory::CWeakPointer<CBaseAnimatedVariable> m_pSelf;
|
||||
|
||||
Memory::CWeakPointer<CAnimationManager::SAnimationManagerSignals> m_pSignals;
|
||||
|
||||
private:
|
||||
Memory::CWeakPointer<SAnimationPropertyConfig> m_pConfig;
|
||||
|
||||
std::chrono::steady_clock::time_point animationBegin;
|
||||
|
||||
bool m_bDummy = true;
|
||||
|
||||
bool m_bRemoveEndAfterRan = true;
|
||||
bool m_bRemoveBeginAfterRan = true;
|
||||
|
||||
CallbackFun m_fEndCallback;
|
||||
CallbackFun m_fBeginCallback;
|
||||
CallbackFun m_fUpdateCallback;
|
||||
};
|
||||
|
||||
/* This concept represents the minimum requirement for a type to be used with CGenericAnimatedVariable */
|
||||
template <class ValueImpl>
|
||||
concept AnimatedType = requires(ValueImpl val) {
|
||||
requires std::is_copy_constructible_v<ValueImpl>;
|
||||
{ val == val } -> std::same_as<bool>; // requires operator==
|
||||
{ val = val }; // requires operator=
|
||||
};
|
||||
|
||||
/*
|
||||
A generic class for variables.
|
||||
VarType is the type of the variable to be animated.
|
||||
AnimationContext is there to attach additional data to the animation.
|
||||
In Hyprland that struct would contain a reference to window, workspace or layer for example.
|
||||
*/
|
||||
template <AnimatedType VarType, class AnimationContext>
|
||||
class CGenericAnimatedVariable : public CBaseAnimatedVariable {
|
||||
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;
|
||||
m_Value = initialValue;
|
||||
m_Goal = initialValue;
|
||||
|
||||
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;
|
||||
CGenericAnimatedVariable& operator=(CGenericAnimatedVariable&&) = delete;
|
||||
|
||||
virtual void warp(bool endCallback = true, bool forceDisconnect = true) {
|
||||
if (!m_bIsBeingAnimated)
|
||||
return;
|
||||
|
||||
m_Value = m_Goal;
|
||||
|
||||
onUpdate();
|
||||
|
||||
m_bIsBeingAnimated = false;
|
||||
|
||||
if (forceDisconnect)
|
||||
disconnectFromActive();
|
||||
|
||||
if (endCallback)
|
||||
onAnimationEnd();
|
||||
}
|
||||
|
||||
const VarType& value() const {
|
||||
return m_Value;
|
||||
}
|
||||
|
||||
/* used to update the value each tick via the AnimationManager */
|
||||
VarType& value() {
|
||||
return m_Value;
|
||||
}
|
||||
|
||||
const VarType& goal() const {
|
||||
return m_Goal;
|
||||
}
|
||||
|
||||
const VarType& begun() const {
|
||||
return m_Begun;
|
||||
}
|
||||
|
||||
CGenericAnimatedVariable& operator=(const VarType& v) {
|
||||
if (v == m_Goal)
|
||||
return *this;
|
||||
|
||||
m_Goal = v;
|
||||
m_Begun = m_Value;
|
||||
|
||||
onAnimationBegin();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* Sets the actual stored value, without affecting the goal, but resets the timer*/
|
||||
void setValue(const VarType& v) {
|
||||
if (v == m_Value)
|
||||
return;
|
||||
|
||||
m_Value = v;
|
||||
m_Begun = m_Value;
|
||||
|
||||
onAnimationBegin();
|
||||
}
|
||||
|
||||
/* Sets the actual value and goal*/
|
||||
void setValueAndWarp(const VarType& v) {
|
||||
m_Goal = v;
|
||||
m_bIsBeingAnimated = true;
|
||||
|
||||
warp();
|
||||
}
|
||||
|
||||
AnimationContext m_Context;
|
||||
|
||||
private:
|
||||
VarType m_Value{};
|
||||
VarType m_Goal{};
|
||||
VarType m_Begun{};
|
||||
};
|
||||
}
|
||||
}
|
||||
56
include/hyprutils/animation/AnimationConfig.hpp
Normal file
56
include/hyprutils/animation/AnimationConfig.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include "../memory/WeakPtr.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace Animation {
|
||||
/*
|
||||
Structure for animation properties.
|
||||
Config properties need to have a static lifetime to allow for config reload.
|
||||
*/
|
||||
struct SAnimationPropertyConfig {
|
||||
bool overridden = false;
|
||||
|
||||
std::string internalBezier = "";
|
||||
std::string internalStyle = "";
|
||||
float internalSpeed = 0.f;
|
||||
int internalEnabled = -1;
|
||||
|
||||
Memory::CWeakPointer<SAnimationPropertyConfig> pValues;
|
||||
Memory::CWeakPointer<SAnimationPropertyConfig> pParentAnimation;
|
||||
};
|
||||
|
||||
/* A class to manage SAnimationPropertyConfig objects in a tree structure */
|
||||
class CAnimationConfigTree {
|
||||
public:
|
||||
CAnimationConfigTree() = default;
|
||||
~CAnimationConfigTree() = default;
|
||||
|
||||
/* Add a new animation node inheriting from a parent.
|
||||
If parent is empty, a root node will be created that references it's own values.
|
||||
Make sure the parent node has already been created through this interface. */
|
||||
void createNode(const std::string& nodeName, const std::string& parent = "");
|
||||
|
||||
/* check if a node name has been created using createNode */
|
||||
bool nodeExists(const std::string& nodeName) const;
|
||||
|
||||
/* Override the values of a node. The root node can also be overriden. */
|
||||
void setConfigForNode(const std::string& nodeName, int enabled, float speed, const std::string& bezier, const std::string& style = "");
|
||||
|
||||
Memory::CSharedPointer<SAnimationPropertyConfig> getConfig(const std::string& name) const;
|
||||
const std::unordered_map<std::string, Memory::CSharedPointer<SAnimationPropertyConfig>>& getFullConfig() const;
|
||||
|
||||
CAnimationConfigTree(const CAnimationConfigTree&) = delete;
|
||||
CAnimationConfigTree(CAnimationConfigTree&&) = delete;
|
||||
CAnimationConfigTree& operator=(const CAnimationConfigTree&) = delete;
|
||||
CAnimationConfigTree& operator=(CAnimationConfigTree&&) = delete;
|
||||
|
||||
private:
|
||||
void setAnimForChildren(Memory::CSharedPointer<SAnimationPropertyConfig> PANIM);
|
||||
std::unordered_map<std::string, Memory::CSharedPointer<SAnimationPropertyConfig>> m_mAnimationConfig;
|
||||
};
|
||||
}
|
||||
}
|
||||
60
include/hyprutils/animation/AnimationManager.hpp
Normal file
60
include/hyprutils/animation/AnimationManager.hpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#pragma once
|
||||
|
||||
#include "./BezierCurve.hpp"
|
||||
#include "../math/Vector2D.hpp"
|
||||
#include "../memory/WeakPtr.hpp"
|
||||
#include "../signal/Signal.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace Animation {
|
||||
class CBaseAnimatedVariable;
|
||||
|
||||
/* A class for managing bezier curves and variables that are being animated. */
|
||||
class CAnimationManager {
|
||||
public:
|
||||
CAnimationManager();
|
||||
virtual ~CAnimationManager() = default;
|
||||
|
||||
void tickDone();
|
||||
void rotateActive();
|
||||
bool shouldTickForNext();
|
||||
|
||||
virtual void scheduleTick() = 0;
|
||||
virtual void onTicked() = 0;
|
||||
|
||||
void addBezierWithName(std::string, const Math::Vector2D&, const Math::Vector2D&);
|
||||
void removeAllBeziers();
|
||||
|
||||
bool bezierExists(const std::string&);
|
||||
Memory::CSharedPointer<CBezierCurve> getBezier(const std::string&);
|
||||
|
||||
const std::unordered_map<std::string, Memory::CSharedPointer<CBezierCurve>>& getAllBeziers();
|
||||
|
||||
struct SAnimationManagerSignals {
|
||||
Signal::CSignalT<Memory::CWeakPointer<CBaseAnimatedVariable>> connect;
|
||||
Signal::CSignalT<Memory::CWeakPointer<CBaseAnimatedVariable>> disconnect;
|
||||
};
|
||||
|
||||
Memory::CWeakPointer<SAnimationManagerSignals> getSignals() const;
|
||||
|
||||
std::vector<Memory::CWeakPointer<CBaseAnimatedVariable>> m_vActiveAnimatedVariables;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, Memory::CSharedPointer<CBezierCurve>> m_mBezierCurves;
|
||||
|
||||
bool m_bTickScheduled = false;
|
||||
|
||||
struct SAnimVarListeners {
|
||||
Signal::CHyprSignalListener connect;
|
||||
Signal::CHyprSignalListener disconnect;
|
||||
};
|
||||
|
||||
Memory::CUniquePointer<SAnimVarListeners> m_listeners;
|
||||
Memory::CUniquePointer<SAnimationManagerSignals> m_events;
|
||||
};
|
||||
}
|
||||
}
|
||||
35
include/hyprutils/animation/BezierCurve.hpp
Normal file
35
include/hyprutils/animation/BezierCurve.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "../math/Vector2D.hpp"
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace Animation {
|
||||
constexpr int BAKEDPOINTS = 255;
|
||||
constexpr float INVBAKEDPOINTS = 1.f / BAKEDPOINTS;
|
||||
|
||||
/* An implementation of a cubic bezier curve. */
|
||||
class CBezierCurve {
|
||||
public:
|
||||
/* Calculates a cubic bezier curve based on 2 control points (EXCLUDES the 0,0 and 1,1 points). */
|
||||
void setup(const std::array<Hyprutils::Math::Vector2D, 2>& points);
|
||||
/* Calculates a cubic bezier curve based on 4 control points. */
|
||||
void setup4(const std::array<Hyprutils::Math::Vector2D, 4>& points);
|
||||
|
||||
float getYForT(float const& t) const;
|
||||
float getXForT(float const& t) const;
|
||||
float getYForPoint(float const& x) const;
|
||||
|
||||
/* this INCLUDES the 0,0 and 1,1 points. */
|
||||
const std::vector<Hyprutils::Math::Vector2D>& getControlPoints() const;
|
||||
|
||||
private:
|
||||
/* this INCLUDES the 0,0 and 1,1 points. */
|
||||
std::vector<Hyprutils::Math::Vector2D> m_vPoints;
|
||||
|
||||
std::array<Hyprutils::Math::Vector2D, BAKEDPOINTS> m_aPointsBaked;
|
||||
};
|
||||
}
|
||||
}
|
||||
36
include/hyprutils/cli/ArgumentParser.hpp
Normal file
36
include/hyprutils/cli/ArgumentParser.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#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;
|
||||
};
|
||||
};
|
||||
116
include/hyprutils/cli/Logger.hpp
Normal file
116
include/hyprutils/cli/Logger.hpp
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#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 = "";
|
||||
};
|
||||
};
|
||||
55
include/hyprutils/i18n/I18nEngine.hpp
Normal file
55
include/hyprutils/i18n/I18nEngine.hpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#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;
|
||||
};
|
||||
}
|
||||
|
|
@ -135,6 +135,12 @@ namespace Hyprutils::Math {
|
|||
*/
|
||||
Vector2D size() const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves the size of the box offset by its position.
|
||||
* @return Vector2D representing the bottom right extent of the box.
|
||||
*/
|
||||
Vector2D extent() const;
|
||||
|
||||
/**
|
||||
* @brief Finds the closest point within the box to a given vector.
|
||||
* @param vec Vector from which to find the closest point.
|
||||
|
|
@ -179,4 +185,4 @@ namespace Hyprutils::Math {
|
|||
private:
|
||||
CBox roundInternal();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
112
include/hyprutils/math/Edges.hpp
Normal file
112
include/hyprutils/math/Edges.hpp
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
#pragma once
|
||||
#include "hyprutils/memory/Casts.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Hyprutils::Math {
|
||||
|
||||
/**
|
||||
* @brief Flag set of box edges
|
||||
*/
|
||||
class CEdges {
|
||||
public:
|
||||
enum eEdges : uint8_t {
|
||||
NONE = 0,
|
||||
TOP = 1,
|
||||
LEFT = 2,
|
||||
BOTTOM = 4,
|
||||
RIGHT = 8,
|
||||
};
|
||||
|
||||
CEdges() = default;
|
||||
CEdges(eEdges edges) : m_edges(edges) {}
|
||||
CEdges(uint8_t edges) : m_edges(Memory::sc<eEdges>(edges)) {}
|
||||
|
||||
bool operator==(const CEdges& other) {
|
||||
return m_edges == other.m_edges;
|
||||
}
|
||||
|
||||
CEdges operator|(const CEdges& other) {
|
||||
return m_edges | other.m_edges;
|
||||
}
|
||||
|
||||
CEdges operator&(const CEdges& other) {
|
||||
return m_edges & other.m_edges;
|
||||
}
|
||||
|
||||
CEdges operator^(const CEdges& other) {
|
||||
return m_edges ^ other.m_edges;
|
||||
}
|
||||
|
||||
void operator|=(const CEdges& other) {
|
||||
m_edges = (*this | other).m_edges;
|
||||
}
|
||||
|
||||
void operator&=(const CEdges& other) {
|
||||
m_edges = (*this & other).m_edges;
|
||||
}
|
||||
|
||||
void operator^=(const CEdges& other) {
|
||||
m_edges = (*this ^ other).m_edges;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the edge set contains the top edge.
|
||||
*/
|
||||
bool top() {
|
||||
return m_edges & TOP;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the edge set contains the left edge.
|
||||
*/
|
||||
bool left() {
|
||||
return m_edges & LEFT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the edge set contains the bottom edge.
|
||||
*/
|
||||
bool bottom() {
|
||||
return m_edges & BOTTOM;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the edge set contains the right edge.
|
||||
*/
|
||||
bool right() {
|
||||
return m_edges & RIGHT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param top The state the top edge should be set to.
|
||||
*/
|
||||
void setTop(bool top) {
|
||||
m_edges = Memory::sc<eEdges>((m_edges & ~TOP) | (TOP * top));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param left The state the left edge should be set to.
|
||||
*/
|
||||
void setLeft(bool left) {
|
||||
m_edges = Memory::sc<eEdges>((m_edges & ~LEFT) | (LEFT * left));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bottom The state the bottom edge should be set to.
|
||||
*/
|
||||
void setBottom(bool bottom) {
|
||||
m_edges = Memory::sc<eEdges>((m_edges & ~BOTTOM) | (BOTTOM * bottom));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param right The state the right edge should be set to.
|
||||
*/
|
||||
void setRight(bool right) {
|
||||
m_edges = Memory::sc<eEdges>((m_edges & ~RIGHT) | (RIGHT * right));
|
||||
}
|
||||
|
||||
eEdges m_edges = NONE;
|
||||
};
|
||||
|
||||
}
|
||||
59
include/hyprutils/math/Mat3x3.hpp
Normal file
59
include/hyprutils/math/Mat3x3.hpp
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <ostream>
|
||||
|
||||
#include "./Misc.hpp"
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace Math {
|
||||
class CBox;
|
||||
class Vector2D;
|
||||
|
||||
class Mat3x3 {
|
||||
public:
|
||||
Mat3x3();
|
||||
Mat3x3(std::array<float, 9>);
|
||||
Mat3x3(std::vector<float>);
|
||||
|
||||
/* create an identity 3x3 matrix */
|
||||
static Mat3x3 identity();
|
||||
|
||||
/* create an output projection matrix */
|
||||
static Mat3x3 outputProjection(const Vector2D& size, eTransform transform);
|
||||
|
||||
/* get the matrix as an array, in a row-major order. */
|
||||
std::array<float, 9> getMatrix() const;
|
||||
|
||||
/* create a box projection matrix */
|
||||
Mat3x3 projectBox(const CBox& box, eTransform transform, float rot = 0.F /* rad, CCW */) const;
|
||||
|
||||
/* in-place functions */
|
||||
Mat3x3& transform(eTransform transform);
|
||||
Mat3x3& rotate(float rot /* rad, CCW */);
|
||||
Mat3x3& scale(const Vector2D& scale);
|
||||
Mat3x3& scale(const float scale);
|
||||
Mat3x3& translate(const Vector2D& offset);
|
||||
Mat3x3& transpose();
|
||||
Mat3x3& multiply(const Mat3x3& other);
|
||||
|
||||
/* misc utils */
|
||||
Mat3x3 copy() const;
|
||||
std::string toString() const;
|
||||
|
||||
bool operator==(const Mat3x3& other) const {
|
||||
return other.matrix == matrix;
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const Mat3x3& mat) {
|
||||
os << mat.toString();
|
||||
return os;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<float, 9> matrix;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -21,17 +21,21 @@ namespace Hyprutils {
|
|||
CRegion(pixman_box32_t* box);
|
||||
|
||||
CRegion(const CRegion&);
|
||||
CRegion(CRegion&&);
|
||||
CRegion(CRegion&&) noexcept;
|
||||
|
||||
~CRegion();
|
||||
|
||||
CRegion& operator=(CRegion&& other) {
|
||||
pixman_region32_copy(&m_rRegion, other.pixman());
|
||||
CRegion& operator=(CRegion&& other) noexcept {
|
||||
if (this != &other)
|
||||
pixman_region32_copy(&m_rRegion, other.pixman());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CRegion& operator=(CRegion& other) {
|
||||
pixman_region32_copy(&m_rRegion, other.pixman());
|
||||
CRegion& operator=(const CRegion& other) {
|
||||
if (this != &other)
|
||||
pixman_region32_copy(&m_rRegion, other.pixman());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
@ -58,12 +62,24 @@ namespace Hyprutils {
|
|||
CRegion copy() const;
|
||||
|
||||
std::vector<pixman_box32_t> getRects() const;
|
||||
template <typename T>
|
||||
void forEachRect(T&& cb) const {
|
||||
int rectsNum = 0;
|
||||
const auto* rects = pixman_region32_rectangles(&m_rRegion, &rectsNum);
|
||||
for (int i = 0; i < rectsNum; ++i) {
|
||||
std::forward<T>(cb)(rects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
pixman_region32_t* pixman() {
|
||||
return &m_rRegion;
|
||||
}
|
||||
|
||||
const pixman_region32_t* pixman() const {
|
||||
return &m_rRegion;
|
||||
}
|
||||
|
||||
private:
|
||||
pixman_region32_t m_rRegion;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprutils/memory/Casts.hpp>
|
||||
#include <hyprutils/math/Misc.hpp>
|
||||
|
||||
#include <format>
|
||||
#include <string>
|
||||
|
||||
|
|
@ -7,82 +10,86 @@ namespace Hyprutils {
|
|||
namespace Math {
|
||||
class Vector2D {
|
||||
public:
|
||||
Vector2D(double, double);
|
||||
Vector2D(int, int);
|
||||
Vector2D();
|
||||
~Vector2D();
|
||||
constexpr Vector2D(double xx, double yy) : x(xx), y(yy) {
|
||||
;
|
||||
}
|
||||
constexpr Vector2D(int xx, int yy) : x(Hyprutils::Memory::sc<double>(xx)), y(Hyprutils::Memory::sc<double>(yy)) {
|
||||
;
|
||||
}
|
||||
constexpr Vector2D() = default;
|
||||
~Vector2D() = default;
|
||||
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
|
||||
// returns the scale
|
||||
double normalize();
|
||||
double normalize();
|
||||
|
||||
Vector2D operator+(const Vector2D& a) const {
|
||||
constexpr Vector2D operator+(const Vector2D& a) const {
|
||||
return Vector2D(this->x + a.x, this->y + a.y);
|
||||
}
|
||||
Vector2D operator-(const Vector2D& a) const {
|
||||
constexpr Vector2D operator-(const Vector2D& a) const {
|
||||
return Vector2D(this->x - a.x, this->y - a.y);
|
||||
}
|
||||
Vector2D operator-() const {
|
||||
constexpr Vector2D operator-() const {
|
||||
return Vector2D(-this->x, -this->y);
|
||||
}
|
||||
Vector2D operator*(const double& a) const {
|
||||
constexpr Vector2D operator*(const double& a) const {
|
||||
return Vector2D(this->x * a, this->y * a);
|
||||
}
|
||||
Vector2D operator/(const double& a) const {
|
||||
constexpr Vector2D operator/(const double& a) const {
|
||||
return Vector2D(this->x / a, this->y / a);
|
||||
}
|
||||
|
||||
bool operator==(const Vector2D& a) const {
|
||||
constexpr bool operator==(const Vector2D& a) const {
|
||||
return a.x == x && a.y == y;
|
||||
}
|
||||
|
||||
bool operator!=(const Vector2D& a) const {
|
||||
constexpr bool operator!=(const Vector2D& a) const {
|
||||
return a.x != x || a.y != y;
|
||||
}
|
||||
|
||||
Vector2D operator*(const Vector2D& a) const {
|
||||
constexpr Vector2D operator*(const Vector2D& a) const {
|
||||
return Vector2D(this->x * a.x, this->y * a.y);
|
||||
}
|
||||
|
||||
Vector2D operator/(const Vector2D& a) const {
|
||||
constexpr Vector2D operator/(const Vector2D& a) const {
|
||||
return Vector2D(this->x / a.x, this->y / a.y);
|
||||
}
|
||||
|
||||
bool operator>(const Vector2D& a) const {
|
||||
constexpr bool operator>(const Vector2D& a) const {
|
||||
return this->x > a.x && this->y > a.y;
|
||||
}
|
||||
|
||||
bool operator<(const Vector2D& a) const {
|
||||
constexpr bool operator<(const Vector2D& a) const {
|
||||
return this->x < a.x && this->y < a.y;
|
||||
}
|
||||
Vector2D& operator+=(const Vector2D& a) {
|
||||
constexpr Vector2D& operator+=(const Vector2D& a) {
|
||||
this->x += a.x;
|
||||
this->y += a.y;
|
||||
return *this;
|
||||
}
|
||||
Vector2D& operator-=(const Vector2D& a) {
|
||||
constexpr Vector2D& operator-=(const Vector2D& a) {
|
||||
this->x -= a.x;
|
||||
this->y -= a.y;
|
||||
return *this;
|
||||
}
|
||||
Vector2D& operator*=(const Vector2D& a) {
|
||||
constexpr Vector2D& operator*=(const Vector2D& a) {
|
||||
this->x *= a.x;
|
||||
this->y *= a.y;
|
||||
return *this;
|
||||
}
|
||||
Vector2D& operator/=(const Vector2D& a) {
|
||||
constexpr Vector2D& operator/=(const Vector2D& a) {
|
||||
this->x /= a.x;
|
||||
this->y /= a.y;
|
||||
return *this;
|
||||
}
|
||||
Vector2D& operator*=(const double& a) {
|
||||
constexpr Vector2D& operator*=(const double& a) {
|
||||
this->x *= a;
|
||||
this->y *= a;
|
||||
return *this;
|
||||
}
|
||||
Vector2D& operator/=(const double& a) {
|
||||
constexpr Vector2D& operator/=(const double& a) {
|
||||
this->x /= a;
|
||||
this->y /= a;
|
||||
return *this;
|
||||
|
|
@ -97,12 +104,14 @@ namespace Hyprutils {
|
|||
Vector2D round() const;
|
||||
|
||||
Vector2D getComponentMax(const Vector2D& other) const;
|
||||
|
||||
Vector2D transform(eTransform transform, const Vector2D& monitorSize) const;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// absolutely ridiculous formatter spec parsing
|
||||
#define AQ_FORMAT_PARSE(specs__, type__) \
|
||||
#define AQ_FORMAT_PARSE(specs__, type__) \
|
||||
template <typename FormatContext> \
|
||||
constexpr auto parse(FormatContext& ctx) { \
|
||||
auto it = ctx.begin(); \
|
||||
|
|
@ -112,10 +121,10 @@ namespace Hyprutils {
|
|||
return it; \
|
||||
}
|
||||
|
||||
#define AQ_FORMAT_FLAG(spec__, flag__) \
|
||||
#define AQ_FORMAT_FLAG(spec__, flag__) \
|
||||
case spec__: (flag__) = true; break;
|
||||
|
||||
#define AQ_FORMAT_NUMBER(buf__) \
|
||||
#define AQ_FORMAT_NUMBER(buf__) \
|
||||
case '0': \
|
||||
case '1': \
|
||||
case '2': \
|
||||
|
|
@ -139,9 +148,9 @@ struct std::formatter<Hyprutils::Math::Vector2D, CharT> : std::formatter<CharT>
|
|||
bool formatX = false;
|
||||
std::string precision = "";
|
||||
AQ_FORMAT_PARSE(AQ_FORMAT_FLAG('j', formatJson) //
|
||||
AQ_FORMAT_FLAG('X', formatX) //
|
||||
AQ_FORMAT_NUMBER(precision),
|
||||
Hyprutils::Math::Vector2D)
|
||||
AQ_FORMAT_FLAG('X', formatX) //
|
||||
AQ_FORMAT_NUMBER(precision),
|
||||
Hyprutils::Math::Vector2D)
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const Hyprutils::Math::Vector2D& vec, FormatContext& ctx) const {
|
||||
|
|
|
|||
437
include/hyprutils/memory/Atomic.hpp
Normal file
437
include/hyprutils/memory/Atomic.hpp
Normal file
|
|
@ -0,0 +1,437 @@
|
|||
#pragma once
|
||||
|
||||
#include "./ImplBase.hpp"
|
||||
#include "./SharedPtr.hpp"
|
||||
#include "./WeakPtr.hpp"
|
||||
#include <mutex>
|
||||
|
||||
/*
|
||||
This header provides a thread-safe wrapper for Hyprutils shared pointer implementations.
|
||||
Like with STL shared pointers, that does not mean that individual SP/WP objects can be shared across threads without synchronization.
|
||||
It only means that the refcounting of the data is thread-safe.
|
||||
|
||||
Should an Atomic SP/WP be shared across threads, calling a non-const member leads to a data race.
|
||||
To avoid that, each thread should have thread-local SP/WP objects.
|
||||
|
||||
Example:
|
||||
We have a CAtomicSharedPointer member in a class. Suppose this member is accessed by multiple threads and is not constant.
|
||||
In such a case we need external synchronization to ensure valid data access.
|
||||
However, if we create a copy of this CAtomicWeakPointer member for each thread that accesses it,
|
||||
then the references to the object will be counted in a thread-safe manner and it will be safe to lock a WP and to access the data in case of an SP.
|
||||
In such an example, the inner data would need its own synchronization mechanism if it isn't constant itself.
|
||||
*/
|
||||
|
||||
namespace Hyprutils::Memory {
|
||||
namespace Atomic_ {
|
||||
class impl : public Impl_::impl_base {
|
||||
std::recursive_mutex m_mutex;
|
||||
|
||||
public:
|
||||
impl(void* data, DeleteFn deleter) noexcept : Impl_::impl_base(data, deleter) {
|
||||
;
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lockGuard() {
|
||||
return std::lock_guard<std::recursive_mutex>(m_mutex);
|
||||
}
|
||||
|
||||
// Needed when unlock order or mutex lifetime matters.
|
||||
std::recursive_mutex& getMutex() {
|
||||
return m_mutex;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Forward declaration for friend
|
||||
template <typename T>
|
||||
class CAtomicWeakPointer;
|
||||
|
||||
template <typename T>
|
||||
class CAtomicSharedPointer {
|
||||
template <typename X>
|
||||
using isConstructible = std::enable_if_t<std::is_constructible_v<T&, X&>>;
|
||||
template <typename X>
|
||||
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)) {
|
||||
;
|
||||
}
|
||||
|
||||
CAtomicSharedPointer(Impl_::impl_base* impl, void* data) noexcept : m_ptr(impl, data) {
|
||||
;
|
||||
}
|
||||
|
||||
CAtomicSharedPointer(const CAtomicSharedPointer<T>& ref) {
|
||||
if (!ref.m_ptr.impl_)
|
||||
return;
|
||||
|
||||
auto lg = ref.implLockGuard();
|
||||
m_ptr = ref.m_ptr;
|
||||
}
|
||||
|
||||
template <typename U, typename = isConstructible<U>>
|
||||
CAtomicSharedPointer(const CAtomicSharedPointer<U>& ref) {
|
||||
if (!ref.m_ptr.impl_)
|
||||
return;
|
||||
|
||||
auto lg = ref.implLockGuard();
|
||||
m_ptr = ref.m_ptr;
|
||||
}
|
||||
|
||||
template <typename U, typename = isConstructible<U>>
|
||||
CAtomicSharedPointer(CAtomicSharedPointer<U>&& ref) noexcept {
|
||||
std::swap(m_ptr, ref.m_ptr);
|
||||
}
|
||||
|
||||
CAtomicSharedPointer(CAtomicSharedPointer&& ref) noexcept {
|
||||
std::swap(m_ptr, ref.m_ptr);
|
||||
}
|
||||
|
||||
CAtomicSharedPointer() noexcept = default;
|
||||
|
||||
CAtomicSharedPointer(std::nullptr_t) noexcept {
|
||||
; // empty
|
||||
}
|
||||
|
||||
~CAtomicSharedPointer() {
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
validHierarchy<const CAtomicSharedPointer<U>&> operator=(const CAtomicSharedPointer<U>& rhs) {
|
||||
reset();
|
||||
|
||||
if (!rhs.m_ptr.impl_)
|
||||
return *this;
|
||||
|
||||
auto lg = rhs.implLockGuard();
|
||||
m_ptr = rhs.m_ptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CAtomicSharedPointer& operator=(const CAtomicSharedPointer& rhs) {
|
||||
if (this == &rhs)
|
||||
return *this;
|
||||
|
||||
reset();
|
||||
|
||||
if (!rhs.m_ptr.impl_)
|
||||
return *this;
|
||||
|
||||
auto lg = rhs.implLockGuard();
|
||||
m_ptr = rhs.m_ptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
validHierarchy<const CAtomicSharedPointer<U>&> operator=(CAtomicSharedPointer<U>&& rhs) noexcept {
|
||||
std::swap(m_ptr, rhs.m_ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CAtomicSharedPointer& operator=(CAtomicSharedPointer&& rhs) noexcept {
|
||||
if (this == &rhs)
|
||||
return *this;
|
||||
|
||||
std::swap(m_ptr, rhs.m_ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
if (!m_ptr.impl_)
|
||||
return;
|
||||
|
||||
// last ref and last wref?
|
||||
// -> must unlock BEFORE reset
|
||||
// not last ref?
|
||||
// -> must unlock AFTER reset
|
||||
auto& mutex = sc<Atomic_::impl*>(m_ptr.impl_)->getMutex();
|
||||
mutex.lock();
|
||||
|
||||
if (m_ptr.impl_->ref() > 1) {
|
||||
m_ptr.reset();
|
||||
mutex.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ptr.impl_->wref() == 0) {
|
||||
mutex.unlock(); // Don't hold the mutex when destroying it
|
||||
|
||||
m_ptr.impl_->destroy();
|
||||
delete sc<Atomic_::impl*>(m_ptr.impl_);
|
||||
m_ptr.impl_ = nullptr;
|
||||
|
||||
// mutex invalid
|
||||
return;
|
||||
} else {
|
||||
// When the control block gets destroyed, the mutex is destroyed with it.
|
||||
// Thus we must avoid attempting an unlock after impl_ has been destroyed.
|
||||
// Without the workaround is no safe way of checking whether it has been destroyed or not.
|
||||
//
|
||||
// To avoid this altogether, keep a weak pointer here.
|
||||
// This guarantees that impl_ is still valid after the reset.
|
||||
CWeakPointer<T> guard = m_ptr;
|
||||
m_ptr.reset(); // destroys the data
|
||||
|
||||
// Now we can safely check if guard is the last wref.
|
||||
if (guard.impl_->wref() == 1) {
|
||||
mutex.unlock();
|
||||
|
||||
// destroy impl_ (includes the mutex)
|
||||
delete sc<Atomic_::impl*>(guard.impl_);
|
||||
guard.impl_ = nullptr;
|
||||
|
||||
// mutex invalid
|
||||
return;
|
||||
}
|
||||
|
||||
guard.reset();
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
T& operator*() const {
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
return m_ptr.get();
|
||||
}
|
||||
|
||||
T* get() const {
|
||||
return m_ptr.get();
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
bool operator==(const CAtomicSharedPointer& rhs) const {
|
||||
return m_ptr == rhs.m_ptr;
|
||||
}
|
||||
|
||||
bool operator()(const CAtomicSharedPointer& lhs, const CAtomicSharedPointer& rhs) const {
|
||||
return lhs.m_ptr == rhs.m_ptr;
|
||||
}
|
||||
|
||||
unsigned int strongRef() const {
|
||||
return m_ptr.impl_ ? m_ptr.impl_->ref() : 0;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
CSharedPointer<T> m_ptr;
|
||||
|
||||
template <typename U>
|
||||
friend class CAtomicWeakPointer;
|
||||
template <typename U>
|
||||
friend class CAtomicSharedPointer;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class CAtomicWeakPointer {
|
||||
|
||||
template <typename X>
|
||||
using isConstructible = std::enable_if_t<std::is_constructible_v<T&, X&>>;
|
||||
template <typename X>
|
||||
using validHierarchy = std::enable_if_t<std::is_assignable_v<CAtomicWeakPointer<T>&, X>, CAtomicWeakPointer&>;
|
||||
|
||||
public:
|
||||
CAtomicWeakPointer(const CAtomicWeakPointer<T>& ref) {
|
||||
if (!ref.m_ptr.impl_)
|
||||
return;
|
||||
|
||||
auto lg = ref.implLockGuard();
|
||||
m_ptr = ref.m_ptr;
|
||||
}
|
||||
|
||||
template <typename U, typename = isConstructible<U>>
|
||||
CAtomicWeakPointer(const CAtomicWeakPointer<U>& ref) {
|
||||
if (!ref.m_ptr.impl_)
|
||||
return;
|
||||
|
||||
auto lg = ref.implLockGuard();
|
||||
m_ptr = ref.m_ptr;
|
||||
}
|
||||
|
||||
template <typename U, typename = isConstructible<U>>
|
||||
CAtomicWeakPointer(CAtomicWeakPointer<U>&& ref) noexcept {
|
||||
std::swap(m_ptr, ref.m_ptr);
|
||||
}
|
||||
|
||||
CAtomicWeakPointer(CAtomicWeakPointer&& ref) noexcept {
|
||||
std::swap(m_ptr, ref.m_ptr);
|
||||
}
|
||||
|
||||
CAtomicWeakPointer(const CAtomicSharedPointer<T>& ref) {
|
||||
if (!ref.m_ptr.impl_)
|
||||
return;
|
||||
|
||||
auto lg = ref.implLockGuard();
|
||||
m_ptr = ref.m_ptr;
|
||||
}
|
||||
|
||||
CAtomicWeakPointer() noexcept = default;
|
||||
|
||||
CAtomicWeakPointer(std::nullptr_t) noexcept {
|
||||
; // empty
|
||||
}
|
||||
|
||||
~CAtomicWeakPointer() {
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
validHierarchy<const CAtomicWeakPointer<U>&> operator=(const CAtomicWeakPointer<U>& rhs) {
|
||||
reset();
|
||||
|
||||
auto lg = rhs.implLockGuard();
|
||||
m_ptr = rhs.m_ptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CAtomicWeakPointer& operator=(const CAtomicWeakPointer& rhs) {
|
||||
if (this == &rhs)
|
||||
return *this;
|
||||
|
||||
reset();
|
||||
|
||||
auto lg = rhs.implLockGuard();
|
||||
m_ptr = rhs.m_ptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
validHierarchy<const CAtomicWeakPointer<U>&> operator=(CAtomicWeakPointer<U>&& rhs) noexcept {
|
||||
std::swap(m_ptr, rhs.m_ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CAtomicWeakPointer& operator=(CAtomicWeakPointer&& rhs) noexcept {
|
||||
if (this == &rhs)
|
||||
return *this;
|
||||
|
||||
std::swap(m_ptr, rhs.m_ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
if (!m_ptr.impl_)
|
||||
return;
|
||||
|
||||
// last ref and last wref?
|
||||
// -> must unlock BEFORE reset
|
||||
// not last ref?
|
||||
// -> must unlock AFTER reset
|
||||
auto& mutex = sc<Atomic_::impl*>(m_ptr.impl_)->getMutex();
|
||||
mutex.lock();
|
||||
if (m_ptr.impl_->ref() == 0 && m_ptr.impl_->wref() == 1) {
|
||||
mutex.unlock();
|
||||
|
||||
delete sc<Atomic_::impl*>(m_ptr.impl_);
|
||||
m_ptr.impl_ = nullptr;
|
||||
// mutex invalid
|
||||
return;
|
||||
}
|
||||
|
||||
m_ptr.reset();
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
T& operator*() const {
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
return m_ptr.get();
|
||||
}
|
||||
|
||||
T* get() const {
|
||||
return m_ptr.get();
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
bool operator==(const CAtomicWeakPointer& rhs) const {
|
||||
return m_ptr == rhs.m_ptr;
|
||||
}
|
||||
|
||||
bool operator==(const CAtomicSharedPointer<T>& rhs) const {
|
||||
return m_ptr == rhs.m_ptr;
|
||||
}
|
||||
|
||||
bool operator()(const CAtomicWeakPointer& lhs, const CAtomicWeakPointer& rhs) const {
|
||||
return lhs.m_ptr == rhs.m_ptr;
|
||||
}
|
||||
|
||||
bool expired() {
|
||||
return m_ptr.expired();
|
||||
}
|
||||
|
||||
bool valid() {
|
||||
return m_ptr.valid();
|
||||
}
|
||||
|
||||
CAtomicSharedPointer<T> lock() const {
|
||||
if (!m_ptr.impl_)
|
||||
return {};
|
||||
|
||||
auto lg = implLockGuard();
|
||||
|
||||
if (!m_ptr.impl_->dataNonNull() || m_ptr.impl_->destroying() || !m_ptr.impl_->lockable())
|
||||
return {};
|
||||
|
||||
return CAtomicSharedPointer<T>(m_ptr.impl_, m_ptr.m_data);
|
||||
}
|
||||
|
||||
Atomic_::impl* impl() const {
|
||||
return sc<Atomic_::impl*>(m_ptr.impl_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::lock_guard<std::recursive_mutex> implLockGuard() const {
|
||||
return impl()->lockGuard();
|
||||
}
|
||||
|
||||
CWeakPointer<T> m_ptr;
|
||||
|
||||
template <typename U>
|
||||
friend class CAtomicWeakPointer;
|
||||
template <typename U>
|
||||
friend class CAtomicSharedPointer;
|
||||
};
|
||||
|
||||
template <typename U, typename... Args>
|
||||
[[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);
|
||||
}
|
||||
}
|
||||
29
include/hyprutils/memory/Casts.hpp
Normal file
29
include/hyprutils/memory/Casts.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
#include <bit>
|
||||
#include <utility>
|
||||
namespace Hyprutils::Memory {
|
||||
template <typename To, typename From>
|
||||
constexpr To sc(From&& from) noexcept {
|
||||
return static_cast<To>(std::forward<From>(from));
|
||||
}
|
||||
|
||||
template <typename To, typename From>
|
||||
constexpr To cc(From&& from) noexcept {
|
||||
return const_cast<To>(std::forward<From>(from));
|
||||
}
|
||||
|
||||
template <typename To, typename From>
|
||||
constexpr To rc(From&& from) noexcept {
|
||||
return reinterpret_cast<To>(std::forward<From>(from));
|
||||
}
|
||||
|
||||
template <typename To, typename From>
|
||||
constexpr To dc(From&& from) {
|
||||
return dynamic_cast<To>(std::forward<From>(from));
|
||||
}
|
||||
|
||||
template <typename To, typename From>
|
||||
constexpr To bc(const From& from) noexcept {
|
||||
return std::bit_cast<To>(from);
|
||||
}
|
||||
}
|
||||
98
include/hyprutils/memory/ImplBase.hpp
Normal file
98
include/hyprutils/memory/ImplBase.hpp
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace Memory {
|
||||
namespace Impl_ {
|
||||
class impl_base {
|
||||
public:
|
||||
using DeleteFn = void (*)(void*);
|
||||
|
||||
impl_base(void* data, DeleteFn deleter, bool lock = true) noexcept : _lockable(lock), _data(data), _deleter(deleter) {
|
||||
;
|
||||
}
|
||||
|
||||
void inc() noexcept {
|
||||
_ref++;
|
||||
}
|
||||
|
||||
void dec() noexcept {
|
||||
_ref--;
|
||||
}
|
||||
|
||||
void incWeak() noexcept {
|
||||
_weak++;
|
||||
}
|
||||
|
||||
void decWeak() noexcept {
|
||||
_weak--;
|
||||
}
|
||||
|
||||
unsigned int ref() noexcept {
|
||||
return _ref;
|
||||
}
|
||||
|
||||
unsigned int wref() noexcept {
|
||||
return _weak;
|
||||
}
|
||||
|
||||
void destroy() noexcept {
|
||||
_destroy();
|
||||
}
|
||||
|
||||
bool destroying() noexcept {
|
||||
return _destroying;
|
||||
}
|
||||
|
||||
bool lockable() noexcept {
|
||||
return _lockable;
|
||||
}
|
||||
|
||||
bool dataNonNull() noexcept {
|
||||
return _data != nullptr;
|
||||
}
|
||||
|
||||
void* getData() noexcept {
|
||||
return _data;
|
||||
}
|
||||
|
||||
~impl_base() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
private:
|
||||
/* strong refcount */
|
||||
unsigned int _ref = 0;
|
||||
/* weak refcount */
|
||||
unsigned int _weak = 0;
|
||||
/* if this is lockable (shared) */
|
||||
bool _lockable = true;
|
||||
|
||||
void* _data = nullptr;
|
||||
|
||||
/* if the destructor was called,
|
||||
creating shared_ptrs is no longer valid */
|
||||
bool _destroying = false;
|
||||
|
||||
void _destroy() {
|
||||
if (!_data || _destroying)
|
||||
return;
|
||||
|
||||
// first, we destroy the data, but keep the pointer.
|
||||
// this way, weak pointers will still be able to
|
||||
// reference and use, but no longer create shared ones.
|
||||
_destroying = true;
|
||||
_deleter(_data);
|
||||
// now, we can reset the data and call it a day.
|
||||
_data = nullptr;
|
||||
_destroying = false;
|
||||
}
|
||||
|
||||
DeleteFn _deleter = nullptr;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "ImplBase.hpp"
|
||||
#include "Casts.hpp"
|
||||
|
||||
/*
|
||||
This is a custom impl of std::shared_ptr.
|
||||
|
|
@ -17,156 +17,49 @@
|
|||
|
||||
namespace Hyprutils {
|
||||
namespace Memory {
|
||||
namespace CSharedPointer_ {
|
||||
class impl_base {
|
||||
public:
|
||||
virtual ~impl_base() {};
|
||||
|
||||
virtual void inc() noexcept = 0;
|
||||
virtual void dec() noexcept = 0;
|
||||
virtual void incWeak() noexcept = 0;
|
||||
virtual void decWeak() noexcept = 0;
|
||||
virtual unsigned int ref() noexcept = 0;
|
||||
virtual unsigned int wref() noexcept = 0;
|
||||
virtual void destroy() noexcept = 0;
|
||||
virtual bool destroying() noexcept = 0;
|
||||
virtual bool dataNonNull() noexcept = 0;
|
||||
virtual void* getData() noexcept = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class impl : public impl_base {
|
||||
public:
|
||||
impl(T* data) noexcept : _data(data) {
|
||||
;
|
||||
}
|
||||
|
||||
/* strong refcount */
|
||||
unsigned int _ref = 0;
|
||||
/* weak refcount */
|
||||
unsigned int _weak = 0;
|
||||
|
||||
T* _data = nullptr;
|
||||
|
||||
friend void swap(impl*& a, impl*& b) {
|
||||
impl* tmp = a;
|
||||
a = b;
|
||||
b = tmp;
|
||||
}
|
||||
|
||||
/* if the destructor was called,
|
||||
creating shared_ptrs is no longer valid */
|
||||
bool _destroying = false;
|
||||
|
||||
void _destroy() {
|
||||
if (!_data || _destroying)
|
||||
return;
|
||||
|
||||
// first, we destroy the data, but keep the pointer.
|
||||
// this way, weak pointers will still be able to
|
||||
// reference and use, but no longer create shared ones.
|
||||
_destroying = true;
|
||||
__deleter(_data);
|
||||
// now, we can reset the data and call it a day.
|
||||
_data = nullptr;
|
||||
_destroying = false;
|
||||
}
|
||||
|
||||
std::default_delete<T> __deleter{};
|
||||
|
||||
//
|
||||
virtual void inc() noexcept {
|
||||
_ref++;
|
||||
}
|
||||
|
||||
virtual void dec() noexcept {
|
||||
_ref--;
|
||||
}
|
||||
|
||||
virtual void incWeak() noexcept {
|
||||
_weak++;
|
||||
}
|
||||
|
||||
virtual void decWeak() noexcept {
|
||||
_weak--;
|
||||
}
|
||||
|
||||
virtual unsigned int ref() noexcept {
|
||||
return _ref;
|
||||
}
|
||||
|
||||
virtual unsigned int wref() noexcept {
|
||||
return _weak;
|
||||
}
|
||||
|
||||
virtual void destroy() noexcept {
|
||||
_destroy();
|
||||
}
|
||||
|
||||
virtual bool destroying() noexcept {
|
||||
return _destroying;
|
||||
}
|
||||
|
||||
virtual bool dataNonNull() noexcept {
|
||||
return _data != nullptr;
|
||||
}
|
||||
|
||||
virtual void* getData() noexcept {
|
||||
return _data;
|
||||
}
|
||||
|
||||
virtual ~impl() {
|
||||
destroy();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class CSharedPointer {
|
||||
public:
|
||||
template <typename X>
|
||||
using validHierarchy = typename std::enable_if<std::is_assignable<CSharedPointer<T>&, X>::value, CSharedPointer&>::type;
|
||||
using validHierarchy = std::enable_if_t<std::is_assignable_v<CSharedPointer<T>&, X>, CSharedPointer&>;
|
||||
template <typename X>
|
||||
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type;
|
||||
using isConstructible = std::enable_if_t<std::is_constructible_v<T&, X&>>;
|
||||
|
||||
/* creates a new shared pointer managing a resource
|
||||
avoid calling. Could duplicate ownership. Prefer makeShared */
|
||||
explicit CSharedPointer(T* object) noexcept {
|
||||
impl_ = new CSharedPointer_::impl<T>(object);
|
||||
explicit CSharedPointer(T* object) noexcept : impl_(new Impl_::impl_base(sc<void*>(object), _delete)), m_data(sc<void*>(object)) {
|
||||
increment();
|
||||
}
|
||||
|
||||
/* creates a shared pointer from a reference */
|
||||
template <typename U, typename = isConstructible<U>>
|
||||
CSharedPointer(const CSharedPointer<U>& ref) noexcept {
|
||||
impl_ = ref.impl_;
|
||||
CSharedPointer(const CSharedPointer<U>& ref) noexcept : impl_(ref.impl_), m_data(ref.m_data) {
|
||||
increment();
|
||||
}
|
||||
|
||||
CSharedPointer(const CSharedPointer& ref) noexcept {
|
||||
impl_ = ref.impl_;
|
||||
CSharedPointer(const CSharedPointer& ref) noexcept : impl_(ref.impl_), m_data(ref.m_data) {
|
||||
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(CSharedPointer_::impl_base* implementation) noexcept {
|
||||
impl_ = implementation;
|
||||
CSharedPointer(Impl_::impl_base* implementation, void* data) noexcept : impl_(implementation), m_data(data) {
|
||||
increment();
|
||||
}
|
||||
|
||||
/* creates an empty shared pointer with no implementation */
|
||||
CSharedPointer() noexcept {
|
||||
; // empty
|
||||
}
|
||||
CSharedPointer() noexcept = default;
|
||||
|
||||
/* creates an empty shared pointer with no implementation */
|
||||
CSharedPointer(std::nullptr_t) noexcept {
|
||||
|
|
@ -183,7 +76,8 @@ namespace Hyprutils {
|
|||
return *this;
|
||||
|
||||
decrement();
|
||||
impl_ = rhs.impl_;
|
||||
impl_ = rhs.impl_;
|
||||
m_data = rhs.m_data;
|
||||
increment();
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -193,7 +87,8 @@ namespace Hyprutils {
|
|||
return *this;
|
||||
|
||||
decrement();
|
||||
impl_ = rhs.impl_;
|
||||
impl_ = rhs.impl_;
|
||||
m_data = rhs.m_data;
|
||||
increment();
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -201,11 +96,13 @@ 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) {
|
||||
CSharedPointer& operator=(CSharedPointer&& rhs) noexcept {
|
||||
std::swap(impl_, rhs.impl_);
|
||||
std::swap(m_data, rhs.m_data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
@ -213,16 +110,18 @@ 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_;
|
||||
}
|
||||
|
||||
bool operator()(const CSharedPointer& lhs, const CSharedPointer& rhs) const {
|
||||
return reinterpret_cast<uintptr_t>(lhs.impl_) < reinterpret_cast<uintptr_t>(rhs.impl_);
|
||||
return rc<uintptr_t>(lhs.impl_) < rc<uintptr_t>(rhs.impl_);
|
||||
}
|
||||
|
||||
bool operator<(const CSharedPointer& rhs) const {
|
||||
return reinterpret_cast<uintptr_t>(impl_) < reinterpret_cast<uintptr_t>(rhs.impl_);
|
||||
return rc<uintptr_t>(impl_) < rc<uintptr_t>(rhs.impl_);
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
|
|
@ -235,21 +134,29 @@ namespace Hyprutils {
|
|||
|
||||
void reset() {
|
||||
decrement();
|
||||
impl_ = nullptr;
|
||||
impl_ = nullptr;
|
||||
m_data = nullptr;
|
||||
}
|
||||
|
||||
T* get() const {
|
||||
return impl_ ? static_cast<T*>(impl_->getData()) : nullptr;
|
||||
return impl_ && impl_->dataNonNull() ? sc<T*>(m_data) : nullptr;
|
||||
}
|
||||
|
||||
unsigned int strongRef() const {
|
||||
return impl_ ? impl_->ref() : 0;
|
||||
}
|
||||
|
||||
CSharedPointer_::impl_base* impl_ = nullptr;
|
||||
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));
|
||||
}
|
||||
|
||||
/*
|
||||
no-op if there is no impl_
|
||||
may delete the stored object if ref == 0
|
||||
may delete and reset impl_ if ref == 0 and weak == 0
|
||||
|
|
@ -272,7 +179,7 @@ namespace Hyprutils {
|
|||
impl_->inc();
|
||||
}
|
||||
|
||||
/* destroy the pointed-to object
|
||||
/* destroy the pointed-to object
|
||||
if able, will also destroy impl */
|
||||
void destroyImpl() {
|
||||
// destroy the impl contents
|
||||
|
|
@ -287,9 +194,24 @@ namespace Hyprutils {
|
|||
};
|
||||
|
||||
template <typename U, typename... Args>
|
||||
static CSharedPointer<U> makeShared(Args&&... args) {
|
||||
[[nodiscard]] inline CSharedPointer<U> makeShared(Args&&... args) {
|
||||
return CSharedPointer<U>(new U(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -298,4 +220,4 @@ struct std::hash<Hyprutils::Memory::CSharedPointer<T>> {
|
|||
std::size_t operator()(const Hyprutils::Memory::CSharedPointer<T>& p) const noexcept {
|
||||
return std::hash<void*>{}(p.impl_);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
147
include/hyprutils/memory/UniquePtr.hpp
Normal file
147
include/hyprutils/memory/UniquePtr.hpp
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#pragma once
|
||||
|
||||
#include "ImplBase.hpp"
|
||||
#include "Casts.hpp"
|
||||
|
||||
/*
|
||||
This is a custom impl of std::unique_ptr.
|
||||
In contrast to the STL one, it allows for
|
||||
creation of a weak_ptr, that will then be unable
|
||||
to be locked.
|
||||
*/
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace Memory {
|
||||
template <typename T>
|
||||
class CUniquePointer {
|
||||
public:
|
||||
template <typename X>
|
||||
using validHierarchy = std::enable_if_t<std::is_assignable_v<CUniquePointer<T>&, X>, CUniquePointer&>;
|
||||
template <typename X>
|
||||
using isConstructible = std::enable_if_t<std::is_constructible_v<T&, X&>>;
|
||||
|
||||
/* creates a new unique pointer managing a resource
|
||||
avoid calling. Could duplicate ownership. Prefer makeUnique */
|
||||
explicit CUniquePointer(T* object) noexcept : impl_(new Impl_::impl_base(sc<void*>(object), [](void* p) { std::default_delete<T>{}(sc<T*>(p)); }, false)) {
|
||||
increment();
|
||||
}
|
||||
|
||||
/* creates a shared pointer from a reference */
|
||||
template <typename U, typename = isConstructible<U>>
|
||||
CUniquePointer(const CUniquePointer<U>& ref) = delete;
|
||||
CUniquePointer(const CUniquePointer& ref) = delete;
|
||||
|
||||
template <typename U, typename = isConstructible<U>>
|
||||
CUniquePointer(CUniquePointer<U>&& ref) noexcept {
|
||||
std::swap(impl_, ref.impl_);
|
||||
}
|
||||
|
||||
CUniquePointer(CUniquePointer&& ref) noexcept {
|
||||
std::swap(impl_, ref.impl_);
|
||||
}
|
||||
|
||||
/* creates an empty unique pointer with no implementation */
|
||||
CUniquePointer() noexcept = default;
|
||||
|
||||
/* creates an empty unique pointer with no implementation */
|
||||
CUniquePointer(std::nullptr_t) noexcept {
|
||||
; // empty
|
||||
}
|
||||
|
||||
~CUniquePointer() {
|
||||
decrement();
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
validHierarchy<const CUniquePointer<U>&> operator=(const CUniquePointer<U>& rhs) = delete;
|
||||
CUniquePointer& operator=(const CUniquePointer& rhs) = delete;
|
||||
|
||||
template <typename U>
|
||||
validHierarchy<const CUniquePointer<U>&> operator=(CUniquePointer<U>&& rhs) {
|
||||
std::swap(impl_, rhs.impl_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CUniquePointer& operator=(CUniquePointer&& rhs) noexcept {
|
||||
std::swap(impl_, rhs.impl_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return impl_;
|
||||
}
|
||||
|
||||
bool operator()(const CUniquePointer& lhs, const CUniquePointer& rhs) const {
|
||||
return rc<uintptr_t>(lhs.impl_) < rc<uintptr_t>(rhs.impl_);
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
T& operator*() const {
|
||||
return *get();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
decrement();
|
||||
impl_ = nullptr;
|
||||
}
|
||||
|
||||
T* get() const {
|
||||
return impl_ ? sc<T*>(impl_->getData()) : nullptr;
|
||||
}
|
||||
|
||||
Impl_::impl_base* impl_ = nullptr;
|
||||
|
||||
private:
|
||||
/*
|
||||
no-op if there is no impl_
|
||||
may delete the stored object if ref == 0
|
||||
may delete and reset impl_ if ref == 0 and weak == 0
|
||||
*/
|
||||
void decrement() {
|
||||
if (!impl_)
|
||||
return;
|
||||
|
||||
impl_->dec();
|
||||
|
||||
// if ref == 0, we can destroy impl
|
||||
if (impl_->ref() == 0)
|
||||
destroyImpl();
|
||||
}
|
||||
/* no-op if there is no impl_ */
|
||||
void increment() {
|
||||
if (!impl_)
|
||||
return;
|
||||
|
||||
impl_->inc();
|
||||
}
|
||||
|
||||
/* destroy the pointed-to object
|
||||
if able, will also destroy impl */
|
||||
void destroyImpl() {
|
||||
// destroy the impl contents
|
||||
impl_->destroy();
|
||||
|
||||
// check for weak refs, if zero, we can also delete impl_
|
||||
if (impl_->wref() == 0) {
|
||||
delete impl_;
|
||||
impl_ = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename U, typename... Args>
|
||||
[[nodiscard]] inline CUniquePointer<U> makeUnique(Args&&... args) {
|
||||
return CUniquePointer<U>(new U(std::forward<Args>(args)...));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct std::hash<Hyprutils::Memory::CUniquePointer<T>> {
|
||||
std::size_t operator()(const Hyprutils::Memory::CUniquePointer<T>& p) const noexcept {
|
||||
return std::hash<void*>{}(p.impl_);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "./SharedPtr.hpp"
|
||||
#include "./UniquePtr.hpp"
|
||||
#include "./Casts.hpp"
|
||||
|
||||
/*
|
||||
This is a Hyprland implementation of std::weak_ptr.
|
||||
|
|
@ -14,9 +16,9 @@ namespace Hyprutils {
|
|||
class CWeakPointer {
|
||||
public:
|
||||
template <typename X>
|
||||
using validHierarchy = typename std::enable_if<std::is_assignable<CWeakPointer<T>&, X>::value, CWeakPointer&>::type;
|
||||
using validHierarchy = std::enable_if_t<std::is_assignable_v<CWeakPointer<T>&, X>, CWeakPointer&>;
|
||||
template <typename X>
|
||||
using isConstructible = typename std::enable_if<std::is_constructible<T&, X&>::value>::type;
|
||||
using isConstructible = std::enable_if_t<std::is_constructible_v<T&, X&>>;
|
||||
|
||||
/* create a weak ptr from a reference */
|
||||
template <typename U, typename = isConstructible<U>>
|
||||
|
|
@ -24,7 +26,19 @@ namespace Hyprutils {
|
|||
if (!ref.impl_)
|
||||
return;
|
||||
|
||||
impl_ = ref.impl_;
|
||||
impl_ = ref.impl_;
|
||||
m_data = ref.m_data;
|
||||
incrementWeak();
|
||||
}
|
||||
|
||||
/* create a weak ptr from a reference */
|
||||
template <typename U, typename = isConstructible<U>>
|
||||
CWeakPointer(const CUniquePointer<U>& ref) noexcept {
|
||||
if (!ref.impl_)
|
||||
return;
|
||||
|
||||
impl_ = ref.impl_;
|
||||
m_data = ref.impl_->getData();
|
||||
incrementWeak();
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +48,8 @@ namespace Hyprutils {
|
|||
if (!ref.impl_)
|
||||
return;
|
||||
|
||||
impl_ = ref.impl_;
|
||||
impl_ = ref.impl_;
|
||||
m_data = ref.m_data;
|
||||
incrementWeak();
|
||||
}
|
||||
|
||||
|
|
@ -42,17 +57,20 @@ namespace Hyprutils {
|
|||
if (!ref.impl_)
|
||||
return;
|
||||
|
||||
impl_ = ref.impl_;
|
||||
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 */
|
||||
|
|
@ -62,7 +80,8 @@ namespace Hyprutils {
|
|||
return *this;
|
||||
|
||||
decrementWeak();
|
||||
impl_ = rhs.impl_;
|
||||
impl_ = rhs.impl_;
|
||||
m_data = rhs.m_data;
|
||||
incrementWeak();
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -72,7 +91,8 @@ namespace Hyprutils {
|
|||
return *this;
|
||||
|
||||
decrementWeak();
|
||||
impl_ = rhs.impl_;
|
||||
impl_ = rhs.impl_;
|
||||
m_data = rhs.m_data;
|
||||
incrementWeak();
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -80,19 +100,18 @@ namespace Hyprutils {
|
|||
/* create a weak ptr from a shared ptr with assignment */
|
||||
template <typename U>
|
||||
validHierarchy<const CWeakPointer<U>&> operator=(const CSharedPointer<U>& rhs) {
|
||||
if (reinterpret_cast<uintptr_t>(impl_) == reinterpret_cast<uintptr_t>(rhs.impl_))
|
||||
if (rc<uintptr_t>(impl_) == rc<uintptr_t>(rhs.impl_))
|
||||
return *this;
|
||||
|
||||
decrementWeak();
|
||||
impl_ = rhs.impl_;
|
||||
impl_ = rhs.impl_;
|
||||
m_data = rhs.m_data;
|
||||
incrementWeak();
|
||||
return *this;
|
||||
}
|
||||
|
||||
/* create an empty weak ptr */
|
||||
CWeakPointer() {
|
||||
;
|
||||
}
|
||||
CWeakPointer() noexcept = default;
|
||||
|
||||
~CWeakPointer() {
|
||||
decrementWeak();
|
||||
|
|
@ -115,14 +134,15 @@ namespace Hyprutils {
|
|||
|
||||
void reset() {
|
||||
decrementWeak();
|
||||
impl_ = nullptr;
|
||||
impl_ = nullptr;
|
||||
m_data = nullptr;
|
||||
}
|
||||
|
||||
CSharedPointer<T> lock() const {
|
||||
if (!impl_ || !impl_->dataNonNull() || impl_->destroying())
|
||||
if (!impl_ || !impl_->dataNonNull() || impl_->destroying() || !impl_->lockable())
|
||||
return {};
|
||||
|
||||
return CSharedPointer<T>(impl_);
|
||||
return CSharedPointer<T>(impl_, m_data);
|
||||
}
|
||||
|
||||
/* this returns valid() */
|
||||
|
|
@ -138,23 +158,42 @@ namespace Hyprutils {
|
|||
return impl_ == rhs.impl_;
|
||||
}
|
||||
|
||||
bool operator==(const CUniquePointer<T>& rhs) const {
|
||||
return impl_ == rhs.impl_;
|
||||
}
|
||||
|
||||
bool operator==(std::nullptr_t) const {
|
||||
return !valid();
|
||||
}
|
||||
|
||||
bool operator!=(std::nullptr_t) const {
|
||||
return valid();
|
||||
}
|
||||
|
||||
bool operator()(const CWeakPointer& lhs, const CWeakPointer& rhs) const {
|
||||
return reinterpret_cast<uintptr_t>(lhs.impl_) < reinterpret_cast<uintptr_t>(rhs.impl_);
|
||||
return rc<uintptr_t>(lhs.impl_) < rc<uintptr_t>(rhs.impl_);
|
||||
}
|
||||
|
||||
bool operator<(const CWeakPointer& rhs) const {
|
||||
return reinterpret_cast<uintptr_t>(impl_) < reinterpret_cast<uintptr_t>(rhs.impl_);
|
||||
return rc<uintptr_t>(impl_) < rc<uintptr_t>(rhs.impl_);
|
||||
}
|
||||
|
||||
T* get() const {
|
||||
return impl_ ? static_cast<T*>(impl_->getData()) : nullptr;
|
||||
return impl_ && impl_->dataNonNull() ? sc<T*>(m_data) : nullptr;
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
CSharedPointer_::impl_base* impl_ = nullptr;
|
||||
T& operator*() const {
|
||||
return *get();
|
||||
}
|
||||
|
||||
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_ */
|
||||
|
|
@ -181,6 +220,16 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
9
include/hyprutils/os/File.hpp
Normal file
9
include/hyprutils/os/File.hpp
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <expected>
|
||||
#include <string_view>
|
||||
|
||||
namespace Hyprutils::File {
|
||||
std::expected<std::string, std::string> readFileAsString(const std::string_view& path);
|
||||
}
|
||||
39
include/hyprutils/os/FileDescriptor.hpp
Normal file
39
include/hyprutils/os/FileDescriptor.hpp
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include <fcntl.h>
|
||||
namespace Hyprutils {
|
||||
namespace OS {
|
||||
class CFileDescriptor {
|
||||
public:
|
||||
CFileDescriptor() = default;
|
||||
explicit CFileDescriptor(int const fd);
|
||||
CFileDescriptor(CFileDescriptor&&);
|
||||
CFileDescriptor& operator=(CFileDescriptor&&);
|
||||
~CFileDescriptor();
|
||||
|
||||
CFileDescriptor(const CFileDescriptor&) = delete;
|
||||
CFileDescriptor& operator=(const CFileDescriptor&) = delete;
|
||||
|
||||
bool operator==(const CFileDescriptor& rhs) const {
|
||||
return m_fd == rhs.m_fd;
|
||||
}
|
||||
|
||||
bool isValid() const;
|
||||
int get() const;
|
||||
int getFlags() const;
|
||||
bool setFlags(int flags);
|
||||
int take();
|
||||
void reset();
|
||||
CFileDescriptor duplicate(int flags = F_DUPFD_CLOEXEC) const;
|
||||
|
||||
bool isReadable() const;
|
||||
bool isClosed() const;
|
||||
|
||||
static bool isReadable(int fd);
|
||||
static bool isClosed(int fd);
|
||||
|
||||
private:
|
||||
int m_fd = -1;
|
||||
};
|
||||
};
|
||||
};
|
||||
52
include/hyprutils/os/Process.hpp
Normal file
52
include/hyprutils/os/Process.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace OS {
|
||||
class CProcess {
|
||||
public:
|
||||
/* Creates a process object, doesn't run yet */
|
||||
CProcess(const std::string& binary_, const std::vector<std::string>& args_);
|
||||
~CProcess();
|
||||
|
||||
CProcess(CProcess&) = delete;
|
||||
CProcess(CProcess&&) = delete;
|
||||
CProcess(const CProcess&&) = delete;
|
||||
CProcess(const CProcess&) = delete;
|
||||
CProcess& operator=(const CProcess&) = delete;
|
||||
CProcess& operator=(CProcess&&) = delete;
|
||||
|
||||
void addEnv(const std::string& name, const std::string& value);
|
||||
|
||||
// only for async, sync doesn't make sense
|
||||
void setStdinFD(int fd);
|
||||
// only for async, sync doesn't make sense
|
||||
void setStdoutFD(int fd);
|
||||
// only for async, sync doesn't make sense
|
||||
void setStderrFD(int fd);
|
||||
|
||||
/* Run the process, synchronously, get the stdout and stderr. False on fail */
|
||||
bool runSync();
|
||||
|
||||
/* Run the process, asynchronously. This will detach the process from this object (and process) and let it live a happy life. False on fail. */
|
||||
bool runAsync();
|
||||
|
||||
// only populated when ran sync
|
||||
const std::string& stdOut();
|
||||
const std::string& stdErr();
|
||||
|
||||
pid_t pid();
|
||||
|
||||
// only for sync
|
||||
int exitCode();
|
||||
|
||||
private:
|
||||
struct impl;
|
||||
impl* m_impl;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -6,39 +6,27 @@
|
|||
|
||||
namespace Hyprutils {
|
||||
namespace Signal {
|
||||
class CSignal;
|
||||
class CSignalBase;
|
||||
|
||||
class CSignalListener {
|
||||
public:
|
||||
CSignalListener(std::function<void(std::any)> handler);
|
||||
|
||||
CSignalListener(CSignalListener&&) = delete;
|
||||
CSignalListener(CSignalListener&) = delete;
|
||||
CSignalListener(const CSignalListener&) = delete;
|
||||
CSignalListener(const CSignalListener&&) = delete;
|
||||
|
||||
void emit(std::any data);
|
||||
[[deprecated("Relic of the legacy untyped signal API. Using this with CSignalT is undefined behavior.")]] void emit(std::any data);
|
||||
|
||||
private:
|
||||
std::function<void(std::any)> m_fHandler;
|
||||
CSignalListener(std::function<void(void*)> handler);
|
||||
|
||||
void emitInternal(void* args);
|
||||
|
||||
std::function<void(void*)> m_fHandler;
|
||||
|
||||
friend class CSignalBase;
|
||||
};
|
||||
|
||||
typedef Hyprutils::Memory::CSharedPointer<CSignalListener> CHyprSignalListener;
|
||||
|
||||
class CStaticSignalListener {
|
||||
public:
|
||||
CStaticSignalListener(std::function<void(void*, std::any)> handler, void* owner);
|
||||
|
||||
CStaticSignalListener(CStaticSignalListener&&) = delete;
|
||||
CStaticSignalListener(CStaticSignalListener&) = delete;
|
||||
CStaticSignalListener(const CStaticSignalListener&) = delete;
|
||||
CStaticSignalListener(const CStaticSignalListener&&) = delete;
|
||||
|
||||
void emit(std::any data);
|
||||
|
||||
private:
|
||||
void* m_pOwner = nullptr;
|
||||
std::function<void(void*, std::any)> m_fHandler;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,112 @@
|
|||
|
||||
#include <functional>
|
||||
#include <any>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <hyprutils/memory/SharedPtr.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include "./Listener.hpp"
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace Signal {
|
||||
class CSignal {
|
||||
public:
|
||||
void emit(std::any data = {});
|
||||
class CSignalBase {
|
||||
protected:
|
||||
CHyprSignalListener registerListenerInternal(std::function<void(void*)> handler);
|
||||
void registerStaticListenerInternal(std::function<void(void*)> handler);
|
||||
void emitInternal(void* args);
|
||||
|
||||
//
|
||||
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener registerListener(std::function<void(std::any)> handler);
|
||||
std::vector<Hyprutils::Memory::CWeakPointer<CSignalListener>> m_vListeners;
|
||||
std::vector<Hyprutils::Memory::CSharedPointer<CSignalListener>> m_vStaticListeners;
|
||||
};
|
||||
|
||||
template <typename... Args>
|
||||
class CSignalT : public CSignalBase {
|
||||
template <typename T>
|
||||
using RefArg = std::conditional_t<std::is_trivially_copyable_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 {
|
||||
auto argsTuple = std::tuple<RefArg<Args>...>(args...);
|
||||
|
||||
if constexpr (sizeof...(Args) == 1)
|
||||
// NOLINTNEXTLINE: const is reapplied by handler invocation if required
|
||||
emitInternal(Memory::cc<void*>(Memory::sc<const void*>(&std::get<0>(argsTuple))));
|
||||
else
|
||||
emitInternal(&argsTuple);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener listen(std::function<void(RefArg<Args>...)> handler) {
|
||||
return registerListenerInternal(mkHandler(handler));
|
||||
}
|
||||
|
||||
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener listen(std::function<void()> handler)
|
||||
requires(sizeof...(Args) != 0)
|
||||
{
|
||||
return listen([handler](RefArg<Args>... args) { handler(); });
|
||||
}
|
||||
|
||||
template <typename... OtherArgs>
|
||||
[[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener forward(CSignalT<OtherArgs...>& signal) {
|
||||
if constexpr (sizeof...(OtherArgs) == 0)
|
||||
return listen([&signal](RefArg<Args>... args) { signal.emit(); });
|
||||
else
|
||||
return listen([&signal](RefArg<Args>... args) { signal.emit(args...); });
|
||||
}
|
||||
|
||||
// deprecated, use listen()
|
||||
CHyprSignalListener registerListener(std::function<void(std::any d)> handler) {
|
||||
return listen([handler](const Args&... args) {
|
||||
constexpr auto mkAny = [](std::any d = {}) { return d; };
|
||||
handler(mkAny(args...));
|
||||
});
|
||||
}
|
||||
|
||||
// this is for static listeners. They die with this signal.
|
||||
// TODO: can we somehow rid of the void* data and make it a custom this?
|
||||
void registerStaticListener(std::function<void(void*, std::any)> handler, void* owner);
|
||||
void listenStatic(std::function<void(RefArg<Args>...)> handler) {
|
||||
registerStaticListenerInternal(mkHandler(handler));
|
||||
}
|
||||
|
||||
void listenStatic(std::function<void()> handler)
|
||||
requires(sizeof...(Args) != 0)
|
||||
{
|
||||
return listenStatic([handler](RefArg<Args>... args) { handler(); });
|
||||
}
|
||||
|
||||
// Deprecated: use listenStatic()
|
||||
void registerStaticListener(std::function<void(void*, std::any)> handler, void* owner) {
|
||||
return listenStatic([handler, owner](const RefArg<Args>&... args) {
|
||||
constexpr auto mkAny = [](std::any d = {}) { return d; };
|
||||
handler(owner, mkAny(args...));
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Hyprutils::Memory::CWeakPointer<CSignalListener>> m_vListeners;
|
||||
std::vector<std::unique_ptr<CStaticSignalListener>> m_vStaticListeners;
|
||||
std::function<void(void*)> mkHandler(std::function<void(RefArg<Args>...)> handler) {
|
||||
return [handler](void* args) {
|
||||
if constexpr (sizeof...(Args) == 0)
|
||||
handler();
|
||||
else if constexpr (sizeof...(Args) == 1)
|
||||
handler(*Memory::sc<std::remove_reference_t<std::tuple_element_t<0, std::tuple<RefArg<Args>...>>>*>(args));
|
||||
else
|
||||
std::apply(handler, *Memory::sc<std::tuple<RefArg<Args>...>*>(args));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// compat. Deprecated.
|
||||
class CSignal : public CSignalT<std::any> {
|
||||
public:
|
||||
void emit(std::any data = {});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
64
include/hyprutils/string/ConstVarList.hpp
Normal file
64
include/hyprutils/string/ConstVarList.hpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace String {
|
||||
class CConstVarList {
|
||||
public:
|
||||
/** Split string into an immutable arg list
|
||||
@param lastArgNo stop splitting after argv reaches maximum size, last arg will contain rest of unsplit args
|
||||
@param delim if delimiter is 's', use std::isspace
|
||||
@param removeEmpty remove empty args from argv
|
||||
*/
|
||||
CConstVarList(const std::string& in, const size_t lastArgNo = 0, const char delim = ',', const bool removeEmpty = false);
|
||||
|
||||
~CConstVarList() = default;
|
||||
|
||||
size_t size() const {
|
||||
return m_args.size();
|
||||
}
|
||||
|
||||
std::string join(const std::string& joiner, size_t from = 0, size_t to = 0) const;
|
||||
|
||||
void map(std::function<void(const std::string_view&)> func) {
|
||||
for (auto& s : m_args)
|
||||
func(s);
|
||||
}
|
||||
|
||||
std::string_view operator[](const size_t& idx) const {
|
||||
if (idx >= m_args.size())
|
||||
return "";
|
||||
return m_args[idx];
|
||||
}
|
||||
|
||||
// for range-based loops
|
||||
std::vector<std::string_view>::iterator begin() {
|
||||
return m_args.begin();
|
||||
}
|
||||
std::vector<std::string_view>::const_iterator begin() const {
|
||||
return m_args.begin();
|
||||
}
|
||||
std::vector<std::string_view>::iterator end() {
|
||||
return m_args.end();
|
||||
}
|
||||
std::vector<std::string_view>::const_iterator end() const {
|
||||
return m_args.end();
|
||||
}
|
||||
|
||||
bool contains(const std::string_view& el) {
|
||||
for (auto& a : m_args) {
|
||||
if (a == el)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_str;
|
||||
std::vector<std::string_view> m_args;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,11 @@
|
|||
namespace Hyprutils {
|
||||
namespace String {
|
||||
// trims beginning and end of whitespace characters
|
||||
std::string trim(const std::string& in);
|
||||
bool isNumber(const std::string& str, bool allowfloat = false);
|
||||
void replaceInString(std::string& string, const std::string& what, const std::string& to);
|
||||
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);
|
||||
};
|
||||
};
|
||||
50
include/hyprutils/string/VarList2.hpp
Normal file
50
include/hyprutils/string/VarList2.hpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#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;
|
||||
};
|
||||
}
|
||||
}
|
||||
17
include/hyprutils/utils/ScopeGuard.hpp
Normal file
17
include/hyprutils/utils/ScopeGuard.hpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace Hyprutils {
|
||||
namespace Utils {
|
||||
// calls a function when it goes out of scope
|
||||
class CScopeGuard {
|
||||
public:
|
||||
CScopeGuard(const std::function<void()>& fn_);
|
||||
~CScopeGuard();
|
||||
|
||||
private:
|
||||
std::function<void()> fn;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -1,31 +1,52 @@
|
|||
{
|
||||
lib,
|
||||
stdenv,
|
||||
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,
|
||||
}:
|
||||
stdenv.mkDerivation {
|
||||
pname = "hyprutils";
|
||||
inherit version doCheck;
|
||||
let
|
||||
inherit (builtins) foldl';
|
||||
inherit (lib.lists) flatten optional;
|
||||
inherit (lib.strings) optionalString;
|
||||
|
||||
adapters = flatten [
|
||||
(lib.optional withMold stdenvAdapters.useMoldLinker)
|
||||
(lib.optional debug stdenvAdapters.keepDebugInfo)
|
||||
];
|
||||
|
||||
customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters;
|
||||
in
|
||||
customStdenv.mkDerivation {
|
||||
pname = "hyprutils" + optionalString debug "-debug";
|
||||
inherit version;
|
||||
src = ../.;
|
||||
|
||||
doCheck = debug;
|
||||
|
||||
nativeBuildInputs = [
|
||||
cmake
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
buildInputs = flatten [
|
||||
(optional debug gtest)
|
||||
pixman
|
||||
];
|
||||
|
||||
outputs = ["out" "dev"];
|
||||
outputs = [
|
||||
"out"
|
||||
"dev"
|
||||
];
|
||||
|
||||
cmakeBuildType = "RelWithDebInfo";
|
||||
|
||||
dontStrip = true;
|
||||
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
|
||||
|
||||
meta = with lib; {
|
||||
homepage = "https://github.com/hyprwm/hyprutils";
|
||||
|
|
|
|||
23
nix/overlays.nix
Normal file
23
nix/overlays.nix
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
self,
|
||||
lib,
|
||||
}: let
|
||||
mkDate = longDate: (lib.concatStringsSep "-" [
|
||||
(builtins.substring 0 4 longDate)
|
||||
(builtins.substring 4 2 longDate)
|
||||
(builtins.substring 6 2 longDate)
|
||||
]);
|
||||
|
||||
ver = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
|
||||
version = ver + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
|
||||
in {
|
||||
default = self.overlays.hyprutils;
|
||||
hyprutils = final: prev: {
|
||||
hyprutils = final.callPackage ./default.nix {
|
||||
stdenv = final.gcc15Stdenv;
|
||||
inherit version;
|
||||
};
|
||||
hyprutils-debug = final.hyprutils.override {debug = true;};
|
||||
hyprutils-with-tests = final.hyprutils-debug;
|
||||
};
|
||||
}
|
||||
155
src/animation/AnimatedVariable.cpp
Normal file
155
src/animation/AnimatedVariable.cpp
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
#include <hyprutils/animation/AnimatedVariable.hpp>
|
||||
#include <hyprutils/animation/AnimationManager.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
|
||||
using namespace Hyprutils::Animation;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
static const std::string DEFAULTBEZIERNAME = "default";
|
||||
static const std::string DEFAULTSTYLE = "";
|
||||
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
|
||||
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_pAnimationManager = pManager;
|
||||
m_pSignals = pManager->getSignals();
|
||||
m_bDummy = false;
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::connectToActive() {
|
||||
if (m_bDummy || m_bIsConnectedToActive || isAnimationManagerDead())
|
||||
return;
|
||||
|
||||
m_pSignals->connect.emit(m_pSelf);
|
||||
m_bIsConnectedToActive = true;
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::disconnectFromActive() {
|
||||
if (isAnimationManagerDead())
|
||||
return;
|
||||
|
||||
m_pSignals->disconnect.emit(m_pSelf);
|
||||
m_bIsConnectedToActive = false;
|
||||
}
|
||||
|
||||
bool Hyprutils::Animation::CBaseAnimatedVariable::enabled() const {
|
||||
if (m_pConfig && m_pConfig->pValues)
|
||||
return m_pConfig->pValues->internalEnabled;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& CBaseAnimatedVariable::getBezierName() const {
|
||||
if (m_pConfig && m_pConfig->pValues)
|
||||
return m_pConfig->pValues->internalBezier;
|
||||
|
||||
return DEFAULTBEZIERNAME;
|
||||
}
|
||||
|
||||
const std::string& CBaseAnimatedVariable::getStyle() const {
|
||||
if (m_pConfig && m_pConfig->pValues)
|
||||
return m_pConfig->pValues->internalStyle;
|
||||
|
||||
return DEFAULTSTYLE;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return 1.f;
|
||||
}
|
||||
|
||||
float CBaseAnimatedVariable::getCurveValue() const {
|
||||
if (!m_bIsBeingAnimated || isAnimationManagerDead())
|
||||
return 1.f;
|
||||
|
||||
const auto BEZIER = m_pAnimationManager->getBezier(getBezierName());
|
||||
if (!BEZIER)
|
||||
return 1.f;
|
||||
|
||||
const auto SPENT = getPercent();
|
||||
if (SPENT >= 1.f)
|
||||
return 1.f;
|
||||
|
||||
return BEZIER->getYForPoint(SPENT);
|
||||
}
|
||||
|
||||
bool CBaseAnimatedVariable::ok() const {
|
||||
return m_pConfig && !m_bDummy && !isAnimationManagerDead();
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::onUpdate() {
|
||||
if (m_bIsBeingAnimated && m_fUpdateCallback)
|
||||
m_fUpdateCallback(m_pSelf);
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::setCallbackOnEnd(CallbackFun func, bool remove) {
|
||||
m_fEndCallback = std::move(func);
|
||||
m_bRemoveEndAfterRan = remove;
|
||||
|
||||
if (!isBeingAnimated())
|
||||
onAnimationEnd();
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::setCallbackOnBegin(CallbackFun func, bool remove) {
|
||||
m_fBeginCallback = std::move(func);
|
||||
m_bRemoveBeginAfterRan = remove;
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::setUpdateCallback(CallbackFun func) {
|
||||
m_fUpdateCallback = std::move(func);
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::resetAllCallbacks() {
|
||||
m_fBeginCallback = nullptr;
|
||||
m_fEndCallback = nullptr;
|
||||
m_fUpdateCallback = nullptr;
|
||||
m_bRemoveBeginAfterRan = false;
|
||||
m_bRemoveEndAfterRan = false;
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::onAnimationEnd() {
|
||||
m_bIsBeingAnimated = false;
|
||||
/* We do not call disconnectFromActive here. The animation manager will remove it on a call to tickDone. */
|
||||
|
||||
if (m_fEndCallback) {
|
||||
CallbackFun cb = nullptr;
|
||||
m_fEndCallback.swap(cb);
|
||||
|
||||
cb(m_pSelf);
|
||||
if (!m_bRemoveEndAfterRan && /* callback did not set a new one by itself */ !m_fEndCallback)
|
||||
m_fEndCallback = cb; // restore
|
||||
}
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::onAnimationBegin() {
|
||||
m_bIsBeingAnimated = true;
|
||||
animationBegin = std::chrono::steady_clock::now();
|
||||
connectToActive();
|
||||
|
||||
if (m_fBeginCallback) {
|
||||
m_fBeginCallback(m_pSelf);
|
||||
if (m_bRemoveBeginAfterRan)
|
||||
m_fBeginCallback = nullptr; // reset
|
||||
}
|
||||
}
|
||||
|
||||
bool CBaseAnimatedVariable::isAnimationManagerDead() const {
|
||||
return m_pSignals.expired();
|
||||
}
|
||||
70
src/animation/AnimationConfig.cpp
Normal file
70
src/animation/AnimationConfig.cpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#include <hyprutils/animation/AnimationConfig.hpp>
|
||||
|
||||
using namespace Hyprutils::Animation;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
|
||||
void CAnimationConfigTree::createNode(const std::string& nodeName, const std::string& parent) {
|
||||
auto pConfig = m_mAnimationConfig[nodeName];
|
||||
if (!pConfig)
|
||||
pConfig = makeShared<SAnimationPropertyConfig>();
|
||||
|
||||
WP<SAnimationPropertyConfig> parentRef;
|
||||
if (!parent.empty() && m_mAnimationConfig.find(parent) != m_mAnimationConfig.end())
|
||||
parentRef = m_mAnimationConfig[parent];
|
||||
|
||||
*pConfig = {
|
||||
.overridden = false,
|
||||
.internalBezier = "",
|
||||
.internalStyle = "",
|
||||
.internalSpeed = 0.f,
|
||||
.internalEnabled = -1,
|
||||
.pValues = (parentRef) ? parentRef->pValues : pConfig,
|
||||
.pParentAnimation = (parentRef) ? parentRef : pConfig,
|
||||
};
|
||||
|
||||
m_mAnimationConfig[nodeName] = pConfig;
|
||||
}
|
||||
|
||||
bool CAnimationConfigTree::nodeExists(const std::string& nodeName) const {
|
||||
return m_mAnimationConfig.find(nodeName) != m_mAnimationConfig.end();
|
||||
}
|
||||
|
||||
void CAnimationConfigTree::setConfigForNode(const std::string& nodeName, int enabled, float speed, const std::string& bezier, const std::string& style) {
|
||||
auto pConfig = m_mAnimationConfig[nodeName];
|
||||
if (!pConfig)
|
||||
return;
|
||||
|
||||
*pConfig = {
|
||||
.overridden = true,
|
||||
.internalBezier = bezier,
|
||||
.internalStyle = style,
|
||||
.internalSpeed = speed,
|
||||
.internalEnabled = enabled,
|
||||
.pValues = pConfig,
|
||||
.pParentAnimation = pConfig->pParentAnimation, // keep the parent!
|
||||
};
|
||||
|
||||
setAnimForChildren(pConfig);
|
||||
}
|
||||
|
||||
SP<SAnimationPropertyConfig> CAnimationConfigTree::getConfig(const std::string& name) const {
|
||||
return m_mAnimationConfig.at(name);
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, SP<SAnimationPropertyConfig>>& CAnimationConfigTree::getFullConfig() const {
|
||||
return m_mAnimationConfig;
|
||||
}
|
||||
|
||||
void CAnimationConfigTree::setAnimForChildren(SP<SAnimationPropertyConfig> PANIM) {
|
||||
for (auto& [name, anim] : m_mAnimationConfig) {
|
||||
if (anim->pParentAnimation == PANIM && !anim->overridden) {
|
||||
// if a child isnt overridden, set the values of the parent
|
||||
anim->pValues = PANIM->pValues;
|
||||
|
||||
setAnimForChildren(anim);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
src/animation/AnimationManager.cpp
Normal file
100
src/animation/AnimationManager.cpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#include <algorithm>
|
||||
#include <hyprutils/animation/AnimationManager.hpp>
|
||||
#include <hyprutils/animation/AnimatedVariable.hpp>
|
||||
|
||||
using namespace Hyprutils::Animation;
|
||||
using namespace Hyprutils::Math;
|
||||
using namespace Hyprutils::Memory;
|
||||
using namespace Hyprutils::Signal;
|
||||
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
|
||||
const std::array<Vector2D, 2> DEFAULTBEZIERPOINTS = {Vector2D(0.0, 0.75), Vector2D(0.15, 1.0)};
|
||||
|
||||
CAnimationManager::CAnimationManager() {
|
||||
const auto BEZIER = makeShared<CBezierCurve>();
|
||||
BEZIER->setup(DEFAULTBEZIERPOINTS);
|
||||
m_mBezierCurves["default"] = BEZIER;
|
||||
|
||||
m_events = makeUnique<SAnimationManagerSignals>();
|
||||
m_listeners = makeUnique<SAnimVarListeners>();
|
||||
|
||||
m_listeners->connect = m_events->connect.listen([this](const WP<CBaseAnimatedVariable>& animVar) {
|
||||
if (!m_bTickScheduled)
|
||||
scheduleTick();
|
||||
|
||||
if (animVar)
|
||||
m_vActiveAnimatedVariables.emplace_back(animVar);
|
||||
});
|
||||
|
||||
m_listeners->disconnect = m_events->disconnect.listen([this](const WP<CBaseAnimatedVariable>& animVar) {
|
||||
if (animVar)
|
||||
std::erase_if(m_vActiveAnimatedVariables, [&](const auto& other) { return !other || other == animVar; });
|
||||
});
|
||||
}
|
||||
|
||||
void CAnimationManager::removeAllBeziers() {
|
||||
m_mBezierCurves.clear();
|
||||
|
||||
// add the default one
|
||||
const auto BEZIER = makeShared<CBezierCurve>();
|
||||
BEZIER->setup(DEFAULTBEZIERPOINTS);
|
||||
m_mBezierCurves["default"] = BEZIER;
|
||||
}
|
||||
|
||||
void CAnimationManager::addBezierWithName(std::string name, const Vector2D& p1, const Vector2D& p2) {
|
||||
const auto BEZIER = makeShared<CBezierCurve>();
|
||||
BEZIER->setup({
|
||||
p1,
|
||||
p2,
|
||||
});
|
||||
m_mBezierCurves[name] = BEZIER;
|
||||
}
|
||||
|
||||
bool CAnimationManager::shouldTickForNext() {
|
||||
return !m_vActiveAnimatedVariables.empty();
|
||||
}
|
||||
|
||||
void CAnimationManager::tickDone() {
|
||||
rotateActive();
|
||||
}
|
||||
|
||||
void CAnimationManager::rotateActive() {
|
||||
std::vector<CWeakPointer<CBaseAnimatedVariable>> active;
|
||||
active.reserve(m_vActiveAnimatedVariables.size()); // avoid reallocations
|
||||
for (auto const& av : m_vActiveAnimatedVariables) {
|
||||
if (!av)
|
||||
continue;
|
||||
|
||||
if (av->ok() && av->isBeingAnimated())
|
||||
active.emplace_back(av);
|
||||
else
|
||||
av->m_bIsConnectedToActive = false;
|
||||
}
|
||||
|
||||
m_vActiveAnimatedVariables = std::move(active);
|
||||
}
|
||||
|
||||
bool CAnimationManager::bezierExists(const std::string& bezier) {
|
||||
for (auto const& [bc, bz] : m_mBezierCurves) {
|
||||
if (bc == bezier)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SP<CBezierCurve> CAnimationManager::getBezier(const std::string& name) {
|
||||
const auto BEZIER = std::ranges::find_if(m_mBezierCurves, [&](const auto& other) { return other.first == name; });
|
||||
|
||||
return BEZIER == m_mBezierCurves.end() ? m_mBezierCurves["default"] : BEZIER->second;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, SP<CBezierCurve>>& CAnimationManager::getAllBeziers() {
|
||||
return m_mBezierCurves;
|
||||
}
|
||||
|
||||
CWeakPointer<CAnimationManager::SAnimationManagerSignals> CAnimationManager::getSignals() const {
|
||||
return m_events;
|
||||
}
|
||||
107
src/animation/BezierCurve.cpp
Normal file
107
src/animation/BezierCurve.cpp
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#include <hyprutils/animation/BezierCurve.hpp>
|
||||
#include <hyprutils/memory/Casts.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
using namespace Hyprutils::Animation;
|
||||
using namespace Hyprutils::Math;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
void CBezierCurve::setup(const std::array<Vector2D, 2>& pVec) {
|
||||
setup4(std::array<Vector2D, 4>{
|
||||
Vector2D(0, 0), // Start point
|
||||
pVec[0], pVec[1], // Control points
|
||||
Vector2D(1, 1) // End point
|
||||
});
|
||||
}
|
||||
|
||||
void CBezierCurve::setup4(const std::array<Vector2D, 4>& pVec) {
|
||||
// Avoid reallocations by reserving enough memory upfront
|
||||
m_vPoints.resize(4);
|
||||
m_vPoints = {
|
||||
pVec[0],
|
||||
pVec[1],
|
||||
pVec[2],
|
||||
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
|
||||
for (int i = 0; i < BAKEDPOINTS; ++i) {
|
||||
// When i=0 -> t=1/255
|
||||
const float t = (i + 1) * INVBAKEDPOINTS;
|
||||
m_aPointsBaked[i] = Vector2D(getXForT(t), getYForT(t));
|
||||
}
|
||||
}
|
||||
|
||||
float CBezierCurve::getXForT(float const& t) const {
|
||||
float t2 = t * t;
|
||||
float t3 = t2 * t;
|
||||
|
||||
return ((1 - t) * (1 - t) * (1 - t) * m_vPoints[0].x) + (3 * t * (1 - t) * (1 - t) * m_vPoints[1].x) + (3 * t2 * (1 - t) * m_vPoints[2].x) + (t3 * m_vPoints[3].x);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Todo: this probably can be done better and faster
|
||||
float CBezierCurve::getYForPoint(float const& x) const {
|
||||
if (x >= 1.f)
|
||||
return 1.f;
|
||||
if (x <= 0.f)
|
||||
return 0.f;
|
||||
|
||||
int index = 0;
|
||||
bool below = true;
|
||||
for (int step = (BAKEDPOINTS + 1) / 2; step > 0; step /= 2) {
|
||||
if (below)
|
||||
index += step;
|
||||
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];
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const std::vector<Hyprutils::Math::Vector2D>& CBezierCurve::getControlPoints() const {
|
||||
return m_vPoints;
|
||||
}
|
||||
298
src/cli/ArgumentParser.cpp
Normal file
298
src/cli/ArgumentParser.cpp
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
#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;
|
||||
}
|
||||
39
src/cli/ArgumentParser.hpp
Normal file
39
src/cli/ArgumentParser.hpp
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#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;
|
||||
};
|
||||
}
|
||||
199
src/cli/Logger.cpp
Normal file
199
src/cli/Logger.cpp
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
#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;
|
||||
}
|
||||
35
src/cli/Logger.hpp
Normal file
35
src/cli/Logger.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#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;
|
||||
};
|
||||
}
|
||||
165
src/i18n/I18nEngine.cpp
Normal file
165
src/i18n/I18nEngine.cpp
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
#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"); }
|
||||
}
|
||||
18
src/i18n/I18nEngine.hpp
Normal file
18
src/i18n/I18nEngine.hpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#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);
|
||||
};
|
||||
44
src/i18n/I18nLocale.cpp
Normal file
44
src/i18n/I18nLocale.cpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#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;
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ CBox& Hyprutils::Math::CBox::translate(const Vector2D& vec) {
|
|||
}
|
||||
|
||||
Vector2D Hyprutils::Math::CBox::middle() const {
|
||||
return Vector2D{x + w * HALF, y + h * HALF};
|
||||
return Vector2D{x + (w * HALF), y + (h * HALF)};
|
||||
}
|
||||
|
||||
bool Hyprutils::Math::CBox::containsPoint(const Vector2D& vec) const {
|
||||
|
|
@ -199,6 +199,10 @@ Vector2D Hyprutils::Math::CBox::size() const {
|
|||
return {w, h};
|
||||
}
|
||||
|
||||
Vector2D Hyprutils::Math::CBox::extent() const {
|
||||
return pos() + size();
|
||||
}
|
||||
|
||||
Vector2D Hyprutils::Math::CBox::closestPoint(const Vector2D& vec) const {
|
||||
if (containsPoint(vec))
|
||||
return vec;
|
||||
|
|
@ -229,5 +233,5 @@ Vector2D Hyprutils::Math::CBox::closestPoint(const Vector2D& vec) const {
|
|||
}
|
||||
|
||||
SBoxExtents Hyprutils::Math::CBox::extentsFrom(const CBox& small) {
|
||||
return {{small.x - x, small.y - y}, {w - small.w - (small.x - x), h - small.h - (small.y - y)}};
|
||||
return {.topLeft = {small.x - x, small.y - y}, .bottomRight = {w - small.w - (small.x - x), h - small.h - (small.y - y)}};
|
||||
}
|
||||
|
|
|
|||
155
src/math/Mat3x3.cpp
Normal file
155
src/math/Mat3x3.cpp
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
#include <hyprutils/math/Mat3x3.hpp>
|
||||
#include <hyprutils/math/Vector2D.hpp>
|
||||
#include <hyprutils/math/Box.hpp>
|
||||
#include <hyprutils/memory/Casts.hpp>
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
#include <format>
|
||||
|
||||
using namespace Hyprutils::Math;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
static std::unordered_map<eTransform, Mat3x3> transforms = {
|
||||
{HYPRUTILS_TRANSFORM_NORMAL, std::array<float, 9>{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
|
||||
{HYPRUTILS_TRANSFORM_90, std::array<float, 9>{0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
|
||||
{HYPRUTILS_TRANSFORM_180, std::array<float, 9>{-1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
|
||||
{HYPRUTILS_TRANSFORM_270, std::array<float, 9>{0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
|
||||
{HYPRUTILS_TRANSFORM_FLIPPED, std::array<float, 9>{-1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
|
||||
{HYPRUTILS_TRANSFORM_FLIPPED_90, std::array<float, 9>{0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
|
||||
{HYPRUTILS_TRANSFORM_FLIPPED_180, std::array<float, 9>{1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
|
||||
{HYPRUTILS_TRANSFORM_FLIPPED_270, std::array<float, 9>{0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}},
|
||||
};
|
||||
|
||||
Mat3x3::Mat3x3() {
|
||||
matrix = {0};
|
||||
}
|
||||
|
||||
Mat3x3::Mat3x3(std::array<float, 9> mat) : matrix(mat) {
|
||||
;
|
||||
}
|
||||
|
||||
Mat3x3::Mat3x3(std::vector<float> mat) {
|
||||
for (size_t i = 0; i < 9; ++i) {
|
||||
matrix.at(i) = mat.size() < i ? mat.at(i) : 0.F;
|
||||
}
|
||||
}
|
||||
|
||||
Mat3x3 Mat3x3::identity() {
|
||||
return Mat3x3(std::array<float, 9>{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f});
|
||||
}
|
||||
|
||||
Mat3x3 Mat3x3::outputProjection(const Vector2D& size, eTransform transform) {
|
||||
Mat3x3 mat;
|
||||
|
||||
const auto& t = transforms.at(transform);
|
||||
float x = 2.0f / size.x;
|
||||
float y = 2.0f / size.y;
|
||||
|
||||
// Rotation + reflection
|
||||
mat.matrix[0] = x * t.matrix[0];
|
||||
mat.matrix[1] = x * t.matrix[1];
|
||||
mat.matrix[3] = y * t.matrix[3];
|
||||
mat.matrix[4] = y * t.matrix[4];
|
||||
|
||||
// Translation
|
||||
mat.matrix[2] = -copysign(1.0f, mat.matrix[0] + mat.matrix[1]);
|
||||
mat.matrix[5] = -copysign(1.0f, mat.matrix[3] + mat.matrix[4]);
|
||||
|
||||
// Identity
|
||||
mat.matrix[8] = 1.0f;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
std::array<float, 9> Mat3x3::getMatrix() const {
|
||||
return matrix;
|
||||
}
|
||||
|
||||
Mat3x3 Mat3x3::projectBox(const CBox& box, eTransform transform, float rot /* rad, CCW */) const {
|
||||
Mat3x3 mat = Mat3x3::identity();
|
||||
|
||||
const auto boxSize = box.size();
|
||||
|
||||
mat.translate(box.pos());
|
||||
|
||||
if (rot != 0) {
|
||||
mat.translate(boxSize / 2);
|
||||
mat.rotate(rot);
|
||||
mat.translate(-boxSize / 2);
|
||||
}
|
||||
|
||||
mat.scale(boxSize);
|
||||
|
||||
if (transform != HYPRUTILS_TRANSFORM_NORMAL) {
|
||||
mat.translate({0.5, 0.5});
|
||||
mat.transform(transform);
|
||||
mat.translate({-0.5, -0.5});
|
||||
}
|
||||
|
||||
return this->copy().multiply(mat);
|
||||
}
|
||||
|
||||
Mat3x3& Mat3x3::transform(eTransform transform) {
|
||||
multiply(transforms.at(transform));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Mat3x3& Mat3x3::rotate(float rot) {
|
||||
multiply(std::array<float, 9>{cosf(rot), -sinf(rot), 0.0f, sinf(rot), cosf(rot), 0.0f, 0.0f, 0.0f, 1.0f});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Mat3x3& Mat3x3::scale(const Vector2D& scale_) {
|
||||
multiply(std::array<float, 9>{sc<float>(scale_.x), 0.0f, 0.0f, 0.0f, sc<float>(scale_.y), 0.0f, 0.0f, 0.0f, 1.0f});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Mat3x3& Mat3x3::scale(const float scale_) {
|
||||
return scale({scale_, scale_});
|
||||
}
|
||||
|
||||
Mat3x3& Mat3x3::translate(const Vector2D& offset) {
|
||||
multiply(std::array<float, 9>{1.0f, 0.0f, sc<float>(offset.x), 0.0f, 1.0f, sc<float>(offset.y), 0.0f, 0.0f, 1.0f});
|
||||
return *this;
|
||||
}
|
||||
|
||||
Mat3x3& Mat3x3::transpose() {
|
||||
matrix = std::array<float, 9>{matrix[0], matrix[3], matrix[6], matrix[1], matrix[4], matrix[7], matrix[2], matrix[5], matrix[8]};
|
||||
return *this;
|
||||
}
|
||||
|
||||
Mat3x3& Mat3x3::multiply(const Mat3x3& other) {
|
||||
const float* m1 = matrix.data(); // Pointer to current matrix
|
||||
const float* m2 = other.matrix.data(); // Pointer to the other matrix
|
||||
|
||||
std::array<float, 9> product;
|
||||
|
||||
product[0] = m1[0] * m2[0] + m1[1] * m2[3] + m1[2] * m2[6];
|
||||
product[1] = m1[0] * m2[1] + m1[1] * m2[4] + m1[2] * m2[7];
|
||||
product[2] = m1[0] * m2[2] + m1[1] * m2[5] + m1[2] * m2[8];
|
||||
|
||||
product[3] = m1[3] * m2[0] + m1[4] * m2[3] + m1[5] * m2[6];
|
||||
product[4] = m1[3] * m2[1] + m1[4] * m2[4] + m1[5] * m2[7];
|
||||
product[5] = m1[3] * m2[2] + m1[4] * m2[5] + m1[5] * m2[8];
|
||||
|
||||
product[6] = m1[6] * m2[0] + m1[7] * m2[3] + m1[8] * m2[6];
|
||||
product[7] = m1[6] * m2[1] + m1[7] * m2[4] + m1[8] * m2[7];
|
||||
product[8] = m1[6] * m2[2] + m1[7] * m2[5] + m1[8] * m2[8];
|
||||
|
||||
matrix = product;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Mat3x3 Mat3x3::copy() const {
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string Mat3x3::toString() const {
|
||||
for (const auto& m : matrix) {
|
||||
if (!std::isfinite(m))
|
||||
return "[mat3x3: invalid values]";
|
||||
}
|
||||
|
||||
return std::format("[mat3x3: {}, {}, {}, {}, {}, {}, {}, {}, {}]", matrix.at(0), matrix.at(1), matrix.at(2), matrix.at(3), matrix.at(4), matrix.at(5), matrix.at(6),
|
||||
matrix.at(7), matrix.at(8));
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
#include "hyprutils/memory/Casts.hpp"
|
||||
#include <hyprutils/math/Region.hpp>
|
||||
#include <cmath>
|
||||
|
||||
using namespace Hyprutils::Math;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
constexpr const int64_t MAX_REGION_SIDE = 10000000;
|
||||
|
||||
|
|
@ -28,10 +30,10 @@ Hyprutils::Math::CRegion::CRegion(pixman_box32_t* box) {
|
|||
|
||||
Hyprutils::Math::CRegion::CRegion(const CRegion& other) {
|
||||
pixman_region32_init(&m_rRegion);
|
||||
pixman_region32_copy(&m_rRegion, const_cast<CRegion*>(&other)->pixman());
|
||||
pixman_region32_copy(&m_rRegion, other.pixman());
|
||||
}
|
||||
|
||||
Hyprutils::Math::CRegion::CRegion(CRegion&& other) {
|
||||
Hyprutils::Math::CRegion::CRegion(CRegion&& other) noexcept {
|
||||
pixman_region32_init(&m_rRegion);
|
||||
pixman_region32_copy(&m_rRegion, other.pixman());
|
||||
}
|
||||
|
|
@ -46,12 +48,12 @@ CRegion& Hyprutils::Math::CRegion::clear() {
|
|||
}
|
||||
|
||||
CRegion& Hyprutils::Math::CRegion::set(const CRegion& other) {
|
||||
pixman_region32_copy(&m_rRegion, const_cast<CRegion*>(&other)->pixman());
|
||||
pixman_region32_copy(&m_rRegion, other.pixman());
|
||||
return *this;
|
||||
}
|
||||
|
||||
CRegion& Hyprutils::Math::CRegion::add(const CRegion& other) {
|
||||
pixman_region32_union(&m_rRegion, &m_rRegion, const_cast<CRegion*>(&other)->pixman());
|
||||
pixman_region32_union(&m_rRegion, &m_rRegion, other.pixman());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
@ -66,12 +68,12 @@ CRegion& Hyprutils::Math::CRegion::add(const CBox& other) {
|
|||
}
|
||||
|
||||
CRegion& Hyprutils::Math::CRegion::subtract(const CRegion& other) {
|
||||
pixman_region32_subtract(&m_rRegion, &m_rRegion, const_cast<CRegion*>(&other)->pixman());
|
||||
pixman_region32_subtract(&m_rRegion, &m_rRegion, other.pixman());
|
||||
return *this;
|
||||
}
|
||||
|
||||
CRegion& Hyprutils::Math::CRegion::intersect(const CRegion& other) {
|
||||
pixman_region32_intersect(&m_rRegion, &m_rRegion, const_cast<CRegion*>(&other)->pixman());
|
||||
pixman_region32_intersect(&m_rRegion, &m_rRegion, other.pixman());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +88,7 @@ CRegion& Hyprutils::Math::CRegion::invert(pixman_box32_t* box) {
|
|||
}
|
||||
|
||||
CRegion& Hyprutils::Math::CRegion::invert(const CBox& box) {
|
||||
pixman_box32 pixmanBox = {(int32_t)box.x, (int32_t)box.y, (int32_t)box.w + (int32_t)box.x, (int32_t)box.h + (int32_t)box.y};
|
||||
pixman_box32 pixmanBox = {.x1 = sc<int32_t>(box.x), .y1 = sc<int32_t>(box.y), .x2 = sc<int32_t>(box.w) + sc<int32_t>(box.x), .y2 = sc<int32_t>(box.h) + sc<int32_t>(box.y)};
|
||||
return this->invert(&pixmanBox);
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +106,7 @@ CRegion& Hyprutils::Math::CRegion::transform(const eTransform t, double w, doubl
|
|||
clear();
|
||||
|
||||
for (auto& r : rects) {
|
||||
CBox xfmd{(double)r.x1, (double)r.y1, (double)r.x2 - r.x1, (double)r.y2 - r.y1};
|
||||
CBox xfmd{r.x1, r.y1, r.x2 - r.x1, r.y2 - r.y1};
|
||||
xfmd.transform(t, w, h);
|
||||
add(xfmd);
|
||||
}
|
||||
|
|
@ -118,7 +120,7 @@ CRegion& Hyprutils::Math::CRegion::expand(double units) {
|
|||
clear();
|
||||
|
||||
for (auto& r : rects) {
|
||||
CBox b{(double)r.x1 - units, (double)r.y1 - units, (double)r.x2 - r.x1 + units * 2, (double)r.y2 - r.y1 + units * 2};
|
||||
CBox b{sc<double>(r.x1) - units, sc<double>(r.y1) - units, sc<double>(r.x2) - r.x1 + (units * 2), sc<double>(r.y2) - r.y1 + (units * 2)};
|
||||
add(b);
|
||||
}
|
||||
|
||||
|
|
@ -143,18 +145,21 @@ CRegion& Hyprutils::Math::CRegion::scale(const Vector2D& scale) {
|
|||
if (scale == Vector2D{1, 1})
|
||||
return *this;
|
||||
|
||||
auto rects = getRects();
|
||||
int rectsNum = 0;
|
||||
auto RECTSARR = pixman_region32_rectangles(&m_rRegion, &rectsNum);
|
||||
|
||||
clear();
|
||||
std::vector<pixman_box32_t> boxes;
|
||||
boxes.resize(rectsNum);
|
||||
|
||||
for (auto& r : rects) {
|
||||
r.x1 = std::floor(r.x1 * scale.x);
|
||||
r.y1 = std::floor(r.y1 * scale.x);
|
||||
r.x2 = std::ceil(r.x2 * scale.x);
|
||||
r.y2 = std::ceil(r.y2 * scale.x);
|
||||
add(&r);
|
||||
for (int i = 0; i < rectsNum; ++i) {
|
||||
boxes[i].x1 = std::floor(RECTSARR[i].x1 * scale.x);
|
||||
boxes[i].x2 = std::ceil(RECTSARR[i].x2 * scale.x);
|
||||
boxes[i].y1 = std::floor(RECTSARR[i].y1 * scale.y);
|
||||
boxes[i].y2 = std::ceil(RECTSARR[i].y2 * scale.y);
|
||||
}
|
||||
|
||||
pixman_region32_fini(&m_rRegion);
|
||||
pixman_region32_init_rects(&m_rRegion, boxes.data(), boxes.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
@ -171,7 +176,7 @@ std::vector<pixman_box32_t> Hyprutils::Math::CRegion::getRects() const {
|
|||
|
||||
CBox Hyprutils::Math::CRegion::getExtents() {
|
||||
pixman_box32_t* box = pixman_region32_extents(&m_rRegion);
|
||||
return {(double)box->x1, (double)box->y1, (double)box->x2 - box->x1, (double)box->y2 - box->y1};
|
||||
return {sc<double>(box->x1), sc<double>(box->y1), sc<double>(box->x2) - box->x1, sc<double>(box->y2) - box->y1};
|
||||
}
|
||||
|
||||
bool Hyprutils::Math::CRegion::containsPoint(const Vector2D& vec) const {
|
||||
|
|
@ -214,4 +219,4 @@ Vector2D Hyprutils::Math::CRegion::closestPoint(const Vector2D& vec) const {
|
|||
}
|
||||
|
||||
return leader;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,11 @@
|
|||
#include <hyprutils/math/Vector2D.hpp>
|
||||
#include <hyprutils/memory/Casts.hpp>
|
||||
#include <hyprutils/math/Misc.hpp>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
using namespace Hyprutils::Math;
|
||||
|
||||
Hyprutils::Math::Vector2D::Vector2D(double xx, double yy) {
|
||||
x = xx;
|
||||
y = yy;
|
||||
}
|
||||
|
||||
Hyprutils::Math::Vector2D::Vector2D(int xx, int yy) {
|
||||
x = (double)xx;
|
||||
y = (double)yy;
|
||||
}
|
||||
|
||||
Hyprutils::Math::Vector2D::Vector2D() {
|
||||
x = 0;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
Hyprutils::Math::Vector2D::~Vector2D() {}
|
||||
|
||||
double Hyprutils::Math::Vector2D::normalize() {
|
||||
// get max abs
|
||||
const auto max = std::abs(x) > std::abs(y) ? std::abs(x) : std::abs(y);
|
||||
|
|
@ -48,13 +33,27 @@ double Hyprutils::Math::Vector2D::distance(const Vector2D& other) const {
|
|||
}
|
||||
|
||||
double Hyprutils::Math::Vector2D::distanceSq(const Vector2D& other) const {
|
||||
return (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y);
|
||||
return ((x - other.x) * (x - other.x)) + ((y - other.y) * (y - other.y));
|
||||
}
|
||||
|
||||
double Hyprutils::Math::Vector2D::size() const {
|
||||
return std::sqrt(x * x + y * y);
|
||||
return std::sqrt((x * x) + (y * y));
|
||||
}
|
||||
|
||||
Vector2D Hyprutils::Math::Vector2D::getComponentMax(const Vector2D& other) const {
|
||||
return Vector2D(std::max(this->x, other.x), std::max(this->y, other.y));
|
||||
}
|
||||
|
||||
Vector2D Hyprutils::Math::Vector2D::transform(eTransform transform, const Vector2D& monitorSize) const {
|
||||
switch (transform) {
|
||||
case HYPRUTILS_TRANSFORM_NORMAL: return *this;
|
||||
case HYPRUTILS_TRANSFORM_90: return Vector2D(y, monitorSize.y - x);
|
||||
case HYPRUTILS_TRANSFORM_180: return Vector2D(monitorSize.x - x, monitorSize.y - y);
|
||||
case HYPRUTILS_TRANSFORM_270: return Vector2D(monitorSize.x - y, x);
|
||||
case HYPRUTILS_TRANSFORM_FLIPPED: return Vector2D(monitorSize.x - x, y);
|
||||
case HYPRUTILS_TRANSFORM_FLIPPED_90: return Vector2D(y, x);
|
||||
case HYPRUTILS_TRANSFORM_FLIPPED_180: return Vector2D(x, monitorSize.y - y);
|
||||
case HYPRUTILS_TRANSFORM_FLIPPED_270: return Vector2D(monitorSize.x - y, monitorSize.y - x);
|
||||
default: return *this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
src/os/File.cpp
Normal file
20
src/os/File.cpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#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>()));
|
||||
}
|
||||
86
src/os/FileDescriptor.cpp
Normal file
86
src/os/FileDescriptor.cpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#include <cstdlib>
|
||||
#include <hyprutils/os/FileDescriptor.hpp>
|
||||
#include <fcntl.h>
|
||||
#include <sys/poll.h>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
|
||||
CFileDescriptor::CFileDescriptor(int const fd) : m_fd(fd) {}
|
||||
|
||||
CFileDescriptor::CFileDescriptor(CFileDescriptor&& other) : m_fd(std::exchange(other.m_fd, -1)) {}
|
||||
|
||||
CFileDescriptor& CFileDescriptor::operator=(CFileDescriptor&& other) {
|
||||
if (this == &other) // Shit will go haywire if there is duplicate ownership
|
||||
abort();
|
||||
|
||||
reset();
|
||||
m_fd = std::exchange(other.m_fd, -1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CFileDescriptor::~CFileDescriptor() {
|
||||
reset();
|
||||
}
|
||||
|
||||
bool CFileDescriptor::isValid() const {
|
||||
return m_fd != -1;
|
||||
}
|
||||
|
||||
int CFileDescriptor::get() const {
|
||||
return m_fd;
|
||||
}
|
||||
|
||||
int CFileDescriptor::getFlags() const {
|
||||
return fcntl(m_fd, F_GETFD);
|
||||
}
|
||||
|
||||
bool CFileDescriptor::setFlags(int flags) {
|
||||
return fcntl(m_fd, F_SETFD, flags) != -1;
|
||||
}
|
||||
|
||||
int CFileDescriptor::take() {
|
||||
return std::exchange(m_fd, -1);
|
||||
}
|
||||
|
||||
void CFileDescriptor::reset() {
|
||||
if (m_fd != -1) {
|
||||
close(m_fd);
|
||||
m_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
CFileDescriptor CFileDescriptor::duplicate(int flags) const {
|
||||
if (m_fd == -1)
|
||||
return {};
|
||||
|
||||
return CFileDescriptor{fcntl(m_fd, flags, 0)};
|
||||
}
|
||||
|
||||
bool CFileDescriptor::isClosed() const {
|
||||
return isClosed(m_fd);
|
||||
}
|
||||
|
||||
bool CFileDescriptor::isReadable() const {
|
||||
return isReadable(m_fd);
|
||||
}
|
||||
|
||||
bool CFileDescriptor::isClosed(int fd) {
|
||||
pollfd pfd = {
|
||||
.fd = fd,
|
||||
.events = POLLIN,
|
||||
.revents = 0,
|
||||
};
|
||||
|
||||
if (poll(&pfd, 1, 0) < 0)
|
||||
return true;
|
||||
|
||||
return pfd.revents & (POLLHUP | POLLERR);
|
||||
}
|
||||
|
||||
bool CFileDescriptor::isReadable(int fd) {
|
||||
pollfd pfd = {.fd = fd, .events = POLLIN, .revents = 0};
|
||||
|
||||
return poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN);
|
||||
}
|
||||
285
src/os/Process.cpp
Normal file
285
src/os/Process.cpp
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/memory/Casts.hpp>
|
||||
using namespace Hyprutils::OS;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#include <array>
|
||||
#include <thread>
|
||||
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/poll.h>
|
||||
|
||||
struct Hyprutils::OS::CProcess::impl {
|
||||
std::string binary, out, err;
|
||||
std::vector<std::string> args;
|
||||
std::vector<std::pair<std::string, std::string>> env;
|
||||
pid_t grandchildPid = 0;
|
||||
int stdoutFD = -1, stderrFD = -1, exitCode = 0, stdinFD = -1;
|
||||
};
|
||||
|
||||
Hyprutils::OS::CProcess::CProcess(const std::string& binary, const std::vector<std::string>& args) : m_impl(new impl()) {
|
||||
m_impl->binary = binary;
|
||||
m_impl->args = args;
|
||||
}
|
||||
|
||||
Hyprutils::OS::CProcess::~CProcess() {
|
||||
delete m_impl;
|
||||
}
|
||||
|
||||
void Hyprutils::OS::CProcess::addEnv(const std::string& name, const std::string& value) {
|
||||
m_impl->env.emplace_back(std::make_pair<>(name, value));
|
||||
}
|
||||
|
||||
bool Hyprutils::OS::CProcess::runSync() {
|
||||
int outPipe[2], errPipe[2];
|
||||
if (pipe(outPipe))
|
||||
return false;
|
||||
if (pipe(errPipe)) {
|
||||
close(outPipe[0]);
|
||||
close(outPipe[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
int pid = fork();
|
||||
if (pid == -1) {
|
||||
close(outPipe[0]);
|
||||
close(outPipe[1]);
|
||||
close(outPipe[0]);
|
||||
close(outPipe[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pid) {
|
||||
// child
|
||||
close(outPipe[0]);
|
||||
close(errPipe[0]);
|
||||
|
||||
dup2(outPipe[1], 1 /* stdout */);
|
||||
dup2(errPipe[1], 2 /* stderr */);
|
||||
|
||||
// build argv
|
||||
std::vector<char*> argsC;
|
||||
argsC.emplace_back(strdup(m_impl->binary.c_str()));
|
||||
for (auto& arg : m_impl->args) {
|
||||
// TODO: does this leak? Can we just pipe c_str() as the strings won't be realloc'd?
|
||||
argsC.emplace_back(strdup(arg.c_str()));
|
||||
}
|
||||
|
||||
argsC.emplace_back(nullptr);
|
||||
|
||||
// pass env
|
||||
for (auto& [n, v] : m_impl->env) {
|
||||
setenv(n.c_str(), v.c_str(), 1);
|
||||
}
|
||||
|
||||
execvp(m_impl->binary.c_str(), argsC.data());
|
||||
exit(1);
|
||||
} else {
|
||||
// parent
|
||||
close(outPipe[1]);
|
||||
close(errPipe[1]);
|
||||
|
||||
m_impl->out = "";
|
||||
m_impl->err = "";
|
||||
|
||||
m_impl->grandchildPid = pid;
|
||||
|
||||
std::array<char, 1024> buf;
|
||||
buf.fill(0);
|
||||
|
||||
// wait for read
|
||||
ssize_t ret = 0;
|
||||
|
||||
int fdFlags = fcntl(outPipe[0], F_GETFL, 0);
|
||||
if (fcntl(outPipe[0], F_SETFL, fdFlags | O_NONBLOCK) < 0)
|
||||
return false;
|
||||
fdFlags = fcntl(errPipe[0], F_GETFL, 0);
|
||||
if (fcntl(errPipe[0], F_SETFL, fdFlags | O_NONBLOCK) < 0)
|
||||
return false;
|
||||
|
||||
pollfd pollfds[2] = {
|
||||
{.fd = outPipe[0], .events = POLLIN, .revents = 0},
|
||||
{.fd = errPipe[0], .events = POLLIN, .revents = 0},
|
||||
};
|
||||
|
||||
while (1337) {
|
||||
int ret = poll(pollfds, 2, 5000);
|
||||
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hupd = false;
|
||||
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
if (pollfds[i].revents & POLLHUP) {
|
||||
hupd = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hupd)
|
||||
break;
|
||||
|
||||
if (pollfds[0].revents & POLLIN) {
|
||||
while ((ret = read(outPipe[0], buf.data(), 1023)) > 0) {
|
||||
m_impl->out += std::string_view{buf.data(), sc<size_t>(ret)};
|
||||
}
|
||||
|
||||
buf.fill(0);
|
||||
}
|
||||
|
||||
if (pollfds[1].revents & POLLIN) {
|
||||
while ((ret = read(errPipe[0], buf.data(), 1023)) > 0) {
|
||||
m_impl->err += std::string_view{buf.data(), sc<size_t>(ret)};
|
||||
}
|
||||
|
||||
buf.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Final reads. Nonblock, so its ok.
|
||||
while ((ret = read(outPipe[0], buf.data(), 1023)) > 0) {
|
||||
m_impl->out += std::string_view{buf.data(), sc<size_t>(ret)};
|
||||
}
|
||||
|
||||
buf.fill(0);
|
||||
|
||||
while ((ret = read(errPipe[0], buf.data(), 1023)) > 0) {
|
||||
m_impl->err += std::string_view{buf.data(), sc<size_t>(ret)};
|
||||
}
|
||||
|
||||
buf.fill(0);
|
||||
|
||||
close(outPipe[0]);
|
||||
close(errPipe[0]);
|
||||
|
||||
// reap child
|
||||
int status = 0;
|
||||
waitpid(pid, &status, 0);
|
||||
|
||||
if (WIFEXITED(status))
|
||||
m_impl->exitCode = WEXITSTATUS(status);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Hyprutils::OS::CProcess::runAsync() {
|
||||
int socket[2];
|
||||
if (pipe(socket) != 0)
|
||||
return false;
|
||||
|
||||
pid_t child, grandchild;
|
||||
child = fork();
|
||||
if (child < 0) {
|
||||
close(socket[0]);
|
||||
close(socket[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (child == 0) {
|
||||
// run in child
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
sigprocmask(SIG_SETMASK, &set, nullptr);
|
||||
|
||||
grandchild = fork();
|
||||
if (grandchild == 0) {
|
||||
// run in grandchild
|
||||
close(socket[0]);
|
||||
close(socket[1]);
|
||||
// build argv
|
||||
std::vector<char*> argsC;
|
||||
argsC.emplace_back(strdup(m_impl->binary.c_str()));
|
||||
for (auto& arg : m_impl->args) {
|
||||
argsC.emplace_back(strdup(arg.c_str()));
|
||||
}
|
||||
|
||||
argsC.emplace_back(nullptr);
|
||||
|
||||
// pass env
|
||||
for (auto& [n, v] : m_impl->env) {
|
||||
setenv(n.c_str(), v.c_str(), 1);
|
||||
}
|
||||
|
||||
if (m_impl->stdinFD != -1) {
|
||||
dup2(m_impl->stdinFD, STDIN_FILENO);
|
||||
close(m_impl->stdinFD);
|
||||
}
|
||||
if (m_impl->stdoutFD != -1) {
|
||||
dup2(m_impl->stdoutFD, STDOUT_FILENO);
|
||||
close(m_impl->stdoutFD);
|
||||
}
|
||||
if (m_impl->stderrFD != -1) {
|
||||
dup2(m_impl->stderrFD, STDERR_FILENO);
|
||||
close(m_impl->stderrFD);
|
||||
}
|
||||
|
||||
execvp(m_impl->binary.c_str(), argsC.data());
|
||||
_exit(0);
|
||||
}
|
||||
close(socket[0]);
|
||||
if (write(socket[1], &grandchild, sizeof(grandchild)) != sizeof(grandchild)) {
|
||||
close(socket[1]);
|
||||
_exit(1);
|
||||
}
|
||||
close(socket[1]);
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
// run in parent
|
||||
close(socket[1]);
|
||||
ssize_t bytesRead = read(socket[0], &grandchild, sizeof(grandchild));
|
||||
close(socket[0]);
|
||||
|
||||
if (bytesRead != sizeof(grandchild)) {
|
||||
waitpid(child, nullptr, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
// clear child and leave grandchild to init
|
||||
waitpid(child, nullptr, 0);
|
||||
|
||||
m_impl->grandchildPid = grandchild;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string& Hyprutils::OS::CProcess::stdOut() {
|
||||
return m_impl->out;
|
||||
}
|
||||
|
||||
const std::string& Hyprutils::OS::CProcess::stdErr() {
|
||||
return m_impl->err;
|
||||
}
|
||||
|
||||
pid_t Hyprutils::OS::CProcess::pid() {
|
||||
return m_impl->grandchildPid;
|
||||
}
|
||||
|
||||
int Hyprutils::OS::CProcess::exitCode() {
|
||||
return m_impl->exitCode;
|
||||
}
|
||||
|
||||
void Hyprutils::OS::CProcess::setStdinFD(int fd) {
|
||||
m_impl->stdinFD = fd;
|
||||
}
|
||||
|
||||
void Hyprutils::OS::CProcess::setStdoutFD(int fd) {
|
||||
m_impl->stdoutFD = fd;
|
||||
}
|
||||
|
||||
void Hyprutils::OS::CProcess::setStderrFD(int fd) {
|
||||
m_impl->stderrFD = fd;
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ namespace Hyprutils::Path {
|
|||
|
||||
static const auto xdgConfigDirs = getXdgConfigDirs();
|
||||
if (xdgConfigDirs.has_value()) {
|
||||
for (auto dir : xdgConfigDirs.value()) {
|
||||
for (auto& dir : xdgConfigDirs.value()) {
|
||||
if (checkConfigExists(dir, programName))
|
||||
return std::make_pair(fullConfigPath(dir, programName), std::nullopt);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,20 @@
|
|||
#include <hyprutils/signal/Listener.hpp>
|
||||
#include <tuple>
|
||||
|
||||
using namespace Hyprutils::Signal;
|
||||
|
||||
Hyprutils::Signal::CSignalListener::CSignalListener(std::function<void(std::any)> handler) : m_fHandler(handler) {
|
||||
Hyprutils::Signal::CSignalListener::CSignalListener(std::function<void(void*)> handler) : m_fHandler(handler) {
|
||||
;
|
||||
}
|
||||
|
||||
void Hyprutils::Signal::CSignalListener::emit(std::any data) {
|
||||
void Hyprutils::Signal::CSignalListener::emitInternal(void* data) {
|
||||
if (!m_fHandler)
|
||||
return;
|
||||
|
||||
m_fHandler(data);
|
||||
}
|
||||
|
||||
Hyprutils::Signal::CStaticSignalListener::CStaticSignalListener(std::function<void(void*, std::any)> handler, void* owner) : m_pOwner(owner), m_fHandler(handler) {
|
||||
;
|
||||
}
|
||||
|
||||
void Hyprutils::Signal::CStaticSignalListener::emit(std::any data) {
|
||||
m_fHandler(m_pOwner, data);
|
||||
void Hyprutils::Signal::CSignalListener::emit(std::any data) {
|
||||
auto dataTuple = std::tuple<std::any>(data);
|
||||
emitInternal(&dataTuple);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#include "hyprutils/memory/SharedPtr.hpp"
|
||||
#include <hyprutils/signal/Signal.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <algorithm>
|
||||
|
|
@ -8,8 +9,9 @@ using namespace Hyprutils::Memory;
|
|||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
|
||||
void Hyprutils::Signal::CSignal::emit(std::any data) {
|
||||
void Hyprutils::Signal::CSignalBase::emitInternal(void* args) {
|
||||
std::vector<SP<CSignalListener>> listeners;
|
||||
listeners.reserve(m_vListeners.size());
|
||||
for (auto& l : m_vListeners) {
|
||||
if (l.expired())
|
||||
continue;
|
||||
|
|
@ -17,41 +19,42 @@ void Hyprutils::Signal::CSignal::emit(std::any data) {
|
|||
listeners.emplace_back(l.lock());
|
||||
}
|
||||
|
||||
std::vector<CStaticSignalListener*> statics;
|
||||
for (auto& l : m_vStaticListeners) {
|
||||
statics.emplace_back(l.get());
|
||||
}
|
||||
auto statics = m_vStaticListeners;
|
||||
|
||||
for (auto& l : listeners) {
|
||||
// if there is only one lock, it means the event is only held by the listeners
|
||||
// vector and was removed during our iteration
|
||||
if (l.strongRef() == 1)
|
||||
continue;
|
||||
|
||||
l->emit(data);
|
||||
|
||||
l->emitInternal(args);
|
||||
}
|
||||
|
||||
for (auto& l : statics) {
|
||||
l->emit(data);
|
||||
l->emitInternal(args);
|
||||
}
|
||||
|
||||
// release SPs
|
||||
listeners.clear();
|
||||
|
||||
// we cannot release any expired refs here as one of the listeners could've removed this object and
|
||||
// we cannot release any expired refs here as one of the listeners could've removed this object and
|
||||
// as such we'd be doing a UAF
|
||||
}
|
||||
|
||||
CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function<void(std::any)> handler) {
|
||||
CHyprSignalListener listener = makeShared<CSignalListener>(handler);
|
||||
CHyprSignalListener Hyprutils::Signal::CSignalBase::registerListenerInternal(std::function<void(void*)> handler) {
|
||||
CHyprSignalListener listener = SP<CSignalListener>(new CSignalListener(handler));
|
||||
m_vListeners.emplace_back(listener);
|
||||
|
||||
// housekeeping: remove any stale listeners
|
||||
std::erase_if(m_vListeners, [](const auto& other) { return other.expired(); });
|
||||
|
||||
|
||||
return listener;
|
||||
}
|
||||
|
||||
void Hyprutils::Signal::CSignal::registerStaticListener(std::function<void(void*, std::any)> handler, void* owner) {
|
||||
m_vStaticListeners.emplace_back(std::make_unique<CStaticSignalListener>(handler, owner));
|
||||
}
|
||||
void Hyprutils::Signal::CSignalBase::registerStaticListenerInternal(std::function<void(void*)> handler) {
|
||||
m_vStaticListeners.emplace_back(SP<CSignalListener>(new CSignalListener(handler)));
|
||||
}
|
||||
|
||||
void Hyprutils::Signal::CSignal::emit(std::any data) {
|
||||
CSignalT::emit(data);
|
||||
}
|
||||
|
|
|
|||
38
src/string/ConstVarList.cpp
Normal file
38
src/string/ConstVarList.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#include <ranges>
|
||||
#include <algorithm>
|
||||
#include <hyprutils/string/String.hpp>
|
||||
#include <hyprutils/string/ConstVarList.hpp>
|
||||
|
||||
using namespace Hyprutils::String;
|
||||
|
||||
CConstVarList::CConstVarList(const std::string& in, const size_t lastArgNo, const char delim, const bool removeEmpty) : m_str(in) {
|
||||
if (in.empty())
|
||||
return;
|
||||
|
||||
size_t idx = 0;
|
||||
size_t pos = 0;
|
||||
std::ranges::replace_if(m_str, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0);
|
||||
|
||||
for (const auto& s : m_str | std::views::split(0)) {
|
||||
if (removeEmpty && s.empty())
|
||||
continue;
|
||||
if (++idx == lastArgNo) {
|
||||
m_args.emplace_back(trim(std::string_view{m_str}.substr(pos)));
|
||||
break;
|
||||
}
|
||||
pos += s.size() + 1;
|
||||
m_args.emplace_back(trim(std::string_view{s.data()}));
|
||||
}
|
||||
}
|
||||
|
||||
std::string CConstVarList::join(const std::string& joiner, size_t from, size_t to) const {
|
||||
size_t last = to == 0 ? size() : to;
|
||||
|
||||
std::string rolling;
|
||||
for (size_t i = from; i < last; ++i) {
|
||||
// cast can be removed once C++26's change to allow this is supported
|
||||
rolling += std::string{m_args[i]} + (i + 1 < last ? joiner : "");
|
||||
}
|
||||
|
||||
return rolling;
|
||||
}
|
||||
|
|
@ -7,12 +7,12 @@ std::string Hyprutils::String::trim(const std::string& in) {
|
|||
if (in.empty())
|
||||
return in;
|
||||
|
||||
int countBefore = 0;
|
||||
size_t countBefore = 0;
|
||||
while (countBefore < in.length() && std::isspace(in.at(countBefore))) {
|
||||
countBefore++;
|
||||
}
|
||||
|
||||
int countAfter = 0;
|
||||
size_t countAfter = 0;
|
||||
while (countAfter < in.length() - countBefore && std::isspace(in.at(in.length() - countAfter - 1))) {
|
||||
countAfter++;
|
||||
}
|
||||
|
|
@ -22,10 +22,33 @@ 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;
|
||||
|
||||
bool decimalParsed = false;
|
||||
|
||||
for (size_t i = 0; i < str.length(); ++i) {
|
||||
const char& c = str.at(i);
|
||||
|
||||
|
|
@ -44,17 +67,16 @@ bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) {
|
|||
if (i == 0)
|
||||
return false;
|
||||
|
||||
if (str.at(0) == '-')
|
||||
if (decimalParsed)
|
||||
return false;
|
||||
|
||||
decimalParsed = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isdigit(str.back()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return isdigit(str.back()) != 0;
|
||||
}
|
||||
|
||||
void Hyprutils::String::replaceInString(std::string& string, const std::string& what, const std::string& to) {
|
||||
|
|
@ -66,3 +88,16 @@ 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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ Hyprutils::String::CVarList::CVarList(const std::string& in, const size_t lastAr
|
|||
std::string args{in};
|
||||
size_t idx = 0;
|
||||
size_t pos = 0;
|
||||
std::ranges::replace_if(
|
||||
args, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0);
|
||||
std::ranges::replace_if(args, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0);
|
||||
|
||||
for (const auto& s : args | std::views::split(0)) {
|
||||
if (removeEmpty && s.empty())
|
||||
|
|
@ -23,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_view{s}.data()));
|
||||
m_vArgs.emplace_back(trim(std::string{s.data()}));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -36,4 +35,4 @@ std::string Hyprutils::String::CVarList::join(const std::string& joiner, size_t
|
|||
}
|
||||
|
||||
return rolling;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
113
src/string/VarList2.cpp
Normal file
113
src/string/VarList2.cpp
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#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; });
|
||||
}
|
||||
12
src/utils/ScopeGuard.cpp
Normal file
12
src/utils/ScopeGuard.cpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#include <hyprutils/utils/ScopeGuard.hpp>
|
||||
|
||||
using namespace Hyprutils::Utils;
|
||||
|
||||
Hyprutils::Utils::CScopeGuard::CScopeGuard(const std::function<void()>& fn_) : fn(fn_) {
|
||||
;
|
||||
}
|
||||
|
||||
Hyprutils::Utils::CScopeGuard::~CScopeGuard() {
|
||||
if (fn)
|
||||
fn();
|
||||
}
|
||||
395
tests/animation/Animation.cpp
Normal file
395
tests/animation/Animation.cpp
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
|
||||
#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>
|
||||
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
#define UP CUniquePointer
|
||||
|
||||
using namespace Hyprutils::Animation;
|
||||
using namespace Hyprutils::Math;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
class EmtpyContext {};
|
||||
|
||||
template <typename VarType>
|
||||
using CAnimatedVariable = CGenericAnimatedVariable<VarType, EmtpyContext>;
|
||||
|
||||
template <typename VarType>
|
||||
using PANIMVAR = UP<CAnimatedVariable<VarType>>;
|
||||
|
||||
template <typename VarType>
|
||||
using PANIMVARREF = WP<CAnimatedVariable<VarType>>;
|
||||
|
||||
enum eAVTypes {
|
||||
INT = 1,
|
||||
TEST,
|
||||
};
|
||||
|
||||
struct SomeTestType {
|
||||
bool done = false;
|
||||
bool operator==(const SomeTestType& other) const {
|
||||
return done == other.done;
|
||||
}
|
||||
SomeTestType& operator=(const SomeTestType& other) {
|
||||
done = other.done;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
CAnimationConfigTree animationTree;
|
||||
|
||||
class CMyAnimationManager : public CAnimationManager {
|
||||
public:
|
||||
void tick() {
|
||||
for (const auto& PAV : m_vActiveAnimatedVariables) {
|
||||
if (!PAV || !PAV->ok() || !PAV->isBeingAnimated())
|
||||
continue;
|
||||
|
||||
const auto SPENT = PAV->getPercent();
|
||||
const auto PBEZIER = getBezier(PAV->getBezierName());
|
||||
|
||||
if (SPENT >= 1.f || !PAV->enabled()) {
|
||||
PAV->warp(true, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto POINTY = PBEZIER->getYForPoint(SPENT);
|
||||
|
||||
switch (PAV->m_Type) {
|
||||
case eAVTypes::INT: {
|
||||
auto avInt = dc<CAnimatedVariable<int>*>(PAV.get());
|
||||
if (!avInt)
|
||||
std::cout << "Dynamic cast upcast failed\n";
|
||||
|
||||
const auto DELTA = avInt->goal() - avInt->begun();
|
||||
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";
|
||||
|
||||
if (SPENT >= 1.f)
|
||||
avCustom->value().done = true;
|
||||
} break;
|
||||
default: {
|
||||
std::cout << "What are we even doing?\n";
|
||||
} break;
|
||||
}
|
||||
|
||||
PAV->onUpdate();
|
||||
}
|
||||
|
||||
tickDone();
|
||||
}
|
||||
|
||||
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>>();
|
||||
|
||||
av->create2(EAVTYPE, sc<CAnimationManager*>(this), av, v);
|
||||
av->setConfig(animationTree.getConfig(animationConfigName));
|
||||
}
|
||||
|
||||
virtual void scheduleTick() {
|
||||
;
|
||||
}
|
||||
|
||||
virtual void onTicked() {
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
UP<CMyAnimationManager> pAnimationManager;
|
||||
|
||||
class Subject {
|
||||
public:
|
||||
Subject(const int& a, const int& b) {
|
||||
pAnimationManager->createAnimation(a, m_iA, "default");
|
||||
pAnimationManager->createAnimation(b, m_iB, "internal");
|
||||
pAnimationManager->createAnimation({}, m_iC, "default");
|
||||
}
|
||||
PANIMVAR<int> m_iA;
|
||||
PANIMVAR<int> m_iB;
|
||||
PANIMVAR<SomeTestType> m_iC;
|
||||
};
|
||||
|
||||
static int config() {
|
||||
pAnimationManager = makeUnique<CMyAnimationManager>();
|
||||
|
||||
int ret = 0;
|
||||
|
||||
animationTree.createNode("global");
|
||||
animationTree.createNode("internal");
|
||||
|
||||
animationTree.createNode("foo", "internal");
|
||||
animationTree.createNode("default", "global");
|
||||
animationTree.createNode("bar", "default");
|
||||
|
||||
/*
|
||||
internal
|
||||
↳ foo
|
||||
global
|
||||
↳ default
|
||||
↳ bar
|
||||
*/
|
||||
|
||||
auto barCfg = animationTree.getConfig("bar");
|
||||
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());
|
||||
|
||||
animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf");
|
||||
|
||||
EXPECT_EQ(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_EQ(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);
|
||||
}
|
||||
|
||||
// Now overwrite the parent
|
||||
animationTree.setConfigForNode("default", 0, 0.0, "zxcv", "foo");
|
||||
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
TEST(Animation, animation) {
|
||||
config();
|
||||
|
||||
animationTree.createNode("global");
|
||||
animationTree.createNode("internal");
|
||||
|
||||
animationTree.createNode("default", "global");
|
||||
animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf");
|
||||
|
||||
Subject s(0, 0);
|
||||
|
||||
EXPECT_EQ(s.m_iA->value(), 0);
|
||||
EXPECT_EQ(s.m_iB->value(), 0);
|
||||
|
||||
// Test destruction of a CAnimatedVariable
|
||||
{
|
||||
Subject s2(10, 10);
|
||||
// Adds them to active
|
||||
*s2.m_iA = 1;
|
||||
*s2.m_iB = 2;
|
||||
// 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);
|
||||
|
||||
*s.m_iA = 10;
|
||||
*s.m_iB = 100;
|
||||
*s.m_iC = SomeTestType(true);
|
||||
|
||||
EXPECT_EQ(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);
|
||||
|
||||
s.m_iA->setValue(0);
|
||||
s.m_iB->setValue(0);
|
||||
|
||||
while (pAnimationManager->shouldTickForNext()) {
|
||||
pAnimationManager->tick();
|
||||
}
|
||||
|
||||
EXPECT_EQ(s.m_iA->value(), 10);
|
||||
EXPECT_EQ(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);
|
||||
|
||||
animationTree.getConfig("global")->internalEnabled = 0;
|
||||
|
||||
EXPECT_EQ(s.m_iA->enabled(), false);
|
||||
|
||||
*s.m_iA = 50;
|
||||
pAnimationManager->tick(); // Expecting a warp
|
||||
EXPECT_EQ(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);
|
||||
|
||||
// Reset
|
||||
animationTree.setConfigForNode("default", 1, 1, "default");
|
||||
|
||||
//
|
||||
// Test callbacks
|
||||
//
|
||||
int beginCallbackRan = 0;
|
||||
int updateCallbackRan = 0;
|
||||
int endCallbackRan = 0;
|
||||
s.m_iA->setCallbackOnBegin([&beginCallbackRan](WP<CBaseAnimatedVariable> pav) { beginCallbackRan++; });
|
||||
s.m_iA->setUpdateCallback([&updateCallbackRan](WP<CBaseAnimatedVariable> pav) { updateCallbackRan++; });
|
||||
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> pav) { endCallbackRan++; }, false);
|
||||
|
||||
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.
|
||||
|
||||
*s.m_iA = 1337;
|
||||
while (pAnimationManager->shouldTickForNext()) {
|
||||
pAnimationManager->tick();
|
||||
}
|
||||
|
||||
EXPECT_EQ(beginCallbackRan, 1);
|
||||
EXPECT_EQ(updateCallbackRan > 2, true);
|
||||
EXPECT_EQ(endCallbackRan, 3);
|
||||
|
||||
std::vector<PANIMVAR<int>> vars;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
vars.resize(vars.size() + 1);
|
||||
pAnimationManager->createAnimation(1, vars.back(), "default");
|
||||
*vars.back() = 1337;
|
||||
}
|
||||
|
||||
// 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())
|
||||
vars.back()->warp();
|
||||
});
|
||||
s.m_iA->setCallbackOnEnd([&s, &vars](auto) {
|
||||
vars.resize(vars.size() + 1);
|
||||
pAnimationManager->createAnimation(1, vars.back(), "default");
|
||||
*vars.back() = 1337;
|
||||
});
|
||||
|
||||
*s.m_iA = 1000000;
|
||||
|
||||
while (pAnimationManager->shouldTickForNext()) {
|
||||
pAnimationManager->tick();
|
||||
}
|
||||
|
||||
EXPECT_EQ(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);
|
||||
|
||||
// test one-time callbacks
|
||||
s.m_iA->resetAllCallbacks();
|
||||
s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, true);
|
||||
|
||||
EXPECT_EQ(endCallbackRan, 4);
|
||||
|
||||
s.m_iA->setValueAndWarp(10);
|
||||
|
||||
EXPECT_EQ(endCallbackRan, 4);
|
||||
EXPECT_EQ(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);
|
||||
|
||||
*s.m_iA = 4;
|
||||
s.m_iA->warp(true);
|
||||
EXPECT_EQ(endCallbackRan, 5);
|
||||
|
||||
// test getCurveValue
|
||||
*s.m_iA = 0;
|
||||
EXPECT_EQ(s.m_iA->getCurveValue(), 0.f);
|
||||
s.m_iA->warp();
|
||||
EXPECT_EQ(s.m_iA->getCurveValue(), 1.f);
|
||||
EXPECT_EQ(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());
|
||||
|
||||
*PAV = 10;
|
||||
PAV->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> v) { endCallbackRan++; });
|
||||
});
|
||||
|
||||
while (pAnimationManager->shouldTickForNext()) {
|
||||
pAnimationManager->tick();
|
||||
}
|
||||
|
||||
EXPECT_EQ(endCallbackRan, 8);
|
||||
EXPECT_EQ(s.m_iA->value(), 10);
|
||||
|
||||
// Test duplicate active anim vars are not allowed
|
||||
{
|
||||
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
|
||||
PANIMVAR<int> a;
|
||||
pAnimationManager->createAnimation(1, a, "default");
|
||||
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
|
||||
*a = 10;
|
||||
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
|
||||
*a = 20;
|
||||
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1);
|
||||
a->warp();
|
||||
EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0);
|
||||
EXPECT_EQ(a->value(), 20);
|
||||
}
|
||||
|
||||
// Test no crash when animation manager gets destroyed
|
||||
{
|
||||
PANIMVAR<int> a;
|
||||
pAnimationManager->createAnimation(1, a, "default");
|
||||
*a = 10;
|
||||
pAnimationManager.reset();
|
||||
EXPECT_EQ(a->isAnimationManagerDead(), true);
|
||||
a->setValueAndWarp(11);
|
||||
EXPECT_EQ(a->value(), 11);
|
||||
*a = 12;
|
||||
a->warp();
|
||||
EXPECT_EQ(a->value(), 12);
|
||||
*a = 13;
|
||||
} // a gets destroyed
|
||||
|
||||
EXPECT_EQ(pAnimationManager.get(), nullptr);
|
||||
}
|
||||
79
tests/animation/Bezier.cpp
Normal file
79
tests/animation/Bezier.cpp
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#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();
|
||||
}
|
||||
105
tests/cli/ArgumentParser.cpp
Normal file
105
tests/cli/ArgumentParser.cpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#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"));
|
||||
}
|
||||
104
tests/cli/Logger.cpp
Normal file
104
tests/cli/Logger.cpp
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#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.");
|
||||
}
|
||||
80
tests/i18n/Engine.cpp
Normal file
80
tests/i18n/Engine.cpp
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
#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 !");
|
||||
}
|
||||
11
tests/i18n/Locale.cpp
Normal file
11
tests/i18n/Locale.cpp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#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");
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
#include <hyprutils/math/Region.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);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
73
tests/math/Box.cpp
Normal file
73
tests/math/Box.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#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);
|
||||
}
|
||||
}
|
||||
23
tests/math/Mat3x3.cpp
Normal file
23
tests/math/Mat3x3.cpp
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#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);
|
||||
}
|
||||
18
tests/math/Region.cpp
Normal file
18
tests/math/Region.cpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#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));
|
||||
}
|
||||
19
tests/math/Vector2D.cpp
Normal file
19
tests/math/Vector2D.cpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#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));
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include "shared.hpp"
|
||||
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
|
||||
int main(int argc, char** argv, char** envp) {
|
||||
SP<int> intPtr = makeShared<int>(10);
|
||||
|
||||
int ret = 0;
|
||||
|
||||
EXPECT(*intPtr, 10);
|
||||
EXPECT(intPtr.strongRef(), 1);
|
||||
|
||||
WP<int> weak = intPtr;
|
||||
|
||||
EXPECT(*intPtr, 10);
|
||||
EXPECT(intPtr.strongRef(), 1);
|
||||
EXPECT(*weak.get(), 10);
|
||||
EXPECT(weak.expired(), false);
|
||||
|
||||
intPtr = {};
|
||||
|
||||
EXPECT(weak.expired(), true);
|
||||
|
||||
return ret;
|
||||
}
|
||||
294
tests/memory/Memory.cpp
Normal file
294
tests/memory/Memory.cpp
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
|
||||
#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();
|
||||
}
|
||||
45
tests/os/Fd.cpp
Normal file
45
tests/os/Fd.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#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());
|
||||
}
|
||||
28
tests/os/Process.cpp
Normal file
28
tests/os/Process.cpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#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);
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
#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)
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
#include <hyprutils/signal/Signal.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include "shared.hpp"
|
||||
|
||||
using namespace Hyprutils::Signal;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
int main(int argc, char** argv, char** envp) {
|
||||
int ret = 0;
|
||||
|
||||
CSignal signal;
|
||||
int data = 0;
|
||||
auto listener = signal.registerListener([&] (std::any d) {
|
||||
data = 1;
|
||||
});
|
||||
|
||||
signal.emit();
|
||||
|
||||
EXPECT(data, 1);
|
||||
|
||||
data = 0;
|
||||
|
||||
listener.reset();
|
||||
|
||||
signal.emit();
|
||||
|
||||
EXPECT(data, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
369
tests/signal/Signal.cpp
Normal file
369
tests/signal/Signal.cpp
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <any>
|
||||
#include <hyprutils/signal/Signal.hpp>
|
||||
#include <hyprutils/signal/Listener.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <memory>
|
||||
|
||||
using namespace Hyprutils::Signal;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
//
|
||||
|
||||
static void legacy() {
|
||||
CSignal signal;
|
||||
int data = 0;
|
||||
auto listener = signal.registerListener([&]([[maybe_unused]] std::any d) { data = 1; });
|
||||
|
||||
signal.emit();
|
||||
|
||||
EXPECT_EQ(data, 1);
|
||||
|
||||
data = 0;
|
||||
|
||||
listener.reset();
|
||||
|
||||
signal.emit();
|
||||
|
||||
EXPECT_EQ(data, 0);
|
||||
}
|
||||
|
||||
static void legacyListenerEmit() {
|
||||
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);
|
||||
}
|
||||
|
||||
static void legacyListeners() {
|
||||
int data = 0;
|
||||
|
||||
CSignalT<> signal0;
|
||||
CSignalT<int> signal1;
|
||||
|
||||
auto listener0 = signal0.registerListener([&](std::any d) { data += 1; });
|
||||
auto listener1 = signal1.registerListener([&](std::any d) { data += std::any_cast<int>(d); });
|
||||
|
||||
signal0.registerStaticListener([&](void* o, std::any d) { data += 10; }, nullptr);
|
||||
signal1.registerStaticListener([&](void* o, std::any d) { data += std::any_cast<int>(d) * 10; }, nullptr);
|
||||
|
||||
signal0.emit();
|
||||
signal1.emit(2);
|
||||
|
||||
EXPECT_EQ(data, 33);
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
//
|
||||
|
||||
static void empty() {
|
||||
int data = 0;
|
||||
|
||||
CSignalT<> signal;
|
||||
auto listener = signal.listen([&] { data = 1; });
|
||||
|
||||
signal.emit();
|
||||
EXPECT_EQ(data, 1);
|
||||
|
||||
data = 0;
|
||||
listener.reset();
|
||||
signal.emit();
|
||||
EXPECT_EQ(data, 0);
|
||||
}
|
||||
|
||||
static void typed() {
|
||||
int data = 0;
|
||||
|
||||
CSignalT<int> signal;
|
||||
auto listener = signal.listen([&](int newData) { data = newData; });
|
||||
|
||||
signal.emit(1);
|
||||
EXPECT_EQ(data, 1);
|
||||
}
|
||||
|
||||
static void ignoreParams() {
|
||||
int data = 0;
|
||||
|
||||
CSignalT<int> signal;
|
||||
auto listener = signal.listen([&] { data += 1; });
|
||||
|
||||
signal.listenStatic([&] { data += 1; });
|
||||
|
||||
signal.emit(2);
|
||||
EXPECT_EQ(data, 2);
|
||||
}
|
||||
|
||||
static void typedMany() {
|
||||
int data1 = 0;
|
||||
int data2 = 0;
|
||||
int data3 = 0;
|
||||
|
||||
CSignalT<int, int, int> signal;
|
||||
auto listener = signal.listen([&](int d1, int d2, int d3) {
|
||||
data1 = d1;
|
||||
data2 = d2;
|
||||
data3 = d3;
|
||||
});
|
||||
|
||||
signal.emit(1, 2, 3);
|
||||
EXPECT_EQ(data1, 1);
|
||||
EXPECT_EQ(data2, 2);
|
||||
EXPECT_EQ(data3, 3);
|
||||
}
|
||||
|
||||
static void ref() {
|
||||
int count = 0;
|
||||
int data = 0;
|
||||
|
||||
CSignalT<int&> signal;
|
||||
auto l1 = signal.listen([&](int& v) { v += 1; });
|
||||
auto l2 = signal.listen([&](int v) { count += v; });
|
||||
signal.emit(data);
|
||||
|
||||
CSignalT<const int&> constSignal;
|
||||
auto l3 = constSignal.listen([&](const int& v) { count += v; });
|
||||
auto l4 = constSignal.listen([&](int v) { count += v; });
|
||||
constSignal.emit(data);
|
||||
|
||||
EXPECT_EQ(data, 1);
|
||||
EXPECT_EQ(count, 3);
|
||||
}
|
||||
|
||||
static void refMany() {
|
||||
int count = 0;
|
||||
int data1 = 0;
|
||||
int data2 = 10;
|
||||
|
||||
CSignalT<int&, const int&> signal;
|
||||
auto l1 = signal.listen([&](int& v, const int&) { v += 1; });
|
||||
auto l2 = signal.listen([&](int v1, int v2) { count += v1 + v2; });
|
||||
|
||||
signal.emit(data1, data2);
|
||||
EXPECT_EQ(data1, 1);
|
||||
EXPECT_EQ(count, 11);
|
||||
}
|
||||
|
||||
static void autoRefTypes() {
|
||||
class CCopyCounter {
|
||||
public:
|
||||
CCopyCounter(int& createCount, int& destroyCount) : createCount(createCount), destroyCount(destroyCount) {
|
||||
createCount += 1;
|
||||
}
|
||||
|
||||
CCopyCounter(CCopyCounter&& other) noexcept : CCopyCounter(other.createCount, other.destroyCount) {}
|
||||
CCopyCounter(const CCopyCounter& other) noexcept : CCopyCounter(other.createCount, other.destroyCount) {}
|
||||
|
||||
~CCopyCounter() {
|
||||
destroyCount += 1;
|
||||
}
|
||||
|
||||
private:
|
||||
int& createCount;
|
||||
int& destroyCount;
|
||||
};
|
||||
|
||||
auto createCount = 0;
|
||||
auto destroyCount = 0;
|
||||
|
||||
CSignalT<CCopyCounter> signal;
|
||||
auto listener = signal.listen([](const CCopyCounter& counter) {});
|
||||
|
||||
signal.emit(CCopyCounter(createCount, destroyCount));
|
||||
EXPECT_EQ(createCount, 1);
|
||||
EXPECT_EQ(destroyCount, 1);
|
||||
}
|
||||
|
||||
static void forward() {
|
||||
int count = 0;
|
||||
|
||||
CSignalT<int> sig;
|
||||
CSignalT<int> connected1;
|
||||
CSignalT<> connected2;
|
||||
|
||||
auto conn1 = sig.forward(connected1);
|
||||
auto conn2 = sig.forward(connected2);
|
||||
|
||||
auto listener1 = connected1.listen([&](int v) { count += v; });
|
||||
auto listener2 = connected2.listen([&] { count += 1; });
|
||||
|
||||
sig.emit(2);
|
||||
|
||||
EXPECT_EQ(count, 3);
|
||||
}
|
||||
|
||||
static void listenerAdded() {
|
||||
int count = 0;
|
||||
|
||||
CSignalT<> signal;
|
||||
CHyprSignalListener secondListener;
|
||||
|
||||
auto listener = signal.listen([&] {
|
||||
count += 1;
|
||||
|
||||
if (!secondListener)
|
||||
secondListener = signal.listen([&] { count += 1; });
|
||||
});
|
||||
|
||||
signal.emit();
|
||||
EXPECT_EQ(count, 1); // second should NOT be invoked as it was registed during emit
|
||||
|
||||
signal.emit();
|
||||
EXPECT_EQ(count, 3); // second should be invoked
|
||||
}
|
||||
|
||||
static void lastListenerSwapped() {
|
||||
int count = 0;
|
||||
|
||||
CSignalT<> signal;
|
||||
CHyprSignalListener removedListener;
|
||||
CHyprSignalListener addedListener;
|
||||
|
||||
auto firstListener = signal.listen([&] {
|
||||
removedListener.reset(); // dropped and should NOT be invoked
|
||||
|
||||
if (!addedListener)
|
||||
addedListener = signal.listen([&] { count += 2; });
|
||||
});
|
||||
|
||||
removedListener = signal.listen([&] { count += 1; });
|
||||
|
||||
signal.emit();
|
||||
EXPECT_EQ(count, 0); // neither the removed nor added listeners should fire
|
||||
|
||||
signal.emit();
|
||||
EXPECT_EQ(count, 2); // only the new listener should fire
|
||||
}
|
||||
|
||||
static void signalDestroyed() {
|
||||
int count = 0;
|
||||
|
||||
auto signal = std::make_unique<CSignalT<>>();
|
||||
|
||||
// This ensures a destructor of a listener called before signal reset is safe.
|
||||
auto preListener = signal->listen([&] { count += 1; });
|
||||
|
||||
auto listener = signal->listen([&] { signal.reset(); });
|
||||
|
||||
// This ensures a destructor of a listener called after signal reset is safe
|
||||
// and gets called.
|
||||
auto postListener = signal->listen([&] { count += 1; });
|
||||
|
||||
signal->emit();
|
||||
EXPECT_EQ(count, 2); // all listeners should fire regardless of signal deletion
|
||||
}
|
||||
|
||||
// purely an asan test
|
||||
static void signalDestroyedBeforeListener() {
|
||||
CHyprSignalListener listener1;
|
||||
CHyprSignalListener listener2;
|
||||
|
||||
CSignalT<> signal;
|
||||
|
||||
listener1 = signal.listen([] {});
|
||||
listener2 = signal.listen([] {});
|
||||
}
|
||||
|
||||
static void signalDestroyedWithAddedListener() {
|
||||
int count = 0;
|
||||
|
||||
auto signal = std::make_unique<CSignalT<>>();
|
||||
CHyprSignalListener shouldNotRun;
|
||||
|
||||
auto listener = signal->listen([&] {
|
||||
shouldNotRun = signal->listen([&] { count += 2; });
|
||||
signal.reset();
|
||||
});
|
||||
|
||||
signal->emit();
|
||||
EXPECT_EQ(count, 0);
|
||||
}
|
||||
|
||||
static void signalDestroyedWithRemovedAndAddedListener() {
|
||||
int count = 0;
|
||||
|
||||
auto signal = std::make_unique<CSignalT<>>();
|
||||
CHyprSignalListener removed;
|
||||
CHyprSignalListener shouldNotRun;
|
||||
|
||||
auto listener = signal->listen([&] {
|
||||
removed.reset();
|
||||
shouldNotRun = signal->listen([&] { count += 2; });
|
||||
signal.reset();
|
||||
});
|
||||
|
||||
removed = signal->listen([&] { count += 1; });
|
||||
|
||||
signal->emit();
|
||||
EXPECT_EQ(count, 0);
|
||||
}
|
||||
|
||||
static void staticListener() {
|
||||
int data = 0;
|
||||
|
||||
CSignalT<int> signal;
|
||||
signal.listenStatic([&](int newData) { data = newData; });
|
||||
|
||||
signal.emit(1);
|
||||
EXPECT_EQ(data, 1);
|
||||
}
|
||||
|
||||
static void staticListenerDestroy() {
|
||||
int count = 0;
|
||||
|
||||
auto signal = makeShared<CSignalT<>>();
|
||||
signal->listenStatic([&] { count += 1; });
|
||||
|
||||
signal->listenStatic([&] {
|
||||
// should not fire but SHOULD be freed
|
||||
signal->listenStatic([&] { count += 3; });
|
||||
|
||||
signal.reset();
|
||||
});
|
||||
|
||||
signal->listenStatic([&] { count += 1; });
|
||||
|
||||
signal->emit();
|
||||
EXPECT_EQ(count, 2);
|
||||
}
|
||||
|
||||
// purely an asan test
|
||||
static void listenerDestroysSelf() {
|
||||
CSignalT<> signal;
|
||||
|
||||
CHyprSignalListener listener;
|
||||
listener = signal.listen([&] { listener.reset(); });
|
||||
|
||||
// the static signal case is taken care of above
|
||||
|
||||
signal.emit();
|
||||
}
|
||||
|
||||
TEST(Signal, signal) {
|
||||
legacy();
|
||||
legacyListenerEmit();
|
||||
legacyListeners();
|
||||
empty();
|
||||
typed();
|
||||
ignoreParams();
|
||||
typedMany();
|
||||
ref();
|
||||
refMany();
|
||||
autoRefTypes();
|
||||
forward();
|
||||
listenerAdded();
|
||||
lastListenerSwapped();
|
||||
signalDestroyed();
|
||||
signalDestroyedBeforeListener();
|
||||
signalDestroyedWithAddedListener();
|
||||
signalDestroyedWithRemovedAndAddedListener();
|
||||
staticListener();
|
||||
staticListenerDestroy();
|
||||
signalDestroyed();
|
||||
listenerDestroysSelf();
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
#include <hyprutils/string/String.hpp>
|
||||
#include <hyprutils/string/VarList.hpp>
|
||||
#include "shared.hpp"
|
||||
|
||||
using namespace Hyprutils::String;
|
||||
|
||||
int main(int argc, char** argv, char** envp) {
|
||||
int ret = 0;
|
||||
|
||||
EXPECT(trim(" a "), "a");
|
||||
EXPECT(trim(" a a "), "a a");
|
||||
EXPECT(trim("a"), "a");
|
||||
EXPECT(trim(" "), "");
|
||||
|
||||
EXPECT(isNumber("99214123434"), true);
|
||||
EXPECT(isNumber("-35252345234"), true);
|
||||
EXPECT(isNumber("---3423--432"), false);
|
||||
EXPECT(isNumber("s---3423--432"), false);
|
||||
EXPECT(isNumber("---3423--432s"), false);
|
||||
EXPECT(isNumber("1s"), false);
|
||||
EXPECT(isNumber(""), false);
|
||||
EXPECT(isNumber("-"), false);
|
||||
EXPECT(isNumber("--0"), false);
|
||||
EXPECT(isNumber("abc"), false);
|
||||
EXPECT(isNumber("0.0", true), true);
|
||||
EXPECT(isNumber("0.2", true), true);
|
||||
EXPECT(isNumber("0.", true), false);
|
||||
EXPECT(isNumber(".0", true), false);
|
||||
EXPECT(isNumber("", true), false);
|
||||
EXPECT(isNumber("vvss", true), false);
|
||||
EXPECT(isNumber("0.9999s", true), false);
|
||||
EXPECT(isNumber("s0.9999", true), false);
|
||||
|
||||
CVarList list("hello world!", 0, 's', true);
|
||||
EXPECT(list[0], "hello");
|
||||
EXPECT(list[1], "world!");
|
||||
|
||||
std::string hello = "hello world!";
|
||||
replaceInString(hello, "hello", "hi");
|
||||
EXPECT(hello, "hi world!");
|
||||
|
||||
return ret;
|
||||
}
|
||||
15
tests/string/ConstVarList.cpp
Normal file
15
tests/string/ConstVarList.cpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#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");
|
||||
}
|
||||
47
tests/string/String.cpp
Normal file
47
tests/string/String.cpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#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);
|
||||
}
|
||||
11
tests/string/VarList.cpp
Normal file
11
tests/string/VarList.cpp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#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!");
|
||||
}
|
||||
67
tests/string/VarList2.cpp
Normal file
67
tests/string/VarList2.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#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");
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue