├── .clang-format ├── .clang-tidy ├── .eslintrc.cjs ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── bug-report.yml └── workflows │ ├── build-container.yml │ ├── publish.yml │ ├── server.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── addon ├── CMakeLists.txt └── addon.cpp ├── cmake ├── FindPulseAudio.cmake └── cpm.cmake ├── docker ├── Dockerfile └── build.sh ├── include └── vencord │ ├── logger.hpp │ └── patchbay.hpp ├── lib ├── index.js ├── module.d.ts └── options.js ├── package.json ├── pnpm-lock.yaml ├── private ├── message.hpp └── patchbay.impl.hpp ├── server ├── CMakeLists.txt └── main.cpp ├── src ├── logger.cpp ├── patchbay.cpp └── patchbay.impl.cpp └── tests └── node └── api.test.js /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | InsertNewlineAtEOF: true 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: 8 | Enabled: true 9 | AcrossComments: false 10 | AcrossEmptyLines: false 11 | AlignConsecutiveDeclarations: false 12 | AlignEscapedNewlines: Right 13 | AlignOperands: true 14 | AlignTrailingComments: true 15 | AllowAllArgumentsOnNextLine: true 16 | AllowAllConstructorInitializersOnNextLine: true 17 | AllowAllParametersOfDeclarationOnNextLine: true 18 | AllowShortBlocksOnASingleLine: Never 19 | AllowShortCaseLabelsOnASingleLine: false 20 | AllowShortFunctionsOnASingleLine: Empty 21 | AllowShortLambdasOnASingleLine: Inline 22 | AllowShortIfStatementsOnASingleLine: Never 23 | AllowShortLoopsOnASingleLine: false 24 | AlwaysBreakAfterDefinitionReturnType: None 25 | AlwaysBreakAfterReturnType: None 26 | AlwaysBreakBeforeMultilineStrings: false 27 | AlwaysBreakTemplateDeclarations: Yes 28 | BinPackArguments: true 29 | BinPackParameters: true 30 | BraceWrapping: 31 | BeforeLambdaBody: true 32 | AfterCaseLabel: false 33 | AfterClass: true 34 | AfterControlStatement: true 35 | AfterEnum: true 36 | AfterFunction: true 37 | AfterNamespace: true 38 | AfterObjCDeclaration: true 39 | AfterStruct: true 40 | AfterUnion: false 41 | AfterExternBlock: true 42 | BeforeCatch: true 43 | BeforeElse: true 44 | IndentBraces: false 45 | SplitEmptyFunction: true 46 | SplitEmptyRecord: true 47 | SplitEmptyNamespace: true 48 | BreakBeforeBinaryOperators: None 49 | BreakBeforeBraces: Custom 50 | BreakBeforeInheritanceComma: false 51 | BreakInheritanceList: BeforeColon 52 | BreakBeforeTernaryOperators: true 53 | BreakConstructorInitializersBeforeComma: false 54 | BreakConstructorInitializers: BeforeColon 55 | BreakAfterJavaFieldAnnotations: false 56 | BreakStringLiterals: true 57 | ColumnLimit: 120 58 | CompactNamespaces: false 59 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 60 | ConstructorInitializerIndentWidth: 4 61 | ContinuationIndentWidth: 4 62 | Cpp11BracedListStyle: true 63 | DeriveLineEnding: true 64 | DerivePointerAlignment: false 65 | FixNamespaceComments: true 66 | IndentCaseLabels: false 67 | IndentGotoLabels: true 68 | IndentPPDirectives: None 69 | IndentWidth: 4 70 | IndentWrappedFunctionNames: false 71 | KeepEmptyLinesAtTheStartOfBlocks: true 72 | NamespaceIndentation: All 73 | PointerAlignment: Right 74 | ReflowComments: true 75 | SortIncludes: false 76 | SortUsingDeclarations: true 77 | SpaceAfterCStyleCast: false 78 | SpaceAfterLogicalNot: false 79 | SpaceAfterTemplateKeyword: true 80 | SpaceBeforeAssignmentOperators: true 81 | SpaceBeforeCpp11BracedList: false 82 | SpaceBeforeCtorInitializerColon: true 83 | SpaceBeforeInheritanceColon: true 84 | SpaceBeforeParens: ControlStatements 85 | SpaceBeforeRangeBasedForLoopColon: true 86 | SpaceInEmptyBlock: false 87 | SpaceInEmptyParentheses: false 88 | SpacesBeforeTrailingComments: 1 89 | SpacesInAngles: false 90 | SpacesInConditionalStatement: false 91 | SpacesInContainerLiterals: true 92 | SpacesInCStyleCastParentheses: false 93 | SpacesInParentheses: false 94 | SpacesInSquareBrackets: false 95 | SpaceBeforeSquareBrackets: false 96 | Standard: Latest 97 | TabWidth: 4 98 | UseCRLF: false 99 | UseTab: Never 100 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: "*, 3 | -abseil-*, 4 | -altera-*, 5 | -android-*, 6 | -fuchsia-*, 7 | -google-*, 8 | -llvm*, 9 | -modernize-use-trailing-return-type, 10 | -zircon-*, 11 | -readability-else-after-return, 12 | -readability-static-accessed-through-instance, 13 | -readability-avoid-const-params-in-decls, 14 | -cppcoreguidelines-non-private-member-variables-in-classes, 15 | -misc-non-private-member-variables-in-classes, 16 | -readability-redundant-access-specifiers, 17 | -cppcoreguidelines-special-member-functions, 18 | -hicpp-special-member-functions, 19 | -readability-implicit-bool-conversion, 20 | -hicpp-explicit-conversions, 21 | -*-magic-numbers, 22 | -readability-named-parameter, 23 | -hicpp-named-parameter, 24 | -readability-identifier-length, 25 | -cppcoreguidelines-owning-memory, 26 | -cppcoreguidelines-pro-type-reinterpret-cast, 27 | -cppcoreguidelines-avoid-non-const-global-variables, 28 | -hicpp-signed-bitwise, 29 | -*-uppercase-literal-suffix, 30 | -*-cognitive-complexity, 31 | -*-multiway-paths-covered, 32 | -*-switch-missing-default-case, 33 | -*-avoid-endl, 34 | -cppcoreguidelines-pro-type-static-cast-downcast, 35 | -misc-header-include-cycle, 36 | -cppcoreguidelines-pro-type-vararg, 37 | -*-member-init, 38 | -bugprone-easily-swappable-parameters, 39 | -hicpp-vararg, 40 | -cppcoreguidelines-pro-bounds-pointer-arithmetic, 41 | -*-dcl21-cpp, 42 | -cert-err58-cpp, 43 | -performance-no-int-to-ptr, 44 | -cppcoreguidelines-interfaces-global-init, 45 | -*-avoid-c-arrays, 46 | -hicpp-no-array-decay 47 | -*-array-to-pointer-decay 48 | " 49 | WarningsAsErrors: '' 50 | HeaderFilterRegex: '' 51 | FormatStyle: none 52 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | }, 5 | extends : ["eslint:recommended"], 6 | parserOptions: { 7 | ecmaVersion: "latest", 8 | sourceType : "module", 9 | }, 10 | rules: { 11 | "object-curly-spacing": ["error", "always"], 12 | "brace-style" : ["error", "allman"], 13 | quotes : ["error", "double"], 14 | semi : ["error", "always"], 15 | "linebreak-style" : ["error", "unix"], 16 | indent : ["error", 4], 17 | "no-else-return" : 1, 18 | "space-unary-ops" : 2, 19 | "keyword-spacing" : ["error", { 20 | "before": true, 21 | "after" : true, 22 | }], 23 | "arrow-spacing" : "error", 24 | "comma-spacing" : ["error", { "before": false, "after": true }], 25 | "space-infix-ops" : ["error", { "int32Hint": false }], 26 | "space-before-blocks": ["error", { "functions": "always", "keywords": "always", "classes": "always" }], 27 | "no-multi-spaces" : "error", 28 | "no-trailing-spaces" : "error", 29 | "semi-spacing" : "error", 30 | "key-spacing" : [2, { 31 | "singleLine": { 32 | "beforeColon": false, 33 | "afterColon" : true 34 | }, 35 | "multiLine": { 36 | "beforeColon": false, 37 | "afterColon" : true, 38 | "align" : "colon" 39 | } 40 | }] 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: Curve 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug Report 2 | description: Report a bug in the current version of venmic 3 | labels: ["bug"] 4 | assignees: Curve 5 | 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: ✍️ Bug Description 10 | description: A clear and concise description of what the bug is. 11 | validations: 12 | required: true 13 | 14 | - type: textarea 15 | attributes: 16 | label: 🔁 Steps to reproduce 17 | description: Steps to reproduce the behavior. 18 | validations: 19 | required: true 20 | 21 | - type: textarea 22 | attributes: 23 | label: 🗒️ Debug Output 24 | description: Please dump the contents of the log or stdout here (see https://github.com/Vencord/venmic#-debugging). 25 | validations: 26 | required: true 27 | 28 | - type: textarea 29 | attributes: 30 | label: ❔ Expected behavior 31 | description: A clear and concise description of what you expected to happen. 32 | validations: 33 | required: false 34 | 35 | - type: textarea 36 | attributes: 37 | label: 👀 Screenshots 38 | description: (If applicable) Add screenshots from applications like qpwgraph (https://flathub.org/apps/org.rncbc.qpwgraph) here. 39 | validations: 40 | required: false 41 | 42 | - type: input 43 | attributes: 44 | label: 📦 Vesktop Version 45 | description: Please note the Vesktop version used (can be found in Vesktop -> About Vesktop). 46 | validations: 47 | required: true 48 | 49 | - type: input 50 | attributes: 51 | label: 🖥️ Distribution 52 | description: Please note the distribution you use here as well as it's release if applicable. 53 | validations: 54 | required: false 55 | 56 | - type: checkboxes 57 | attributes: 58 | label: ✅ I confirm that... 59 | description: Please confirm these things by clicking the checkboxes 60 | options: 61 | - label: I have collected all the required information and read all the comments in this document 62 | required: true 63 | - label: I searched for an existing bug report for this issue 64 | required: true 65 | - label: the problem does occur with the reproduction steps I provided 66 | required: true 67 | -------------------------------------------------------------------------------- /.github/workflows/build-container.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | inputs: 4 | skip-cache: 5 | type: boolean 6 | description: Skip Cache 7 | 8 | name: 🐳 Build & Publish Docker Image 9 | 10 | jobs: 11 | build-container: 12 | strategy: 13 | matrix: 14 | arch: ["arm64", "amd64"] 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: 📥 Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: 🤖 Setup Qemu 23 | uses: docker/setup-qemu-action@v3 24 | with: 25 | platforms: "arm64" 26 | 27 | - name: 🐋 Setup Buildx 28 | uses: docker/setup-buildx-action@v3 29 | 30 | - name: 🦥 Cache Image 31 | id: cache-image 32 | uses: actions/cache@v4 33 | with: 34 | key: venmic-builder-cache-${{ matrix.arch }} 35 | path: | 36 | docker/venmic-builder-${{ matrix.arch }}.tar 37 | 38 | - name: 🏗️ Build Image 39 | if: steps.cache-image.outputs.cache-hit != 'true' && ${{ github.event.inputs.skip-cache != 'true' }} 40 | run: | 41 | cd docker 42 | 43 | docker buildx build --platform=linux/${{ matrix.arch }} --tag venmic-builder-${{ matrix.arch }} --load . 44 | docker save venmic-builder-${{ matrix.arch }} > venmic-builder-${{ matrix.arch }}.tar 45 | 46 | - name: 🔐 Login 47 | uses: docker/login-action@v3 48 | with: 49 | registry: ghcr.io 50 | username: ${{ github.actor }} 51 | password: ${{ secrets.GITHUB_TOKEN }} 52 | 53 | - name: ♻️ Publish Images 54 | run: | 55 | docker load < docker/venmic-builder-${{ matrix.arch }}.tar 56 | docker image tag venmic-builder-${{ matrix.arch }} ghcr.io/vencord/venmic-builder-${{ matrix.arch }} 57 | 58 | docker push ghcr.io/vencord/venmic-builder-${{ matrix.arch }}:latest 59 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | inputs: 4 | test: 5 | type: boolean 6 | description: Skip publishing step 7 | fix: 8 | type: boolean 9 | description: Skip build step 10 | reuse: 11 | type: string 12 | description: Workflow to reuse artifacts from 13 | no-tag-check: 14 | type: boolean 15 | description: Skip tag checking step 16 | push: 17 | tags: 18 | - "*" 19 | 20 | name: 🚀 Publish Package 21 | 22 | jobs: 23 | build: 24 | strategy: 25 | matrix: 26 | arch: ["arm64", "amd64"] 27 | 28 | runs-on: ubuntu-latest 29 | 30 | steps: 31 | - name: 📥 Checkout 32 | uses: actions/checkout@v4 33 | 34 | - name: 🤖 Setup Qemu 35 | uses: docker/setup-qemu-action@v3 36 | with: 37 | platforms: "arm64" 38 | 39 | - name: 🏗️ Build Addon 40 | if: ${{ github.event.inputs.fix != 'true' }} 41 | run: | 42 | docker run -v ${{ github.workspace }}:/work --platform linux/${{ matrix.arch }} ghcr.io/vencord/venmic-builder-${{ matrix.arch }}:latest /build.sh 43 | 44 | - name: ♻️ Reuse Artifact 45 | if: ${{ github.event.inputs.fix == 'true' }} 46 | uses: actions/download-artifact@v4 47 | with: 48 | name: addon-${{ matrix.arch }} 49 | path: build/Release 50 | github-token: ${{ secrets.ACTIONS_TOKEN }} 51 | run-id: ${{ github.event.inputs.reuse }} 52 | 53 | - name: 📤 Upload Artifact 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: addon-${{ matrix.arch }} 57 | path: build/Release 58 | 59 | publish: 60 | needs: [build] 61 | runs-on: ubuntu-latest 62 | container: fedora:38 63 | 64 | steps: 65 | - name: 📥 Checkout 66 | uses: actions/checkout@v4 67 | 68 | - name: 🛑 Check Tag 69 | if: ${{ github.event.inputs.no-tag-check != 'true' }} 70 | run: | 71 | dnf install -y jq 72 | pkg_version="v$(jq -r .version < package.json)" 73 | if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then 74 | echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2 75 | exit 1 76 | fi 77 | 78 | - name: 🍃 Install Node 79 | uses: actions/setup-node@v4 80 | with: 81 | node-version: 16 82 | registry-url: "https://registry.npmjs.org" 83 | 84 | - name: 🍃 Install pnpm 85 | uses: pnpm/action-setup@v3 86 | with: 87 | version: 8 88 | run_install: false 89 | 90 | - name: 🏗️ Setup Dependencies 91 | run: pnpm install --ignore-scripts 92 | 93 | - name: 📦 Download Build (amd64) 94 | uses: actions/download-artifact@v4 95 | with: 96 | name: addon-amd64 97 | path: build/Release 98 | 99 | - name: 🛠️ Prepare Prebuilds (amd64) 100 | run: pnpm pkg-prebuilds-copy --baseDir build/Release --source venmic-addon.node --name=venmic-addon --strip --napi_version=7 --arch=x64 101 | 102 | - name: 📦 Download Build (arm64) 103 | uses: actions/download-artifact@v4 104 | with: 105 | name: addon-arm64 106 | path: build/Release 107 | 108 | - name: 🛠️ Prepare Prebuilds (arm64) 109 | run: pnpm pkg-prebuilds-copy --baseDir build/Release --source venmic-addon.node --name=venmic-addon --strip --napi_version=7 --arch=arm64 110 | 111 | - name: 🛒 Publish 112 | if: ${{ github.event.inputs.test != 'true' }} 113 | run: pnpm publish 114 | env: 115 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 116 | -------------------------------------------------------------------------------- /.github/workflows/server.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | pull_request: 4 | 5 | name: 🏗️ Build Server 6 | 7 | jobs: 8 | build-fedora: 9 | runs-on: ubuntu-latest 10 | container: fedora:38 11 | 12 | steps: 13 | - name: 📥 Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: 👷 Build Dependencies 17 | run: | 18 | dnf install -y make automake gcc gcc-c++ kernel-devel cmake git 19 | dnf install -y pipewire-devel pipewire-libs pulseaudio-libs-devel pipewire-pulseaudio 20 | 21 | - name: 🔨 Build 22 | run: | 23 | cmake -B build 24 | cmake --build build 25 | 26 | - name: 🚀 Upload Artifact 27 | uses: actions/upload-artifact@v3 28 | with: 29 | name: server-amd64 30 | path: build/server 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | pull_request: 4 | 5 | name: 🧪 Test 6 | 7 | jobs: 8 | test-addon: 9 | runs-on: ubuntu-latest 10 | container: archlinux:base-devel 11 | 12 | steps: 13 | - name: 📥 Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: 👷 Build Dependencies 17 | run: "pacman --noconfirm -Syu base-devel cmake gcc git make pipewire pipewire-pulse" 18 | 19 | - name: 🍃 Install Node 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 16 23 | 24 | - name: 🍃 Install pnpm 25 | uses: pnpm/action-setup@v2 26 | with: 27 | version: 8 28 | run_install: false 29 | 30 | - name: 🔨 Build 31 | run: "pnpm install" 32 | 33 | - name: 🧪 Test 34 | run: "pnpm run test" 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | prebuilds 3 | build 4 | 5 | .vscode 6 | .cache 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | 4 | docker 5 | server 6 | tests 7 | 8 | .gitignore 9 | .github 10 | 11 | .clang-* 12 | .eslint* 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | project(venmic LANGUAGES CXX VERSION 6.1.0) 3 | 4 | # -------------------------------------------------------------------------------------------------------- 5 | # Library options 6 | # -------------------------------------------------------------------------------------------------------- 7 | 8 | option(venmic_addon "Build as addon" OFF) 9 | option(venmic_server "Build as rest server" ON) 10 | option(venmic_prefer_remote "Prefer remote packages over local packages" ON) 11 | 12 | # -------------------------------------------------------------------------------------------------------- 13 | # Addon and Rest-Server are mutually exclusive 14 | # -------------------------------------------------------------------------------------------------------- 15 | 16 | if (venmic_addon) 17 | set(venmic_server OFF) 18 | endif() 19 | 20 | if (venmic_server) 21 | set(venmic_addon OFF) 22 | endif() 23 | 24 | # -------------------------------------------------------------------------------------------------------- 25 | # Sync `CPM_DOWNLOAD_ALL` with `venmic_prefer_remote` 26 | # -------------------------------------------------------------------------------------------------------- 27 | 28 | if (venmic_prefer_remote) 29 | message(STATUS "[venmic] Avoiding local packages as 'venmic_prefer_remote' is ON") 30 | endif() 31 | 32 | set(CPM_DOWNLOAD_ALL ${venmic_prefer_remote}) 33 | 34 | # -------------------------------------------------------------------------------------------------------- 35 | # CMake options 36 | # -------------------------------------------------------------------------------------------------------- 37 | 38 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 39 | 40 | # -------------------------------------------------------------------------------------------------------- 41 | # Create library 42 | # -------------------------------------------------------------------------------------------------------- 43 | 44 | add_library(${PROJECT_NAME}) 45 | add_library(vencord::venmic ALIAS ${PROJECT_NAME}) 46 | 47 | target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) 48 | set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20 CXX_EXTENSIONS OFF CXX_STANDARD_REQUIRED ON) 49 | 50 | if (PROJECT_IS_TOP_LEVEL) 51 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror -pedantic -pedantic-errors -Wfatal-errors) 52 | endif() 53 | 54 | if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") 55 | target_compile_options(${PROJECT_NAME} PUBLIC -Wno-attributes=vc::) 56 | else() 57 | target_compile_options(${PROJECT_NAME} PUBLIC -Wno-unknown-attributes) 58 | endif() 59 | 60 | target_compile_options(${PROJECT_NAME} PRIVATE -Wno-missing-field-initializers -Wno-cast-function-type) 61 | 62 | # -------------------------------------------------------------------------------------------------------- 63 | # Add source files 64 | # -------------------------------------------------------------------------------------------------------- 65 | 66 | file(GLOB src "src/*.cpp") 67 | target_sources(${PROJECT_NAME} PRIVATE ${src}) 68 | 69 | # -------------------------------------------------------------------------------------------------------- 70 | # Include "include" folder 71 | # -------------------------------------------------------------------------------------------------------- 72 | 73 | target_include_directories(${PROJECT_NAME} PUBLIC "include") 74 | target_include_directories(${PROJECT_NAME} PRIVATE "include/vencord" "private") 75 | 76 | # -------------------------------------------------------------------------------------------------------- 77 | # Setup compile definitions 78 | # -------------------------------------------------------------------------------------------------------- 79 | 80 | target_compile_definitions(${PROJECT_NAME} PUBLIC VENMIC_VERSION="${PROJECT_VERSION}") 81 | 82 | # -------------------------------------------------------------------------------------------------------- 83 | # Setup Dependencies 84 | # -------------------------------------------------------------------------------------------------------- 85 | 86 | include("cmake/cpm.cmake") 87 | 88 | CPMFindPackage( 89 | NAME rohrkabel 90 | VERSION 7.0 91 | GIT_REPOSITORY "https://github.com/Curve/rohrkabel" 92 | ) 93 | 94 | CPMFindPackage( 95 | NAME tl-expected 96 | VERSION 1.1.0 97 | GIT_REPOSITORY "https://github.com/TartanLlama/expected" 98 | ) 99 | 100 | CPMFindPackage( 101 | NAME channel 102 | VERSION 2.3 103 | GIT_REPOSITORY "https://github.com/Curve/channel" 104 | ) 105 | 106 | CPMFindPackage( 107 | NAME range-v3 108 | GIT_TAG 0.12.0 109 | GIT_REPOSITORY "https://github.com/ericniebler/range-v3" 110 | ) 111 | 112 | CPMFindPackage( 113 | NAME glaze 114 | VERSION 2.6.8 115 | GIT_REPOSITORY "https://github.com/stephenberry/glaze" 116 | ) 117 | 118 | CPMFindPackage( 119 | NAME spdlog 120 | VERSION 1.14.1 121 | GIT_REPOSITORY "https://github.com/gabime/spdlog" 122 | ) 123 | 124 | target_link_libraries(${PROJECT_NAME} PUBLIC cr::rohrkabel tl::expected cr::channel glaze::glaze range-v3::meta spdlog::spdlog) 125 | 126 | # -------------------------------------------------------------------------------------------------------- 127 | # Custom Find-Package configurations 128 | # -------------------------------------------------------------------------------------------------------- 129 | 130 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 131 | 132 | # -------------------------------------------------------------------------------------------------------- 133 | # Link PulseAudio 134 | # -------------------------------------------------------------------------------------------------------- 135 | 136 | find_package(PulseAudio) 137 | target_link_libraries(${PROJECT_NAME} PUBLIC PulseAudio::PulseAudio) 138 | 139 | # -------------------------------------------------------------------------------------------------------- 140 | # Setup Rest Server 141 | # -------------------------------------------------------------------------------------------------------- 142 | 143 | if (venmic_server) 144 | add_subdirectory(server) 145 | endif() 146 | 147 | # -------------------------------------------------------------------------------------------------------- 148 | # Setup Node Addon 149 | # -------------------------------------------------------------------------------------------------------- 150 | 151 | if (venmic_addon AND NOT CMAKE_JS_VERSION) 152 | message(FATAL_ERROR "[venmic] Please build the addon using CMake.js") 153 | endif() 154 | 155 | if (venmic_addon) 156 | add_subdirectory(addon) 157 | endif() 158 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | 7 | venmic - screenshare support for pipewire 8 | 9 |
10 | 11 | > [!WARNING] 12 | > This project is not intended for standalone usage. You need a modified discord client that makes use of this. 13 | 14 | ## 📖 Usage 15 | 16 | _venmic_ can be used as node-module or as a local rest-server. 17 | 18 | The node-module is mainly intended for internal usage by [Vesktop](https://github.com/Vencord/Vesktop). 19 | For a usage example, see the following Vesktop source files: 20 | - [src/main/venmic.ts](https://github.com/Vencord/Vesktop/blob/main/src/main/venmic.ts) 21 | - [src/renderer/patches/screenShareFixes.ts](https://github.com/Vencord/Vesktop/blob/main/src/renderer/patches/screenShareFixes.ts) 22 | - src/renderer/components/ScreenSharePicker.tsx: [1](https://github.com/Vencord/Vesktop/blob/4abae9c7082081dcae667916d9608e23adf688a9/src/renderer/components/ScreenSharePicker.tsx#L109-L115), [2](https://github.com/Vencord/Vesktop/blob/4abae9c7082081dcae667916d9608e23adf688a9/src/renderer/components/ScreenSharePicker.tsx#L253-L256), [3](https://github.com/Vencord/Vesktop/blob/4abae9c7082081dcae667916d9608e23adf688a9/src/renderer/components/ScreenSharePicker.tsx#L94) 23 | 24 | The Rest-Server exposes three simple endpoints 25 | * (POST) `/list` 26 | > List all available applications to share. 27 | > You can optionally define a JSON-Body containing all the props the listed nodes should have (i.e. `["node.name"]`). 28 | 29 | * (POST) `/link` 30 |
31 | Expects a JSON-Body in the following form: 32 |
 33 |   {
 34 |     "include": 
 35 |     [
 36 |       { "node.name": "Firefox" }
 37 |     ],
 38 |     "exclude":
 39 |     [
 40 |       { "node.name": "Chrome" }
 41 |     ]
 42 |     "ignore_devices": true,
 43 |     "workaround": [{ "node.name": "Chrome" }]
 44 |   }
 45 |   
46 | 47 | Depending on wether or not `include` or `exclude` are defined the behavior will change: 48 | 49 | * only `include` 50 | * Links nodes that match given props 51 | * only `exclude` 52 | * Links nodes that do not match given props 53 | * both `include` and `exclude` 54 | * Links all applications that match props in `include` and not those given in `exclude` 55 | 56 | The setting `ignore_devices` is optional and will default to `true`. 57 | When enabled it will prevent hardware-devices like speakers and microphones from being linked to the virtual microphone. 58 | 59 | The setting `only_speakers` is optional and will default to `true`. 60 | When enabled it will prevent linking against nodes that don't play to a speaker. 61 | 62 | The setting `only_default_speakers` is optional and will default to `true`. 63 | When enabled it will prevent linking against nodes that don't play to the default speaker. 64 | 65 | The setting `workaround` is also optional and will default to an empty array. 66 | When set, venmic will redirect the first node that matches all of the specified properties to itself. 67 |
68 | 69 | * (GET) `/unlink` 70 | > Unlinks the currently linked application 71 | 72 | ## 🏗️ Compiling 73 | 74 | * Rest-Server 75 | ```bash 76 | git clone https://github.com/Vencord/linux-virtmic && cd linux-virtmic 77 | cmake -B build && cmake --build build 78 | ``` 79 | 80 | * Node-Addon 81 | ```bash 82 | git clone https://github.com/Vencord/linux-virtmic && cd linux-virtmic 83 | pnpm install 84 | ``` 85 | 86 | ## 🐛 Debugging 87 | 88 | When reporting an issue please make sure to set the environment variable `VENMIC_ENABLE_LOG`. 89 | 90 | If said variable is set venmic will output a lot of useful information to stdout and a log-file which can be found in `~/.local/state/venmic/venmic.log`. 91 | 92 | It is highly recommended to include this log file in your issue report otherwise we may not be able to help you! 93 | 94 | ## 🤝 Acknowledgements 95 | 96 | * [Curve/rohrkabel](https://github.com/Curve/rohrkabel/) 97 | * [cmake-js](https://github.com/cmake-js/cmake-js) 98 | * [@wwmm](https://github.com/wwmm) for improving compatibility with [EasyEffects](https://github.com/wwmm/easyeffects) 99 | 100 | Kudos to all the developers involved, keep up the great work! 101 | -------------------------------------------------------------------------------- /addon/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(venmic-addon LANGUAGES CXX VERSION 2.0) 3 | 4 | # -------------------------------------------------------------------------------------------------------- 5 | # Create library 6 | # -------------------------------------------------------------------------------------------------------- 7 | 8 | add_library(${PROJECT_NAME} SHARED) 9 | add_library(vencord::venmic-addon ALIAS ${PROJECT_NAME}) 10 | 11 | target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) 12 | set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") 13 | set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20 CXX_EXTENSIONS OFF CXX_STANDARD_REQUIRED ON) 14 | 15 | if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 16 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror -pedantic -pedantic-errors -Wfatal-errors) 17 | endif() 18 | 19 | target_compile_options(${PROJECT_NAME} PRIVATE -Wno-missing-field-initializers -Wno-cast-function-type) 20 | 21 | # -------------------------------------------------------------------------------------------------------- 22 | # Add source files 23 | # -------------------------------------------------------------------------------------------------------- 24 | 25 | file(GLOB src "*.cpp") 26 | target_sources(${PROJECT_NAME} PRIVATE ${src}) 27 | 28 | # -------------------------------------------------------------------------------------------------------- 29 | # Setup Dependencies 30 | # -------------------------------------------------------------------------------------------------------- 31 | 32 | include("../cmake/cpm.cmake") 33 | 34 | CPMFindPackage( 35 | NAME range-v3 36 | GIT_TAG 0.12.0 37 | GIT_REPOSITORY "https://github.com/ericniebler/range-v3" 38 | ) 39 | 40 | target_link_libraries(${PROJECT_NAME} PUBLIC vencord::venmic range-v3::meta) 41 | 42 | # -------------------------------------------------------------------------------------------------------- 43 | # CMake.js related 44 | # -------------------------------------------------------------------------------------------------------- 45 | 46 | add_definitions(-DNAPI_VERSION=7) 47 | 48 | target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_JS_LIB}) 49 | target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_JS_INC}) 50 | 51 | if(MSVC AND CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET) 52 | execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}) 53 | endif() 54 | -------------------------------------------------------------------------------- /addon/addon.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | template 11 | std::optional convert(Napi::Value) = delete; 12 | 13 | template <> 14 | std::optional convert(Napi::Value value) 15 | { 16 | if (!value.IsString()) 17 | { 18 | return std::nullopt; 19 | } 20 | 21 | return value.ToString(); 22 | } 23 | 24 | template <> 25 | std::optional convert(Napi::Value value) 26 | { 27 | if (!value.IsBoolean()) 28 | { 29 | return std::nullopt; 30 | } 31 | 32 | return value.ToBoolean(); 33 | } 34 | 35 | template <> 36 | std::optional convert(Napi::Value value) 37 | { 38 | if (!value.IsObject()) 39 | { 40 | return std::nullopt; 41 | } 42 | 43 | auto object = value.As(); 44 | auto rtn = vencord::node{}; 45 | 46 | for (const auto &[obj_key, obj_value] : object) 47 | { 48 | auto key = convert(obj_key); 49 | auto value = convert(obj_value); 50 | 51 | if (!key || !value) 52 | { 53 | return std::nullopt; 54 | } 55 | 56 | rtn.emplace(key.value(), value.value()); 57 | } 58 | 59 | return rtn; 60 | } 61 | 62 | template 63 | std::optional> to_array(Napi::Value value) 64 | { 65 | if (!value.IsArray()) 66 | { 67 | return std::nullopt; 68 | } 69 | 70 | auto array = value.As(); 71 | 72 | std::vector rtn; 73 | rtn.reserve(array.Length()); 74 | 75 | for (auto i = 0u; array.Length() > i; i++) 76 | { 77 | auto converted = convert(array.Get(i)); 78 | 79 | if (!converted) 80 | { 81 | return std::nullopt; 82 | } 83 | 84 | rtn.emplace_back(converted.value()); 85 | } 86 | 87 | return rtn; 88 | } 89 | 90 | struct patchbay : public Napi::ObjectWrap 91 | { 92 | patchbay(const Napi::CallbackInfo &info) : Napi::ObjectWrap::ObjectWrap(info) 93 | { 94 | try 95 | { 96 | static_cast(vencord::patchbay::get()); 97 | } 98 | catch (std::exception &e) 99 | { 100 | Napi::Error::New(info.Env(), e.what()).ThrowAsJavaScriptException(); 101 | } 102 | } 103 | 104 | public: 105 | Napi::Value list(const Napi::CallbackInfo &info) // NOLINT(*-static) 106 | { 107 | auto env = info.Env(); 108 | 109 | std::vector props{}; 110 | 111 | if (info.Length() == 1 && !info[0].IsUndefined()) 112 | { 113 | auto array = to_array(info[0]); 114 | 115 | if (!array) 116 | { 117 | Napi::Error::New(env, "[venmic] expected list of strings").ThrowAsJavaScriptException(); 118 | return {}; 119 | } 120 | 121 | props = std::move(array.value()); 122 | } 123 | 124 | auto list = vencord::patchbay::get().list(props); 125 | auto rtn = Napi::Array::New(env, list.size()); 126 | 127 | auto convert = [&](const auto &item) 128 | { 129 | auto rtn = Napi::Object::New(env); 130 | 131 | for (const auto &[key, value] : item) 132 | { 133 | rtn.Set(key, Napi::String::New(env, value)); 134 | } 135 | 136 | return rtn; 137 | }; 138 | auto add = [&](const auto &item) 139 | { 140 | rtn.Set(item.first, item.second); 141 | }; 142 | 143 | ranges::for_each(list // 144 | | ranges::views::transform(convert) // 145 | | ranges::views::enumerate, 146 | add); 147 | 148 | return rtn; 149 | } 150 | 151 | Napi::Value link(const Napi::CallbackInfo &info) // NOLINT(*-static) 152 | { 153 | auto env = info.Env(); 154 | 155 | if (info.Length() != 1 || !info[0].IsObject()) 156 | { 157 | Napi::Error::New(env, "[venmic] expected link object").ThrowAsJavaScriptException(); 158 | return Napi::Boolean::New(env, false); 159 | } 160 | 161 | auto data = info[0].ToObject(); 162 | 163 | if (!data.Has("include") && !data.Has("exclude")) 164 | { 165 | Napi::Error::New(env, "[venmic] expected at least one of keys 'include' or 'exclude'") 166 | .ThrowAsJavaScriptException(); 167 | 168 | return Napi::Boolean::New(env, false); 169 | } 170 | 171 | auto include = to_array(data.Get("include")); 172 | auto exclude = to_array(data.Get("exclude")); 173 | auto ignore_devices = convert(data.Get("ignore_devices")); 174 | auto only_speakers = convert(data.Get("only_speakers")); 175 | auto only_default_speakers = convert(data.Get("only_default_speakers")); 176 | auto workaround = to_array(data.Get("workaround")); 177 | 178 | if (!include && !exclude) 179 | { 180 | Napi::Error::New(env, "[venmic] expected either 'include' or 'exclude' or both to be present and to be " 181 | "arrays of key-value pairs") 182 | .ThrowAsJavaScriptException(); 183 | 184 | return Napi::Boolean::New(env, false); 185 | } 186 | 187 | vencord::patchbay::get().link({ 188 | .include = include.value_or(std::vector{}), 189 | .exclude = exclude.value_or(std::vector{}), 190 | .ignore_devices = ignore_devices.value_or(true), 191 | .only_speakers = only_speakers.value_or(true), 192 | .only_default_speakers = only_default_speakers.value_or(true), 193 | .workaround = workaround.value_or(std::vector{}), 194 | }); 195 | 196 | return Napi::Boolean::New(env, true); 197 | } 198 | 199 | Napi::Value unlink([[maybe_unused]] const Napi::CallbackInfo &) // NOLINT(*-static) 200 | { 201 | vencord::patchbay::get().unlink(); 202 | return {}; 203 | } 204 | 205 | static Napi::Value has_pipewire(const Napi::CallbackInfo &info) 206 | { 207 | return Napi::Boolean::New(info.Env(), vencord::patchbay::has_pipewire()); 208 | } 209 | 210 | public: 211 | static Napi::Object Init(Napi::Env env, Napi::Object exports) 212 | { 213 | static constexpr auto attributes = static_cast(napi_writable | napi_configurable); 214 | 215 | auto func = DefineClass(env, "PatchBay", 216 | { 217 | InstanceMethod<&patchbay::link>("link", attributes), 218 | InstanceMethod<&patchbay::list>("list", attributes), 219 | InstanceMethod<&patchbay::unlink>("unlink", attributes), 220 | StaticMethod<&patchbay::has_pipewire>("hasPipeWire", attributes), 221 | }); 222 | 223 | auto *constructor = new Napi::FunctionReference{Napi::Persistent(func)}; 224 | 225 | exports.Set("PatchBay", func); 226 | 227 | env.SetInstanceData(constructor); 228 | 229 | return exports; 230 | } 231 | 232 | static Napi::Object CreateNewItem(const Napi::CallbackInfo &info) 233 | { 234 | auto env = info.Env(); 235 | auto *constructor = env.GetInstanceData(); 236 | 237 | return constructor->New({}); 238 | } 239 | }; 240 | 241 | Napi::Object init(Napi::Env env, Napi::Object exports) 242 | { 243 | patchbay::Init(env, exports); 244 | return exports; 245 | } 246 | 247 | // NOLINTNEXTLINE 248 | NODE_API_MODULE(venmic, init); 249 | -------------------------------------------------------------------------------- /cmake/FindPulseAudio.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2008 Matthias Kretz 2 | # SPDX-FileCopyrightText: 2009 Marcus Hufgard 3 | # 4 | # SPDX-License-Identifier: BSD-3-Clause 5 | 6 | #[=======================================================================[.rst: 7 | FindPulseAudio 8 | -------------- 9 | 10 | Try to locate the PulseAudio library. 11 | If found, this will define the following variables: 12 | 13 | ``PulseAudio_FOUND`` 14 | True if the system has the PulseAudio library of at least 15 | the minimum version specified by either the version parameter 16 | to find_package() or the variable PulseAudio_MINIMUM_VERSION 17 | ``PulseAudio_INCLUDE_DIRS`` 18 | The PulseAudio include directory 19 | ``PulseAudio_LIBRARIES`` 20 | The PulseAudio libraries for linking 21 | ``PulseAudio_MAINLOOP_LIBRARY`` 22 | The libraries needed to use PulseAudio Mainloop 23 | ``PulseAudio_VERSION`` 24 | The version of PulseAudio that was found 25 | ``PulseAudio_INCLUDE_DIR`` 26 | Deprecated, use ``PulseAudio_INCLUDE_DIRS`` 27 | ``PulseAudio_LIBRARY`` 28 | Deprecated, use ``PulseAudio_LIBRARIES`` 29 | 30 | If ``PulseAudio_FOUND`` is TRUE, it will also define the following 31 | imported target: 32 | 33 | ``PulseAudio::PulseAudio`` 34 | The PulseAudio library 35 | 36 | Since 5.41.0. 37 | #]=======================================================================] 38 | 39 | # Support PulseAudio_MINIMUM_VERSION for compatibility: 40 | if(NOT PulseAudio_FIND_VERSION) 41 | set(PulseAudio_FIND_VERSION "${PulseAudio_MINIMUM_VERSION}") 42 | endif() 43 | 44 | # the minimum version of PulseAudio we require 45 | if(NOT PulseAudio_FIND_VERSION) 46 | set(PulseAudio_FIND_VERSION "0.9.9") 47 | endif() 48 | 49 | find_package(PkgConfig QUIET) 50 | pkg_check_modules(PC_PulseAudio QUIET libpulse>=${PulseAudio_FIND_VERSION}) 51 | pkg_check_modules(PC_PulseAudio_MAINLOOP QUIET libpulse-mainloop-glib) 52 | 53 | find_path(PulseAudio_INCLUDE_DIRS pulse/pulseaudio.h 54 | HINTS 55 | ${PC_PulseAudio_INCLUDEDIR} 56 | ${PC_PulseAudio_INCLUDE_DIRS} 57 | ) 58 | 59 | find_library(PulseAudio_LIBRARIES NAMES pulse libpulse 60 | HINTS 61 | ${PC_PulseAudio_LIBDIR} 62 | ${PC_PulseAudio_LIBRARY_DIRS} 63 | ) 64 | 65 | find_library(PulseAudio_MAINLOOP_LIBRARY NAMES pulse-mainloop pulse-mainloop-glib libpulse-mainloop-glib 66 | HINTS 67 | ${PC_PulseAudio_LIBDIR} 68 | ${PC_PulseAudio_LIBRARY_DIRS} 69 | ) 70 | 71 | # Store the version number in the cache, so we don't have to search every time again: 72 | if (PulseAudio_INCLUDE_DIRS AND NOT PulseAudio_VERSION) 73 | 74 | # get PulseAudio's version from its version.h 75 | file(STRINGS "${PulseAudio_INCLUDE_DIRS}/pulse/version.h" pulse_version_h 76 | REGEX ".*pa_get_headers_version\\(\\).*") 77 | string(REGEX REPLACE ".*pa_get_headers_version\\(\\)\ \\(\"([0-9]+\\.[0-9]+\\.[0-9]+)[^\"]*\"\\).*" "\\1" 78 | _PulseAudio_VERSION "${pulse_version_h}") 79 | 80 | set(PulseAudio_VERSION "${_PulseAudio_VERSION}" CACHE STRING "Version number of PulseAudio" FORCE) 81 | endif() 82 | 83 | # Use the new extended syntax of find_package_handle_standard_args(), which also handles version checking: 84 | include(FindPackageHandleStandardArgs) 85 | find_package_handle_standard_args(PulseAudio REQUIRED_VARS PulseAudio_LIBRARIES PulseAudio_INCLUDE_DIRS 86 | VERSION_VAR PulseAudio_VERSION) 87 | 88 | # Deprecated synonyms 89 | set(PULSEAUDIO_INCLUDE_DIR "${PulseAudio_INCLUDE_DIRS}") 90 | set(PULSEAUDIO_LIBRARY "${PulseAudio_LIBRARIES}") 91 | set(PULSEAUDIO_MAINLOOP_LIBRARY "${PulseAudio_MAINLOOP_LIBRARY}") 92 | set(PULSEAUDIO_FOUND "${PulseAudio_FOUND}") 93 | 94 | if(PulseAudio_FOUND AND NOT TARGET PulseAudio::PulseAudio) 95 | add_library(PulseAudio::PulseAudio UNKNOWN IMPORTED) 96 | set_target_properties(PulseAudio::PulseAudio PROPERTIES 97 | IMPORTED_LOCATION "${PulseAudio_LIBRARIES}" 98 | INTERFACE_INCLUDE_DIRECTORIES "${PulseAudio_INCLUDE_DIRS}") 99 | endif() 100 | 101 | mark_as_advanced(PulseAudio_INCLUDE_DIRS PULSEAUDIO_INCLUDE_DIR 102 | PulseAudio_LIBRARIES PULSEAUDIO_LIBRARY 103 | PulseAudio_MAINLOOP_LIBRARY PULSEAUDIO_MAINLOOP_LIBRARY) 104 | 105 | include(FeatureSummary) 106 | set_package_properties(PulseAudio PROPERTIES 107 | URL "https://www.freedesktop.org/wiki/Software/PulseAudio" 108 | DESCRIPTION "Sound server, for sound stream routing and mixing") 109 | -------------------------------------------------------------------------------- /cmake/cpm.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors 4 | 5 | set(CPM_DOWNLOAD_VERSION 0.38.7) 6 | set(CPM_HASH_SUM "83e5eb71b2bbb8b1f2ad38f1950287a057624e385c238f6087f94cdfc44af9c5") 7 | 8 | if(CPM_SOURCE_CACHE) 9 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 10 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 11 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 12 | else() 13 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 14 | endif() 15 | 16 | # Expand relative path. This is important if the provided path contains a tilde (~) 17 | get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) 18 | 19 | file(DOWNLOAD 20 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 21 | ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} 22 | ) 23 | 24 | include(${CPM_DOWNLOAD_LOCATION}) 25 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # This Dockerfile describes the base image used for compiling the node addon on different CPU-Architectures 2 | 3 | FROM fedora:38 4 | 5 | # Build dependencies 6 | 7 | RUN dnf install -y make automake gcc gcc-c++ kernel-devel cmake git nodejs libstdc++-static ninja-build 8 | RUN dnf install -y pipewire-devel pipewire-libs pulseaudio-libs-devel pipewire-pulseaudio 9 | 10 | # PNPM 11 | 12 | RUN curl -fsSL https://get.pnpm.io/install.sh | sh - 13 | 14 | # Add build script 15 | 16 | ADD build.sh . 17 | -------------------------------------------------------------------------------- /docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Ensure pnpm is loaded 4 | source /root/.bashrc 5 | 6 | # Let's get to work! 7 | cd /work 8 | 9 | # Export Threads for Make and Ninja 10 | export MAKEFLAGS=-j$(nproc) 11 | export PARALLEL_LEVEL=$(nproc) 12 | 13 | pnpm install --ignore-scripts && pnpm run install 14 | -------------------------------------------------------------------------------- /include/vencord/logger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace vencord 6 | { 7 | class logger 8 | { 9 | struct impl; 10 | 11 | private: 12 | std::unique_ptr m_impl; 13 | 14 | private: 15 | logger(); 16 | 17 | public: 18 | spdlog::logger *operator->() const; 19 | 20 | public: 21 | [[nodiscard]] static logger &get(); 22 | }; 23 | } // namespace vencord 24 | -------------------------------------------------------------------------------- /include/vencord/patchbay.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace vencord 9 | { 10 | using node = std::map; 11 | 12 | struct link_options 13 | { 14 | std::vector include; 15 | std::vector exclude; 16 | 17 | public: 18 | bool ignore_devices{true}; // Only link against non-device nodes 19 | public: 20 | bool only_speakers{true}; // Ignore nodes that don't play to speakers 21 | bool only_default_speakers{true}; // Ignore nodes that don't play to the default speaker 22 | 23 | public: 24 | std::vector workaround; 25 | }; 26 | 27 | class patchbay 28 | { 29 | class impl; 30 | 31 | private: 32 | std::unique_ptr m_impl; 33 | 34 | public: 35 | ~patchbay(); 36 | 37 | private: 38 | patchbay(); 39 | 40 | public: 41 | void link(link_options options); 42 | 43 | public: 44 | void unlink(); 45 | 46 | public: 47 | [[nodiscard]] std::vector list(std::vector props); 48 | 49 | public: 50 | [[nodiscard]] static patchbay &get(); 51 | [[nodiscard]] static bool has_pipewire(); 52 | }; 53 | } // namespace vencord 54 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const binding = require("pkg-prebuilds")( 2 | require("path").resolve(__dirname, ".."), 3 | require("./options") 4 | ); 5 | 6 | module.exports = binding; 7 | -------------------------------------------------------------------------------- /lib/module.d.ts: -------------------------------------------------------------------------------- 1 | type DefaultProps = 'node.name' | 'application.name'; 2 | 3 | type LiteralUnion< 4 | LiteralType, 5 | BaseType extends string, 6 | > = LiteralType | (BaseType & Record); 7 | 8 | type Optional< 9 | Type, 10 | Key extends keyof Type 11 | > = Partial> & Omit; 12 | 13 | export type Node = Record, string>; 14 | 15 | export interface LinkData 16 | { 17 | include: Node[]; 18 | exclude: Node[]; 19 | 20 | ignore_devices?: boolean; 21 | 22 | only_speakers?: boolean; 23 | only_default_speakers?: boolean; 24 | 25 | workaround?: Node[]; 26 | } 27 | 28 | export class PatchBay 29 | { 30 | unlink(): void; 31 | 32 | list(props?: T[]): Node[]; 33 | link(data: Optional | Optional): boolean; 34 | 35 | static hasPipeWire(): boolean; 36 | } 37 | -------------------------------------------------------------------------------- /lib/options.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | { 3 | name : "venmic-addon", 4 | napi_versions: [7] 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vencord/venmic", 3 | "description": "linux audio screenshare for discord (or any electron app) via pipewire", 4 | "keywords": [ 5 | "pipewire", 6 | "electron", 7 | "screenshare", 8 | "capturer", 9 | "audio" 10 | ], 11 | "private": false, 12 | "license": "MPL-2.0", 13 | "author": "Curve (https://github.com/Curve)", 14 | "version": "6.1.0", 15 | "main": "./lib/index.js", 16 | "types": "./lib/module.d.ts", 17 | "scripts": { 18 | "clean": "cmake-js clean", 19 | "test": "node tests/node/*.js", 20 | "cpcmds": "cmake-js configure --CDvenmic_addon=ON --CDCMAKE_EXPORT_COMPILE_COMMANDS=ON", 21 | "install": "pkg-prebuilds-verify ./lib/options.js || cmake-js compile --CDvenmic_addon=ON" 22 | }, 23 | "os": [ 24 | "linux" 25 | ], 26 | "binary": { 27 | "napi_versions": [ 28 | 7 29 | ] 30 | }, 31 | "engines": { 32 | "node": ">=14.15" 33 | }, 34 | "dependencies": { 35 | "cmake-js": "^7.3.0", 36 | "node-addon-api": "^8.0.0", 37 | "pkg-prebuilds": "^0.2.1" 38 | }, 39 | "devDependencies": { 40 | "@typescript-eslint/eslint-plugin": "^7.4.0", 41 | "@typescript-eslint/parser": "^7.4.0", 42 | "eslint": "^8.57.0" 43 | }, 44 | "publishConfig": { 45 | "access": "public", 46 | "registry": "https://registry.npmjs.org/" 47 | }, 48 | "bugs": { 49 | "url": "https://github.com/Vencord/venmic/issues" 50 | }, 51 | "homepage": "https://github.com/Vencord/venmic", 52 | "repository": { 53 | "type": "git", 54 | "url": "https://github.com/Vencord/venmic.git" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | cmake-js: 9 | specifier: ^7.3.0 10 | version: 7.3.0 11 | node-addon-api: 12 | specifier: ^8.0.0 13 | version: 8.0.0 14 | pkg-prebuilds: 15 | specifier: ^0.2.1 16 | version: 0.2.1 17 | 18 | devDependencies: 19 | '@typescript-eslint/eslint-plugin': 20 | specifier: ^7.4.0 21 | version: 7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.2.2) 22 | '@typescript-eslint/parser': 23 | specifier: ^7.4.0 24 | version: 7.4.0(eslint@8.57.0)(typescript@5.2.2) 25 | eslint: 26 | specifier: ^8.57.0 27 | version: 8.57.0 28 | 29 | packages: 30 | 31 | /@aashutoshrathi/word-wrap@1.2.6: 32 | resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} 33 | engines: {node: '>=0.10.0'} 34 | dev: true 35 | 36 | /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): 37 | resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} 38 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 39 | peerDependencies: 40 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 41 | dependencies: 42 | eslint: 8.57.0 43 | eslint-visitor-keys: 3.4.3 44 | dev: true 45 | 46 | /@eslint-community/regexpp@4.9.1: 47 | resolution: {integrity: sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==} 48 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 49 | dev: true 50 | 51 | /@eslint/eslintrc@2.1.4: 52 | resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} 53 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 54 | dependencies: 55 | ajv: 6.12.6 56 | debug: 4.3.4 57 | espree: 9.6.1 58 | globals: 13.23.0 59 | ignore: 5.2.4 60 | import-fresh: 3.3.0 61 | js-yaml: 4.1.0 62 | minimatch: 3.1.2 63 | strip-json-comments: 3.1.1 64 | transitivePeerDependencies: 65 | - supports-color 66 | dev: true 67 | 68 | /@eslint/js@8.57.0: 69 | resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} 70 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 71 | dev: true 72 | 73 | /@humanwhocodes/config-array@0.11.14: 74 | resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} 75 | engines: {node: '>=10.10.0'} 76 | dependencies: 77 | '@humanwhocodes/object-schema': 2.0.2 78 | debug: 4.3.4 79 | minimatch: 3.1.2 80 | transitivePeerDependencies: 81 | - supports-color 82 | dev: true 83 | 84 | /@humanwhocodes/module-importer@1.0.1: 85 | resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 86 | engines: {node: '>=12.22'} 87 | dev: true 88 | 89 | /@humanwhocodes/object-schema@2.0.2: 90 | resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} 91 | dev: true 92 | 93 | /@nodelib/fs.scandir@2.1.5: 94 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 95 | engines: {node: '>= 8'} 96 | dependencies: 97 | '@nodelib/fs.stat': 2.0.5 98 | run-parallel: 1.2.0 99 | dev: true 100 | 101 | /@nodelib/fs.stat@2.0.5: 102 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 103 | engines: {node: '>= 8'} 104 | dev: true 105 | 106 | /@nodelib/fs.walk@1.2.8: 107 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 108 | engines: {node: '>= 8'} 109 | dependencies: 110 | '@nodelib/fs.scandir': 2.1.5 111 | fastq: 1.15.0 112 | dev: true 113 | 114 | /@types/json-schema@7.0.13: 115 | resolution: {integrity: sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==} 116 | dev: true 117 | 118 | /@types/semver@7.5.3: 119 | resolution: {integrity: sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==} 120 | dev: true 121 | 122 | /@typescript-eslint/eslint-plugin@7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.2.2): 123 | resolution: {integrity: sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==} 124 | engines: {node: ^18.18.0 || >=20.0.0} 125 | peerDependencies: 126 | '@typescript-eslint/parser': ^7.0.0 127 | eslint: ^8.56.0 128 | typescript: '*' 129 | peerDependenciesMeta: 130 | typescript: 131 | optional: true 132 | dependencies: 133 | '@eslint-community/regexpp': 4.9.1 134 | '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.2.2) 135 | '@typescript-eslint/scope-manager': 7.4.0 136 | '@typescript-eslint/type-utils': 7.4.0(eslint@8.57.0)(typescript@5.2.2) 137 | '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.2.2) 138 | '@typescript-eslint/visitor-keys': 7.4.0 139 | debug: 4.3.4 140 | eslint: 8.57.0 141 | graphemer: 1.4.0 142 | ignore: 5.2.4 143 | natural-compare: 1.4.0 144 | semver: 7.5.4 145 | ts-api-utils: 1.0.3(typescript@5.2.2) 146 | typescript: 5.2.2 147 | transitivePeerDependencies: 148 | - supports-color 149 | dev: true 150 | 151 | /@typescript-eslint/parser@7.4.0(eslint@8.57.0)(typescript@5.2.2): 152 | resolution: {integrity: sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==} 153 | engines: {node: ^18.18.0 || >=20.0.0} 154 | peerDependencies: 155 | eslint: ^8.56.0 156 | typescript: '*' 157 | peerDependenciesMeta: 158 | typescript: 159 | optional: true 160 | dependencies: 161 | '@typescript-eslint/scope-manager': 7.4.0 162 | '@typescript-eslint/types': 7.4.0 163 | '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.2.2) 164 | '@typescript-eslint/visitor-keys': 7.4.0 165 | debug: 4.3.4 166 | eslint: 8.57.0 167 | typescript: 5.2.2 168 | transitivePeerDependencies: 169 | - supports-color 170 | dev: true 171 | 172 | /@typescript-eslint/scope-manager@7.4.0: 173 | resolution: {integrity: sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==} 174 | engines: {node: ^18.18.0 || >=20.0.0} 175 | dependencies: 176 | '@typescript-eslint/types': 7.4.0 177 | '@typescript-eslint/visitor-keys': 7.4.0 178 | dev: true 179 | 180 | /@typescript-eslint/type-utils@7.4.0(eslint@8.57.0)(typescript@5.2.2): 181 | resolution: {integrity: sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==} 182 | engines: {node: ^18.18.0 || >=20.0.0} 183 | peerDependencies: 184 | eslint: ^8.56.0 185 | typescript: '*' 186 | peerDependenciesMeta: 187 | typescript: 188 | optional: true 189 | dependencies: 190 | '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.2.2) 191 | '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.2.2) 192 | debug: 4.3.4 193 | eslint: 8.57.0 194 | ts-api-utils: 1.0.3(typescript@5.2.2) 195 | typescript: 5.2.2 196 | transitivePeerDependencies: 197 | - supports-color 198 | dev: true 199 | 200 | /@typescript-eslint/types@7.4.0: 201 | resolution: {integrity: sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==} 202 | engines: {node: ^18.18.0 || >=20.0.0} 203 | dev: true 204 | 205 | /@typescript-eslint/typescript-estree@7.4.0(typescript@5.2.2): 206 | resolution: {integrity: sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==} 207 | engines: {node: ^18.18.0 || >=20.0.0} 208 | peerDependencies: 209 | typescript: '*' 210 | peerDependenciesMeta: 211 | typescript: 212 | optional: true 213 | dependencies: 214 | '@typescript-eslint/types': 7.4.0 215 | '@typescript-eslint/visitor-keys': 7.4.0 216 | debug: 4.3.4 217 | globby: 11.1.0 218 | is-glob: 4.0.3 219 | minimatch: 9.0.3 220 | semver: 7.5.4 221 | ts-api-utils: 1.0.3(typescript@5.2.2) 222 | typescript: 5.2.2 223 | transitivePeerDependencies: 224 | - supports-color 225 | dev: true 226 | 227 | /@typescript-eslint/utils@7.4.0(eslint@8.57.0)(typescript@5.2.2): 228 | resolution: {integrity: sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==} 229 | engines: {node: ^18.18.0 || >=20.0.0} 230 | peerDependencies: 231 | eslint: ^8.56.0 232 | dependencies: 233 | '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) 234 | '@types/json-schema': 7.0.13 235 | '@types/semver': 7.5.3 236 | '@typescript-eslint/scope-manager': 7.4.0 237 | '@typescript-eslint/types': 7.4.0 238 | '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.2.2) 239 | eslint: 8.57.0 240 | semver: 7.5.4 241 | transitivePeerDependencies: 242 | - supports-color 243 | - typescript 244 | dev: true 245 | 246 | /@typescript-eslint/visitor-keys@7.4.0: 247 | resolution: {integrity: sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==} 248 | engines: {node: ^18.18.0 || >=20.0.0} 249 | dependencies: 250 | '@typescript-eslint/types': 7.4.0 251 | eslint-visitor-keys: 3.4.3 252 | dev: true 253 | 254 | /@ungap/structured-clone@1.2.0: 255 | resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} 256 | dev: true 257 | 258 | /acorn-jsx@5.3.2(acorn@8.10.0): 259 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 260 | peerDependencies: 261 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 262 | dependencies: 263 | acorn: 8.10.0 264 | dev: true 265 | 266 | /acorn@8.10.0: 267 | resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} 268 | engines: {node: '>=0.4.0'} 269 | hasBin: true 270 | dev: true 271 | 272 | /ajv@6.12.6: 273 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 274 | dependencies: 275 | fast-deep-equal: 3.1.3 276 | fast-json-stable-stringify: 2.1.0 277 | json-schema-traverse: 0.4.1 278 | uri-js: 4.4.1 279 | dev: true 280 | 281 | /ansi-regex@5.0.1: 282 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 283 | engines: {node: '>=8'} 284 | 285 | /ansi-styles@4.3.0: 286 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 287 | engines: {node: '>=8'} 288 | dependencies: 289 | color-convert: 2.0.1 290 | 291 | /aproba@2.0.0: 292 | resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} 293 | dev: false 294 | 295 | /are-we-there-yet@3.0.1: 296 | resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} 297 | engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} 298 | dependencies: 299 | delegates: 1.0.0 300 | readable-stream: 3.6.2 301 | dev: false 302 | 303 | /argparse@2.0.1: 304 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 305 | dev: true 306 | 307 | /array-union@2.1.0: 308 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 309 | engines: {node: '>=8'} 310 | dev: true 311 | 312 | /asynckit@0.4.0: 313 | resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} 314 | dev: false 315 | 316 | /axios@1.6.8(debug@4.3.4): 317 | resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==} 318 | dependencies: 319 | follow-redirects: 1.15.6(debug@4.3.4) 320 | form-data: 4.0.0 321 | proxy-from-env: 1.1.0 322 | transitivePeerDependencies: 323 | - debug 324 | dev: false 325 | 326 | /balanced-match@1.0.2: 327 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 328 | dev: true 329 | 330 | /brace-expansion@1.1.11: 331 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 332 | dependencies: 333 | balanced-match: 1.0.2 334 | concat-map: 0.0.1 335 | dev: true 336 | 337 | /brace-expansion@2.0.1: 338 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 339 | dependencies: 340 | balanced-match: 1.0.2 341 | dev: true 342 | 343 | /braces@3.0.2: 344 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 345 | engines: {node: '>=8'} 346 | dependencies: 347 | fill-range: 7.0.1 348 | dev: true 349 | 350 | /callsites@3.1.0: 351 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 352 | engines: {node: '>=6'} 353 | dev: true 354 | 355 | /chalk@4.1.2: 356 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 357 | engines: {node: '>=10'} 358 | dependencies: 359 | ansi-styles: 4.3.0 360 | supports-color: 7.2.0 361 | dev: true 362 | 363 | /chownr@2.0.0: 364 | resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} 365 | engines: {node: '>=10'} 366 | dev: false 367 | 368 | /cliui@8.0.1: 369 | resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} 370 | engines: {node: '>=12'} 371 | dependencies: 372 | string-width: 4.2.3 373 | strip-ansi: 6.0.1 374 | wrap-ansi: 7.0.0 375 | dev: false 376 | 377 | /cmake-js@7.3.0: 378 | resolution: {integrity: sha512-dXs2zq9WxrV87bpJ+WbnGKv8WUBXDw8blNiwNHoRe/it+ptscxhQHKB1SJXa1w+kocLMeP28Tk4/eTCezg4o+w==} 379 | engines: {node: '>= 14.15.0'} 380 | hasBin: true 381 | dependencies: 382 | axios: 1.6.8(debug@4.3.4) 383 | debug: 4.3.4 384 | fs-extra: 11.2.0 385 | lodash.isplainobject: 4.0.6 386 | memory-stream: 1.0.0 387 | node-api-headers: 1.1.0 388 | npmlog: 6.0.2 389 | rc: 1.2.8 390 | semver: 7.5.4 391 | tar: 6.2.0 392 | url-join: 4.0.1 393 | which: 2.0.2 394 | yargs: 17.7.2 395 | transitivePeerDependencies: 396 | - supports-color 397 | dev: false 398 | 399 | /color-convert@2.0.1: 400 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 401 | engines: {node: '>=7.0.0'} 402 | dependencies: 403 | color-name: 1.1.4 404 | 405 | /color-name@1.1.4: 406 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 407 | 408 | /color-support@1.1.3: 409 | resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} 410 | hasBin: true 411 | dev: false 412 | 413 | /combined-stream@1.0.8: 414 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} 415 | engines: {node: '>= 0.8'} 416 | dependencies: 417 | delayed-stream: 1.0.0 418 | dev: false 419 | 420 | /concat-map@0.0.1: 421 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 422 | dev: true 423 | 424 | /console-control-strings@1.1.0: 425 | resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} 426 | dev: false 427 | 428 | /cross-spawn@7.0.3: 429 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 430 | engines: {node: '>= 8'} 431 | dependencies: 432 | path-key: 3.1.1 433 | shebang-command: 2.0.0 434 | which: 2.0.2 435 | dev: true 436 | 437 | /debug@4.3.4: 438 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 439 | engines: {node: '>=6.0'} 440 | peerDependencies: 441 | supports-color: '*' 442 | peerDependenciesMeta: 443 | supports-color: 444 | optional: true 445 | dependencies: 446 | ms: 2.1.2 447 | 448 | /deep-extend@0.6.0: 449 | resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} 450 | engines: {node: '>=4.0.0'} 451 | dev: false 452 | 453 | /deep-is@0.1.4: 454 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 455 | dev: true 456 | 457 | /delayed-stream@1.0.0: 458 | resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} 459 | engines: {node: '>=0.4.0'} 460 | dev: false 461 | 462 | /delegates@1.0.0: 463 | resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} 464 | dev: false 465 | 466 | /dir-glob@3.0.1: 467 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 468 | engines: {node: '>=8'} 469 | dependencies: 470 | path-type: 4.0.0 471 | dev: true 472 | 473 | /doctrine@3.0.0: 474 | resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 475 | engines: {node: '>=6.0.0'} 476 | dependencies: 477 | esutils: 2.0.3 478 | dev: true 479 | 480 | /emoji-regex@8.0.0: 481 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 482 | dev: false 483 | 484 | /escalade@3.1.1: 485 | resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} 486 | engines: {node: '>=6'} 487 | dev: false 488 | 489 | /escape-string-regexp@4.0.0: 490 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 491 | engines: {node: '>=10'} 492 | dev: true 493 | 494 | /eslint-scope@7.2.2: 495 | resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} 496 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 497 | dependencies: 498 | esrecurse: 4.3.0 499 | estraverse: 5.3.0 500 | dev: true 501 | 502 | /eslint-visitor-keys@3.4.3: 503 | resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 504 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 505 | dev: true 506 | 507 | /eslint@8.57.0: 508 | resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} 509 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 510 | hasBin: true 511 | dependencies: 512 | '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) 513 | '@eslint-community/regexpp': 4.9.1 514 | '@eslint/eslintrc': 2.1.4 515 | '@eslint/js': 8.57.0 516 | '@humanwhocodes/config-array': 0.11.14 517 | '@humanwhocodes/module-importer': 1.0.1 518 | '@nodelib/fs.walk': 1.2.8 519 | '@ungap/structured-clone': 1.2.0 520 | ajv: 6.12.6 521 | chalk: 4.1.2 522 | cross-spawn: 7.0.3 523 | debug: 4.3.4 524 | doctrine: 3.0.0 525 | escape-string-regexp: 4.0.0 526 | eslint-scope: 7.2.2 527 | eslint-visitor-keys: 3.4.3 528 | espree: 9.6.1 529 | esquery: 1.5.0 530 | esutils: 2.0.3 531 | fast-deep-equal: 3.1.3 532 | file-entry-cache: 6.0.1 533 | find-up: 5.0.0 534 | glob-parent: 6.0.2 535 | globals: 13.23.0 536 | graphemer: 1.4.0 537 | ignore: 5.2.4 538 | imurmurhash: 0.1.4 539 | is-glob: 4.0.3 540 | is-path-inside: 3.0.3 541 | js-yaml: 4.1.0 542 | json-stable-stringify-without-jsonify: 1.0.1 543 | levn: 0.4.1 544 | lodash.merge: 4.6.2 545 | minimatch: 3.1.2 546 | natural-compare: 1.4.0 547 | optionator: 0.9.3 548 | strip-ansi: 6.0.1 549 | text-table: 0.2.0 550 | transitivePeerDependencies: 551 | - supports-color 552 | dev: true 553 | 554 | /espree@9.6.1: 555 | resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} 556 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 557 | dependencies: 558 | acorn: 8.10.0 559 | acorn-jsx: 5.3.2(acorn@8.10.0) 560 | eslint-visitor-keys: 3.4.3 561 | dev: true 562 | 563 | /esquery@1.5.0: 564 | resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} 565 | engines: {node: '>=0.10'} 566 | dependencies: 567 | estraverse: 5.3.0 568 | dev: true 569 | 570 | /esrecurse@4.3.0: 571 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 572 | engines: {node: '>=4.0'} 573 | dependencies: 574 | estraverse: 5.3.0 575 | dev: true 576 | 577 | /estraverse@5.3.0: 578 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 579 | engines: {node: '>=4.0'} 580 | dev: true 581 | 582 | /esutils@2.0.3: 583 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 584 | engines: {node: '>=0.10.0'} 585 | dev: true 586 | 587 | /fast-deep-equal@3.1.3: 588 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 589 | dev: true 590 | 591 | /fast-glob@3.3.1: 592 | resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} 593 | engines: {node: '>=8.6.0'} 594 | dependencies: 595 | '@nodelib/fs.stat': 2.0.5 596 | '@nodelib/fs.walk': 1.2.8 597 | glob-parent: 5.1.2 598 | merge2: 1.4.1 599 | micromatch: 4.0.5 600 | dev: true 601 | 602 | /fast-json-stable-stringify@2.1.0: 603 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 604 | dev: true 605 | 606 | /fast-levenshtein@2.0.6: 607 | resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 608 | dev: true 609 | 610 | /fastq@1.15.0: 611 | resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} 612 | dependencies: 613 | reusify: 1.0.4 614 | dev: true 615 | 616 | /file-entry-cache@6.0.1: 617 | resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 618 | engines: {node: ^10.12.0 || >=12.0.0} 619 | dependencies: 620 | flat-cache: 3.1.1 621 | dev: true 622 | 623 | /fill-range@7.0.1: 624 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 625 | engines: {node: '>=8'} 626 | dependencies: 627 | to-regex-range: 5.0.1 628 | dev: true 629 | 630 | /find-up@5.0.0: 631 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 632 | engines: {node: '>=10'} 633 | dependencies: 634 | locate-path: 6.0.0 635 | path-exists: 4.0.0 636 | dev: true 637 | 638 | /flat-cache@3.1.1: 639 | resolution: {integrity: sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==} 640 | engines: {node: '>=12.0.0'} 641 | dependencies: 642 | flatted: 3.2.9 643 | keyv: 4.5.4 644 | rimraf: 3.0.2 645 | dev: true 646 | 647 | /flatted@3.2.9: 648 | resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} 649 | dev: true 650 | 651 | /follow-redirects@1.15.6(debug@4.3.4): 652 | resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} 653 | engines: {node: '>=4.0'} 654 | peerDependencies: 655 | debug: '*' 656 | peerDependenciesMeta: 657 | debug: 658 | optional: true 659 | dependencies: 660 | debug: 4.3.4 661 | dev: false 662 | 663 | /form-data@4.0.0: 664 | resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} 665 | engines: {node: '>= 6'} 666 | dependencies: 667 | asynckit: 0.4.0 668 | combined-stream: 1.0.8 669 | mime-types: 2.1.35 670 | dev: false 671 | 672 | /fs-extra@11.2.0: 673 | resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} 674 | engines: {node: '>=14.14'} 675 | dependencies: 676 | graceful-fs: 4.2.11 677 | jsonfile: 6.1.0 678 | universalify: 2.0.0 679 | dev: false 680 | 681 | /fs-minipass@2.1.0: 682 | resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} 683 | engines: {node: '>= 8'} 684 | dependencies: 685 | minipass: 3.3.6 686 | dev: false 687 | 688 | /fs.realpath@1.0.0: 689 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 690 | dev: true 691 | 692 | /gauge@4.0.4: 693 | resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} 694 | engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} 695 | dependencies: 696 | aproba: 2.0.0 697 | color-support: 1.1.3 698 | console-control-strings: 1.1.0 699 | has-unicode: 2.0.1 700 | signal-exit: 3.0.7 701 | string-width: 4.2.3 702 | strip-ansi: 6.0.1 703 | wide-align: 1.1.5 704 | dev: false 705 | 706 | /get-caller-file@2.0.5: 707 | resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 708 | engines: {node: 6.* || 8.* || >= 10.*} 709 | dev: false 710 | 711 | /glob-parent@5.1.2: 712 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 713 | engines: {node: '>= 6'} 714 | dependencies: 715 | is-glob: 4.0.3 716 | dev: true 717 | 718 | /glob-parent@6.0.2: 719 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 720 | engines: {node: '>=10.13.0'} 721 | dependencies: 722 | is-glob: 4.0.3 723 | dev: true 724 | 725 | /glob@7.2.3: 726 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 727 | dependencies: 728 | fs.realpath: 1.0.0 729 | inflight: 1.0.6 730 | inherits: 2.0.4 731 | minimatch: 3.1.2 732 | once: 1.4.0 733 | path-is-absolute: 1.0.1 734 | dev: true 735 | 736 | /globals@13.23.0: 737 | resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} 738 | engines: {node: '>=8'} 739 | dependencies: 740 | type-fest: 0.20.2 741 | dev: true 742 | 743 | /globby@11.1.0: 744 | resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 745 | engines: {node: '>=10'} 746 | dependencies: 747 | array-union: 2.1.0 748 | dir-glob: 3.0.1 749 | fast-glob: 3.3.1 750 | ignore: 5.2.4 751 | merge2: 1.4.1 752 | slash: 3.0.0 753 | dev: true 754 | 755 | /graceful-fs@4.2.11: 756 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 757 | dev: false 758 | 759 | /graphemer@1.4.0: 760 | resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 761 | dev: true 762 | 763 | /has-flag@4.0.0: 764 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 765 | engines: {node: '>=8'} 766 | dev: true 767 | 768 | /has-unicode@2.0.1: 769 | resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} 770 | dev: false 771 | 772 | /ignore@5.2.4: 773 | resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} 774 | engines: {node: '>= 4'} 775 | dev: true 776 | 777 | /import-fresh@3.3.0: 778 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 779 | engines: {node: '>=6'} 780 | dependencies: 781 | parent-module: 1.0.1 782 | resolve-from: 4.0.0 783 | dev: true 784 | 785 | /imurmurhash@0.1.4: 786 | resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 787 | engines: {node: '>=0.8.19'} 788 | dev: true 789 | 790 | /inflight@1.0.6: 791 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 792 | dependencies: 793 | once: 1.4.0 794 | wrappy: 1.0.2 795 | dev: true 796 | 797 | /inherits@2.0.4: 798 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 799 | 800 | /ini@1.3.8: 801 | resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} 802 | dev: false 803 | 804 | /is-extglob@2.1.1: 805 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 806 | engines: {node: '>=0.10.0'} 807 | dev: true 808 | 809 | /is-fullwidth-code-point@3.0.0: 810 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 811 | engines: {node: '>=8'} 812 | dev: false 813 | 814 | /is-glob@4.0.3: 815 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 816 | engines: {node: '>=0.10.0'} 817 | dependencies: 818 | is-extglob: 2.1.1 819 | dev: true 820 | 821 | /is-number@7.0.0: 822 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 823 | engines: {node: '>=0.12.0'} 824 | dev: true 825 | 826 | /is-path-inside@3.0.3: 827 | resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} 828 | engines: {node: '>=8'} 829 | dev: true 830 | 831 | /isexe@2.0.0: 832 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 833 | 834 | /js-yaml@4.1.0: 835 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 836 | hasBin: true 837 | dependencies: 838 | argparse: 2.0.1 839 | dev: true 840 | 841 | /json-buffer@3.0.1: 842 | resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 843 | dev: true 844 | 845 | /json-schema-traverse@0.4.1: 846 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 847 | dev: true 848 | 849 | /json-stable-stringify-without-jsonify@1.0.1: 850 | resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 851 | dev: true 852 | 853 | /jsonfile@6.1.0: 854 | resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} 855 | dependencies: 856 | universalify: 2.0.0 857 | optionalDependencies: 858 | graceful-fs: 4.2.11 859 | dev: false 860 | 861 | /keyv@4.5.4: 862 | resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 863 | dependencies: 864 | json-buffer: 3.0.1 865 | dev: true 866 | 867 | /levn@0.4.1: 868 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 869 | engines: {node: '>= 0.8.0'} 870 | dependencies: 871 | prelude-ls: 1.2.1 872 | type-check: 0.4.0 873 | dev: true 874 | 875 | /locate-path@6.0.0: 876 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 877 | engines: {node: '>=10'} 878 | dependencies: 879 | p-locate: 5.0.0 880 | dev: true 881 | 882 | /lodash.isplainobject@4.0.6: 883 | resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} 884 | dev: false 885 | 886 | /lodash.merge@4.6.2: 887 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 888 | dev: true 889 | 890 | /lru-cache@6.0.0: 891 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 892 | engines: {node: '>=10'} 893 | dependencies: 894 | yallist: 4.0.0 895 | 896 | /memory-stream@1.0.0: 897 | resolution: {integrity: sha512-Wm13VcsPIMdG96dzILfij09PvuS3APtcKNh7M28FsCA/w6+1mjR7hhPmfFNoilX9xU7wTdhsH5lJAm6XNzdtww==} 898 | dependencies: 899 | readable-stream: 3.6.2 900 | dev: false 901 | 902 | /merge2@1.4.1: 903 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 904 | engines: {node: '>= 8'} 905 | dev: true 906 | 907 | /micromatch@4.0.5: 908 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 909 | engines: {node: '>=8.6'} 910 | dependencies: 911 | braces: 3.0.2 912 | picomatch: 2.3.1 913 | dev: true 914 | 915 | /mime-db@1.52.0: 916 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 917 | engines: {node: '>= 0.6'} 918 | dev: false 919 | 920 | /mime-types@2.1.35: 921 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 922 | engines: {node: '>= 0.6'} 923 | dependencies: 924 | mime-db: 1.52.0 925 | dev: false 926 | 927 | /minimatch@3.1.2: 928 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 929 | dependencies: 930 | brace-expansion: 1.1.11 931 | dev: true 932 | 933 | /minimatch@9.0.3: 934 | resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} 935 | engines: {node: '>=16 || 14 >=14.17'} 936 | dependencies: 937 | brace-expansion: 2.0.1 938 | dev: true 939 | 940 | /minimist@1.2.8: 941 | resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 942 | dev: false 943 | 944 | /minipass@3.3.6: 945 | resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} 946 | engines: {node: '>=8'} 947 | dependencies: 948 | yallist: 4.0.0 949 | dev: false 950 | 951 | /minipass@5.0.0: 952 | resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} 953 | engines: {node: '>=8'} 954 | dev: false 955 | 956 | /minizlib@2.1.2: 957 | resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} 958 | engines: {node: '>= 8'} 959 | dependencies: 960 | minipass: 3.3.6 961 | yallist: 4.0.0 962 | dev: false 963 | 964 | /mkdirp@1.0.4: 965 | resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} 966 | engines: {node: '>=10'} 967 | hasBin: true 968 | dev: false 969 | 970 | /ms@2.1.2: 971 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 972 | 973 | /natural-compare@1.4.0: 974 | resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 975 | dev: true 976 | 977 | /node-addon-api@8.0.0: 978 | resolution: {integrity: sha512-ipO7rsHEBqa9STO5C5T10fj732ml+5kLN1cAG8/jdHd56ldQeGj3Q7+scUS+VHK/qy1zLEwC4wMK5+yM0btPvw==} 979 | engines: {node: ^18 || ^20 || >= 21} 980 | dev: false 981 | 982 | /node-api-headers@1.1.0: 983 | resolution: {integrity: sha512-ucQW+SbYCUPfprvmzBsnjT034IGRB2XK8rRc78BgjNKhTdFKgAwAmgW704bKIBmcYW48it0Gkjpkd39Azrwquw==} 984 | dev: false 985 | 986 | /npmlog@6.0.2: 987 | resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} 988 | engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} 989 | dependencies: 990 | are-we-there-yet: 3.0.1 991 | console-control-strings: 1.1.0 992 | gauge: 4.0.4 993 | set-blocking: 2.0.0 994 | dev: false 995 | 996 | /once@1.4.0: 997 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 998 | dependencies: 999 | wrappy: 1.0.2 1000 | dev: true 1001 | 1002 | /optionator@0.9.3: 1003 | resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} 1004 | engines: {node: '>= 0.8.0'} 1005 | dependencies: 1006 | '@aashutoshrathi/word-wrap': 1.2.6 1007 | deep-is: 0.1.4 1008 | fast-levenshtein: 2.0.6 1009 | levn: 0.4.1 1010 | prelude-ls: 1.2.1 1011 | type-check: 0.4.0 1012 | dev: true 1013 | 1014 | /p-limit@3.1.0: 1015 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1016 | engines: {node: '>=10'} 1017 | dependencies: 1018 | yocto-queue: 0.1.0 1019 | dev: true 1020 | 1021 | /p-locate@5.0.0: 1022 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1023 | engines: {node: '>=10'} 1024 | dependencies: 1025 | p-limit: 3.1.0 1026 | dev: true 1027 | 1028 | /parent-module@1.0.1: 1029 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1030 | engines: {node: '>=6'} 1031 | dependencies: 1032 | callsites: 3.1.0 1033 | dev: true 1034 | 1035 | /path-exists@4.0.0: 1036 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1037 | engines: {node: '>=8'} 1038 | dev: true 1039 | 1040 | /path-is-absolute@1.0.1: 1041 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 1042 | engines: {node: '>=0.10.0'} 1043 | dev: true 1044 | 1045 | /path-key@3.1.1: 1046 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1047 | engines: {node: '>=8'} 1048 | dev: true 1049 | 1050 | /path-type@4.0.0: 1051 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 1052 | engines: {node: '>=8'} 1053 | dev: true 1054 | 1055 | /picomatch@2.3.1: 1056 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1057 | engines: {node: '>=8.6'} 1058 | dev: true 1059 | 1060 | /pkg-prebuilds@0.2.1: 1061 | resolution: {integrity: sha512-FdOlDiRqRL7i9aYzQflhGWCoiJf/8u6Qgzq48gKsRDYejtfjvGb1U5QGSzllcqpNg2a8Swx/9fMgtuVefwU+zw==} 1062 | engines: {node: '>= 14.15.0'} 1063 | hasBin: true 1064 | dependencies: 1065 | yargs: 17.7.2 1066 | dev: false 1067 | 1068 | /prelude-ls@1.2.1: 1069 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1070 | engines: {node: '>= 0.8.0'} 1071 | dev: true 1072 | 1073 | /proxy-from-env@1.1.0: 1074 | resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} 1075 | dev: false 1076 | 1077 | /punycode@2.3.0: 1078 | resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} 1079 | engines: {node: '>=6'} 1080 | dev: true 1081 | 1082 | /queue-microtask@1.2.3: 1083 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1084 | dev: true 1085 | 1086 | /rc@1.2.8: 1087 | resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} 1088 | hasBin: true 1089 | dependencies: 1090 | deep-extend: 0.6.0 1091 | ini: 1.3.8 1092 | minimist: 1.2.8 1093 | strip-json-comments: 2.0.1 1094 | dev: false 1095 | 1096 | /readable-stream@3.6.2: 1097 | resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} 1098 | engines: {node: '>= 6'} 1099 | dependencies: 1100 | inherits: 2.0.4 1101 | string_decoder: 1.3.0 1102 | util-deprecate: 1.0.2 1103 | dev: false 1104 | 1105 | /require-directory@2.1.1: 1106 | resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} 1107 | engines: {node: '>=0.10.0'} 1108 | dev: false 1109 | 1110 | /resolve-from@4.0.0: 1111 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1112 | engines: {node: '>=4'} 1113 | dev: true 1114 | 1115 | /reusify@1.0.4: 1116 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1117 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1118 | dev: true 1119 | 1120 | /rimraf@3.0.2: 1121 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 1122 | hasBin: true 1123 | dependencies: 1124 | glob: 7.2.3 1125 | dev: true 1126 | 1127 | /run-parallel@1.2.0: 1128 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1129 | dependencies: 1130 | queue-microtask: 1.2.3 1131 | dev: true 1132 | 1133 | /safe-buffer@5.2.1: 1134 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1135 | dev: false 1136 | 1137 | /semver@7.5.4: 1138 | resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 1139 | engines: {node: '>=10'} 1140 | hasBin: true 1141 | dependencies: 1142 | lru-cache: 6.0.0 1143 | 1144 | /set-blocking@2.0.0: 1145 | resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} 1146 | dev: false 1147 | 1148 | /shebang-command@2.0.0: 1149 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1150 | engines: {node: '>=8'} 1151 | dependencies: 1152 | shebang-regex: 3.0.0 1153 | dev: true 1154 | 1155 | /shebang-regex@3.0.0: 1156 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1157 | engines: {node: '>=8'} 1158 | dev: true 1159 | 1160 | /signal-exit@3.0.7: 1161 | resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 1162 | dev: false 1163 | 1164 | /slash@3.0.0: 1165 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 1166 | engines: {node: '>=8'} 1167 | dev: true 1168 | 1169 | /string-width@4.2.3: 1170 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 1171 | engines: {node: '>=8'} 1172 | dependencies: 1173 | emoji-regex: 8.0.0 1174 | is-fullwidth-code-point: 3.0.0 1175 | strip-ansi: 6.0.1 1176 | dev: false 1177 | 1178 | /string_decoder@1.3.0: 1179 | resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 1180 | dependencies: 1181 | safe-buffer: 5.2.1 1182 | dev: false 1183 | 1184 | /strip-ansi@6.0.1: 1185 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1186 | engines: {node: '>=8'} 1187 | dependencies: 1188 | ansi-regex: 5.0.1 1189 | 1190 | /strip-json-comments@2.0.1: 1191 | resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} 1192 | engines: {node: '>=0.10.0'} 1193 | dev: false 1194 | 1195 | /strip-json-comments@3.1.1: 1196 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1197 | engines: {node: '>=8'} 1198 | dev: true 1199 | 1200 | /supports-color@7.2.0: 1201 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1202 | engines: {node: '>=8'} 1203 | dependencies: 1204 | has-flag: 4.0.0 1205 | dev: true 1206 | 1207 | /tar@6.2.0: 1208 | resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} 1209 | engines: {node: '>=10'} 1210 | dependencies: 1211 | chownr: 2.0.0 1212 | fs-minipass: 2.1.0 1213 | minipass: 5.0.0 1214 | minizlib: 2.1.2 1215 | mkdirp: 1.0.4 1216 | yallist: 4.0.0 1217 | dev: false 1218 | 1219 | /text-table@0.2.0: 1220 | resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} 1221 | dev: true 1222 | 1223 | /to-regex-range@5.0.1: 1224 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1225 | engines: {node: '>=8.0'} 1226 | dependencies: 1227 | is-number: 7.0.0 1228 | dev: true 1229 | 1230 | /ts-api-utils@1.0.3(typescript@5.2.2): 1231 | resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} 1232 | engines: {node: '>=16.13.0'} 1233 | peerDependencies: 1234 | typescript: '>=4.2.0' 1235 | dependencies: 1236 | typescript: 5.2.2 1237 | dev: true 1238 | 1239 | /type-check@0.4.0: 1240 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1241 | engines: {node: '>= 0.8.0'} 1242 | dependencies: 1243 | prelude-ls: 1.2.1 1244 | dev: true 1245 | 1246 | /type-fest@0.20.2: 1247 | resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 1248 | engines: {node: '>=10'} 1249 | dev: true 1250 | 1251 | /typescript@5.2.2: 1252 | resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} 1253 | engines: {node: '>=14.17'} 1254 | hasBin: true 1255 | dev: true 1256 | 1257 | /universalify@2.0.0: 1258 | resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} 1259 | engines: {node: '>= 10.0.0'} 1260 | dev: false 1261 | 1262 | /uri-js@4.4.1: 1263 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1264 | dependencies: 1265 | punycode: 2.3.0 1266 | dev: true 1267 | 1268 | /url-join@4.0.1: 1269 | resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} 1270 | dev: false 1271 | 1272 | /util-deprecate@1.0.2: 1273 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1274 | dev: false 1275 | 1276 | /which@2.0.2: 1277 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1278 | engines: {node: '>= 8'} 1279 | hasBin: true 1280 | dependencies: 1281 | isexe: 2.0.0 1282 | 1283 | /wide-align@1.1.5: 1284 | resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} 1285 | dependencies: 1286 | string-width: 4.2.3 1287 | dev: false 1288 | 1289 | /wrap-ansi@7.0.0: 1290 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 1291 | engines: {node: '>=10'} 1292 | dependencies: 1293 | ansi-styles: 4.3.0 1294 | string-width: 4.2.3 1295 | strip-ansi: 6.0.1 1296 | dev: false 1297 | 1298 | /wrappy@1.0.2: 1299 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1300 | dev: true 1301 | 1302 | /y18n@5.0.8: 1303 | resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} 1304 | engines: {node: '>=10'} 1305 | dev: false 1306 | 1307 | /yallist@4.0.0: 1308 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1309 | 1310 | /yargs-parser@21.1.1: 1311 | resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} 1312 | engines: {node: '>=12'} 1313 | dev: false 1314 | 1315 | /yargs@17.7.2: 1316 | resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} 1317 | engines: {node: '>=12'} 1318 | dependencies: 1319 | cliui: 8.0.1 1320 | escalade: 3.1.1 1321 | get-caller-file: 2.0.5 1322 | require-directory: 2.1.1 1323 | string-width: 4.2.3 1324 | y18n: 5.0.8 1325 | yargs-parser: 21.1.1 1326 | dev: false 1327 | 1328 | /yocto-queue@0.1.0: 1329 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1330 | engines: {node: '>=10'} 1331 | dev: true 1332 | -------------------------------------------------------------------------------- /private/message.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "patchbay.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace pw = pipewire; 11 | 12 | namespace vencord 13 | { 14 | struct list_nodes 15 | { 16 | std::vector props; 17 | }; 18 | 19 | struct unset_target 20 | { 21 | }; 22 | 23 | struct quit 24 | { 25 | }; 26 | 27 | struct abort 28 | { 29 | }; 30 | 31 | struct ready 32 | { 33 | bool success{true}; 34 | }; 35 | 36 | using pw_recipe = pw::recipe; 37 | using cr_recipe = cr::recipe, ready, quit>; 38 | } // namespace vencord 39 | -------------------------------------------------------------------------------- /private/patchbay.impl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "patchbay.hpp" 4 | #include "message.hpp" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | namespace vencord 25 | { 26 | using port_map = std::vector>; 27 | 28 | struct node_with_ports 29 | { 30 | pw::node_info info; 31 | std::vector ports; 32 | }; 33 | 34 | struct speaker 35 | { 36 | std::string name; 37 | std::uint32_t id; 38 | }; 39 | 40 | class patchbay::impl 41 | { 42 | std::jthread thread; 43 | 44 | public: 45 | std::stop_source update_source; 46 | std::unique_ptr sender; 47 | std::unique_ptr receiver; 48 | 49 | private: 50 | link_options options; 51 | 52 | private: 53 | /** 54 | * ╔══════════════════╗ 55 | * ║ Metadata related ║ 56 | * ╚══════════════════╝ 57 | * 58 | * 1. The *default* metadata is bound to @see metadata 59 | * 2. A listener is installed to check for default speaker updates, @see meta_listener 60 | * 3. Speaker info is saved to @see speaker, the name is parsed from the metadata, the id is set in @see on_node 61 | * 4. The @see lettuce_target is used by the workaround, a redirect is installed in the metadata (see venmic#15) 62 | */ 63 | 64 | std::unique_ptr metadata; 65 | std::unique_ptr meta_listener; 66 | 67 | std::optional speaker; 68 | std::optional lettuce_target; 69 | 70 | private: 71 | /** 72 | * ╔══════════════════╗ 73 | * ║ Virt mic related ║ 74 | * ╚══════════════════╝ 75 | * 76 | * 1. The @see virt_mic is created by @see create_mic 77 | * 2. All created links, @see relink, are bound in @see created 78 | * └┬─────────┘ 79 | * key: related node 80 | * value: bound link 81 | */ 82 | 83 | std::unique_ptr virt_mic; 84 | [[vc::check_erase]] std::multimap created; 85 | 86 | private: 87 | /** 88 | * ╔═══════════════╗ 89 | * ║ Logic related ║ 90 | * ╚═══════════════╝ 91 | * 92 | * 1. All links we encounter are saved in @see links 93 | * 2. All nodes we encounter are saved in @see nodes 94 | */ 95 | 96 | [[vc::check_erase]] std::map links; 97 | [[vc::check_erase]] std::map nodes; 98 | 99 | private: 100 | std::shared_ptr core; 101 | std::optional registry; 102 | 103 | private: 104 | std::atomic_bool should_exit{false}; 105 | 106 | public: 107 | ~impl(); 108 | 109 | public: 110 | impl(); 111 | 112 | private: 113 | [[nodiscard]] port_map map_ports(const node_with_ports &); 114 | 115 | private: 116 | void create_mic(); 117 | void cleanup(bool); 118 | 119 | private: 120 | void reload(); 121 | 122 | private: 123 | void link(std::uint32_t); 124 | 125 | private: 126 | void meta_update(std::string_view, pw::metadata_property); 127 | 128 | private: 129 | void on_link(std::uint32_t); 130 | void on_node(std::uint32_t); 131 | 132 | private: 133 | template 134 | void bind(const pw::global &); 135 | 136 | public: 137 | template 138 | void handle(T &, const pw::global &); 139 | 140 | private: 141 | void del_global(std::uint32_t); 142 | void add_global(const pw::global &); 143 | 144 | private: 145 | template 146 | void receive(cr_recipe::sender &, T &); 147 | 148 | private: 149 | void start(pw_recipe::receiver, cr_recipe::sender); 150 | }; 151 | } // namespace vencord 152 | -------------------------------------------------------------------------------- /server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(venmic-server LANGUAGES CXX VERSION 2.0) 3 | 4 | # -------------------------------------------------------------------------------------------------------- 5 | # Create library 6 | # -------------------------------------------------------------------------------------------------------- 7 | 8 | add_executable(${PROJECT_NAME}) 9 | 10 | target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) 11 | set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20 CXX_EXTENSIONS OFF CXX_STANDARD_REQUIRED ON) 12 | 13 | if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 14 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror -pedantic -pedantic-errors -Wfatal-errors) 15 | endif() 16 | 17 | target_compile_options(${PROJECT_NAME} PRIVATE -Wno-missing-field-initializers -Wno-cast-function-type) 18 | 19 | # -------------------------------------------------------------------------------------------------------- 20 | # Add source files 21 | # -------------------------------------------------------------------------------------------------------- 22 | 23 | file(GLOB src "*.cpp") 24 | target_sources(${PROJECT_NAME} PRIVATE ${src}) 25 | 26 | # -------------------------------------------------------------------------------------------------------- 27 | # Setup Dependencies 28 | # -------------------------------------------------------------------------------------------------------- 29 | 30 | include("../cmake/cpm.cmake") 31 | 32 | CPMFindPackage( 33 | NAME cpp-httplib 34 | VERSION 0.15.3 35 | GIT_REPOSITORY "https://github.com/yhirose/cpp-httplib" 36 | ) 37 | 38 | target_link_libraries(${PROJECT_NAME} PUBLIC vencord::venmic glaze::glaze httplib) 39 | -------------------------------------------------------------------------------- /server/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | int main(int argc, char **args) 10 | { 11 | using vencord::logger; 12 | using vencord::patchbay; 13 | 14 | auto port = 7591; 15 | 16 | if (argc > 1) 17 | { 18 | try 19 | { 20 | port = std::stoi(args[1]); 21 | } 22 | catch (...) 23 | { 24 | logger::get()->error("Bad arguments, usage: {} [port]", args[0]); 25 | return 1; 26 | } 27 | } 28 | 29 | logger::get()->warn("DISCLAIMER: This program is not intended for standalone usage. You need a modified discord " 30 | "client that makes use of this!"); 31 | 32 | logger::get()->info("Running on port: {}", port); 33 | 34 | httplib::Server server; 35 | 36 | server.set_exception_handler( 37 | [&](const auto &, auto &, auto exception) 38 | { 39 | try 40 | { 41 | std::rethrow_exception(exception); 42 | } 43 | catch (const std::exception &ex) 44 | { 45 | logger::get()->error("Encountered error: {}", ex.what()); 46 | } 47 | catch (...) 48 | { 49 | logger::get()->error("Encountered error: "); 50 | } 51 | 52 | server.stop(); 53 | }); 54 | 55 | server.Post("/list", 56 | [](const auto &req, auto &response) 57 | { 58 | auto props = glz::read_json>(req.body); 59 | auto data = glz::write_json(patchbay::get().list(props.value_or(std::vector{}))); 60 | 61 | response.set_content(data, "application/json"); 62 | }); 63 | 64 | server.Post("/link", 65 | [](const auto &req, auto &response) 66 | { 67 | vencord::link_options parsed; 68 | 69 | const auto error = glz::read_json(parsed, req.body); 70 | 71 | if (error) 72 | { 73 | response.status = 418; 74 | return; 75 | } 76 | 77 | patchbay::get().link(std::move(parsed)); 78 | 79 | response.status = 200; 80 | }); 81 | 82 | server.Get("/has-pipewire-pulse", 83 | [](const auto &, auto &response) 84 | { 85 | auto data = glz::write_json(patchbay::has_pipewire()); 86 | 87 | response.set_content(data, "application/json"); 88 | response.status = 200; 89 | }); 90 | 91 | server.Get("/unlink", 92 | [](const auto &, auto &response) 93 | { 94 | patchbay::get().unlink(); 95 | response.status = 200; 96 | }); 97 | 98 | server.listen("0.0.0.0", port); 99 | 100 | return 0; 101 | } 102 | -------------------------------------------------------------------------------- /src/logger.cpp: -------------------------------------------------------------------------------- 1 | #include "logger.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace vencord 8 | { 9 | namespace fs = std::filesystem; 10 | 11 | struct logger::impl 12 | { 13 | std::unique_ptr logger; 14 | }; 15 | 16 | static fs::path log_directory() 17 | { 18 | auto rtn = fs::temp_directory_path(); 19 | 20 | // NOLINTNEXTLINE(*-mt-unsafe) 21 | if (auto *home = std::getenv("HOME")) 22 | { 23 | rtn = fs::path{home} / ".local" / "state"; 24 | } 25 | 26 | // NOLINTNEXTLINE(*-mt-unsafe) 27 | if (auto *state_home = std::getenv("XDG_STATE_HOME")) 28 | { 29 | rtn = state_home; 30 | } 31 | 32 | return rtn / "venmic"; 33 | } 34 | 35 | logger::logger() : m_impl(std::make_unique()) 36 | { 37 | namespace sinks = spdlog::sinks; 38 | 39 | m_impl->logger = std::make_unique("venmic"); 40 | m_impl->logger->set_level(spdlog::level::trace); 41 | m_impl->logger->flush_on(spdlog::level::trace); 42 | 43 | auto stdout_sink = std::make_shared(); 44 | stdout_sink->set_level(spdlog::level::info); 45 | 46 | m_impl->logger->sinks().emplace_back(stdout_sink); 47 | 48 | // NOLINTNEXTLINE(*-mt-unsafe) 49 | if (!std::getenv("VENMIC_ENABLE_LOG")) 50 | { 51 | return; 52 | } 53 | 54 | auto directory = log_directory(); 55 | 56 | if (!fs::exists(directory)) 57 | { 58 | [[maybe_unused]] std::error_code ec; 59 | fs::create_directories(directory, ec); 60 | } 61 | 62 | auto file_sink = std::make_shared((directory / "venmic.log").string()); 63 | 64 | file_sink->set_level(spdlog::level::trace); 65 | stdout_sink->set_level(spdlog::level::trace); 66 | 67 | m_impl->logger->sinks().emplace_back(file_sink); 68 | } 69 | 70 | spdlog::logger *logger::operator->() const 71 | { 72 | return m_impl->logger.get(); 73 | } 74 | 75 | logger &logger::get() 76 | { 77 | static std::unique_ptr instance; 78 | 79 | if (!instance) 80 | { 81 | instance = std::unique_ptr(new logger); 82 | } 83 | 84 | return *instance; 85 | } 86 | } // namespace vencord 87 | -------------------------------------------------------------------------------- /src/patchbay.cpp: -------------------------------------------------------------------------------- 1 | #include "patchbay.impl.hpp" 2 | #include "logger.hpp" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace vencord 11 | { 12 | patchbay::~patchbay() = default; 13 | 14 | patchbay::patchbay() : m_impl(std::make_unique()) {} 15 | 16 | void patchbay::link(link_options options) 17 | { 18 | m_impl->sender->send(std::move(options)); 19 | } 20 | 21 | void patchbay::unlink() 22 | { 23 | m_impl->sender->send(unset_target{}); 24 | } 25 | 26 | std::vector patchbay::list(std::vector props) 27 | { 28 | m_impl->sender->send(list_nodes{std::move(props)}); 29 | return m_impl->receiver->recv_as>(); 30 | } 31 | 32 | patchbay &patchbay::get() 33 | { 34 | static std::unique_ptr instance; 35 | 36 | if (!instance && !has_pipewire()) 37 | { 38 | logger::get()->warn("[patchbay] (get) pipewire was not detected as main audio server"); 39 | } 40 | 41 | if (!instance) 42 | { 43 | instance = std::unique_ptr(new patchbay); 44 | logger::get()->info("[patchbay] (get) running venmic {}", VENMIC_VERSION); 45 | } 46 | 47 | return *instance; 48 | } 49 | 50 | bool patchbay::has_pipewire() 51 | { 52 | static std::optional cached; 53 | 54 | if (cached) 55 | { 56 | logger::get()->trace("[patchbay] (has_pipewire) using cached result"); 57 | return cached.value(); 58 | } 59 | 60 | auto *loop = pa_mainloop_new(); 61 | auto *context = pa_context_new(pa_mainloop_get_api(loop), "venmic-pulse-info"); 62 | 63 | struct state 64 | { 65 | pa_mainloop *loop; 66 | std::promise result; 67 | }; 68 | 69 | static auto info = [](pa_context *, const pa_server_info *info, void *data) 70 | { 71 | auto &[loop, result] = *reinterpret_cast(data); 72 | 73 | result.set_value(info->server_name); 74 | pa_mainloop_quit(loop, 0); 75 | }; 76 | 77 | static auto notify = [](pa_context *context, void *data) 78 | { 79 | auto &[loop, result] = *reinterpret_cast(data); 80 | auto state = pa_context_get_state(context); 81 | 82 | if (state == PA_CONTEXT_FAILED) 83 | { 84 | logger::get()->error("[patchbay] (has_pipewire) failed to connect pulse context"); 85 | pa_mainloop_quit(loop, 0); 86 | 87 | return; 88 | } 89 | 90 | if (state != PA_CONTEXT_READY) 91 | { 92 | return; 93 | } 94 | 95 | pa_context_get_server_info(context, info, data); 96 | }; 97 | 98 | state state{loop}; 99 | pa_context_set_state_callback(context, notify, &state); 100 | 101 | if (pa_context_connect(context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) >= 0) 102 | { 103 | pa_mainloop_run(loop, nullptr); 104 | } 105 | 106 | pa_context_disconnect(context); 107 | pa_mainloop_free(loop); 108 | 109 | auto result = state.result.get_future(); 110 | 111 | if (result.wait_for(std::chrono::seconds(0)) != std::future_status::ready) 112 | { 113 | logger::get()->error("[patchbay] (has_pipewire) result timed out"); 114 | return false; 115 | } 116 | 117 | auto name = result.get(); 118 | std::transform(name.begin(), name.end(), name.begin(), [](char c) { return std::tolower(c); }); 119 | 120 | logger::get()->debug("[patchbay] (has_pipewire) pulse-server is \"{}\"", name); 121 | 122 | return cached.emplace(name.find("pipewire") != std::string::npos); 123 | } 124 | } // namespace vencord 125 | -------------------------------------------------------------------------------- /src/patchbay.impl.cpp: -------------------------------------------------------------------------------- 1 | #include "patchbay.impl.hpp" 2 | 3 | #include "logger.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | namespace vencord 16 | { 17 | struct pw_metadata_name 18 | { 19 | std::string name; 20 | }; 21 | 22 | bool matches(const std::vector &targets, pw::spa::dict props) // NOLINT(*-value-param) 23 | { 24 | auto props_match = [&](const auto &prop) 25 | { 26 | return props[prop.first] == prop.second; 27 | }; 28 | 29 | auto match = [&](const auto &target) 30 | { 31 | return ranges::all_of(target, props_match); 32 | }; 33 | 34 | return ranges::any_of(targets, match); 35 | } 36 | 37 | patchbay::impl::~impl() 38 | { 39 | should_exit = true; 40 | 41 | sender->send(quit{}); 42 | receiver->recv_as(); 43 | } 44 | 45 | patchbay::impl::impl() 46 | { 47 | auto [pw_sender, pw_receiver] = pw::channel(); 48 | auto [cr_sender, cr_receiver] = cr::channel(); 49 | 50 | sender = std::make_unique(std::move(pw_sender)); 51 | receiver = std::make_unique(std::move(cr_receiver)); 52 | 53 | auto thread_start = [this](auto receiver, auto sender) 54 | { 55 | start(std::move(receiver), std::move(sender)); 56 | }; 57 | 58 | thread = std::jthread{thread_start, std::move(pw_receiver), cr_sender}; 59 | 60 | auto response = receiver->recv_timeout_as(std::chrono::seconds(1)); 61 | 62 | if (!response.has_value()) 63 | { 64 | logger::get()->error("[patchbay] (init) pw_receiver failed to respond within 1 second, aborting"); 65 | 66 | sender->send(abort{}); 67 | response = receiver->recv_as(); 68 | } 69 | 70 | if (response->success) 71 | { 72 | logger::get()->trace("[patchbay] (init) pw_receiver is ready"); 73 | return; 74 | } 75 | 76 | throw std::runtime_error("Failed to create patchbay instance"); 77 | } 78 | 79 | port_map patchbay::impl::map_ports(const node_with_ports &target) 80 | { 81 | port_map rtn; 82 | 83 | auto is_output = [](const auto &item) 84 | { 85 | return item.direction == pw::port_direction::output; 86 | }; 87 | auto is_input = [](const auto &item) 88 | { 89 | return item.direction == pw::port_direction::input; 90 | }; 91 | 92 | const auto mic = nodes[virt_mic->id()]; 93 | auto mic_inputs = mic.ports | ranges::views::filter(is_input); 94 | 95 | const auto id = target.info.id; 96 | const auto target_outputs = target.ports | ranges::views::filter(is_output) | ranges::to; 97 | 98 | if (target_outputs.empty()) 99 | { 100 | logger::get()->warn("[patchbay] (map_ports) {} has no ports", id); 101 | return rtn; 102 | } 103 | 104 | const auto is_mono = target_outputs.size() == 1; 105 | 106 | if (is_mono) 107 | { 108 | logger::get()->debug("[patchbay] (map_ports) {} is mono", id); 109 | } 110 | 111 | for (const auto &port : target_outputs) 112 | { 113 | auto port_props = port.props; 114 | 115 | auto matching_channel = [is_mono, &port_props](auto &item) 116 | { 117 | if (is_mono) 118 | { 119 | return true; 120 | } 121 | 122 | auto props = item.props; 123 | 124 | if (props["audio.channel"] == "UNK" || port_props["audio.channel"] == "UNK") 125 | { 126 | return props["port.id"] == port_props["port.id"]; 127 | } 128 | 129 | return props["audio.channel"] == port_props["audio.channel"]; 130 | }; 131 | 132 | auto mapping = 133 | mic_inputs // 134 | | ranges::views::filter(matching_channel) // 135 | | ranges::views::transform([port](const auto &mic_port) { return std::make_pair(mic_port, port); }) // 136 | | ranges::to; 137 | 138 | ranges::move(mapping, std::back_inserter(rtn)); 139 | 140 | logger::get()->debug("[patchbay] (map_ports) {} maps to {} mic port(s)", port.id, mapping.size()); 141 | } 142 | 143 | return rtn; 144 | } 145 | 146 | void patchbay::impl::create_mic() 147 | { 148 | logger::get()->trace("[patchbay] (create_mic) creating virt-mic"); 149 | 150 | auto node = core->create(pw::null_sink_factory{ 151 | .name = "vencord-screen-share", 152 | .positions = {"FL", "FR"}, 153 | }) 154 | .get(); 155 | 156 | while (nodes[node->id()].ports.size() < 4) 157 | { 158 | core->update(); 159 | } 160 | 161 | virt_mic = std::make_unique(std::move(*node)); 162 | 163 | logger::get()->info("[patchbay] (create_mic) created: {}", virt_mic->id()); 164 | } 165 | 166 | void patchbay::impl::cleanup(bool mic) 167 | { 168 | created.clear(); 169 | 170 | if (metadata && lettuce_target) 171 | { 172 | metadata->clear_property(lettuce_target.value(), "target.node"); 173 | metadata->clear_property(lettuce_target.value(), "target.object"); 174 | } 175 | 176 | if (!mic) 177 | { 178 | return; 179 | } 180 | 181 | virt_mic.reset(); 182 | } 183 | 184 | void patchbay::impl::reload() 185 | { 186 | cleanup(false); 187 | 188 | for (const auto &[id, info] : nodes) 189 | { 190 | on_node(id); 191 | } 192 | 193 | for (const auto &[id, info] : links) 194 | { 195 | on_link(id); 196 | } 197 | 198 | logger::get()->debug("[patchbay] (reload) finished"); 199 | } 200 | 201 | void patchbay::impl::link(std::uint32_t id) 202 | { 203 | if (!virt_mic) 204 | { 205 | return; 206 | } 207 | 208 | const auto mic_id = virt_mic->id(); 209 | 210 | if (id == mic_id) 211 | { 212 | logger::get()->warn("[patchbay] (link) prevented link to self"); 213 | return; 214 | } 215 | 216 | if (!nodes.contains(id)) 217 | { 218 | logger::get()->warn("[patchbay] (link) called with bad node: {}", id); 219 | return; 220 | } 221 | 222 | const auto &target = nodes[id]; 223 | auto props = target.info.props; 224 | 225 | if (options.ignore_devices && !props["device.id"].empty()) 226 | { 227 | logger::get()->warn("[patchbay] (link) prevented link to device: {}", id); 228 | return; 229 | } 230 | 231 | logger::get()->debug("[patchbay] (link) linking {}", id); 232 | 233 | auto mapping = map_ports(target); 234 | 235 | for (auto [it, end] = created.equal_range(id); it != end; ++it) 236 | { 237 | auto equal = [info = it->second.info()](const auto &map) 238 | { 239 | return map.first.id == info.input.port && map.second.id == info.output.port; 240 | }; 241 | 242 | std::erase_if(mapping, equal); 243 | } 244 | 245 | for (auto [mic_port, target_port] : mapping) 246 | { 247 | auto link = core->create(pw::link_factory{ 248 | mic_port.id, 249 | target_port.id, 250 | }) 251 | .get(); 252 | 253 | if (!link.has_value()) 254 | { 255 | logger::get()->warn("[patchbay] (link) failed to link {} (mic) -> {} (node): {}", mic_port.id, 256 | target_port.id, link.error().message); 257 | 258 | return; 259 | } 260 | 261 | logger::get()->debug("[patchbay] (link) created {}: {} (mic) -> {} (node) (channel: {})", link->id(), 262 | mic_port.id, target_port.id, target_port.props["audio.channel"]); 263 | 264 | created.emplace(id, std::move(*link)); 265 | } 266 | 267 | logger::get()->debug("[patchbay] (link) linked all ports of {}", id); 268 | } 269 | 270 | void patchbay::impl::meta_update(std::string_view key, pw::metadata_property prop) 271 | { 272 | logger::get()->debug(R"([patchbay] (meta_update) metadata property changed: "{}" (value: "{}"))", key, 273 | prop.value); 274 | 275 | if (key != "default.audio.sink") 276 | { 277 | return; 278 | } 279 | 280 | auto parsed = glz::read_json(prop.value); 281 | 282 | if (!parsed.has_value()) 283 | { 284 | logger::get()->warn("[patchbay] (meta_update) failed to parse speaker"); 285 | return; 286 | } 287 | 288 | speaker.emplace(parsed->name); 289 | logger::get()->info(R"([patchbay] (meta_update) speaker name: "{}")", speaker->name); 290 | 291 | reload(); 292 | } 293 | 294 | void patchbay::impl::on_link(std::uint32_t id) 295 | { 296 | if (!options.include.empty() || (options.only_default_speakers && !speaker)) 297 | { 298 | return; 299 | } 300 | 301 | const auto &info = links[id]; 302 | 303 | const auto output_id = info.output.node; 304 | const auto input_id = info.input.node; 305 | 306 | if (options.only_default_speakers && input_id != speaker->id) 307 | { 308 | logger::get()->debug("[patchbay] (on_link) {} is not connected to speaker but with {}", id, input_id); 309 | return; 310 | } 311 | 312 | auto output_props = nodes[output_id].info.props; // The node emitting sound 313 | auto input_props = nodes[input_id].info.props; // The node receiving sound 314 | 315 | if (options.only_speakers && input_props["device.id"].empty()) 316 | { 317 | logger::get()->debug("[patchbay] (on_link) {} is not playing to a device: {}", id, input_id); 318 | return; 319 | } 320 | 321 | if (matches(options.exclude, output_props)) 322 | { 323 | return; 324 | } 325 | 326 | core->update(); 327 | 328 | link(output_id); 329 | } 330 | 331 | void patchbay::impl::on_node(std::uint32_t id) 332 | { 333 | const auto &[info, ports] = nodes[id]; 334 | auto props = info.props; 335 | 336 | if (speaker && props["node.name"] == speaker->name) 337 | { 338 | logger::get()->debug("[patchbay] (on_node) speakers are {}", id); 339 | speaker->id = id; 340 | 341 | return; 342 | } 343 | 344 | if (options.include.empty()) 345 | { 346 | return; 347 | } 348 | 349 | if (ports.empty()) 350 | { 351 | logger::get()->debug("[patchbay] (on_node) {} has no ports", id); 352 | return; 353 | } 354 | 355 | if (matches(options.exclude, props)) 356 | { 357 | logger::get()->debug("[patchbay] (on_node) {} is excluded", id); 358 | return; 359 | } 360 | 361 | if (!matches(options.include, props)) 362 | { 363 | logger::get()->debug("[patchbay] (on_node) {} is not included", id); 364 | return; 365 | } 366 | 367 | created.erase(id); 368 | core->update(); 369 | 370 | link(id); 371 | } 372 | 373 | template <> 374 | void patchbay::impl::handle(pw::node &node, const pw::global &global) 375 | { 376 | const auto id = global.id; 377 | 378 | auto info = node.info(); 379 | auto props = info.props; 380 | 381 | nodes[id].info = std::move(info); 382 | 383 | logger::get()->trace(R"([patchbay] (handle) new node: {} (name: "{}", app: "{}"))", id, props["node.name"], 384 | props["application.name"]); 385 | 386 | if (!virt_mic) 387 | { 388 | return; 389 | } 390 | 391 | if (!metadata || options.workaround.empty()) 392 | { 393 | return on_node(id); 394 | } 395 | 396 | logger::get()->trace("[patchbay] (handle) workaround is active ({})", glz::write_json(options.workaround)); 397 | 398 | if (!matches(options.workaround, props)) 399 | { 400 | return on_node(id); 401 | } 402 | 403 | const auto serial = virt_mic->info().props["object.serial"]; 404 | logger::get()->debug("[patchbay] (handle) applying workaround to {} (serial = {})", id, serial); 405 | 406 | // https://github.com/Vencord/venmic/issues/13#issuecomment-1884975782 407 | 408 | metadata->set_property(id, "target.object", "Spa:Id", serial); 409 | metadata->set_property(id, "target.node", "Spa:Id", std::to_string(virt_mic->id())); 410 | 411 | lettuce_target.emplace(id); 412 | options.workaround.clear(); 413 | 414 | core->update(); 415 | } 416 | 417 | template <> 418 | void patchbay::impl::handle(pw::link &link, const pw::global &global) 419 | { 420 | const auto id = global.id; 421 | auto info = link.info(); 422 | 423 | logger::get()->trace( 424 | "[patchbay] (handle) new link: {} (input-node: {}, output-node: {}, input-port: {}, output-port: {})", id, 425 | info.input.node, info.output.node, info.input.port, info.output.port); 426 | 427 | links[id] = std::move(info); 428 | 429 | on_link(id); 430 | } 431 | 432 | template <> 433 | void patchbay::impl::handle(pw::port &port, const pw::global &global) 434 | { 435 | auto info = port.info(); 436 | auto props = info.props; 437 | 438 | if (!props.contains("node.id")) 439 | { 440 | logger::get()->warn("[patchbay] (handle) {} has no parent", global.id); 441 | return; 442 | } 443 | 444 | std::uint32_t parent{}; 445 | 446 | auto node_id = info.props["node.id"]; 447 | auto status = std::from_chars(node_id.data(), node_id.data() + node_id.size(), parent); 448 | 449 | if (status.ec != std::errc{}) 450 | { 451 | logger::get()->warn("[patchbay] (handle) {} failed to parse parent node: '{}'", global.id, node_id); 452 | return; 453 | } 454 | 455 | nodes[parent].ports.emplace_back(std::move(info)); 456 | logger::get()->trace("[patchbay] (handle) new port: {} with parent {}", global.id, parent); 457 | 458 | // Check the parent again 459 | on_node(parent); 460 | } 461 | 462 | template <> 463 | void patchbay::impl::handle(pw::metadata &data, const pw::global &global) 464 | { 465 | auto props = global.props; 466 | auto properties = data.properties(); 467 | const auto name = props["metadata.name"]; 468 | 469 | logger::get()->trace(R"([patchbay] (handle) new metadata: {} (name: "{}"))", global.id, name); 470 | 471 | if (name != "default") 472 | { 473 | return; 474 | } 475 | 476 | metadata = std::make_unique(std::move(data)); 477 | meta_listener = std::make_unique(metadata->listen()); 478 | 479 | logger::get()->info("[patchbay] (handle) found default metadata: {}", global.id); 480 | 481 | meta_listener->on( 482 | [this](const char *key, auto property) 483 | { 484 | if (key) 485 | { 486 | meta_update(key, std::move(property)); 487 | } 488 | 489 | return 0; 490 | }); 491 | 492 | meta_update("default.audio.sink", properties["default.audio.sink"]); 493 | } 494 | 495 | template 496 | void patchbay::impl::bind(const pw::global &global) 497 | { 498 | auto bound = registry->bind(global.id).get(); 499 | 500 | if (!bound.has_value()) 501 | { 502 | logger::get()->warn("[patchbay] (bind) failed to bind {} (\"{}\"): {}", global.id, global.type, 503 | bound.error().message); 504 | return; 505 | } 506 | 507 | handle(bound.value(), global); 508 | } 509 | 510 | void patchbay::impl::del_global(std::uint32_t id) 511 | { 512 | nodes.erase(id); 513 | links.erase(id); 514 | created.erase(id); 515 | } 516 | 517 | void patchbay::impl::add_global(const pw::global &global) 518 | { 519 | logger::get()->trace(R"([patchbay] (add_global) new global: {} (type: "{}"))", global.id, global.type); 520 | 521 | if (global.type == pw::node::type) 522 | { 523 | bind(global); 524 | } 525 | else if (global.type == pw::metadata::type) 526 | { 527 | bind(global); 528 | } 529 | else if (global.type == pw::port::type) 530 | { 531 | bind(global); 532 | } 533 | else if (global.type == pw::link::type) 534 | { 535 | bind(global); 536 | } 537 | } 538 | 539 | template <> 540 | void patchbay::impl::receive(cr_recipe::sender &sender, list_nodes &req) 541 | { 542 | static const std::vector required{"application.name", "node.name"}; 543 | const auto &props = req.props.empty() ? required : req.props; 544 | 545 | auto desireable = [&props](const auto &item) 546 | { 547 | auto item_props = item.second.info.props; 548 | return ranges::all_of(props, [&](const auto &key) { return !item_props[key].empty(); }); 549 | }; 550 | auto can_output = [](const auto &item) 551 | { 552 | return item.second.info.output.max > 0; 553 | }; 554 | auto to_updated_node = [this](const auto &item) 555 | { 556 | /* 557 | * Some nodes update their props (metadata) over time, and to avoid binding the node constantly, 558 | * we simply rebind it to fetch the updates only when needed. 559 | */ 560 | 561 | const auto &[id, data] = item; 562 | auto props = data.info.props; 563 | 564 | logger::get()->trace("[patchbay] (receive): rebinding {}", id); 565 | auto updated = registry->bind(id).get(); 566 | 567 | if (updated.has_value()) 568 | { 569 | props = updated->info().props; 570 | } 571 | else 572 | { 573 | logger::get()->warn(R"([patchbay] (receive) failed to rebind {}: "{}")", id, updated.error().message); 574 | } 575 | 576 | auto rtn = node{props}; 577 | nodes[id].info.props = std::move(props); 578 | 579 | return rtn; 580 | }; 581 | 582 | logger::get()->trace("[patchbay] (receive): listing nodes ({{{}}})", fmt::join(req.props, ",")); 583 | 584 | core->update(); 585 | 586 | auto filtered = nodes // 587 | | ranges::views::filter(desireable) // 588 | | ranges::views::filter(can_output) // 589 | | ranges::to; 590 | 591 | logger::get()->trace("[patchbay] (receive): found {} nodes", filtered.size()); 592 | 593 | auto rtn = filtered // 594 | | ranges::views::transform(to_updated_node) // 595 | | ranges::to; 596 | 597 | sender.send(rtn); 598 | } 599 | 600 | template <> 601 | void patchbay::impl::receive([[maybe_unused]] cr_recipe::sender &, link_options &req) 602 | { 603 | if (!virt_mic) 604 | { 605 | create_mic(); 606 | } 607 | 608 | options = std::move(req); 609 | 610 | reload(); 611 | } 612 | 613 | template <> 614 | void patchbay::impl::receive([[maybe_unused]] cr_recipe::sender &, [[maybe_unused]] unset_target &) 615 | { 616 | cleanup(true); 617 | } 618 | 619 | template <> 620 | void patchbay::impl::receive([[maybe_unused]] cr_recipe::sender &, [[maybe_unused]] quit &) 621 | { 622 | core->context()->loop()->quit(); 623 | } 624 | 625 | template <> 626 | void patchbay::impl::receive([[maybe_unused]] cr_recipe::sender &, [[maybe_unused]] abort &) 627 | { 628 | should_exit = true; 629 | 630 | update_source.request_stop(); 631 | core->context()->loop()->quit(); 632 | } 633 | 634 | void patchbay::impl::start(pw_recipe::receiver receiver, cr_recipe::sender sender) 635 | { 636 | auto loop = pw::main_loop::create(); 637 | auto context = pw::context::create(loop); 638 | 639 | core = context ? pw::core::create(context) : nullptr; 640 | registry = core ? pw::registry::create(core) : std::nullopt; 641 | 642 | if (!core || !registry) 643 | { 644 | logger::get()->error("could not create core or registry"); 645 | sender.send(ready{false}); 646 | return; 647 | } 648 | 649 | receiver.attach(loop, 650 | [this, &sender](T message) 651 | { 652 | logger::get()->trace("[patchbay] (main_loop) received message: {}", glz::type_name); 653 | receive(sender, message); 654 | }); 655 | 656 | auto listener = registry->listen(); 657 | 658 | listener.on([this](std::uint32_t id) { del_global(id); }); 659 | listener.on([this](auto global) { add_global(global); }); 660 | 661 | auto future = core->update(); 662 | update_source = future.stop_source(); 663 | 664 | auto success = future.get(); 665 | sender.send(ready{success.value_or(false)}); 666 | 667 | while (!should_exit) 668 | { 669 | loop->run(); 670 | } 671 | 672 | cleanup(true); 673 | 674 | core->update(); 675 | sender.send(quit{}); 676 | 677 | logger::get()->trace("[patchbay] (main_loop) finished"); 678 | } 679 | } // namespace vencord 680 | -------------------------------------------------------------------------------- /tests/node/api.test.js: -------------------------------------------------------------------------------- 1 | const venmic = require("../../lib"); 2 | const assert = require("assert"); 3 | 4 | let patchbay = null; 5 | 6 | try 7 | { 8 | patchbay = new venmic.PatchBay(); 9 | } 10 | catch (error) 11 | { 12 | console.warn("No PipeWire Server available"); 13 | 14 | assert(!venmic.PatchBay.hasPipeWire()); 15 | assert.throws(() => new venmic.PatchBay(), /failed to create patchbay/ig); 16 | 17 | process.exit(0); 18 | } 19 | 20 | assert(Array.isArray(patchbay.list())); 21 | assert(Array.isArray(patchbay.list(["node.name"]))); 22 | 23 | assert.throws(() => patchbay.list({}), /expected list of strings/ig); 24 | assert.throws(() => patchbay.list([10]), /expected list of strings/ig); 25 | 26 | assert.throws(() => patchbay.link(10), /expected link object/ig); 27 | 28 | assert.throws(() => patchbay.link({ }), /'include' or 'exclude'/ig); 29 | assert.throws(() => patchbay.link({ a: "A", b: "B", c: "C" }), /'include' or 'exclude'/ig); 30 | assert.throws(() => patchbay.link({ "node.name": "Firefox", mode: "gibberish" }), /'include' or 'exclude'/ig); 31 | 32 | assert.throws(() => patchbay.link({ include: "Firefox" }), /key-value/ig); 33 | assert.throws(() => patchbay.link({ include: {} }), /key-value/ig); 34 | 35 | assert.doesNotThrow(() => patchbay.link({ include: [{ "node.name": "Firefox" }] })); 36 | assert.doesNotThrow(() => patchbay.link({ exclude: [{ "node.name": "Firefox" }] })); 37 | 38 | assert.doesNotThrow(() => patchbay.link({ include: [{ "node.name": "Firefox" }], exclude: [{ "object.id": "100" }] })); 39 | 40 | assert.doesNotThrow(() => patchbay.link({ exclude: [{ "node.name": "Firefox" }], ignore_devices: true })); 41 | assert.doesNotThrow(() => patchbay.link({ exclude: [{ "node.name": "Firefox" }], ignore_devices: true, only_default_speakers: true })); 42 | 43 | assert.doesNotThrow(() => patchbay.unlink()); 44 | --------------------------------------------------------------------------------