mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-11 00:05:36 +02:00
Compare commits
3 Commits
480370ecae
...
d3bc1d17e2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3bc1d17e2 | ||
|
|
32341574dc | ||
|
|
c62a22b552 |
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -3,13 +3,13 @@
|
||||
/apps/ @victorpahuus @AquaWolf
|
||||
/packages/ui/ @wanjohiryan @victorpahuus @AquaWolf
|
||||
|
||||
/protobuf/ @AquaWolf
|
||||
/protobufs/ @AquaWolf @DatCaptainHorse
|
||||
|
||||
/infra/ @wanjohiryan
|
||||
/packages/core/ @wanjohiryan
|
||||
/packages/functions/ @wanjohiryan
|
||||
|
||||
/containers/ @DatCaptainHorse
|
||||
/containerfiles/ @DatCaptainHorse
|
||||
/packages/server/ @DatCaptainHorse
|
||||
/packages/relay/ @DatCaptainHorse
|
||||
/packages/scripts/ @DatCaptainHorse
|
||||
|
||||
48
.github/workflows/docker-bake.hcl
vendored
Normal file
48
.github/workflows/docker-bake.hcl
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
variable "BASE_IMAGE" {
|
||||
default = "docker.io/cachyos/cachyos:latest"
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = ["runner"]
|
||||
}
|
||||
|
||||
target "runner-base" {
|
||||
dockerfile = "containerfiles/runner-base.Containerfile"
|
||||
context = "."
|
||||
args = {
|
||||
BASE_IMAGE = "${BASE_IMAGE}"
|
||||
}
|
||||
cache-from = ["type=gha,scope=runner-base-pr"]
|
||||
cache-to = ["type=gha,scope=runner-base-pr,mode=max"]
|
||||
tags = ["runner-base:latest"]
|
||||
}
|
||||
|
||||
target "runner-builder" {
|
||||
dockerfile = "containerfiles/runner-builder.Containerfile"
|
||||
context = "."
|
||||
args = {
|
||||
RUNNER_BASE_IMAGE = "runner-base:latest"
|
||||
}
|
||||
cache-from = ["type=gha,scope=runner-builder-pr"]
|
||||
cache-to = ["type=gha,scope=runner-builder-pr,mode=max"]
|
||||
tags = ["runner-builder:latest"]
|
||||
contexts = {
|
||||
runner-base = "target:runner-base"
|
||||
}
|
||||
}
|
||||
|
||||
target "runner" {
|
||||
dockerfile = "containerfiles/runner.Containerfile"
|
||||
context = "."
|
||||
args = {
|
||||
RUNNER_BASE_IMAGE = "runner-base:latest"
|
||||
RUNNER_BUILDER_IMAGE = "runner-builder:latest"
|
||||
}
|
||||
cache-from = ["type=gha,scope=runner-pr"]
|
||||
cache-to = ["type=gha,scope=runner-pr,mode=max"]
|
||||
tags = ["nestri-runner"]
|
||||
contexts = {
|
||||
runner-base = "target:runner-base"
|
||||
runner-builder = "target:runner-builder"
|
||||
}
|
||||
}
|
||||
101
.github/workflows/runner.yml
vendored
101
.github/workflows/runner.yml
vendored
@@ -1,11 +1,11 @@
|
||||
#Tabs not spaces, you moron :)
|
||||
|
||||
name: Build nestri:runner
|
||||
name: Build nestri-runner
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "containerfiles/runner.Containerfile"
|
||||
- "containerfiles/runner*.Containerfile"
|
||||
- "packages/scripts/**"
|
||||
- "packages/server/**"
|
||||
- ".github/workflows/runner.yml"
|
||||
@@ -14,7 +14,7 @@ on:
|
||||
push:
|
||||
branches: [dev, production]
|
||||
paths:
|
||||
- "containerfiles/runner.Containerfile"
|
||||
- "containerfiles/runner*.Containerfile"
|
||||
- ".github/workflows/runner.yml"
|
||||
- "packages/scripts/**"
|
||||
- "packages/server/**"
|
||||
@@ -26,7 +26,6 @@ on:
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: nestrilabs/nestri
|
||||
BASE_TAG_PREFIX: runner
|
||||
BASE_IMAGE: docker.io/cachyos/cachyos:latest
|
||||
|
||||
# This makes our release ci quit prematurely
|
||||
@@ -36,43 +35,46 @@ env:
|
||||
|
||||
jobs:
|
||||
build-docker-pr:
|
||||
name: Build image on PR
|
||||
name: Build images on PR
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
-
|
||||
name: Set Swap Space
|
||||
uses: pierotofy/set-swap-space@master
|
||||
with:
|
||||
swap-size-gb: 20
|
||||
-
|
||||
name: Build Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
-
|
||||
name: Build images using bake
|
||||
uses: docker/bake-action@v6
|
||||
env:
|
||||
BASE_IMAGE: ${{ env.BASE_IMAGE }}
|
||||
with:
|
||||
file: containerfiles/runner.Containerfile
|
||||
context: ./
|
||||
files: |
|
||||
./.github/workflows/docker-bake.hcl
|
||||
targets: runner
|
||||
push: false
|
||||
load: true
|
||||
tags: nestri:runner
|
||||
cache-from: type=gha,mode=max
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
build-and-push-docker:
|
||||
name: Build and push image
|
||||
name: Build and push images
|
||||
if: ${{ github.ref == 'refs/heads/production' || github.ref == 'refs/heads/dev' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
variant:
|
||||
- { suffix: "", base: "docker.io/cachyos/cachyos:latest" }
|
||||
- { suffix: "-v3", base: "docker.io/cachyos/cachyos-v3:latest" }
|
||||
#- { suffix: "-v4", base: "docker.io/cachyos/cachyos-v4:latest" } # Disabled until GHA has this
|
||||
steps:
|
||||
-
|
||||
name: Checkout repo
|
||||
@@ -85,21 +87,19 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
-
|
||||
name: Extract Container metadata
|
||||
id: meta
|
||||
name: Extract runner metadata
|
||||
id: meta-runner
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ env.BASE_TAG_PREFIX }}
|
||||
#
|
||||
#tag on release, and a nightly build for 'dev'
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner
|
||||
tags: |
|
||||
type=raw,value=nightly,enable={{is_default_branch}}
|
||||
type=raw,value={{branch}}
|
||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'production') }}
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
-
|
||||
type=raw,value=nightly${{ matrix.variant.suffix }},enable={{is_default_branch}}
|
||||
type=raw,value={{branch}}${{ matrix.variant.suffix }}
|
||||
type=raw,value=latest${{ matrix.variant.suffix }},enable=${{ github.ref == format('refs/heads/{0}', 'production') }}
|
||||
type=semver,pattern={{version}}${{ matrix.variant.suffix }}
|
||||
type=semver,pattern={{major}}.{{minor}}${{ matrix.variant.suffix }}
|
||||
type=semver,pattern={{major}}${{ matrix.variant.suffix }}
|
||||
-
|
||||
name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
@@ -108,14 +108,41 @@ jobs:
|
||||
with:
|
||||
swap-size-gb: 20
|
||||
-
|
||||
name: Build Docker image
|
||||
name: Build and push runner-base image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: containerfiles/runner.Containerfile
|
||||
file: containerfiles/runner-base.Containerfile
|
||||
context: ./
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha,mode=max
|
||||
cache-to: type=gha,mode=max
|
||||
pull: ${{ github.event_name == 'schedule' }} # Pull base image for scheduled builds
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner-base:latest${{ matrix.variant.suffix }}
|
||||
build-args: |
|
||||
BASE_IMAGE=${{ matrix.variant.base }}
|
||||
cache-from: type=gha,scope=runner-base${{ matrix.variant.suffix }},mode=max
|
||||
cache-to: type=gha,scope=runner-base${{ matrix.variant.suffix }},mode=max
|
||||
pull: ${{ github.event_name == 'schedule' }}
|
||||
-
|
||||
name: Build and push runner-builder image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: containerfiles/runner-builder.Containerfile
|
||||
context: ./
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner-builder:latest${{ matrix.variant.suffix }}
|
||||
build-args: |
|
||||
RUNNER_BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner-base:latest${{ matrix.variant.suffix }}
|
||||
cache-from: type=gha,scope=runner-builder${{ matrix.variant.suffix }},mode=max
|
||||
cache-to: type=gha,scope=runner-builder${{ matrix.variant.suffix }},mode=max
|
||||
-
|
||||
name: Build and push runner image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: containerfiles/runner.Containerfile
|
||||
context: ./
|
||||
push: true
|
||||
tags: ${{ steps.meta-runner.outputs.tags }}
|
||||
labels: ${{ steps.meta-runner.outputs.labels }}
|
||||
build-args: |
|
||||
RUNNER_BASE_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner-base:latest${{ matrix.variant.suffix }}
|
||||
RUNNER_BUILDER_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/runner-builder:latest${{ matrix.variant.suffix }}
|
||||
cache-from: type=gha,scope=runner${{ matrix.variant.suffix }},mode=max
|
||||
cache-to: type=gha,scope=runner${{ matrix.variant.suffix }},mode=max
|
||||
|
||||
@@ -3,7 +3,6 @@ FROM docker.io/node:24-alpine AS base
|
||||
FROM base AS build
|
||||
WORKDIR /usr/src/app
|
||||
COPY package.json ./
|
||||
COPY patches ./patches
|
||||
COPY packages/input ./packages/input
|
||||
COPY packages/play-standalone ./packages/play-standalone
|
||||
RUN cd packages/play-standalone && npm install && npm run build
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM docker.io/golang:1.24-alpine AS go-build
|
||||
FROM docker.io/golang:1.25-alpine AS go-build
|
||||
WORKDIR /builder
|
||||
COPY packages/relay/ /builder/
|
||||
RUN go build
|
||||
|
||||
FROM docker.io/golang:1.24-alpine
|
||||
FROM docker.io/golang:1.25-alpine
|
||||
COPY --from=go-build /builder/relay /relay/relay
|
||||
WORKDIR /relay
|
||||
|
||||
@@ -22,8 +22,4 @@ ENV WEBRTC_NAT_IPS=""
|
||||
ENV AUTO_ADD_LOCAL_IP=true
|
||||
ENV PERSIST_DIR="./persist-data"
|
||||
|
||||
EXPOSE $ENDPOINT_PORT
|
||||
EXPOSE $WEBRTC_UDP_START-$WEBRTC_UDP_END/udp
|
||||
EXPOSE $WEBRTC_UDP_MUX/udp
|
||||
|
||||
ENTRYPOINT ["/relay/relay"]
|
||||
13
containerfiles/runner-base.Containerfile
Normal file
13
containerfiles/runner-base.Containerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
# Container build arguments #
|
||||
ARG BASE_IMAGE=docker.io/cachyos/cachyos:latest
|
||||
|
||||
#*******************************************#
|
||||
# Base Stage - Simple with light essentials #
|
||||
#*******************************************#
|
||||
FROM ${BASE_IMAGE} AS bases
|
||||
|
||||
# Only lightweight stuff needed by both builder and runtime
|
||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
pacman -Sy --noconfirm \
|
||||
libssh2 curl wget libevdev libc++abi \
|
||||
gstreamer gst-plugins-base
|
||||
218
containerfiles/runner-builder.Containerfile
Normal file
218
containerfiles/runner-builder.Containerfile
Normal file
@@ -0,0 +1,218 @@
|
||||
# Container build arguments #
|
||||
ARG RUNNER_BASE_IMAGE=runner-base:latest
|
||||
|
||||
#**************#
|
||||
# builder base #
|
||||
#**************#
|
||||
FROM ${RUNNER_BASE_IMAGE} AS base-builder
|
||||
|
||||
ENV ARTIFACTS=/artifacts
|
||||
RUN mkdir -p "${ARTIFACTS}"
|
||||
|
||||
# Environment setup for Rust and Cargo
|
||||
ENV CARGO_HOME=/usr/local/cargo \
|
||||
PATH="${CARGO_HOME}/bin:${PATH}"
|
||||
|
||||
# Install build essentials and caching tools
|
||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
pacman -Sy --noconfirm rustup git base-devel mold \
|
||||
meson pkgconf cmake git gcc make
|
||||
|
||||
# Override various linker with symlink so mold is forcefully used (ld, ld.lld, lld)
|
||||
RUN ln -sf /usr/bin/mold /usr/bin/ld && \
|
||||
ln -sf /usr/bin/mold /usr/bin/ld.lld && \
|
||||
ln -sf /usr/bin/mold /usr/bin/lld
|
||||
|
||||
# Install latest Rust using rustup
|
||||
RUN rustup default stable
|
||||
|
||||
# Install cargo-chef with proper caching
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
cargo install -j $(nproc) cargo-chef --locked
|
||||
|
||||
#*******************************#
|
||||
# vimputti manager build stages #
|
||||
#*******************************#
|
||||
FROM base-builder AS vimputti-manager-deps
|
||||
WORKDIR /builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
pacman -Sy --noconfirm lib32-gcc-libs
|
||||
|
||||
# Clone repository
|
||||
RUN git clone --depth 1 --rev "9e8bfd0217eeab011c5afc368d3ea67a4c239e81" https://github.com/DatCaptainHorse/vimputti.git
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
FROM vimputti-manager-deps AS vimputti-manager-planner
|
||||
WORKDIR /builder/vimputti
|
||||
|
||||
# Prepare recipe for dependency caching
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
FROM vimputti-manager-deps AS vimputti-manager-cached-builder
|
||||
WORKDIR /builder/vimputti
|
||||
|
||||
COPY --from=vimputti-manager-planner /builder/vimputti/recipe.json .
|
||||
|
||||
# Cache dependencies using cargo-chef
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
cargo chef cook --release --recipe-path recipe.json
|
||||
|
||||
ENV CARGO_TARGET_DIR=/builder/target
|
||||
COPY --from=vimputti-manager-planner /builder/vimputti/ .
|
||||
|
||||
# Build and install directly to artifacts
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
--mount=type=cache,target=/builder/target \
|
||||
cargo build --release --package vimputti-manager && \
|
||||
cargo build --release --package vimputti-shim && \
|
||||
rustup target add i686-unknown-linux-gnu && \
|
||||
cargo build --release --package vimputti-shim --target i686-unknown-linux-gnu && \
|
||||
cp "${CARGO_TARGET_DIR}/release/vimputti-manager" "${ARTIFACTS}" && \
|
||||
cp "${CARGO_TARGET_DIR}/release/libvimputti_shim.so" "${ARTIFACTS}/libvimputti_shim_64.so" && \
|
||||
cp "${CARGO_TARGET_DIR}/i686-unknown-linux-gnu/release/libvimputti_shim.so" "${ARTIFACTS}/libvimputti_shim_32.so"
|
||||
|
||||
#****************************#
|
||||
# nestri-server build stages #
|
||||
#****************************#
|
||||
FROM base-builder AS nestri-server-deps
|
||||
WORKDIR /builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
pacman -Sy --noconfirm gst-plugins-good gst-plugin-rswebrtc
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
FROM nestri-server-deps AS nestri-server-planner
|
||||
WORKDIR /builder/nestri
|
||||
|
||||
COPY packages/server/Cargo.toml packages/server/Cargo.lock ./
|
||||
|
||||
# Prepare recipe for dependency caching
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
FROM nestri-server-deps AS nestri-server-cached-builder
|
||||
WORKDIR /builder/nestri
|
||||
|
||||
COPY --from=nestri-server-planner /builder/nestri/recipe.json .
|
||||
|
||||
# Cache dependencies using cargo-chef
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
cargo chef cook --release --recipe-path recipe.json
|
||||
|
||||
|
||||
ENV CARGO_TARGET_DIR=/builder/target
|
||||
COPY packages/server/ ./
|
||||
|
||||
# Build and install directly to artifacts
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
--mount=type=cache,target=/builder/target \
|
||||
cargo build --release && \
|
||||
cp "${CARGO_TARGET_DIR}/release/nestri-server" "${ARTIFACTS}"
|
||||
|
||||
#**********************************#
|
||||
# gst-wayland-display build stages #
|
||||
#**********************************#
|
||||
FROM base-builder AS gst-wayland-deps
|
||||
WORKDIR /builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
pacman -Sy --noconfirm libxkbcommon wayland \
|
||||
gst-plugins-good gst-plugins-bad libinput
|
||||
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
cargo install cargo-c
|
||||
|
||||
# Grab cudart from NVIDIA..
|
||||
RUN wget https://developer.download.nvidia.com/compute/cuda/redist/cuda_cudart/linux-x86_64/cuda_cudart-linux-x86_64-13.0.96-archive.tar.xz -O cuda_cudart.tar.xz && \
|
||||
mkdir cuda_cudart && tar -xf cuda_cudart.tar.xz -C cuda_cudart --strip-components=1 && \
|
||||
cp cuda_cudart/lib/libcudart.so cuda_cudart/lib/libcudart.so.* /usr/lib/ && \
|
||||
rm -r cuda_cudart && \
|
||||
rm cuda_cudart.tar.xz
|
||||
|
||||
# Grab cuda lib from NVIDIA (it's in driver package of all things..)
|
||||
RUN wget https://developer.download.nvidia.com/compute/cuda/redist/nvidia_driver/linux-x86_64/nvidia_driver-linux-x86_64-580.95.05-archive.tar.xz -O nvidia_driver.tar.xz && \
|
||||
mkdir nvidia_driver && tar -xf nvidia_driver.tar.xz -C nvidia_driver --strip-components=1 && \
|
||||
cp nvidia_driver/lib/libcuda.so.* /usr/lib/libcuda.so && \
|
||||
ln -s /usr/lib/libcuda.so /usr/lib/libcuda.so.1 && \
|
||||
rm -r nvidia_driver && \
|
||||
rm nvidia_driver.tar.xz
|
||||
|
||||
# Clone repository
|
||||
RUN git clone --depth 1 --rev "afa853fa03e8403c83bbb3bc0cf39147ad46c266" https://github.com/games-on-whales/gst-wayland-display.git
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
FROM gst-wayland-deps AS gst-wayland-planner
|
||||
WORKDIR /builder/gst-wayland-display
|
||||
|
||||
# Prepare recipe for dependency caching
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
FROM gst-wayland-deps AS gst-wayland-cached-builder
|
||||
WORKDIR /builder/gst-wayland-display
|
||||
|
||||
COPY --from=gst-wayland-planner /builder/gst-wayland-display/recipe.json .
|
||||
|
||||
# Cache dependencies using cargo-chef
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
cargo chef cook --release --recipe-path recipe.json
|
||||
|
||||
|
||||
ENV CARGO_TARGET_DIR=/builder/target
|
||||
|
||||
COPY --from=gst-wayland-planner /builder/gst-wayland-display/ .
|
||||
|
||||
# Build and install directly to artifacts
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
--mount=type=cache,target=/builder/target \
|
||||
cargo cinstall --prefix=${ARTIFACTS} --release
|
||||
|
||||
#*********************************#
|
||||
# Patched bubblewrap build stages #
|
||||
#*********************************#
|
||||
FROM base-builder AS bubblewrap-deps
|
||||
WORKDIR /builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
pacman -Sy --noconfirm libtool libcap libselinux
|
||||
|
||||
# Copy patch file from host
|
||||
COPY packages/patches/bubblewrap/ /builder/patches/
|
||||
|
||||
# Clone repository
|
||||
RUN git clone --depth 1 --rev "9ca3b05ec787acfb4b17bed37db5719fa777834f" https://github.com/containers/bubblewrap.git && \
|
||||
cd bubblewrap && \
|
||||
# Apply patch to fix user namespace issue
|
||||
git apply ../patches/bubbleunheck.patch
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
FROM bubblewrap-deps AS bubblewrap-builder
|
||||
WORKDIR /builder/bubblewrap
|
||||
|
||||
# Build and install directly to artifacts
|
||||
RUN meson setup build --prefix=${ARTIFACTS} && \
|
||||
meson compile -C build && \
|
||||
meson install -C build
|
||||
|
||||
#*********************************************#
|
||||
# Final Export Stage - Collects all artifacts #
|
||||
#*********************************************#
|
||||
FROM scratch AS artifacts
|
||||
|
||||
COPY --from=nestri-server-cached-builder /artifacts/nestri-server /artifacts/bin/
|
||||
COPY --from=gst-wayland-cached-builder /artifacts/lib/ /artifacts/lib/
|
||||
COPY --from=gst-wayland-cached-builder /artifacts/include/ /artifacts/include/
|
||||
COPY --from=vimputti-manager-cached-builder /artifacts/vimputti-manager /artifacts/bin/
|
||||
COPY --from=vimputti-manager-cached-builder /artifacts/libvimputti_shim_64.so /artifacts/lib64/libvimputti_shim.so
|
||||
COPY --from=vimputti-manager-cached-builder /artifacts/libvimputti_shim_32.so /artifacts/lib32/libvimputti_shim.so
|
||||
COPY --from=gst-wayland-deps /usr/lib/libcuda.so /usr/lib/libcuda.so.* /artifacts/lib/
|
||||
COPY --from=bubblewrap-builder /artifacts/bin/bwrap /artifacts/bin/
|
||||
@@ -1,125 +1,13 @@
|
||||
# Container build arguments #
|
||||
ARG BASE_IMAGE=docker.io/cachyos/cachyos:latest
|
||||
ARG RUNNER_BASE_IMAGE=runner-base:latest
|
||||
ARG RUNNER_BUILDER_IMAGE=runner-builder:latest
|
||||
|
||||
#******************************************************************************
|
||||
# Base Stage - Updates system packages
|
||||
#******************************************************************************
|
||||
FROM ${BASE_IMAGE} AS base
|
||||
|
||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
pacman --noconfirm -Syu
|
||||
|
||||
#******************************************************************************
|
||||
# Base Builder Stage - Prepares core build environment
|
||||
#******************************************************************************
|
||||
FROM base AS base-builder
|
||||
|
||||
# Environment setup for Rust and Cargo
|
||||
ENV CARGO_HOME=/usr/local/cargo \
|
||||
ARTIFACTS=/artifacts \
|
||||
PATH="${CARGO_HOME}/bin:${PATH}" \
|
||||
RUSTFLAGS="-C link-arg=-fuse-ld=mold"
|
||||
|
||||
# Install build essentials and caching tools
|
||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
pacman -Sy --noconfirm mold rustup && \
|
||||
mkdir -p "${ARTIFACTS}"
|
||||
|
||||
# Install latest Rust using rustup
|
||||
RUN rustup default stable
|
||||
|
||||
# Install cargo-chef with proper caching
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
cargo install -j $(nproc) cargo-chef cargo-c --locked
|
||||
|
||||
#******************************************************************************
|
||||
# Nestri Server Build Stages
|
||||
#******************************************************************************
|
||||
FROM base-builder AS nestri-server-deps
|
||||
WORKDIR /builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
pacman -Sy --noconfirm meson pkgconf cmake git gcc make \
|
||||
gstreamer gst-plugins-base gst-plugins-good gst-plugin-rswebrtc
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
FROM nestri-server-deps AS nestri-server-planner
|
||||
WORKDIR /builder/nestri
|
||||
|
||||
COPY packages/server/Cargo.toml packages/server/Cargo.lock ./
|
||||
|
||||
# Prepare recipe for dependency caching
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
FROM nestri-server-deps AS nestri-server-cached-builder
|
||||
WORKDIR /builder/nestri
|
||||
|
||||
COPY --from=nestri-server-planner /builder/nestri/recipe.json .
|
||||
|
||||
# Cache dependencies using cargo-chef
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
cargo chef cook --release --recipe-path recipe.json
|
||||
|
||||
|
||||
ENV CARGO_TARGET_DIR=/builder/target
|
||||
|
||||
COPY packages/server/ ./
|
||||
|
||||
# Build and install directly to artifacts
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
--mount=type=cache,target=/builder/target \
|
||||
cargo build --release && \
|
||||
cp "${CARGO_TARGET_DIR}/release/nestri-server" "${ARTIFACTS}"
|
||||
|
||||
#******************************************************************************
|
||||
# GST-Wayland Plugin Build Stages
|
||||
#******************************************************************************
|
||||
FROM base-builder AS gst-wayland-deps
|
||||
WORKDIR /builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
pacman -Sy --noconfirm meson pkgconf cmake git gcc make \
|
||||
libxkbcommon wayland gstreamer gst-plugins-base gst-plugins-good libinput
|
||||
|
||||
# Clone repository
|
||||
RUN git clone --depth 1 --rev "dfeebb19b48f32207469e166a3955f5d65b5e6c6" https://github.com/games-on-whales/gst-wayland-display.git
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
FROM gst-wayland-deps AS gst-wayland-planner
|
||||
WORKDIR /builder/gst-wayland-display
|
||||
|
||||
# Prepare recipe for dependency caching
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
FROM gst-wayland-deps AS gst-wayland-cached-builder
|
||||
WORKDIR /builder/gst-wayland-display
|
||||
|
||||
COPY --from=gst-wayland-planner /builder/gst-wayland-display/recipe.json .
|
||||
|
||||
# Cache dependencies using cargo-chef
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
cargo chef cook --release --recipe-path recipe.json
|
||||
|
||||
|
||||
ENV CARGO_TARGET_DIR=/builder/target
|
||||
|
||||
COPY --from=gst-wayland-planner /builder/gst-wayland-display/ .
|
||||
|
||||
# Build and install directly to artifacts
|
||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||
--mount=type=cache,target=/builder/target \
|
||||
cargo cinstall --prefix=${ARTIFACTS} --release
|
||||
|
||||
#******************************************************************************
|
||||
# Final Runtime Stage
|
||||
#******************************************************************************
|
||||
FROM base AS runtime
|
||||
#*********************#
|
||||
# Final Runtime Stage #
|
||||
#*********************#
|
||||
FROM ${RUNNER_BASE_IMAGE} AS runtime
|
||||
FROM ${RUNNER_BUILDER_IMAGE} AS builder
|
||||
FROM runtime
|
||||
|
||||
### Package Installation ###
|
||||
# Core system components
|
||||
@@ -127,20 +15,17 @@ RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
pacman -Sy --needed --noconfirm \
|
||||
vulkan-intel lib32-vulkan-intel vpl-gpu-rt \
|
||||
vulkan-radeon lib32-vulkan-radeon \
|
||||
mesa steam-native-runtime proton-cachyos lib32-mesa \
|
||||
mesa lib32-mesa \
|
||||
steam gtk3 lib32-gtk3 \
|
||||
sudo xorg-xwayland seatd libinput gamescope mangohud wlr-randr \
|
||||
libssh2 curl wget \
|
||||
pipewire pipewire-pulse pipewire-alsa wireplumber \
|
||||
noto-fonts-cjk supervisor jq chwd lshw pacman-contrib \
|
||||
noto-fonts-cjk supervisor jq pacman-contrib \
|
||||
hwdata openssh \
|
||||
# GStreamer stack
|
||||
gstreamer gst-plugins-base gst-plugins-good \
|
||||
gst-plugins-good \
|
||||
gst-plugins-bad gst-plugin-pipewire \
|
||||
gst-plugin-webrtchttp gst-plugin-rswebrtc gst-plugin-rsrtp \
|
||||
gst-plugin-va gst-plugin-qsv \
|
||||
# lib32 GStreamer stack to fix some games with videos
|
||||
lib32-gstreamer lib32-gst-plugins-base lib32-gst-plugins-good && \
|
||||
gst-plugin-va gst-plugin-qsv && \
|
||||
# Cleanup
|
||||
paccache -rk1 && \
|
||||
rm -rf /usr/share/{info,man,doc}/*
|
||||
@@ -153,6 +38,7 @@ ENV NESTRI_USER="nestri" \
|
||||
NESTRI_LANG=en_US.UTF-8 \
|
||||
NESTRI_XDG_RUNTIME_DIR=/run/user/1000 \
|
||||
NESTRI_HOME=/home/nestri \
|
||||
NESTRI_VIMPUTTI_PATH=/tmp/vimputti-1000 \
|
||||
NVIDIA_DRIVER_CAPABILITIES=all
|
||||
|
||||
RUN mkdir -p "/home/${NESTRI_USER}" && \
|
||||
@@ -174,29 +60,33 @@ RUN mkdir -p /run/dbus && \
|
||||
-e '/wants = \[/{s/hooks\.node\.suspend\s*//; s/,\s*\]/]/}' \
|
||||
/usr/share/wireplumber/wireplumber.conf
|
||||
|
||||
### Audio Systems Configs - Latency optimizations + Loopback ###
|
||||
## Audio Systems Configs - Latency optimizations + Loopback ##
|
||||
RUN mkdir -p /etc/pipewire/pipewire.conf.d && \
|
||||
mkdir -p /etc/wireplumber/wireplumber.conf.d
|
||||
|
||||
COPY packages/configs/wireplumber.conf.d/* /etc/wireplumber/wireplumber.conf.d/
|
||||
COPY packages/configs/pipewire.conf.d/* /etc/pipewire/pipewire.conf.d/
|
||||
|
||||
## Steam Configs - Proton (CachyOS flavor) ##
|
||||
## Steam Configs - Proton (Experimental flavor) ##
|
||||
RUN mkdir -p "${NESTRI_HOME}/.local/share/Steam/config"
|
||||
|
||||
COPY packages/configs/steam/config.vdf "${NESTRI_HOME}/.local/share/Steam/config/"
|
||||
|
||||
### Artifacts and Verification ###
|
||||
COPY --from=nestri-server-cached-builder /artifacts/nestri-server /usr/bin/
|
||||
COPY --from=gst-wayland-cached-builder /artifacts/lib/ /usr/lib/
|
||||
COPY --from=gst-wayland-cached-builder /artifacts/include/ /usr/include/
|
||||
RUN which nestri-server && ls -la /usr/lib/ | grep 'gstwaylanddisplay'
|
||||
### Artifacts from Builder ###
|
||||
COPY --from=builder /artifacts/bin/nestri-server /usr/bin/
|
||||
COPY --from=builder /artifacts/bin/bwrap /usr/bin/
|
||||
COPY --from=builder /artifacts/lib/ /usr/lib/
|
||||
COPY --from=builder /artifacts/lib32/ /usr/lib32/
|
||||
COPY --from=builder /artifacts/lib64/ /usr/lib64/
|
||||
COPY --from=builder /artifacts/bin/vimputti-manager /usr/bin/
|
||||
|
||||
### Scripts and Final Configuration ###
|
||||
COPY packages/scripts/ /etc/nestri/
|
||||
RUN chmod +x /etc/nestri/{envs.sh,entrypoint*.sh} && \
|
||||
chown -R "${NESTRI_USER}:${NESTRI_USER}" "${NESTRI_HOME}" && \
|
||||
sed -i 's/^#\(en_US\.UTF-8\)/\1/' /etc/locale.gen && \
|
||||
setcap cap_net_admin+ep /usr/bin/vimputti-manager && \
|
||||
dbus-uuidgen > /etc/machine-id && \
|
||||
LANG=en_US.UTF-8 locale-gen
|
||||
|
||||
# Root for most container engines, nestri-user compatible for apptainer without fakeroot
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{
|
||||
"0"
|
||||
{
|
||||
"name" "proton-cachyos"
|
||||
"name" "proton_experimental"
|
||||
"config" ""
|
||||
"priority" "75"
|
||||
}
|
||||
|
||||
@@ -7,21 +7,23 @@
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@bufbuild/buf": "^1.50.0",
|
||||
"@bufbuild/protoc-gen-es": "^2.2.3"
|
||||
"@bufbuild/buf": "^1.57.2",
|
||||
"@bufbuild/protoc-gen-es": "^2.9.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.2.3",
|
||||
"@chainsafe/libp2p-noise": "^16.1.3",
|
||||
"@chainsafe/libp2p-yamux": "^7.0.1",
|
||||
"@libp2p/identify": "^3.0.32",
|
||||
"@libp2p/interface": "^2.10.2",
|
||||
"@libp2p/ping": "^2.0.32",
|
||||
"@libp2p/websockets": "^9.2.13",
|
||||
"@multiformats/multiaddr": "^12.4.0",
|
||||
"@bufbuild/protobuf": "^2.9.0",
|
||||
"@chainsafe/libp2p-noise": "^16.1.4",
|
||||
"@chainsafe/libp2p-quic": "^1.1.3",
|
||||
"@chainsafe/libp2p-yamux": "^7.0.4",
|
||||
"@libp2p/identify": "^3.0.39",
|
||||
"@libp2p/interface": "^2.11.0",
|
||||
"@libp2p/ping": "^2.0.37",
|
||||
"@libp2p/websockets": "^9.2.19",
|
||||
"@libp2p/webtransport": "^5.0.51",
|
||||
"@multiformats/multiaddr": "^12.5.1",
|
||||
"it-length-prefixed": "^10.0.1",
|
||||
"it-pipe": "^3.0.1",
|
||||
"libp2p": "^2.8.8",
|
||||
"libp2p": "^2.10.0",
|
||||
"uint8arraylist": "^2.4.8",
|
||||
"uint8arrays": "^5.1.0"
|
||||
}
|
||||
|
||||
@@ -1,113 +1,133 @@
|
||||
export const keyCodeToLinuxEventCode: { [key: string]: number } = {
|
||||
'KeyA': 30,
|
||||
'KeyB': 48,
|
||||
'KeyC': 46,
|
||||
'KeyD': 32,
|
||||
'KeyE': 18,
|
||||
'KeyF': 33,
|
||||
'KeyG': 34,
|
||||
'KeyH': 35,
|
||||
'KeyI': 23,
|
||||
'KeyJ': 36,
|
||||
'KeyK': 37,
|
||||
'KeyL': 38,
|
||||
'KeyM': 50,
|
||||
'KeyN': 49,
|
||||
'KeyO': 24,
|
||||
'KeyP': 25,
|
||||
'KeyQ': 16,
|
||||
'KeyR': 19,
|
||||
'KeyS': 31,
|
||||
'KeyT': 20,
|
||||
'KeyU': 22,
|
||||
'KeyV': 47,
|
||||
'KeyW': 17,
|
||||
'KeyX': 45,
|
||||
'KeyY': 21,
|
||||
'KeyZ': 44,
|
||||
'Digit1': 2,
|
||||
'Digit2': 3,
|
||||
'Digit3': 4,
|
||||
'Digit4': 5,
|
||||
'Digit5': 6,
|
||||
'Digit6': 7,
|
||||
'Digit7': 8,
|
||||
'Digit8': 9,
|
||||
'Digit9': 10,
|
||||
'Digit0': 11,
|
||||
'Enter': 28,
|
||||
'Escape': 1,
|
||||
'Backspace': 14,
|
||||
'Tab': 15,
|
||||
'Space': 57,
|
||||
'Minus': 12,
|
||||
'Equal': 13,
|
||||
'BracketLeft': 26,
|
||||
'BracketRight': 27,
|
||||
'Backslash': 43,
|
||||
'Semicolon': 39,
|
||||
'Quote': 40,
|
||||
'Backquote': 41,
|
||||
'Comma': 51,
|
||||
'Period': 52,
|
||||
'Slash': 53,
|
||||
'CapsLock': 58,
|
||||
'F1': 59,
|
||||
'F2': 60,
|
||||
'F3': 61,
|
||||
'F4': 62,
|
||||
'F5': 63,
|
||||
'F6': 64,
|
||||
'F7': 65,
|
||||
'F8': 66,
|
||||
'F9': 67,
|
||||
'F10': 68,
|
||||
'F11': 87,
|
||||
'F12': 88,
|
||||
'Insert': 110,
|
||||
'Delete': 111,
|
||||
'ArrowUp': 103,
|
||||
'ArrowDown': 108,
|
||||
'ArrowLeft': 105,
|
||||
'ArrowRight': 106,
|
||||
'Home': 102,
|
||||
'End': 107,
|
||||
'PageUp': 104,
|
||||
'PageDown': 109,
|
||||
'NumLock': 69,
|
||||
'ScrollLock': 70,
|
||||
'Pause': 119,
|
||||
'Numpad0': 82,
|
||||
'Numpad1': 79,
|
||||
'Numpad2': 80,
|
||||
'Numpad3': 81,
|
||||
'Numpad4': 75,
|
||||
'Numpad5': 76,
|
||||
'Numpad6': 77,
|
||||
'Numpad7': 71,
|
||||
'Numpad8': 72,
|
||||
'Numpad9': 73,
|
||||
'NumpadDivide': 98,
|
||||
'NumpadMultiply': 55,
|
||||
'NumpadSubtract': 74,
|
||||
'NumpadAdd': 78,
|
||||
'NumpadEnter': 96,
|
||||
'NumpadDecimal': 83,
|
||||
'ControlLeft': 29,
|
||||
'ControlRight': 97,
|
||||
'ShiftLeft': 42,
|
||||
'ShiftRight': 54,
|
||||
'AltLeft': 56,
|
||||
'AltRight': 100,
|
||||
//'MetaLeft': 125, // Disabled as will break input
|
||||
//'MetaRight': 126, // Disabled as will break input
|
||||
'ContextMenu': 127,
|
||||
KeyA: 30,
|
||||
KeyB: 48,
|
||||
KeyC: 46,
|
||||
KeyD: 32,
|
||||
KeyE: 18,
|
||||
KeyF: 33,
|
||||
KeyG: 34,
|
||||
KeyH: 35,
|
||||
KeyI: 23,
|
||||
KeyJ: 36,
|
||||
KeyK: 37,
|
||||
KeyL: 38,
|
||||
KeyM: 50,
|
||||
KeyN: 49,
|
||||
KeyO: 24,
|
||||
KeyP: 25,
|
||||
KeyQ: 16,
|
||||
KeyR: 19,
|
||||
KeyS: 31,
|
||||
KeyT: 20,
|
||||
KeyU: 22,
|
||||
KeyV: 47,
|
||||
KeyW: 17,
|
||||
KeyX: 45,
|
||||
KeyY: 21,
|
||||
KeyZ: 44,
|
||||
Digit1: 2,
|
||||
Digit2: 3,
|
||||
Digit3: 4,
|
||||
Digit4: 5,
|
||||
Digit5: 6,
|
||||
Digit6: 7,
|
||||
Digit7: 8,
|
||||
Digit8: 9,
|
||||
Digit9: 10,
|
||||
Digit0: 11,
|
||||
Enter: 28,
|
||||
Escape: 1,
|
||||
Backspace: 14,
|
||||
Tab: 15,
|
||||
Space: 57,
|
||||
Minus: 12,
|
||||
Equal: 13,
|
||||
BracketLeft: 26,
|
||||
BracketRight: 27,
|
||||
Backslash: 43,
|
||||
Semicolon: 39,
|
||||
Quote: 40,
|
||||
Backquote: 41,
|
||||
Comma: 51,
|
||||
Period: 52,
|
||||
Slash: 53,
|
||||
CapsLock: 58,
|
||||
F1: 59,
|
||||
F2: 60,
|
||||
F3: 61,
|
||||
F4: 62,
|
||||
F5: 63,
|
||||
F6: 64,
|
||||
F7: 65,
|
||||
F8: 66,
|
||||
F9: 67,
|
||||
F10: 68,
|
||||
F11: 87,
|
||||
F12: 88,
|
||||
Insert: 110,
|
||||
Delete: 111,
|
||||
ArrowUp: 103,
|
||||
ArrowDown: 108,
|
||||
ArrowLeft: 105,
|
||||
ArrowRight: 106,
|
||||
Home: 102,
|
||||
End: 107,
|
||||
PageUp: 104,
|
||||
PageDown: 109,
|
||||
NumLock: 69,
|
||||
ScrollLock: 70,
|
||||
Pause: 119,
|
||||
Numpad0: 82,
|
||||
Numpad1: 79,
|
||||
Numpad2: 80,
|
||||
Numpad3: 81,
|
||||
Numpad4: 75,
|
||||
Numpad5: 76,
|
||||
Numpad6: 77,
|
||||
Numpad7: 71,
|
||||
Numpad8: 72,
|
||||
Numpad9: 73,
|
||||
NumpadDivide: 98,
|
||||
NumpadMultiply: 55,
|
||||
NumpadSubtract: 74,
|
||||
NumpadAdd: 78,
|
||||
NumpadEnter: 96,
|
||||
NumpadDecimal: 83,
|
||||
ControlLeft: 29,
|
||||
ControlRight: 97,
|
||||
ShiftLeft: 42,
|
||||
ShiftRight: 54,
|
||||
AltLeft: 56,
|
||||
AltRight: 100,
|
||||
//'MetaLeft': 125, // Disabled as will break input
|
||||
//'MetaRight': 126, // Disabled as will break input
|
||||
ContextMenu: 127,
|
||||
};
|
||||
|
||||
export const mouseButtonToLinuxEventCode: { [button: number]: number } = {
|
||||
0: 272,
|
||||
2: 273,
|
||||
1: 274,
|
||||
3: 275,
|
||||
4: 276
|
||||
0: 272,
|
||||
2: 273,
|
||||
1: 274,
|
||||
3: 275,
|
||||
4: 276,
|
||||
};
|
||||
|
||||
export const controllerButtonToLinuxEventCode: { [button: number]: number } = {
|
||||
0: 0x130,
|
||||
1: 0x131,
|
||||
2: 0x134,
|
||||
3: 0x133,
|
||||
4: 0x136,
|
||||
5: 0x137,
|
||||
6: 0x138,
|
||||
7: 0x139,
|
||||
8: 0x13a,
|
||||
9: 0x13b,
|
||||
10: 0x13d,
|
||||
11: 0x13e,
|
||||
12: 0x220,
|
||||
13: 0x221,
|
||||
14: 0x222,
|
||||
15: 0x223,
|
||||
16: 0x13c,
|
||||
};
|
||||
|
||||
509
packages/input/src/controller.ts
Normal file
509
packages/input/src/controller.ts
Normal file
@@ -0,0 +1,509 @@
|
||||
import { controllerButtonToLinuxEventCode } from "./codes";
|
||||
import { WebRTCStream } from "./webrtc-stream";
|
||||
import {
|
||||
ProtoMessageBase,
|
||||
ProtoMessageInput,
|
||||
ProtoMessageInputSchema,
|
||||
} from "./proto/messages_pb";
|
||||
import {
|
||||
ProtoInputSchema,
|
||||
ProtoControllerAttachSchema,
|
||||
ProtoControllerDetachSchema,
|
||||
ProtoControllerButtonSchema,
|
||||
ProtoControllerTriggerSchema,
|
||||
ProtoControllerAxisSchema,
|
||||
ProtoControllerStickSchema,
|
||||
ProtoControllerRumble,
|
||||
} from "./proto/types_pb";
|
||||
import { create, toBinary, fromBinary } from "@bufbuild/protobuf";
|
||||
|
||||
interface Props {
|
||||
webrtc: WebRTCStream;
|
||||
e: GamepadEvent;
|
||||
}
|
||||
|
||||
interface GamepadState {
|
||||
buttonState: Map<number, boolean>;
|
||||
leftTrigger: number;
|
||||
rightTrigger: number;
|
||||
leftX: number;
|
||||
leftY: number;
|
||||
rightX: number;
|
||||
rightY: number;
|
||||
dpadX: number;
|
||||
dpadY: number;
|
||||
}
|
||||
|
||||
export class Controller {
|
||||
protected wrtc: WebRTCStream;
|
||||
protected slot: number;
|
||||
protected connected: boolean = false;
|
||||
protected gamepad: Gamepad | null = null;
|
||||
protected lastState: GamepadState = {
|
||||
buttonState: new Map<number, boolean>(),
|
||||
leftTrigger: 0,
|
||||
rightTrigger: 0,
|
||||
leftX: 0,
|
||||
leftY: 0,
|
||||
rightX: 0,
|
||||
rightY: 0,
|
||||
dpadX: 0,
|
||||
dpadY: 0,
|
||||
};
|
||||
// TODO: As user configurable, set quite low now for decent controllers (not Nintendo ones :P)
|
||||
protected stickDeadzone: number = 2048; // 2048 / 32768 = ~0.06 (6% of stick range)
|
||||
|
||||
private updateInterval = 10.0; // 100 updates per second
|
||||
private _dcRumbleHandler: ((data: ArrayBuffer) => void) | null = null;
|
||||
|
||||
constructor({ webrtc, e }: Props) {
|
||||
this.wrtc = webrtc;
|
||||
this.slot = e.gamepad.index;
|
||||
|
||||
this.updateInterval = 1000 / webrtc.currentFrameRate;
|
||||
|
||||
// Gamepad connected
|
||||
this.gamepad = e.gamepad;
|
||||
|
||||
// Get vendor of gamepad from id string (i.e. "... Vendor: 054c Product: 09cc")
|
||||
const vendorMatch = e.gamepad.id.match(/Vendor:\s?([0-9a-fA-F]{4})/);
|
||||
const vendorId = vendorMatch ? vendorMatch[1].toLowerCase() : "unknown";
|
||||
// Get product id of gamepad from id string
|
||||
const productMatch = e.gamepad.id.match(/Product:\s?([0-9a-fA-F]{4})/);
|
||||
const productId = productMatch ? productMatch[1].toLowerCase() : "unknown";
|
||||
|
||||
const attachMsg = create(ProtoInputSchema, {
|
||||
$typeName: "proto.ProtoInput",
|
||||
inputType: {
|
||||
case: "controllerAttach",
|
||||
value: create(ProtoControllerAttachSchema, {
|
||||
type: "ControllerAttach",
|
||||
id: this.vendor_id_to_controller(vendorId, productId),
|
||||
slot: this.slot,
|
||||
}),
|
||||
},
|
||||
});
|
||||
const message: ProtoMessageInput = {
|
||||
$typeName: "proto.ProtoMessageInput",
|
||||
messageBase: {
|
||||
$typeName: "proto.ProtoMessageBase",
|
||||
payloadType: "controllerInput",
|
||||
} as ProtoMessageBase,
|
||||
data: attachMsg,
|
||||
};
|
||||
this.wrtc.sendBinary(toBinary(ProtoMessageInputSchema, message));
|
||||
|
||||
// Listen to feedback rumble events from server
|
||||
this._dcRumbleHandler = (data: any) => this.rumbleCallback(data as ArrayBuffer);
|
||||
this.wrtc.addDataChannelCallback(this._dcRumbleHandler);
|
||||
|
||||
this.run();
|
||||
}
|
||||
|
||||
// Maps vendor id and product id to supported controller type
|
||||
// Currently supported: Sony (ps4, ps5), Microsoft (xbox360, xboxone), Nintendo (switchpro)
|
||||
// Default fallback to xbox360
|
||||
private vendor_id_to_controller(vendorId: string, productId: string): string {
|
||||
switch (vendorId) {
|
||||
case "054c": // Sony
|
||||
switch (productId) {
|
||||
case "0ce6":
|
||||
return "ps5";
|
||||
case "05c4":
|
||||
case "09cc":
|
||||
return "ps4";
|
||||
default:
|
||||
return "ps4"; // default to ps4
|
||||
}
|
||||
case "045e": // Microsoft
|
||||
switch (productId) {
|
||||
case "02d1":
|
||||
case "02dd":
|
||||
return "xboxone";
|
||||
case "028e":
|
||||
return "xbox360";
|
||||
default:
|
||||
return "xbox360"; // default to xbox360
|
||||
}
|
||||
case "057e": // Nintendo
|
||||
switch (productId) {
|
||||
case "2009":
|
||||
case "200e":
|
||||
return "switchpro";
|
||||
default:
|
||||
return "switchpro"; // default to switchpro
|
||||
}
|
||||
default: {
|
||||
return "xbox360";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private remapFromTo(
|
||||
value: number,
|
||||
fromMin: number,
|
||||
fromMax: number,
|
||||
toMin: number,
|
||||
toMax: number,
|
||||
) {
|
||||
return ((value - fromMin) * (toMax - toMin)) / (fromMax - fromMin) + toMin;
|
||||
}
|
||||
|
||||
private pollGamepad() {
|
||||
const gamepads = navigator.getGamepads();
|
||||
if (this.slot < gamepads.length) {
|
||||
const gamepad = gamepads[this.slot];
|
||||
if (gamepad) {
|
||||
/* Button handling */
|
||||
gamepad.buttons.forEach((button, index) => {
|
||||
// Ignore d-pad buttons (12-15) as we handle those as axis
|
||||
if (index >= 12 && index <= 15) return;
|
||||
// ignore trigger buttons (6-7) as we handle those as axis
|
||||
if (index === 6 || index === 7) return;
|
||||
// If state differs, send
|
||||
if (button.pressed !== this.lastState.buttonState.get(index)) {
|
||||
const linuxCode = this.controllerButtonToVirtualKeyCode(index);
|
||||
if (linuxCode === undefined) {
|
||||
// Skip unmapped button index
|
||||
this.lastState.buttonState.set(index, button.pressed);
|
||||
return;
|
||||
}
|
||||
|
||||
const buttonProto = create(ProtoInputSchema, {
|
||||
$typeName: "proto.ProtoInput",
|
||||
inputType: {
|
||||
case: "controllerButton",
|
||||
value: create(ProtoControllerButtonSchema, {
|
||||
type: "ControllerButton",
|
||||
slot: this.slot,
|
||||
button: linuxCode,
|
||||
pressed: button.pressed,
|
||||
}),
|
||||
},
|
||||
});
|
||||
const buttonMessage: ProtoMessageInput = {
|
||||
$typeName: "proto.ProtoMessageInput",
|
||||
messageBase: {
|
||||
$typeName: "proto.ProtoMessageBase",
|
||||
payloadType: "controllerInput",
|
||||
} as ProtoMessageBase,
|
||||
data: buttonProto,
|
||||
};
|
||||
this.wrtc.sendBinary(
|
||||
toBinary(ProtoMessageInputSchema, buttonMessage),
|
||||
);
|
||||
// Store button state
|
||||
this.lastState.buttonState.set(index, button.pressed);
|
||||
}
|
||||
});
|
||||
|
||||
/* Trigger handling */
|
||||
// map trigger value from 0.0 to 1.0 to -32768 to 32767
|
||||
const leftTrigger = Math.round(
|
||||
this.remapFromTo(gamepad.buttons[6]?.value ?? 0, 0, 1, -32768, 32767),
|
||||
);
|
||||
// If state differs, send
|
||||
if (leftTrigger !== this.lastState.leftTrigger) {
|
||||
const triggerProto = create(ProtoInputSchema, {
|
||||
$typeName: "proto.ProtoInput",
|
||||
inputType: {
|
||||
case: "controllerTrigger",
|
||||
value: create(ProtoControllerTriggerSchema, {
|
||||
type: "ControllerTrigger",
|
||||
slot: this.slot,
|
||||
trigger: 0, // 0 = left, 1 = right
|
||||
value: leftTrigger,
|
||||
}),
|
||||
},
|
||||
});
|
||||
const triggerMessage: ProtoMessageInput = {
|
||||
$typeName: "proto.ProtoMessageInput",
|
||||
messageBase: {
|
||||
$typeName: "proto.ProtoMessageBase",
|
||||
payloadType: "controllerInput",
|
||||
} as ProtoMessageBase,
|
||||
data: triggerProto,
|
||||
};
|
||||
this.lastState.leftTrigger = leftTrigger;
|
||||
this.wrtc.sendBinary(
|
||||
toBinary(ProtoMessageInputSchema, triggerMessage),
|
||||
);
|
||||
}
|
||||
const rightTrigger = Math.round(
|
||||
this.remapFromTo(gamepad.buttons[7]?.value ?? 0, 0, 1, -32768, 32767),
|
||||
);
|
||||
// If state differs, send
|
||||
if (rightTrigger !== this.lastState.rightTrigger) {
|
||||
const triggerProto = create(ProtoInputSchema, {
|
||||
$typeName: "proto.ProtoInput",
|
||||
inputType: {
|
||||
case: "controllerTrigger",
|
||||
value: create(ProtoControllerTriggerSchema, {
|
||||
type: "ControllerTrigger",
|
||||
slot: this.slot,
|
||||
trigger: 1, // 0 = left, 1 = right
|
||||
value: rightTrigger,
|
||||
}),
|
||||
},
|
||||
});
|
||||
const triggerMessage: ProtoMessageInput = {
|
||||
$typeName: "proto.ProtoMessageInput",
|
||||
messageBase: {
|
||||
$typeName: "proto.ProtoMessageBase",
|
||||
payloadType: "controllerInput",
|
||||
} as ProtoMessageBase,
|
||||
data: triggerProto,
|
||||
};
|
||||
this.lastState.rightTrigger = rightTrigger;
|
||||
this.wrtc.sendBinary(
|
||||
toBinary(ProtoMessageInputSchema, triggerMessage),
|
||||
);
|
||||
}
|
||||
|
||||
/* DPad handling */
|
||||
// We send dpad buttons as axis values -1 to 1 for left/up, right/down
|
||||
const dpadLeft = gamepad.buttons[14]?.pressed ? 1 : 0;
|
||||
const dpadRight = gamepad.buttons[15]?.pressed ? 1 : 0;
|
||||
const dpadX = dpadLeft ? -1 : dpadRight ? 1 : 0;
|
||||
if (dpadX !== this.lastState.dpadX) {
|
||||
const dpadProto = create(ProtoInputSchema, {
|
||||
$typeName: "proto.ProtoInput",
|
||||
inputType: {
|
||||
case: "controllerAxis",
|
||||
value: create(ProtoControllerAxisSchema, {
|
||||
type: "ControllerAxis",
|
||||
slot: this.slot,
|
||||
axis: 0, // 0 = dpadX, 1 = dpadY
|
||||
value: dpadX,
|
||||
}),
|
||||
},
|
||||
});
|
||||
const dpadMessage: ProtoMessageInput = {
|
||||
$typeName: "proto.ProtoMessageInput",
|
||||
messageBase: {
|
||||
$typeName: "proto.ProtoMessageBase",
|
||||
payloadType: "controllerInput",
|
||||
} as ProtoMessageBase,
|
||||
data: dpadProto,
|
||||
};
|
||||
this.lastState.dpadX = dpadX;
|
||||
this.wrtc.sendBinary(toBinary(ProtoMessageInputSchema, dpadMessage));
|
||||
}
|
||||
|
||||
const dpadUp = gamepad.buttons[12]?.pressed ? 1 : 0;
|
||||
const dpadDown = gamepad.buttons[13]?.pressed ? 1 : 0;
|
||||
const dpadY = dpadUp ? -1 : dpadDown ? 1 : 0;
|
||||
if (dpadY !== this.lastState.dpadY) {
|
||||
const dpadProto = create(ProtoInputSchema, {
|
||||
$typeName: "proto.ProtoInput",
|
||||
inputType: {
|
||||
case: "controllerAxis",
|
||||
value: create(ProtoControllerAxisSchema, {
|
||||
type: "ControllerAxis",
|
||||
slot: this.slot,
|
||||
axis: 1, // 0 = dpadX, 1 = dpadY
|
||||
value: dpadY,
|
||||
}),
|
||||
},
|
||||
});
|
||||
const dpadMessage: ProtoMessageInput = {
|
||||
$typeName: "proto.ProtoMessageInput",
|
||||
messageBase: {
|
||||
$typeName: "proto.ProtoMessageBase",
|
||||
payloadType: "controllerInput",
|
||||
} as ProtoMessageBase,
|
||||
data: dpadProto,
|
||||
};
|
||||
this.lastState.dpadY = dpadY;
|
||||
this.wrtc.sendBinary(toBinary(ProtoMessageInputSchema, dpadMessage));
|
||||
}
|
||||
|
||||
/* Stick handling */
|
||||
// stick values need to be mapped from -1.0 to 1.0 to -32768 to 32767
|
||||
const leftX = this.remapFromTo(gamepad.axes[0] ?? 0, -1, 1, -32768, 32767);
|
||||
const leftY = this.remapFromTo(gamepad.axes[1] ?? 0, -1, 1, -32768, 32767);
|
||||
// Apply deadzone
|
||||
const sendLeftX =
|
||||
Math.abs(leftX) > this.stickDeadzone ? Math.round(leftX) : 0;
|
||||
const sendLeftY =
|
||||
Math.abs(leftY) > this.stickDeadzone ? Math.round(leftY) : 0;
|
||||
// if outside deadzone, send normally if changed
|
||||
// if moves inside deadzone, zero it if not inside deadzone last time
|
||||
if (
|
||||
sendLeftX !== this.lastState.leftX ||
|
||||
sendLeftY !== this.lastState.leftY
|
||||
) {
|
||||
// console.log("Sticks: ", sendLeftX, sendLeftY, sendRightX, sendRightY);
|
||||
const stickProto = create(ProtoInputSchema, {
|
||||
$typeName: "proto.ProtoInput",
|
||||
inputType: {
|
||||
case: "controllerStick",
|
||||
value: create(ProtoControllerStickSchema, {
|
||||
type: "ControllerStick",
|
||||
slot: this.slot,
|
||||
stick: 0, // 0 = left, 1 = right
|
||||
x: sendLeftX,
|
||||
y: sendLeftY,
|
||||
}),
|
||||
},
|
||||
});
|
||||
const stickMessage: ProtoMessageInput = {
|
||||
$typeName: "proto.ProtoMessageInput",
|
||||
messageBase: {
|
||||
$typeName: "proto.ProtoMessageBase",
|
||||
payloadType: "controllerInput",
|
||||
} as ProtoMessageBase,
|
||||
data: stickProto,
|
||||
};
|
||||
this.lastState.leftX = sendLeftX;
|
||||
this.lastState.leftY = sendLeftY;
|
||||
this.wrtc.sendBinary(toBinary(ProtoMessageInputSchema, stickMessage));
|
||||
}
|
||||
|
||||
const rightX = this.remapFromTo(gamepad.axes[2] ?? 0, -1, 1, -32768, 32767);
|
||||
const rightY = this.remapFromTo(gamepad.axes[3] ?? 0, -1, 1, -32768, 32767);
|
||||
// Apply deadzone
|
||||
const sendRightX =
|
||||
Math.abs(rightX) > this.stickDeadzone ? Math.round(rightX) : 0;
|
||||
const sendRightY =
|
||||
Math.abs(rightY) > this.stickDeadzone ? Math.round(rightY) : 0;
|
||||
if (
|
||||
sendRightX !== this.lastState.rightX ||
|
||||
sendRightY !== this.lastState.rightY
|
||||
) {
|
||||
const stickProto = create(ProtoInputSchema, {
|
||||
$typeName: "proto.ProtoInput",
|
||||
inputType: {
|
||||
case: "controllerStick",
|
||||
value: create(ProtoControllerStickSchema, {
|
||||
type: "ControllerStick",
|
||||
slot: this.slot,
|
||||
stick: 1, // 0 = left, 1 = right
|
||||
x: sendRightX,
|
||||
y: sendRightY,
|
||||
}),
|
||||
},
|
||||
});
|
||||
const stickMessage: ProtoMessageInput = {
|
||||
$typeName: "proto.ProtoMessageInput",
|
||||
messageBase: {
|
||||
$typeName: "proto.ProtoMessageBase",
|
||||
payloadType: "controllerInput",
|
||||
} as ProtoMessageBase,
|
||||
data: stickProto,
|
||||
};
|
||||
this.lastState.rightX = sendRightX;
|
||||
this.lastState.rightY = sendRightY;
|
||||
this.wrtc.sendBinary(toBinary(ProtoMessageInputSchema, stickMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private loopInterval: any = null;
|
||||
|
||||
public run() {
|
||||
if (this.connected)
|
||||
this.stop();
|
||||
|
||||
this.connected = true;
|
||||
// Poll gamepads in setInterval loop
|
||||
this.loopInterval = setInterval(() => {
|
||||
if (this.connected) this.pollGamepad();
|
||||
}, this.updateInterval);
|
||||
}
|
||||
|
||||
public stop() {
|
||||
if (this.loopInterval) {
|
||||
clearInterval(this.loopInterval);
|
||||
this.loopInterval = null;
|
||||
}
|
||||
this.connected = false;
|
||||
}
|
||||
|
||||
public getSlot() {
|
||||
return this.slot;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.stop();
|
||||
// Remove callback
|
||||
if (this._dcRumbleHandler !== null) {
|
||||
this.wrtc.removeDataChannelCallback(this._dcRumbleHandler);
|
||||
this._dcRumbleHandler = null;
|
||||
}
|
||||
// Gamepad disconnected
|
||||
const detachMsg = create(ProtoInputSchema, {
|
||||
$typeName: "proto.ProtoInput",
|
||||
inputType: {
|
||||
case: "controllerDetach",
|
||||
value: create(ProtoControllerDetachSchema, {
|
||||
type: "ControllerDetach",
|
||||
slot: this.slot,
|
||||
}),
|
||||
},
|
||||
});
|
||||
const message: ProtoMessageInput = {
|
||||
$typeName: "proto.ProtoMessageInput",
|
||||
messageBase: {
|
||||
$typeName: "proto.ProtoMessageBase",
|
||||
payloadType: "controllerInput",
|
||||
} as ProtoMessageBase,
|
||||
data: detachMsg,
|
||||
};
|
||||
this.wrtc.sendBinary(toBinary(ProtoMessageInputSchema, message));
|
||||
}
|
||||
|
||||
private controllerButtonToVirtualKeyCode(code: number) {
|
||||
return controllerButtonToLinuxEventCode[code] || undefined;
|
||||
}
|
||||
|
||||
private rumbleCallback(data: ArrayBuffer) {
|
||||
// If not connected, ignore
|
||||
if (!this.connected) return;
|
||||
try {
|
||||
// First decode the wrapper message
|
||||
const uint8Data = new Uint8Array(data);
|
||||
const messageWrapper = fromBinary(ProtoMessageInputSchema, uint8Data);
|
||||
|
||||
// Check if it contains controller rumble data
|
||||
if (messageWrapper.data?.inputType?.case === "controllerRumble") {
|
||||
const rumbleMsg = messageWrapper.data.inputType.value as ProtoControllerRumble;
|
||||
|
||||
// Check if aimed at this controller slot
|
||||
if (rumbleMsg.slot !== this.slot) return;
|
||||
|
||||
// Trigger actual rumble
|
||||
// Need to remap from 0-65535 to 0.0-1.0 ranges
|
||||
const clampedLowFreq = Math.max(0, Math.min(65535, rumbleMsg.lowFrequency));
|
||||
const rumbleLowFreq = this.remapFromTo(
|
||||
clampedLowFreq,
|
||||
0,
|
||||
65535,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
const clampedHighFreq = Math.max(0, Math.min(65535, rumbleMsg.highFrequency));
|
||||
const rumbleHighFreq = this.remapFromTo(
|
||||
clampedHighFreq,
|
||||
0,
|
||||
65535,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
// Cap to valid range (max 5000)
|
||||
const rumbleDuration = Math.max(0, Math.min(5000, rumbleMsg.duration));
|
||||
if (this.gamepad.vibrationActuator) {
|
||||
this.gamepad.vibrationActuator.playEffect("dual-rumble", {
|
||||
startDelay: 0,
|
||||
duration: rumbleDuration,
|
||||
weakMagnitude: rumbleLowFreq,
|
||||
strongMagnitude: rumbleHighFreq,
|
||||
}).catch(console.error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to decode rumble message:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./keyboard"
|
||||
export * from "./mouse"
|
||||
export * from "./controller"
|
||||
export * from "./webrtc-stream"
|
||||
@@ -9,27 +9,23 @@ import {
|
||||
ProtoInputSchema,
|
||||
ProtoKeyDownSchema,
|
||||
ProtoKeyUpSchema,
|
||||
ProtoMouseMoveSchema
|
||||
} from "./proto/types_pb";
|
||||
import {create, toBinary} from "@bufbuild/protobuf";
|
||||
|
||||
interface Props {
|
||||
webrtc: WebRTCStream;
|
||||
canvas: HTMLCanvasElement;
|
||||
}
|
||||
|
||||
export class Keyboard {
|
||||
protected wrtc: WebRTCStream;
|
||||
protected canvas: HTMLCanvasElement;
|
||||
protected connected!: boolean;
|
||||
|
||||
// Store references to event listeners
|
||||
private readonly keydownListener: (e: KeyboardEvent) => void;
|
||||
private readonly keyupListener: (e: KeyboardEvent) => void;
|
||||
|
||||
constructor({webrtc, canvas}: Props) {
|
||||
constructor({webrtc}: Props) {
|
||||
this.wrtc = webrtc;
|
||||
this.canvas = canvas;
|
||||
this.keydownListener = this.createKeyboardListener((e: any) => create(ProtoInputSchema, {
|
||||
$typeName: "proto.ProtoInput",
|
||||
inputType: {
|
||||
@@ -54,23 +50,12 @@ export class Keyboard {
|
||||
}
|
||||
|
||||
private run() {
|
||||
//calls all the other functions
|
||||
if (!document.pointerLockElement) {
|
||||
if (this.connected) {
|
||||
this.stop()
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.connected)
|
||||
this.stop()
|
||||
|
||||
if (document.pointerLockElement == this.canvas) {
|
||||
this.connected = true
|
||||
document.addEventListener("keydown", this.keydownListener, {passive: false});
|
||||
document.addEventListener("keyup", this.keyupListener, {passive: false});
|
||||
} else {
|
||||
if (this.connected) {
|
||||
this.stop()
|
||||
}
|
||||
}
|
||||
this.connected = true
|
||||
document.addEventListener("keydown", this.keydownListener, {passive: false});
|
||||
document.addEventListener("keyup", this.keyupListener, {passive: false});
|
||||
}
|
||||
|
||||
private stop() {
|
||||
@@ -120,7 +105,6 @@ export class Keyboard {
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
document.exitPointerLock();
|
||||
this.stop();
|
||||
this.connected = false;
|
||||
}
|
||||
|
||||
@@ -24,13 +24,12 @@ export class Mouse {
|
||||
protected canvas: HTMLCanvasElement;
|
||||
protected connected!: boolean;
|
||||
|
||||
// Store references to event listeners
|
||||
private sendInterval = 16 //60fps
|
||||
private sendInterval = 10 // 100 updates per second
|
||||
|
||||
// Store references to event listeners
|
||||
private readonly mousemoveListener: (e: MouseEvent) => void;
|
||||
private movementX: number = 0;
|
||||
private movementY: number = 0;
|
||||
private isProcessing: boolean = false;
|
||||
|
||||
private readonly mousedownListener: (e: MouseEvent) => void;
|
||||
private readonly mouseupListener: (e: MouseEvent) => void;
|
||||
@@ -40,7 +39,7 @@ export class Mouse {
|
||||
this.wrtc = webrtc;
|
||||
this.canvas = canvas;
|
||||
|
||||
this.sendInterval = 1000 / webrtc.currentFrameRate
|
||||
this.sendInterval = 1000 / webrtc.currentFrameRate;
|
||||
|
||||
this.mousemoveListener = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -75,8 +74,8 @@ export class Mouse {
|
||||
case: "mouseWheel",
|
||||
value: create(ProtoMouseWheelSchema, {
|
||||
type: "MouseWheel",
|
||||
x: e.deltaX,
|
||||
y: e.deltaY
|
||||
x: Math.round(e.deltaX),
|
||||
y: Math.round(e.deltaY),
|
||||
}),
|
||||
}
|
||||
}));
|
||||
@@ -135,8 +134,8 @@ export class Mouse {
|
||||
case: "mouseMove",
|
||||
value: create(ProtoMouseMoveSchema, {
|
||||
type: "MouseMove",
|
||||
x: this.movementX,
|
||||
y: this.movementY,
|
||||
x: Math.round(this.movementX),
|
||||
y: Math.round(this.movementY),
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// @generated by protoc-gen-es v2.2.3 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
|
||||
// @generated from file latency_tracker.proto (package proto, syntax proto3)
|
||||
/* eslint-disable */
|
||||
|
||||
import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1";
|
||||
import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1";
|
||||
import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2";
|
||||
import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
|
||||
import type { Timestamp } from "@bufbuild/protobuf/wkt";
|
||||
import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt";
|
||||
import type { Message } from "@bufbuild/protobuf";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// @generated by protoc-gen-es v2.2.3 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
|
||||
// @generated from file messages.proto (package proto, syntax proto3)
|
||||
/* eslint-disable */
|
||||
|
||||
import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1";
|
||||
import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1";
|
||||
import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2";
|
||||
import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
|
||||
import type { ProtoInput } from "./types_pb";
|
||||
import { file_types } from "./types_pb";
|
||||
import type { ProtoLatencyTracker } from "./latency_tracker_pb";
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// @generated by protoc-gen-es v2.2.3 with parameter "target=ts"
|
||||
// @generated by protoc-gen-es v2.9.0 with parameter "target=ts"
|
||||
// @generated from file types.proto (package proto, syntax proto3)
|
||||
/* eslint-disable */
|
||||
|
||||
import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1";
|
||||
import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1";
|
||||
import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2";
|
||||
import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
|
||||
import type { Message } from "@bufbuild/protobuf";
|
||||
|
||||
/**
|
||||
* Describes the file types.proto.
|
||||
*/
|
||||
export const file_types: GenFile = /*@__PURE__*/
|
||||
fileDesc("Cgt0eXBlcy5wcm90bxIFcHJvdG8iNAoOUHJvdG9Nb3VzZU1vdmUSDAoEdHlwZRgBIAEoCRIJCgF4GAIgASgFEgkKAXkYAyABKAUiNwoRUHJvdG9Nb3VzZU1vdmVBYnMSDAoEdHlwZRgBIAEoCRIJCgF4GAIgASgFEgkKAXkYAyABKAUiNQoPUHJvdG9Nb3VzZVdoZWVsEgwKBHR5cGUYASABKAkSCQoBeBgCIAEoBRIJCgF5GAMgASgFIi4KEVByb3RvTW91c2VLZXlEb3duEgwKBHR5cGUYASABKAkSCwoDa2V5GAIgASgFIiwKD1Byb3RvTW91c2VLZXlVcBIMCgR0eXBlGAEgASgJEgsKA2tleRgCIAEoBSIpCgxQcm90b0tleURvd24SDAoEdHlwZRgBIAEoCRILCgNrZXkYAiABKAUiJwoKUHJvdG9LZXlVcBIMCgR0eXBlGAEgASgJEgsKA2tleRgCIAEoBSLcAgoKUHJvdG9JbnB1dBIrCgptb3VzZV9tb3ZlGAEgASgLMhUucHJvdG8uUHJvdG9Nb3VzZU1vdmVIABIyCg5tb3VzZV9tb3ZlX2FicxgCIAEoCzIYLnByb3RvLlByb3RvTW91c2VNb3ZlQWJzSAASLQoLbW91c2Vfd2hlZWwYAyABKAsyFi5wcm90by5Qcm90b01vdXNlV2hlZWxIABIyCg5tb3VzZV9rZXlfZG93bhgEIAEoCzIYLnByb3RvLlByb3RvTW91c2VLZXlEb3duSAASLgoMbW91c2Vfa2V5X3VwGAUgASgLMhYucHJvdG8uUHJvdG9Nb3VzZUtleVVwSAASJwoIa2V5X2Rvd24YBiABKAsyEy5wcm90by5Qcm90b0tleURvd25IABIjCgZrZXlfdXAYByABKAsyES5wcm90by5Qcm90b0tleVVwSABCDAoKaW5wdXRfdHlwZUIWWhRyZWxheS9pbnRlcm5hbC9wcm90b2IGcHJvdG8z");
|
||||
fileDesc("Cgt0eXBlcy5wcm90bxIFcHJvdG8iNAoOUHJvdG9Nb3VzZU1vdmUSDAoEdHlwZRgBIAEoCRIJCgF4GAIgASgFEgkKAXkYAyABKAUiNwoRUHJvdG9Nb3VzZU1vdmVBYnMSDAoEdHlwZRgBIAEoCRIJCgF4GAIgASgFEgkKAXkYAyABKAUiNQoPUHJvdG9Nb3VzZVdoZWVsEgwKBHR5cGUYASABKAkSCQoBeBgCIAEoBRIJCgF5GAMgASgFIi4KEVByb3RvTW91c2VLZXlEb3duEgwKBHR5cGUYASABKAkSCwoDa2V5GAIgASgFIiwKD1Byb3RvTW91c2VLZXlVcBIMCgR0eXBlGAEgASgJEgsKA2tleRgCIAEoBSIpCgxQcm90b0tleURvd24SDAoEdHlwZRgBIAEoCRILCgNrZXkYAiABKAUiJwoKUHJvdG9LZXlVcBIMCgR0eXBlGAEgASgJEgsKA2tleRgCIAEoBSI/ChVQcm90b0NvbnRyb2xsZXJBdHRhY2gSDAoEdHlwZRgBIAEoCRIKCgJpZBgCIAEoCRIMCgRzbG90GAMgASgFIjMKFVByb3RvQ29udHJvbGxlckRldGFjaBIMCgR0eXBlGAEgASgJEgwKBHNsb3QYAiABKAUiVAoVUHJvdG9Db250cm9sbGVyQnV0dG9uEgwKBHR5cGUYASABKAkSDAoEc2xvdBgCIAEoBRIOCgZidXR0b24YAyABKAUSDwoHcHJlc3NlZBgEIAEoCCJUChZQcm90b0NvbnRyb2xsZXJUcmlnZ2VyEgwKBHR5cGUYASABKAkSDAoEc2xvdBgCIAEoBRIPCgd0cmlnZ2VyGAMgASgFEg0KBXZhbHVlGAQgASgFIlcKFFByb3RvQ29udHJvbGxlclN0aWNrEgwKBHR5cGUYASABKAkSDAoEc2xvdBgCIAEoBRINCgVzdGljaxgDIAEoBRIJCgF4GAQgASgFEgkKAXkYBSABKAUiTgoTUHJvdG9Db250cm9sbGVyQXhpcxIMCgR0eXBlGAEgASgJEgwKBHNsb3QYAiABKAUSDAoEYXhpcxgDIAEoBRINCgV2YWx1ZRgEIAEoBSJ0ChVQcm90b0NvbnRyb2xsZXJSdW1ibGUSDAoEdHlwZRgBIAEoCRIMCgRzbG90GAIgASgFEhUKDWxvd19mcmVxdWVuY3kYAyABKAUSFgoOaGlnaF9mcmVxdWVuY3kYBCABKAUSEAoIZHVyYXRpb24YBSABKAUi9QUKClByb3RvSW5wdXQSKwoKbW91c2VfbW92ZRgBIAEoCzIVLnByb3RvLlByb3RvTW91c2VNb3ZlSAASMgoObW91c2VfbW92ZV9hYnMYAiABKAsyGC5wcm90by5Qcm90b01vdXNlTW92ZUFic0gAEi0KC21vdXNlX3doZWVsGAMgASgLMhYucHJvdG8uUHJvdG9Nb3VzZVdoZWVsSAASMgoObW91c2Vfa2V5X2Rvd24YBCABKAsyGC5wcm90by5Qcm90b01vdXNlS2V5RG93bkgAEi4KDG1vdXNlX2tleV91cBgFIAEoCzIWLnByb3RvLlByb3RvTW91c2VLZXlVcEgAEicKCGtleV9kb3duGAYgASgLMhMucHJvdG8uUHJvdG9LZXlEb3duSAASIwoGa2V5X3VwGAcgASgLMhEucHJvdG8uUHJvdG9LZXlVcEgAEjkKEWNvbnRyb2xsZXJfYXR0YWNoGAggASgLMhwucHJvdG8uUHJvdG9Db250cm9sbGVyQXR0YWNoSAASOQoRY29udHJvbGxlcl9kZXRhY2gYCSABKAsyHC5wcm90by5Qcm90b0NvbnRyb2xsZXJEZXRhY2hIABI5ChFjb250cm9sbGVyX2J1dHRvbhgKIAEoCzIcLnByb3RvLlByb3RvQ29udHJvbGxlckJ1dHRvbkgAEjsKEmNvbnRyb2xsZXJfdHJpZ2dlchgLIAEoCzIdLnByb3RvLlByb3RvQ29udHJvbGxlclRyaWdnZXJIABI3ChBjb250cm9sbGVyX3N0aWNrGAwgASgLMhsucHJvdG8uUHJvdG9Db250cm9sbGVyU3RpY2tIABI1Cg9jb250cm9sbGVyX2F4aXMYDSABKAsyGi5wcm90by5Qcm90b0NvbnRyb2xsZXJBeGlzSAASOQoRY29udHJvbGxlcl9ydW1ibGUYDiABKAsyHC5wcm90by5Qcm90b0NvbnRyb2xsZXJSdW1ibGVIAEIMCgppbnB1dF90eXBlQhZaFHJlbGF5L2ludGVybmFsL3Byb3RvYgZwcm90bzM");
|
||||
|
||||
/**
|
||||
* MouseMove message
|
||||
@@ -209,6 +209,293 @@ export type ProtoKeyUp = Message<"proto.ProtoKeyUp"> & {
|
||||
export const ProtoKeyUpSchema: GenMessage<ProtoKeyUp> = /*@__PURE__*/
|
||||
messageDesc(file_types, 6);
|
||||
|
||||
/**
|
||||
* ControllerAttach message
|
||||
*
|
||||
* @generated from message proto.ProtoControllerAttach
|
||||
*/
|
||||
export type ProtoControllerAttach = Message<"proto.ProtoControllerAttach"> & {
|
||||
/**
|
||||
* Fixed value "ControllerAttach"
|
||||
*
|
||||
* @generated from field: string type = 1;
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* One of the following enums: "ps", "xbox" or "switch"
|
||||
*
|
||||
* @generated from field: string id = 2;
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* Slot number (0-3)
|
||||
*
|
||||
* @generated from field: int32 slot = 3;
|
||||
*/
|
||||
slot: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message proto.ProtoControllerAttach.
|
||||
* Use `create(ProtoControllerAttachSchema)` to create a new message.
|
||||
*/
|
||||
export const ProtoControllerAttachSchema: GenMessage<ProtoControllerAttach> = /*@__PURE__*/
|
||||
messageDesc(file_types, 7);
|
||||
|
||||
/**
|
||||
* ControllerDetach message
|
||||
*
|
||||
* @generated from message proto.ProtoControllerDetach
|
||||
*/
|
||||
export type ProtoControllerDetach = Message<"proto.ProtoControllerDetach"> & {
|
||||
/**
|
||||
* Fixed value "ControllerDetach"
|
||||
*
|
||||
* @generated from field: string type = 1;
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* Slot number (0-3)
|
||||
*
|
||||
* @generated from field: int32 slot = 2;
|
||||
*/
|
||||
slot: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message proto.ProtoControllerDetach.
|
||||
* Use `create(ProtoControllerDetachSchema)` to create a new message.
|
||||
*/
|
||||
export const ProtoControllerDetachSchema: GenMessage<ProtoControllerDetach> = /*@__PURE__*/
|
||||
messageDesc(file_types, 8);
|
||||
|
||||
/**
|
||||
* ControllerButton message
|
||||
*
|
||||
* @generated from message proto.ProtoControllerButton
|
||||
*/
|
||||
export type ProtoControllerButton = Message<"proto.ProtoControllerButton"> & {
|
||||
/**
|
||||
* Fixed value "ControllerButtons"
|
||||
*
|
||||
* @generated from field: string type = 1;
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* Slot number (0-3)
|
||||
*
|
||||
* @generated from field: int32 slot = 2;
|
||||
*/
|
||||
slot: number;
|
||||
|
||||
/**
|
||||
* Button code (linux input event code)
|
||||
*
|
||||
* @generated from field: int32 button = 3;
|
||||
*/
|
||||
button: number;
|
||||
|
||||
/**
|
||||
* true if pressed, false if released
|
||||
*
|
||||
* @generated from field: bool pressed = 4;
|
||||
*/
|
||||
pressed: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message proto.ProtoControllerButton.
|
||||
* Use `create(ProtoControllerButtonSchema)` to create a new message.
|
||||
*/
|
||||
export const ProtoControllerButtonSchema: GenMessage<ProtoControllerButton> = /*@__PURE__*/
|
||||
messageDesc(file_types, 9);
|
||||
|
||||
/**
|
||||
* ControllerTriggers message
|
||||
*
|
||||
* @generated from message proto.ProtoControllerTrigger
|
||||
*/
|
||||
export type ProtoControllerTrigger = Message<"proto.ProtoControllerTrigger"> & {
|
||||
/**
|
||||
* Fixed value "ControllerTriggers"
|
||||
*
|
||||
* @generated from field: string type = 1;
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* Slot number (0-3)
|
||||
*
|
||||
* @generated from field: int32 slot = 2;
|
||||
*/
|
||||
slot: number;
|
||||
|
||||
/**
|
||||
* Trigger number (0 for left, 1 for right)
|
||||
*
|
||||
* @generated from field: int32 trigger = 3;
|
||||
*/
|
||||
trigger: number;
|
||||
|
||||
/**
|
||||
* trigger value (-32768 to 32767)
|
||||
*
|
||||
* @generated from field: int32 value = 4;
|
||||
*/
|
||||
value: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message proto.ProtoControllerTrigger.
|
||||
* Use `create(ProtoControllerTriggerSchema)` to create a new message.
|
||||
*/
|
||||
export const ProtoControllerTriggerSchema: GenMessage<ProtoControllerTrigger> = /*@__PURE__*/
|
||||
messageDesc(file_types, 10);
|
||||
|
||||
/**
|
||||
* ControllerSticks message
|
||||
*
|
||||
* @generated from message proto.ProtoControllerStick
|
||||
*/
|
||||
export type ProtoControllerStick = Message<"proto.ProtoControllerStick"> & {
|
||||
/**
|
||||
* Fixed value "ControllerStick"
|
||||
*
|
||||
* @generated from field: string type = 1;
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* Slot number (0-3)
|
||||
*
|
||||
* @generated from field: int32 slot = 2;
|
||||
*/
|
||||
slot: number;
|
||||
|
||||
/**
|
||||
* Stick number (0 for left, 1 for right)
|
||||
*
|
||||
* @generated from field: int32 stick = 3;
|
||||
*/
|
||||
stick: number;
|
||||
|
||||
/**
|
||||
* X axis value (-32768 to 32767)
|
||||
*
|
||||
* @generated from field: int32 x = 4;
|
||||
*/
|
||||
x: number;
|
||||
|
||||
/**
|
||||
* Y axis value (-32768 to 32767)
|
||||
*
|
||||
* @generated from field: int32 y = 5;
|
||||
*/
|
||||
y: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message proto.ProtoControllerStick.
|
||||
* Use `create(ProtoControllerStickSchema)` to create a new message.
|
||||
*/
|
||||
export const ProtoControllerStickSchema: GenMessage<ProtoControllerStick> = /*@__PURE__*/
|
||||
messageDesc(file_types, 11);
|
||||
|
||||
/**
|
||||
* ControllerAxis message
|
||||
*
|
||||
* @generated from message proto.ProtoControllerAxis
|
||||
*/
|
||||
export type ProtoControllerAxis = Message<"proto.ProtoControllerAxis"> & {
|
||||
/**
|
||||
* Fixed value "ControllerAxis"
|
||||
*
|
||||
* @generated from field: string type = 1;
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* Slot number (0-3)
|
||||
*
|
||||
* @generated from field: int32 slot = 2;
|
||||
*/
|
||||
slot: number;
|
||||
|
||||
/**
|
||||
* Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
||||
*
|
||||
* @generated from field: int32 axis = 3;
|
||||
*/
|
||||
axis: number;
|
||||
|
||||
/**
|
||||
* axis value (-1 to 1)
|
||||
*
|
||||
* @generated from field: int32 value = 4;
|
||||
*/
|
||||
value: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message proto.ProtoControllerAxis.
|
||||
* Use `create(ProtoControllerAxisSchema)` to create a new message.
|
||||
*/
|
||||
export const ProtoControllerAxisSchema: GenMessage<ProtoControllerAxis> = /*@__PURE__*/
|
||||
messageDesc(file_types, 12);
|
||||
|
||||
/**
|
||||
* ControllerRumble message
|
||||
*
|
||||
* @generated from message proto.ProtoControllerRumble
|
||||
*/
|
||||
export type ProtoControllerRumble = Message<"proto.ProtoControllerRumble"> & {
|
||||
/**
|
||||
* Fixed value "ControllerRumble"
|
||||
*
|
||||
* @generated from field: string type = 1;
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* Slot number (0-3)
|
||||
*
|
||||
* @generated from field: int32 slot = 2;
|
||||
*/
|
||||
slot: number;
|
||||
|
||||
/**
|
||||
* Low frequency rumble (0-65535)
|
||||
*
|
||||
* @generated from field: int32 low_frequency = 3;
|
||||
*/
|
||||
lowFrequency: number;
|
||||
|
||||
/**
|
||||
* High frequency rumble (0-65535)
|
||||
*
|
||||
* @generated from field: int32 high_frequency = 4;
|
||||
*/
|
||||
highFrequency: number;
|
||||
|
||||
/**
|
||||
* Duration in milliseconds
|
||||
*
|
||||
* @generated from field: int32 duration = 5;
|
||||
*/
|
||||
duration: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message proto.ProtoControllerRumble.
|
||||
* Use `create(ProtoControllerRumbleSchema)` to create a new message.
|
||||
*/
|
||||
export const ProtoControllerRumbleSchema: GenMessage<ProtoControllerRumble> = /*@__PURE__*/
|
||||
messageDesc(file_types, 13);
|
||||
|
||||
/**
|
||||
* Union of all Input types
|
||||
*
|
||||
@@ -260,6 +547,48 @@ export type ProtoInput = Message<"proto.ProtoInput"> & {
|
||||
*/
|
||||
value: ProtoKeyUp;
|
||||
case: "keyUp";
|
||||
} | {
|
||||
/**
|
||||
* @generated from field: proto.ProtoControllerAttach controller_attach = 8;
|
||||
*/
|
||||
value: ProtoControllerAttach;
|
||||
case: "controllerAttach";
|
||||
} | {
|
||||
/**
|
||||
* @generated from field: proto.ProtoControllerDetach controller_detach = 9;
|
||||
*/
|
||||
value: ProtoControllerDetach;
|
||||
case: "controllerDetach";
|
||||
} | {
|
||||
/**
|
||||
* @generated from field: proto.ProtoControllerButton controller_button = 10;
|
||||
*/
|
||||
value: ProtoControllerButton;
|
||||
case: "controllerButton";
|
||||
} | {
|
||||
/**
|
||||
* @generated from field: proto.ProtoControllerTrigger controller_trigger = 11;
|
||||
*/
|
||||
value: ProtoControllerTrigger;
|
||||
case: "controllerTrigger";
|
||||
} | {
|
||||
/**
|
||||
* @generated from field: proto.ProtoControllerStick controller_stick = 12;
|
||||
*/
|
||||
value: ProtoControllerStick;
|
||||
case: "controllerStick";
|
||||
} | {
|
||||
/**
|
||||
* @generated from field: proto.ProtoControllerAxis controller_axis = 13;
|
||||
*/
|
||||
value: ProtoControllerAxis;
|
||||
case: "controllerAxis";
|
||||
} | {
|
||||
/**
|
||||
* @generated from field: proto.ProtoControllerRumble controller_rumble = 14;
|
||||
*/
|
||||
value: ProtoControllerRumble;
|
||||
case: "controllerRumble";
|
||||
} | { case: undefined; value?: undefined };
|
||||
};
|
||||
|
||||
@@ -268,5 +597,5 @@ export type ProtoInput = Message<"proto.ProtoInput"> & {
|
||||
* Use `create(ProtoInputSchema)` to create a new message.
|
||||
*/
|
||||
export const ProtoInputSchema: GenMessage<ProtoInput> = /*@__PURE__*/
|
||||
messageDesc(file_types, 7);
|
||||
messageDesc(file_types, 14);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
SafeStream,
|
||||
} from "./messages";
|
||||
import { webSockets } from "@libp2p/websockets";
|
||||
import { webTransport } from "@libp2p/webtransport";
|
||||
import { createLibp2p, Libp2p } from "libp2p";
|
||||
import { noise } from "@chainsafe/libp2p-noise";
|
||||
import { yamux } from "@chainsafe/libp2p-yamux";
|
||||
@@ -13,9 +14,6 @@ import { multiaddr } from "@multiformats/multiaddr";
|
||||
import { Connection } from "@libp2p/interface";
|
||||
import { ping } from "@libp2p/ping";
|
||||
|
||||
//FIXME: Sometimes the room will wait to say offline, then appear to be online after retrying :D
|
||||
// This works for me, with my trashy internet, does it work for you as well?
|
||||
|
||||
const NESTRI_PROTOCOL_STREAM_REQUEST = "/nestri-relay/stream-request/1.0.0";
|
||||
|
||||
export class WebRTCStream {
|
||||
@@ -30,8 +28,9 @@ export class WebRTCStream {
|
||||
private _connectionTimer: NodeJS.Timeout | NodeJS.Timer | undefined = undefined;
|
||||
private _serverURL: string | undefined = undefined;
|
||||
private _roomName: string | undefined = undefined;
|
||||
private _isConnected: boolean = false; // Add flag to track connection state
|
||||
currentFrameRate: number = 60;
|
||||
private _isConnected: boolean = false;
|
||||
private _dataChannelCallbacks: Array<(data: any) => void> = [];
|
||||
currentFrameRate: number = 100;
|
||||
|
||||
constructor(
|
||||
serverURL: string,
|
||||
@@ -59,7 +58,7 @@ export class WebRTCStream {
|
||||
console.log("Setting up libp2p");
|
||||
|
||||
this._p2p = await createLibp2p({
|
||||
transports: [webSockets()],
|
||||
transports: [webSockets(), webTransport()],
|
||||
connectionEncrypters: [noise()],
|
||||
streamMuxers: [yamux()],
|
||||
connectionGater: {
|
||||
@@ -219,7 +218,8 @@ export class WebRTCStream {
|
||||
}
|
||||
|
||||
private _checkConnectionState() {
|
||||
if (!this._pc) return;
|
||||
if (!this._pc || !this._p2p || !this._p2pConn)
|
||||
return;
|
||||
|
||||
console.debug("Checking connection state:", {
|
||||
connectionState: this._pc.connectionState,
|
||||
@@ -267,9 +267,9 @@ export class WebRTCStream {
|
||||
this._pc.connectionState === "closed" ||
|
||||
this._pc.iceConnectionState === "failed"
|
||||
) {
|
||||
console.log("Connection failed or closed, attempting reconnect");
|
||||
this._isConnected = false; // Reset connected state
|
||||
this._handleConnectionFailure();
|
||||
console.log("PeerConnection failed or closed");
|
||||
//this._isConnected = false; // Reset connected state
|
||||
//this._handleConnectionFailure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +318,7 @@ export class WebRTCStream {
|
||||
console.error("Error closing data channel:", err);
|
||||
}
|
||||
this._dataChannel = undefined;
|
||||
this._dataChannelCallbacks = [];
|
||||
}
|
||||
this._isConnected = false; // Reset connected state during cleanup
|
||||
}
|
||||
@@ -329,15 +330,31 @@ export class WebRTCStream {
|
||||
}
|
||||
}
|
||||
|
||||
public addDataChannelCallback(callback: (data: any) => void) {
|
||||
this._dataChannelCallbacks.push(callback);
|
||||
}
|
||||
|
||||
public removeDataChannelCallback(callback: (data: any) => void) {
|
||||
this._dataChannelCallbacks = this._dataChannelCallbacks.filter(cb => cb !== callback);
|
||||
}
|
||||
|
||||
private _setupDataChannelEvents() {
|
||||
if (!this._dataChannel) return;
|
||||
|
||||
this._dataChannel.onclose = () => console.log("sendChannel has closed");
|
||||
this._dataChannel.onopen = () => console.log("sendChannel has opened");
|
||||
this._dataChannel.onmessage = (e) =>
|
||||
console.log(
|
||||
`Message from DataChannel '${this._dataChannel?.label}' payload '${e.data}'`,
|
||||
);
|
||||
this._dataChannel.onmessage = (event => {
|
||||
// Parse as ProtoBuf message
|
||||
const data = event.data;
|
||||
// Call registered callback if exists
|
||||
this._dataChannelCallbacks.forEach((callback) => {
|
||||
try {
|
||||
callback(data);
|
||||
} catch (err) {
|
||||
console.error("Error in data channel callback:", err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _gatherFrameRate() {
|
||||
|
||||
18
packages/patches/bubblewrap/bubbleunheck.patch
Normal file
18
packages/patches/bubblewrap/bubbleunheck.patch
Normal file
@@ -0,0 +1,18 @@
|
||||
diff --git a/bubblewrap.c b/bubblewrap.c
|
||||
index f8728c7..42cfe2e 100644
|
||||
--- a/bubblewrap.c
|
||||
+++ b/bubblewrap.c
|
||||
@@ -876,13 +876,6 @@ acquire_privs (void)
|
||||
/* Keep only the required capabilities for setup */
|
||||
set_required_caps ();
|
||||
}
|
||||
- else if (real_uid != 0 && has_caps ())
|
||||
- {
|
||||
- /* We have some capabilities in the non-setuid case, which should not happen.
|
||||
- Probably caused by the binary being setcap instead of setuid which we
|
||||
- don't support anymore */
|
||||
- die ("Unexpected capabilities but not setuid, old file caps config?");
|
||||
- }
|
||||
else if (real_uid == 0)
|
||||
{
|
||||
/* If our uid is 0, default to inheriting all caps; the caller
|
||||
@@ -11,6 +11,6 @@
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^9.4.2",
|
||||
"@nestri/input": "*",
|
||||
"astro": "5.13.2"
|
||||
"astro": "5.14.5"
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ if (envs_map.size > 0) {
|
||||
</DefaultLayout>
|
||||
|
||||
<script>
|
||||
import { Mouse, Keyboard, WebRTCStream } from "@nestri/input";
|
||||
import { Mouse, Keyboard, Controller, WebRTCStream } from "@nestri/input";
|
||||
const ENVS = document.getElementById("ENVS")!.dataset.envs as string;
|
||||
let ENVS_MAP: Map<string, string | undefined> | null = null;
|
||||
if (ENVS && ENVS.length > 0) {
|
||||
@@ -32,6 +32,11 @@ if (envs_map.size > 0) {
|
||||
console.debug("ENVS_MAP:", ENVS_MAP);
|
||||
}
|
||||
|
||||
// Method which returns true if mobile device
|
||||
const isMobile = () => {
|
||||
return /Mobi|Android/i.test(navigator.userAgent);
|
||||
};
|
||||
|
||||
// Elements
|
||||
const canvas = document.getElementById("playCanvas")! as HTMLCanvasElement;
|
||||
const offlineText = document.getElementById("offlineText")! as HTMLHeadingElement;
|
||||
@@ -82,51 +87,88 @@ if (envs_map.size > 0) {
|
||||
// Input
|
||||
let nestriMouse: Mouse | null = null;
|
||||
let nestriKeyboard: Keyboard | null = null;
|
||||
let nestriControllers: Controller[] = [];
|
||||
|
||||
window.addEventListener("gamepadconnected", (e) => {
|
||||
// Ignore gamepads with id including "nestri"
|
||||
console.log("Gamepad connected:", e.gamepad);
|
||||
if (e.gamepad.id.toLowerCase().includes("nestri"))
|
||||
return;
|
||||
|
||||
const controller = new Controller({
|
||||
webrtc: stream,
|
||||
e: e,
|
||||
});
|
||||
nestriControllers.push(controller);
|
||||
});
|
||||
window.addEventListener("gamepaddisconnected", (e) => {
|
||||
console.log("Gamepad disconnected:", e.gamepad);
|
||||
if (e.gamepad.id.toLowerCase().includes("nestri"))
|
||||
return;
|
||||
|
||||
let disconnected = nestriControllers.find((c) => c.getSlot() === e.gamepad.index);
|
||||
if (disconnected) {
|
||||
disconnected.dispose();
|
||||
nestriControllers = nestriControllers.filter((c) => c !== disconnected);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("pointerlockchange", () => {
|
||||
if (document.pointerLockElement === canvas) {
|
||||
if (nestriMouse || nestriKeyboard)
|
||||
if (nestriMouse)
|
||||
return;
|
||||
|
||||
nestriMouse = new Mouse({
|
||||
canvas: canvas,
|
||||
webrtc: stream,
|
||||
});
|
||||
nestriKeyboard = new Keyboard({
|
||||
canvas: canvas,
|
||||
webrtc: stream,
|
||||
});
|
||||
} else {
|
||||
if (nestriMouse) {
|
||||
nestriMouse.dispose();
|
||||
nestriMouse = null;
|
||||
}
|
||||
if (nestriKeyboard) {
|
||||
nestriKeyboard.dispose();
|
||||
nestriKeyboard = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("fullscreenchange", () => {
|
||||
if (document.fullscreenElement) {
|
||||
if (nestriKeyboard)
|
||||
return;
|
||||
|
||||
nestriKeyboard = new Keyboard({
|
||||
webrtc: stream,
|
||||
});
|
||||
|
||||
nestriControllers.forEach((c) => c.run());
|
||||
|
||||
if ("keyboard" in navigator && "lock" in (navigator.keyboard as any)) {
|
||||
const keys = [
|
||||
"AltLeft", "AltRight", "Tab", "Escape",
|
||||
"ContextMenu", "MetaLeft", "MetaRight"
|
||||
];
|
||||
|
||||
(navigator.keyboard as any).lock(keys).then(() => {
|
||||
console.log("Keyboard lock acquired");
|
||||
}).catch((err: any) => {
|
||||
console.error("Failed to acquire keyboard lock:", err);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (nestriKeyboard) {
|
||||
nestriKeyboard.dispose();
|
||||
nestriKeyboard = null;
|
||||
}
|
||||
nestriControllers.forEach((c) => c.stop());
|
||||
}
|
||||
})
|
||||
|
||||
const lockPlay = async function () {
|
||||
if (document.fullscreenElement)
|
||||
return;
|
||||
|
||||
await canvas.requestFullscreen();
|
||||
await canvas.requestPointerLock();
|
||||
|
||||
if (document.fullscreenElement !== null) {
|
||||
if ("keyboard" in navigator && "lock" in (navigator.keyboard as any)) {
|
||||
const keys = [
|
||||
"AltLeft", "AltRight", "Tab", "Escape",
|
||||
"ContextMenu", "MetaLeft", "MetaRight"
|
||||
];
|
||||
|
||||
try {
|
||||
await (navigator.keyboard as any).lock(keys);
|
||||
console.log("Keyboard lock acquired");
|
||||
} catch (e) {
|
||||
console.warn("Keyboard lock failed:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isMobile())
|
||||
await canvas.requestPointerLock();
|
||||
};
|
||||
|
||||
canvas.addEventListener("click", lockPlay);
|
||||
|
||||
1
packages/relay/.dockerignore
Normal file
1
packages/relay/.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
persist-data/
|
||||
@@ -1,59 +1,50 @@
|
||||
module relay
|
||||
|
||||
go 1.24
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/libp2p/go-libp2p v0.41.1
|
||||
github.com/libp2p/go-libp2p-pubsub v0.13.1
|
||||
github.com/libp2p/go-libp2p v0.44.0
|
||||
github.com/libp2p/go-libp2p-pubsub v0.15.0
|
||||
github.com/libp2p/go-reuseport v0.4.0
|
||||
github.com/multiformats/go-multiaddr v0.15.0
|
||||
github.com/multiformats/go-multiaddr v0.16.1
|
||||
github.com/oklog/ulid/v2 v2.1.1
|
||||
github.com/pion/ice/v4 v4.0.10
|
||||
github.com/pion/interceptor v0.1.38
|
||||
github.com/pion/rtp v1.8.15
|
||||
github.com/pion/webrtc/v4 v4.1.1
|
||||
google.golang.org/protobuf v1.36.6
|
||||
github.com/pion/interceptor v0.1.41
|
||||
github.com/pion/rtp v1.8.24
|
||||
github.com/pion/webrtc/v4 v4.1.6
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
google.golang.org/protobuf v1.36.10
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/containerd/cgroups v1.1.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/elastic/gosigar v0.14.3 // indirect
|
||||
github.com/filecoin-project/go-clock v0.1.0 // indirect
|
||||
github.com/flynn/noise v1.1.0 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/gopacket v1.1.19 // indirect
|
||||
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/huin/goupnp v1.3.0 // indirect
|
||||
github.com/ipfs/go-cid v0.5.0 // indirect
|
||||
github.com/ipfs/go-log/v2 v2.6.0 // indirect
|
||||
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
|
||||
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/koron/go-ssdp v0.0.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/koron/go-ssdp v0.1.0 // indirect
|
||||
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
||||
github.com/libp2p/go-flow-metrics v0.3.0 // indirect
|
||||
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
|
||||
github.com/libp2p/go-msgio v0.3.0 // indirect
|
||||
github.com/libp2p/go-netroute v0.2.2 // indirect
|
||||
github.com/libp2p/go-yamux/v5 v5.0.0 // indirect
|
||||
github.com/libp2p/go-netroute v0.3.0 // indirect
|
||||
github.com/libp2p/go-yamux/v5 v5.1.0 // indirect
|
||||
github.com/libp2p/zeroconf/v2 v2.2.0 // indirect
|
||||
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/miekg/dns v1.1.66 // indirect
|
||||
github.com/miekg/dns v1.1.68 // indirect
|
||||
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
|
||||
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
@@ -63,54 +54,51 @@ require (
|
||||
github.com/multiformats/go-multiaddr-dns v0.4.1 // indirect
|
||||
github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
|
||||
github.com/multiformats/go-multibase v0.2.0 // indirect
|
||||
github.com/multiformats/go-multicodec v0.9.0 // indirect
|
||||
github.com/multiformats/go-multicodec v0.10.0 // indirect
|
||||
github.com/multiformats/go-multihash v0.2.3 // indirect
|
||||
github.com/multiformats/go-multistream v0.6.0 // indirect
|
||||
github.com/multiformats/go-varint v0.0.7 // indirect
|
||||
github.com/multiformats/go-multistream v0.6.1 // indirect
|
||||
github.com/multiformats/go-varint v0.1.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.2.1 // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pion/datachannel v1.5.10 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.12 // indirect
|
||||
github.com/pion/dtls/v3 v3.0.6 // indirect
|
||||
github.com/pion/logging v0.2.3 // indirect
|
||||
github.com/pion/dtls/v3 v3.0.7 // indirect
|
||||
github.com/pion/logging v0.2.4 // indirect
|
||||
github.com/pion/mdns/v2 v2.0.7 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.15 // indirect
|
||||
github.com/pion/sctp v1.8.39 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.13 // indirect
|
||||
github.com/pion/srtp/v3 v3.0.4 // indirect
|
||||
github.com/pion/rtcp v1.2.16 // indirect
|
||||
github.com/pion/sctp v1.8.40 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.16 // indirect
|
||||
github.com/pion/srtp/v3 v3.0.8 // indirect
|
||||
github.com/pion/stun v0.6.1 // indirect
|
||||
github.com/pion/stun/v3 v3.0.0 // indirect
|
||||
github.com/pion/transport/v2 v2.2.10 // indirect
|
||||
github.com/pion/transport/v3 v3.0.7 // indirect
|
||||
github.com/pion/turn/v4 v4.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_golang v1.22.0 // indirect
|
||||
github.com/pion/transport/v3 v3.0.8 // indirect
|
||||
github.com/pion/turn/v4 v4.1.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.64.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/prometheus/common v0.67.1 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.52.0 // indirect
|
||||
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 // indirect
|
||||
github.com/raulk/go-watchdog v1.3.0 // indirect
|
||||
github.com/quic-go/quic-go v0.55.0 // indirect
|
||||
github.com/quic-go/webtransport-go v0.9.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/wlynxg/anet v0.0.5 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/dig v1.19.0 // indirect
|
||||
go.uber.org/fx v1.24.0 // indirect
|
||||
go.uber.org/mock v0.5.2 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251017212417-90e834f514db // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/net v0.46.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20251014153721-24f779f6aaef // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
lukechampine.com/blake3 v1.4.1 // indirect
|
||||
)
|
||||
|
||||
@@ -9,7 +9,6 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -19,17 +18,8 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
|
||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -39,13 +29,7 @@ github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
|
||||
github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/uo=
|
||||
github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
|
||||
github.com/filecoin-project/go-clock v0.1.0 h1:SFbYIM75M8NnFm1yMHhN9Ahy3W5bEZV9gd6MPfXbKVU=
|
||||
github.com/filecoin-project/go-clock v0.1.0/go.mod h1:4uB/O4PvOjlx1VCMdZ9MyDZXRm//gkj1ELEbxfI1AZs=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
@@ -57,16 +41,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -77,17 +52,12 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4=
|
||||
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
@@ -103,8 +73,6 @@ github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
|
||||
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
||||
github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
|
||||
github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=
|
||||
github.com/ipfs/go-log/v2 v2.6.0 h1:2Nu1KKQQ2ayonKp4MPo6pXCjqw1ULc9iohRqWV5EYqg=
|
||||
github.com/ipfs/go-log/v2 v2.6.0/go.mod h1:p+Efr3qaY5YXpx9TX7MoLCSEZX5boSWj9wh86P5HJa8=
|
||||
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
||||
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||
github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=
|
||||
@@ -112,15 +80,14 @@ github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPw
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/koron/go-ssdp v0.0.6 h1:Jb0h04599eq/CY7rB5YEqPS83HmRfHP2azkxMN2rFtU=
|
||||
github.com/koron/go-ssdp v0.0.6/go.mod h1:0R9LfRJGek1zWTjN3JUNlm5INCDYGpRDfAptnct63fI=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/koron/go-ssdp v0.1.0 h1:ckl5x5H6qSNFmi+wCuROvvGUu2FQnMbQrU95IHCcv3Y=
|
||||
github.com/koron/go-ssdp v0.1.0/go.mod h1:GltaDBjtK1kemZOusWYLGotV0kBeEf59Bp0wtSB0uyU=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -130,39 +97,41 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
|
||||
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
|
||||
github.com/libp2p/go-flow-metrics v0.3.0 h1:q31zcHUvHnwDO0SHaukewPYgwOBSxtt830uJtUx6784=
|
||||
github.com/libp2p/go-flow-metrics v0.3.0/go.mod h1:nuhlreIwEguM1IvHAew3ij7A8BMlyHQJ279ao24eZZo=
|
||||
github.com/libp2p/go-libp2p v0.41.1 h1:8ecNQVT5ev/jqALTvisSJeVNvXYJyK4NhQx1nNRXQZE=
|
||||
github.com/libp2p/go-libp2p v0.41.1/go.mod h1:DcGTovJzQl/I7HMrby5ZRjeD0kQkGiy+9w6aEkSZpRI=
|
||||
github.com/libp2p/go-libp2p v0.44.0 h1:5Gtt8OrF8yiXmH+Mx4+/iBeFRMK1TY3a8OrEBDEqAvs=
|
||||
github.com/libp2p/go-libp2p v0.44.0/go.mod h1:NovCojezAt4dnDd4fH048K7PKEqH0UFYYqJRjIIu8zc=
|
||||
github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=
|
||||
github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8=
|
||||
github.com/libp2p/go-libp2p-pubsub v0.13.1 h1:tV3ttzzZSCk0EtEXnxVmWIXgjVxXx+20Jwjbs/Ctzjo=
|
||||
github.com/libp2p/go-libp2p-pubsub v0.13.1/go.mod h1:MKPU5vMI8RRFyTP0HfdsF9cLmL1nHAeJm44AxJGJx44=
|
||||
github.com/libp2p/go-libp2p-pubsub v0.15.0 h1:cG7Cng2BT82WttmPFMi50gDNV+58K626m/wR00vGL1o=
|
||||
github.com/libp2p/go-libp2p-pubsub v0.15.0/go.mod h1:lr4oE8bFgQaifRcoc2uWhWWiK6tPdOEKpUuR408GFN4=
|
||||
github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=
|
||||
github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg=
|
||||
github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=
|
||||
github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=
|
||||
github.com/libp2p/go-netroute v0.2.2 h1:Dejd8cQ47Qx2kRABg6lPwknU7+nBnFRpko45/fFPuZ8=
|
||||
github.com/libp2p/go-netroute v0.2.2/go.mod h1:Rntq6jUAH0l9Gg17w5bFGhcC9a+vk4KNXs6s7IljKYE=
|
||||
github.com/libp2p/go-netroute v0.3.0 h1:nqPCXHmeNmgTJnktosJ/sIef9hvwYCrsLxXmfNks/oc=
|
||||
github.com/libp2p/go-netroute v0.3.0/go.mod h1:Nkd5ShYgSMS5MUKy/MU2T57xFoOKvvLR92Lic48LEyA=
|
||||
github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=
|
||||
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
|
||||
github.com/libp2p/go-yamux/v5 v5.0.0 h1:2djUh96d3Jiac/JpGkKs4TO49YhsfLopAoryfPmf+Po=
|
||||
github.com/libp2p/go-yamux/v5 v5.0.0/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU=
|
||||
github.com/libp2p/go-yamux/v5 v5.1.0 h1:8Qlxj4E9JGJAQVW6+uj2o7mqkqsIVlSUGmTWhlXzoHE=
|
||||
github.com/libp2p/go-yamux/v5 v5.1.0/go.mod h1:tgIQ07ObtRR/I0IWsFOyQIL9/dR5UXgc2s8xKmNZv1o=
|
||||
github.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv53Q=
|
||||
github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marcopolo/simnet v0.0.1 h1:rSMslhPz6q9IvJeFWDoMGxMIrlsbXau3NkuIXHGJxfg=
|
||||
github.com/marcopolo/simnet v0.0.1/go.mod h1:WDaQkgLAjqDUEBAOXz22+1j6wXKfGlC5sD5XWt3ddOs=
|
||||
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=
|
||||
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
|
||||
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
|
||||
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
|
||||
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
||||
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=
|
||||
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=
|
||||
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=
|
||||
@@ -183,36 +152,29 @@ github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYg
|
||||
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
|
||||
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
|
||||
github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=
|
||||
github.com/multiformats/go-multiaddr v0.15.0 h1:zB/HeaI/apcZiTDwhY5YqMvNVl/oQYvs3XySU+qeAVo=
|
||||
github.com/multiformats/go-multiaddr v0.15.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0=
|
||||
github.com/multiformats/go-multiaddr v0.16.1 h1:fgJ0Pitow+wWXzN9do+1b8Pyjmo8m5WhGfzpL82MpCw=
|
||||
github.com/multiformats/go-multiaddr v0.16.1/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0=
|
||||
github.com/multiformats/go-multiaddr-dns v0.4.1 h1:whi/uCLbDS3mSEUMb1MsoT4uzUeZB0N32yzufqS0i5M=
|
||||
github.com/multiformats/go-multiaddr-dns v0.4.1/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc=
|
||||
github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
|
||||
github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=
|
||||
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
|
||||
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
|
||||
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
|
||||
github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
|
||||
github.com/multiformats/go-multicodec v0.10.0 h1:UpP223cig/Cx8J76jWt91njpK3GTAO1w02sdcjZDSuc=
|
||||
github.com/multiformats/go-multicodec v0.10.0/go.mod h1:wg88pM+s2kZJEQfRCKBNU+g32F5aWBEjyFHXvZLTcLI=
|
||||
github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
|
||||
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
|
||||
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
|
||||
github.com/multiformats/go-multistream v0.6.0 h1:ZaHKbsL404720283o4c/IHQXiS6gb8qAN5EIJ4PN5EA=
|
||||
github.com/multiformats/go-multistream v0.6.0/go.mod h1:MOyoG5otO24cHIg8kf9QW2/NozURlkP/rvi2FQJyCPg=
|
||||
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
|
||||
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
|
||||
github.com/multiformats/go-multistream v0.6.1 h1:4aoX5v6T+yWmc2raBHsTvzmFhOI8WVOer28DeBBEYdQ=
|
||||
github.com/multiformats/go-multistream v0.6.1/go.mod h1:ksQf6kqHAb6zIsyw7Zm+gAuVo57Qbq84E27YlYqavqw=
|
||||
github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOoETFs5dI=
|
||||
github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
|
||||
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
|
||||
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
|
||||
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
@@ -222,29 +184,29 @@ github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oL
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk=
|
||||
github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
|
||||
github.com/pion/dtls/v3 v3.0.6 h1:7Hkd8WhAJNbRgq9RgdNh1aaWlZlGpYTzdqjy9x9sK2E=
|
||||
github.com/pion/dtls/v3 v3.0.6/go.mod h1:iJxNQ3Uhn1NZWOMWlLxEEHAN5yX7GyPvvKw04v9bzYU=
|
||||
github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q=
|
||||
github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8=
|
||||
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
|
||||
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
|
||||
github.com/pion/interceptor v0.1.38 h1:Mgt3XIIq47uR5vcLLahfRucE6tFPjxHak+z5ZZFEzLU=
|
||||
github.com/pion/interceptor v0.1.38/go.mod h1:HS9X+Ue5LDE6q2C2tuvOuO83XkBdJFgn6MBDtfoJX4Q=
|
||||
github.com/pion/interceptor v0.1.41 h1:NpvX3HgWIukTf2yTBVjVGFXtpSpWgXjqz7IIpu7NsOw=
|
||||
github.com/pion/interceptor v0.1.41/go.mod h1:nEt4187unvRXJFyjiw00GKo+kIuXMWQI9K89fsosDLY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
|
||||
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
|
||||
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
||||
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
|
||||
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
||||
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
||||
github.com/pion/rtp v1.8.15 h1:MuhuGn1cxpVCPLNY1lI7F1tQ8Spntpgf12ob+pOYT8s=
|
||||
github.com/pion/rtp v1.8.15/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
|
||||
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
|
||||
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
|
||||
github.com/pion/sdp/v3 v3.0.13 h1:uN3SS2b+QDZnWXgdr69SM8KB4EbcnPnPf2Laxhty/l4=
|
||||
github.com/pion/sdp/v3 v3.0.13/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
|
||||
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
|
||||
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
|
||||
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
|
||||
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
|
||||
github.com/pion/rtp v1.8.24 h1:+ICyZXUQDv95EsHN70RrA4XKJf5MGWyC6QQc1u6/ynI=
|
||||
github.com/pion/rtp v1.8.24/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
|
||||
github.com/pion/sctp v1.8.40 h1:bqbgWYOrUhsYItEnRObUYZuzvOMsVplS3oNgzedBlG8=
|
||||
github.com/pion/sctp v1.8.40/go.mod h1:SPBBUENXE6ThkEksN5ZavfAhFYll+h+66ZiG6IZQuzo=
|
||||
github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo=
|
||||
github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo=
|
||||
github.com/pion/srtp/v3 v3.0.8 h1:RjRrjcIeQsilPzxvdaElN0CpuQZdMvcl9VZ5UY9suUM=
|
||||
github.com/pion/srtp/v3 v3.0.8/go.mod h1:2Sq6YnDH7/UDCvkSoHSDNDeyBcFgWL0sAVycVbAsXFg=
|
||||
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
|
||||
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
|
||||
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
|
||||
@@ -253,43 +215,36 @@ github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1A
|
||||
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
||||
github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q=
|
||||
github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E=
|
||||
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
|
||||
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
|
||||
github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=
|
||||
github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=
|
||||
github.com/pion/webrtc/v4 v4.1.1 h1:PMFPtLg1kpD2pVtun+LGUzA3k54JdFl87WO0Z1+HKug=
|
||||
github.com/pion/webrtc/v4 v4.1.1/go.mod h1:cgEGkcpxGkT6Di2ClBYO5lP9mFXbCfEOrkYUpjjCQO4=
|
||||
github.com/pion/transport/v3 v3.0.8 h1:oI3myyYnTKUSTthu/NZZ8eu2I5sHbxbUNNFW62olaYc=
|
||||
github.com/pion/transport/v3 v3.0.8/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
|
||||
github.com/pion/turn/v4 v4.1.1 h1:9UnY2HB99tpDyz3cVVZguSxcqkJ1DsTSZ+8TGruh4fc=
|
||||
github.com/pion/turn/v4 v4.1.1/go.mod h1:2123tHk1O++vmjI5VSD0awT50NywDAq5A2NNNU4Jjs8=
|
||||
github.com/pion/webrtc/v4 v4.1.6 h1:srHH2HwvCGwPba25EYJgUzgLqCQoXl1VCUnrGQMSzUw=
|
||||
github.com/pion/webrtc/v4 v4.1.6/go.mod h1:wKecGRlkl3ox/As/MYghJL+b/cVXMEhoPMJWPuGQFhU=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
|
||||
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI=
|
||||
github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA=
|
||||
github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
|
||||
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 h1:4WFk6u3sOT6pLa1kQ50ZVdm8BQFgJNA117cepZxtLIg=
|
||||
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw=
|
||||
github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk=
|
||||
github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU=
|
||||
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
|
||||
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
|
||||
github.com/quic-go/webtransport-go v0.9.0 h1:jgys+7/wm6JarGDrW+lD/r9BGqBAmqY/ssklE09bA70=
|
||||
github.com/quic-go/webtransport-go v0.9.0/go.mod h1:4FUYIiUc75XSsF6HShcLeXXYZJ9AGwo/xh3L8M/P1ao=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
@@ -311,10 +266,8 @@ github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b
|
||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
@@ -323,15 +276,13 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||
@@ -341,20 +292,20 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
|
||||
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||
go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
|
||||
go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -369,22 +320,20 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
||||
golang.org/x/exp v0.0.0-20251017212417-90e834f514db h1:by6IehL4BH5k3e3SJmcoNbOobMey2SLpAF79iPOEBvw=
|
||||
golang.org/x/exp v0.0.0-20251017212417-90e834f514db/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -406,8 +355,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -423,17 +372,14 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -444,13 +390,14 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20251014153721-24f779f6aaef h1:5xFtU4tmJMJSxSeDlr1dgBff2tDXrq0laLdS1EA3LYw=
|
||||
golang.org/x/telemetry v0.0.0-20251014153721-24f779f6aaef/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -467,24 +414,24 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -505,10 +452,9 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
|
||||
@@ -30,29 +30,12 @@ func InitWebRTCAPI() error {
|
||||
return fmt.Errorf("failed to register extensions: %w", err)
|
||||
}
|
||||
|
||||
// Default codecs cover most of our needs
|
||||
// Default codecs cover our needs
|
||||
err = mediaEngine.RegisterDefaultCodecs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add H.265 for special cases
|
||||
videoRTCPFeedback := []webrtc.RTCPFeedback{{"goog-remb", ""}, {"ccm", "fir"}, {"nack", ""}, {"nack", "pli"}}
|
||||
for _, codec := range []webrtc.RTPCodecParameters{
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH265, ClockRate: 90000, RTCPFeedback: videoRTCPFeedback},
|
||||
PayloadType: 48,
|
||||
},
|
||||
{
|
||||
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeRTX, ClockRate: 90000, SDPFmtpLine: "apt=48"},
|
||||
PayloadType: 49,
|
||||
},
|
||||
} {
|
||||
if err = mediaEngine.RegisterCodec(codec, webrtc.RTPCodecTypeVideo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Interceptor registry
|
||||
interceptorRegistry := &interceptor.Registry{}
|
||||
|
||||
@@ -98,7 +81,8 @@ func InitWebRTCAPI() error {
|
||||
slog.Info("Using WebRTC UDP Port Range", "start", flags.WebRTCUDPStart, "end", flags.WebRTCUDPEnd)
|
||||
}
|
||||
|
||||
settingEngine.SetIncludeLoopbackCandidate(true) // Just in case
|
||||
// Improves speed when sending offers to browsers (https://github.com/pion/webrtc/issues/3174)
|
||||
settingEngine.SetIncludeLoopbackCandidate(true)
|
||||
|
||||
// Create a new API object with our customized settings
|
||||
globalWebRTCAPI = webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine), webrtc.WithSettingEngine(settingEngine), webrtc.WithInterceptorRegistry(interceptorRegistry))
|
||||
|
||||
@@ -24,6 +24,8 @@ type Flags struct {
|
||||
AutoAddLocalIP bool // Automatically add local IP to NAT 1 to 1 IPs
|
||||
NAT11IP string // WebRTC NAT 1 to 1 IP - allows specifying IP of relay if behind NAT
|
||||
PersistDir string // Directory to save persistent data to
|
||||
Metrics bool // Enable metrics endpoint
|
||||
MetricsPort int // Port for metrics endpoint
|
||||
}
|
||||
|
||||
func (flags *Flags) DebugLog() {
|
||||
@@ -39,6 +41,8 @@ func (flags *Flags) DebugLog() {
|
||||
"autoAddLocalIP", flags.AutoAddLocalIP,
|
||||
"webrtcNAT11IPs", flags.NAT11IP,
|
||||
"persistDir", flags.PersistDir,
|
||||
"metrics", flags.Metrics,
|
||||
"metricsPort", flags.MetricsPort,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -79,12 +83,14 @@ func InitFlags() {
|
||||
flag.IntVar(&globalFlags.WebRTCUDPStart, "webrtcUDPStart", getEnvAsInt("WEBRTC_UDP_START", 0), "WebRTC UDP port range start")
|
||||
flag.IntVar(&globalFlags.WebRTCUDPEnd, "webrtcUDPEnd", getEnvAsInt("WEBRTC_UDP_END", 0), "WebRTC UDP port range end")
|
||||
flag.StringVar(&globalFlags.STUNServer, "stunServer", getEnvAsString("STUN_SERVER", "stun.l.google.com:19302"), "WebRTC STUN server")
|
||||
flag.IntVar(&globalFlags.UDPMuxPort, "webrtcUDPMux", getEnvAsInt("WEBRTC_UDP_MUX", 8088), "WebRTC UDP mux port")
|
||||
flag.BoolVar(&globalFlags.AutoAddLocalIP, "autoAddLocalIP", getEnvAsBool("AUTO_ADD_LOCAL_IP", true), "Automatically add local IP to NAT 1 to 1 IPs")
|
||||
flag.IntVar(&globalFlags.UDPMuxPort, "webrtcUDPMux", getEnvAsInt("WEBRTC_UDP_MUX", 9099), "WebRTC UDP mux port")
|
||||
flag.BoolVar(&globalFlags.AutoAddLocalIP, "autoAddLocalIP", getEnvAsBool("AUTO_ADD_LOCAL_IP", false), "Automatically add local IP to NAT 1 to 1 IPs")
|
||||
// String with comma separated IPs
|
||||
nat11IP := ""
|
||||
flag.StringVar(&nat11IP, "webrtcNAT11IP", getEnvAsString("WEBRTC_NAT_IP", ""), "WebRTC NAT 1 to 1 IP")
|
||||
flag.StringVar(&globalFlags.PersistDir, "persistDir", getEnvAsString("PERSIST_DIR", "./persist-data"), "Directory to save persistent data to")
|
||||
flag.BoolVar(&globalFlags.Metrics, "metrics", getEnvAsBool("METRICS", false), "Enable metrics endpoint")
|
||||
flag.IntVar(&globalFlags.MetricsPort, "metricsPort", getEnvAsInt("METRICS_PORT", 3030), "Port for metrics endpoint")
|
||||
// Parse flags
|
||||
flag.Parse()
|
||||
|
||||
|
||||
@@ -4,24 +4,31 @@ import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"relay/internal/common"
|
||||
"relay/internal/shared"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p"
|
||||
pubsub "github.com/libp2p/go-libp2p-pubsub"
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/host"
|
||||
"github.com/libp2p/go-libp2p/core/network"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
|
||||
"github.com/libp2p/go-libp2p/p2p/protocol/ping"
|
||||
"github.com/libp2p/go-libp2p/p2p/security/noise"
|
||||
"github.com/libp2p/go-libp2p/p2p/transport/quicreuse"
|
||||
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
|
||||
ws "github.com/libp2p/go-libp2p/p2p/transport/websocket"
|
||||
webtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
// -- Variables --
|
||||
@@ -30,17 +37,9 @@ var globalRelay *Relay
|
||||
|
||||
// -- Structs --
|
||||
|
||||
// RelayInfo contains light information of Relay, in mesh-friendly format
|
||||
type RelayInfo struct {
|
||||
ID peer.ID
|
||||
MeshAddrs []string // Addresses of this relay
|
||||
MeshRooms *common.SafeMap[string, shared.RoomInfo] // Rooms hosted by this relay
|
||||
MeshLatencies *common.SafeMap[string, time.Duration] // Latencies to other peers from this relay
|
||||
}
|
||||
|
||||
// Relay structure enhanced with metrics and state
|
||||
type Relay struct {
|
||||
RelayInfo
|
||||
*PeerInfo
|
||||
|
||||
Host host.Host // libp2p host for peer-to-peer networking
|
||||
PubSub *pubsub.PubSub // PubSub for state synchronization
|
||||
@@ -48,7 +47,6 @@ type Relay struct {
|
||||
|
||||
// Local
|
||||
LocalRooms *common.SafeMap[ulid.ULID, *shared.Room] // room ID -> local Room struct (hosted by this relay)
|
||||
LocalMeshPeers *common.SafeMap[peer.ID, *RelayInfo] // peer ID -> mesh peer relay info (connected to this relay)
|
||||
LocalMeshConnections *common.SafeMap[peer.ID, *webrtc.PeerConnection] // peer ID -> PeerConnection (connected to this relay)
|
||||
|
||||
// Protocols
|
||||
@@ -60,11 +58,43 @@ type Relay struct {
|
||||
}
|
||||
|
||||
func NewRelay(ctx context.Context, port int, identityKey crypto.PrivKey) (*Relay, error) {
|
||||
// If metrics are enabled, start the metrics server first
|
||||
metricsOpts := make([]libp2p.Option, 0)
|
||||
var rmgr network.ResourceManager
|
||||
if common.GetFlags().Metrics {
|
||||
go func() {
|
||||
slog.Info("Starting prometheus metrics server at '/debug/metrics/prometheus'", "port", common.GetFlags().MetricsPort)
|
||||
http.Handle("/debug/metrics/prometheus", promhttp.Handler())
|
||||
if err := http.ListenAndServe(fmt.Sprintf(":%d", common.GetFlags().MetricsPort), nil); err != nil {
|
||||
slog.Error("Failed to start metrics server", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
rcmgr.MustRegisterWith(prometheus.DefaultRegisterer)
|
||||
|
||||
str, err := rcmgr.NewStatsTraceReporter()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
rmgr, err = rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(rcmgr.DefaultLimits.AutoScale()), rcmgr.WithTraceReporter(str))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
metricsOpts = append(metricsOpts, libp2p.ResourceManager(rmgr))
|
||||
metricsOpts = append(metricsOpts, libp2p.PrometheusRegisterer(prometheus.DefaultRegisterer))
|
||||
} else {
|
||||
rmgr = nil
|
||||
}
|
||||
|
||||
listenAddrs := []string{
|
||||
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port), // IPv4 - Raw TCP
|
||||
fmt.Sprintf("/ip6/::/tcp/%d", port), // IPv6 - Raw TCP
|
||||
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d/ws", port), // IPv4 - TCP WebSocket
|
||||
fmt.Sprintf("/ip6/::/tcp/%d/ws", port), // IPv6 - TCP WebSocket
|
||||
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port), // IPv4 - Raw TCP
|
||||
fmt.Sprintf("/ip6/::/tcp/%d", port), // IPv6 - Raw TCP
|
||||
fmt.Sprintf("/ip4/0.0.0.0/tcp/%d/ws", port), // IPv4 - TCP WebSocket
|
||||
fmt.Sprintf("/ip6/::/tcp/%d/ws", port), // IPv6 - TCP WebSocket
|
||||
fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic-v1/webtransport", port), // IPv4 - UDP QUIC WebTransport
|
||||
fmt.Sprintf("/ip6/::/udp/%d/quic-v1/webtransport", port), // IPv6 - UDP QUIC WebTransport
|
||||
}
|
||||
|
||||
var muAddrs []multiaddr.Multiaddr
|
||||
@@ -78,11 +108,12 @@ func NewRelay(ctx context.Context, port int, identityKey crypto.PrivKey) (*Relay
|
||||
|
||||
// Initialize libp2p host
|
||||
p2pHost, err := libp2p.New(
|
||||
// TODO: Currently static identity
|
||||
libp2p.ChainOptions(metricsOpts...),
|
||||
libp2p.Identity(identityKey),
|
||||
// Enable required transports
|
||||
libp2p.Transport(tcp.NewTCPTransport),
|
||||
libp2p.Transport(ws.New),
|
||||
libp2p.Transport(webtransport.New),
|
||||
// Other options
|
||||
libp2p.ListenAddrs(muAddrs...),
|
||||
libp2p.Security(noise.ID, noise.New),
|
||||
@@ -91,6 +122,7 @@ func NewRelay(ctx context.Context, port int, identityKey crypto.PrivKey) (*Relay
|
||||
libp2p.EnableNATService(),
|
||||
libp2p.EnableAutoNATv2(),
|
||||
libp2p.ShareTCPListener(),
|
||||
libp2p.QUICReuse(quicreuse.NewConnManager),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create libp2p host for relay: %w", err)
|
||||
@@ -105,23 +137,13 @@ func NewRelay(ctx context.Context, port int, identityKey crypto.PrivKey) (*Relay
|
||||
// Initialize Ping Service
|
||||
pingSvc := ping.NewPingService(p2pHost)
|
||||
|
||||
var addresses []string
|
||||
for _, addr := range p2pHost.Addrs() {
|
||||
addresses = append(addresses, addr.String())
|
||||
}
|
||||
|
||||
r := &Relay{
|
||||
RelayInfo: RelayInfo{
|
||||
ID: p2pHost.ID(),
|
||||
MeshAddrs: addresses,
|
||||
MeshRooms: common.NewSafeMap[string, shared.RoomInfo](),
|
||||
MeshLatencies: common.NewSafeMap[string, time.Duration](),
|
||||
},
|
||||
Host: p2pHost,
|
||||
PubSub: p2pPubsub,
|
||||
PingService: pingSvc,
|
||||
LocalRooms: common.NewSafeMap[ulid.ULID, *shared.Room](),
|
||||
LocalMeshPeers: common.NewSafeMap[peer.ID, *RelayInfo](),
|
||||
PeerInfo: NewPeerInfo(p2pHost.ID(), p2pHost.Addrs()),
|
||||
Host: p2pHost,
|
||||
PubSub: p2pPubsub,
|
||||
PingService: pingSvc,
|
||||
LocalRooms: common.NewSafeMap[ulid.ULID, *shared.Room](),
|
||||
LocalMeshConnections: common.NewSafeMap[peer.ID, *webrtc.PeerConnection](),
|
||||
}
|
||||
|
||||
// Add network notifier after relay is initialized
|
||||
@@ -152,7 +174,7 @@ func NewRelay(ctx context.Context, port int, identityKey crypto.PrivKey) (*Relay
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func InitRelay(ctx context.Context, ctxCancel context.CancelFunc) error {
|
||||
func InitRelay(ctx context.Context, ctxCancel context.CancelFunc) (*Relay, error) {
|
||||
var err error
|
||||
persistentDir := common.GetFlags().PersistDir
|
||||
|
||||
@@ -164,7 +186,7 @@ func InitRelay(ctx context.Context, ctxCancel context.CancelFunc) error {
|
||||
if hasIdentity {
|
||||
_, err = os.Stat(persistentDir + "/identity.key")
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to check identity key file: %w", err)
|
||||
return nil, fmt.Errorf("failed to check identity key file: %w", err)
|
||||
} else if os.IsNotExist(err) {
|
||||
hasIdentity = false
|
||||
}
|
||||
@@ -172,17 +194,17 @@ func InitRelay(ctx context.Context, ctxCancel context.CancelFunc) error {
|
||||
if !hasIdentity {
|
||||
// Make sure the persistent directory exists
|
||||
if err = os.MkdirAll(persistentDir, 0700); err != nil {
|
||||
return fmt.Errorf("failed to create persistent data directory: %w", err)
|
||||
return nil, fmt.Errorf("failed to create persistent data directory: %w", err)
|
||||
}
|
||||
// Generate
|
||||
slog.Info("Generating new identity for relay")
|
||||
privKey, err = common.GenerateED25519Key()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate new identity: %w", err)
|
||||
return nil, fmt.Errorf("failed to generate new identity: %w", err)
|
||||
}
|
||||
// Save the key
|
||||
if err = common.SaveED25519Key(privKey, persistentDir+"/identity.key"); err != nil {
|
||||
return fmt.Errorf("failed to save identity key: %w", err)
|
||||
return nil, fmt.Errorf("failed to save identity key: %w", err)
|
||||
}
|
||||
slog.Info("New identity generated and saved", "path", persistentDir+"/identity.key")
|
||||
} else {
|
||||
@@ -190,25 +212,45 @@ func InitRelay(ctx context.Context, ctxCancel context.CancelFunc) error {
|
||||
// Load the key
|
||||
privKey, err = common.LoadED25519Key(persistentDir + "/identity.key")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load identity key: %w", err)
|
||||
return nil, fmt.Errorf("failed to load identity key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to libp2p crypto.PrivKey
|
||||
identityKey, err = crypto.UnmarshalEd25519PrivateKey(privKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal ED25519 private key: %w", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal ED25519 private key: %w", err)
|
||||
}
|
||||
|
||||
globalRelay, err = NewRelay(ctx, common.GetFlags().EndpointPort, identityKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create relay: %w", err)
|
||||
return nil, fmt.Errorf("failed to create relay: %w", err)
|
||||
}
|
||||
|
||||
if err = common.InitWebRTCAPI(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
slog.Info("Relay initialized", "id", globalRelay.ID)
|
||||
return nil
|
||||
|
||||
// Load previous peers on startup
|
||||
defaultFile := common.GetFlags().PersistDir + "/peerstore.json"
|
||||
if err = globalRelay.LoadFromFile(defaultFile); err != nil {
|
||||
slog.Warn("Failed to load previous peer store", "error", err)
|
||||
} else {
|
||||
globalRelay.Peers.Range(func(id peer.ID, pi *PeerInfo) bool {
|
||||
if len(pi.Addrs) <= 0 {
|
||||
slog.Warn("Peer from peer store has no addresses", "peer", id)
|
||||
return true
|
||||
}
|
||||
|
||||
// Connect to first address only
|
||||
if err = globalRelay.ConnectToPeer(context.Background(), pi.Addrs[0]); err != nil {
|
||||
slog.Error("Failed to connect to peer from peer store", "peer", id, "error", err)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return globalRelay, nil
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ type discoveryNotifee struct {
|
||||
|
||||
func (d *discoveryNotifee) HandlePeerFound(pi peer.AddrInfo) {
|
||||
if d.relay != nil {
|
||||
if err := d.relay.connectToRelay(context.Background(), &pi); err != nil {
|
||||
if err := d.relay.connectToPeer(context.Background(), &pi); err != nil {
|
||||
slog.Error("failed to connect to discovered relay", "peer", pi.ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (r *Relay) publishRelayMetrics(ctx context.Context) error {
|
||||
// Check all peer latencies
|
||||
r.checkAllPeerLatencies(ctx)
|
||||
|
||||
data, err := json.Marshal(r.RelayInfo)
|
||||
data, err := json.Marshal(r.PeerInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal relay status: %w", err)
|
||||
}
|
||||
@@ -109,8 +109,8 @@ func (r *Relay) measureLatencyToPeer(ctx context.Context, peerID peer.ID) {
|
||||
if result.Error != nil {
|
||||
slog.Warn("Latency check failed, removing peer from local peers map", "peer", peerID, "err", result.Error)
|
||||
// Remove from MeshPeers if ping failed
|
||||
if r.LocalMeshPeers.Has(peerID) {
|
||||
r.LocalMeshPeers.Delete(peerID)
|
||||
if r.Peers.Has(peerID) {
|
||||
r.Peers.Delete(peerID)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -123,6 +123,6 @@ func (r *Relay) measureLatencyToPeer(ctx context.Context, peerID peer.ID) {
|
||||
latency = 1 * time.Microsecond
|
||||
}
|
||||
|
||||
r.RelayInfo.MeshLatencies.Set(peerID.String(), latency)
|
||||
r.PeerInfo.Latencies.Set(peerID, latency)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ type networkNotifier struct {
|
||||
|
||||
// Connected is called when a connection is established
|
||||
func (n *networkNotifier) Connected(net network.Network, conn network.Conn) {
|
||||
if n.relay == nil {
|
||||
if n.relay != nil {
|
||||
n.relay.onPeerConnected(conn.RemotePeer())
|
||||
}
|
||||
}
|
||||
@@ -75,8 +75,8 @@ func (r *Relay) setupPubSub(ctx context.Context) error {
|
||||
|
||||
// --- Connection Management ---
|
||||
|
||||
// connectToRelay is internal method to connect to a relay peer using multiaddresses
|
||||
func (r *Relay) connectToRelay(ctx context.Context, peerInfo *peer.AddrInfo) error {
|
||||
// connectToPeer is internal method to connect to a peer using multiaddresses
|
||||
func (r *Relay) connectToPeer(ctx context.Context, peerInfo *peer.AddrInfo) error {
|
||||
if peerInfo.ID == r.ID {
|
||||
return errors.New("cannot connect to self")
|
||||
}
|
||||
@@ -94,19 +94,14 @@ func (r *Relay) connectToRelay(ctx context.Context, peerInfo *peer.AddrInfo) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConnectToRelay connects to another relay by its multiaddress.
|
||||
func (r *Relay) ConnectToRelay(ctx context.Context, addr string) error {
|
||||
ma, err := multiaddr.NewMultiaddr(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid multiaddress: %w", err)
|
||||
}
|
||||
|
||||
peerInfo, err := peer.AddrInfoFromP2pAddr(ma)
|
||||
// ConnectToPeer connects to another peer by its multiaddress.
|
||||
func (r *Relay) ConnectToPeer(ctx context.Context, addr multiaddr.Multiaddr) error {
|
||||
peerInfo, err := peer.AddrInfoFromP2pAddr(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract peer info: %w", err)
|
||||
}
|
||||
|
||||
return r.connectToRelay(ctx, peerInfo)
|
||||
return r.connectToPeer(ctx, peerInfo)
|
||||
}
|
||||
|
||||
// printConnectInstructions logs the multiaddresses for connecting to this relay.
|
||||
|
||||
77
packages/relay/internal/core/peer.go
Normal file
77
packages/relay/internal/core/peer.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"os"
|
||||
"relay/internal/common"
|
||||
"relay/internal/shared"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
)
|
||||
|
||||
// PeerInfo contains information of a peer, in light transmit-friendly format
|
||||
type PeerInfo struct {
|
||||
ID peer.ID
|
||||
Addrs []multiaddr.Multiaddr // Addresses of this peer
|
||||
Peers *common.SafeMap[peer.ID, *PeerInfo] // Peers connected to this peer
|
||||
Latencies *common.SafeMap[peer.ID, time.Duration] // Latencies to other peers from this peer
|
||||
Rooms *common.SafeMap[string, shared.RoomInfo] // Rooms this peer is part of or owner of
|
||||
}
|
||||
|
||||
func NewPeerInfo(id peer.ID, addrs []multiaddr.Multiaddr) *PeerInfo {
|
||||
return &PeerInfo{
|
||||
ID: id,
|
||||
Addrs: addrs,
|
||||
Peers: common.NewSafeMap[peer.ID, *PeerInfo](),
|
||||
Latencies: common.NewSafeMap[peer.ID, time.Duration](),
|
||||
Rooms: common.NewSafeMap[string, shared.RoomInfo](),
|
||||
}
|
||||
}
|
||||
|
||||
// SaveToFile saves the peer store to a JSON file in persistent path
|
||||
func (pi *PeerInfo) SaveToFile(filePath string) error {
|
||||
if len(filePath) <= 0 {
|
||||
return errors.New("filepath is not set")
|
||||
}
|
||||
|
||||
// Marshal the peer store to JSON array (we don't need to store IDs..)
|
||||
data, err := pi.Peers.MarshalJSON()
|
||||
if err != nil {
|
||||
return errors.New("failed to marshal peer store data: " + err.Error())
|
||||
}
|
||||
|
||||
// Save the data to a file
|
||||
if err = os.WriteFile(filePath, data, 0644); err != nil {
|
||||
return errors.New("failed to save peer store to file: " + err.Error())
|
||||
}
|
||||
|
||||
slog.Info("PeerStore saved to file", "path", filePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadFromFile loads the peer store from a JSON file in persistent path
|
||||
func (pi *PeerInfo) LoadFromFile(filePath string) error {
|
||||
if len(filePath) <= 0 {
|
||||
return errors.New("filepath is not set")
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
slog.Info("PeerStore file does not exist, starting with empty store")
|
||||
return nil // No peers to load
|
||||
}
|
||||
return errors.New("failed to read peer store file: " + err.Error())
|
||||
}
|
||||
|
||||
// Unmarshal the JSON data into the peer store
|
||||
if err = pi.Peers.UnmarshalJSON(data); err != nil {
|
||||
return errors.New("failed to unmarshal peer store data: " + err.Error())
|
||||
}
|
||||
|
||||
slog.Info("PeerStore loaded from file", "path", filePath)
|
||||
return nil
|
||||
}
|
||||
@@ -40,15 +40,15 @@ type StreamConnection struct {
|
||||
// StreamProtocol deals with meshed stream forwarding
|
||||
type StreamProtocol struct {
|
||||
relay *Relay
|
||||
servedConns *common.SafeMap[peer.ID, *StreamConnection] // peer ID -> StreamConnection (for served streams)
|
||||
incomingConns *common.SafeMap[string, *StreamConnection] // room name -> StreamConnection (for incoming pushed streams)
|
||||
requestedConns *common.SafeMap[string, *StreamConnection] // room name -> StreamConnection (for requested streams from other relays)
|
||||
servedConns *common.SafeMap[string, *common.SafeMap[peer.ID, *StreamConnection]] // room name -> (peer ID -> StreamConnection) (for served streams)
|
||||
incomingConns *common.SafeMap[string, *StreamConnection] // room name -> StreamConnection (for incoming pushed streams)
|
||||
requestedConns *common.SafeMap[string, *StreamConnection] // room name -> StreamConnection (for requested streams from other relays)
|
||||
}
|
||||
|
||||
func NewStreamProtocol(relay *Relay) *StreamProtocol {
|
||||
protocol := &StreamProtocol{
|
||||
relay: relay,
|
||||
servedConns: common.NewSafeMap[peer.ID, *StreamConnection](),
|
||||
servedConns: common.NewSafeMap[string, *common.SafeMap[peer.ID, *StreamConnection]](),
|
||||
incomingConns: common.NewSafeMap[string, *StreamConnection](),
|
||||
requestedConns: common.NewSafeMap[string, *StreamConnection](),
|
||||
}
|
||||
@@ -66,6 +66,7 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
||||
brw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))
|
||||
safeBRW := common.NewSafeBufioRW(brw)
|
||||
|
||||
var currentRoomName string // Track the current room for this stream
|
||||
iceHolder := make([]webrtc.ICECandidateInit, 0)
|
||||
for {
|
||||
data, err := safeBRW.Receive()
|
||||
@@ -101,7 +102,9 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
||||
continue
|
||||
}
|
||||
|
||||
currentRoomName = roomName // Store the room name
|
||||
slog.Info("Received stream request for room", "room", roomName)
|
||||
|
||||
room := sp.relay.GetRoomByName(roomName)
|
||||
if room == nil || !room.IsOnline() || room.OwnerID != sp.relay.ID {
|
||||
// TODO: Allow forward requests to other relays from here?
|
||||
@@ -126,8 +129,12 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
||||
pc, err := common.CreatePeerConnection(func() {
|
||||
slog.Info("PeerConnection closed for requested stream", "room", roomName)
|
||||
// Cleanup the stream connection
|
||||
if ok := sp.servedConns.Has(stream.Conn().RemotePeer()); ok {
|
||||
sp.servedConns.Delete(stream.Conn().RemotePeer())
|
||||
if roomMap, ok := sp.servedConns.Get(roomName); ok {
|
||||
roomMap.Delete(stream.Conn().RemotePeer())
|
||||
// If the room map is empty, delete it
|
||||
if roomMap.Len() == 0 {
|
||||
sp.servedConns.Delete(roomName)
|
||||
}
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
@@ -204,7 +211,12 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
||||
}
|
||||
|
||||
// Store the connection
|
||||
sp.servedConns.Set(stream.Conn().RemotePeer(), &StreamConnection{
|
||||
roomMap, ok := sp.servedConns.Get(roomName)
|
||||
if !ok {
|
||||
roomMap = common.NewSafeMap[peer.ID, *StreamConnection]()
|
||||
sp.servedConns.Set(roomName, roomMap)
|
||||
}
|
||||
roomMap.Set(stream.Conn().RemotePeer(), &StreamConnection{
|
||||
pc: pc,
|
||||
ndc: ndc,
|
||||
})
|
||||
@@ -216,17 +228,25 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
||||
slog.Error("Failed to unmarshal ICE message", "err", err)
|
||||
continue
|
||||
}
|
||||
if conn, ok := sp.servedConns.Get(stream.Conn().RemotePeer()); ok && conn.pc.RemoteDescription() != nil {
|
||||
if err := conn.pc.AddICECandidate(iceMsg.Candidate); err != nil {
|
||||
slog.Error("Failed to add ICE candidate", "err", err)
|
||||
}
|
||||
for _, heldIce := range iceHolder {
|
||||
if err := conn.pc.AddICECandidate(heldIce); err != nil {
|
||||
slog.Error("Failed to add held ICE candidate", "err", err)
|
||||
// Use currentRoomName to get the connection from nested map
|
||||
if len(currentRoomName) > 0 {
|
||||
if roomMap, ok := sp.servedConns.Get(currentRoomName); ok {
|
||||
if conn, ok := roomMap.Get(stream.Conn().RemotePeer()); ok && conn.pc.RemoteDescription() != nil {
|
||||
if err := conn.pc.AddICECandidate(iceMsg.Candidate); err != nil {
|
||||
slog.Error("Failed to add ICE candidate", "err", err)
|
||||
}
|
||||
for _, heldIce := range iceHolder {
|
||||
if err := conn.pc.AddICECandidate(heldIce); err != nil {
|
||||
slog.Error("Failed to add held ICE candidate", "err", err)
|
||||
}
|
||||
}
|
||||
// Clear the held candidates
|
||||
iceHolder = make([]webrtc.ICECandidateInit, 0)
|
||||
} else {
|
||||
// Hold the candidate until remote description is set
|
||||
iceHolder = append(iceHolder, iceMsg.Candidate)
|
||||
}
|
||||
}
|
||||
// Clear the held candidates
|
||||
iceHolder = make([]webrtc.ICECandidateInit, 0)
|
||||
} else {
|
||||
// Hold the candidate until remote description is set
|
||||
iceHolder = append(iceHolder, iceMsg.Candidate)
|
||||
@@ -237,12 +257,19 @@ func (sp *StreamProtocol) handleStreamRequest(stream network.Stream) {
|
||||
slog.Error("Failed to unmarshal answer from signaling message", "err", err)
|
||||
continue
|
||||
}
|
||||
if conn, ok := sp.servedConns.Get(stream.Conn().RemotePeer()); ok {
|
||||
if err := conn.pc.SetRemoteDescription(answerMsg.SDP); err != nil {
|
||||
slog.Error("Failed to set remote description for answer", "err", err)
|
||||
continue
|
||||
// Use currentRoomName to get the connection from nested map
|
||||
if len(currentRoomName) > 0 {
|
||||
if roomMap, ok := sp.servedConns.Get(currentRoomName); ok {
|
||||
if conn, ok := roomMap.Get(stream.Conn().RemotePeer()); ok {
|
||||
if err := conn.pc.SetRemoteDescription(answerMsg.SDP); err != nil {
|
||||
slog.Error("Failed to set remote description for answer", "err", err)
|
||||
continue
|
||||
}
|
||||
slog.Debug("Set remote description for answer")
|
||||
} else {
|
||||
slog.Warn("Received answer without active PeerConnection")
|
||||
}
|
||||
}
|
||||
slog.Debug("Set remote description for answer")
|
||||
} else {
|
||||
slog.Warn("Received answer without active PeerConnection")
|
||||
}
|
||||
@@ -452,7 +479,7 @@ func (sp *StreamProtocol) handleStreamPush(stream network.Stream) {
|
||||
data, err := safeBRW.Receive()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) || errors.Is(err, network.ErrReset) {
|
||||
slog.Debug("Stream push connection closed by peer", "peer", stream.Conn().RemotePeer())
|
||||
slog.Debug("Stream push connection closed by peer", "peer", stream.Conn().RemotePeer(), "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -568,6 +595,21 @@ func (sp *StreamProtocol) handleStreamPush(stream network.Stream) {
|
||||
room.DataChannel.RegisterOnClose(func() {
|
||||
slog.Debug("DataChannel closed for pushed stream", "room", room.Name)
|
||||
})
|
||||
room.DataChannel.RegisterMessageCallback("input", func(data []byte) {
|
||||
if room.DataChannel != nil {
|
||||
// Pass to servedConns DataChannels for this specific room
|
||||
if roomMap, ok := sp.servedConns.Get(room.Name); ok {
|
||||
roomMap.Range(func(peerID peer.ID, conn *StreamConnection) bool {
|
||||
if conn.ndc != nil {
|
||||
if err = conn.ndc.SendBinary(data); err != nil {
|
||||
slog.Error("Failed to forward input message from pushed stream to viewer", "room", room.Name, "peer", peerID, "err", err)
|
||||
}
|
||||
}
|
||||
return true // Continue iteration
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Set the DataChannel in the incomingConns map
|
||||
if conn, ok := sp.incomingConns.Get(room.Name); ok {
|
||||
@@ -687,7 +729,7 @@ func (sp *StreamProtocol) handleStreamPush(stream network.Stream) {
|
||||
func (sp *StreamProtocol) RequestStream(ctx context.Context, room *shared.Room, peerID peer.ID) error {
|
||||
stream, err := sp.relay.Host.NewStream(ctx, peerID, protocolStreamRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create stream request: %w", err)
|
||||
return fmt.Errorf("failed to create stream: %w", err)
|
||||
}
|
||||
|
||||
return sp.requestStream(stream, room)
|
||||
|
||||
@@ -57,15 +57,15 @@ func (r *Relay) DeleteRoomIfEmpty(room *shared.Room) {
|
||||
|
||||
// GetRemoteRoomByName returns room from mesh by name
|
||||
func (r *Relay) GetRemoteRoomByName(roomName string) *shared.RoomInfo {
|
||||
for _, room := range r.MeshRooms.Copy() {
|
||||
for _, room := range r.Rooms.Copy() {
|
||||
if room.Name == roomName && room.OwnerID != r.ID {
|
||||
// Make sure connection is alive
|
||||
if r.Host.Network().Connectedness(room.OwnerID) == network.Connected {
|
||||
return &room
|
||||
} else {
|
||||
slog.Debug("Removing stale peer, owns a room without connection", "room", roomName, "peer", room.OwnerID)
|
||||
r.onPeerDisconnected(room.OwnerID)
|
||||
}
|
||||
|
||||
slog.Debug("Removing stale peer, owns a room without connection", "room", roomName, "peer", room.OwnerID)
|
||||
r.onPeerDisconnected(room.OwnerID)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -72,8 +72,8 @@ func (r *Relay) handleRelayMetricsMessages(ctx context.Context, sub *pubsub.Subs
|
||||
continue
|
||||
}
|
||||
|
||||
var info RelayInfo
|
||||
if err := json.Unmarshal(msg.Data, &info); err != nil {
|
||||
var info PeerInfo
|
||||
if err = json.Unmarshal(msg.Data, &info); err != nil {
|
||||
slog.Error("Failed to unmarshal relay status", "from", msg.GetFrom(), "data_len", len(msg.Data), "err", err)
|
||||
continue
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func (r *Relay) handleRelayMetricsMessages(ctx context.Context, sub *pubsub.Subs
|
||||
// --- State Check Functions ---
|
||||
// hasConnectedPeer checks if peer is in map and has a valid connection
|
||||
func (r *Relay) hasConnectedPeer(peerID peer.ID) bool {
|
||||
if _, ok := r.LocalMeshPeers.Get(peerID); !ok {
|
||||
if _, ok := r.Peers.Get(peerID); !ok {
|
||||
return false
|
||||
}
|
||||
if r.Host.Network().Connectedness(peerID) != network.Connected {
|
||||
@@ -102,14 +102,14 @@ func (r *Relay) hasConnectedPeer(peerID peer.ID) bool {
|
||||
// --- State Change Functions ---
|
||||
|
||||
// onPeerStatus updates the status of a peer based on received metrics, adding local perspective
|
||||
func (r *Relay) onPeerStatus(recvInfo RelayInfo) {
|
||||
r.LocalMeshPeers.Set(recvInfo.ID, &recvInfo)
|
||||
func (r *Relay) onPeerStatus(recvInfo PeerInfo) {
|
||||
r.Peers.Set(recvInfo.ID, &recvInfo)
|
||||
}
|
||||
|
||||
// onPeerConnected is called when a new peer connects to the relay
|
||||
func (r *Relay) onPeerConnected(peerID peer.ID) {
|
||||
// Add to local peer map
|
||||
r.LocalMeshPeers.Set(peerID, &RelayInfo{
|
||||
r.Peers.Set(peerID, &PeerInfo{
|
||||
ID: peerID,
|
||||
})
|
||||
|
||||
@@ -131,16 +131,12 @@ func (r *Relay) onPeerConnected(peerID peer.ID) {
|
||||
func (r *Relay) onPeerDisconnected(peerID peer.ID) {
|
||||
slog.Info("Mesh peer disconnected, deleting from local peer map", "peer", peerID)
|
||||
// Remove peer from local mesh peers
|
||||
if r.LocalMeshPeers.Has(peerID) {
|
||||
r.LocalMeshPeers.Delete(peerID)
|
||||
if r.Peers.Has(peerID) {
|
||||
r.Peers.Delete(peerID)
|
||||
}
|
||||
// Remove any rooms associated with this peer
|
||||
if r.MeshRooms.Has(peerID.String()) {
|
||||
r.MeshRooms.Delete(peerID.String())
|
||||
}
|
||||
// Remove any latencies associated with this peer
|
||||
if r.LocalMeshPeers.Has(peerID) {
|
||||
r.LocalMeshPeers.Delete(peerID)
|
||||
if r.Rooms.Has(peerID.String()) {
|
||||
r.Rooms.Delete(peerID.String())
|
||||
}
|
||||
|
||||
// TODO: If any rooms were routed through this peer, handle that case
|
||||
@@ -155,7 +151,7 @@ func (r *Relay) updateMeshRoomStates(peerID peer.ID, states []shared.RoomInfo) {
|
||||
}
|
||||
|
||||
// If previously did not exist, but does now, request a connection if participants exist for our room
|
||||
existed := r.MeshRooms.Has(state.ID.String())
|
||||
existed := r.Rooms.Has(state.ID.String())
|
||||
if !existed {
|
||||
// Request connection to this peer if we have participants in our local room
|
||||
if room, ok := r.LocalRooms.Get(state.ID); ok {
|
||||
@@ -168,6 +164,6 @@ func (r *Relay) updateMeshRoomStates(peerID peer.ID, states []shared.RoomInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
r.MeshRooms.Set(state.ID.String(), state)
|
||||
r.Rooms.Set(state.ID.String(), state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: latency_tracker.proto
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: messages.proto
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc (unknown)
|
||||
// source: types.proto
|
||||
|
||||
@@ -416,6 +416,481 @@ func (x *ProtoKeyUp) GetKey() int32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// ControllerAttach message
|
||||
type ProtoControllerAttach struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerAttach"
|
||||
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // One of the following enums: "ps", "xbox" or "switch"
|
||||
Slot int32 `protobuf:"varint,3,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAttach) Reset() {
|
||||
*x = ProtoControllerAttach{}
|
||||
mi := &file_types_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAttach) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerAttach) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerAttach) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[7]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerAttach.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerAttach) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAttach) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAttach) GetId() string {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAttach) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ControllerDetach message
|
||||
type ProtoControllerDetach struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerDetach"
|
||||
Slot int32 `protobuf:"varint,2,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerDetach) Reset() {
|
||||
*x = ProtoControllerDetach{}
|
||||
mi := &file_types_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerDetach) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerDetach) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerDetach) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[8]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerDetach.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerDetach) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerDetach) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerDetach) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ControllerButton message
|
||||
type ProtoControllerButton struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerButtons"
|
||||
Slot int32 `protobuf:"varint,2,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
Button int32 `protobuf:"varint,3,opt,name=button,proto3" json:"button,omitempty"` // Button code (linux input event code)
|
||||
Pressed bool `protobuf:"varint,4,opt,name=pressed,proto3" json:"pressed,omitempty"` // true if pressed, false if released
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerButton) Reset() {
|
||||
*x = ProtoControllerButton{}
|
||||
mi := &file_types_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerButton) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerButton) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerButton) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[9]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerButton.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerButton) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerButton) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerButton) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerButton) GetButton() int32 {
|
||||
if x != nil {
|
||||
return x.Button
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerButton) GetPressed() bool {
|
||||
if x != nil {
|
||||
return x.Pressed
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ControllerTriggers message
|
||||
type ProtoControllerTrigger struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerTriggers"
|
||||
Slot int32 `protobuf:"varint,2,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
Trigger int32 `protobuf:"varint,3,opt,name=trigger,proto3" json:"trigger,omitempty"` // Trigger number (0 for left, 1 for right)
|
||||
Value int32 `protobuf:"varint,4,opt,name=value,proto3" json:"value,omitempty"` // trigger value (-32768 to 32767)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerTrigger) Reset() {
|
||||
*x = ProtoControllerTrigger{}
|
||||
mi := &file_types_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerTrigger) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerTrigger) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerTrigger) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[10]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerTrigger.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerTrigger) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerTrigger) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerTrigger) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerTrigger) GetTrigger() int32 {
|
||||
if x != nil {
|
||||
return x.Trigger
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerTrigger) GetValue() int32 {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ControllerSticks message
|
||||
type ProtoControllerStick struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerStick"
|
||||
Slot int32 `protobuf:"varint,2,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
Stick int32 `protobuf:"varint,3,opt,name=stick,proto3" json:"stick,omitempty"` // Stick number (0 for left, 1 for right)
|
||||
X int32 `protobuf:"varint,4,opt,name=x,proto3" json:"x,omitempty"` // X axis value (-32768 to 32767)
|
||||
Y int32 `protobuf:"varint,5,opt,name=y,proto3" json:"y,omitempty"` // Y axis value (-32768 to 32767)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) Reset() {
|
||||
*x = ProtoControllerStick{}
|
||||
mi := &file_types_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerStick) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerStick) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[11]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerStick.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerStick) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{11}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) GetStick() int32 {
|
||||
if x != nil {
|
||||
return x.Stick
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) GetX() int32 {
|
||||
if x != nil {
|
||||
return x.X
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerStick) GetY() int32 {
|
||||
if x != nil {
|
||||
return x.Y
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ControllerAxis message
|
||||
type ProtoControllerAxis struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerAxis"
|
||||
Slot int32 `protobuf:"varint,2,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
Axis int32 `protobuf:"varint,3,opt,name=axis,proto3" json:"axis,omitempty"` // Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
||||
Value int32 `protobuf:"varint,4,opt,name=value,proto3" json:"value,omitempty"` // axis value (-1 to 1)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAxis) Reset() {
|
||||
*x = ProtoControllerAxis{}
|
||||
mi := &file_types_proto_msgTypes[12]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAxis) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerAxis) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerAxis) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[12]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerAxis.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerAxis) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{12}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAxis) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAxis) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAxis) GetAxis() int32 {
|
||||
if x != nil {
|
||||
return x.Axis
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerAxis) GetValue() int32 {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ControllerRumble message
|
||||
type ProtoControllerRumble struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` // Fixed value "ControllerRumble"
|
||||
Slot int32 `protobuf:"varint,2,opt,name=slot,proto3" json:"slot,omitempty"` // Slot number (0-3)
|
||||
LowFrequency int32 `protobuf:"varint,3,opt,name=low_frequency,json=lowFrequency,proto3" json:"low_frequency,omitempty"` // Low frequency rumble (0-65535)
|
||||
HighFrequency int32 `protobuf:"varint,4,opt,name=high_frequency,json=highFrequency,proto3" json:"high_frequency,omitempty"` // High frequency rumble (0-65535)
|
||||
Duration int32 `protobuf:"varint,5,opt,name=duration,proto3" json:"duration,omitempty"` // Duration in milliseconds
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) Reset() {
|
||||
*x = ProtoControllerRumble{}
|
||||
mi := &file_types_proto_msgTypes[13]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoControllerRumble) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoControllerRumble) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[13]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoControllerRumble.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoControllerRumble) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{13}
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) GetSlot() int32 {
|
||||
if x != nil {
|
||||
return x.Slot
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) GetLowFrequency() int32 {
|
||||
if x != nil {
|
||||
return x.LowFrequency
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) GetHighFrequency() int32 {
|
||||
if x != nil {
|
||||
return x.HighFrequency
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ProtoControllerRumble) GetDuration() int32 {
|
||||
if x != nil {
|
||||
return x.Duration
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Union of all Input types
|
||||
type ProtoInput struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
@@ -428,6 +903,13 @@ type ProtoInput struct {
|
||||
// *ProtoInput_MouseKeyUp
|
||||
// *ProtoInput_KeyDown
|
||||
// *ProtoInput_KeyUp
|
||||
// *ProtoInput_ControllerAttach
|
||||
// *ProtoInput_ControllerDetach
|
||||
// *ProtoInput_ControllerButton
|
||||
// *ProtoInput_ControllerTrigger
|
||||
// *ProtoInput_ControllerStick
|
||||
// *ProtoInput_ControllerAxis
|
||||
// *ProtoInput_ControllerRumble
|
||||
InputType isProtoInput_InputType `protobuf_oneof:"input_type"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -435,7 +917,7 @@ type ProtoInput struct {
|
||||
|
||||
func (x *ProtoInput) Reset() {
|
||||
*x = ProtoInput{}
|
||||
mi := &file_types_proto_msgTypes[7]
|
||||
mi := &file_types_proto_msgTypes[14]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -447,7 +929,7 @@ func (x *ProtoInput) String() string {
|
||||
func (*ProtoInput) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoInput) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_types_proto_msgTypes[7]
|
||||
mi := &file_types_proto_msgTypes[14]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -460,7 +942,7 @@ func (x *ProtoInput) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ProtoInput.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoInput) Descriptor() ([]byte, []int) {
|
||||
return file_types_proto_rawDescGZIP(), []int{7}
|
||||
return file_types_proto_rawDescGZIP(), []int{14}
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetInputType() isProtoInput_InputType {
|
||||
@@ -533,6 +1015,69 @@ func (x *ProtoInput) GetKeyUp() *ProtoKeyUp {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerAttach() *ProtoControllerAttach {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerAttach); ok {
|
||||
return x.ControllerAttach
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerDetach() *ProtoControllerDetach {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerDetach); ok {
|
||||
return x.ControllerDetach
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerButton() *ProtoControllerButton {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerButton); ok {
|
||||
return x.ControllerButton
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerTrigger() *ProtoControllerTrigger {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerTrigger); ok {
|
||||
return x.ControllerTrigger
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerStick() *ProtoControllerStick {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerStick); ok {
|
||||
return x.ControllerStick
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerAxis() *ProtoControllerAxis {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerAxis); ok {
|
||||
return x.ControllerAxis
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ProtoInput) GetControllerRumble() *ProtoControllerRumble {
|
||||
if x != nil {
|
||||
if x, ok := x.InputType.(*ProtoInput_ControllerRumble); ok {
|
||||
return x.ControllerRumble
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isProtoInput_InputType interface {
|
||||
isProtoInput_InputType()
|
||||
}
|
||||
@@ -565,6 +1110,34 @@ type ProtoInput_KeyUp struct {
|
||||
KeyUp *ProtoKeyUp `protobuf:"bytes,7,opt,name=key_up,json=keyUp,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerAttach struct {
|
||||
ControllerAttach *ProtoControllerAttach `protobuf:"bytes,8,opt,name=controller_attach,json=controllerAttach,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerDetach struct {
|
||||
ControllerDetach *ProtoControllerDetach `protobuf:"bytes,9,opt,name=controller_detach,json=controllerDetach,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerButton struct {
|
||||
ControllerButton *ProtoControllerButton `protobuf:"bytes,10,opt,name=controller_button,json=controllerButton,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerTrigger struct {
|
||||
ControllerTrigger *ProtoControllerTrigger `protobuf:"bytes,11,opt,name=controller_trigger,json=controllerTrigger,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerStick struct {
|
||||
ControllerStick *ProtoControllerStick `protobuf:"bytes,12,opt,name=controller_stick,json=controllerStick,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerAxis struct {
|
||||
ControllerAxis *ProtoControllerAxis `protobuf:"bytes,13,opt,name=controller_axis,json=controllerAxis,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ProtoInput_ControllerRumble struct {
|
||||
ControllerRumble *ProtoControllerRumble `protobuf:"bytes,14,opt,name=controller_rumble,json=controllerRumble,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*ProtoInput_MouseMove) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_MouseMoveAbs) isProtoInput_InputType() {}
|
||||
@@ -579,6 +1152,20 @@ func (*ProtoInput_KeyDown) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_KeyUp) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerAttach) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerDetach) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerButton) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerTrigger) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerStick) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerAxis) isProtoInput_InputType() {}
|
||||
|
||||
func (*ProtoInput_ControllerRumble) isProtoInput_InputType() {}
|
||||
|
||||
var File_types_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_types_proto_rawDesc = "" +
|
||||
@@ -608,7 +1195,41 @@ const file_types_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"ProtoKeyUp\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x10\n" +
|
||||
"\x03key\x18\x02 \x01(\x05R\x03key\"\xab\x03\n" +
|
||||
"\x03key\x18\x02 \x01(\x05R\x03key\"O\n" +
|
||||
"\x15ProtoControllerAttach\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x0e\n" +
|
||||
"\x02id\x18\x02 \x01(\tR\x02id\x12\x12\n" +
|
||||
"\x04slot\x18\x03 \x01(\x05R\x04slot\"?\n" +
|
||||
"\x15ProtoControllerDetach\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04slot\x18\x02 \x01(\x05R\x04slot\"q\n" +
|
||||
"\x15ProtoControllerButton\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04slot\x18\x02 \x01(\x05R\x04slot\x12\x16\n" +
|
||||
"\x06button\x18\x03 \x01(\x05R\x06button\x12\x18\n" +
|
||||
"\apressed\x18\x04 \x01(\bR\apressed\"p\n" +
|
||||
"\x16ProtoControllerTrigger\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04slot\x18\x02 \x01(\x05R\x04slot\x12\x18\n" +
|
||||
"\atrigger\x18\x03 \x01(\x05R\atrigger\x12\x14\n" +
|
||||
"\x05value\x18\x04 \x01(\x05R\x05value\"p\n" +
|
||||
"\x14ProtoControllerStick\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04slot\x18\x02 \x01(\x05R\x04slot\x12\x14\n" +
|
||||
"\x05stick\x18\x03 \x01(\x05R\x05stick\x12\f\n" +
|
||||
"\x01x\x18\x04 \x01(\x05R\x01x\x12\f\n" +
|
||||
"\x01y\x18\x05 \x01(\x05R\x01y\"g\n" +
|
||||
"\x13ProtoControllerAxis\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04slot\x18\x02 \x01(\x05R\x04slot\x12\x12\n" +
|
||||
"\x04axis\x18\x03 \x01(\x05R\x04axis\x12\x14\n" +
|
||||
"\x05value\x18\x04 \x01(\x05R\x05value\"\xa7\x01\n" +
|
||||
"\x15ProtoControllerRumble\x12\x12\n" +
|
||||
"\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" +
|
||||
"\x04slot\x18\x02 \x01(\x05R\x04slot\x12#\n" +
|
||||
"\rlow_frequency\x18\x03 \x01(\x05R\flowFrequency\x12%\n" +
|
||||
"\x0ehigh_frequency\x18\x04 \x01(\x05R\rhighFrequency\x12\x1a\n" +
|
||||
"\bduration\x18\x05 \x01(\x05R\bduration\"\xc0\a\n" +
|
||||
"\n" +
|
||||
"ProtoInput\x126\n" +
|
||||
"\n" +
|
||||
@@ -620,7 +1241,15 @@ const file_types_proto_rawDesc = "" +
|
||||
"\fmouse_key_up\x18\x05 \x01(\v2\x16.proto.ProtoMouseKeyUpH\x00R\n" +
|
||||
"mouseKeyUp\x120\n" +
|
||||
"\bkey_down\x18\x06 \x01(\v2\x13.proto.ProtoKeyDownH\x00R\akeyDown\x12*\n" +
|
||||
"\x06key_up\x18\a \x01(\v2\x11.proto.ProtoKeyUpH\x00R\x05keyUpB\f\n" +
|
||||
"\x06key_up\x18\a \x01(\v2\x11.proto.ProtoKeyUpH\x00R\x05keyUp\x12K\n" +
|
||||
"\x11controller_attach\x18\b \x01(\v2\x1c.proto.ProtoControllerAttachH\x00R\x10controllerAttach\x12K\n" +
|
||||
"\x11controller_detach\x18\t \x01(\v2\x1c.proto.ProtoControllerDetachH\x00R\x10controllerDetach\x12K\n" +
|
||||
"\x11controller_button\x18\n" +
|
||||
" \x01(\v2\x1c.proto.ProtoControllerButtonH\x00R\x10controllerButton\x12N\n" +
|
||||
"\x12controller_trigger\x18\v \x01(\v2\x1d.proto.ProtoControllerTriggerH\x00R\x11controllerTrigger\x12H\n" +
|
||||
"\x10controller_stick\x18\f \x01(\v2\x1b.proto.ProtoControllerStickH\x00R\x0fcontrollerStick\x12E\n" +
|
||||
"\x0fcontroller_axis\x18\r \x01(\v2\x1a.proto.ProtoControllerAxisH\x00R\x0econtrollerAxis\x12K\n" +
|
||||
"\x11controller_rumble\x18\x0e \x01(\v2\x1c.proto.ProtoControllerRumbleH\x00R\x10controllerRumbleB\f\n" +
|
||||
"\n" +
|
||||
"input_typeB\x16Z\x14relay/internal/protob\x06proto3"
|
||||
|
||||
@@ -636,30 +1265,44 @@ func file_types_proto_rawDescGZIP() []byte {
|
||||
return file_types_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
|
||||
var file_types_proto_goTypes = []any{
|
||||
(*ProtoMouseMove)(nil), // 0: proto.ProtoMouseMove
|
||||
(*ProtoMouseMoveAbs)(nil), // 1: proto.ProtoMouseMoveAbs
|
||||
(*ProtoMouseWheel)(nil), // 2: proto.ProtoMouseWheel
|
||||
(*ProtoMouseKeyDown)(nil), // 3: proto.ProtoMouseKeyDown
|
||||
(*ProtoMouseKeyUp)(nil), // 4: proto.ProtoMouseKeyUp
|
||||
(*ProtoKeyDown)(nil), // 5: proto.ProtoKeyDown
|
||||
(*ProtoKeyUp)(nil), // 6: proto.ProtoKeyUp
|
||||
(*ProtoInput)(nil), // 7: proto.ProtoInput
|
||||
(*ProtoMouseMove)(nil), // 0: proto.ProtoMouseMove
|
||||
(*ProtoMouseMoveAbs)(nil), // 1: proto.ProtoMouseMoveAbs
|
||||
(*ProtoMouseWheel)(nil), // 2: proto.ProtoMouseWheel
|
||||
(*ProtoMouseKeyDown)(nil), // 3: proto.ProtoMouseKeyDown
|
||||
(*ProtoMouseKeyUp)(nil), // 4: proto.ProtoMouseKeyUp
|
||||
(*ProtoKeyDown)(nil), // 5: proto.ProtoKeyDown
|
||||
(*ProtoKeyUp)(nil), // 6: proto.ProtoKeyUp
|
||||
(*ProtoControllerAttach)(nil), // 7: proto.ProtoControllerAttach
|
||||
(*ProtoControllerDetach)(nil), // 8: proto.ProtoControllerDetach
|
||||
(*ProtoControllerButton)(nil), // 9: proto.ProtoControllerButton
|
||||
(*ProtoControllerTrigger)(nil), // 10: proto.ProtoControllerTrigger
|
||||
(*ProtoControllerStick)(nil), // 11: proto.ProtoControllerStick
|
||||
(*ProtoControllerAxis)(nil), // 12: proto.ProtoControllerAxis
|
||||
(*ProtoControllerRumble)(nil), // 13: proto.ProtoControllerRumble
|
||||
(*ProtoInput)(nil), // 14: proto.ProtoInput
|
||||
}
|
||||
var file_types_proto_depIdxs = []int32{
|
||||
0, // 0: proto.ProtoInput.mouse_move:type_name -> proto.ProtoMouseMove
|
||||
1, // 1: proto.ProtoInput.mouse_move_abs:type_name -> proto.ProtoMouseMoveAbs
|
||||
2, // 2: proto.ProtoInput.mouse_wheel:type_name -> proto.ProtoMouseWheel
|
||||
3, // 3: proto.ProtoInput.mouse_key_down:type_name -> proto.ProtoMouseKeyDown
|
||||
4, // 4: proto.ProtoInput.mouse_key_up:type_name -> proto.ProtoMouseKeyUp
|
||||
5, // 5: proto.ProtoInput.key_down:type_name -> proto.ProtoKeyDown
|
||||
6, // 6: proto.ProtoInput.key_up:type_name -> proto.ProtoKeyUp
|
||||
7, // [7:7] is the sub-list for method output_type
|
||||
7, // [7:7] is the sub-list for method input_type
|
||||
7, // [7:7] is the sub-list for extension type_name
|
||||
7, // [7:7] is the sub-list for extension extendee
|
||||
0, // [0:7] is the sub-list for field type_name
|
||||
0, // 0: proto.ProtoInput.mouse_move:type_name -> proto.ProtoMouseMove
|
||||
1, // 1: proto.ProtoInput.mouse_move_abs:type_name -> proto.ProtoMouseMoveAbs
|
||||
2, // 2: proto.ProtoInput.mouse_wheel:type_name -> proto.ProtoMouseWheel
|
||||
3, // 3: proto.ProtoInput.mouse_key_down:type_name -> proto.ProtoMouseKeyDown
|
||||
4, // 4: proto.ProtoInput.mouse_key_up:type_name -> proto.ProtoMouseKeyUp
|
||||
5, // 5: proto.ProtoInput.key_down:type_name -> proto.ProtoKeyDown
|
||||
6, // 6: proto.ProtoInput.key_up:type_name -> proto.ProtoKeyUp
|
||||
7, // 7: proto.ProtoInput.controller_attach:type_name -> proto.ProtoControllerAttach
|
||||
8, // 8: proto.ProtoInput.controller_detach:type_name -> proto.ProtoControllerDetach
|
||||
9, // 9: proto.ProtoInput.controller_button:type_name -> proto.ProtoControllerButton
|
||||
10, // 10: proto.ProtoInput.controller_trigger:type_name -> proto.ProtoControllerTrigger
|
||||
11, // 11: proto.ProtoInput.controller_stick:type_name -> proto.ProtoControllerStick
|
||||
12, // 12: proto.ProtoInput.controller_axis:type_name -> proto.ProtoControllerAxis
|
||||
13, // 13: proto.ProtoInput.controller_rumble:type_name -> proto.ProtoControllerRumble
|
||||
14, // [14:14] is the sub-list for method output_type
|
||||
14, // [14:14] is the sub-list for method input_type
|
||||
14, // [14:14] is the sub-list for extension type_name
|
||||
14, // [14:14] is the sub-list for extension extendee
|
||||
0, // [0:14] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_types_proto_init() }
|
||||
@@ -667,7 +1310,7 @@ func file_types_proto_init() {
|
||||
if File_types_proto != nil {
|
||||
return
|
||||
}
|
||||
file_types_proto_msgTypes[7].OneofWrappers = []any{
|
||||
file_types_proto_msgTypes[14].OneofWrappers = []any{
|
||||
(*ProtoInput_MouseMove)(nil),
|
||||
(*ProtoInput_MouseMoveAbs)(nil),
|
||||
(*ProtoInput_MouseWheel)(nil),
|
||||
@@ -675,6 +1318,13 @@ func file_types_proto_init() {
|
||||
(*ProtoInput_MouseKeyUp)(nil),
|
||||
(*ProtoInput_KeyDown)(nil),
|
||||
(*ProtoInput_KeyUp)(nil),
|
||||
(*ProtoInput_ControllerAttach)(nil),
|
||||
(*ProtoInput_ControllerDetach)(nil),
|
||||
(*ProtoInput_ControllerButton)(nil),
|
||||
(*ProtoInput_ControllerTrigger)(nil),
|
||||
(*ProtoInput_ControllerStick)(nil),
|
||||
(*ProtoInput_ControllerAxis)(nil),
|
||||
(*ProtoInput_ControllerRumble)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
@@ -682,7 +1332,7 @@ func file_types_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_proto_rawDesc), len(file_types_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 8,
|
||||
NumMessages: 15,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -49,25 +49,12 @@ func (r *Room) removeParticipantByID(pID ulid.ULID) {
|
||||
}
|
||||
}
|
||||
|
||||
// Removes all participants from a Room
|
||||
/*func (r *Room) removeAllParticipants() {
|
||||
for id, participant := range r.Participants.Copy() {
|
||||
if err := r.signalParticipantOffline(participant); err != nil {
|
||||
slog.Error("Failed to signal participant offline", "participant", participant.ID, "room", r.Name, "err", err)
|
||||
}
|
||||
r.Participants.Delete(id)
|
||||
slog.Debug("Removed participant from room", "participant", id, "room", r.Name)
|
||||
}
|
||||
}*/
|
||||
|
||||
// IsOnline checks if the room is online (has both audio and video tracks)
|
||||
func (r *Room) IsOnline() bool {
|
||||
return r.AudioTrack != nil && r.VideoTrack != nil
|
||||
}
|
||||
|
||||
func (r *Room) SetTrack(trackType webrtc.RTPCodecType, track *webrtc.TrackLocalStaticRTP) {
|
||||
//oldOnline := r.IsOnline()
|
||||
|
||||
switch trackType {
|
||||
case webrtc.RTPCodecTypeAudio:
|
||||
r.AudioTrack = track
|
||||
@@ -76,69 +63,4 @@ func (r *Room) SetTrack(trackType webrtc.RTPCodecType, track *webrtc.TrackLocalS
|
||||
default:
|
||||
slog.Warn("Unknown track type", "room", r.Name, "trackType", trackType)
|
||||
}
|
||||
|
||||
/*newOnline := r.IsOnline()
|
||||
if oldOnline != newOnline {
|
||||
if newOnline {
|
||||
slog.Debug("Room online, participants will be signaled", "room", r.Name)
|
||||
r.signalParticipantsWithTracks()
|
||||
} else {
|
||||
slog.Debug("Room offline, signaling participants", "room", r.Name)
|
||||
r.signalParticipantsOffline()
|
||||
}
|
||||
|
||||
// TODO: Publish updated state to mesh
|
||||
go func() {
|
||||
if err := r.Relay.publishRoomStates(context.Background()); err != nil {
|
||||
slog.Error("Failed to publish room states on change", "room", r.Name, "err", err)
|
||||
}
|
||||
}()
|
||||
}*/
|
||||
}
|
||||
|
||||
/* TODO: libp2p'ify
|
||||
func (r *Room) signalParticipantsWithTracks() {
|
||||
for _, participant := range r.Participants.Copy() {
|
||||
if err := r.signalParticipantWithTracks(participant); err != nil {
|
||||
slog.Error("Failed to signal participant with tracks", "participant", participant.ID, "room", r.Name, "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Room) signalParticipantWithTracks(participant *Participant) error {
|
||||
if r.AudioTrack != nil {
|
||||
if err := participant.addTrack(r.AudioTrack); err != nil {
|
||||
return fmt.Errorf("failed to add audio track: %w", err)
|
||||
}
|
||||
}
|
||||
if r.VideoTrack != nil {
|
||||
if err := participant.addTrack(r.VideoTrack); err != nil {
|
||||
return fmt.Errorf("failed to add video track: %w", err)
|
||||
}
|
||||
}
|
||||
if err := participant.signalOffer(); err != nil {
|
||||
return fmt.Errorf("failed to signal offer: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Room) signalParticipantsOffline() {
|
||||
for _, participant := range r.Participants.Copy() {
|
||||
if err := r.signalParticipantOffline(participant); err != nil {
|
||||
slog.Error("Failed to signal participant offline", "participant", participant.ID, "room", r.Name, "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// signalParticipantOffline signals a single participant offline
|
||||
func (r *Room) signalParticipantOffline(participant *Participant) error {
|
||||
// Skip if websocket is nil or closed
|
||||
if participant.WebSocket == nil || participant.WebSocket.IsClosed() {
|
||||
return nil
|
||||
}
|
||||
if err := participant.WebSocket.SendAnswerMessageWS(connections.AnswerOffline); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -32,7 +32,7 @@ func main() {
|
||||
slog.SetDefault(logger)
|
||||
|
||||
// Start relay
|
||||
err := core.InitRelay(mainCtx, mainStopper)
|
||||
relay, err := core.InitRelay(mainCtx, mainStopper)
|
||||
if err != nil {
|
||||
slog.Error("Failed to initialize relay", "err", err)
|
||||
mainStopper()
|
||||
@@ -41,5 +41,10 @@ func main() {
|
||||
|
||||
// Wait for exit signal
|
||||
<-mainCtx.Done()
|
||||
slog.Info("Shutting down gracefully by signal...")
|
||||
slog.Info("Shutting down gracefully by signal..")
|
||||
|
||||
defaultFile := common.GetFlags().PersistDir + "/peerstore.json"
|
||||
if err = relay.SaveToFile(defaultFile); err != nil {
|
||||
slog.Error("Failed to save peer store", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
while true; do
|
||||
case "$1" in
|
||||
--*) shift ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
exec "$@"
|
||||
@@ -21,6 +21,13 @@ chown_user_directory() {
|
||||
echo "Error: Failed to change ownership of ${NESTRI_HOME} to ${NESTRI_USER}:${NESTRI_USER}" >&2
|
||||
return 1
|
||||
fi
|
||||
# Also apply to .cache separately
|
||||
if [[ -d "${NESTRI_HOME}/.cache" ]]; then
|
||||
if ! $ENTCMD_PREFIX chown "${NESTRI_USER}:${NESTRI_USER}" "${NESTRI_HOME}/.cache" 2>/dev/null; then
|
||||
echo "Error: Failed to change ownership of ${NESTRI_HOME}/.cache to ${NESTRI_USER}:${NESTRI_USER}" >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -48,13 +55,13 @@ setup_namespaceless() {
|
||||
|
||||
# Ensures cache directory exists
|
||||
setup_cache() {
|
||||
log "Setting up NVIDIA driver cache directory at $CACHE_DIR..."
|
||||
log "Setting up cache directory at $CACHE_DIR..."
|
||||
mkdir -p "$CACHE_DIR" || {
|
||||
log "Warning: Failed to create cache directory, continuing without cache."
|
||||
log "Warning: Failed to create cache directory, continuing.."
|
||||
return 1
|
||||
}
|
||||
$ENTCMD_PREFIX chown "${NESTRI_USER}:${NESTRI_USER}" "$CACHE_DIR" 2>/dev/null || {
|
||||
log "Warning: Failed to set cache directory ownership, continuing..."
|
||||
log "Warning: Failed to set cache directory ownership, continuing.."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +230,7 @@ main() {
|
||||
|
||||
# Start by getting the container we are running under
|
||||
get_container_info || {
|
||||
log "Warning: Failed to detect container information."
|
||||
log "Warning: Failed to detect container information"
|
||||
}
|
||||
log_container_info
|
||||
|
||||
@@ -231,6 +238,9 @@ main() {
|
||||
ENTCMD_PREFIX="sudo -E"
|
||||
fi
|
||||
|
||||
# Setup cache now
|
||||
setup_cache
|
||||
|
||||
# Configure SSH
|
||||
if [ -n "${SSH_ENABLE_PORT+x}" ] && [ "${SSH_ENABLE_PORT:-0}" -ne 0 ] && \
|
||||
[ -n "${SSH_ALLOWED_KEY+x}" ] && [ -n "${SSH_ALLOWED_KEY}" ]; then
|
||||
@@ -244,14 +254,14 @@ main() {
|
||||
|
||||
# Get and detect GPU(s)
|
||||
get_gpu_info || {
|
||||
log "Error: Failed to detect GPU information."
|
||||
log "Error: Failed to detect GPU information"
|
||||
exit 1
|
||||
}
|
||||
log_gpu_info
|
||||
|
||||
# Handle NVIDIA GPU
|
||||
if [[ -n "${vendor_devices[nvidia]:-}" ]]; then
|
||||
log "NVIDIA GPU(s) detected, applying driver fix..."
|
||||
log "NVIDIA GPU(s) detected, applying driver fix.."
|
||||
|
||||
# Determine NVIDIA driver version
|
||||
local nvidia_driver_version=""
|
||||
@@ -265,16 +275,15 @@ main() {
|
||||
log "Error: Failed to determine NVIDIA driver version."
|
||||
# Check for other GPU vendors before exiting
|
||||
if [[ -n "${vendor_devices[amd]:-}" || -n "${vendor_devices[intel]:-}" ]]; then
|
||||
log "Other GPUs (AMD or Intel) detected, continuing without NVIDIA driver."
|
||||
log "Other GPUs (AMD or Intel) detected, continuing without NVIDIA driver"
|
||||
else
|
||||
log "No other GPUs detected, exiting due to NVIDIA driver version failure."
|
||||
log "No other GPUs detected, exiting due to NVIDIA driver version failure"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log "Detected NVIDIA driver version: $nvidia_driver_version"
|
||||
|
||||
# Set up cache and get installer
|
||||
setup_cache
|
||||
# Get installer
|
||||
local arch=$(uname -m)
|
||||
local filename="NVIDIA-Linux-${arch}-${nvidia_driver_version}.run"
|
||||
cd "$NVIDIA_INSTALLER_DIR" || {
|
||||
@@ -284,9 +293,9 @@ main() {
|
||||
get_nvidia_installer "$nvidia_driver_version" "$arch" || {
|
||||
# Check for other GPU vendors before exiting
|
||||
if [[ -n "${vendor_devices[amd]:-}" || -n "${vendor_devices[intel]:-}" ]]; then
|
||||
log "Other GPUs (AMD or Intel) detected, continuing without NVIDIA driver."
|
||||
log "Other GPUs (AMD or Intel) detected, continuing without NVIDIA driver"
|
||||
else
|
||||
log "No other GPUs detected, exiting due to NVIDIA installer failure."
|
||||
log "No other GPUs detected, exiting due to NVIDIA installer failure"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
@@ -295,9 +304,9 @@ main() {
|
||||
install_nvidia_driver "$filename" || {
|
||||
# Check for other GPU vendors before exiting
|
||||
if [[ -n "${vendor_devices[amd]:-}" || -n "${vendor_devices[intel]:-}" ]]; then
|
||||
log "Other GPUs (AMD or Intel) detected, continuing without NVIDIA driver."
|
||||
log "Other GPUs (AMD or Intel) detected, continuing without NVIDIA driver"
|
||||
else
|
||||
log "No other GPUs detected, exiting due to NVIDIA driver installation failure."
|
||||
log "No other GPUs detected, exiting due to NVIDIA driver installation failure"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
@@ -305,14 +314,14 @@ main() {
|
||||
fi
|
||||
|
||||
# Make sure gamescope has CAP_SYS_NICE capabilities if available
|
||||
log "Checking for CAP_SYS_NICE availability..."
|
||||
log "Checking for CAP_SYS_NICE availability.."
|
||||
if capsh --print | grep -q "Current:.*cap_sys_nice"; then
|
||||
log "Giving gamescope compositor CAP_SYS_NICE permissions..."
|
||||
log "Giving gamescope compositor CAP_SYS_NICE permissions.."
|
||||
setcap 'CAP_SYS_NICE+eip' /usr/bin/gamescope 2>/dev/null || {
|
||||
log "Warning: Failed to set CAP_SYS_NICE on gamescope, continuing without it..."
|
||||
log "Warning: Failed to set CAP_SYS_NICE on gamescope, continuing without it.."
|
||||
}
|
||||
else
|
||||
log "Skipping CAP_SYS_NICE for gamescope, capability not available..."
|
||||
log "Skipping CAP_SYS_NICE for gamescope, capability not available"
|
||||
fi
|
||||
|
||||
# Handle user directory permissions
|
||||
@@ -325,6 +334,19 @@ main() {
|
||||
setup_namespaceless
|
||||
fi
|
||||
|
||||
# Make sure /run/udev/ directory exists with /run/udev/control, needed for virtual controller support
|
||||
if [[ ! -d "/run/udev" || ! -e "/run/udev/control" ]]; then
|
||||
log "Creating /run/udev directory and control file..."
|
||||
$ENTCMD_PREFIX mkdir -p /run/udev || {
|
||||
log "Error: Failed to create /run/udev directory"
|
||||
exit 1
|
||||
}
|
||||
$ENTCMD_PREFIX touch /run/udev/control || {
|
||||
log "Error: Failed to create /run/udev/control file"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# Switch to nestri runner entrypoint
|
||||
log "Switching to application startup entrypoint..."
|
||||
if [[ ! -f /etc/nestri/entrypoint_nestri.sh ]]; then
|
||||
@@ -339,6 +361,6 @@ main() {
|
||||
}
|
||||
|
||||
# Trap signals for clean exit
|
||||
trap 'log "Received termination signal, exiting..."; exit 1' SIGINT SIGTERM
|
||||
trap 'log "Received termination signal, exiting.."; exit 0' SIGINT SIGTERM
|
||||
|
||||
main
|
||||
|
||||
@@ -31,6 +31,9 @@ parse_resolution() {
|
||||
MAX_RETRIES=3
|
||||
RETRY_COUNT=0
|
||||
WAYLAND_READY_DELAY=3
|
||||
ENTCMD_PREFIX=""
|
||||
PRELOAD_SHIM_64=/usr/lib64/libvimputti_shim.so
|
||||
PRELOAD_SHIM_32=/usr/lib32/libvimputti_shim.so
|
||||
|
||||
# Kills process if running
|
||||
kill_if_running() {
|
||||
@@ -43,83 +46,49 @@ kill_if_running() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Starts up Steam namespace-less live-patcher
|
||||
start_steam_namespaceless_patcher() {
|
||||
kill_if_running "${PATCHER_PID:-}" "steam-patcher"
|
||||
|
||||
local entrypoints=(
|
||||
"${HOME}/.local/share/Steam/steamrt64/steam-runtime-steamrt/_v2-entry-point"
|
||||
"${HOME}/.local/share/Steam/steamapps/common/SteamLinuxRuntime_soldier/_v2-entry-point"
|
||||
"${HOME}/.local/share/Steam/steamapps/common/SteamLinuxRuntime_sniper/_v2-entry-point"
|
||||
# < Add more entrypoints here if needed >
|
||||
)
|
||||
local custom_entrypoint="/etc/nestri/_v2-entry-point"
|
||||
local temp_entrypoint="/tmp/_v2-entry-point.padded"
|
||||
|
||||
if [[ ! -f "$custom_entrypoint" ]]; then
|
||||
log "Error: Custom _v2-entry-point not found at $custom_entrypoint"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Starting Steam _v2-entry-point patcher..."
|
||||
(
|
||||
while true; do
|
||||
for i in "${!entrypoints[@]}"; do
|
||||
local steam_entrypoint="${entrypoints[$i]}"
|
||||
|
||||
if [[ -f "$steam_entrypoint" ]]; then
|
||||
# Get original file size
|
||||
local original_size
|
||||
original_size=$(stat -c %s "$steam_entrypoint" 2>/dev/null)
|
||||
if [[ -z "$original_size" ]] || [[ "$original_size" -eq 0 ]]; then
|
||||
log "Warning: Could not determine size of $steam_entrypoint, retrying..."
|
||||
continue
|
||||
fi
|
||||
|
||||
# Copy custom entrypoint to temp location
|
||||
cp "$custom_entrypoint" "$temp_entrypoint" 2>/dev/null || {
|
||||
log "Warning: Failed to copy custom entrypoint to $temp_entrypoint"
|
||||
continue
|
||||
}
|
||||
|
||||
# Pad the temporary file to match original size
|
||||
if (( $(stat -c %s "$temp_entrypoint") < original_size )); then
|
||||
truncate -s "$original_size" "$temp_entrypoint" 2>/dev/null || {
|
||||
log "Warning: Failed to pad $temp_entrypoint to $original_size bytes"
|
||||
continue
|
||||
}
|
||||
fi
|
||||
|
||||
# Copy padded file to Steam's entrypoint, if contents differ
|
||||
if ! cmp -s "$temp_entrypoint" "$steam_entrypoint"; then
|
||||
cp "$temp_entrypoint" "$steam_entrypoint" 2>/dev/null || {
|
||||
log "Warning: Failed to patch $steam_entrypoint"
|
||||
}
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Sleep for 1s
|
||||
sleep 1
|
||||
done
|
||||
) &
|
||||
PATCHER_PID=$!
|
||||
log "Steam _v2-entry-point patcher started (PID: $PATCHER_PID)"
|
||||
}
|
||||
|
||||
# Starts nestri-server
|
||||
start_nestri_server() {
|
||||
kill_if_running "${NESTRI_PID:-}" "nestri-server"
|
||||
|
||||
log "Starting nestri-server..."
|
||||
nestri-server $NESTRI_PARAMS &
|
||||
NESTRI_PID=$!
|
||||
|
||||
log "Waiting for Wayland display 'wayland-1'..."
|
||||
WAYLAND_SOCKET="${XDG_RUNTIME_DIR}/wayland-1"
|
||||
|
||||
# Make sure to remove old socket if exists (along with .lock file)
|
||||
if [[ -e "$WAYLAND_SOCKET" ]]; then
|
||||
log "Removing stale Wayland socket $WAYLAND_SOCKET"
|
||||
rm -f "$WAYLAND_SOCKET" 2>/dev/null || {
|
||||
log "Error: Failed to remove stale Wayland socket $WAYLAND_SOCKET"
|
||||
exit 1
|
||||
}
|
||||
# Ignore error if .lock file doesn't exist
|
||||
rm -f "${WAYLAND_SOCKET}.lock" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Also if gstreamer cache exists, remove it to avoid previous errors from persisting
|
||||
local gst_cache="${NESTRI_HOME}/.cache/gstreamer-1.0/"
|
||||
if [[ -d "$gst_cache" ]]; then
|
||||
log "Removing gstreamer cache at $gst_cache"
|
||||
rm -rf "${gst_cache}" 2>/dev/null || {
|
||||
log "Warning: Failed to remove gstreamer cache at $gst_cache"
|
||||
}
|
||||
fi
|
||||
|
||||
# Start nestri-server
|
||||
log "Starting nestri-server.."
|
||||
# Try with realtime scheduling first (chrt -f 80), if fails, launch normally
|
||||
if $ENTCMD_PREFIX chrt -f 80 true 2>/dev/null; then
|
||||
$ENTCMD_PREFIX chrt -f 80 nestri-server $NESTRI_PARAMS &
|
||||
NESTRI_PID=$!
|
||||
log "Started nestri-server with realtime scheduling"
|
||||
else
|
||||
$ENTCMD_PREFIX nestri-server $NESTRI_PARAMS &
|
||||
NESTRI_PID=$!
|
||||
log "Started nestri-server"
|
||||
fi
|
||||
|
||||
log "Waiting for Wayland display $WAYLAND_SOCKET.."
|
||||
for ((i=1; i<=15; i++)); do
|
||||
if [[ -e "$WAYLAND_SOCKET" ]]; then
|
||||
log "Wayland display 'wayland-1' ready"
|
||||
log "Wayland display $WAYLAND_SOCKET ready"
|
||||
sleep "${WAYLAND_READY_DELAY:-3}"
|
||||
start_compositor
|
||||
return
|
||||
@@ -127,12 +96,7 @@ start_nestri_server() {
|
||||
sleep 1
|
||||
done
|
||||
|
||||
log "Error: Wayland display 'wayland-1' not available"
|
||||
|
||||
# Workaround for gstreamer being bit slow at times
|
||||
log "Clearing gstreamer cache.."
|
||||
rm -rf "${HOME}/.cache/gstreamer-1.0" 2>/dev/null || true
|
||||
|
||||
log "Error: Wayland display $WAYLAND_SOCKET not available"
|
||||
increment_retry "nestri-server"
|
||||
restart_chain
|
||||
}
|
||||
@@ -144,15 +108,17 @@ start_compositor() {
|
||||
|
||||
# Set default values only if variables are unset (not empty)
|
||||
if [[ -z "${NESTRI_LAUNCH_CMD+x}" ]]; then
|
||||
NESTRI_LAUNCH_CMD="steam-native -tenfoot -cef-force-gpu"
|
||||
NESTRI_LAUNCH_CMD="dbus-launch steam -tenfoot -cef-force-gpu"
|
||||
fi
|
||||
if [[ -z "${NESTRI_LAUNCH_COMPOSITOR+x}" ]]; then
|
||||
NESTRI_LAUNCH_COMPOSITOR="gamescope --backend wayland --force-grab-cursor -g -f --rt --mangoapp -W ${WIDTH} -H ${HEIGHT} -r ${FRAMERATE:-60}"
|
||||
fi
|
||||
|
||||
# Start Steam patcher only if Steam command is present and if needed for container runtime
|
||||
if [[ -n "${NESTRI_LAUNCH_CMD}" ]] && [[ "$NESTRI_LAUNCH_CMD" == *"steam"* ]] && [[ "${container_runtime:-}" != "podman" ]]; then
|
||||
start_steam_namespaceless_patcher
|
||||
# If PRELOAD_SHIM_arch's are set and exist, set LD_PRELOAD for 32/64-bit apps
|
||||
local do_ld_preload=false
|
||||
if [[ -f "$PRELOAD_SHIM_64" ]] || [[ -f "$PRELOAD_SHIM_32" ]]; then
|
||||
do_ld_preload=true
|
||||
log "Using LD_PRELOAD shim(s)"
|
||||
fi
|
||||
|
||||
# Launch compositor if configured
|
||||
@@ -163,32 +129,51 @@ start_compositor() {
|
||||
# Check if this is a gamescope command
|
||||
if [[ "$compositor_cmd" == *"gamescope"* ]]; then
|
||||
is_gamescope=true
|
||||
# Append application command for gamescope if needed
|
||||
if [[ -n "$NESTRI_LAUNCH_CMD" ]] && [[ "$compositor_cmd" != *" -- "* ]]; then
|
||||
# If steam in launch command, enable gamescope integration via -e
|
||||
if [[ "$NESTRI_LAUNCH_CMD" == *"steam"* ]]; then
|
||||
compositor_cmd+=" -e"
|
||||
fi
|
||||
compositor_cmd+=" -- $NESTRI_LAUNCH_CMD"
|
||||
# If ld_preload is true, add env with LD_PRELOAD
|
||||
if $do_ld_preload; then
|
||||
compositor_cmd+=" -- env LD_PRELOAD='/usr/\$LIB/libvimputti_shim.so' bash -c $(printf %q "$NESTRI_LAUNCH_CMD")"
|
||||
else
|
||||
compositor_cmd+=" -- bash -c $(printf %q "$NESTRI_LAUNCH_CMD")"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get appropriate socket based on compositor type
|
||||
if $is_gamescope; then
|
||||
COMPOSITOR_SOCKET="${XDG_RUNTIME_DIR}/gamescope-0"
|
||||
else
|
||||
COMPOSITOR_SOCKET="${XDG_RUNTIME_DIR}/wayland-0"
|
||||
fi
|
||||
|
||||
# Clean up old socket if exists
|
||||
if [[ -e "$COMPOSITOR_SOCKET" ]]; then
|
||||
log "Removing stale compositor socket $COMPOSITOR_SOCKET"
|
||||
rm -f "$COMPOSITOR_SOCKET" 2>/dev/null || {
|
||||
log "Error: Failed to remove stale compositor socket $COMPOSITOR_SOCKET"
|
||||
exit 1
|
||||
}
|
||||
# Ignore error if .lock file doesn't exist
|
||||
rm -f "${COMPOSITOR_SOCKET}.lock" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log "Starting compositor: $compositor_cmd"
|
||||
WAYLAND_DISPLAY=wayland-1 /bin/bash -c "$compositor_cmd" &
|
||||
COMPOSITOR_PID=$!
|
||||
|
||||
# Wait for appropriate socket based on compositor type
|
||||
if $is_gamescope; then
|
||||
COMPOSITOR_SOCKET="${XDG_RUNTIME_DIR}/gamescope-0"
|
||||
log "Waiting for gamescope socket..."
|
||||
else
|
||||
COMPOSITOR_SOCKET="${XDG_RUNTIME_DIR}/wayland-0"
|
||||
log "Waiting for wayland-0 socket..."
|
||||
# If ld_preload is true, export LD_PRELOAD
|
||||
if $do_ld_preload; then
|
||||
export LD_PRELOAD='/usr/$LIB/libvimputti_shim.so'
|
||||
fi
|
||||
|
||||
log "Waiting for compositor socket $COMPOSITOR_SOCKET.."
|
||||
for ((i=1; i<=15; i++)); do
|
||||
if [[ -e "$COMPOSITOR_SOCKET" ]]; then
|
||||
log "Compositor socket ready ($COMPOSITOR_SOCKET)."
|
||||
log "Compositor socket ready $COMPOSITOR_SOCKET"
|
||||
# Patch resolution with wlr-randr for non-gamescope compositors
|
||||
if ! $is_gamescope; then
|
||||
local OUTPUT_NAME
|
||||
@@ -205,6 +190,8 @@ start_compositor() {
|
||||
WAYLAND_DISPLAY=wayland-0 /bin/bash -c "$NESTRI_LAUNCH_CMD" &
|
||||
APP_PID=$!
|
||||
fi
|
||||
else
|
||||
log "Gamescope detected, skipping wlr-randr resolution patch"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
@@ -246,8 +233,6 @@ cleanup() {
|
||||
kill_if_running "${NESTRI_PID:-}" "nestri-server"
|
||||
kill_if_running "${COMPOSITOR_PID:-}" "compositor"
|
||||
kill_if_running "${APP_PID:-}" "application"
|
||||
kill_if_running "${PATCHER_PID:-}" "steam-patcher"
|
||||
rm -f "/tmp/_v2-entry-point.padded" 2>/dev/null
|
||||
exit $exit_code
|
||||
}
|
||||
|
||||
@@ -272,11 +257,6 @@ main_loop() {
|
||||
log "application died"
|
||||
increment_retry "application"
|
||||
start_compositor
|
||||
# Check patcher
|
||||
elif [[ -n "${PATCHER_PID:-}" ]] && ! kill -0 "${PATCHER_PID}" 2>/dev/null; then
|
||||
log "steam-patcher died"
|
||||
increment_retry "steam-patcher"
|
||||
start_steam_namespaceless_patcher
|
||||
fi
|
||||
done
|
||||
}
|
||||
@@ -287,9 +267,8 @@ main() {
|
||||
log "Warning: Failed to detect container information."
|
||||
}
|
||||
|
||||
# Ensure DBus session env exists
|
||||
if command -v dbus-launch >/dev/null 2>&1 && [[ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]]; then
|
||||
eval "$(dbus-launch)"
|
||||
if [[ "$container_runtime" != "apptainer" ]]; then
|
||||
ENTCMD_PREFIX="sudo -E -u ${NESTRI_USER}"
|
||||
fi
|
||||
|
||||
restart_chain
|
||||
|
||||
@@ -41,6 +41,15 @@ priority=5
|
||||
nice=-10
|
||||
environment=HOME=%(ENV_NESTRI_HOME)s,XDG_RUNTIME_DIR=%(ENV_NESTRI_XDG_RUNTIME_DIR)s
|
||||
|
||||
[program:vimputti-manager]
|
||||
user=nestri
|
||||
command=vimputti-manager
|
||||
autorestart=true
|
||||
autostart=true
|
||||
startretries=3
|
||||
priority=6
|
||||
environment=HOME=%(ENV_NESTRI_HOME)s,XDG_RUNTIME_DIR=%(ENV_NESTRI_XDG_RUNTIME_DIR)s,VIMPUTTI_PATH=%(ENV_NESTRI_VIMPUTTI_PATH)s
|
||||
|
||||
[program:entrypoint]
|
||||
command=/etc/nestri/entrypoint.sh
|
||||
autorestart=false
|
||||
|
||||
1712
packages/server/Cargo.lock
generated
1712
packages/server/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,22 +11,22 @@ path = "src/main.rs"
|
||||
gstreamer = { version = "0.24", features = ["v1_26"] }
|
||||
gstreamer-webrtc = { version = "0.24", features = ["v1_26"] }
|
||||
gst-plugin-webrtc = { version = "0.14" }
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
tokio = { version = "1.45", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tokio = { version = "1.48", features = ["full"] }
|
||||
tokio-stream = { version = "0.1", features = ["full"] }
|
||||
clap = { version = "4.5", features = ["env", "derive"] }
|
||||
serde_json = "1.0"
|
||||
webrtc = "0.13"
|
||||
webrtc = "0.14"
|
||||
regex = "1.11"
|
||||
rand = "0.9"
|
||||
rustls = { version = "0.23", features = ["ring"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
vimputti = "0.1.3"
|
||||
chrono = "0.4"
|
||||
prost = "0.14"
|
||||
prost-types = "0.14"
|
||||
parking_lot = "0.12"
|
||||
atomic_refcell = "0.1"
|
||||
byteorder = "1.5"
|
||||
libp2p = { version = "0.56", features = ["identify", "dns", "tcp", "noise", "ping", "tokio", "serde", "yamux", "macros", "websocket", "autonat"] }
|
||||
libp2p-identify = "0.47"
|
||||
@@ -39,3 +39,4 @@ libp2p-dns = { version = "0.44", features = ["tokio"] }
|
||||
libp2p-tcp = { version = "0.44", features = ["tokio"] }
|
||||
libp2p-websocket = "0.45"
|
||||
dashmap = "6.1"
|
||||
anyhow = "1.0"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.89"
|
||||
channel = "1.90"
|
||||
@@ -58,6 +58,14 @@ impl Args {
|
||||
.env("NESTRI_ROOM")
|
||||
.help("Nestri room name/identifier"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("vimputti-path")
|
||||
.long("vimputti-path")
|
||||
.env("VIMPUTTI_PATH")
|
||||
.help("Path to vimputti socket")
|
||||
.value_parser(NonEmptyStringValueParser::new())
|
||||
.default_value("/tmp/vimputti-0"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("gpu-vendor")
|
||||
.short('g')
|
||||
@@ -204,10 +212,10 @@ impl Args {
|
||||
.default_value("192"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("dma-buf")
|
||||
.long("dma-buf")
|
||||
.env("DMA_BUF")
|
||||
.help("Use DMA-BUF for pipeline")
|
||||
Arg::new("zero-copy")
|
||||
.long("zero-copy")
|
||||
.env("ZERO_COPY")
|
||||
.help("Use zero-copy pipeline")
|
||||
.value_parser(BoolishValueParser::new())
|
||||
.default_value("false"),
|
||||
)
|
||||
|
||||
@@ -12,9 +12,12 @@ pub struct AppArgs {
|
||||
/// Nestri room name/identifier
|
||||
pub room: String,
|
||||
|
||||
/// Experimental DMA-BUF support
|
||||
/// vimputti socket path
|
||||
pub vimputti_path: Option<String>,
|
||||
|
||||
/// Experimental zero-copy pipeline support
|
||||
/// TODO: Move to video encoding flags
|
||||
pub dma_buf: bool,
|
||||
pub zero_copy: bool,
|
||||
}
|
||||
impl AppArgs {
|
||||
pub fn from_matches(matches: &clap::ArgMatches) -> Self {
|
||||
@@ -45,7 +48,13 @@ impl AppArgs {
|
||||
.get_one::<String>("room")
|
||||
.unwrap_or(&rand::random::<u32>().to_string())
|
||||
.clone(),
|
||||
dma_buf: matches.get_one::<bool>("dma-buf").unwrap_or(&false).clone(),
|
||||
vimputti_path: matches
|
||||
.get_one::<String>("vimputti-path")
|
||||
.map(|s| s.clone()),
|
||||
zero_copy: matches
|
||||
.get_one::<bool>("zero-copy")
|
||||
.unwrap_or(&false)
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +69,10 @@ impl AppArgs {
|
||||
tracing::info!("> framerate: {}", self.framerate);
|
||||
tracing::info!("> relay_url: '{}'", self.relay_url);
|
||||
tracing::info!("> room: '{}'", self.room);
|
||||
tracing::info!("> dma_buf: {}", self.dma_buf);
|
||||
tracing::info!(
|
||||
"> vimputti_path: '{}'",
|
||||
self.vimputti_path.as_ref().map_or("None", |s| s.as_str())
|
||||
);
|
||||
tracing::info!("> zero_copy: {}", self.zero_copy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,18 +11,10 @@ pub struct DeviceArgs {
|
||||
impl DeviceArgs {
|
||||
pub fn from_matches(matches: &clap::ArgMatches) -> Self {
|
||||
Self {
|
||||
gpu_vendor: matches
|
||||
.get_one::<String>("gpu-vendor")
|
||||
.cloned(),
|
||||
gpu_name: matches
|
||||
.get_one::<String>("gpu-name")
|
||||
.cloned(),
|
||||
gpu_index: matches
|
||||
.get_one::<u32>("gpu-index")
|
||||
.cloned(),
|
||||
gpu_card_path: matches
|
||||
.get_one::<String>("gpu-card-path")
|
||||
.cloned(),
|
||||
gpu_vendor: matches.get_one::<String>("gpu-vendor").cloned(),
|
||||
gpu_name: matches.get_one::<String>("gpu-name").cloned(),
|
||||
gpu_index: matches.get_one::<u32>("gpu-index").cloned(),
|
||||
gpu_card_path: matches.get_one::<String>("gpu-card-path").cloned(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -276,8 +276,8 @@ pub fn encoder_low_latency_params(
|
||||
_rate_control: &RateControl,
|
||||
framerate: u32,
|
||||
) -> VideoEncoderInfo {
|
||||
// 2 second GOP size, maybe lower to 1 second for fast recovery, if needed?
|
||||
let mut encoder_optz = encoder_gop_params(encoder, framerate * 2);
|
||||
// 1 second keyframe interval for fast recovery, is this too taxing?
|
||||
let mut encoder_optz = encoder_gop_params(encoder, framerate);
|
||||
|
||||
match encoder_optz.encoder_api {
|
||||
EncoderAPI::QSV => {
|
||||
@@ -291,6 +291,7 @@ pub fn encoder_low_latency_params(
|
||||
encoder_optz.set_parameter("multi-pass", "disabled");
|
||||
encoder_optz.set_parameter("preset", "p1");
|
||||
encoder_optz.set_parameter("tune", "ultra-low-latency");
|
||||
encoder_optz.set_parameter("zerolatency", "true");
|
||||
}
|
||||
EncoderAPI::AMF => {
|
||||
encoder_optz.set_parameter("preset", "speed");
|
||||
@@ -400,11 +401,21 @@ pub fn get_compatible_encoders(gpus: &Vec<GPUInfo>) -> Vec<VideoEncoderInfo> {
|
||||
}
|
||||
None
|
||||
} else if element.has_property("cuda-device-id") {
|
||||
let device_id =
|
||||
match element.property_value("cuda-device-id").get::<i32>() {
|
||||
Ok(v) if v >= 0 => Some(v as usize),
|
||||
_ => None,
|
||||
};
|
||||
let device_id = match element
|
||||
.property_value("cuda-device-id")
|
||||
.get::<i32>()
|
||||
{
|
||||
Ok(v) if v >= 0 => Some(v as usize),
|
||||
_ => {
|
||||
// If only one NVIDIA GPU, default to 0
|
||||
// fixes "Type: 'Hardware', Device: 'CPU'" issue
|
||||
if get_gpus_by_vendor(&gpus, GPUVendor::NVIDIA).len() == 1 {
|
||||
Some(0)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// We'll just treat cuda-device-id as an index
|
||||
device_id.and_then(|id| {
|
||||
@@ -574,7 +585,7 @@ pub fn get_best_working_encoder(
|
||||
encoders: &Vec<VideoEncoderInfo>,
|
||||
codec: &Codec,
|
||||
encoder_type: &EncoderType,
|
||||
dma_buf: bool,
|
||||
zero_copy: bool,
|
||||
) -> Result<VideoEncoderInfo, Box<dyn Error>> {
|
||||
let mut candidates = get_encoders_by_videocodec(
|
||||
encoders,
|
||||
@@ -590,7 +601,7 @@ pub fn get_best_working_encoder(
|
||||
while !candidates.is_empty() {
|
||||
let best = get_best_compatible_encoder(&candidates, codec, encoder_type)?;
|
||||
tracing::info!("Testing encoder: {}", best.name,);
|
||||
if test_encoder(&best, dma_buf).is_ok() {
|
||||
if test_encoder(&best, zero_copy).is_ok() {
|
||||
return Ok(best);
|
||||
} else {
|
||||
// Remove this encoder and try next best
|
||||
@@ -602,7 +613,7 @@ pub fn get_best_working_encoder(
|
||||
}
|
||||
|
||||
/// Test if a pipeline with the given encoder can be created and set to Playing
|
||||
pub fn test_encoder(encoder: &VideoEncoderInfo, dma_buf: bool) -> Result<(), Box<dyn Error>> {
|
||||
pub fn test_encoder(encoder: &VideoEncoderInfo, zero_copy: bool) -> Result<(), Box<dyn Error>> {
|
||||
let src = gstreamer::ElementFactory::make("waylanddisplaysrc").build()?;
|
||||
if let Some(gpu_info) = &encoder.gpu_info {
|
||||
src.set_property_from_str("render-node", gpu_info.render_path());
|
||||
@@ -610,12 +621,16 @@ pub fn test_encoder(encoder: &VideoEncoderInfo, dma_buf: bool) -> Result<(), Box
|
||||
let caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
|
||||
let caps = gstreamer::Caps::from_str(&format!(
|
||||
"{},width=1280,height=720,framerate=30/1{}",
|
||||
if dma_buf {
|
||||
"video/x-raw(memory:DMABuf)"
|
||||
if zero_copy {
|
||||
if encoder.encoder_api == EncoderAPI::NVENC {
|
||||
"video/x-raw(memory:CUDAMemory)"
|
||||
} else {
|
||||
"video/x-raw(memory:DMABuf)"
|
||||
}
|
||||
} else {
|
||||
"video/x-raw"
|
||||
},
|
||||
if dma_buf { "" } else { ",format=RGBx" }
|
||||
if zero_copy { "" } else { ",format=RGBx" }
|
||||
))?;
|
||||
caps_filter.set_property("caps", &caps);
|
||||
|
||||
@@ -627,66 +642,47 @@ pub fn test_encoder(encoder: &VideoEncoderInfo, dma_buf: bool) -> Result<(), Box
|
||||
// Create pipeline and link elements
|
||||
let pipeline = gstreamer::Pipeline::new();
|
||||
|
||||
if dma_buf && encoder.encoder_api == EncoderAPI::NVENC {
|
||||
// GL upload element
|
||||
let glupload = gstreamer::ElementFactory::make("glupload").build()?;
|
||||
// GL color convert element
|
||||
let glconvert = gstreamer::ElementFactory::make("glcolorconvert").build()?;
|
||||
// GL color convert caps
|
||||
let gl_caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
|
||||
let gl_caps = gstreamer::Caps::from_str("video/x-raw(memory:GLMemory),format=NV12")?;
|
||||
gl_caps_filter.set_property("caps", &gl_caps);
|
||||
// CUDA upload element
|
||||
let cudaupload = gstreamer::ElementFactory::make("cudaupload").build()?;
|
||||
if zero_copy {
|
||||
if encoder.encoder_api == EncoderAPI::NVENC {
|
||||
// NVENC zero-copy path
|
||||
pipeline.add_many(&[&src, &caps_filter, &enc, &sink])?;
|
||||
gstreamer::Element::link_many(&[&src, &caps_filter, &enc, &sink])?;
|
||||
} else {
|
||||
// VA-API/QSV zero-copy path
|
||||
let vapostproc = gstreamer::ElementFactory::make("vapostproc").build()?;
|
||||
let va_caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
|
||||
let va_caps = gstreamer::Caps::from_str("video/x-raw(memory:VAMemory),format=NV12")?;
|
||||
va_caps_filter.set_property("caps", &va_caps);
|
||||
|
||||
pipeline.add_many(&[
|
||||
&src,
|
||||
&caps_filter,
|
||||
&glupload,
|
||||
&glconvert,
|
||||
&gl_caps_filter,
|
||||
&cudaupload,
|
||||
&enc,
|
||||
&sink,
|
||||
])?;
|
||||
gstreamer::Element::link_many(&[
|
||||
&src,
|
||||
&caps_filter,
|
||||
&glupload,
|
||||
&glconvert,
|
||||
&gl_caps_filter,
|
||||
&cudaupload,
|
||||
&enc,
|
||||
&sink,
|
||||
])?;
|
||||
pipeline.add_many(&[
|
||||
&src,
|
||||
&caps_filter,
|
||||
&vapostproc,
|
||||
&va_caps_filter,
|
||||
&enc,
|
||||
&sink,
|
||||
])?;
|
||||
gstreamer::Element::link_many(&[
|
||||
&src,
|
||||
&caps_filter,
|
||||
&vapostproc,
|
||||
&va_caps_filter,
|
||||
&enc,
|
||||
&sink,
|
||||
])?;
|
||||
}
|
||||
} else {
|
||||
let vapostproc = gstreamer::ElementFactory::make("vapostproc").build()?;
|
||||
// VA caps filter
|
||||
let va_caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
|
||||
let va_caps = gstreamer::Caps::from_str("video/x-raw(memory:VAMemory),format=NV12")?;
|
||||
va_caps_filter.set_property("caps", &va_caps);
|
||||
|
||||
pipeline.add_many(&[
|
||||
&src,
|
||||
&caps_filter,
|
||||
&vapostproc,
|
||||
&va_caps_filter,
|
||||
&enc,
|
||||
&sink,
|
||||
])?;
|
||||
gstreamer::Element::link_many(&[
|
||||
&src,
|
||||
&caps_filter,
|
||||
&vapostproc,
|
||||
&va_caps_filter,
|
||||
&enc,
|
||||
&sink,
|
||||
])?;
|
||||
// Non-zero-copy path for all encoders - needs videoconvert
|
||||
let videoconvert = gstreamer::ElementFactory::make("videoconvert").build()?;
|
||||
pipeline.add_many(&[&src, &caps_filter, &videoconvert, &enc, &sink])?;
|
||||
gstreamer::Element::link_many(&[&src, &caps_filter, &videoconvert, &enc, &sink])?;
|
||||
}
|
||||
|
||||
let bus = pipeline.bus().ok_or("Pipeline has no bus")?;
|
||||
let _ = pipeline.set_state(gstreamer::State::Playing);
|
||||
for msg in bus.iter_timed(gstreamer::ClockTime::from_seconds(2)) {
|
||||
pipeline.set_state(gstreamer::State::Playing)?;
|
||||
|
||||
// Wait for either error or async-done (state change complete)
|
||||
for msg in bus.iter_timed(gstreamer::ClockTime::from_seconds(10)) {
|
||||
match msg.view() {
|
||||
gstreamer::MessageView::Error(err) => {
|
||||
let err_msg = format!("Pipeline error: {}", err.error());
|
||||
@@ -694,14 +690,17 @@ pub fn test_encoder(encoder: &VideoEncoderInfo, dma_buf: bool) -> Result<(), Box
|
||||
let _ = pipeline.set_state(gstreamer::State::Null);
|
||||
return Err(err_msg.into());
|
||||
}
|
||||
gstreamer::MessageView::Eos(_) => {
|
||||
tracing::info!("Pipeline EOS received");
|
||||
gstreamer::MessageView::AsyncDone(_) => {
|
||||
// Pipeline successfully reached PLAYING state
|
||||
tracing::debug!("Pipeline reached PLAYING state successfully");
|
||||
let _ = pipeline.set_state(gstreamer::State::Null);
|
||||
return Err("Pipeline EOS received, encoder test failed".into());
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// If we got here, timeout occurred without reaching PLAYING or error
|
||||
let _ = pipeline.set_state(gstreamer::State::Null);
|
||||
Ok(())
|
||||
Err("Encoder test timed out".into())
|
||||
}
|
||||
|
||||
@@ -112,11 +112,25 @@ pub fn get_gpus() -> Result<Vec<GPUInfo>, Box<dyn Error>> {
|
||||
let minor = &caps[1];
|
||||
|
||||
// Read vendor and device ID
|
||||
let vendor_str = fs::read_to_string(format!("/sys/class/drm/card{}/device/vendor", minor))?;
|
||||
let vendor_str = fs::read_to_string(format!("/sys/class/drm/card{}/device/vendor", minor));
|
||||
let vendor_str = match vendor_str {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to read vendor for card{}: {}", minor, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let vendor_str = vendor_str.trim_start_matches("0x").trim_end_matches('\n');
|
||||
let vendor = u16::from_str_radix(vendor_str, 16)?;
|
||||
|
||||
let device_str = fs::read_to_string(format!("/sys/class/drm/card{}/device/device", minor))?;
|
||||
let device_str = fs::read_to_string(format!("/sys/class/drm/card{}/device/device", minor));
|
||||
let device_str = match device_str {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to read device for card{}: {}", minor, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let device_str = device_str.trim_start_matches("0x").trim_end_matches('\n');
|
||||
|
||||
// Look up in hwdata PCI database
|
||||
@@ -129,7 +143,15 @@ pub fn get_gpus() -> Result<Vec<GPUInfo>, Box<dyn Error>> {
|
||||
};
|
||||
|
||||
// Read PCI bus ID
|
||||
let pci_bus_id = fs::read_to_string(format!("/sys/class/drm/card{}/device/uevent", minor))?;
|
||||
let pci_bus_id = fs::read_to_string(format!("/sys/class/drm/card{}/device/uevent", minor));
|
||||
let pci_bus_id = match pci_bus_id {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to read PCI bus ID for card{}: {}", minor, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
// Extract PCI_SLOT_NAME from uevent content
|
||||
let pci_bus_id = pci_bus_id
|
||||
.lines()
|
||||
.find_map(|line| {
|
||||
@@ -191,7 +213,6 @@ fn parse_pci_ids(pci_data: &str, vendor_id: &str, device_id: &str) -> Option<Str
|
||||
|
||||
fn get_dri_device_path(pci_addr: &str) -> Option<(String, String)> {
|
||||
let entries = fs::read_dir("/sys/bus/pci/devices").ok()?;
|
||||
|
||||
for entry in entries.flatten() {
|
||||
if !entry.path().to_string_lossy().contains(&pci_addr) {
|
||||
continue;
|
||||
|
||||
1
packages/server/src/input.rs
Normal file
1
packages/server/src/input.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod controller;
|
||||
205
packages/server/src/input/controller.rs
Normal file
205
packages/server/src/input/controller.rs
Normal file
@@ -0,0 +1,205 @@
|
||||
use crate::proto::proto::proto_input::InputType::{
|
||||
ControllerAttach, ControllerAxis, ControllerButton, ControllerDetach, ControllerRumble,
|
||||
ControllerStick, ControllerTrigger,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
fn controller_string_to_type(controller_type: &str) -> Result<vimputti::DeviceConfig> {
|
||||
match controller_type.to_lowercase().as_str() {
|
||||
"ps4" => Ok(vimputti::ControllerTemplates::ps4()),
|
||||
"ps5" => Ok(vimputti::ControllerTemplates::ps5()),
|
||||
"xbox360" => Ok(vimputti::ControllerTemplates::xbox360()),
|
||||
"xboxone" => Ok(vimputti::ControllerTemplates::xbox_one()),
|
||||
"switchpro" => Ok(vimputti::ControllerTemplates::switch_pro()),
|
||||
_ => Err(anyhow::anyhow!(
|
||||
"Unsupported controller type: {}",
|
||||
controller_type
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ControllerInput {
|
||||
config: vimputti::DeviceConfig,
|
||||
device: vimputti::client::VirtualController,
|
||||
}
|
||||
impl ControllerInput {
|
||||
pub async fn new(
|
||||
controller_type: String,
|
||||
client: &vimputti::client::VimputtiClient,
|
||||
) -> Result<Self> {
|
||||
let config = controller_string_to_type(&controller_type)?;
|
||||
Ok(Self {
|
||||
config: config.clone(),
|
||||
device: client.create_device(config).await?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn device_mut(&mut self) -> &mut vimputti::client::VirtualController {
|
||||
&mut self.device
|
||||
}
|
||||
|
||||
pub fn device(&self) -> &vimputti::client::VirtualController {
|
||||
&self.device
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ControllerManager {
|
||||
vimputti_client: Arc<vimputti::client::VimputtiClient>,
|
||||
cmd_tx: mpsc::Sender<crate::proto::proto::ProtoInput>,
|
||||
rumble_tx: mpsc::Sender<(u32, u16, u16, u16)>, // (slot, strong, weak, duration_ms)
|
||||
}
|
||||
impl ControllerManager {
|
||||
pub fn new(
|
||||
vimputti_client: Arc<vimputti::client::VimputtiClient>,
|
||||
) -> Result<(Self, mpsc::Receiver<(u32, u16, u16, u16)>)> {
|
||||
let (cmd_tx, cmd_rx) = mpsc::channel(100);
|
||||
let (rumble_tx, rumble_rx) = mpsc::channel(100);
|
||||
tokio::spawn(command_loop(
|
||||
cmd_rx,
|
||||
vimputti_client.clone(),
|
||||
rumble_tx.clone(),
|
||||
));
|
||||
Ok((
|
||||
Self {
|
||||
vimputti_client,
|
||||
cmd_tx,
|
||||
rumble_tx,
|
||||
},
|
||||
rumble_rx,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn send_command(&self, input: crate::proto::proto::ProtoInput) -> Result<()> {
|
||||
self.cmd_tx.send(input).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn command_loop(
|
||||
mut cmd_rx: mpsc::Receiver<crate::proto::proto::ProtoInput>,
|
||||
vimputti_client: Arc<vimputti::client::VimputtiClient>,
|
||||
rumble_tx: mpsc::Sender<(u32, u16, u16, u16)>,
|
||||
) {
|
||||
let mut controllers: HashMap<u32, ControllerInput> = HashMap::new();
|
||||
while let Some(input) = cmd_rx.recv().await {
|
||||
if let Some(input_type) = input.input_type {
|
||||
match input_type {
|
||||
ControllerAttach(data) => {
|
||||
// Check if controller already exists in the slot, if so, ignore
|
||||
if controllers.contains_key(&(data.slot as u32)) {
|
||||
tracing::warn!(
|
||||
"Controller slot {} already occupied, ignoring attach",
|
||||
data.slot
|
||||
);
|
||||
} else {
|
||||
if let Ok(mut controller) =
|
||||
ControllerInput::new(data.id.clone(), &vimputti_client).await
|
||||
{
|
||||
let slot = data.slot as u32;
|
||||
let rumble_tx = rumble_tx.clone();
|
||||
|
||||
controller
|
||||
.device_mut()
|
||||
.on_rumble(move |strong, weak, duration_ms| {
|
||||
let _ = rumble_tx.try_send((slot, strong, weak, duration_ms));
|
||||
})
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::warn!(
|
||||
"Failed to register rumble callback for slot {}: {}",
|
||||
slot,
|
||||
e
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
|
||||
controllers.insert(data.slot as u32, controller);
|
||||
tracing::info!("Controller {} attached to slot {}", data.id, data.slot);
|
||||
} else {
|
||||
tracing::error!(
|
||||
"Failed to create controller of type {} for slot {}",
|
||||
data.id,
|
||||
data.slot
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ControllerDetach(data) => {
|
||||
if controllers.remove(&(data.slot as u32)).is_some() {
|
||||
tracing::info!("Controller detached from slot {}", data.slot);
|
||||
} else {
|
||||
tracing::warn!("No controller found in slot {} to detach", data.slot);
|
||||
}
|
||||
}
|
||||
ControllerButton(data) => {
|
||||
if let Some(controller) = controllers.get(&(data.slot as u32)) {
|
||||
if let Some(button) = vimputti::Button::from_ev_code(data.button as u16) {
|
||||
let device = controller.device();
|
||||
device.button(button, data.pressed);
|
||||
device.sync();
|
||||
}
|
||||
} else {
|
||||
tracing::warn!("Controller slot {} not found for button event", data.slot);
|
||||
}
|
||||
}
|
||||
ControllerStick(data) => {
|
||||
if let Some(controller) = controllers.get(&(data.slot as u32)) {
|
||||
let device = controller.device();
|
||||
if data.stick == 0 {
|
||||
// Left stick
|
||||
device.axis(vimputti::Axis::LeftStickX, data.x);
|
||||
device.sync();
|
||||
device.axis(vimputti::Axis::LeftStickY, data.y);
|
||||
} else if data.stick == 1 {
|
||||
// Right stick
|
||||
device.axis(vimputti::Axis::RightStickX, data.x);
|
||||
device.sync();
|
||||
device.axis(vimputti::Axis::RightStickY, data.y);
|
||||
}
|
||||
device.sync();
|
||||
} else {
|
||||
tracing::warn!("Controller slot {} not found for stick event", data.slot);
|
||||
}
|
||||
}
|
||||
ControllerTrigger(data) => {
|
||||
if let Some(controller) = controllers.get(&(data.slot as u32)) {
|
||||
let device = controller.device();
|
||||
if data.trigger == 0 {
|
||||
// Left trigger
|
||||
device.axis(vimputti::Axis::LowerLeftTrigger, data.value);
|
||||
} else if data.trigger == 1 {
|
||||
// Right trigger
|
||||
device.axis(vimputti::Axis::LowerRightTrigger, data.value);
|
||||
}
|
||||
device.sync();
|
||||
} else {
|
||||
tracing::warn!("Controller slot {} not found for trigger event", data.slot);
|
||||
}
|
||||
}
|
||||
ControllerAxis(data) => {
|
||||
if let Some(controller) = controllers.get(&(data.slot as u32)) {
|
||||
let device = controller.device();
|
||||
if data.axis == 0 {
|
||||
// dpad x
|
||||
device.axis(vimputti::Axis::DPadX, data.value);
|
||||
} else if data.axis == 1 {
|
||||
// dpad y
|
||||
device.axis(vimputti::Axis::DPadY, data.value);
|
||||
}
|
||||
device.sync();
|
||||
}
|
||||
}
|
||||
// Rumble will be outgoing event..
|
||||
ControllerRumble(_) => {
|
||||
//no-op
|
||||
}
|
||||
_ => {
|
||||
//no-op
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod args;
|
||||
mod enc_helper;
|
||||
mod gpu;
|
||||
mod input;
|
||||
mod latency;
|
||||
mod messages;
|
||||
mod nestrisink;
|
||||
@@ -10,6 +11,7 @@ mod proto;
|
||||
use crate::args::encoding_args;
|
||||
use crate::enc_helper::{EncoderAPI, EncoderType};
|
||||
use crate::gpu::{GPUInfo, GPUVendor};
|
||||
use crate::input::controller::ControllerManager;
|
||||
use crate::nestrisink::NestriSignaller;
|
||||
use crate::p2p::p2p::NestriP2P;
|
||||
use gstreamer::prelude::*;
|
||||
@@ -118,7 +120,7 @@ fn handle_encoder_video(
|
||||
&video_encoders,
|
||||
&args.encoding.video.codec,
|
||||
&args.encoding.video.encoder_type,
|
||||
args.app.dma_buf,
|
||||
args.app.zero_copy,
|
||||
)?;
|
||||
}
|
||||
tracing::info!("Selected video encoder: '{}'", video_encoder.name);
|
||||
@@ -174,9 +176,6 @@ fn handle_encoder_audio(args: &args::Args) -> String {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Parse command line arguments
|
||||
let mut args = args::Args::new();
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
EnvFilter::builder()
|
||||
@@ -185,6 +184,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
)
|
||||
.init();
|
||||
|
||||
// Parse command line arguments
|
||||
let mut args = args::Args::new();
|
||||
|
||||
if args.app.verbose {
|
||||
args.debug_print();
|
||||
}
|
||||
@@ -199,13 +201,15 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
gstreamer::init()?;
|
||||
let _ = gstrswebrtc::plugin_register_static(); // Might be already registered, so we'll pass..
|
||||
|
||||
if args.app.dma_buf {
|
||||
if args.app.zero_copy {
|
||||
if args.encoding.video.encoder_type != EncoderType::HARDWARE {
|
||||
tracing::warn!("DMA-BUF is only supported with hardware encoders, disabling DMA-BUF..");
|
||||
args.app.dma_buf = false;
|
||||
tracing::warn!(
|
||||
"zero-copy is only supported with hardware encoders, disabling zero-copy.."
|
||||
);
|
||||
args.app.zero_copy = false;
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"DMA-BUF is experimental, it may or may not improve performance, or even work at all."
|
||||
"zero-copy is experimental, it may or may not improve performance, or even work at all."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -238,6 +242,28 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let nestri_p2p = Arc::new(NestriP2P::new().await?);
|
||||
let p2p_conn = nestri_p2p.connect(relay_url).await?;
|
||||
|
||||
// Get vimputti manager connection if available
|
||||
let vpath = match args.app.vimputti_path {
|
||||
Some(ref path) => path.clone(),
|
||||
None => "/tmp/vimputti-0".to_string(),
|
||||
};
|
||||
let vimputti_client = match vimputti::VimputtiClient::connect(vpath).await {
|
||||
Ok(client) => {
|
||||
tracing::info!("Connected to vimputti manager");
|
||||
Some(Arc::new(client))
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to connect to vimputti manager: {}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
let (controller_manager, rumble_rx) = if let Some(vclient) = vimputti_client {
|
||||
let (controller_manager, rumble_rx) = ControllerManager::new(vclient)?;
|
||||
(Some(Arc::new(controller_manager)), Some(rumble_rx))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
/*** PIPELINE CREATION ***/
|
||||
// Create the pipeline
|
||||
let pipeline = Arc::new(gstreamer::Pipeline::new());
|
||||
@@ -266,7 +292,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
// Required to fix gstreamer opus issue, where quality sounds off (due to wrong sample rate)
|
||||
let audio_capsfilter = gstreamer::ElementFactory::make("capsfilter").build()?;
|
||||
let audio_caps = gstreamer::Caps::from_str("audio/x-raw,rate=48000,channels=2").unwrap();
|
||||
let audio_caps = gstreamer::Caps::from_str("audio/x-raw,rate=48000,channels=2")?;
|
||||
audio_capsfilter.set_property("caps", &audio_caps);
|
||||
|
||||
// Audio Encoder Element
|
||||
@@ -302,22 +328,30 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
|
||||
let caps = gstreamer::Caps::from_str(&format!(
|
||||
"{},width={},height={},framerate={}/1{}",
|
||||
if args.app.dma_buf {
|
||||
"video/x-raw(memory:DMABuf)"
|
||||
if args.app.zero_copy {
|
||||
if video_encoder_info.encoder_api == EncoderAPI::NVENC {
|
||||
"video/x-raw(memory:CUDAMemory)"
|
||||
} else {
|
||||
"video/x-raw(memory:DMABuf)"
|
||||
}
|
||||
} else {
|
||||
"video/x-raw"
|
||||
},
|
||||
args.app.resolution.0,
|
||||
args.app.resolution.1,
|
||||
args.app.framerate,
|
||||
if args.app.dma_buf { "" } else { ",format=RGBx" }
|
||||
if args.app.zero_copy {
|
||||
""
|
||||
} else {
|
||||
",format=RGBx"
|
||||
}
|
||||
))?;
|
||||
caps_filter.set_property("caps", &caps);
|
||||
|
||||
// Get bit-depth and choose appropriate format (NV12 or P010_10LE)
|
||||
// H.264 does not support above 8-bit. Also we require DMA-BUF.
|
||||
let video_format = if args.encoding.video.bit_depth == 10
|
||||
&& args.app.dma_buf
|
||||
&& args.app.zero_copy
|
||||
&& video_encoder_info.codec != enc_helper::VideoCodec::H264
|
||||
{
|
||||
"P010_10LE"
|
||||
@@ -325,27 +359,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
"NV12"
|
||||
};
|
||||
|
||||
// GL and CUDA elements (NVIDIA only..)
|
||||
let mut glupload = None;
|
||||
let mut glconvert = None;
|
||||
let mut gl_caps_filter = None;
|
||||
let mut cudaupload = None;
|
||||
if args.app.dma_buf && video_encoder_info.encoder_api == EncoderAPI::NVENC {
|
||||
// GL upload element
|
||||
glupload = Some(gstreamer::ElementFactory::make("glupload").build()?);
|
||||
// GL color convert element
|
||||
glconvert = Some(gstreamer::ElementFactory::make("glcolorconvert").build()?);
|
||||
// GL color convert caps
|
||||
let caps_filter = gstreamer::ElementFactory::make("capsfilter").build()?;
|
||||
let gl_caps = gstreamer::Caps::from_str(
|
||||
format!("video/x-raw(memory:GLMemory),format={video_format}").as_str(),
|
||||
)?;
|
||||
caps_filter.set_property("caps", &gl_caps);
|
||||
gl_caps_filter = Some(caps_filter);
|
||||
// CUDA upload element
|
||||
cudaupload = Some(gstreamer::ElementFactory::make("cudaupload").build()?);
|
||||
}
|
||||
|
||||
// vapostproc for VA compatible encoders
|
||||
let mut vapostproc = None;
|
||||
let mut va_caps_filter = None;
|
||||
@@ -364,7 +377,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
// Video Converter Element
|
||||
let mut video_converter = None;
|
||||
if !args.app.dma_buf {
|
||||
if !args.app.zero_copy {
|
||||
video_converter = Some(gstreamer::ElementFactory::make("videoconvert").build()?);
|
||||
}
|
||||
|
||||
@@ -397,24 +410,34 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
/* Output */
|
||||
// WebRTC sink Element
|
||||
let signaller =
|
||||
NestriSignaller::new(args.app.room, p2p_conn.clone(), video_source.clone()).await?;
|
||||
let signaller = NestriSignaller::new(
|
||||
args.app.room,
|
||||
p2p_conn.clone(),
|
||||
video_source.clone(),
|
||||
controller_manager,
|
||||
rumble_rx,
|
||||
)
|
||||
.await?;
|
||||
let webrtcsink = BaseWebRTCSink::with_signaller(Signallable::from(signaller.clone()));
|
||||
webrtcsink.set_property_from_str("stun-server", "stun://stun.l.google.com:19302");
|
||||
webrtcsink.set_property_from_str("congestion-control", "disabled");
|
||||
webrtcsink.set_property("do-retransmission", false);
|
||||
|
||||
/* Queues */
|
||||
let video_queue = gstreamer::ElementFactory::make("queue2")
|
||||
.property("max-size-buffers", 3u32)
|
||||
.property("max-size-time", 0u64)
|
||||
.property("max-size-bytes", 0u32)
|
||||
let video_source_queue = gstreamer::ElementFactory::make("queue")
|
||||
.property("max-size-buffers", 5u32)
|
||||
.build()?;
|
||||
|
||||
let audio_queue = gstreamer::ElementFactory::make("queue2")
|
||||
.property("max-size-buffers", 3u32)
|
||||
.property("max-size-time", 0u64)
|
||||
.property("max-size-bytes", 0u32)
|
||||
let audio_source_queue = gstreamer::ElementFactory::make("queue")
|
||||
.property("max-size-buffers", 5u32)
|
||||
.build()?;
|
||||
|
||||
let video_queue = gstreamer::ElementFactory::make("queue")
|
||||
.property("max-size-buffers", 5u32)
|
||||
.build()?;
|
||||
|
||||
let audio_queue = gstreamer::ElementFactory::make("queue")
|
||||
.property("max-size-buffers", 5u32)
|
||||
.build()?;
|
||||
|
||||
/* Clock Sync */
|
||||
@@ -433,6 +456,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
&caps_filter,
|
||||
&video_queue,
|
||||
&video_clocksync,
|
||||
&video_source_queue,
|
||||
&video_source,
|
||||
&audio_encoder,
|
||||
&audio_capsfilter,
|
||||
@@ -440,6 +464,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
&audio_clocksync,
|
||||
&audio_rate,
|
||||
&audio_converter,
|
||||
&audio_source_queue,
|
||||
&audio_source,
|
||||
])?;
|
||||
|
||||
@@ -455,24 +480,18 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
pipeline.add(parser)?;
|
||||
}
|
||||
|
||||
// If DMA-BUF..
|
||||
if args.app.dma_buf {
|
||||
// If zero-copy..
|
||||
if args.app.zero_copy {
|
||||
// VA-API / QSV pipeline
|
||||
if let (Some(vapostproc), Some(va_caps_filter)) = (&vapostproc, &va_caps_filter) {
|
||||
pipeline.add_many(&[vapostproc, va_caps_filter])?;
|
||||
} else {
|
||||
// NVENC pipeline
|
||||
if let (Some(glupload), Some(glconvert), Some(gl_caps_filter), Some(cudaupload)) =
|
||||
(&glupload, &glconvert, &gl_caps_filter, &cudaupload)
|
||||
{
|
||||
pipeline.add_many(&[glupload, glconvert, gl_caps_filter, cudaupload])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Link main audio branch
|
||||
gstreamer::Element::link_many(&[
|
||||
&audio_source,
|
||||
&audio_source_queue,
|
||||
&audio_converter,
|
||||
&audio_rate,
|
||||
&audio_capsfilter,
|
||||
@@ -488,12 +507,13 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
gstreamer::Element::link_many(&[&audio_encoder, webrtcsink.upcast_ref()])?;
|
||||
}
|
||||
|
||||
// With DMA-BUF..
|
||||
if args.app.dma_buf {
|
||||
// With zero-copy..
|
||||
if args.app.zero_copy {
|
||||
// VA-API / QSV pipeline
|
||||
if let (Some(vapostproc), Some(va_caps_filter)) = (&vapostproc, &va_caps_filter) {
|
||||
gstreamer::Element::link_many(&[
|
||||
&video_source,
|
||||
&video_source_queue,
|
||||
&caps_filter,
|
||||
&video_queue,
|
||||
&video_clocksync,
|
||||
@@ -501,27 +521,19 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
&va_caps_filter,
|
||||
&video_encoder,
|
||||
])?;
|
||||
} else {
|
||||
} else if video_encoder_info.encoder_api == EncoderAPI::NVENC {
|
||||
// NVENC pipeline
|
||||
if let (Some(glupload), Some(glconvert), Some(gl_caps_filter), Some(cudaupload)) =
|
||||
(&glupload, &glconvert, &gl_caps_filter, &cudaupload)
|
||||
{
|
||||
gstreamer::Element::link_many(&[
|
||||
&video_source,
|
||||
&caps_filter,
|
||||
&video_queue,
|
||||
&video_clocksync,
|
||||
&glupload,
|
||||
&glconvert,
|
||||
&gl_caps_filter,
|
||||
&cudaupload,
|
||||
&video_encoder,
|
||||
])?;
|
||||
}
|
||||
gstreamer::Element::link_many(&[
|
||||
&video_source,
|
||||
&video_source_queue,
|
||||
&caps_filter,
|
||||
&video_encoder,
|
||||
])?;
|
||||
}
|
||||
} else {
|
||||
gstreamer::Element::link_many(&[
|
||||
&video_source,
|
||||
&video_source_queue,
|
||||
&caps_filter,
|
||||
&video_queue,
|
||||
&video_clocksync,
|
||||
@@ -537,8 +549,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
gstreamer::Element::link_many(&[&video_encoder, webrtcsink.upcast_ref()])?;
|
||||
}
|
||||
|
||||
// Set QOS
|
||||
video_encoder.set_property("qos", true);
|
||||
// Make sure QOS is disabled to avoid latency
|
||||
video_encoder.set_property("qos", false);
|
||||
|
||||
// Optimize latency of pipeline
|
||||
video_source
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::input::controller::ControllerManager;
|
||||
use crate::messages::{MessageBase, MessageICE, MessageRaw, MessageSDP};
|
||||
use crate::p2p::p2p::NestriConnection;
|
||||
use crate::p2p::p2p_protocol_stream::NestriStreamProtocol;
|
||||
@@ -5,7 +6,7 @@ use crate::proto::proto::proto_input::InputType::{
|
||||
KeyDown, KeyUp, MouseKeyDown, MouseKeyUp, MouseMove, MouseMoveAbs, MouseWheel,
|
||||
};
|
||||
use crate::proto::proto::{ProtoInput, ProtoMessageInput};
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
use anyhow::Result;
|
||||
use glib::subclass::prelude::*;
|
||||
use gstreamer::glib;
|
||||
use gstreamer::prelude::*;
|
||||
@@ -14,6 +15,7 @@ use gstrswebrtc::signaller::{Signallable, SignallableImpl};
|
||||
use parking_lot::RwLock as PLRwLock;
|
||||
use prost::Message;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use tokio::sync::{Mutex, mpsc};
|
||||
use webrtc::ice_transport::ice_candidate::RTCIceCandidateInit;
|
||||
use webrtc::peer_connection::sdp::session_description::RTCSessionDescription;
|
||||
|
||||
@@ -21,7 +23,9 @@ pub struct Signaller {
|
||||
stream_room: PLRwLock<Option<String>>,
|
||||
stream_protocol: PLRwLock<Option<Arc<NestriStreamProtocol>>>,
|
||||
wayland_src: PLRwLock<Option<Arc<gstreamer::Element>>>,
|
||||
data_channel: AtomicRefCell<Option<gstreamer_webrtc::WebRTCDataChannel>>,
|
||||
data_channel: PLRwLock<Option<Arc<gstreamer_webrtc::WebRTCDataChannel>>>,
|
||||
controller_manager: PLRwLock<Option<Arc<ControllerManager>>>,
|
||||
rumble_rx: Mutex<Option<mpsc::Receiver<(u32, u16, u16, u16)>>>,
|
||||
}
|
||||
impl Default for Signaller {
|
||||
fn default() -> Self {
|
||||
@@ -29,15 +33,14 @@ impl Default for Signaller {
|
||||
stream_room: PLRwLock::new(None),
|
||||
stream_protocol: PLRwLock::new(None),
|
||||
wayland_src: PLRwLock::new(None),
|
||||
data_channel: AtomicRefCell::new(None),
|
||||
data_channel: PLRwLock::new(None),
|
||||
controller_manager: PLRwLock::new(None),
|
||||
rumble_rx: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Signaller {
|
||||
pub async fn set_nestri_connection(
|
||||
&self,
|
||||
nestri_conn: NestriConnection,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub async fn set_nestri_connection(&self, nestri_conn: NestriConnection) -> Result<()> {
|
||||
let stream_protocol = NestriStreamProtocol::new(nestri_conn).await?;
|
||||
*self.stream_protocol.write() = Some(Arc::new(stream_protocol));
|
||||
Ok(())
|
||||
@@ -59,14 +62,29 @@ impl Signaller {
|
||||
self.wayland_src.read().clone()
|
||||
}
|
||||
|
||||
pub fn set_controller_manager(&self, controller_manager: Arc<ControllerManager>) {
|
||||
*self.controller_manager.write() = Some(controller_manager);
|
||||
}
|
||||
|
||||
pub fn get_controller_manager(&self) -> Option<Arc<ControllerManager>> {
|
||||
self.controller_manager.read().clone()
|
||||
}
|
||||
|
||||
pub async fn set_rumble_rx(&self, rumble_rx: mpsc::Receiver<(u32, u16, u16, u16)>) {
|
||||
*self.rumble_rx.lock().await = Some(rumble_rx);
|
||||
}
|
||||
|
||||
// Change getter to take ownership:
|
||||
pub async fn take_rumble_rx(&self) -> Option<mpsc::Receiver<(u32, u16, u16, u16)>> {
|
||||
self.rumble_rx.lock().await.take()
|
||||
}
|
||||
|
||||
pub fn set_data_channel(&self, data_channel: gstreamer_webrtc::WebRTCDataChannel) {
|
||||
match self.data_channel.try_borrow_mut() {
|
||||
Ok(mut dc) => *dc = Some(data_channel),
|
||||
Err(_) => gstreamer::warning!(
|
||||
gstreamer::CAT_DEFAULT,
|
||||
"Failed to set data channel - already borrowed"
|
||||
),
|
||||
}
|
||||
*self.data_channel.write() = Some(Arc::new(data_channel));
|
||||
}
|
||||
|
||||
pub fn get_data_channel(&self) -> Option<Arc<gstreamer_webrtc::WebRTCDataChannel>> {
|
||||
self.data_channel.read().clone()
|
||||
}
|
||||
|
||||
/// Helper method to clean things up
|
||||
@@ -79,15 +97,15 @@ impl Signaller {
|
||||
let self_obj = self.obj().clone();
|
||||
stream_protocol.register_callback("answer", move |data| {
|
||||
if let Ok(message) = serde_json::from_slice::<MessageSDP>(&data) {
|
||||
let sdp =
|
||||
gst_sdp::SDPMessage::parse_buffer(message.sdp.sdp.as_bytes()).unwrap();
|
||||
let sdp = gst_sdp::SDPMessage::parse_buffer(message.sdp.sdp.as_bytes())
|
||||
.map_err(|e| anyhow::anyhow!("Invalid SDP in 'answer': {e:?}"))?;
|
||||
let answer = WebRTCSessionDescription::new(WebRTCSDPType::Answer, sdp);
|
||||
self_obj.emit_by_name::<()>(
|
||||
Ok(self_obj.emit_by_name::<()>(
|
||||
"session-description",
|
||||
&[&"unique-session-id", &answer],
|
||||
);
|
||||
))
|
||||
} else {
|
||||
gstreamer::error!(gstreamer::CAT_DEFAULT, "Failed to decode SDP message");
|
||||
anyhow::bail!("Failed to decode SDP message");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -98,7 +116,7 @@ impl Signaller {
|
||||
let candidate = message.candidate;
|
||||
let sdp_m_line_index = candidate.sdp_mline_index.unwrap_or(0) as u32;
|
||||
let sdp_mid = candidate.sdp_mid;
|
||||
self_obj.emit_by_name::<()>(
|
||||
Ok(self_obj.emit_by_name::<()>(
|
||||
"handle-ice",
|
||||
&[
|
||||
&"unique-session-id",
|
||||
@@ -106,9 +124,9 @@ impl Signaller {
|
||||
&sdp_mid,
|
||||
&candidate.candidate,
|
||||
],
|
||||
);
|
||||
))
|
||||
} else {
|
||||
gstreamer::error!(gstreamer::CAT_DEFAULT, "Failed to decode ICE message");
|
||||
anyhow::bail!("Failed to decode ICE message");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -131,16 +149,16 @@ impl Signaller {
|
||||
}
|
||||
|
||||
// Send our SDP offer
|
||||
self_obj.emit_by_name::<()>(
|
||||
Ok(self_obj.emit_by_name::<()>(
|
||||
"session-requested",
|
||||
&[
|
||||
&"unique-session-id",
|
||||
&"consumer-identifier",
|
||||
&None::<WebRTCSessionDescription>,
|
||||
],
|
||||
);
|
||||
))
|
||||
} else {
|
||||
gstreamer::error!(gstreamer::CAT_DEFAULT, "Failed to decode answer");
|
||||
anyhow::bail!("Failed to decode answer");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -173,8 +191,25 @@ impl Signaller {
|
||||
if let Some(data_channel) = data_channel {
|
||||
gstreamer::info!(gstreamer::CAT_DEFAULT, "Data channel created");
|
||||
if let Some(wayland_src) = signaller.imp().get_wayland_src() {
|
||||
setup_data_channel(&data_channel, &*wayland_src);
|
||||
signaller.imp().set_data_channel(data_channel);
|
||||
signaller.imp().set_data_channel(data_channel.clone());
|
||||
|
||||
let signaller = signaller.clone();
|
||||
let data_channel = Arc::new(data_channel);
|
||||
let wayland_src = wayland_src.clone();
|
||||
|
||||
// Spawn async task to take the receiver and set up
|
||||
tokio::spawn(async move {
|
||||
let rumble_rx = signaller.imp().take_rumble_rx().await;
|
||||
let controller_manager =
|
||||
signaller.imp().get_controller_manager();
|
||||
|
||||
setup_data_channel(
|
||||
controller_manager,
|
||||
rumble_rx,
|
||||
data_channel,
|
||||
&wayland_src,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
gstreamer::error!(
|
||||
gstreamer::CAT_DEFAULT,
|
||||
@@ -315,31 +350,83 @@ impl ObjectImpl for Signaller {
|
||||
}
|
||||
|
||||
fn setup_data_channel(
|
||||
data_channel: &gstreamer_webrtc::WebRTCDataChannel,
|
||||
controller_manager: Option<Arc<ControllerManager>>,
|
||||
rumble_rx: Option<mpsc::Receiver<(u32, u16, u16, u16)>>, // (slot, strong, weak, duration_ms)
|
||||
data_channel: Arc<gstreamer_webrtc::WebRTCDataChannel>,
|
||||
wayland_src: &gstreamer::Element,
|
||||
) {
|
||||
let wayland_src = wayland_src.clone();
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<Vec<u8>>();
|
||||
|
||||
data_channel.connect_on_message_data(move |_data_channel, data| {
|
||||
if let Some(data) = data {
|
||||
match ProtoMessageInput::decode(data.to_vec().as_slice()) {
|
||||
// Spawn async processor
|
||||
tokio::spawn(async move {
|
||||
while let Some(data) = rx.recv().await {
|
||||
match ProtoMessageInput::decode(data.as_slice()) {
|
||||
Ok(message_input) => {
|
||||
if let Some(input_msg) = message_input.data {
|
||||
// Process the input message and create an event
|
||||
if let Some(event) = handle_input_message(input_msg) {
|
||||
// Send the event to wayland source, result bool is ignored
|
||||
let _ = wayland_src.send_event(event);
|
||||
if let Some(message_base) = message_input.message_base {
|
||||
if message_base.payload_type == "input" {
|
||||
if let Some(input_data) = message_input.data {
|
||||
if let Some(event) = handle_input_message(input_data) {
|
||||
// Send the event to wayland source, result bool is ignored
|
||||
let _ = wayland_src.send_event(event);
|
||||
}
|
||||
}
|
||||
} else if message_base.payload_type == "controllerInput" {
|
||||
if let Some(controller_manager) = &controller_manager {
|
||||
if let Some(input_data) = message_input.data {
|
||||
let _ = controller_manager.send_command(input_data).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::error!("Failed to parse InputMessage");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to decode MessageInput: {:?}", e);
|
||||
tracing::error!("Failed to decode input message: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Spawn rumble sender
|
||||
if let Some(mut rumble_rx) = rumble_rx {
|
||||
let data_channel_clone = data_channel.clone();
|
||||
tokio::spawn(async move {
|
||||
while let Some((slot, strong, weak, duration_ms)) = rumble_rx.recv().await {
|
||||
let rumble_msg = ProtoMessageInput {
|
||||
message_base: Some(crate::proto::proto::ProtoMessageBase {
|
||||
payload_type: "controllerInput".to_string(),
|
||||
latency: None,
|
||||
}),
|
||||
data: Some(ProtoInput {
|
||||
input_type: Some(
|
||||
crate::proto::proto::proto_input::InputType::ControllerRumble(
|
||||
crate::proto::proto::ProtoControllerRumble {
|
||||
r#type: "ControllerRumble".to_string(),
|
||||
slot: slot as i32,
|
||||
low_frequency: weak as i32,
|
||||
high_frequency: strong as i32,
|
||||
duration: duration_ms as i32,
|
||||
},
|
||||
),
|
||||
),
|
||||
}),
|
||||
};
|
||||
|
||||
let data = rumble_msg.encode_to_vec();
|
||||
let bytes = glib::Bytes::from_owned(data);
|
||||
|
||||
if let Err(e) = data_channel_clone.send_data_full(Some(&bytes)) {
|
||||
tracing::warn!("Failed to send rumble data: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
data_channel.connect_on_message_data(move |_data_channel, data| {
|
||||
if let Some(data) = data {
|
||||
let _ = tx.send(data.to_vec());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_input_message(input_msg: ProtoInput) -> Option<gstreamer::Event> {
|
||||
@@ -401,6 +488,7 @@ fn handle_input_message(input_msg: ProtoInput) -> Option<gstreamer::Event> {
|
||||
|
||||
Some(gstreamer::event::CustomUpstream::new(structure))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::input::controller::ControllerManager;
|
||||
use crate::p2p::p2p::NestriConnection;
|
||||
use gstreamer::glib;
|
||||
use gstreamer::subclass::prelude::*;
|
||||
use gstrswebrtc::signaller::Signallable;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
mod imp;
|
||||
|
||||
@@ -15,11 +17,19 @@ impl NestriSignaller {
|
||||
room: String,
|
||||
nestri_conn: NestriConnection,
|
||||
wayland_src: Arc<gstreamer::Element>,
|
||||
controller_manager: Option<Arc<ControllerManager>>,
|
||||
rumble_rx: Option<mpsc::Receiver<(u32, u16, u16, u16)>>,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let obj: Self = glib::Object::new();
|
||||
obj.imp().set_stream_room(room);
|
||||
obj.imp().set_nestri_connection(nestri_conn).await?;
|
||||
obj.imp().set_wayland_src(wayland_src);
|
||||
if let Some(controller_manager) = controller_manager {
|
||||
obj.imp().set_controller_manager(controller_manager);
|
||||
}
|
||||
if let Some(rumble_rx) = rumble_rx {
|
||||
obj.imp().set_rumble_rx(rumble_rx).await;
|
||||
}
|
||||
Ok(obj)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use libp2p::futures::StreamExt;
|
||||
use libp2p::multiaddr::Protocol;
|
||||
use libp2p::{
|
||||
@@ -11,7 +12,6 @@ use libp2p_ping as ping;
|
||||
use libp2p_stream as stream;
|
||||
use libp2p_tcp as tcp;
|
||||
use libp2p_yamux as yamux;
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
@@ -46,7 +46,7 @@ pub struct NestriP2P {
|
||||
swarm: Arc<Mutex<Swarm<NestriBehaviour>>>,
|
||||
}
|
||||
impl NestriP2P {
|
||||
pub async fn new() -> Result<Self, Box<dyn Error>> {
|
||||
pub async fn new() -> Result<Self> {
|
||||
let swarm = Arc::new(Mutex::new(
|
||||
libp2p::SwarmBuilder::with_new_identity()
|
||||
.with_tokio()
|
||||
@@ -69,14 +69,16 @@ impl NestriP2P {
|
||||
Ok(NestriP2P { swarm })
|
||||
}
|
||||
|
||||
pub async fn connect(&self, conn_url: &str) -> Result<NestriConnection, Box<dyn Error>> {
|
||||
pub async fn connect(&self, conn_url: &str) -> Result<NestriConnection> {
|
||||
let conn_addr: Multiaddr = conn_url.parse()?;
|
||||
|
||||
let mut swarm_lock = self.swarm.lock().await;
|
||||
swarm_lock.dial(conn_addr.clone())?;
|
||||
|
||||
let Some(Protocol::P2p(peer_id)) = conn_addr.clone().iter().last() else {
|
||||
return Err("Invalid connection URL: missing peer ID".into());
|
||||
return Err(anyhow::Error::msg(
|
||||
"Invalid multiaddr: missing /p2p/<peer_id>",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(NestriConnection {
|
||||
@@ -88,10 +90,7 @@ impl NestriP2P {
|
||||
|
||||
async fn swarm_loop(swarm: Arc<Mutex<Swarm<NestriBehaviour>>>) {
|
||||
loop {
|
||||
let event = {
|
||||
let mut swarm_lock = swarm.lock().await;
|
||||
swarm_lock.select_next_some().await
|
||||
};
|
||||
let event = swarm.lock().await.select_next_some().await;
|
||||
match event {
|
||||
/* Ping Events */
|
||||
SwarmEvent::Behaviour(NestriBehaviourEvent::Ping(ping::Event {
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
use crate::p2p::p2p::NestriConnection;
|
||||
use crate::p2p::p2p_safestream::SafeStream;
|
||||
use anyhow::Result;
|
||||
use dashmap::DashMap;
|
||||
use libp2p::StreamProtocol;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::{self, Duration};
|
||||
|
||||
// Cloneable callback type
|
||||
pub type CallbackInner = dyn Fn(Vec<u8>) + Send + Sync + 'static;
|
||||
pub type CallbackInner = dyn Fn(Vec<u8>) -> Result<()> + Send + Sync + 'static;
|
||||
pub struct Callback(Arc<CallbackInner>);
|
||||
impl Callback {
|
||||
pub fn new<F>(f: F) -> Self
|
||||
where
|
||||
F: Fn(Vec<u8>) + Send + Sync + 'static,
|
||||
F: Fn(Vec<u8>) -> Result<()> + Send + Sync + 'static,
|
||||
{
|
||||
Callback(Arc::new(f))
|
||||
}
|
||||
|
||||
pub fn call(&self, data: Vec<u8>) {
|
||||
pub fn call(&self, data: Vec<u8>) -> Result<()> {
|
||||
self.0(data)
|
||||
}
|
||||
}
|
||||
@@ -44,9 +44,7 @@ impl NestriStreamProtocol {
|
||||
const NESTRI_PROTOCOL_STREAM_PUSH: StreamProtocol =
|
||||
StreamProtocol::new("/nestri-relay/stream-push/1.0.0");
|
||||
|
||||
pub async fn new(
|
||||
nestri_connection: NestriConnection,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
pub async fn new(nestri_connection: NestriConnection) -> Result<Self> {
|
||||
let mut nestri_connection = nestri_connection.clone();
|
||||
let push_stream = match nestri_connection
|
||||
.control
|
||||
@@ -55,7 +53,10 @@ impl NestriStreamProtocol {
|
||||
{
|
||||
Ok(stream) => stream,
|
||||
Err(e) => {
|
||||
return Err(Box::new(e));
|
||||
return Err(anyhow::Error::msg(format!(
|
||||
"Failed to open push stream: {}",
|
||||
e
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -73,7 +74,7 @@ impl NestriStreamProtocol {
|
||||
Ok(sp)
|
||||
}
|
||||
|
||||
pub fn restart(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub fn restart(&mut self) -> Result<()> {
|
||||
// Return if tx and handles are already initialized
|
||||
if self.tx.is_some() && self.read_handle.is_some() && self.write_handle.is_some() {
|
||||
tracing::warn!("NestriStreamProtocol is already running, restart skipped");
|
||||
@@ -111,13 +112,9 @@ impl NestriStreamProtocol {
|
||||
// we just get the callback directly if it exists
|
||||
if let Some(callback) = callbacks.get(&response_type) {
|
||||
// Execute the callback
|
||||
if let Err(e) =
|
||||
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
callback.call(data.clone())
|
||||
}))
|
||||
{
|
||||
if let Err(e) = callback.call(data.clone()) {
|
||||
tracing::error!(
|
||||
"Callback for response type '{}' panicked: {:?}",
|
||||
"Callback for response type '{}' errored: {:?}",
|
||||
response_type,
|
||||
e
|
||||
);
|
||||
@@ -133,9 +130,6 @@ impl NestriStreamProtocol {
|
||||
tracing::error!("Failed to decode message: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a small sleep to reduce CPU usage
|
||||
time::sleep(Duration::from_micros(100)).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -156,27 +150,20 @@ impl NestriStreamProtocol {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a small sleep to reduce CPU usage
|
||||
time::sleep(Duration::from_micros(100)).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send_message<M: serde::Serialize>(
|
||||
&self,
|
||||
message: &M,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub fn send_message<M: serde::Serialize>(&self, message: &M) -> Result<()> {
|
||||
let json_data = serde_json::to_vec(message)?;
|
||||
let Some(tx) = &self.tx else {
|
||||
return Err(Box::new(std::io::Error::new(
|
||||
std::io::ErrorKind::NotConnected,
|
||||
return Err(anyhow::Error::msg(
|
||||
if self.read_handle.is_none() && self.write_handle.is_none() {
|
||||
"NestriStreamProtocol has been shutdown"
|
||||
} else {
|
||||
"NestriStreamProtocol is not properly initialized"
|
||||
},
|
||||
)));
|
||||
));
|
||||
};
|
||||
tx.try_send(json_data)?;
|
||||
Ok(())
|
||||
@@ -184,7 +171,7 @@ impl NestriStreamProtocol {
|
||||
|
||||
pub fn register_callback<F>(&self, response_type: &str, callback: F)
|
||||
where
|
||||
F: Fn(Vec<u8>) + Send + Sync + 'static,
|
||||
F: Fn(Vec<u8>) -> Result<()> + Send + Sync + 'static,
|
||||
{
|
||||
self.callbacks
|
||||
.insert(response_type.to_string(), Callback::new(callback));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use libp2p::futures::io::{ReadHalf, WriteHalf};
|
||||
use libp2p::futures::{AsyncReadExt, AsyncWriteExt};
|
||||
@@ -19,17 +20,17 @@ impl SafeStream {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_raw(&self, data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub async fn send_raw(&self, data: &[u8]) -> Result<()> {
|
||||
self.send_with_length_prefix(data).await
|
||||
}
|
||||
|
||||
pub async fn receive_raw(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
pub async fn receive_raw(&self) -> Result<Vec<u8>> {
|
||||
self.receive_with_length_prefix().await
|
||||
}
|
||||
|
||||
async fn send_with_length_prefix(&self, data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
async fn send_with_length_prefix(&self, data: &[u8]) -> Result<()> {
|
||||
if data.len() > MAX_SIZE {
|
||||
return Err("Data exceeds maximum size".into());
|
||||
anyhow::bail!("Data exceeds maximum size");
|
||||
}
|
||||
|
||||
let mut buffer = Vec::with_capacity(4 + data.len());
|
||||
@@ -42,7 +43,7 @@ impl SafeStream {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive_with_length_prefix(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
async fn receive_with_length_prefix(&self) -> Result<Vec<u8>> {
|
||||
let mut stream_read = self.stream_read.lock().await;
|
||||
|
||||
// Read length prefix + data in one syscall
|
||||
@@ -51,7 +52,7 @@ impl SafeStream {
|
||||
let length = BigEndian::read_u32(&length_prefix) as usize;
|
||||
|
||||
if length > MAX_SIZE {
|
||||
return Err("Data exceeds maximum size".into());
|
||||
anyhow::bail!("Received data exceeds maximum size");
|
||||
}
|
||||
|
||||
let mut buffer = vec![0u8; length];
|
||||
|
||||
@@ -3,29 +3,31 @@
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoTimestampEntry {
|
||||
#[prost(string, tag = "1")]
|
||||
#[prost(string, tag="1")]
|
||||
pub stage: ::prost::alloc::string::String,
|
||||
#[prost(message, optional, tag = "2")]
|
||||
#[prost(message, optional, tag="2")]
|
||||
pub time: ::core::option::Option<::prost_types::Timestamp>,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoLatencyTracker {
|
||||
#[prost(string, tag = "1")]
|
||||
#[prost(string, tag="1")]
|
||||
pub sequence_id: ::prost::alloc::string::String,
|
||||
#[prost(message, repeated, tag = "2")]
|
||||
#[prost(message, repeated, tag="2")]
|
||||
pub timestamps: ::prost::alloc::vec::Vec<ProtoTimestampEntry>,
|
||||
}
|
||||
// Mouse messages
|
||||
|
||||
/// MouseMove message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoMouseMove {
|
||||
/// Fixed value "MouseMove"
|
||||
#[prost(string, tag = "1")]
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
#[prost(int32, tag = "2")]
|
||||
#[prost(int32, tag="2")]
|
||||
pub x: i32,
|
||||
#[prost(int32, tag = "3")]
|
||||
#[prost(int32, tag="3")]
|
||||
pub y: i32,
|
||||
}
|
||||
/// MouseMoveAbs message
|
||||
@@ -33,11 +35,11 @@ pub struct ProtoMouseMove {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoMouseMoveAbs {
|
||||
/// Fixed value "MouseMoveAbs"
|
||||
#[prost(string, tag = "1")]
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
#[prost(int32, tag = "2")]
|
||||
#[prost(int32, tag="2")]
|
||||
pub x: i32,
|
||||
#[prost(int32, tag = "3")]
|
||||
#[prost(int32, tag="3")]
|
||||
pub y: i32,
|
||||
}
|
||||
/// MouseWheel message
|
||||
@@ -45,11 +47,11 @@ pub struct ProtoMouseMoveAbs {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoMouseWheel {
|
||||
/// Fixed value "MouseWheel"
|
||||
#[prost(string, tag = "1")]
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
#[prost(int32, tag = "2")]
|
||||
#[prost(int32, tag="2")]
|
||||
pub x: i32,
|
||||
#[prost(int32, tag = "3")]
|
||||
#[prost(int32, tag="3")]
|
||||
pub y: i32,
|
||||
}
|
||||
/// MouseKeyDown message
|
||||
@@ -57,9 +59,9 @@ pub struct ProtoMouseWheel {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoMouseKeyDown {
|
||||
/// Fixed value "MouseKeyDown"
|
||||
#[prost(string, tag = "1")]
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
#[prost(int32, tag = "2")]
|
||||
#[prost(int32, tag="2")]
|
||||
pub key: i32,
|
||||
}
|
||||
/// MouseKeyUp message
|
||||
@@ -67,19 +69,21 @@ pub struct ProtoMouseKeyDown {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoMouseKeyUp {
|
||||
/// Fixed value "MouseKeyUp"
|
||||
#[prost(string, tag = "1")]
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
#[prost(int32, tag = "2")]
|
||||
#[prost(int32, tag="2")]
|
||||
pub key: i32,
|
||||
}
|
||||
// Keyboard messages
|
||||
|
||||
/// KeyDown message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoKeyDown {
|
||||
/// Fixed value "KeyDown"
|
||||
#[prost(string, tag = "1")]
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
#[prost(int32, tag = "2")]
|
||||
#[prost(int32, tag="2")]
|
||||
pub key: i32,
|
||||
}
|
||||
/// KeyUp message
|
||||
@@ -87,53 +91,185 @@ pub struct ProtoKeyDown {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoKeyUp {
|
||||
/// Fixed value "KeyUp"
|
||||
#[prost(string, tag = "1")]
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
#[prost(int32, tag = "2")]
|
||||
#[prost(int32, tag="2")]
|
||||
pub key: i32,
|
||||
}
|
||||
// Controller messages
|
||||
|
||||
/// ControllerAttach message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoControllerAttach {
|
||||
/// Fixed value "ControllerAttach"
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
/// One of the following enums: "ps", "xbox" or "switch"
|
||||
#[prost(string, tag="2")]
|
||||
pub id: ::prost::alloc::string::String,
|
||||
/// Slot number (0-3)
|
||||
#[prost(int32, tag="3")]
|
||||
pub slot: i32,
|
||||
}
|
||||
/// ControllerDetach message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoControllerDetach {
|
||||
/// Fixed value "ControllerDetach"
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
/// Slot number (0-3)
|
||||
#[prost(int32, tag="2")]
|
||||
pub slot: i32,
|
||||
}
|
||||
/// ControllerButton message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoControllerButton {
|
||||
/// Fixed value "ControllerButtons"
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
/// Slot number (0-3)
|
||||
#[prost(int32, tag="2")]
|
||||
pub slot: i32,
|
||||
/// Button code (linux input event code)
|
||||
#[prost(int32, tag="3")]
|
||||
pub button: i32,
|
||||
/// true if pressed, false if released
|
||||
#[prost(bool, tag="4")]
|
||||
pub pressed: bool,
|
||||
}
|
||||
/// ControllerTriggers message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoControllerTrigger {
|
||||
/// Fixed value "ControllerTriggers"
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
/// Slot number (0-3)
|
||||
#[prost(int32, tag="2")]
|
||||
pub slot: i32,
|
||||
/// Trigger number (0 for left, 1 for right)
|
||||
#[prost(int32, tag="3")]
|
||||
pub trigger: i32,
|
||||
/// trigger value (-32768 to 32767)
|
||||
#[prost(int32, tag="4")]
|
||||
pub value: i32,
|
||||
}
|
||||
/// ControllerSticks message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoControllerStick {
|
||||
/// Fixed value "ControllerStick"
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
/// Slot number (0-3)
|
||||
#[prost(int32, tag="2")]
|
||||
pub slot: i32,
|
||||
/// Stick number (0 for left, 1 for right)
|
||||
#[prost(int32, tag="3")]
|
||||
pub stick: i32,
|
||||
/// X axis value (-32768 to 32767)
|
||||
#[prost(int32, tag="4")]
|
||||
pub x: i32,
|
||||
/// Y axis value (-32768 to 32767)
|
||||
#[prost(int32, tag="5")]
|
||||
pub y: i32,
|
||||
}
|
||||
/// ControllerAxis message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoControllerAxis {
|
||||
/// Fixed value "ControllerAxis"
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
/// Slot number (0-3)
|
||||
#[prost(int32, tag="2")]
|
||||
pub slot: i32,
|
||||
/// Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
||||
#[prost(int32, tag="3")]
|
||||
pub axis: i32,
|
||||
/// axis value (-1 to 1)
|
||||
#[prost(int32, tag="4")]
|
||||
pub value: i32,
|
||||
}
|
||||
/// ControllerRumble message
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoControllerRumble {
|
||||
/// Fixed value "ControllerRumble"
|
||||
#[prost(string, tag="1")]
|
||||
pub r#type: ::prost::alloc::string::String,
|
||||
/// Slot number (0-3)
|
||||
#[prost(int32, tag="2")]
|
||||
pub slot: i32,
|
||||
/// Low frequency rumble (0-65535)
|
||||
#[prost(int32, tag="3")]
|
||||
pub low_frequency: i32,
|
||||
/// High frequency rumble (0-65535)
|
||||
#[prost(int32, tag="4")]
|
||||
pub high_frequency: i32,
|
||||
/// Duration in milliseconds
|
||||
#[prost(int32, tag="5")]
|
||||
pub duration: i32,
|
||||
}
|
||||
/// Union of all Input types
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoInput {
|
||||
#[prost(oneof = "proto_input::InputType", tags = "1, 2, 3, 4, 5, 6, 7")]
|
||||
#[prost(oneof="proto_input::InputType", tags="1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14")]
|
||||
pub input_type: ::core::option::Option<proto_input::InputType>,
|
||||
}
|
||||
/// Nested message and enum types in `ProtoInput`.
|
||||
pub mod proto_input {
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Oneof)]
|
||||
#[derive(Clone, PartialEq, ::prost::Oneof)]
|
||||
pub enum InputType {
|
||||
#[prost(message, tag = "1")]
|
||||
#[prost(message, tag="1")]
|
||||
MouseMove(super::ProtoMouseMove),
|
||||
#[prost(message, tag = "2")]
|
||||
#[prost(message, tag="2")]
|
||||
MouseMoveAbs(super::ProtoMouseMoveAbs),
|
||||
#[prost(message, tag = "3")]
|
||||
#[prost(message, tag="3")]
|
||||
MouseWheel(super::ProtoMouseWheel),
|
||||
#[prost(message, tag = "4")]
|
||||
#[prost(message, tag="4")]
|
||||
MouseKeyDown(super::ProtoMouseKeyDown),
|
||||
#[prost(message, tag = "5")]
|
||||
#[prost(message, tag="5")]
|
||||
MouseKeyUp(super::ProtoMouseKeyUp),
|
||||
#[prost(message, tag = "6")]
|
||||
#[prost(message, tag="6")]
|
||||
KeyDown(super::ProtoKeyDown),
|
||||
#[prost(message, tag = "7")]
|
||||
#[prost(message, tag="7")]
|
||||
KeyUp(super::ProtoKeyUp),
|
||||
#[prost(message, tag="8")]
|
||||
ControllerAttach(super::ProtoControllerAttach),
|
||||
#[prost(message, tag="9")]
|
||||
ControllerDetach(super::ProtoControllerDetach),
|
||||
#[prost(message, tag="10")]
|
||||
ControllerButton(super::ProtoControllerButton),
|
||||
#[prost(message, tag="11")]
|
||||
ControllerTrigger(super::ProtoControllerTrigger),
|
||||
#[prost(message, tag="12")]
|
||||
ControllerStick(super::ProtoControllerStick),
|
||||
#[prost(message, tag="13")]
|
||||
ControllerAxis(super::ProtoControllerAxis),
|
||||
#[prost(message, tag="14")]
|
||||
ControllerRumble(super::ProtoControllerRumble),
|
||||
}
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoMessageBase {
|
||||
#[prost(string, tag = "1")]
|
||||
#[prost(string, tag="1")]
|
||||
pub payload_type: ::prost::alloc::string::String,
|
||||
#[prost(message, optional, tag = "2")]
|
||||
#[prost(message, optional, tag="2")]
|
||||
pub latency: ::core::option::Option<ProtoLatencyTracker>,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtoMessageInput {
|
||||
#[prost(message, optional, tag = "1")]
|
||||
#[prost(message, optional, tag="1")]
|
||||
pub message_base: ::core::option::Option<ProtoMessageBase>,
|
||||
#[prost(message, optional, tag = "2")]
|
||||
#[prost(message, optional, tag="2")]
|
||||
pub data: ::core::option::Option<ProtoInput>,
|
||||
}
|
||||
// @@protoc_insertion_point(module)
|
||||
|
||||
@@ -4,6 +4,8 @@ option go_package = "relay/internal/proto";
|
||||
|
||||
package proto;
|
||||
|
||||
/* Mouse messages */
|
||||
|
||||
// MouseMove message
|
||||
message ProtoMouseMove {
|
||||
string type = 1; // Fixed value "MouseMove"
|
||||
@@ -37,6 +39,8 @@ message ProtoMouseKeyUp {
|
||||
int32 key = 2;
|
||||
}
|
||||
|
||||
/* Keyboard messages */
|
||||
|
||||
// KeyDown message
|
||||
message ProtoKeyDown {
|
||||
string type = 1; // Fixed value "KeyDown"
|
||||
@@ -49,6 +53,63 @@ message ProtoKeyUp {
|
||||
int32 key = 2;
|
||||
}
|
||||
|
||||
/* Controller messages */
|
||||
|
||||
// ControllerAttach message
|
||||
message ProtoControllerAttach {
|
||||
string type = 1; // Fixed value "ControllerAttach"
|
||||
string id = 2; // One of the following enums: "ps", "xbox" or "switch"
|
||||
int32 slot = 3; // Slot number (0-3)
|
||||
}
|
||||
|
||||
// ControllerDetach message
|
||||
message ProtoControllerDetach {
|
||||
string type = 1; // Fixed value "ControllerDetach"
|
||||
int32 slot = 2; // Slot number (0-3)
|
||||
}
|
||||
|
||||
// ControllerButton message
|
||||
message ProtoControllerButton {
|
||||
string type = 1; // Fixed value "ControllerButtons"
|
||||
int32 slot = 2; // Slot number (0-3)
|
||||
int32 button = 3; // Button code (linux input event code)
|
||||
bool pressed = 4; // true if pressed, false if released
|
||||
}
|
||||
|
||||
// ControllerTriggers message
|
||||
message ProtoControllerTrigger {
|
||||
string type = 1; // Fixed value "ControllerTriggers"
|
||||
int32 slot = 2; // Slot number (0-3)
|
||||
int32 trigger = 3; // Trigger number (0 for left, 1 for right)
|
||||
int32 value = 4; // trigger value (-32768 to 32767)
|
||||
}
|
||||
|
||||
// ControllerSticks message
|
||||
message ProtoControllerStick {
|
||||
string type = 1; // Fixed value "ControllerStick"
|
||||
int32 slot = 2; // Slot number (0-3)
|
||||
int32 stick = 3; // Stick number (0 for left, 1 for right)
|
||||
int32 x = 4; // X axis value (-32768 to 32767)
|
||||
int32 y = 5; // Y axis value (-32768 to 32767)
|
||||
}
|
||||
|
||||
// ControllerAxis message
|
||||
message ProtoControllerAxis {
|
||||
string type = 1; // Fixed value "ControllerAxis"
|
||||
int32 slot = 2; // Slot number (0-3)
|
||||
int32 axis = 3; // Axis number (0 for d-pad horizontal, 1 for d-pad vertical)
|
||||
int32 value = 4; // axis value (-1 to 1)
|
||||
}
|
||||
|
||||
// ControllerRumble message
|
||||
message ProtoControllerRumble {
|
||||
string type = 1; // Fixed value "ControllerRumble"
|
||||
int32 slot = 2; // Slot number (0-3)
|
||||
int32 low_frequency = 3; // Low frequency rumble (0-65535)
|
||||
int32 high_frequency = 4; // High frequency rumble (0-65535)
|
||||
int32 duration = 5; // Duration in milliseconds
|
||||
}
|
||||
|
||||
// Union of all Input types
|
||||
message ProtoInput {
|
||||
oneof input_type {
|
||||
@@ -59,5 +120,12 @@ message ProtoInput {
|
||||
ProtoMouseKeyUp mouse_key_up = 5;
|
||||
ProtoKeyDown key_down = 6;
|
||||
ProtoKeyUp key_up = 7;
|
||||
ProtoControllerAttach controller_attach = 8;
|
||||
ProtoControllerDetach controller_detach = 9;
|
||||
ProtoControllerButton controller_button = 10;
|
||||
ProtoControllerTrigger controller_trigger = 11;
|
||||
ProtoControllerStick controller_stick = 12;
|
||||
ProtoControllerAxis controller_axis = 13;
|
||||
ProtoControllerRumble controller_rumble = 14;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user