├── .clang-format ├── .clang-tidy ├── .github └── workflows │ ├── arch.yml │ └── nix.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── VERSION ├── flake.lock ├── flake.nix ├── hyprutils.pc.in ├── include └── hyprutils │ ├── animation │ ├── AnimatedVariable.hpp │ ├── AnimationConfig.hpp │ ├── AnimationManager.hpp │ └── BezierCurve.hpp │ ├── math │ ├── Box.hpp │ ├── Edges.hpp │ ├── Mat3x3.hpp │ ├── Misc.hpp │ ├── Region.hpp │ └── Vector2D.hpp │ ├── memory │ ├── ImplBase.hpp │ ├── SharedPtr.hpp │ ├── UniquePtr.hpp │ └── WeakPtr.hpp │ ├── os │ ├── FileDescriptor.hpp │ └── Process.hpp │ ├── path │ └── Path.hpp │ ├── signal │ ├── Listener.hpp │ └── Signal.hpp │ ├── string │ ├── ConstVarList.hpp │ ├── String.hpp │ └── VarList.hpp │ └── utils │ └── ScopeGuard.hpp ├── nix └── default.nix ├── src ├── animation │ ├── AnimatedVariable.cpp │ ├── AnimationConfig.cpp │ ├── AnimationManager.cpp │ └── BezierCurve.cpp ├── math │ ├── Box.cpp │ ├── Mat3x3.cpp │ ├── Region.cpp │ └── Vector2D.cpp ├── os │ ├── FileDescriptor.cpp │ └── Process.cpp ├── path │ └── Path.cpp ├── signal │ ├── Listener.cpp │ └── Signal.cpp ├── string │ ├── ConstVarList.cpp │ ├── String.cpp │ └── VarList.cpp └── utils │ └── ScopeGuard.cpp └── tests ├── animation.cpp ├── filedescriptor.cpp ├── math.cpp ├── memory.cpp ├── os.cpp ├── shared.hpp ├── signal.cpp └── string.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | 5 | AccessModifierOffset: -2 6 | AlignAfterOpenBracket: Align 7 | AlignConsecutiveMacros: true 8 | AlignConsecutiveAssignments: true 9 | AlignEscapedNewlines: Right 10 | AlignOperands: false 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: true 16 | AllowShortCaseLabelsOnASingleLine: true 17 | AllowShortFunctionsOnASingleLine: Empty 18 | AllowShortIfStatementsOnASingleLine: Never 19 | AllowShortLambdasOnASingleLine: All 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: Yes 25 | BreakBeforeBraces: Attach 26 | BreakBeforeTernaryOperators: false 27 | BreakConstructorInitializers: AfterColon 28 | ColumnLimit: 180 29 | CompactNamespaces: false 30 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 31 | ExperimentalAutoDetectBinPacking: false 32 | FixNamespaceComments: false 33 | IncludeBlocks: Preserve 34 | IndentCaseLabels: true 35 | IndentWidth: 4 36 | PointerAlignment: Left 37 | ReflowComments: false 38 | SortIncludes: false 39 | SortUsingDeclarations: false 40 | SpaceAfterCStyleCast: false 41 | SpaceAfterLogicalNot: false 42 | SpaceAfterTemplateKeyword: true 43 | SpaceBeforeCtorInitializerColon: true 44 | SpaceBeforeInheritanceColon: true 45 | SpaceBeforeParens: ControlStatements 46 | SpaceBeforeRangeBasedForLoopColon: true 47 | SpaceInEmptyParentheses: false 48 | SpacesBeforeTrailingComments: 1 49 | SpacesInAngles: false 50 | SpacesInCStyleCastParentheses: false 51 | SpacesInContainerLiterals: false 52 | SpacesInParentheses: false 53 | SpacesInSquareBrackets: false 54 | Standard: Auto 55 | TabWidth: 4 56 | UseTab: Never 57 | 58 | AllowShortEnumsOnASingleLine: false 59 | 60 | BraceWrapping: 61 | AfterEnum: false 62 | 63 | AlignConsecutiveDeclarations: AcrossEmptyLines 64 | 65 | NamespaceIndentation: All 66 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | WarningsAsErrors: '*' 2 | HeaderFilterRegex: '.*\.hpp' 3 | FormatStyle: file 4 | Checks: > 5 | -*, 6 | bugprone-*, 7 | -bugprone-easily-swappable-parameters, 8 | -bugprone-forward-declararion-namespace, 9 | -bugprone-forward-declararion-namespace, 10 | -bugprone-macro-parentheses, 11 | -bugprone-narrowing-conversions, 12 | -bugprone-branch-clone, 13 | -bugprone-assignment-in-if-condition, 14 | concurrency-*, 15 | -concurrency-mt-unsafe, 16 | cppcoreguidelines-*, 17 | -cppcoreguidelines-owning-memory, 18 | -cppcoreguidelines-avoid-magic-numbers, 19 | -cppcoreguidelines-pro-bounds-constant-array-index, 20 | -cppcoreguidelines-avoid-const-or-ref-data-members, 21 | -cppcoreguidelines-non-private-member-variables-in-classes, 22 | -cppcoreguidelines-avoid-goto, 23 | -cppcoreguidelines-pro-bounds-array-to-pointer-decay, 24 | -cppcoreguidelines-avoid-do-while, 25 | -cppcoreguidelines-avoid-non-const-global-variables, 26 | -cppcoreguidelines-special-member-functions, 27 | -cppcoreguidelines-explicit-virtual-functions, 28 | -cppcoreguidelines-avoid-c-arrays, 29 | -cppcoreguidelines-pro-bounds-pointer-arithmetic, 30 | -cppcoreguidelines-narrowing-conversions, 31 | -cppcoreguidelines-pro-type-union-access, 32 | -cppcoreguidelines-pro-type-member-init, 33 | -cppcoreguidelines-macro-usage, 34 | -cppcoreguidelines-macro-to-enum, 35 | -cppcoreguidelines-init-variables, 36 | -cppcoreguidelines-pro-type-cstyle-cast, 37 | -cppcoreguidelines-pro-type-vararg, 38 | -cppcoreguidelines-pro-type-reinterpret-cast, 39 | google-global-names-in-headers, 40 | -google-readability-casting, 41 | google-runtime-operator, 42 | misc-*, 43 | -misc-unused-parameters, 44 | -misc-no-recursion, 45 | -misc-non-private-member-variables-in-classes, 46 | -misc-include-cleaner, 47 | -misc-use-anonymous-namespace, 48 | -misc-const-correctness, 49 | modernize-*, 50 | -modernize-return-braced-init-list, 51 | -modernize-use-trailing-return-type, 52 | -modernize-use-using, 53 | -modernize-use-override, 54 | -modernize-avoid-c-arrays, 55 | -modernize-macro-to-enum, 56 | -modernize-loop-convert, 57 | -modernize-use-nodiscard, 58 | -modernize-pass-by-value, 59 | -modernize-use-auto, 60 | performance-*, 61 | -performance-avoid-endl, 62 | -performance-unnecessary-value-param, 63 | portability-std-allocator-const, 64 | readability-*, 65 | -readability-function-cognitive-complexity, 66 | -readability-function-size, 67 | -readability-identifier-length, 68 | -readability-magic-numbers, 69 | -readability-uppercase-literal-suffix, 70 | -readability-braces-around-statements, 71 | -readability-redundant-access-specifiers, 72 | -readability-else-after-return, 73 | -readability-container-data-pointer, 74 | -readability-implicit-bool-conversion, 75 | -readability-avoid-nested-conditional-operator, 76 | -readability-redundant-member-init, 77 | -readability-redundant-string-init, 78 | -readability-avoid-const-params-in-decls, 79 | -readability-named-parameter, 80 | -readability-convert-member-functions-to-static, 81 | -readability-qualified-auto, 82 | -readability-make-member-function-const, 83 | -readability-isolate-declaration, 84 | -readability-inconsistent-declaration-parameter-name, 85 | -clang-diagnostic-error, 86 | 87 | CheckOptions: 88 | performance-for-range-copy.WarnOnAllAutoCopies: true 89 | performance-inefficient-string-concatenation.StrictMode: true 90 | readability-braces-around-statements.ShortStatementLines: 0 91 | readability-identifier-naming.ClassCase: CamelCase 92 | readability-identifier-naming.ClassIgnoredRegexp: I.* 93 | readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!? 94 | readability-identifier-naming.EnumCase: CamelCase 95 | readability-identifier-naming.EnumPrefix: e 96 | readability-identifier-naming.EnumConstantCase: UPPER_CASE 97 | readability-identifier-naming.FunctionCase: camelBack 98 | readability-identifier-naming.NamespaceCase: CamelCase 99 | readability-identifier-naming.NamespacePrefix: N 100 | readability-identifier-naming.StructPrefix: S 101 | readability-identifier-naming.StructCase: CamelCase 102 | -------------------------------------------------------------------------------- /.github/workflows/arch.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test (Arch) 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | jobs: 5 | gcc: 6 | name: "Arch: Build and Test (gcc)" 7 | runs-on: ubuntu-latest 8 | container: 9 | image: archlinux 10 | steps: 11 | - name: Checkout repository actions 12 | uses: actions/checkout@v4 13 | with: 14 | sparse-checkout: .github/actions 15 | 16 | - name: Get required pkgs 17 | run: | 18 | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf 19 | pacman --noconfirm --noprogressbar -Syyu 20 | pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman 21 | 22 | - name: Build hyprutils with gcc 23 | run: | 24 | CC="/usr/bin/gcc" CXX="/usr/bin/g++" cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build 25 | CC="/usr/bin/gcc" CXX="/usr/bin/g++" cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` 26 | cmake --install ./build 27 | 28 | - name: Run tests 29 | run: | 30 | cd ./build && ctest --output-on-failure 31 | 32 | clang: 33 | name: "Arch: Build and Test (clang)" 34 | runs-on: ubuntu-latest 35 | container: 36 | image: archlinux 37 | steps: 38 | - name: Checkout repository actions 39 | uses: actions/checkout@v4 40 | with: 41 | sparse-checkout: .github/actions 42 | 43 | - name: Get required pkgs 44 | run: | 45 | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf 46 | pacman --noconfirm --noprogressbar -Syyu 47 | pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman 48 | 49 | - name: Build hyprutils with clang 50 | run: | 51 | CC="/usr/bin/clang" CXX="/usr/bin/clang++" cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build 52 | CC="/usr/bin/clang" CXX="/usr/bin/clang++" cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` 53 | cmake --install ./build 54 | 55 | - name: Run tests 56 | run: | 57 | cd ./build && ctest --output-on-failure 58 | -------------------------------------------------------------------------------- /.github/workflows/nix.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | jobs: 5 | nix: 6 | strategy: 7 | matrix: 8 | package: 9 | - hyprutils 10 | - hyprutils-with-tests 11 | 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - uses: cachix/install-nix-action@v26 17 | 18 | # not needed (yet) 19 | # - uses: cachix/cachix-action@v12 20 | # with: 21 | # name: hyprland 22 | # authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 23 | 24 | - name: Build & Test 25 | run: nix build .#${{ matrix.package }} --print-build-logs 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | build/ 35 | .vscode/ 36 | .cache/ 37 | 38 | .cmake/ 39 | CMakeCache.txt 40 | CMakeFiles/ 41 | CTestTestfile.cmake 42 | DartConfiguration.tcl 43 | Makefile 44 | cmake_install.cmake 45 | compile_commands.json 46 | hyprutils.pc 47 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | 3 | file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) 4 | string(STRIP ${VER_RAW} HYPRUTILS_VERSION) 5 | add_compile_definitions(HYPRUTILS_VERSION="${HYPRUTILS_VERSION}") 6 | 7 | project( 8 | hyprutils 9 | VERSION ${HYPRUTILS_VERSION} 10 | DESCRIPTION "Small C++ library for utilities used across the Hypr* ecosystem") 11 | 12 | include(CTest) 13 | include(GNUInstallDirs) 14 | 15 | set(PREFIX ${CMAKE_INSTALL_PREFIX}) 16 | set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR}) 17 | set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}) 18 | 19 | configure_file(hyprutils.pc.in hyprutils.pc @ONLY) 20 | 21 | set(CMAKE_CXX_STANDARD 23) 22 | add_compile_options( 23 | -Wall 24 | -Wextra 25 | -Wpedantic 26 | -Wno-unused-parameter 27 | -Wno-unused-value 28 | -Wno-missing-field-initializers 29 | -Wno-narrowing 30 | -Wno-pointer-arith) 31 | set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) 32 | 33 | if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) 34 | message(STATUS "Configuring hyprutils in Debug") 35 | add_compile_definitions(HYPRLAND_DEBUG) 36 | else() 37 | add_compile_options(-O3) 38 | message(STATUS "Configuring hyprutils in Release") 39 | endif() 40 | 41 | file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp") 42 | file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "include/*.hpp") 43 | 44 | find_package(PkgConfig REQUIRED) 45 | pkg_check_modules(deps REQUIRED IMPORTED_TARGET pixman-1) 46 | 47 | add_library(hyprutils SHARED ${SRCFILES}) 48 | target_include_directories( 49 | hyprutils 50 | PUBLIC "./include" 51 | PRIVATE "./src") 52 | set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION} 53 | SOVERSION 6) 54 | target_link_libraries(hyprutils PkgConfig::deps) 55 | 56 | # tests 57 | add_custom_target(tests) 58 | 59 | add_executable(hyprutils_memory "tests/memory.cpp") 60 | target_link_libraries(hyprutils_memory PRIVATE hyprutils PkgConfig::deps) 61 | add_test( 62 | NAME "Memory" 63 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests 64 | COMMAND hyprutils_memory "memory") 65 | add_dependencies(tests hyprutils_memory) 66 | 67 | add_executable(hyprutils_string "tests/string.cpp") 68 | target_link_libraries(hyprutils_string PRIVATE hyprutils PkgConfig::deps) 69 | add_test( 70 | NAME "String" 71 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests 72 | COMMAND hyprutils_string "string") 73 | add_dependencies(tests hyprutils_string) 74 | 75 | add_executable(hyprutils_signal "tests/signal.cpp") 76 | target_link_libraries(hyprutils_signal PRIVATE hyprutils PkgConfig::deps) 77 | add_test( 78 | NAME "Signal" 79 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests 80 | COMMAND hyprutils_signal "signal") 81 | add_dependencies(tests hyprutils_signal) 82 | 83 | add_executable(hyprutils_math "tests/math.cpp") 84 | target_link_libraries(hyprutils_math PRIVATE hyprutils PkgConfig::deps) 85 | add_test( 86 | NAME "Math" 87 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests 88 | COMMAND hyprutils_math "math") 89 | add_dependencies(tests hyprutils_math) 90 | 91 | add_executable(hyprutils_os "tests/os.cpp") 92 | target_link_libraries(hyprutils_os PRIVATE hyprutils PkgConfig::deps) 93 | add_test( 94 | NAME "OS" 95 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests 96 | COMMAND hyprutils_os "os") 97 | add_dependencies(tests hyprutils_os) 98 | 99 | add_executable(hyprutils_filedescriptor "tests/filedescriptor.cpp") 100 | target_link_libraries(hyprutils_filedescriptor PRIVATE hyprutils PkgConfig::deps) 101 | add_test( 102 | NAME "Filedescriptor" 103 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests 104 | COMMAND hyprutils_filedescriptor "filedescriptor") 105 | add_dependencies(tests hyprutils_filedescriptor) 106 | 107 | add_executable(hyprutils_animation "tests/animation.cpp") 108 | target_link_libraries(hyprutils_animation PRIVATE hyprutils PkgConfig::deps) 109 | add_test( 110 | NAME "Animation" 111 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests 112 | COMMAND hyprutils_animation "utils") 113 | add_dependencies(tests hyprutils_animation) 114 | 115 | # Installation 116 | install(TARGETS hyprutils) 117 | install(DIRECTORY "include/hyprutils" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 118 | install(FILES ${CMAKE_BINARY_DIR}/hyprutils.pc 119 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 120 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024, Hypr Development 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hyprutils 2 | 3 | Hyprutils is a small C++ library for utilities used across the Hypr* ecosystem. 4 | 5 | ## Stability 6 | 7 | Hyprutils depends on the ABI stability of the stdlib implementation of your compiler. Sover bumps will be done only for hyprutils ABI breaks, not stdlib. 8 | 9 | ## Building 10 | 11 | ```sh 12 | git clone https://github.com/hyprwm/hyprutils.git 13 | cd hyprutils/ 14 | cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build 15 | cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` 16 | sudo cmake --install build 17 | ``` 18 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.7.1 2 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1748929857, 6 | "narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs", 22 | "systems": "systems" 23 | } 24 | }, 25 | "systems": { 26 | "locked": { 27 | "lastModified": 1689347949, 28 | "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", 29 | "owner": "nix-systems", 30 | "repo": "default-linux", 31 | "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "nix-systems", 36 | "repo": "default-linux", 37 | "type": "github" 38 | } 39 | } 40 | }, 41 | "root": "root", 42 | "version": 7 43 | } 44 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Small C++ library for utilities used across the Hypr* ecosystem"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | systems.url = "github:nix-systems/default-linux"; 7 | }; 8 | 9 | outputs = { 10 | self, 11 | nixpkgs, 12 | systems, 13 | }: let 14 | inherit (nixpkgs) lib; 15 | eachSystem = lib.genAttrs (import systems); 16 | pkgsFor = eachSystem (system: 17 | import nixpkgs { 18 | localSystem.system = system; 19 | overlays = with self.overlays; [hyprutils]; 20 | }); 21 | mkDate = longDate: (lib.concatStringsSep "-" [ 22 | (builtins.substring 0 4 longDate) 23 | (builtins.substring 4 2 longDate) 24 | (builtins.substring 6 2 longDate) 25 | ]); 26 | 27 | version = lib.removeSuffix "\n" (builtins.readFile ./VERSION); 28 | in { 29 | overlays = { 30 | default = self.overlays.hyprutils; 31 | hyprutils = final: prev: { 32 | hyprutils = final.callPackage ./nix/default.nix { 33 | stdenv = final.gcc15Stdenv; 34 | version = version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); 35 | }; 36 | hyprutils-with-tests = final.hyprutils.override {doCheck = true;}; 37 | }; 38 | }; 39 | 40 | packages = eachSystem (system: { 41 | default = self.packages.${system}.hyprutils; 42 | inherit (pkgsFor.${system}) hyprutils hyprutils-with-tests; 43 | }); 44 | 45 | formatter = eachSystem (system: pkgsFor.${system}.alejandra); 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /hyprutils.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@PREFIX@ 2 | includedir=@INCLUDE@ 3 | libdir=@LIBDIR@ 4 | 5 | Name: hyprutils 6 | URL: https://github.com/hyprwm/hyprutils 7 | Description: Hyprland utilities library used across the ecosystem 8 | Version: @HYPRUTILS_VERSION@ 9 | Cflags: -I${includedir} 10 | Libs: -L${libdir} -lhyprutils 11 | -------------------------------------------------------------------------------- /include/hyprutils/animation/AnimatedVariable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AnimationConfig.hpp" 4 | #include "../memory/WeakPtr.hpp" 5 | #include "../memory/SharedPtr.hpp" 6 | #include "../signal/Signal.hpp" 7 | #include "AnimationManager.hpp" 8 | 9 | #include 10 | #include 11 | 12 | namespace Hyprutils { 13 | namespace Animation { 14 | 15 | /* A base class for animated variables. */ 16 | class CBaseAnimatedVariable { 17 | public: 18 | using CallbackFun = std::function thisptr)>; 19 | 20 | CBaseAnimatedVariable() { 21 | ; // m_bDummy = true; 22 | }; 23 | 24 | void create(CAnimationManager*, int, Memory::CSharedPointer); 25 | void connectToActive(); 26 | void disconnectFromActive(); 27 | 28 | /* Needs to call disconnectFromActive to remove `m_pSelf` from the active animation list */ 29 | virtual ~CBaseAnimatedVariable() { 30 | disconnectFromActive(); 31 | }; 32 | 33 | virtual void warp(bool endCallback = true, bool forceDisconnect = true) = 0; 34 | 35 | CBaseAnimatedVariable(const CBaseAnimatedVariable&) = delete; 36 | CBaseAnimatedVariable(CBaseAnimatedVariable&&) = delete; 37 | CBaseAnimatedVariable& operator=(const CBaseAnimatedVariable&) = delete; 38 | CBaseAnimatedVariable& operator=(CBaseAnimatedVariable&&) = delete; 39 | 40 | // 41 | void setConfig(Memory::CSharedPointer pConfig) { 42 | m_pConfig = pConfig; 43 | } 44 | 45 | Memory::CWeakPointer getConfig() const { 46 | return m_pConfig; 47 | } 48 | 49 | bool enabled() const; 50 | const std::string& getBezierName() const; 51 | const std::string& getStyle() const; 52 | 53 | /* returns the spent (completion) % */ 54 | float getPercent() const; 55 | 56 | /* returns the current curve value. */ 57 | float getCurveValue() const; 58 | 59 | /* checks if an animation is in progress */ 60 | bool isBeingAnimated() const { 61 | return m_bIsBeingAnimated; 62 | } 63 | 64 | /* checks m_bDummy and m_pAnimationManager */ 65 | bool ok() const; 66 | 67 | /* calls the update callback */ 68 | void onUpdate(); 69 | 70 | /* sets a function to be ran when an animation ended. 71 | if "remove" is set to true, it will remove the callback when ran. */ 72 | void setCallbackOnEnd(CallbackFun func, bool remove = true); 73 | 74 | /* sets a function to be ran when an animation is started. 75 | if "remove" is set to true, it will remove the callback when ran. */ 76 | void setCallbackOnBegin(CallbackFun func, bool remove = true); 77 | 78 | /* sets the update callback, called every time the value is animated and a step is done 79 | Warning: calling unregisterVar/registerVar in this handler will cause UB */ 80 | void setUpdateCallback(CallbackFun func); 81 | 82 | /* resets all callbacks. Does not call any. */ 83 | void resetAllCallbacks(); 84 | 85 | void onAnimationEnd(); 86 | void onAnimationBegin(); 87 | 88 | /* returns whether the parent CAnimationManager is dead */ 89 | bool isAnimationManagerDead() const; 90 | 91 | int m_Type = -1; 92 | 93 | protected: 94 | friend class CAnimationManager; 95 | 96 | CAnimationManager* m_pAnimationManager = nullptr; 97 | 98 | bool m_bIsConnectedToActive = false; 99 | bool m_bIsBeingAnimated = false; 100 | 101 | Memory::CWeakPointer m_pSelf; 102 | 103 | Memory::CWeakPointer m_pSignals; 104 | 105 | private: 106 | Memory::CWeakPointer m_pConfig; 107 | 108 | std::chrono::steady_clock::time_point animationBegin; 109 | 110 | bool m_bDummy = true; 111 | 112 | bool m_bRemoveEndAfterRan = true; 113 | bool m_bRemoveBeginAfterRan = true; 114 | 115 | CallbackFun m_fEndCallback; 116 | CallbackFun m_fBeginCallback; 117 | CallbackFun m_fUpdateCallback; 118 | }; 119 | 120 | /* This concept represents the minimum requirement for a type to be used with CGenericAnimatedVariable */ 121 | template 122 | concept AnimatedType = requires(ValueImpl val) { 123 | requires std::is_copy_constructible_v; 124 | { val == val } -> std::same_as; // requires operator== 125 | { val = val }; // requires operator= 126 | }; 127 | 128 | /* 129 | A generic class for variables. 130 | VarType is the type of the variable to be animated. 131 | AnimationContext is there to attach additional data to the animation. 132 | In Hyprland that struct would contain a reference to window, workspace or layer for example. 133 | */ 134 | template 135 | class CGenericAnimatedVariable : public CBaseAnimatedVariable { 136 | public: 137 | CGenericAnimatedVariable() = default; 138 | 139 | void create(const int typeInfo, CAnimationManager* pAnimationManager, Memory::CSharedPointer> pSelf, 140 | const VarType& initialValue) { 141 | m_Begun = initialValue; 142 | m_Value = initialValue; 143 | m_Goal = initialValue; 144 | 145 | CBaseAnimatedVariable::create(pAnimationManager, typeInfo, pSelf); 146 | } 147 | 148 | CGenericAnimatedVariable(const CGenericAnimatedVariable&) = delete; 149 | CGenericAnimatedVariable(CGenericAnimatedVariable&&) = delete; 150 | CGenericAnimatedVariable& operator=(const CGenericAnimatedVariable&) = delete; 151 | CGenericAnimatedVariable& operator=(CGenericAnimatedVariable&&) = delete; 152 | 153 | virtual void warp(bool endCallback = true, bool forceDisconnect = true) { 154 | if (!m_bIsBeingAnimated) 155 | return; 156 | 157 | m_Value = m_Goal; 158 | 159 | onUpdate(); 160 | 161 | m_bIsBeingAnimated = false; 162 | 163 | if (forceDisconnect) 164 | disconnectFromActive(); 165 | 166 | if (endCallback) 167 | onAnimationEnd(); 168 | } 169 | 170 | const VarType& value() const { 171 | return m_Value; 172 | } 173 | 174 | /* used to update the value each tick via the AnimationManager */ 175 | VarType& value() { 176 | return m_Value; 177 | } 178 | 179 | const VarType& goal() const { 180 | return m_Goal; 181 | } 182 | 183 | const VarType& begun() const { 184 | return m_Begun; 185 | } 186 | 187 | CGenericAnimatedVariable& operator=(const VarType& v) { 188 | if (v == m_Goal) 189 | return *this; 190 | 191 | m_Goal = v; 192 | m_Begun = m_Value; 193 | 194 | onAnimationBegin(); 195 | 196 | return *this; 197 | } 198 | 199 | /* Sets the actual stored value, without affecting the goal, but resets the timer*/ 200 | void setValue(const VarType& v) { 201 | if (v == m_Value) 202 | return; 203 | 204 | m_Value = v; 205 | m_Begun = m_Value; 206 | 207 | onAnimationBegin(); 208 | } 209 | 210 | /* Sets the actual value and goal*/ 211 | void setValueAndWarp(const VarType& v) { 212 | m_Goal = v; 213 | m_bIsBeingAnimated = true; 214 | 215 | warp(); 216 | } 217 | 218 | AnimationContext m_Context; 219 | 220 | private: 221 | VarType m_Value{}; 222 | VarType m_Goal{}; 223 | VarType m_Begun{}; 224 | }; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /include/hyprutils/animation/AnimationConfig.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../memory/WeakPtr.hpp" 4 | 5 | #include 6 | #include 7 | 8 | namespace Hyprutils { 9 | namespace Animation { 10 | /* 11 | Structure for animation properties. 12 | Config properties need to have a static lifetime to allow for config reload. 13 | */ 14 | struct SAnimationPropertyConfig { 15 | bool overridden = false; 16 | 17 | std::string internalBezier = ""; 18 | std::string internalStyle = ""; 19 | float internalSpeed = 0.f; 20 | int internalEnabled = -1; 21 | 22 | Memory::CWeakPointer pValues; 23 | Memory::CWeakPointer pParentAnimation; 24 | }; 25 | 26 | /* A class to manage SAnimationPropertyConfig objects in a tree structure */ 27 | class CAnimationConfigTree { 28 | public: 29 | CAnimationConfigTree() = default; 30 | ~CAnimationConfigTree() = default; 31 | 32 | /* Add a new animation node inheriting from a parent. 33 | If parent is empty, a root node will be created that references it's own values. 34 | Make sure the parent node has already been created through this interface. */ 35 | void createNode(const std::string& nodeName, const std::string& parent = ""); 36 | 37 | /* check if a node name has been created using createNode */ 38 | bool nodeExists(const std::string& nodeName) const; 39 | 40 | /* Override the values of a node. The root node can also be overriden. */ 41 | void setConfigForNode(const std::string& nodeName, int enabled, float speed, const std::string& bezier, const std::string& style = ""); 42 | 43 | Memory::CSharedPointer getConfig(const std::string& name) const; 44 | const std::unordered_map>& getFullConfig() const; 45 | 46 | CAnimationConfigTree(const CAnimationConfigTree&) = delete; 47 | CAnimationConfigTree(CAnimationConfigTree&&) = delete; 48 | CAnimationConfigTree& operator=(const CAnimationConfigTree&) = delete; 49 | CAnimationConfigTree& operator=(CAnimationConfigTree&&) = delete; 50 | 51 | private: 52 | void setAnimForChildren(Memory::CSharedPointer PANIM); 53 | std::unordered_map> m_mAnimationConfig; 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /include/hyprutils/animation/AnimationManager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "./BezierCurve.hpp" 4 | #include "../math/Vector2D.hpp" 5 | #include "../memory/WeakPtr.hpp" 6 | #include "../signal/Signal.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace Hyprutils { 13 | namespace Animation { 14 | class CBaseAnimatedVariable; 15 | 16 | /* A class for managing bezier curves and variables that are being animated. */ 17 | class CAnimationManager { 18 | public: 19 | CAnimationManager(); 20 | virtual ~CAnimationManager() = default; 21 | 22 | void tickDone(); 23 | void rotateActive(); 24 | bool shouldTickForNext(); 25 | 26 | virtual void scheduleTick() = 0; 27 | virtual void onTicked() = 0; 28 | 29 | void addBezierWithName(std::string, const Math::Vector2D&, const Math::Vector2D&); 30 | void removeAllBeziers(); 31 | 32 | bool bezierExists(const std::string&); 33 | Memory::CSharedPointer getBezier(const std::string&); 34 | 35 | const std::unordered_map>& getAllBeziers(); 36 | 37 | struct SAnimationManagerSignals { 38 | Signal::CSignal connect; // WP 39 | Signal::CSignal disconnect; // WP 40 | }; 41 | 42 | Memory::CWeakPointer getSignals() const; 43 | 44 | std::vector> m_vActiveAnimatedVariables; 45 | 46 | private: 47 | std::unordered_map> m_mBezierCurves; 48 | 49 | bool m_bTickScheduled = false; 50 | 51 | void onConnect(std::any data); 52 | void onDisconnect(std::any data); 53 | 54 | struct SAnimVarListeners { 55 | Signal::CHyprSignalListener connect; 56 | Signal::CHyprSignalListener disconnect; 57 | }; 58 | 59 | Memory::CUniquePointer m_listeners; 60 | Memory::CUniquePointer m_events; 61 | }; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /include/hyprutils/animation/BezierCurve.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "../math/Vector2D.hpp" 7 | 8 | namespace Hyprutils { 9 | namespace Animation { 10 | constexpr int BAKEDPOINTS = 255; 11 | constexpr float INVBAKEDPOINTS = 1.f / BAKEDPOINTS; 12 | 13 | /* An implementation of a cubic bezier curve. */ 14 | class CBezierCurve { 15 | public: 16 | /* Calculates a cubic bezier curve based on 2 control points (EXCLUDES the 0,0 and 1,1 points). */ 17 | void setup(const std::array& points); 18 | 19 | float getYForT(float const& t) const; 20 | float getXForT(float const& t) const; 21 | float getYForPoint(float const& x) const; 22 | 23 | private: 24 | /* this INCLUDES the 0,0 and 1,1 points. */ 25 | std::vector m_vPoints; 26 | 27 | std::array m_aPointsBaked; 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /include/hyprutils/math/Box.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "./Vector2D.hpp" 4 | #include "./Misc.hpp" 5 | 6 | namespace Hyprutils::Math { 7 | 8 | /** 9 | * @brief Represents the extents of a bounding box. 10 | */ 11 | struct SBoxExtents { 12 | Vector2D topLeft; 13 | Vector2D bottomRight; 14 | 15 | /** 16 | * @brief Scales the extents by a given factor. 17 | * @param scale The scaling factor. 18 | * @return Scaled SBoxExtents. 19 | */ 20 | SBoxExtents operator*(const double& scale) const { 21 | return SBoxExtents{topLeft * scale, bottomRight * scale}; 22 | } 23 | /** 24 | * @brief Rounds the coordinates of the extents. 25 | * @return Rounded SBoxExtents. 26 | */ 27 | SBoxExtents round() { 28 | return {topLeft.round(), bottomRight.round()}; 29 | } 30 | /** 31 | * @brief Checks equality between two SBoxExtents objects. 32 | * @param other Another SBoxExtents object to compare. 33 | * @return True if both SBoxExtents are equal, false otherwise. 34 | */ 35 | bool operator==(const SBoxExtents& other) const { 36 | return topLeft == other.topLeft && bottomRight == other.bottomRight; 37 | } 38 | 39 | /** 40 | * @brief Adjusts the extents to encompass another SBoxExtents. 41 | * @param other Another SBoxExtents to add to this one. 42 | */ 43 | void addExtents(const SBoxExtents& other) { 44 | topLeft = topLeft.getComponentMax(other.topLeft); 45 | bottomRight = bottomRight.getComponentMax(other.bottomRight); 46 | } 47 | }; 48 | 49 | /** 50 | * @brief Represents a 2D bounding box. 51 | */ 52 | class CBox { 53 | public: 54 | /** 55 | * @brief Constructs a CBox with specified position and dimensions. 56 | * @param x_ X-coordinate of the top-left corner. 57 | * @param y_ Y-coordinate of the top-left corner. 58 | * @param w_ Width of the box. 59 | * @param h_ Height of the box. 60 | */ 61 | CBox(double x_, double y_, double w_, double h_) { 62 | x = x_; 63 | y = y_; 64 | w = w_; 65 | h = h_; 66 | } 67 | /** 68 | * @brief Default constructor. Initializes an empty box (0 width, 0 height). 69 | */ 70 | CBox() { 71 | w = 0; 72 | h = 0; 73 | } 74 | /** 75 | * @brief Constructs a CBox with uniform dimensions. 76 | * @param d Dimensions to apply uniformly (x, y, width, height). 77 | */ 78 | CBox(const double d) { 79 | x = d; 80 | y = d; 81 | w = d; 82 | h = d; 83 | } 84 | /** 85 | * @brief Constructs a CBox from a position and size vector. 86 | * @param pos Position vector representing the top-left corner. 87 | * @param size Size vector representing width and height. 88 | */ 89 | CBox(const Vector2D& pos, const Vector2D& size) { 90 | x = pos.x; 91 | y = pos.y; 92 | w = size.x; 93 | h = size.y; 94 | } 95 | 96 | // Geometric operations 97 | CBox& applyFromWlr(); 98 | CBox& scale(double scale); 99 | CBox& scaleFromCenter(double scale); 100 | CBox& scale(const Vector2D& scale); 101 | CBox& translate(const Vector2D& vec); 102 | CBox& round(); 103 | CBox& transform(const eTransform t, double w, double h); 104 | CBox& addExtents(const SBoxExtents& e); 105 | CBox& expand(const double& value); 106 | CBox& noNegativeSize(); 107 | 108 | CBox copy() const; 109 | CBox intersection(const CBox& other) const; 110 | bool overlaps(const CBox& other) const; 111 | bool inside(const CBox& bound) const; 112 | 113 | /** 114 | * @brief Computes the extents of the box relative to another box. 115 | * @param small Another CBox to compare against. 116 | * @return SBoxExtents representing the extents of the box relative to 'small'. 117 | */ 118 | SBoxExtents extentsFrom(const CBox&); // this is the big box 119 | 120 | /** 121 | * @brief Calculates the middle point of the box. 122 | * @return Vector2D representing the middle point. 123 | */ 124 | Vector2D middle() const; 125 | 126 | /** 127 | * @brief Retrieves the position of the top-left corner of the box. 128 | * @return Vector2D representing the position. 129 | */ 130 | Vector2D pos() const; 131 | 132 | /** 133 | * @brief Retrieves the size (width and height) of the box. 134 | * @return Vector2D representing the size. 135 | */ 136 | Vector2D size() const; 137 | 138 | /** 139 | * @brief Retrieves the size of the box offset by its position. 140 | * @return Vector2D representing the bottom right extent of the box. 141 | */ 142 | Vector2D extent() const; 143 | 144 | /** 145 | * @brief Finds the closest point within the box to a given vector. 146 | * @param vec Vector from which to find the closest point. 147 | * @return Vector2D representing the closest point within the box. 148 | */ 149 | Vector2D closestPoint(const Vector2D& vec) const; 150 | 151 | /** 152 | * @brief Checks if a given point is inside the box. 153 | * @param vec Vector representing the point to check. 154 | * @return True if the point is inside the box, false otherwise. 155 | */ 156 | bool containsPoint(const Vector2D& vec) const; 157 | 158 | /** 159 | * @brief Checks if the box is empty (zero width or height). 160 | * @return True if the box is empty, false otherwise. 161 | */ 162 | bool empty() const; 163 | 164 | double x = 0, y = 0; // Position of the top-left corner of the box. 165 | union { 166 | double w; 167 | double width; 168 | }; 169 | union { 170 | double h; 171 | double height; 172 | }; 173 | 174 | double rot = 0; //< Rotation angle of the box in radians (counterclockwise). 175 | 176 | /** 177 | * @brief Checks equality between two CBox objects. 178 | * @param rhs Another CBox object to compare. 179 | * @return True if both CBox objects are equal, false otherwise. 180 | */ 181 | bool operator==(const CBox& rhs) const { 182 | return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h; 183 | } 184 | 185 | private: 186 | CBox roundInternal(); 187 | }; 188 | } 189 | -------------------------------------------------------------------------------- /include/hyprutils/math/Edges.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace Hyprutils::Math { 5 | 6 | /** 7 | * @brief Flag set of box edges 8 | */ 9 | class CEdges { 10 | public: 11 | enum eEdges : uint8_t { 12 | NONE = 0, 13 | TOP = 1, 14 | LEFT = 2, 15 | BOTTOM = 4, 16 | RIGHT = 8, 17 | }; 18 | 19 | CEdges() = default; 20 | CEdges(eEdges edges) : m_edges(edges) {} 21 | CEdges(uint8_t edges) : m_edges(static_cast(edges)) {} 22 | 23 | bool operator==(const CEdges& other) { 24 | return m_edges == other.m_edges; 25 | } 26 | 27 | CEdges operator|(const CEdges& other) { 28 | return m_edges | other.m_edges; 29 | } 30 | 31 | CEdges operator&(const CEdges& other) { 32 | return m_edges & other.m_edges; 33 | } 34 | 35 | CEdges operator^(const CEdges& other) { 36 | return m_edges ^ other.m_edges; 37 | } 38 | 39 | void operator|=(const CEdges& other) { 40 | m_edges = (*this | other).m_edges; 41 | } 42 | 43 | void operator&=(const CEdges& other) { 44 | m_edges = (*this & other).m_edges; 45 | } 46 | 47 | void operator^=(const CEdges& other) { 48 | m_edges = (*this ^ other).m_edges; 49 | } 50 | 51 | /** 52 | * @return if the edge set contains the top edge. 53 | */ 54 | bool top() { 55 | return m_edges & TOP; 56 | } 57 | 58 | /** 59 | * @return if the edge set contains the left edge. 60 | */ 61 | bool left() { 62 | return m_edges & LEFT; 63 | } 64 | 65 | /** 66 | * @return if the edge set contains the bottom edge. 67 | */ 68 | bool bottom() { 69 | return m_edges & BOTTOM; 70 | } 71 | 72 | /** 73 | * @return if the edge set contains the right edge. 74 | */ 75 | bool right() { 76 | return m_edges & RIGHT; 77 | } 78 | 79 | /** 80 | * @param top The state the top edge should be set to. 81 | */ 82 | void setTop(bool top) { 83 | m_edges = static_cast((m_edges & ~TOP) | (TOP * top)); 84 | } 85 | 86 | /** 87 | * @param left The state the left edge should be set to. 88 | */ 89 | void setLeft(bool left) { 90 | m_edges = static_cast((m_edges & ~LEFT) | (LEFT * left)); 91 | } 92 | 93 | /** 94 | * @param bottom The state the bottom edge should be set to. 95 | */ 96 | void setBottom(bool bottom) { 97 | m_edges = static_cast((m_edges & ~BOTTOM) | (BOTTOM * bottom)); 98 | } 99 | 100 | /** 101 | * @param right The state the right edge should be set to. 102 | */ 103 | void setRight(bool right) { 104 | m_edges = static_cast((m_edges & ~RIGHT) | (RIGHT * right)); 105 | } 106 | 107 | eEdges m_edges = NONE; 108 | }; 109 | 110 | } 111 | -------------------------------------------------------------------------------- /include/hyprutils/math/Mat3x3.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "./Misc.hpp" 9 | 10 | namespace Hyprutils { 11 | namespace Math { 12 | class CBox; 13 | class Vector2D; 14 | 15 | class Mat3x3 { 16 | public: 17 | Mat3x3(); 18 | Mat3x3(std::array); 19 | Mat3x3(std::vector); 20 | 21 | /* create an identity 3x3 matrix */ 22 | static Mat3x3 identity(); 23 | 24 | /* create an output projection matrix */ 25 | static Mat3x3 outputProjection(const Vector2D& size, eTransform transform); 26 | 27 | /* get the matrix as an array, in a row-major order. */ 28 | std::array getMatrix() const; 29 | 30 | /* create a box projection matrix */ 31 | Mat3x3 projectBox(const CBox& box, eTransform transform, float rot = 0.F /* rad, CCW */) const; 32 | 33 | /* in-place functions */ 34 | Mat3x3& transform(eTransform transform); 35 | Mat3x3& rotate(float rot /* rad, CCW */); 36 | Mat3x3& scale(const Vector2D& scale); 37 | Mat3x3& scale(const float scale); 38 | Mat3x3& translate(const Vector2D& offset); 39 | Mat3x3& transpose(); 40 | Mat3x3& multiply(const Mat3x3& other); 41 | 42 | /* misc utils */ 43 | Mat3x3 copy() const; 44 | std::string toString() const; 45 | 46 | bool operator==(const Mat3x3& other) const { 47 | return other.matrix == matrix; 48 | } 49 | 50 | friend std::ostream& operator<<(std::ostream& os, const Mat3x3& mat) { 51 | os << mat.toString(); 52 | return os; 53 | } 54 | 55 | private: 56 | std::array matrix; 57 | }; 58 | } 59 | } -------------------------------------------------------------------------------- /include/hyprutils/math/Misc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Hyprutils { 4 | namespace Math { 5 | enum eTransform { 6 | HYPRUTILS_TRANSFORM_NORMAL = 0, 7 | HYPRUTILS_TRANSFORM_90 = 1, 8 | HYPRUTILS_TRANSFORM_180 = 2, 9 | HYPRUTILS_TRANSFORM_270 = 3, 10 | HYPRUTILS_TRANSFORM_FLIPPED = 4, 11 | HYPRUTILS_TRANSFORM_FLIPPED_90 = 5, 12 | HYPRUTILS_TRANSFORM_FLIPPED_180 = 6, 13 | HYPRUTILS_TRANSFORM_FLIPPED_270 = 7, 14 | }; 15 | } 16 | } -------------------------------------------------------------------------------- /include/hyprutils/math/Region.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "Vector2D.hpp" 6 | #include "Box.hpp" 7 | 8 | namespace Hyprutils { 9 | namespace Math { 10 | class CRegion { 11 | public: 12 | /* Create an empty region */ 13 | CRegion(); 14 | /* Create from a reference. Copies, does not own. */ 15 | CRegion(const pixman_region32_t* const ref); 16 | /* Create from a box */ 17 | CRegion(double x, double y, double w, double h); 18 | /* Create from a CBox */ 19 | CRegion(const CBox& box); 20 | /* Create from a pixman_box32_t */ 21 | CRegion(pixman_box32_t* box); 22 | 23 | CRegion(const CRegion&); 24 | CRegion(CRegion&&); 25 | 26 | ~CRegion(); 27 | 28 | CRegion& operator=(CRegion&& other) { 29 | pixman_region32_copy(&m_rRegion, other.pixman()); 30 | return *this; 31 | } 32 | 33 | CRegion& operator=(CRegion& other) { 34 | pixman_region32_copy(&m_rRegion, other.pixman()); 35 | return *this; 36 | } 37 | 38 | CRegion& clear(); 39 | CRegion& set(const CRegion& other); 40 | CRegion& add(const CRegion& other); 41 | CRegion& add(double x, double y, double w, double h); 42 | CRegion& add(const CBox& other); 43 | CRegion& subtract(const CRegion& other); 44 | CRegion& intersect(const CRegion& other); 45 | CRegion& intersect(double x, double y, double w, double h); 46 | CRegion& translate(const Vector2D& vec); 47 | CRegion& transform(const eTransform t, double w, double h); 48 | CRegion& invert(pixman_box32_t* box); 49 | CRegion& invert(const CBox& box); 50 | CRegion& scale(float scale); 51 | CRegion& scale(const Vector2D& scale); 52 | CRegion& expand(double units); 53 | CRegion& rationalize(); 54 | CBox getExtents(); 55 | bool containsPoint(const Vector2D& vec) const; 56 | bool empty() const; 57 | Vector2D closestPoint(const Vector2D& vec) const; 58 | CRegion copy() const; 59 | 60 | std::vector getRects() const; 61 | 62 | // 63 | pixman_region32_t* pixman() { 64 | return &m_rRegion; 65 | } 66 | 67 | private: 68 | pixman_region32_t m_rRegion; 69 | }; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /include/hyprutils/math/Vector2D.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Hyprutils { 7 | namespace Math { 8 | class Vector2D { 9 | public: 10 | Vector2D(double, double); 11 | Vector2D(int, int); 12 | Vector2D(); 13 | ~Vector2D(); 14 | 15 | double x = 0; 16 | double y = 0; 17 | 18 | // returns the scale 19 | double normalize(); 20 | 21 | Vector2D operator+(const Vector2D& a) const { 22 | return Vector2D(this->x + a.x, this->y + a.y); 23 | } 24 | Vector2D operator-(const Vector2D& a) const { 25 | return Vector2D(this->x - a.x, this->y - a.y); 26 | } 27 | Vector2D operator-() const { 28 | return Vector2D(-this->x, -this->y); 29 | } 30 | Vector2D operator*(const double& a) const { 31 | return Vector2D(this->x * a, this->y * a); 32 | } 33 | Vector2D operator/(const double& a) const { 34 | return Vector2D(this->x / a, this->y / a); 35 | } 36 | 37 | bool operator==(const Vector2D& a) const { 38 | return a.x == x && a.y == y; 39 | } 40 | 41 | bool operator!=(const Vector2D& a) const { 42 | return a.x != x || a.y != y; 43 | } 44 | 45 | Vector2D operator*(const Vector2D& a) const { 46 | return Vector2D(this->x * a.x, this->y * a.y); 47 | } 48 | 49 | Vector2D operator/(const Vector2D& a) const { 50 | return Vector2D(this->x / a.x, this->y / a.y); 51 | } 52 | 53 | bool operator>(const Vector2D& a) const { 54 | return this->x > a.x && this->y > a.y; 55 | } 56 | 57 | bool operator<(const Vector2D& a) const { 58 | return this->x < a.x && this->y < a.y; 59 | } 60 | Vector2D& operator+=(const Vector2D& a) { 61 | this->x += a.x; 62 | this->y += a.y; 63 | return *this; 64 | } 65 | Vector2D& operator-=(const Vector2D& a) { 66 | this->x -= a.x; 67 | this->y -= a.y; 68 | return *this; 69 | } 70 | Vector2D& operator*=(const Vector2D& a) { 71 | this->x *= a.x; 72 | this->y *= a.y; 73 | return *this; 74 | } 75 | Vector2D& operator/=(const Vector2D& a) { 76 | this->x /= a.x; 77 | this->y /= a.y; 78 | return *this; 79 | } 80 | Vector2D& operator*=(const double& a) { 81 | this->x *= a; 82 | this->y *= a; 83 | return *this; 84 | } 85 | Vector2D& operator/=(const double& a) { 86 | this->x /= a; 87 | this->y /= a; 88 | return *this; 89 | } 90 | 91 | double distance(const Vector2D& other) const; 92 | double distanceSq(const Vector2D& other) const; 93 | double size() const; 94 | Vector2D clamp(const Vector2D& min, const Vector2D& max = Vector2D{-1, -1}) const; 95 | 96 | Vector2D floor() const; 97 | Vector2D round() const; 98 | 99 | Vector2D getComponentMax(const Vector2D& other) const; 100 | }; 101 | } 102 | } 103 | 104 | // absolutely ridiculous formatter spec parsing 105 | #define AQ_FORMAT_PARSE(specs__, type__) \ 106 | template \ 107 | constexpr auto parse(FormatContext& ctx) { \ 108 | auto it = ctx.begin(); \ 109 | for (; it != ctx.end() && *it != '}'; it++) { \ 110 | switch (*it) { specs__ default : throw std::format_error("invalid format specification"); } \ 111 | } \ 112 | return it; \ 113 | } 114 | 115 | #define AQ_FORMAT_FLAG(spec__, flag__) \ 116 | case spec__: (flag__) = true; break; 117 | 118 | #define AQ_FORMAT_NUMBER(buf__) \ 119 | case '0': \ 120 | case '1': \ 121 | case '2': \ 122 | case '3': \ 123 | case '4': \ 124 | case '5': \ 125 | case '6': \ 126 | case '7': \ 127 | case '8': \ 128 | case '9': (buf__).push_back(*it); break; 129 | 130 | /** 131 | format specification 132 | - 'j', as json array 133 | - 'X', same as std::format("{}x{}", vec.x, vec.y) 134 | - number, floating point precision, use `0` to format as integer 135 | */ 136 | template 137 | struct std::formatter : std::formatter { 138 | bool formatJson = false; 139 | bool formatX = false; 140 | std::string precision = ""; 141 | AQ_FORMAT_PARSE(AQ_FORMAT_FLAG('j', formatJson) // 142 | AQ_FORMAT_FLAG('X', formatX) // 143 | AQ_FORMAT_NUMBER(precision), 144 | Hyprutils::Math::Vector2D) 145 | 146 | template 147 | auto format(const Hyprutils::Math::Vector2D& vec, FormatContext& ctx) const { 148 | std::string formatString = precision.empty() ? "{}" : std::format("{{:.{}f}}", precision); 149 | 150 | if (formatJson) 151 | formatString = std::format("[{0}, {0}]", formatString); 152 | else if (formatX) 153 | formatString = std::format("{0}x{0}", formatString); 154 | else 155 | formatString = std::format("[Vector2D: x: {0}, y: {0}]", formatString); 156 | try { 157 | string buf = std::vformat(formatString, std::make_format_args(vec.x, vec.y)); 158 | return std::format_to(ctx.out(), "{}", buf); 159 | } catch (std::format_error& e) { return std::format_to(ctx.out(), "[{}, {}]", vec.x, vec.y); } 160 | } 161 | }; 162 | -------------------------------------------------------------------------------- /include/hyprutils/memory/ImplBase.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Hyprutils { 6 | namespace Memory { 7 | namespace Impl_ { 8 | class impl_base { 9 | public: 10 | virtual ~impl_base() {}; 11 | 12 | virtual void inc() noexcept = 0; 13 | virtual void dec() noexcept = 0; 14 | virtual void incWeak() noexcept = 0; 15 | virtual void decWeak() noexcept = 0; 16 | virtual unsigned int ref() noexcept = 0; 17 | virtual unsigned int wref() noexcept = 0; 18 | virtual void destroy() noexcept = 0; 19 | virtual bool destroying() noexcept = 0; 20 | virtual bool dataNonNull() noexcept = 0; 21 | virtual bool lockable() noexcept = 0; 22 | virtual void* getData() noexcept = 0; 23 | }; 24 | 25 | template 26 | class impl : public impl_base { 27 | public: 28 | impl(T* data, bool lock = true) noexcept : _lockable(lock), _data(data) { 29 | ; 30 | } 31 | 32 | /* strong refcount */ 33 | unsigned int _ref = 0; 34 | /* weak refcount */ 35 | unsigned int _weak = 0; 36 | /* if this is lockable (shared) */ 37 | bool _lockable = true; 38 | 39 | T* _data = nullptr; 40 | 41 | friend void swap(impl*& a, impl*& b) { 42 | impl* tmp = a; 43 | a = b; 44 | b = tmp; 45 | } 46 | 47 | /* if the destructor was called, 48 | creating shared_ptrs is no longer valid */ 49 | bool _destroying = false; 50 | 51 | void _destroy() { 52 | if (!_data || _destroying) 53 | return; 54 | 55 | // first, we destroy the data, but keep the pointer. 56 | // this way, weak pointers will still be able to 57 | // reference and use, but no longer create shared ones. 58 | _destroying = true; 59 | __deleter(_data); 60 | // now, we can reset the data and call it a day. 61 | _data = nullptr; 62 | _destroying = false; 63 | } 64 | 65 | std::default_delete __deleter{}; 66 | 67 | // 68 | virtual void inc() noexcept { 69 | _ref++; 70 | } 71 | 72 | virtual void dec() noexcept { 73 | _ref--; 74 | } 75 | 76 | virtual void incWeak() noexcept { 77 | _weak++; 78 | } 79 | 80 | virtual void decWeak() noexcept { 81 | _weak--; 82 | } 83 | 84 | virtual unsigned int ref() noexcept { 85 | return _ref; 86 | } 87 | 88 | virtual unsigned int wref() noexcept { 89 | return _weak; 90 | } 91 | 92 | virtual void destroy() noexcept { 93 | _destroy(); 94 | } 95 | 96 | virtual bool destroying() noexcept { 97 | return _destroying; 98 | } 99 | 100 | virtual bool lockable() noexcept { 101 | return _lockable; 102 | } 103 | 104 | virtual bool dataNonNull() noexcept { 105 | return _data != nullptr; 106 | } 107 | 108 | virtual void* getData() noexcept { 109 | return _data; 110 | } 111 | 112 | virtual ~impl() { 113 | destroy(); 114 | } 115 | }; 116 | } 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /include/hyprutils/memory/SharedPtr.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "ImplBase.hpp" 5 | 6 | /* 7 | This is a custom impl of std::shared_ptr. 8 | It is not thread-safe like the STL one, 9 | but Hyprland is single-threaded anyways. 10 | 11 | It differs a bit from how the STL one works, 12 | namely in the fact that it keeps the T* inside the 13 | control block, and that you can still make a CWeakPtr 14 | or deref an existing one inside the destructor. 15 | */ 16 | 17 | namespace Hyprutils { 18 | namespace Memory { 19 | 20 | template 21 | class CSharedPointer { 22 | public: 23 | template 24 | using validHierarchy = typename std::enable_if&, X>::value, CSharedPointer&>::type; 25 | template 26 | using isConstructible = typename std::enable_if::value>::type; 27 | 28 | /* creates a new shared pointer managing a resource 29 | avoid calling. Could duplicate ownership. Prefer makeShared */ 30 | explicit CSharedPointer(T* object) noexcept { 31 | impl_ = new Impl_::impl(object); 32 | increment(); 33 | } 34 | 35 | /* creates a shared pointer from a reference */ 36 | template > 37 | CSharedPointer(const CSharedPointer& ref) noexcept { 38 | impl_ = ref.impl_; 39 | increment(); 40 | } 41 | 42 | CSharedPointer(const CSharedPointer& ref) noexcept { 43 | impl_ = ref.impl_; 44 | increment(); 45 | } 46 | 47 | template > 48 | CSharedPointer(CSharedPointer&& ref) noexcept { 49 | std::swap(impl_, ref.impl_); 50 | } 51 | 52 | CSharedPointer(CSharedPointer&& ref) noexcept { 53 | std::swap(impl_, ref.impl_); 54 | } 55 | 56 | /* allows weakPointer to create from an impl */ 57 | CSharedPointer(Impl_::impl_base* implementation) noexcept { 58 | impl_ = implementation; 59 | increment(); 60 | } 61 | 62 | /* creates an empty shared pointer with no implementation */ 63 | CSharedPointer() noexcept { 64 | ; // empty 65 | } 66 | 67 | /* creates an empty shared pointer with no implementation */ 68 | CSharedPointer(std::nullptr_t) noexcept { 69 | ; // empty 70 | } 71 | 72 | ~CSharedPointer() { 73 | decrement(); 74 | } 75 | 76 | template 77 | validHierarchy&> operator=(const CSharedPointer& rhs) { 78 | if (impl_ == rhs.impl_) 79 | return *this; 80 | 81 | decrement(); 82 | impl_ = rhs.impl_; 83 | increment(); 84 | return *this; 85 | } 86 | 87 | CSharedPointer& operator=(const CSharedPointer& rhs) { 88 | if (impl_ == rhs.impl_) 89 | return *this; 90 | 91 | decrement(); 92 | impl_ = rhs.impl_; 93 | increment(); 94 | return *this; 95 | } 96 | 97 | template 98 | validHierarchy&> operator=(CSharedPointer&& rhs) { 99 | std::swap(impl_, rhs.impl_); 100 | return *this; 101 | } 102 | 103 | CSharedPointer& operator=(CSharedPointer&& rhs) { 104 | std::swap(impl_, rhs.impl_); 105 | return *this; 106 | } 107 | 108 | operator bool() const { 109 | return impl_ && impl_->dataNonNull(); 110 | } 111 | 112 | bool operator==(const CSharedPointer& rhs) const { 113 | return impl_ == rhs.impl_; 114 | } 115 | 116 | bool operator()(const CSharedPointer& lhs, const CSharedPointer& rhs) const { 117 | return reinterpret_cast(lhs.impl_) < reinterpret_cast(rhs.impl_); 118 | } 119 | 120 | bool operator<(const CSharedPointer& rhs) const { 121 | return reinterpret_cast(impl_) < reinterpret_cast(rhs.impl_); 122 | } 123 | 124 | T* operator->() const { 125 | return get(); 126 | } 127 | 128 | T& operator*() const { 129 | return *get(); 130 | } 131 | 132 | void reset() { 133 | decrement(); 134 | impl_ = nullptr; 135 | } 136 | 137 | T* get() const { 138 | return impl_ ? static_cast(impl_->getData()) : nullptr; 139 | } 140 | 141 | unsigned int strongRef() const { 142 | return impl_ ? impl_->ref() : 0; 143 | } 144 | 145 | Impl_::impl_base* impl_ = nullptr; 146 | 147 | private: 148 | /* 149 | no-op if there is no impl_ 150 | may delete the stored object if ref == 0 151 | may delete and reset impl_ if ref == 0 and weak == 0 152 | */ 153 | void decrement() { 154 | if (!impl_) 155 | return; 156 | 157 | impl_->dec(); 158 | 159 | // if ref == 0, we can destroy impl 160 | if (impl_->ref() == 0) 161 | destroyImpl(); 162 | } 163 | /* no-op if there is no impl_ */ 164 | void increment() { 165 | if (!impl_) 166 | return; 167 | 168 | impl_->inc(); 169 | } 170 | 171 | /* destroy the pointed-to object 172 | if able, will also destroy impl */ 173 | void destroyImpl() { 174 | // destroy the impl contents 175 | impl_->destroy(); 176 | 177 | // check for weak refs, if zero, we can also delete impl_ 178 | if (impl_->wref() == 0) { 179 | delete impl_; 180 | impl_ = nullptr; 181 | } 182 | } 183 | }; 184 | 185 | template 186 | static CSharedPointer makeShared(Args&&... args) { 187 | return CSharedPointer(new U(std::forward(args)...)); 188 | } 189 | 190 | template 191 | CSharedPointer reinterpretPointerCast(const CSharedPointer& ref) { 192 | return CSharedPointer(ref.impl_); 193 | } 194 | } 195 | } 196 | 197 | template 198 | struct std::hash> { 199 | std::size_t operator()(const Hyprutils::Memory::CSharedPointer& p) const noexcept { 200 | return std::hash{}(p.impl_); 201 | } 202 | }; 203 | -------------------------------------------------------------------------------- /include/hyprutils/memory/UniquePtr.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ImplBase.hpp" 4 | 5 | /* 6 | This is a custom impl of std::unique_ptr. 7 | In contrast to the STL one, it allows for 8 | creation of a weak_ptr, that will then be unable 9 | to be locked. 10 | */ 11 | 12 | namespace Hyprutils { 13 | namespace Memory { 14 | template 15 | class CUniquePointer { 16 | public: 17 | template 18 | using validHierarchy = typename std::enable_if&, X>::value, CUniquePointer&>::type; 19 | template 20 | using isConstructible = typename std::enable_if::value>::type; 21 | 22 | /* creates a new unique pointer managing a resource 23 | avoid calling. Could duplicate ownership. Prefer makeUnique */ 24 | explicit CUniquePointer(T* object) noexcept { 25 | impl_ = new Impl_::impl(object, false); 26 | increment(); 27 | } 28 | 29 | /* creates a shared pointer from a reference */ 30 | template > 31 | CUniquePointer(const CUniquePointer& ref) = delete; 32 | CUniquePointer(const CUniquePointer& ref) = delete; 33 | 34 | template > 35 | CUniquePointer(CUniquePointer&& ref) noexcept { 36 | std::swap(impl_, ref.impl_); 37 | } 38 | 39 | CUniquePointer(CUniquePointer&& ref) noexcept { 40 | std::swap(impl_, ref.impl_); 41 | } 42 | 43 | /* creates an empty unique pointer with no implementation */ 44 | CUniquePointer() noexcept { 45 | ; // empty 46 | } 47 | 48 | /* creates an empty unique pointer with no implementation */ 49 | CUniquePointer(std::nullptr_t) noexcept { 50 | ; // empty 51 | } 52 | 53 | ~CUniquePointer() { 54 | decrement(); 55 | } 56 | 57 | template 58 | validHierarchy&> operator=(const CUniquePointer& rhs) = delete; 59 | CUniquePointer& operator=(const CUniquePointer& rhs) = delete; 60 | 61 | template 62 | validHierarchy&> operator=(CUniquePointer&& rhs) { 63 | std::swap(impl_, rhs.impl_); 64 | return *this; 65 | } 66 | 67 | CUniquePointer& operator=(CUniquePointer&& rhs) { 68 | std::swap(impl_, rhs.impl_); 69 | return *this; 70 | } 71 | 72 | operator bool() const { 73 | return impl_; 74 | } 75 | 76 | bool operator()(const CUniquePointer& lhs, const CUniquePointer& rhs) const { 77 | return reinterpret_cast(lhs.impl_) < reinterpret_cast(rhs.impl_); 78 | } 79 | 80 | T* operator->() const { 81 | return get(); 82 | } 83 | 84 | T& operator*() const { 85 | return *get(); 86 | } 87 | 88 | void reset() { 89 | decrement(); 90 | impl_ = nullptr; 91 | } 92 | 93 | T* get() const { 94 | return impl_ ? static_cast(impl_->getData()) : nullptr; 95 | } 96 | 97 | Impl_::impl_base* impl_ = nullptr; 98 | 99 | private: 100 | /* 101 | no-op if there is no impl_ 102 | may delete the stored object if ref == 0 103 | may delete and reset impl_ if ref == 0 and weak == 0 104 | */ 105 | void decrement() { 106 | if (!impl_) 107 | return; 108 | 109 | impl_->dec(); 110 | 111 | // if ref == 0, we can destroy impl 112 | if (impl_->ref() == 0) 113 | destroyImpl(); 114 | } 115 | /* no-op if there is no impl_ */ 116 | void increment() { 117 | if (!impl_) 118 | return; 119 | 120 | impl_->inc(); 121 | } 122 | 123 | /* destroy the pointed-to object 124 | if able, will also destroy impl */ 125 | void destroyImpl() { 126 | // destroy the impl contents 127 | impl_->destroy(); 128 | 129 | // check for weak refs, if zero, we can also delete impl_ 130 | if (impl_->wref() == 0) { 131 | delete impl_; 132 | impl_ = nullptr; 133 | } 134 | } 135 | }; 136 | 137 | template 138 | static CUniquePointer makeUnique(Args&&... args) { 139 | return CUniquePointer(new U(std::forward(args)...)); 140 | } 141 | } 142 | } 143 | 144 | template 145 | struct std::hash> { 146 | std::size_t operator()(const Hyprutils::Memory::CUniquePointer& p) const noexcept { 147 | return std::hash{}(p.impl_); 148 | } 149 | }; -------------------------------------------------------------------------------- /include/hyprutils/memory/WeakPtr.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "./SharedPtr.hpp" 4 | #include "./UniquePtr.hpp" 5 | 6 | /* 7 | This is a Hyprland implementation of std::weak_ptr. 8 | 9 | See SharedPtr.hpp for more info on how it's different. 10 | */ 11 | 12 | namespace Hyprutils { 13 | namespace Memory { 14 | template 15 | class CWeakPointer { 16 | public: 17 | template 18 | using validHierarchy = typename std::enable_if&, X>::value, CWeakPointer&>::type; 19 | template 20 | using isConstructible = typename std::enable_if::value>::type; 21 | 22 | /* create a weak ptr from a reference */ 23 | template > 24 | CWeakPointer(const CSharedPointer& ref) noexcept { 25 | if (!ref.impl_) 26 | return; 27 | 28 | impl_ = ref.impl_; 29 | incrementWeak(); 30 | } 31 | 32 | /* create a weak ptr from a reference */ 33 | template > 34 | CWeakPointer(const CUniquePointer& ref) noexcept { 35 | if (!ref.impl_) 36 | return; 37 | 38 | impl_ = ref.impl_; 39 | incrementWeak(); 40 | } 41 | 42 | /* create a weak ptr from another weak ptr */ 43 | template > 44 | CWeakPointer(const CWeakPointer& ref) noexcept { 45 | if (!ref.impl_) 46 | return; 47 | 48 | impl_ = ref.impl_; 49 | incrementWeak(); 50 | } 51 | 52 | CWeakPointer(const CWeakPointer& ref) noexcept { 53 | if (!ref.impl_) 54 | return; 55 | 56 | impl_ = ref.impl_; 57 | incrementWeak(); 58 | } 59 | 60 | template > 61 | CWeakPointer(CWeakPointer&& ref) noexcept { 62 | std::swap(impl_, ref.impl_); 63 | } 64 | 65 | CWeakPointer(CWeakPointer&& ref) noexcept { 66 | std::swap(impl_, ref.impl_); 67 | } 68 | 69 | /* create a weak ptr from another weak ptr with assignment */ 70 | template 71 | validHierarchy&> operator=(const CWeakPointer& rhs) { 72 | if (impl_ == rhs.impl_) 73 | return *this; 74 | 75 | decrementWeak(); 76 | impl_ = rhs.impl_; 77 | incrementWeak(); 78 | return *this; 79 | } 80 | 81 | CWeakPointer& operator=(const CWeakPointer& rhs) { 82 | if (impl_ == rhs.impl_) 83 | return *this; 84 | 85 | decrementWeak(); 86 | impl_ = rhs.impl_; 87 | incrementWeak(); 88 | return *this; 89 | } 90 | 91 | /* create a weak ptr from a shared ptr with assignment */ 92 | template 93 | validHierarchy&> operator=(const CSharedPointer& rhs) { 94 | if (reinterpret_cast(impl_) == reinterpret_cast(rhs.impl_)) 95 | return *this; 96 | 97 | decrementWeak(); 98 | impl_ = rhs.impl_; 99 | incrementWeak(); 100 | return *this; 101 | } 102 | 103 | /* create an empty weak ptr */ 104 | CWeakPointer() { 105 | ; 106 | } 107 | 108 | ~CWeakPointer() { 109 | decrementWeak(); 110 | } 111 | 112 | /* expired MAY return true even if the pointer is still stored. 113 | the situation would be e.g. self-weak pointer in a destructor. 114 | for pointer validity, use valid() */ 115 | bool expired() const { 116 | return !impl_ || !impl_->dataNonNull() || impl_->destroying(); 117 | } 118 | 119 | /* this means the pointed-to object is not yet deleted and can still be 120 | referenced, but it might be in the process of being deleted. 121 | check !expired() if you want to check whether it's valid and 122 | assignable to a SP. */ 123 | bool valid() const { 124 | return impl_ && impl_->dataNonNull(); 125 | } 126 | 127 | void reset() { 128 | decrementWeak(); 129 | impl_ = nullptr; 130 | } 131 | 132 | CSharedPointer lock() const { 133 | if (!impl_ || !impl_->dataNonNull() || impl_->destroying() || !impl_->lockable()) 134 | return {}; 135 | 136 | return CSharedPointer(impl_); 137 | } 138 | 139 | /* this returns valid() */ 140 | operator bool() const { 141 | return valid(); 142 | } 143 | 144 | bool operator==(const CWeakPointer& rhs) const { 145 | return impl_ == rhs.impl_; 146 | } 147 | 148 | bool operator==(const CSharedPointer& rhs) const { 149 | return impl_ == rhs.impl_; 150 | } 151 | 152 | bool operator==(const CUniquePointer& rhs) const { 153 | return impl_ == rhs.impl_; 154 | } 155 | 156 | bool operator==(std::nullptr_t) const { 157 | return !valid(); 158 | } 159 | 160 | bool operator!=(std::nullptr_t) const { 161 | return valid(); 162 | } 163 | 164 | bool operator()(const CWeakPointer& lhs, const CWeakPointer& rhs) const { 165 | return reinterpret_cast(lhs.impl_) < reinterpret_cast(rhs.impl_); 166 | } 167 | 168 | bool operator<(const CWeakPointer& rhs) const { 169 | return reinterpret_cast(impl_) < reinterpret_cast(rhs.impl_); 170 | } 171 | 172 | T* get() const { 173 | return impl_ ? static_cast(impl_->getData()) : nullptr; 174 | } 175 | 176 | T* operator->() const { 177 | return get(); 178 | } 179 | 180 | T& operator*() const { 181 | return *get(); 182 | } 183 | 184 | Impl_::impl_base* impl_ = nullptr; 185 | 186 | private: 187 | /* no-op if there is no impl_ */ 188 | void decrementWeak() { 189 | if (!impl_) 190 | return; 191 | 192 | impl_->decWeak(); 193 | 194 | // we need to check for ->destroying, 195 | // because otherwise we could destroy here 196 | // and have a shared_ptr destroy the same thing 197 | // later (in situations where we have a weak_ptr to self) 198 | if (impl_->wref() == 0 && impl_->ref() == 0 && !impl_->destroying()) { 199 | delete impl_; 200 | impl_ = nullptr; 201 | } 202 | } 203 | /* no-op if there is no impl_ */ 204 | void incrementWeak() { 205 | if (!impl_) 206 | return; 207 | 208 | impl_->incWeak(); 209 | } 210 | }; 211 | } 212 | } 213 | 214 | template 215 | struct std::hash> { 216 | std::size_t operator()(const Hyprutils::Memory::CWeakPointer& p) const noexcept { 217 | return std::hash{}(p.impl_); 218 | } 219 | }; 220 | -------------------------------------------------------------------------------- /include/hyprutils/os/FileDescriptor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | namespace Hyprutils { 5 | namespace OS { 6 | class CFileDescriptor { 7 | public: 8 | CFileDescriptor() = default; 9 | explicit CFileDescriptor(int const fd); 10 | CFileDescriptor(CFileDescriptor&&); 11 | CFileDescriptor& operator=(CFileDescriptor&&); 12 | ~CFileDescriptor(); 13 | 14 | CFileDescriptor(const CFileDescriptor&) = delete; 15 | CFileDescriptor& operator=(const CFileDescriptor&) = delete; 16 | 17 | bool operator==(const CFileDescriptor& rhs) const { 18 | return m_fd == rhs.m_fd; 19 | } 20 | 21 | bool isValid() const; 22 | int get() const; 23 | int getFlags() const; 24 | bool setFlags(int flags); 25 | int take(); 26 | void reset(); 27 | CFileDescriptor duplicate(int flags = F_DUPFD_CLOEXEC) const; 28 | 29 | bool isReadable() const; 30 | bool isClosed() const; 31 | 32 | static bool isReadable(int fd); 33 | static bool isClosed(int fd); 34 | 35 | private: 36 | int m_fd = -1; 37 | }; 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /include/hyprutils/os/Process.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace Hyprutils { 9 | namespace OS { 10 | class CProcess { 11 | public: 12 | /* Creates a process object, doesn't run yet */ 13 | CProcess(const std::string& binary_, const std::vector& args_); 14 | ~CProcess(); 15 | 16 | void addEnv(const std::string& name, const std::string& value); 17 | 18 | // only for async, sync doesn't make sense 19 | void setStdoutFD(int fd); 20 | // only for async, sync doesn't make sense 21 | void setStderrFD(int fd); 22 | 23 | /* Run the process, synchronously, get the stdout and stderr. False on fail */ 24 | bool runSync(); 25 | 26 | /* Run the process, asynchronously. This will detach the process from this object (and process) and let it live a happy life. False on fail. */ 27 | bool runAsync(); 28 | 29 | // only populated when ran sync 30 | const std::string& stdOut(); 31 | const std::string& stdErr(); 32 | 33 | pid_t pid(); 34 | 35 | // only for sync 36 | int exitCode(); 37 | 38 | private: 39 | struct impl; 40 | impl* m_impl; 41 | }; 42 | } 43 | } -------------------------------------------------------------------------------- /include/hyprutils/path/Path.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../string/VarList.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Hyprutils { 8 | namespace Path { 9 | /** Check whether a config in the form basePath/hypr/programName.conf exists. 10 | @param basePath the path where the config will be searched 11 | @param programName name of the program (and config file) to search for 12 | */ 13 | bool checkConfigExists(const std::string basePath, const std::string programName); 14 | 15 | /** Constructs a full config path given the basePath and programName. 16 | @param basePath the path where the config hypr/programName.conf is located 17 | @param programName name of the program (and config file) 18 | */ 19 | std::string fullConfigPath(const std::string basePath, const std::string programName); 20 | 21 | /** Retrieves the absolute path of the $HOME env variable. 22 | */ 23 | std::optional getHome(); 24 | 25 | /** Retrieves a CVarList of paths from the $XDG_CONFIG_DIRS env variable. 26 | */ 27 | std::optional getXdgConfigDirs(); 28 | 29 | /** Retrieves the absolute path of the $XDG_CONFIG_HOME env variable. 30 | */ 31 | std::optional getXdgConfigHome(); 32 | 33 | /** Searches for a config according to the XDG Base Directory specification. 34 | Returns a pair of the full path to a config and the base path. 35 | Returns std::nullopt in case of a non-existent value. 36 | @param programName name of the program (and config file) 37 | */ 38 | 39 | using T = std::optional; 40 | std::pair findConfig(const std::string programName); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /include/hyprutils/signal/Listener.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Hyprutils { 8 | namespace Signal { 9 | class CSignal; 10 | 11 | class CSignalListener { 12 | public: 13 | CSignalListener(std::function handler); 14 | 15 | CSignalListener(CSignalListener&&) = delete; 16 | CSignalListener(CSignalListener&) = delete; 17 | CSignalListener(const CSignalListener&) = delete; 18 | CSignalListener(const CSignalListener&&) = delete; 19 | 20 | void emit(std::any data); 21 | 22 | private: 23 | std::function m_fHandler; 24 | }; 25 | 26 | typedef Hyprutils::Memory::CSharedPointer CHyprSignalListener; 27 | 28 | class CStaticSignalListener { 29 | public: 30 | CStaticSignalListener(std::function handler, void* owner); 31 | 32 | CStaticSignalListener(CStaticSignalListener&&) = delete; 33 | CStaticSignalListener(CStaticSignalListener&) = delete; 34 | CStaticSignalListener(const CStaticSignalListener&) = delete; 35 | CStaticSignalListener(const CStaticSignalListener&&) = delete; 36 | 37 | void emit(std::any data); 38 | 39 | private: 40 | void* m_pOwner = nullptr; 41 | std::function m_fHandler; 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /include/hyprutils/signal/Signal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "./Listener.hpp" 9 | 10 | namespace Hyprutils { 11 | namespace Signal { 12 | class CSignal { 13 | public: 14 | void emit(std::any data = {}); 15 | 16 | // 17 | [[nodiscard("Listener is unregistered when the ptr is lost")]] CHyprSignalListener registerListener(std::function handler); 18 | 19 | // this is for static listeners. They die with this signal. 20 | // TODO: can we somehow rid of the void* data and make it a custom this? 21 | void registerStaticListener(std::function handler, void* owner); 22 | 23 | private: 24 | std::vector> m_vListeners; 25 | std::vector> m_vStaticListeners; 26 | }; 27 | } 28 | } -------------------------------------------------------------------------------- /include/hyprutils/string/ConstVarList.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace Hyprutils { 7 | namespace String { 8 | class CConstVarList { 9 | public: 10 | /** Split string into an immutable arg list 11 | @param lastArgNo stop splitting after argv reaches maximum size, last arg will contain rest of unsplit args 12 | @param delim if delimiter is 's', use std::isspace 13 | @param removeEmpty remove empty args from argv 14 | */ 15 | CConstVarList(const std::string& in, const size_t lastArgNo = 0, const char delim = ',', const bool removeEmpty = false); 16 | 17 | ~CConstVarList() = default; 18 | 19 | size_t size() const { 20 | return m_args.size(); 21 | } 22 | 23 | std::string join(const std::string& joiner, size_t from = 0, size_t to = 0) const; 24 | 25 | void map(std::function func) { 26 | for (auto& s : m_args) 27 | func(s); 28 | } 29 | 30 | std::string_view operator[](const size_t& idx) const { 31 | if (idx >= m_args.size()) 32 | return ""; 33 | return m_args[idx]; 34 | } 35 | 36 | // for range-based loops 37 | std::vector::iterator begin() { 38 | return m_args.begin(); 39 | } 40 | std::vector::const_iterator begin() const { 41 | return m_args.begin(); 42 | } 43 | std::vector::iterator end() { 44 | return m_args.end(); 45 | } 46 | std::vector::const_iterator end() const { 47 | return m_args.end(); 48 | } 49 | 50 | bool contains(const std::string_view& el) { 51 | for (auto& a : m_args) { 52 | if (a == el) 53 | return true; 54 | } 55 | 56 | return false; 57 | } 58 | 59 | private: 60 | std::string m_str; 61 | std::vector m_args; 62 | }; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /include/hyprutils/string/String.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace Hyprutils { 5 | namespace String { 6 | // trims beginning and end of whitespace characters 7 | std::string trim(const std::string& in); 8 | bool isNumber(const std::string& str, bool allowfloat = false); 9 | void replaceInString(std::string& string, const std::string& what, const std::string& to); 10 | }; 11 | }; -------------------------------------------------------------------------------- /include/hyprutils/string/VarList.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace Hyprutils { 7 | namespace String { 8 | class CVarList { 9 | public: 10 | /** Split string into arg list 11 | @param lastArgNo stop splitting after argv reaches maximum size, last arg will contain rest of unsplit args 12 | @param delim if delimiter is 's', use std::isspace 13 | @param removeEmpty remove empty args from argv 14 | */ 15 | CVarList(const std::string& in, const size_t lastArgNo = 0, const char delim = ',', const bool removeEmpty = false); 16 | 17 | ~CVarList() = default; 18 | 19 | size_t size() const { 20 | return m_vArgs.size(); 21 | } 22 | 23 | std::string join(const std::string& joiner, size_t from = 0, size_t to = 0) const; 24 | 25 | void map(std::function func) { 26 | for (auto& s : m_vArgs) 27 | func(s); 28 | } 29 | 30 | void append(const std::string arg) { 31 | m_vArgs.emplace_back(arg); 32 | } 33 | 34 | std::string operator[](const size_t& idx) const { 35 | if (idx >= m_vArgs.size()) 36 | return ""; 37 | return m_vArgs[idx]; 38 | } 39 | 40 | // for range-based loops 41 | std::vector::iterator begin() { 42 | return m_vArgs.begin(); 43 | } 44 | std::vector::const_iterator begin() const { 45 | return m_vArgs.begin(); 46 | } 47 | std::vector::iterator end() { 48 | return m_vArgs.end(); 49 | } 50 | std::vector::const_iterator end() const { 51 | return m_vArgs.end(); 52 | } 53 | 54 | bool contains(const std::string& el) { 55 | for (auto& a : m_vArgs) { 56 | if (a == el) 57 | return true; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | private: 64 | std::vector m_vArgs; 65 | }; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /include/hyprutils/utils/ScopeGuard.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Hyprutils { 6 | namespace Utils { 7 | // calls a function when it goes out of scope 8 | class CScopeGuard { 9 | public: 10 | CScopeGuard(const std::function& fn_); 11 | ~CScopeGuard(); 12 | 13 | private: 14 | std::function fn; 15 | }; 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /nix/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | stdenv, 4 | stdenvAdapters, 5 | cmake, 6 | pkg-config, 7 | pixman, 8 | version ? "git", 9 | doCheck ? false, 10 | debug ? false, 11 | }: let 12 | inherit (builtins) foldl'; 13 | inherit (lib.lists) flatten; 14 | 15 | adapters = flatten [ 16 | stdenvAdapters.useMoldLinker 17 | (lib.optional debug stdenvAdapters.keepDebugInfo) 18 | ]; 19 | 20 | customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters; 21 | in 22 | customStdenv.mkDerivation { 23 | pname = "hyprutils"; 24 | inherit version doCheck; 25 | src = ../.; 26 | 27 | nativeBuildInputs = [ 28 | cmake 29 | pkg-config 30 | ]; 31 | 32 | buildInputs = [ 33 | pixman 34 | ]; 35 | 36 | outputs = ["out" "dev"]; 37 | 38 | cmakeBuildType = 39 | if debug 40 | then "Debug" 41 | else "RelWithDebInfo"; 42 | 43 | meta = with lib; { 44 | homepage = "https://github.com/hyprwm/hyprutils"; 45 | description = "Small C++ library for utilities used across the Hypr* ecosystem"; 46 | license = licenses.bsd3; 47 | platforms = platforms.linux; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /src/animation/AnimatedVariable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace Hyprutils::Animation; 6 | using namespace Hyprutils::Memory; 7 | 8 | static const std::string DEFAULTBEZIERNAME = "default"; 9 | static const std::string DEFAULTSTYLE = ""; 10 | 11 | #define SP CSharedPointer 12 | #define WP CWeakPointer 13 | 14 | void CBaseAnimatedVariable::create(CAnimationManager* pManager, int typeInfo, SP pSelf) { 15 | m_Type = typeInfo; 16 | m_pSelf = pSelf; 17 | 18 | m_pAnimationManager = pManager; 19 | m_pSignals = pManager->getSignals(); 20 | m_bDummy = false; 21 | } 22 | 23 | void CBaseAnimatedVariable::connectToActive() { 24 | if (m_bDummy || m_bIsConnectedToActive || isAnimationManagerDead()) 25 | return; 26 | 27 | m_pSignals->connect.emit(m_pSelf); 28 | m_bIsConnectedToActive = true; 29 | } 30 | 31 | void CBaseAnimatedVariable::disconnectFromActive() { 32 | if (isAnimationManagerDead()) 33 | return; 34 | 35 | m_pSignals->disconnect.emit(m_pSelf); 36 | m_bIsConnectedToActive = false; 37 | } 38 | 39 | bool Hyprutils::Animation::CBaseAnimatedVariable::enabled() const { 40 | if (const auto PCONFIG = m_pConfig.lock()) { 41 | const auto PVALUES = PCONFIG->pValues.lock(); 42 | return PVALUES ? PVALUES->internalEnabled : false; 43 | } 44 | 45 | return false; 46 | } 47 | 48 | const std::string& CBaseAnimatedVariable::getBezierName() const { 49 | if (const auto PCONFIG = m_pConfig.lock()) { 50 | const auto PVALUES = PCONFIG->pValues.lock(); 51 | return PVALUES ? PVALUES->internalBezier : DEFAULTBEZIERNAME; 52 | } 53 | 54 | return DEFAULTBEZIERNAME; 55 | } 56 | 57 | const std::string& CBaseAnimatedVariable::getStyle() const { 58 | if (const auto PCONFIG = m_pConfig.lock()) { 59 | const auto PVALUES = PCONFIG->pValues.lock(); 60 | return PVALUES ? PVALUES->internalStyle : DEFAULTSTYLE; 61 | } 62 | 63 | return DEFAULTSTYLE; 64 | } 65 | 66 | float CBaseAnimatedVariable::getPercent() const { 67 | const auto DURATIONPASSED = std::chrono::duration_cast(std::chrono::steady_clock::now() - animationBegin).count(); 68 | 69 | if (const auto PCONFIG = m_pConfig.lock()) { 70 | const auto PVALUES = PCONFIG->pValues.lock(); 71 | return PVALUES ? std::clamp((DURATIONPASSED / 100.f) / PVALUES->internalSpeed, 0.f, 1.f) : 1.f; 72 | } 73 | 74 | return 1.f; 75 | } 76 | 77 | float CBaseAnimatedVariable::getCurveValue() const { 78 | if (!m_bIsBeingAnimated || isAnimationManagerDead()) 79 | return 1.f; 80 | 81 | std::string bezierName = ""; 82 | if (const auto PCONFIG = m_pConfig.lock()) { 83 | const auto PVALUES = PCONFIG->pValues.lock(); 84 | if (PVALUES) 85 | bezierName = PVALUES->internalBezier; 86 | } 87 | 88 | const auto BEZIER = m_pAnimationManager->getBezier(bezierName); 89 | if (!BEZIER) 90 | return 1.f; 91 | 92 | const auto SPENT = getPercent(); 93 | if (SPENT >= 1.f) 94 | return 1.f; 95 | 96 | return BEZIER->getYForPoint(SPENT); 97 | } 98 | 99 | bool CBaseAnimatedVariable::ok() const { 100 | return m_pConfig && !m_bDummy && !isAnimationManagerDead(); 101 | } 102 | 103 | void CBaseAnimatedVariable::onUpdate() { 104 | if (m_bIsBeingAnimated && m_fUpdateCallback) 105 | m_fUpdateCallback(m_pSelf); 106 | } 107 | 108 | void CBaseAnimatedVariable::setCallbackOnEnd(CallbackFun func, bool remove) { 109 | m_fEndCallback = std::move(func); 110 | m_bRemoveEndAfterRan = remove; 111 | 112 | if (!isBeingAnimated()) 113 | onAnimationEnd(); 114 | } 115 | 116 | void CBaseAnimatedVariable::setCallbackOnBegin(CallbackFun func, bool remove) { 117 | m_fBeginCallback = std::move(func); 118 | m_bRemoveBeginAfterRan = remove; 119 | } 120 | 121 | void CBaseAnimatedVariable::setUpdateCallback(CallbackFun func) { 122 | m_fUpdateCallback = std::move(func); 123 | } 124 | 125 | void CBaseAnimatedVariable::resetAllCallbacks() { 126 | m_fBeginCallback = nullptr; 127 | m_fEndCallback = nullptr; 128 | m_fUpdateCallback = nullptr; 129 | m_bRemoveBeginAfterRan = false; 130 | m_bRemoveEndAfterRan = false; 131 | } 132 | 133 | void CBaseAnimatedVariable::onAnimationEnd() { 134 | m_bIsBeingAnimated = false; 135 | /* We do not call disconnectFromActive here. The animation manager will remove it on a call to tickDone. */ 136 | 137 | if (m_fEndCallback) { 138 | CallbackFun cb = nullptr; 139 | m_fEndCallback.swap(cb); 140 | 141 | cb(m_pSelf); 142 | if (!m_bRemoveEndAfterRan && /* callback did not set a new one by itself */ !m_fEndCallback) 143 | m_fEndCallback = cb; // restore 144 | } 145 | } 146 | 147 | void CBaseAnimatedVariable::onAnimationBegin() { 148 | m_bIsBeingAnimated = true; 149 | animationBegin = std::chrono::steady_clock::now(); 150 | connectToActive(); 151 | 152 | if (m_fBeginCallback) { 153 | m_fBeginCallback(m_pSelf); 154 | if (m_bRemoveBeginAfterRan) 155 | m_fBeginCallback = nullptr; // reset 156 | } 157 | } 158 | 159 | bool CBaseAnimatedVariable::isAnimationManagerDead() const { 160 | return m_pSignals.expired(); 161 | } 162 | -------------------------------------------------------------------------------- /src/animation/AnimationConfig.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace Hyprutils::Animation; 4 | using namespace Hyprutils::Memory; 5 | 6 | #define SP CSharedPointer 7 | #define WP CWeakPointer 8 | 9 | void CAnimationConfigTree::createNode(const std::string& nodeName, const std::string& parent) { 10 | auto pConfig = m_mAnimationConfig[nodeName]; 11 | if (!pConfig) 12 | pConfig = makeShared(); 13 | 14 | WP parentRef; 15 | if (!parent.empty() && m_mAnimationConfig.find(parent) != m_mAnimationConfig.end()) 16 | parentRef = m_mAnimationConfig[parent]; 17 | 18 | *pConfig = { 19 | .overridden = false, 20 | .internalBezier = "", 21 | .internalStyle = "", 22 | .internalSpeed = 0.f, 23 | .internalEnabled = -1, 24 | .pValues = (parentRef) ? parentRef->pValues : pConfig, 25 | .pParentAnimation = (parentRef) ? parentRef : pConfig, 26 | }; 27 | 28 | m_mAnimationConfig[nodeName] = pConfig; 29 | } 30 | 31 | bool CAnimationConfigTree::nodeExists(const std::string& nodeName) const { 32 | return m_mAnimationConfig.find(nodeName) != m_mAnimationConfig.end(); 33 | } 34 | 35 | void CAnimationConfigTree::setConfigForNode(const std::string& nodeName, int enabled, float speed, const std::string& bezier, const std::string& style) { 36 | auto pConfig = m_mAnimationConfig[nodeName]; 37 | if (!pConfig) 38 | return; 39 | 40 | *pConfig = { 41 | .overridden = true, 42 | .internalBezier = bezier, 43 | .internalStyle = style, 44 | .internalSpeed = speed, 45 | .internalEnabled = enabled, 46 | .pValues = pConfig, 47 | .pParentAnimation = pConfig->pParentAnimation, // keep the parent! 48 | }; 49 | 50 | setAnimForChildren(pConfig); 51 | } 52 | 53 | SP CAnimationConfigTree::getConfig(const std::string& name) const { 54 | return m_mAnimationConfig.at(name); 55 | } 56 | 57 | const std::unordered_map>& CAnimationConfigTree::getFullConfig() const { 58 | return m_mAnimationConfig; 59 | } 60 | 61 | void CAnimationConfigTree::setAnimForChildren(SP PANIM) { 62 | for (auto& [name, anim] : m_mAnimationConfig) { 63 | if (anim->pParentAnimation == PANIM && !anim->overridden) { 64 | // if a child isnt overridden, set the values of the parent 65 | anim->pValues = PANIM->pValues; 66 | 67 | setAnimForChildren(anim); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/animation/AnimationManager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace Hyprutils::Animation; 6 | using namespace Hyprutils::Math; 7 | using namespace Hyprutils::Memory; 8 | using namespace Hyprutils::Signal; 9 | 10 | #define SP CSharedPointer 11 | #define WP CWeakPointer 12 | 13 | const std::array DEFAULTBEZIERPOINTS = {Vector2D(0.0, 0.75), Vector2D(0.15, 1.0)}; 14 | 15 | CAnimationManager::CAnimationManager() { 16 | const auto BEZIER = makeShared(); 17 | BEZIER->setup(DEFAULTBEZIERPOINTS); 18 | m_mBezierCurves["default"] = BEZIER; 19 | 20 | m_events = makeUnique(); 21 | m_listeners = makeUnique(); 22 | 23 | m_listeners->connect = m_events->connect.registerListener([this](std::any data) { onConnect(data); }); 24 | m_listeners->disconnect = m_events->disconnect.registerListener([this](std::any data) { onDisconnect(data); }); 25 | } 26 | 27 | void CAnimationManager::onConnect(std::any data) { 28 | if (!m_bTickScheduled) 29 | scheduleTick(); 30 | 31 | try { 32 | const auto PAV = std::any_cast>(data); 33 | if (!PAV) 34 | return; 35 | 36 | m_vActiveAnimatedVariables.emplace_back(PAV); 37 | } catch (const std::bad_any_cast&) { return; } 38 | } 39 | 40 | void CAnimationManager::onDisconnect(std::any data) { 41 | try { 42 | const auto PAV = std::any_cast>(data); 43 | if (!PAV) 44 | return; 45 | 46 | std::erase_if(m_vActiveAnimatedVariables, [&](const auto& other) { return !other || other == PAV; }); 47 | } catch (const std::bad_any_cast&) { return; } 48 | } 49 | 50 | void CAnimationManager::removeAllBeziers() { 51 | m_mBezierCurves.clear(); 52 | 53 | // add the default one 54 | const auto BEZIER = makeShared(); 55 | BEZIER->setup(DEFAULTBEZIERPOINTS); 56 | m_mBezierCurves["default"] = BEZIER; 57 | } 58 | 59 | void CAnimationManager::addBezierWithName(std::string name, const Vector2D& p1, const Vector2D& p2) { 60 | const auto BEZIER = makeShared(); 61 | BEZIER->setup({ 62 | p1, 63 | p2, 64 | }); 65 | m_mBezierCurves[name] = BEZIER; 66 | } 67 | 68 | bool CAnimationManager::shouldTickForNext() { 69 | return !m_vActiveAnimatedVariables.empty(); 70 | } 71 | 72 | void CAnimationManager::tickDone() { 73 | rotateActive(); 74 | } 75 | 76 | void CAnimationManager::rotateActive() { 77 | std::vector> active; 78 | active.reserve(m_vActiveAnimatedVariables.size()); // avoid reallocations 79 | for (auto const& av : m_vActiveAnimatedVariables) { 80 | const auto PAV = av.lock(); 81 | if (!PAV) 82 | continue; 83 | 84 | if (PAV->ok() && PAV->isBeingAnimated()) 85 | active.emplace_back(av); 86 | else 87 | PAV->m_bIsConnectedToActive = false; 88 | } 89 | 90 | m_vActiveAnimatedVariables = std::move(active); 91 | } 92 | 93 | bool CAnimationManager::bezierExists(const std::string& bezier) { 94 | for (auto const& [bc, bz] : m_mBezierCurves) { 95 | if (bc == bezier) 96 | return true; 97 | } 98 | 99 | return false; 100 | } 101 | 102 | SP CAnimationManager::getBezier(const std::string& name) { 103 | const auto BEZIER = std::ranges::find_if(m_mBezierCurves, [&](const auto& other) { return other.first == name; }); 104 | 105 | return BEZIER == m_mBezierCurves.end() ? m_mBezierCurves["default"] : BEZIER->second; 106 | } 107 | 108 | const std::unordered_map>& CAnimationManager::getAllBeziers() { 109 | return m_mBezierCurves; 110 | } 111 | 112 | CWeakPointer CAnimationManager::getSignals() const { 113 | return m_events; 114 | } 115 | -------------------------------------------------------------------------------- /src/animation/BezierCurve.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | using namespace Hyprutils::Animation; 7 | using namespace Hyprutils::Math; 8 | 9 | void CBezierCurve::setup(const std::array& pVec) { 10 | // Avoid reallocations by reserving enough memory upfront 11 | m_vPoints.resize(pVec.size() + 2); 12 | m_vPoints = { 13 | Vector2D(0, 0), // Start point 14 | pVec[0], pVec[1], // Control points 15 | Vector2D(1, 1) // End point 16 | }; 17 | 18 | if (m_vPoints.size() != 4) 19 | std::abort(); 20 | 21 | // bake BAKEDPOINTS points for faster lookups 22 | // T -> X ( / BAKEDPOINTS ) 23 | for (int i = 0; i < BAKEDPOINTS; ++i) { 24 | float const t = (i + 1) / (float)BAKEDPOINTS; 25 | m_aPointsBaked[i] = Vector2D(getXForT(t), getYForT(t)); 26 | } 27 | 28 | for (int j = 1; j < 10; ++j) { 29 | float i = j / 10.0f; 30 | getYForPoint(i); 31 | } 32 | } 33 | 34 | float CBezierCurve::getXForT(float const& t) const { 35 | float t2 = t * t; 36 | float t3 = t2 * t; 37 | 38 | return (3 * t * (1 - t) * (1 - t) * m_vPoints[1].x) + (3 * t2 * (1 - t) * m_vPoints[2].x) + (t3 * m_vPoints[3].x); 39 | } 40 | 41 | float CBezierCurve::getYForT(float const& t) const { 42 | float t2 = t * t; 43 | float t3 = t2 * t; 44 | 45 | return (3 * t * (1 - t) * (1 - t) * m_vPoints[1].y) + (3 * t2 * (1 - t) * m_vPoints[2].y) + (t3 * m_vPoints[3].y); 46 | } 47 | 48 | // Todo: this probably can be done better and faster 49 | float CBezierCurve::getYForPoint(float const& x) const { 50 | if (x >= 1.f) 51 | return 1.f; 52 | if (x <= 0.f) 53 | return 0.f; 54 | 55 | int index = 0; 56 | bool below = true; 57 | for (int step = (BAKEDPOINTS + 1) / 2; step > 0; step /= 2) { 58 | if (below) 59 | index += step; 60 | else 61 | index -= step; 62 | 63 | below = m_aPointsBaked[index].x < x; 64 | } 65 | 66 | int lowerIndex = index - (!below || index == BAKEDPOINTS - 1); 67 | 68 | // in the name of performance i shall make a hack 69 | const auto LOWERPOINT = &m_aPointsBaked[lowerIndex]; 70 | const auto UPPERPOINT = &m_aPointsBaked[lowerIndex + 1]; 71 | 72 | const auto PERCINDELTA = (x - LOWERPOINT->x) / (UPPERPOINT->x - LOWERPOINT->x); 73 | 74 | if (std::isnan(PERCINDELTA) || std::isinf(PERCINDELTA)) // can sometimes happen for VERY small x 75 | return 0.f; 76 | 77 | return LOWERPOINT->y + ((UPPERPOINT->y - LOWERPOINT->y) * PERCINDELTA); 78 | } 79 | -------------------------------------------------------------------------------- /src/math/Box.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define VECINRECT(vec, x1, y1, x2, y2) ((vec).x >= (x1) && (vec).x < (x2) && (vec).y >= (y1) && (vec).y < (y2)) 7 | 8 | using namespace Hyprutils::Math; 9 | 10 | constexpr double HALF = 0.5; 11 | constexpr double DOUBLE = 2.0; 12 | constexpr double EPSILON = 1e-9; 13 | 14 | CBox& Hyprutils::Math::CBox::scale(double scale) { 15 | x *= scale; 16 | y *= scale; 17 | w *= scale; 18 | h *= scale; 19 | 20 | return *this; 21 | } 22 | 23 | CBox& Hyprutils::Math::CBox::scale(const Vector2D& scale) { 24 | x *= scale.x; 25 | y *= scale.y; 26 | w *= scale.x; 27 | h *= scale.y; 28 | 29 | return *this; 30 | } 31 | 32 | CBox& Hyprutils::Math::CBox::translate(const Vector2D& vec) { 33 | x += vec.x; 34 | y += vec.y; 35 | 36 | return *this; 37 | } 38 | 39 | Vector2D Hyprutils::Math::CBox::middle() const { 40 | return Vector2D{x + (w * HALF), y + (h * HALF)}; 41 | } 42 | 43 | bool Hyprutils::Math::CBox::containsPoint(const Vector2D& vec) const { 44 | return VECINRECT(vec, x, y, x + w, y + h); 45 | } 46 | 47 | bool Hyprutils::Math::CBox::empty() const { 48 | return std::fabs(w) < EPSILON || std::fabs(h) < EPSILON; 49 | } 50 | 51 | CBox& Hyprutils::Math::CBox::round() { 52 | double roundedX = std::round(x); 53 | double roundedY = std::round(y); 54 | double newW = x + w - roundedX; 55 | double newH = y + h - roundedY; 56 | 57 | x = roundedX; 58 | y = roundedY; 59 | w = std::round(newW); 60 | h = std::round(newH); 61 | 62 | return *this; 63 | } 64 | 65 | CBox& Hyprutils::Math::CBox::transform(const eTransform t, double w, double h) { 66 | 67 | CBox temp = *this; 68 | 69 | if (t % 2 == 0) { 70 | width = temp.width; 71 | height = temp.height; 72 | } else { 73 | width = temp.height; 74 | height = temp.width; 75 | } 76 | 77 | switch (t) { 78 | case HYPRUTILS_TRANSFORM_NORMAL: 79 | x = temp.x; 80 | y = temp.y; 81 | break; 82 | case HYPRUTILS_TRANSFORM_90: 83 | x = h - temp.y - temp.height; 84 | y = temp.x; 85 | break; 86 | case HYPRUTILS_TRANSFORM_180: 87 | x = w - temp.x - temp.width; 88 | y = h - temp.y - temp.height; 89 | break; 90 | case HYPRUTILS_TRANSFORM_270: 91 | x = temp.y; 92 | y = w - temp.x - temp.width; 93 | break; 94 | case HYPRUTILS_TRANSFORM_FLIPPED: 95 | x = w - temp.x - temp.width; 96 | y = temp.y; 97 | break; 98 | case HYPRUTILS_TRANSFORM_FLIPPED_90: 99 | x = temp.y; 100 | y = temp.x; 101 | break; 102 | case HYPRUTILS_TRANSFORM_FLIPPED_180: 103 | x = temp.x; 104 | y = h - temp.y - temp.height; 105 | break; 106 | case HYPRUTILS_TRANSFORM_FLIPPED_270: 107 | x = h - temp.y - temp.height; 108 | y = w - temp.x - temp.width; 109 | break; 110 | } 111 | 112 | return *this; 113 | } 114 | 115 | CBox& Hyprutils::Math::CBox::addExtents(const SBoxExtents& e) { 116 | x -= e.topLeft.x; 117 | y -= e.topLeft.y; 118 | w += e.topLeft.x + e.bottomRight.x; 119 | h += e.topLeft.y + e.bottomRight.y; 120 | 121 | return *this; 122 | } 123 | 124 | CBox& Hyprutils::Math::CBox::scaleFromCenter(double scale) { 125 | double oldW = w, oldH = h; 126 | 127 | w *= scale; 128 | h *= scale; 129 | 130 | x -= (w - oldW) * HALF; 131 | y -= (h - oldH) * HALF; 132 | 133 | return *this; 134 | } 135 | 136 | CBox& Hyprutils::Math::CBox::expand(const double& value) { 137 | x -= value; 138 | y -= value; 139 | w += value * DOUBLE; 140 | h += value * DOUBLE; 141 | 142 | if (w <= EPSILON || h <= EPSILON) { 143 | w = 0; 144 | h = 0; 145 | } 146 | 147 | return *this; 148 | } 149 | 150 | CBox& Hyprutils::Math::CBox::noNegativeSize() { 151 | w = std::clamp(w, 0.0, std::numeric_limits::infinity()); 152 | h = std::clamp(h, 0.0, std::numeric_limits::infinity()); 153 | 154 | return *this; 155 | } 156 | 157 | CBox Hyprutils::Math::CBox::intersection(const CBox& other) const { 158 | const double newX = std::max(x, other.x); 159 | const double newY = std::max(y, other.y); 160 | const double newBottom = std::min(y + h, other.y + other.h); 161 | const double newRight = std::min(x + w, other.x + other.w); 162 | double newW = newRight - newX; 163 | double newH = newBottom - newY; 164 | 165 | if (newW <= EPSILON || newH <= EPSILON) { 166 | newW = 0; 167 | newH = 0; 168 | } 169 | 170 | return {newX, newY, newW, newH}; 171 | } 172 | 173 | bool Hyprutils::Math::CBox::overlaps(const CBox& other) const { 174 | return (other.x + other.w >= x) && (x + w >= other.x) && (other.y + other.h >= y) && (y + h >= other.y); 175 | } 176 | 177 | bool Hyprutils::Math::CBox::inside(const CBox& bound) const { 178 | return bound.x < x && bound.y < y && x + w < bound.x + bound.w && y + h < bound.y + bound.h; 179 | } 180 | 181 | CBox Hyprutils::Math::CBox::roundInternal() { 182 | double flooredX = std::floor(x); 183 | double flooredY = std::floor(y); 184 | double newW = x + w - flooredX; 185 | double newH = y + h - flooredY; 186 | 187 | return CBox{flooredX, flooredY, std::floor(newW), std::floor(newH)}; 188 | } 189 | 190 | CBox Hyprutils::Math::CBox::copy() const { 191 | return CBox{*this}; 192 | } 193 | 194 | Vector2D Hyprutils::Math::CBox::pos() const { 195 | return {x, y}; 196 | } 197 | 198 | Vector2D Hyprutils::Math::CBox::size() const { 199 | return {w, h}; 200 | } 201 | 202 | Vector2D Hyprutils::Math::CBox::extent() const { 203 | return pos() + size(); 204 | } 205 | 206 | Vector2D Hyprutils::Math::CBox::closestPoint(const Vector2D& vec) const { 207 | if (containsPoint(vec)) 208 | return vec; 209 | 210 | Vector2D nv = vec; 211 | Vector2D maxPoint = {x + w - EPSILON, y + h - EPSILON}; 212 | 213 | if (x < maxPoint.x) 214 | nv.x = std::clamp(nv.x, x, maxPoint.x); 215 | else 216 | nv.x = x; 217 | if (y < maxPoint.y) 218 | nv.y = std::clamp(nv.y, y, maxPoint.y); 219 | else 220 | nv.y = y; 221 | 222 | if (std::fabs(nv.x - x) < EPSILON) 223 | nv.x = x; 224 | else if (std::fabs(nv.x - (maxPoint.x)) < EPSILON) 225 | nv.x = maxPoint.x; 226 | 227 | if (std::fabs(nv.y - y) < EPSILON) 228 | nv.y = y; 229 | else if (std::fabs(nv.y - (maxPoint.y)) < EPSILON) 230 | nv.y = maxPoint.y; 231 | 232 | return nv; 233 | } 234 | 235 | SBoxExtents Hyprutils::Math::CBox::extentsFrom(const CBox& small) { 236 | return {.topLeft = {small.x - x, small.y - y}, .bottomRight = {w - small.w - (small.x - x), h - small.h - (small.y - y)}}; 237 | } 238 | -------------------------------------------------------------------------------- /src/math/Mat3x3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace Hyprutils::Math; 9 | 10 | static std::unordered_map transforms = { 11 | {HYPRUTILS_TRANSFORM_NORMAL, std::array{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, 12 | {HYPRUTILS_TRANSFORM_90, std::array{0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, 13 | {HYPRUTILS_TRANSFORM_180, std::array{-1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, 14 | {HYPRUTILS_TRANSFORM_270, std::array{0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, 15 | {HYPRUTILS_TRANSFORM_FLIPPED, std::array{-1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, 16 | {HYPRUTILS_TRANSFORM_FLIPPED_90, std::array{0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, 17 | {HYPRUTILS_TRANSFORM_FLIPPED_180, std::array{1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, 18 | {HYPRUTILS_TRANSFORM_FLIPPED_270, std::array{0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, 19 | }; 20 | 21 | Mat3x3::Mat3x3() { 22 | matrix = {0}; 23 | } 24 | 25 | Mat3x3::Mat3x3(std::array mat) : matrix(mat) { 26 | ; 27 | } 28 | 29 | Mat3x3::Mat3x3(std::vector mat) { 30 | for (size_t i = 0; i < 9; ++i) { 31 | matrix.at(i) = mat.size() < i ? mat.at(i) : 0.F; 32 | } 33 | } 34 | 35 | Mat3x3 Mat3x3::identity() { 36 | return Mat3x3(std::array{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}); 37 | } 38 | 39 | Mat3x3 Mat3x3::outputProjection(const Vector2D& size, eTransform transform) { 40 | Mat3x3 mat; 41 | 42 | const auto& t = transforms.at(transform); 43 | float x = 2.0f / size.x; 44 | float y = 2.0f / size.y; 45 | 46 | // Rotation + reflection 47 | mat.matrix[0] = x * t.matrix[0]; 48 | mat.matrix[1] = x * t.matrix[1]; 49 | mat.matrix[3] = y * t.matrix[3]; 50 | mat.matrix[4] = y * t.matrix[4]; 51 | 52 | // Translation 53 | mat.matrix[2] = -copysign(1.0f, mat.matrix[0] + mat.matrix[1]); 54 | mat.matrix[5] = -copysign(1.0f, mat.matrix[3] + mat.matrix[4]); 55 | 56 | // Identity 57 | mat.matrix[8] = 1.0f; 58 | 59 | return mat; 60 | } 61 | 62 | std::array Mat3x3::getMatrix() const { 63 | return matrix; 64 | } 65 | 66 | Mat3x3 Mat3x3::projectBox(const CBox& box, eTransform transform, float rot /* rad, CCW */) const { 67 | Mat3x3 mat = Mat3x3::identity(); 68 | 69 | const auto boxSize = box.size(); 70 | 71 | mat.translate(box.pos()); 72 | 73 | if (rot != 0) { 74 | mat.translate(boxSize / 2); 75 | mat.rotate(rot); 76 | mat.translate(-boxSize / 2); 77 | } 78 | 79 | mat.scale(boxSize); 80 | 81 | if (transform != HYPRUTILS_TRANSFORM_NORMAL) { 82 | mat.translate({0.5, 0.5}); 83 | mat.transform(transform); 84 | mat.translate({-0.5, -0.5}); 85 | } 86 | 87 | return this->copy().multiply(mat); 88 | } 89 | 90 | Mat3x3& Mat3x3::transform(eTransform transform) { 91 | multiply(transforms.at(transform)); 92 | return *this; 93 | } 94 | 95 | Mat3x3& Mat3x3::rotate(float rot) { 96 | multiply(std::array{(float)cos(rot), (float)-sin(rot), 0.0f, (float)sin(rot), (float)cos(rot), 0.0f, 0.0f, 0.0f, 1.0f}); 97 | return *this; 98 | } 99 | 100 | Mat3x3& Mat3x3::scale(const Vector2D& scale_) { 101 | multiply(std::array{(float)scale_.x, 0.0f, 0.0f, 0.0f, (float)scale_.y, 0.0f, 0.0f, 0.0f, 1.0f}); 102 | return *this; 103 | } 104 | 105 | Mat3x3& Mat3x3::scale(const float scale_) { 106 | return scale({scale_, scale_}); 107 | } 108 | 109 | Mat3x3& Mat3x3::translate(const Vector2D& offset) { 110 | multiply(std::array{1.0f, 0.0f, (float)offset.x, 0.0f, 1.0f, (float)offset.y, 0.0f, 0.0f, 1.0f}); 111 | return *this; 112 | } 113 | 114 | Mat3x3& Mat3x3::transpose() { 115 | matrix = std::array{matrix[0], matrix[3], matrix[6], matrix[1], matrix[4], matrix[7], matrix[2], matrix[5], matrix[8]}; 116 | return *this; 117 | } 118 | 119 | Mat3x3& Mat3x3::multiply(const Mat3x3& other) { 120 | std::array product; 121 | 122 | product[0] = matrix[0] * other.matrix[0] + matrix[1] * other.matrix[3] + matrix[2] * other.matrix[6]; 123 | product[1] = matrix[0] * other.matrix[1] + matrix[1] * other.matrix[4] + matrix[2] * other.matrix[7]; 124 | product[2] = matrix[0] * other.matrix[2] + matrix[1] * other.matrix[5] + matrix[2] * other.matrix[8]; 125 | 126 | product[3] = matrix[3] * other.matrix[0] + matrix[4] * other.matrix[3] + matrix[5] * other.matrix[6]; 127 | product[4] = matrix[3] * other.matrix[1] + matrix[4] * other.matrix[4] + matrix[5] * other.matrix[7]; 128 | product[5] = matrix[3] * other.matrix[2] + matrix[4] * other.matrix[5] + matrix[5] * other.matrix[8]; 129 | 130 | product[6] = matrix[6] * other.matrix[0] + matrix[7] * other.matrix[3] + matrix[8] * other.matrix[6]; 131 | product[7] = matrix[6] * other.matrix[1] + matrix[7] * other.matrix[4] + matrix[8] * other.matrix[7]; 132 | product[8] = matrix[6] * other.matrix[2] + matrix[7] * other.matrix[5] + matrix[8] * other.matrix[8]; 133 | 134 | matrix = product; 135 | return *this; 136 | } 137 | 138 | Mat3x3 Mat3x3::copy() const { 139 | return *this; 140 | } 141 | 142 | std::string Mat3x3::toString() const { 143 | 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), 144 | matrix.at(7), matrix.at(8)); 145 | } 146 | -------------------------------------------------------------------------------- /src/math/Region.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace Hyprutils::Math; 5 | 6 | constexpr const int64_t MAX_REGION_SIDE = 10000000; 7 | 8 | Hyprutils::Math::CRegion::CRegion() { 9 | pixman_region32_init(&m_rRegion); 10 | } 11 | 12 | Hyprutils::Math::CRegion::CRegion(const pixman_region32_t* const ref) { 13 | pixman_region32_init(&m_rRegion); 14 | pixman_region32_copy(&m_rRegion, ref); 15 | } 16 | 17 | Hyprutils::Math::CRegion::CRegion(double x, double y, double w, double h) { 18 | pixman_region32_init_rect(&m_rRegion, x, y, w, h); 19 | } 20 | 21 | Hyprutils::Math::CRegion::CRegion(const CBox& box) { 22 | pixman_region32_init_rect(&m_rRegion, box.x, box.y, box.w, box.h); 23 | } 24 | 25 | Hyprutils::Math::CRegion::CRegion(pixman_box32_t* box) { 26 | pixman_region32_init_rect(&m_rRegion, box->x1, box->y1, box->x2 - box->x1, box->y2 - box->y1); 27 | } 28 | 29 | Hyprutils::Math::CRegion::CRegion(const CRegion& other) { 30 | pixman_region32_init(&m_rRegion); 31 | pixman_region32_copy(&m_rRegion, const_cast(&other)->pixman()); 32 | } 33 | 34 | Hyprutils::Math::CRegion::CRegion(CRegion&& other) { 35 | pixman_region32_init(&m_rRegion); 36 | pixman_region32_copy(&m_rRegion, other.pixman()); 37 | } 38 | 39 | Hyprutils::Math::CRegion::~CRegion() { 40 | pixman_region32_fini(&m_rRegion); 41 | } 42 | 43 | CRegion& Hyprutils::Math::CRegion::clear() { 44 | pixman_region32_clear(&m_rRegion); 45 | return *this; 46 | } 47 | 48 | CRegion& Hyprutils::Math::CRegion::set(const CRegion& other) { 49 | pixman_region32_copy(&m_rRegion, const_cast(&other)->pixman()); 50 | return *this; 51 | } 52 | 53 | CRegion& Hyprutils::Math::CRegion::add(const CRegion& other) { 54 | pixman_region32_union(&m_rRegion, &m_rRegion, const_cast(&other)->pixman()); 55 | return *this; 56 | } 57 | 58 | CRegion& Hyprutils::Math::CRegion::add(double x, double y, double w, double h) { 59 | pixman_region32_union_rect(&m_rRegion, &m_rRegion, x, y, w, h); 60 | return *this; 61 | } 62 | 63 | CRegion& Hyprutils::Math::CRegion::add(const CBox& other) { 64 | pixman_region32_union_rect(&m_rRegion, &m_rRegion, other.x, other.y, other.w, other.h); 65 | return *this; 66 | } 67 | 68 | CRegion& Hyprutils::Math::CRegion::subtract(const CRegion& other) { 69 | pixman_region32_subtract(&m_rRegion, &m_rRegion, const_cast(&other)->pixman()); 70 | return *this; 71 | } 72 | 73 | CRegion& Hyprutils::Math::CRegion::intersect(const CRegion& other) { 74 | pixman_region32_intersect(&m_rRegion, &m_rRegion, const_cast(&other)->pixman()); 75 | return *this; 76 | } 77 | 78 | CRegion& Hyprutils::Math::CRegion::intersect(double x, double y, double w, double h) { 79 | pixman_region32_intersect_rect(&m_rRegion, &m_rRegion, x, y, w, h); 80 | return *this; 81 | } 82 | 83 | CRegion& Hyprutils::Math::CRegion::invert(pixman_box32_t* box) { 84 | pixman_region32_inverse(&m_rRegion, &m_rRegion, box); 85 | return *this; 86 | } 87 | 88 | CRegion& Hyprutils::Math::CRegion::invert(const CBox& box) { 89 | pixman_box32 pixmanBox = {.x1 = (int32_t)box.x, .y1 = (int32_t)box.y, .x2 = (int32_t)box.w + (int32_t)box.x, .y2 = (int32_t)box.h + (int32_t)box.y}; 90 | return this->invert(&pixmanBox); 91 | } 92 | 93 | CRegion& Hyprutils::Math::CRegion::translate(const Vector2D& vec) { 94 | pixman_region32_translate(&m_rRegion, vec.x, vec.y); 95 | return *this; 96 | } 97 | 98 | CRegion& Hyprutils::Math::CRegion::transform(const eTransform t, double w, double h) { 99 | if (t == HYPRUTILS_TRANSFORM_NORMAL) 100 | return *this; 101 | 102 | auto rects = getRects(); 103 | 104 | clear(); 105 | 106 | for (auto& r : rects) { 107 | CBox xfmd{(double)r.x1, (double)r.y1, (double)r.x2 - r.x1, (double)r.y2 - r.y1}; 108 | xfmd.transform(t, w, h); 109 | add(xfmd); 110 | } 111 | 112 | return *this; 113 | } 114 | 115 | CRegion& Hyprutils::Math::CRegion::expand(double units) { 116 | auto rects = getRects(); 117 | 118 | clear(); 119 | 120 | for (auto& r : rects) { 121 | CBox b{(double)r.x1 - units, (double)r.y1 - units, (double)r.x2 - r.x1 + (units * 2), (double)r.y2 - r.y1 + (units * 2)}; 122 | add(b); 123 | } 124 | 125 | return *this; 126 | } 127 | 128 | CRegion& Hyprutils::Math::CRegion::rationalize() { 129 | intersect(CBox{-MAX_REGION_SIDE, -MAX_REGION_SIDE, MAX_REGION_SIDE * 2, MAX_REGION_SIDE * 2}); 130 | return *this; 131 | } 132 | 133 | CRegion Hyprutils::Math::CRegion::copy() const { 134 | return CRegion(*this); 135 | } 136 | 137 | CRegion& Hyprutils::Math::CRegion::scale(float scale_) { 138 | scale({scale_, scale_}); 139 | return *this; 140 | } 141 | 142 | CRegion& Hyprutils::Math::CRegion::scale(const Vector2D& scale) { 143 | if (scale == Vector2D{1, 1}) 144 | return *this; 145 | 146 | auto rects = getRects(); 147 | 148 | clear(); 149 | 150 | for (auto& r : rects) { 151 | r.x1 = std::floor(r.x1 * scale.x); 152 | r.y1 = std::floor(r.y1 * scale.y); 153 | r.x2 = std::ceil(r.x2 * scale.x); 154 | r.y2 = std::ceil(r.y2 * scale.y); 155 | add(&r); 156 | } 157 | 158 | return *this; 159 | } 160 | 161 | std::vector Hyprutils::Math::CRegion::getRects() const { 162 | std::vector result; 163 | 164 | int rectsNum = 0; 165 | const auto RECTSARR = pixman_region32_rectangles(&m_rRegion, &rectsNum); 166 | 167 | result.assign(RECTSARR, RECTSARR + rectsNum); 168 | 169 | return result; 170 | } 171 | 172 | CBox Hyprutils::Math::CRegion::getExtents() { 173 | pixman_box32_t* box = pixman_region32_extents(&m_rRegion); 174 | return {(double)box->x1, (double)box->y1, (double)box->x2 - box->x1, (double)box->y2 - box->y1}; 175 | } 176 | 177 | bool Hyprutils::Math::CRegion::containsPoint(const Vector2D& vec) const { 178 | return pixman_region32_contains_point(&m_rRegion, vec.x, vec.y, nullptr); 179 | } 180 | 181 | bool Hyprutils::Math::CRegion::empty() const { 182 | return !pixman_region32_not_empty(&m_rRegion); 183 | } 184 | 185 | Vector2D Hyprutils::Math::CRegion::closestPoint(const Vector2D& vec) const { 186 | if (containsPoint(vec)) 187 | return vec; 188 | 189 | double bestDist = __FLT_MAX__; 190 | Vector2D leader = vec; 191 | 192 | for (auto& box : getRects()) { 193 | double x = 0, y = 0; 194 | 195 | if (vec.x >= box.x2) 196 | x = box.x2 - 1; 197 | else if (vec.x < box.x1) 198 | x = box.x1; 199 | else 200 | x = vec.x; 201 | 202 | if (vec.y >= box.y2) 203 | y = box.y2 - 1; 204 | else if (vec.y < box.y1) 205 | y = box.y1; 206 | else 207 | y = vec.y; 208 | 209 | double distance = pow(x, 2) + pow(y, 2); 210 | if (distance < bestDist) { 211 | bestDist = distance; 212 | leader = {x, y}; 213 | } 214 | } 215 | 216 | return leader; 217 | } 218 | -------------------------------------------------------------------------------- /src/math/Vector2D.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace Hyprutils::Math; 6 | 7 | Hyprutils::Math::Vector2D::Vector2D(double xx, double yy) : x(xx), y(yy) { 8 | ; 9 | } 10 | 11 | Hyprutils::Math::Vector2D::Vector2D(int xx, int yy) : x((double)xx), y((double)yy) { 12 | ; 13 | } 14 | 15 | Hyprutils::Math::Vector2D::Vector2D() : x(0), y(0) { 16 | ; 17 | } 18 | 19 | Hyprutils::Math::Vector2D::~Vector2D() {} 20 | 21 | double Hyprutils::Math::Vector2D::normalize() { 22 | // get max abs 23 | const auto max = std::abs(x) > std::abs(y) ? std::abs(x) : std::abs(y); 24 | 25 | x /= max; 26 | y /= max; 27 | 28 | return max; 29 | } 30 | 31 | Vector2D Hyprutils::Math::Vector2D::floor() const { 32 | return Vector2D(std::floor(x), std::floor(y)); 33 | } 34 | 35 | Vector2D Hyprutils::Math::Vector2D::round() const { 36 | return Vector2D(std::round(x), std::round(y)); 37 | } 38 | 39 | Vector2D Hyprutils::Math::Vector2D::clamp(const Vector2D& min, const Vector2D& max) const { 40 | return Vector2D(std::clamp(this->x, min.x, max.x < min.x ? INFINITY : max.x), std::clamp(this->y, min.y, max.y < min.y ? INFINITY : max.y)); 41 | } 42 | 43 | double Hyprutils::Math::Vector2D::distance(const Vector2D& other) const { 44 | return std::sqrt(distanceSq(other)); 45 | } 46 | 47 | double Hyprutils::Math::Vector2D::distanceSq(const Vector2D& other) const { 48 | return ((x - other.x) * (x - other.x)) + ((y - other.y) * (y - other.y)); 49 | } 50 | 51 | double Hyprutils::Math::Vector2D::size() const { 52 | return std::sqrt((x * x) + (y * y)); 53 | } 54 | 55 | Vector2D Hyprutils::Math::Vector2D::getComponentMax(const Vector2D& other) const { 56 | return Vector2D(std::max(this->x, other.x), std::max(this->y, other.y)); 57 | } 58 | -------------------------------------------------------------------------------- /src/os/FileDescriptor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace Hyprutils::OS; 9 | 10 | CFileDescriptor::CFileDescriptor(int const fd) : m_fd(fd) {} 11 | 12 | CFileDescriptor::CFileDescriptor(CFileDescriptor&& other) : m_fd(std::exchange(other.m_fd, -1)) {} 13 | 14 | CFileDescriptor& CFileDescriptor::operator=(CFileDescriptor&& other) { 15 | if (this == &other) // Shit will go haywire if there is duplicate ownership 16 | abort(); 17 | 18 | reset(); 19 | m_fd = std::exchange(other.m_fd, -1); 20 | return *this; 21 | } 22 | 23 | CFileDescriptor::~CFileDescriptor() { 24 | reset(); 25 | } 26 | 27 | bool CFileDescriptor::isValid() const { 28 | return m_fd != -1; 29 | } 30 | 31 | int CFileDescriptor::get() const { 32 | return m_fd; 33 | } 34 | 35 | int CFileDescriptor::getFlags() const { 36 | return fcntl(m_fd, F_GETFD); 37 | } 38 | 39 | bool CFileDescriptor::setFlags(int flags) { 40 | return fcntl(m_fd, F_SETFD, flags) != -1; 41 | } 42 | 43 | int CFileDescriptor::take() { 44 | return std::exchange(m_fd, -1); 45 | } 46 | 47 | void CFileDescriptor::reset() { 48 | if (m_fd != -1) { 49 | close(m_fd); 50 | m_fd = -1; 51 | } 52 | } 53 | 54 | CFileDescriptor CFileDescriptor::duplicate(int flags) const { 55 | if (m_fd == -1) 56 | return {}; 57 | 58 | return CFileDescriptor{fcntl(m_fd, flags, 0)}; 59 | } 60 | 61 | bool CFileDescriptor::isClosed() const { 62 | return isClosed(m_fd); 63 | } 64 | 65 | bool CFileDescriptor::isReadable() const { 66 | return isReadable(m_fd); 67 | } 68 | 69 | bool CFileDescriptor::isClosed(int fd) { 70 | pollfd pfd = { 71 | .fd = fd, 72 | .events = POLLIN, 73 | .revents = 0, 74 | }; 75 | 76 | if (poll(&pfd, 1, 0) < 0) 77 | return true; 78 | 79 | return pfd.revents & (POLLHUP | POLLERR); 80 | } 81 | 82 | bool CFileDescriptor::isReadable(int fd) { 83 | pollfd pfd = {.fd = fd, .events = POLLIN, .revents = 0}; 84 | 85 | return poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN); 86 | } 87 | -------------------------------------------------------------------------------- /src/os/Process.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace Hyprutils::OS; 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | struct Hyprutils::OS::CProcess::impl { 16 | std::string binary, out, err; 17 | std::vector args; 18 | std::vector> env; 19 | pid_t grandchildPid = 0; 20 | int stdoutFD = -1, stderrFD = -1, exitCode = 0; 21 | }; 22 | 23 | Hyprutils::OS::CProcess::CProcess(const std::string& binary, const std::vector& args) : m_impl(new impl()) { 24 | m_impl->binary = binary; 25 | m_impl->args = args; 26 | } 27 | 28 | Hyprutils::OS::CProcess::~CProcess() { 29 | delete m_impl; 30 | } 31 | 32 | void Hyprutils::OS::CProcess::addEnv(const std::string& name, const std::string& value) { 33 | m_impl->env.emplace_back(std::make_pair<>(name, value)); 34 | } 35 | 36 | bool Hyprutils::OS::CProcess::runSync() { 37 | int outPipe[2], errPipe[2]; 38 | if (pipe(outPipe)) 39 | return false; 40 | if (pipe(errPipe)) { 41 | close(outPipe[0]); 42 | close(outPipe[1]); 43 | return false; 44 | } 45 | 46 | int pid = fork(); 47 | if (pid == -1) { 48 | close(outPipe[0]); 49 | close(outPipe[1]); 50 | close(outPipe[0]); 51 | close(outPipe[1]); 52 | return false; 53 | } 54 | 55 | if (!pid) { 56 | // child 57 | close(outPipe[0]); 58 | close(errPipe[0]); 59 | 60 | dup2(outPipe[1], 1 /* stdout */); 61 | dup2(errPipe[1], 2 /* stderr */); 62 | 63 | // build argv 64 | std::vector argsC; 65 | argsC.emplace_back(strdup(m_impl->binary.c_str())); 66 | for (auto& arg : m_impl->args) { 67 | // TODO: does this leak? Can we just pipe c_str() as the strings won't be realloc'd? 68 | argsC.emplace_back(strdup(arg.c_str())); 69 | } 70 | 71 | argsC.emplace_back(nullptr); 72 | 73 | // pass env 74 | for (auto& [n, v] : m_impl->env) { 75 | setenv(n.c_str(), v.c_str(), 1); 76 | } 77 | 78 | execvp(m_impl->binary.c_str(), (char* const*)argsC.data()); 79 | exit(1); 80 | } else { 81 | // parent 82 | close(outPipe[1]); 83 | close(errPipe[1]); 84 | 85 | m_impl->out = ""; 86 | m_impl->err = ""; 87 | 88 | m_impl->grandchildPid = pid; 89 | 90 | std::array buf; 91 | buf.fill(0); 92 | 93 | // wait for read 94 | ssize_t ret = 0; 95 | 96 | int fdFlags = fcntl(outPipe[0], F_GETFL, 0); 97 | if (fcntl(outPipe[0], F_SETFL, fdFlags | O_NONBLOCK) < 0) 98 | return false; 99 | fdFlags = fcntl(errPipe[0], F_GETFL, 0); 100 | if (fcntl(errPipe[0], F_SETFL, fdFlags | O_NONBLOCK) < 0) 101 | return false; 102 | 103 | pollfd pollfds[2] = { 104 | {.fd = outPipe[0], .events = POLLIN, .revents = 0}, 105 | {.fd = errPipe[0], .events = POLLIN, .revents = 0}, 106 | }; 107 | 108 | while (1337) { 109 | int ret = poll(pollfds, 2, 5000); 110 | 111 | if (ret < 0) { 112 | if (errno == EINTR) 113 | continue; 114 | 115 | return false; 116 | } 117 | 118 | bool hupd = false; 119 | 120 | for (size_t i = 0; i < 2; ++i) { 121 | if (pollfds[i].revents & POLLHUP) { 122 | hupd = true; 123 | break; 124 | } 125 | } 126 | 127 | if (hupd) 128 | break; 129 | 130 | if (pollfds[0].revents & POLLIN) { 131 | while ((ret = read(outPipe[0], buf.data(), 1023)) > 0) { 132 | m_impl->out += std::string_view{(char*)buf.data(), (size_t)ret}; 133 | } 134 | 135 | buf.fill(0); 136 | } 137 | 138 | if (pollfds[1].revents & POLLIN) { 139 | while ((ret = read(errPipe[0], buf.data(), 1023)) > 0) { 140 | m_impl->err += std::string_view{(char*)buf.data(), (size_t)ret}; 141 | } 142 | 143 | buf.fill(0); 144 | } 145 | } 146 | 147 | // Final reads. Nonblock, so its ok. 148 | while ((ret = read(outPipe[0], buf.data(), 1023)) > 0) { 149 | m_impl->out += std::string_view{(char*)buf.data(), (size_t)ret}; 150 | } 151 | 152 | buf.fill(0); 153 | 154 | while ((ret = read(errPipe[0], buf.data(), 1023)) > 0) { 155 | m_impl->err += std::string_view{(char*)buf.data(), (size_t)ret}; 156 | } 157 | 158 | buf.fill(0); 159 | 160 | close(outPipe[0]); 161 | close(errPipe[0]); 162 | 163 | // reap child 164 | int status = 0; 165 | waitpid(pid, &status, 0); 166 | 167 | if (WIFEXITED(status)) 168 | m_impl->exitCode = WEXITSTATUS(status); 169 | 170 | return true; 171 | } 172 | 173 | return true; 174 | } 175 | 176 | bool Hyprutils::OS::CProcess::runAsync() { 177 | int socket[2]; 178 | if (pipe(socket) != 0) 179 | return false; 180 | 181 | pid_t child, grandchild; 182 | child = fork(); 183 | if (child < 0) { 184 | close(socket[0]); 185 | close(socket[1]); 186 | return false; 187 | } 188 | 189 | if (child == 0) { 190 | // run in child 191 | sigset_t set; 192 | sigemptyset(&set); 193 | sigprocmask(SIG_SETMASK, &set, nullptr); 194 | 195 | grandchild = fork(); 196 | if (grandchild == 0) { 197 | // run in grandchild 198 | close(socket[0]); 199 | close(socket[1]); 200 | // build argv 201 | std::vector argsC; 202 | argsC.emplace_back(strdup(m_impl->binary.c_str())); 203 | for (auto& arg : m_impl->args) { 204 | argsC.emplace_back(strdup(arg.c_str())); 205 | } 206 | 207 | argsC.emplace_back(nullptr); 208 | 209 | if (m_impl->stdoutFD != -1) 210 | dup2(m_impl->stdoutFD, 1); 211 | if (m_impl->stderrFD != -1) 212 | dup2(m_impl->stderrFD, 2); 213 | 214 | execvp(m_impl->binary.c_str(), (char* const*)argsC.data()); 215 | _exit(0); 216 | } 217 | close(socket[0]); 218 | if (write(socket[1], &grandchild, sizeof(grandchild)) != sizeof(grandchild)) { 219 | close(socket[1]); 220 | _exit(1); 221 | } 222 | close(socket[1]); 223 | _exit(0); 224 | } 225 | 226 | // run in parent 227 | close(socket[1]); 228 | ssize_t bytesRead = read(socket[0], &grandchild, sizeof(grandchild)); 229 | close(socket[0]); 230 | 231 | if (bytesRead != sizeof(grandchild)) { 232 | waitpid(child, nullptr, 0); 233 | return false; 234 | } 235 | 236 | // clear child and leave grandchild to init 237 | waitpid(child, nullptr, 0); 238 | 239 | m_impl->grandchildPid = grandchild; 240 | 241 | return true; 242 | } 243 | 244 | const std::string& Hyprutils::OS::CProcess::stdOut() { 245 | return m_impl->out; 246 | } 247 | 248 | const std::string& Hyprutils::OS::CProcess::stdErr() { 249 | return m_impl->err; 250 | } 251 | 252 | pid_t Hyprutils::OS::CProcess::pid() { 253 | return m_impl->grandchildPid; 254 | } 255 | 256 | int Hyprutils::OS::CProcess::exitCode() { 257 | return m_impl->exitCode; 258 | } 259 | 260 | void Hyprutils::OS::CProcess::setStdoutFD(int fd) { 261 | m_impl->stdoutFD = fd; 262 | } 263 | 264 | void Hyprutils::OS::CProcess::setStderrFD(int fd) { 265 | m_impl->stderrFD = fd; 266 | } 267 | -------------------------------------------------------------------------------- /src/path/Path.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace Hyprutils; 6 | 7 | namespace Hyprutils::Path { 8 | std::string fullConfigPath(std::string basePath, std::string programName) { 9 | return basePath + "/hypr/" + programName + ".conf"; 10 | } 11 | 12 | bool checkConfigExists(std::string basePath, std::string programName) { 13 | return std::filesystem::exists(fullConfigPath(basePath, programName)); 14 | } 15 | 16 | std::optional getHome() { 17 | static const auto homeDir = getenv("HOME"); 18 | 19 | if (!homeDir || !std::filesystem::path(homeDir).is_absolute()) 20 | return std::nullopt; 21 | 22 | return std::string(homeDir).append("/.config"); 23 | } 24 | 25 | std::optional getXdgConfigDirs() { 26 | static const auto xdgConfigDirs = getenv("XDG_CONFIG_DIRS"); 27 | 28 | if (!xdgConfigDirs) 29 | return std::nullopt; 30 | 31 | static const auto xdgConfigDirsList = String::CVarList(xdgConfigDirs, 0, ':'); 32 | 33 | return xdgConfigDirsList; 34 | } 35 | 36 | std::optional getXdgConfigHome() { 37 | static const auto xdgConfigHome = getenv("XDG_CONFIG_HOME"); 38 | 39 | if (!xdgConfigHome || !std::filesystem::path(xdgConfigHome).is_absolute()) 40 | return std::nullopt; 41 | 42 | return xdgConfigHome; 43 | } 44 | 45 | using T = std::optional; 46 | std::pair findConfig(std::string programName) { 47 | bool xdgConfigHomeExists = false; 48 | static const auto xdgConfigHome = getXdgConfigHome(); 49 | if (xdgConfigHome.has_value()) { 50 | xdgConfigHomeExists = true; 51 | if (checkConfigExists(xdgConfigHome.value(), programName)) 52 | return std::make_pair(fullConfigPath(xdgConfigHome.value(), programName), xdgConfigHome); 53 | } 54 | 55 | bool homeExists = false; 56 | static const auto home = getHome(); 57 | if (home.has_value()) { 58 | homeExists = true; 59 | if (checkConfigExists(home.value(), programName)) 60 | return std::make_pair(fullConfigPath(home.value(), programName), home); 61 | } 62 | 63 | static const auto xdgConfigDirs = getXdgConfigDirs(); 64 | if (xdgConfigDirs.has_value()) { 65 | for (auto& dir : xdgConfigDirs.value()) { 66 | if (checkConfigExists(dir, programName)) 67 | return std::make_pair(fullConfigPath(dir, programName), std::nullopt); 68 | } 69 | } 70 | 71 | if (checkConfigExists("/etc/xdg", programName)) 72 | return std::make_pair(fullConfigPath("/etc/xdg", programName), std::nullopt); 73 | 74 | if (xdgConfigHomeExists) 75 | return std::make_pair(std::nullopt, xdgConfigHome); 76 | else if (homeExists) 77 | return std::make_pair(std::nullopt, home); 78 | 79 | return std::make_pair(std::nullopt, std::nullopt); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/signal/Listener.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace Hyprutils::Signal; 4 | 5 | Hyprutils::Signal::CSignalListener::CSignalListener(std::function handler) : m_fHandler(handler) { 6 | ; 7 | } 8 | 9 | void Hyprutils::Signal::CSignalListener::emit(std::any data) { 10 | if (!m_fHandler) 11 | return; 12 | 13 | m_fHandler(data); 14 | } 15 | 16 | Hyprutils::Signal::CStaticSignalListener::CStaticSignalListener(std::function handler, void* owner) : m_pOwner(owner), m_fHandler(handler) { 17 | ; 18 | } 19 | 20 | void Hyprutils::Signal::CStaticSignalListener::emit(std::any data) { 21 | m_fHandler(m_pOwner, data); 22 | } 23 | -------------------------------------------------------------------------------- /src/signal/Signal.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace Hyprutils::Signal; 6 | using namespace Hyprutils::Memory; 7 | 8 | #define SP CSharedPointer 9 | #define WP CWeakPointer 10 | 11 | void Hyprutils::Signal::CSignal::emit(std::any data) { 12 | std::vector> listeners; 13 | for (auto& l : m_vListeners) { 14 | if (l.expired()) 15 | continue; 16 | 17 | listeners.emplace_back(l.lock()); 18 | } 19 | 20 | std::vector statics; 21 | statics.reserve(m_vStaticListeners.size()); 22 | for (auto& l : m_vStaticListeners) { 23 | statics.emplace_back(l.get()); 24 | } 25 | 26 | for (auto& l : listeners) { 27 | // if there is only one lock, it means the event is only held by the listeners 28 | // vector and was removed during our iteration 29 | if (l.strongRef() == 1) 30 | continue; 31 | 32 | l->emit(data); 33 | } 34 | 35 | for (auto& l : statics) { 36 | l->emit(data); 37 | } 38 | 39 | // release SPs 40 | listeners.clear(); 41 | 42 | // we cannot release any expired refs here as one of the listeners could've removed this object and 43 | // as such we'd be doing a UAF 44 | } 45 | 46 | CHyprSignalListener Hyprutils::Signal::CSignal::registerListener(std::function handler) { 47 | CHyprSignalListener listener = makeShared(handler); 48 | m_vListeners.emplace_back(listener); 49 | 50 | // housekeeping: remove any stale listeners 51 | std::erase_if(m_vListeners, [](const auto& other) { return other.expired(); }); 52 | 53 | return listener; 54 | } 55 | 56 | void Hyprutils::Signal::CSignal::registerStaticListener(std::function handler, void* owner) { 57 | m_vStaticListeners.emplace_back(std::make_unique(handler, owner)); 58 | } 59 | -------------------------------------------------------------------------------- /src/string/ConstVarList.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace Hyprutils::String; 6 | 7 | static std::string_view trim(const std::string_view& sv) { 8 | if (sv.empty()) 9 | return sv; 10 | 11 | size_t countBefore = 0; 12 | while (countBefore < sv.length() && std::isspace(sv.at(countBefore))) { 13 | countBefore++; 14 | } 15 | 16 | size_t countAfter = 0; 17 | while (countAfter < sv.length() - countBefore && std::isspace(sv.at(sv.length() - countAfter - 1))) { 18 | countAfter++; 19 | } 20 | 21 | return sv.substr(countBefore, sv.length() - countBefore - countAfter); 22 | } 23 | 24 | CConstVarList::CConstVarList(const std::string& in, const size_t lastArgNo, const char delim, const bool removeEmpty) : m_str(in) { 25 | if (in.empty()) 26 | return; 27 | 28 | size_t idx = 0; 29 | size_t pos = 0; 30 | std::ranges::replace_if(m_str, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0); 31 | 32 | for (const auto& s : m_str | std::views::split(0)) { 33 | if (removeEmpty && s.empty()) 34 | continue; 35 | if (++idx == lastArgNo) { 36 | m_args.emplace_back(trim(in.substr(pos))); 37 | break; 38 | } 39 | pos += s.size() + 1; 40 | m_args.emplace_back(trim(s.data())); 41 | } 42 | } 43 | 44 | std::string CConstVarList::join(const std::string& joiner, size_t from, size_t to) const { 45 | size_t last = to == 0 ? size() : to; 46 | 47 | std::string rolling; 48 | for (size_t i = from; i < last; ++i) { 49 | // cast can be removed once C++26's change to allow this is supported 50 | rolling += std::string{m_args[i]} + (i + 1 < last ? joiner : ""); 51 | } 52 | 53 | return rolling; 54 | } 55 | -------------------------------------------------------------------------------- /src/string/String.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace Hyprutils::String; 5 | 6 | std::string Hyprutils::String::trim(const std::string& in) { 7 | if (in.empty()) 8 | return in; 9 | 10 | size_t countBefore = 0; 11 | while (countBefore < in.length() && std::isspace(in.at(countBefore))) { 12 | countBefore++; 13 | } 14 | 15 | size_t countAfter = 0; 16 | while (countAfter < in.length() - countBefore && std::isspace(in.at(in.length() - countAfter - 1))) { 17 | countAfter++; 18 | } 19 | 20 | std::string result = in.substr(countBefore, in.length() - countBefore - countAfter); 21 | 22 | return result; 23 | } 24 | 25 | bool Hyprutils::String::isNumber(const std::string& str, bool allowfloat) { 26 | if (str.empty()) 27 | return false; 28 | 29 | bool decimalParsed = false; 30 | 31 | for (size_t i = 0; i < str.length(); ++i) { 32 | const char& c = str.at(i); 33 | 34 | if (i == 0 && str.at(i) == '-') { 35 | // only place where we allow - 36 | continue; 37 | } 38 | 39 | if (!isdigit(c)) { 40 | if (!allowfloat) 41 | return false; 42 | 43 | if (c != '.') 44 | return false; 45 | 46 | if (i == 0) 47 | return false; 48 | 49 | if (decimalParsed) 50 | return false; 51 | 52 | decimalParsed = true; 53 | 54 | continue; 55 | } 56 | } 57 | 58 | return isdigit(str.back()) != 0; 59 | } 60 | 61 | void Hyprutils::String::replaceInString(std::string& string, const std::string& what, const std::string& to) { 62 | if (string.empty()) 63 | return; 64 | size_t pos = 0; 65 | while ((pos = string.find(what, pos)) != std::string::npos) { 66 | string.replace(pos, what.length(), to); 67 | pos += to.length(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/string/VarList.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace Hyprutils::String; 7 | 8 | Hyprutils::String::CVarList::CVarList(const std::string& in, const size_t lastArgNo, const char delim, const bool removeEmpty) { 9 | if (!removeEmpty && in.empty()) 10 | m_vArgs.emplace_back(""); 11 | 12 | std::string args{in}; 13 | size_t idx = 0; 14 | size_t pos = 0; 15 | std::ranges::replace_if(args, [&](const char& c) { return delim == 's' ? std::isspace(c) : c == delim; }, 0); 16 | 17 | for (const auto& s : args | std::views::split(0)) { 18 | if (removeEmpty && s.empty()) 19 | continue; 20 | if (++idx == lastArgNo) { 21 | m_vArgs.emplace_back(trim(in.substr(pos))); 22 | break; 23 | } 24 | pos += s.size() + 1; 25 | m_vArgs.emplace_back(trim(s.data())); 26 | } 27 | } 28 | 29 | std::string Hyprutils::String::CVarList::join(const std::string& joiner, size_t from, size_t to) const { 30 | size_t last = to == 0 ? size() : to; 31 | 32 | std::string rolling; 33 | for (size_t i = from; i < last; ++i) { 34 | rolling += m_vArgs[i] + (i + 1 < last ? joiner : ""); 35 | } 36 | 37 | return rolling; 38 | } 39 | -------------------------------------------------------------------------------- /src/utils/ScopeGuard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace Hyprutils::Utils; 4 | 5 | Hyprutils::Utils::CScopeGuard::CScopeGuard(const std::function& fn_) : fn(fn_) { 6 | ; 7 | } 8 | 9 | Hyprutils::Utils::CScopeGuard::~CScopeGuard() { 10 | if (fn) 11 | fn(); 12 | } 13 | -------------------------------------------------------------------------------- /tests/animation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "shared.hpp" 7 | 8 | #define SP CSharedPointer 9 | #define WP CWeakPointer 10 | #define UP CUniquePointer 11 | 12 | using namespace Hyprutils::Animation; 13 | using namespace Hyprutils::Math; 14 | using namespace Hyprutils::Memory; 15 | 16 | class EmtpyContext {}; 17 | 18 | template 19 | using CAnimatedVariable = CGenericAnimatedVariable; 20 | 21 | template 22 | using PANIMVAR = SP>; 23 | 24 | template 25 | using PANIMVARREF = WP>; 26 | 27 | enum eAVTypes { 28 | INT = 1, 29 | TEST, 30 | }; 31 | 32 | struct SomeTestType { 33 | bool done = false; 34 | bool operator==(const SomeTestType& other) const { 35 | return done == other.done; 36 | } 37 | SomeTestType& operator=(const SomeTestType& other) { 38 | done = other.done; 39 | return *this; 40 | } 41 | }; 42 | 43 | CAnimationConfigTree animationTree; 44 | 45 | class CMyAnimationManager : public CAnimationManager { 46 | public: 47 | void tick() { 48 | for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) { 49 | const auto PAV = m_vActiveAnimatedVariables[i].lock(); 50 | if (!PAV || !PAV->ok() || !PAV->isBeingAnimated()) 51 | continue; 52 | 53 | const auto SPENT = PAV->getPercent(); 54 | const auto PBEZIER = getBezier(PAV->getBezierName()); 55 | 56 | if (SPENT >= 1.f || !PAV->enabled()) { 57 | PAV->warp(true, false); 58 | continue; 59 | } 60 | 61 | const auto POINTY = PBEZIER->getYForPoint(SPENT); 62 | 63 | switch (PAV->m_Type) { 64 | case eAVTypes::INT: { 65 | auto avInt = dynamic_cast*>(PAV.get()); 66 | if (!avInt) 67 | std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET; 68 | 69 | const auto DELTA = avInt->goal() - avInt->value(); 70 | avInt->value() = avInt->begun() + (DELTA * POINTY); 71 | } break; 72 | case eAVTypes::TEST: { 73 | auto avCustom = dynamic_cast*>(PAV.get()); 74 | if (!avCustom) 75 | std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET; 76 | 77 | if (SPENT >= 1.f) 78 | avCustom->value().done = true; 79 | } break; 80 | default: { 81 | std::cout << Colors::RED << "What are we even doing?" << Colors::RESET; 82 | } break; 83 | } 84 | 85 | PAV->onUpdate(); 86 | } 87 | 88 | tickDone(); 89 | } 90 | 91 | template 92 | void createAnimation(const VarType& v, PANIMVAR& av, const std::string& animationConfigName) { 93 | constexpr const eAVTypes EAVTYPE = std::is_same_v ? eAVTypes::INT : eAVTypes::TEST; 94 | const auto PAV = makeShared>(); 95 | 96 | PAV->create(EAVTYPE, static_cast(this), PAV, v); 97 | PAV->setConfig(animationTree.getConfig(animationConfigName)); 98 | av = std::move(PAV); 99 | } 100 | 101 | virtual void scheduleTick() { 102 | ; 103 | } 104 | 105 | virtual void onTicked() { 106 | ; 107 | } 108 | }; 109 | 110 | UP pAnimationManager; 111 | 112 | class Subject { 113 | public: 114 | Subject(const int& a, const int& b) { 115 | pAnimationManager->createAnimation(a, m_iA, "default"); 116 | pAnimationManager->createAnimation(b, m_iB, "internal"); 117 | pAnimationManager->createAnimation({}, m_iC, "default"); 118 | } 119 | PANIMVAR m_iA; 120 | PANIMVAR m_iB; 121 | PANIMVAR m_iC; 122 | }; 123 | 124 | int config() { 125 | pAnimationManager = makeUnique(); 126 | 127 | int ret = 0; 128 | 129 | animationTree.createNode("global"); 130 | animationTree.createNode("internal"); 131 | 132 | animationTree.createNode("foo", "internal"); 133 | animationTree.createNode("default", "global"); 134 | animationTree.createNode("bar", "default"); 135 | 136 | /* 137 | internal 138 | ↳ foo 139 | global 140 | ↳ default 141 | ↳ bar 142 | */ 143 | 144 | auto barCfg = animationTree.getConfig("bar"); 145 | auto internalCfg = animationTree.getConfig("internal"); 146 | 147 | // internal is a root node and should point to itself 148 | EXPECT(internalCfg->pParentAnimation.get(), internalCfg.get()); 149 | EXPECT(internalCfg->pValues.get(), internalCfg.get()); 150 | 151 | animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf"); 152 | 153 | EXPECT(barCfg->internalEnabled, -1); 154 | { 155 | const auto PVALUES = barCfg->pValues.lock(); 156 | EXPECT(PVALUES->internalEnabled, 1); 157 | EXPECT(PVALUES->internalBezier, "default"); 158 | EXPECT(PVALUES->internalStyle, "asdf"); 159 | EXPECT(PVALUES->internalSpeed, 4.0); 160 | } 161 | EXPECT(barCfg->pParentAnimation.get(), animationTree.getConfig("default").get()); 162 | 163 | // Overwrite our own values 164 | animationTree.setConfigForNode("bar", 1, 4.2, "test", "qwer"); 165 | 166 | { 167 | const auto PVALUES = barCfg->pValues.lock(); 168 | EXPECT(PVALUES->internalEnabled, 1); 169 | EXPECT(PVALUES->internalBezier, "test"); 170 | EXPECT(PVALUES->internalStyle, "qwer"); 171 | EXPECT(PVALUES->internalSpeed, 4.2f); 172 | } 173 | 174 | // Now overwrite the parent 175 | animationTree.setConfigForNode("default", 0, 0.0, "zxcv", "foo"); 176 | 177 | { 178 | // Expecting no change 179 | const auto PVALUES = barCfg->pValues.lock(); 180 | EXPECT(PVALUES->internalEnabled, 1); 181 | EXPECT(PVALUES->internalBezier, "test"); 182 | EXPECT(PVALUES->internalStyle, "qwer"); 183 | EXPECT(PVALUES->internalSpeed, 4.2f); 184 | } 185 | 186 | return ret; 187 | } 188 | 189 | int main(int argc, char** argv, char** envp) { 190 | int ret = config(); 191 | 192 | animationTree.createNode("global"); 193 | animationTree.createNode("internal"); 194 | 195 | animationTree.createNode("default", "global"); 196 | animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf"); 197 | 198 | Subject s(0, 0); 199 | 200 | EXPECT(s.m_iA->value(), 0); 201 | EXPECT(s.m_iB->value(), 0); 202 | 203 | // Test destruction of a CAnimatedVariable 204 | { 205 | Subject s2(10, 10); 206 | // Adds them to active 207 | *s2.m_iA = 1; 208 | *s2.m_iB = 2; 209 | // We deliberately do not tick here, to make sure the destructor removes active animated variables 210 | } 211 | 212 | EXPECT(pAnimationManager->shouldTickForNext(), false); 213 | EXPECT(s.m_iC->value().done, false); 214 | 215 | *s.m_iA = 10; 216 | *s.m_iB = 100; 217 | *s.m_iC = SomeTestType(true); 218 | 219 | EXPECT(s.m_iC->value().done, false); 220 | 221 | while (pAnimationManager->shouldTickForNext()) { 222 | pAnimationManager->tick(); 223 | } 224 | 225 | EXPECT(s.m_iA->value(), 10); 226 | EXPECT(s.m_iB->value(), 100); 227 | EXPECT(s.m_iC->value().done, true); 228 | 229 | s.m_iA->setValue(0); 230 | s.m_iB->setValue(0); 231 | 232 | while (pAnimationManager->shouldTickForNext()) { 233 | pAnimationManager->tick(); 234 | } 235 | 236 | EXPECT(s.m_iA->value(), 10); 237 | EXPECT(s.m_iB->value(), 100); 238 | 239 | // Test config stuff 240 | EXPECT(s.m_iA->getBezierName(), "default"); 241 | EXPECT(s.m_iA->getStyle(), "asdf"); 242 | EXPECT(s.m_iA->enabled(), true); 243 | 244 | animationTree.getConfig("global")->internalEnabled = 0; 245 | 246 | EXPECT(s.m_iA->enabled(), false); 247 | 248 | *s.m_iA = 50; 249 | pAnimationManager->tick(); // Expecting a warp 250 | EXPECT(s.m_iA->value(), 50); 251 | 252 | // Test missing pValues 253 | animationTree.getConfig("global")->internalEnabled = 0; 254 | animationTree.getConfig("default")->pValues.reset(); 255 | 256 | EXPECT(s.m_iA->enabled(), false); 257 | EXPECT(s.m_iA->getBezierName(), "default"); 258 | EXPECT(s.m_iA->getStyle(), ""); 259 | EXPECT(s.m_iA->getPercent(), 1.f); 260 | 261 | // Reset 262 | animationTree.setConfigForNode("default", 1, 1, "default"); 263 | 264 | // 265 | // Test callbacks 266 | // 267 | int beginCallbackRan = 0; 268 | int updateCallbackRan = 0; 269 | int endCallbackRan = 0; 270 | s.m_iA->setCallbackOnBegin([&beginCallbackRan](WP pav) { beginCallbackRan++; }); 271 | s.m_iA->setUpdateCallback([&updateCallbackRan](WP pav) { updateCallbackRan++; }); 272 | s.m_iA->setCallbackOnEnd([&endCallbackRan](WP pav) { endCallbackRan++; }, false); 273 | 274 | s.m_iA->setValueAndWarp(42); 275 | 276 | EXPECT(beginCallbackRan, 0); 277 | EXPECT(updateCallbackRan, 1); 278 | EXPECT(endCallbackRan, 2); // first called when setting the callback, then when warping. 279 | 280 | *s.m_iA = 1337; 281 | while (pAnimationManager->shouldTickForNext()) { 282 | pAnimationManager->tick(); 283 | } 284 | 285 | EXPECT(beginCallbackRan, 1); 286 | EXPECT(updateCallbackRan > 2, true); 287 | EXPECT(endCallbackRan, 3); 288 | 289 | std::vector> vars; 290 | for (int i = 0; i < 10; i++) { 291 | vars.resize(vars.size() + 1); 292 | pAnimationManager->createAnimation(1, vars.back(), "default"); 293 | *vars.back() = 1337; 294 | } 295 | 296 | // test adding / removing vars during a tick 297 | s.m_iA->resetAllCallbacks(); 298 | s.m_iA->setUpdateCallback([&vars](WP v) { 299 | if (v.lock() != vars.back()) 300 | vars.back()->warp(); 301 | }); 302 | s.m_iA->setCallbackOnEnd([&s, &vars](auto) { 303 | vars.resize(vars.size() + 1); 304 | pAnimationManager->createAnimation(1, vars.back(), "default"); 305 | *vars.back() = 1337; 306 | }); 307 | 308 | *s.m_iA = 1000000; 309 | 310 | while (pAnimationManager->shouldTickForNext()) { 311 | pAnimationManager->tick(); 312 | } 313 | 314 | EXPECT(s.m_iA->value(), 1000000); 315 | // all vars should be set to 1337 316 | EXPECT(std::find_if(vars.begin(), vars.end(), [](const auto& v) { return v->value() != 1337; }) == vars.end(), true); 317 | 318 | // test one-time callbacks 319 | s.m_iA->resetAllCallbacks(); 320 | s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, true); 321 | 322 | EXPECT(endCallbackRan, 4); 323 | 324 | s.m_iA->setValueAndWarp(10); 325 | 326 | EXPECT(endCallbackRan, 4); 327 | EXPECT(s.m_iA->value(), 10); 328 | 329 | // test warp 330 | *s.m_iA = 3; 331 | s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, false); 332 | 333 | s.m_iA->warp(false); 334 | EXPECT(endCallbackRan, 4); 335 | 336 | *s.m_iA = 4; 337 | s.m_iA->warp(true); 338 | EXPECT(endCallbackRan, 5); 339 | 340 | // test getCurveValue 341 | *s.m_iA = 0; 342 | EXPECT(s.m_iA->getCurveValue(), 0.f); 343 | s.m_iA->warp(); 344 | EXPECT(s.m_iA->getCurveValue(), 1.f); 345 | EXPECT(endCallbackRan, 6); 346 | 347 | // test end callback readding the var 348 | *s.m_iA = 5; 349 | s.m_iA->setCallbackOnEnd([&endCallbackRan](WP v) { 350 | endCallbackRan++; 351 | const auto PAV = dynamic_cast*>(v.lock().get()); 352 | 353 | *PAV = 10; 354 | PAV->setCallbackOnEnd([&endCallbackRan](WP v) { endCallbackRan++; }); 355 | }); 356 | 357 | while (pAnimationManager->shouldTickForNext()) { 358 | pAnimationManager->tick(); 359 | } 360 | 361 | EXPECT(endCallbackRan, 8); 362 | EXPECT(s.m_iA->value(), 10); 363 | 364 | // Test duplicate active anim vars are not allowed 365 | { 366 | EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); 367 | PANIMVAR a; 368 | pAnimationManager->createAnimation(1, a, "default"); 369 | EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); 370 | *a = 10; 371 | EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 1); 372 | *a = 20; 373 | EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 1); 374 | a->warp(); 375 | EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); 376 | EXPECT(a->value(), 20); 377 | } 378 | 379 | // Test no crash when animation manager gets destroyed 380 | { 381 | PANIMVAR a; 382 | pAnimationManager->createAnimation(1, a, "default"); 383 | *a = 10; 384 | pAnimationManager.reset(); 385 | EXPECT(a->isAnimationManagerDead(), true); 386 | a->setValueAndWarp(11); 387 | EXPECT(a->value(), 11); 388 | *a = 12; 389 | a->warp(); 390 | EXPECT(a->value(), 12); 391 | *a = 13; 392 | } // a gets destroyed 393 | 394 | EXPECT(pAnimationManager.get(), nullptr); 395 | 396 | return ret; 397 | } 398 | -------------------------------------------------------------------------------- /tests/filedescriptor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "shared.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace Hyprutils::OS; 8 | 9 | int main(int argc, char** argv, char** envp) { 10 | std::string name = "/test_filedescriptors"; 11 | CFileDescriptor fd(shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600)); 12 | 13 | int ret = 0; 14 | EXPECT(fd.isValid(), true); 15 | EXPECT(fd.isReadable(), true); 16 | 17 | int flags = fd.getFlags(); 18 | EXPECT(fd.getFlags(), FD_CLOEXEC); 19 | flags &= ~FD_CLOEXEC; 20 | fd.setFlags(flags); 21 | EXPECT(fd.getFlags(), !FD_CLOEXEC); 22 | 23 | CFileDescriptor fd2 = fd.duplicate(); 24 | EXPECT(fd.isValid(), true); 25 | EXPECT(fd.isReadable(), true); 26 | EXPECT(fd2.isValid(), true); 27 | EXPECT(fd2.isReadable(), true); 28 | 29 | CFileDescriptor fd3(fd2.take()); 30 | EXPECT(fd.isValid(), true); 31 | EXPECT(fd.isReadable(), true); 32 | EXPECT(fd2.isValid(), false); 33 | EXPECT(fd2.isReadable(), false); 34 | 35 | // .duplicate default flags is FD_CLOEXEC 36 | EXPECT(fd3.getFlags(), FD_CLOEXEC); 37 | 38 | fd.reset(); 39 | fd2.reset(); 40 | fd3.reset(); 41 | 42 | EXPECT(fd.isReadable(), false); 43 | EXPECT(fd2.isReadable(), false); 44 | EXPECT(fd3.isReadable(), false); 45 | 46 | shm_unlink(name.c_str()); 47 | 48 | return ret; 49 | } 50 | -------------------------------------------------------------------------------- /tests/math.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "shared.hpp" 4 | 5 | using namespace Hyprutils::Math; 6 | 7 | int main(int argc, char** argv, char** envp) { 8 | CRegion rg = {0, 0, 100, 100}; 9 | rg.add(CBox{{}, {20, 200}}); 10 | 11 | int ret = 0; 12 | 13 | EXPECT(rg.getExtents().height, 200); 14 | EXPECT(rg.getExtents().width, 100); 15 | 16 | rg.intersect(CBox{10, 10, 300, 300}); 17 | 18 | EXPECT(rg.getExtents().width, 90); 19 | EXPECT(rg.getExtents().height, 190); 20 | 21 | /*Box.cpp test cases*/ 22 | // Test default constructor and accessors 23 | { 24 | CBox box1; 25 | EXPECT(box1.x, 0); 26 | EXPECT(box1.y, 0); 27 | EXPECT(box1.width, 0); 28 | EXPECT(box1.height, 0); 29 | 30 | // Test parameterized constructor and accessors 31 | CBox box2(10, 20, 30, 40); 32 | EXPECT(box2.x, 10); 33 | EXPECT(box2.y, 20); 34 | EXPECT(box2.width, 30); 35 | EXPECT(box2.height, 40); 36 | 37 | // Test setters and getters 38 | box2.translate(Vector2D(5, -5)); 39 | EXPECT_VECTOR2D(box2.pos(), Vector2D(15, 15)); 40 | } 41 | 42 | //Test Scaling and Transformation 43 | { 44 | CBox box(10, 10, 20, 30); 45 | 46 | // Test scaling 47 | box.scale(2.0); 48 | EXPECT_VECTOR2D(box.size(), Vector2D(40, 60)); 49 | EXPECT_VECTOR2D(box.pos(), Vector2D(20, 20)); 50 | 51 | // Test scaling from center 52 | box.scaleFromCenter(0.5); 53 | EXPECT_VECTOR2D(box.size(), Vector2D(20, 30)); 54 | EXPECT_VECTOR2D(box.pos(), Vector2D(30, 35)); 55 | 56 | // Test transformation 57 | box.transform(HYPRUTILS_TRANSFORM_90, 100, 200); 58 | EXPECT_VECTOR2D(box.pos(), Vector2D(135, 30)); 59 | EXPECT_VECTOR2D(box.size(), Vector2D(30, 20)); 60 | 61 | // Test Intersection and Extents 62 | } 63 | 64 | { 65 | CBox box1(0, 0, 100, 100); 66 | CBox box2(50, 50, 100, 100); 67 | 68 | CBox intersection = box1.intersection(box2); 69 | EXPECT_VECTOR2D(intersection.pos(), Vector2D(50, 50)); 70 | EXPECT_VECTOR2D(intersection.size(), Vector2D(50, 50)); 71 | 72 | SBoxExtents extents = box1.extentsFrom(box2); 73 | EXPECT_VECTOR2D(extents.topLeft, Vector2D(50, 50)); 74 | EXPECT_VECTOR2D(extents.bottomRight, Vector2D(-50, -50)); 75 | } 76 | 77 | // Test Boundary Conditions and Special Cases 78 | { 79 | CBox box(0, 0, 50, 50); 80 | 81 | EXPECT(box.empty(), false); 82 | 83 | EXPECT(box.containsPoint(Vector2D(25, 25)), true); 84 | EXPECT(box.containsPoint(Vector2D(60, 60)), false); 85 | EXPECT(box.overlaps(CBox(25, 25, 50, 50)), true); 86 | EXPECT(box.inside(CBox(0, 0, 100, 100)), false); 87 | } 88 | 89 | // Test matrices 90 | { 91 | Mat3x3 jeremy = Mat3x3::outputProjection({1920, 1080}, HYPRUTILS_TRANSFORM_FLIPPED_90); 92 | Mat3x3 matrixBox = jeremy.projectBox(CBox{10, 10, 200, 200}, HYPRUTILS_TRANSFORM_NORMAL).translate({100, 100}).scale({1.25F, 1.5F}).transpose(); 93 | 94 | Mat3x3 expected = std::array{0, 0.46296296, 0, 0.3125, 0, 0, 19.84375, 36.055557, 1}; 95 | EXPECT(matrixBox, expected); 96 | } 97 | 98 | return ret; 99 | } -------------------------------------------------------------------------------- /tests/memory.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "shared.hpp" 4 | #include 5 | 6 | using namespace Hyprutils::Memory; 7 | 8 | #define SP CSharedPointer 9 | #define WP CWeakPointer 10 | #define UP CUniquePointer 11 | 12 | int main(int argc, char** argv, char** envp) { 13 | SP intPtr = makeShared(10); 14 | SP intPtr2 = makeShared(-1337); 15 | UP intUnique = makeUnique(420); 16 | 17 | int ret = 0; 18 | 19 | EXPECT(*intPtr, 10); 20 | EXPECT(intPtr.strongRef(), 1); 21 | EXPECT(*intUnique, 420); 22 | 23 | WP weak = intPtr; 24 | WP weakUnique = intUnique; 25 | 26 | EXPECT(*intPtr, 10); 27 | EXPECT(intPtr.strongRef(), 1); 28 | EXPECT(*weak, 10); 29 | EXPECT(weak.expired(), false); 30 | EXPECT(*weakUnique, 420); 31 | EXPECT(weakUnique.expired(), false); 32 | EXPECT(intUnique.impl_->wref(), 1); 33 | 34 | SP sharedFromUnique = weakUnique.lock(); 35 | EXPECT(sharedFromUnique, nullptr); 36 | 37 | std::vector> sps; 38 | sps.push_back(intPtr); 39 | sps.emplace_back(intPtr); 40 | sps.push_back(intPtr2); 41 | sps.emplace_back(intPtr2); 42 | std::erase_if(sps, [intPtr](const auto& e) { return e == intPtr; }); 43 | 44 | intPtr.reset(); 45 | intUnique.reset(); 46 | 47 | EXPECT(weak.impl_->ref(), 0); 48 | EXPECT(weakUnique.impl_->ref(), 0); 49 | EXPECT(weakUnique.impl_->wref(), 1); 50 | EXPECT(intPtr2.strongRef(), 3); 51 | 52 | EXPECT(weak.expired(), true); 53 | EXPECT(weakUnique.expired(), true); 54 | 55 | auto intPtr2AsUint = reinterpretPointerCast(intPtr2); 56 | EXPECT(intPtr2.strongRef(), 4); 57 | EXPECT(intPtr2AsUint.strongRef(), 4); 58 | 59 | EXPECT(*intPtr2AsUint > 0, true); 60 | EXPECT(*intPtr2AsUint, (unsigned int)(int)-1337); 61 | *intPtr2AsUint = 10; 62 | EXPECT(*intPtr2AsUint, 10); 63 | EXPECT(*intPtr2, 10); 64 | 65 | return ret; 66 | } -------------------------------------------------------------------------------- /tests/os.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "shared.hpp" 7 | 8 | using namespace Hyprutils::OS; 9 | 10 | int main(int argc, char** argv, char** envp) { 11 | int ret = 0; 12 | 13 | CProcess process("sh", {"-c", "echo \"Hello $WORLD!\""}); 14 | process.addEnv("WORLD", "World"); 15 | 16 | EXPECT(process.runAsync(), true); 17 | EXPECT(process.runSync(), true); 18 | 19 | EXPECT(process.stdOut(), std::string{"Hello World!\n"}); 20 | EXPECT(process.stdErr(), std::string{""}); 21 | EXPECT(process.exitCode(), 0); 22 | 23 | CProcess process2("sh", {"-c", "while true; do sleep 1; done;"}); 24 | 25 | EXPECT(process2.runAsync(), true); 26 | EXPECT(getpgid(process2.pid()) >= 0, true); 27 | 28 | kill(process2.pid(), SIGKILL); 29 | 30 | CProcess process3("sh", {"-c", "cat /geryueruggbuergheruger/reugiheruygyuerghuryeghyer/eruihgyuerguyerghyuerghuyergerguyer/NON_EXISTENT"}); 31 | EXPECT(process3.runSync(), true); 32 | EXPECT(process3.exitCode(), 1); 33 | 34 | return ret; 35 | } -------------------------------------------------------------------------------- /tests/shared.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace Colors { 5 | constexpr const char* RED = "\x1b[31m"; 6 | constexpr const char* GREEN = "\x1b[32m"; 7 | constexpr const char* YELLOW = "\x1b[33m"; 8 | constexpr const char* BLUE = "\x1b[34m"; 9 | constexpr const char* MAGENTA = "\x1b[35m"; 10 | constexpr const char* CYAN = "\x1b[36m"; 11 | constexpr const char* RESET = "\x1b[0m"; 12 | }; 13 | 14 | #define EXPECT(expr, val) \ 15 | if (const auto RESULT = expr; RESULT != (val)) { \ 16 | std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected " << val << " but got " << RESULT << "\n"; \ 17 | ret = 1; \ 18 | } else { \ 19 | std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got " << val << "\n"; \ 20 | } 21 | #define EXPECT_VECTOR2D(expr, val) \ 22 | do { \ 23 | const auto& RESULT = expr; \ 24 | const auto& EXPECTED = val; \ 25 | if (!(std::abs(RESULT.x - EXPECTED.x) < 1e-6 && std::abs(RESULT.y - EXPECTED.y) < 1e-6)) { \ 26 | std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected (" << EXPECTED.x << ", " << EXPECTED.y << ") but got (" << RESULT.x << ", " \ 27 | << RESULT.y << ")\n"; \ 28 | ret = 1; \ 29 | } else { \ 30 | std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got (" << RESULT.x << ", " << RESULT.y << ")\n"; \ 31 | } \ 32 | } while (0) 33 | -------------------------------------------------------------------------------- /tests/signal.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "shared.hpp" 4 | 5 | using namespace Hyprutils::Signal; 6 | using namespace Hyprutils::Memory; 7 | 8 | int main(int argc, char** argv, char** envp) { 9 | int ret = 0; 10 | 11 | CSignal signal; 12 | int data = 0; 13 | auto listener = signal.registerListener([&]([[maybe_unused]] std::any d) { data = 1; }); 14 | 15 | signal.emit(); 16 | 17 | EXPECT(data, 1); 18 | 19 | data = 0; 20 | 21 | listener.reset(); 22 | 23 | signal.emit(); 24 | 25 | EXPECT(data, 0); 26 | 27 | return ret; 28 | } -------------------------------------------------------------------------------- /tests/string.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "shared.hpp" 5 | 6 | using namespace Hyprutils::String; 7 | 8 | int main(int argc, char** argv, char** envp) { 9 | int ret = 0; 10 | 11 | EXPECT(trim(" a "), "a"); 12 | EXPECT(trim(" a a "), "a a"); 13 | EXPECT(trim("a"), "a"); 14 | EXPECT(trim(" "), ""); 15 | 16 | EXPECT(isNumber("99214123434"), true); 17 | EXPECT(isNumber("-35252345234"), true); 18 | EXPECT(isNumber("---3423--432"), false); 19 | EXPECT(isNumber("s---3423--432"), false); 20 | EXPECT(isNumber("---3423--432s"), false); 21 | EXPECT(isNumber("1s"), false); 22 | EXPECT(isNumber(""), false); 23 | EXPECT(isNumber("-"), false); 24 | EXPECT(isNumber("--0"), false); 25 | EXPECT(isNumber("abc"), false); 26 | EXPECT(isNumber("0.0", true), true); 27 | EXPECT(isNumber("0.2", true), true); 28 | EXPECT(isNumber("0.", true), false); 29 | EXPECT(isNumber(".0", true), false); 30 | EXPECT(isNumber("", true), false); 31 | EXPECT(isNumber("vvss", true), false); 32 | EXPECT(isNumber("0.9999s", true), false); 33 | EXPECT(isNumber("s0.9999", true), false); 34 | EXPECT(isNumber("-1.0", true), true); 35 | EXPECT(isNumber("-1..0", true), false); 36 | EXPECT(isNumber("-10.0000000001", true), true); 37 | 38 | CVarList list("hello world!", 0, 's', true); 39 | EXPECT(list[0], "hello"); 40 | EXPECT(list[1], "world!"); 41 | 42 | CConstVarList listConst("hello world!", 0, 's', true); 43 | EXPECT(listConst[0], "hello"); 44 | EXPECT(listConst[1], "world!"); 45 | 46 | std::string hello = "hello world!"; 47 | replaceInString(hello, "hello", "hi"); 48 | EXPECT(hello, "hi world!"); 49 | 50 | return ret; 51 | } --------------------------------------------------------------------------------