Compare commits
1 Commits
main
...
accessorie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
472f6d8fc0 |
10
.envrc
@@ -1,9 +1 @@
|
||||
if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then
|
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs="
|
||||
fi
|
||||
|
||||
nix_direnv_watch_file package.json
|
||||
if ! use flake . --impure
|
||||
then
|
||||
echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2
|
||||
fi
|
||||
use flake
|
||||
|
||||
5
.gitattributes
vendored
@@ -1,5 +0,0 @@
|
||||
* text=auto
|
||||
|
||||
*.png binary
|
||||
*.webp binary
|
||||
*.gif binary
|
||||
67
.github/CODEOWNERS
vendored
@@ -1,37 +1,30 @@
|
||||
# Global code owner
|
||||
* @Eirenliel
|
||||
|
||||
# Make everyone be able to approve SolarXR submodule changes
|
||||
/solarxr-protocol @ButterscotchV @Erimelowo @loucass003
|
||||
|
||||
# Make Loucass the owner of all GUI stuff
|
||||
/gui/ @loucass003
|
||||
/pnpm-lock.yaml @loucass003
|
||||
/pnpm-workspace.yaml @loucass003
|
||||
|
||||
# loucass003 and Erimel responsible for i18n
|
||||
/gui/public/i18n/ @loucass003 @Erimelowo @ImSapphire
|
||||
/gui/src/i18n/ @loucass003 @Erimelowo
|
||||
/l10n.toml @loucass003 @Erimelowo
|
||||
|
||||
/gui/src/components/settings/ @Erimelowo @loucass003
|
||||
|
||||
# Rust part of the GUI
|
||||
/gui/electron/ @loucass003
|
||||
|
||||
# Some server code~
|
||||
/server/ @ButterscotchV @Eirenliel @Erimelowo
|
||||
|
||||
/server/src/main/java/dev/slimevr/autobone/ @ButterscotchV
|
||||
/server/src/main/java/dev/slimevr/poserecorder/ @ButterscotchV
|
||||
/server/src/main/java/dev/slimevr/posestreamer/ @ButterscotchV
|
||||
|
||||
/server/src/main/java/dev/slimevr/osc/ @Erimelowo
|
||||
/server/src/main/java/dev/slimevr/tracking/processor/ @Erimelowo
|
||||
/server/src/main/java/dev/slimevr/filtering/ @Erimelowo
|
||||
|
||||
# Linux files
|
||||
*.nix @loucass003
|
||||
/flake.lock @loucass003
|
||||
/dev.slimevr.SlimeVR.metainfo.xml @loucass003
|
||||
/.envrc @loucass003
|
||||
# Global code owner
|
||||
* @Eirenliel
|
||||
|
||||
# Make Loucas and Uriel the owners of all GUI stuff
|
||||
/gui/ @loucass003 @ImUrX
|
||||
/package-lock.json @loucass003 @ImUrX
|
||||
|
||||
# Uriel and Erimel responsible for i18n
|
||||
/gui/public/i18n/ @ImUrX @Louka3000
|
||||
/gui/src/i18n/ @ImUrX @Louka3000
|
||||
/l10n.toml @ImUrX @Louka3000
|
||||
|
||||
/gui/src/components/settings/ @Louka3000 @loucass003
|
||||
|
||||
# Rust part of the GUI
|
||||
/gui/src-tauri/ @ImUrX @TheButlah
|
||||
/Cargo.lock @ImUrX @TheButlah
|
||||
|
||||
# Some server code~
|
||||
/server/ @ButterscotchV @Eirenliel @Louka3000
|
||||
|
||||
/server/src/main/java/dev/slimevr/autobone/ @ButterscotchV
|
||||
/server/src/main/java/dev/slimevr/poserecorder/ @ButterscotchV
|
||||
/server/src/main/java/dev/slimevr/posestreamer/ @ButterscotchV
|
||||
|
||||
/server/src/main/java/dev/slimevr/osc/ @Louka3000
|
||||
/server/src/main/java/dev/slimevr/tracking/processor/ @Louka3000
|
||||
/server/src/main/java/dev/slimevr/filtering/ @Louka3000
|
||||
|
||||
server/src/main/java/dev/slimevr/config/ @loucass003
|
||||
|
||||
3
.github/FUNDING.yml
vendored
@@ -1,3 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: SlimeVR
|
||||
8
.github/dependabot.yml
vendored
@@ -1,8 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
# Check for updates to GitHub Actions every week
|
||||
interval: "weekly"
|
||||
37
.github/labeler.yml
vendored
@@ -1,37 +0,0 @@
|
||||
"Area: Continuous Integration":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ".github/**"
|
||||
"Area: Application Protocol":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ["solarxr-protocol"]
|
||||
"Area: AutoBone":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "server/core/src/main/java/dev/slimevr/autobone/**"
|
||||
"Area: Documentation":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "**/*.md"
|
||||
"Area: GUI":
|
||||
- all:
|
||||
- changed-files:
|
||||
- all-globs-to-any-file: ["gui/**/*", "!gui/public/i18n/**/*"]
|
||||
"Area: Hardware Protocol":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "server/core/src/main/java/dev/slimevr/tracking/trackers/udp/**"
|
||||
"Area: Server":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ["server/**", "*gradle*", "gradle/**"]
|
||||
"Area: Skeletal Model":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "server/core/src/main/java/dev/slimevr/tracking/processor/**"
|
||||
"Area: SteamVR Driver":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "server/desktop/src/main/java/dev/slimevr/desktop/platform/**"
|
||||
"Area: Translation":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: "gui/public/i18n/**"
|
||||
"Area: VMC":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ["server/core/src/main/java/dev/slimevr/osc/Unity*", "server/core/src/main/java/dev/slimevr/osc/VMC*", "server/core/src/main/java/dev/slimevr/osc/VRM*"]
|
||||
"Area: VRCOSC":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ["server/core/src/main/java/dev/slimevr/osc/VRC*"]
|
||||
110
.github/workflows/build-gui.yml
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
name: Build GUI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- .github/workflows/build-gui.yml
|
||||
- gui/**
|
||||
- package*.json
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/build-gui.yml
|
||||
- gui/**
|
||||
- package*.json
|
||||
workflow_dispatch:
|
||||
create:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
cd gui
|
||||
npm run lint
|
||||
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, windows-latest, macos-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
env:
|
||||
# Don't mark warnings as errors
|
||||
CI: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- if: matrix.os == 'ubuntu-20.04'
|
||||
name: Set up Linux dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf
|
||||
# Increment to invalidate the cache
|
||||
version: 1.0
|
||||
# Enables a workaround to attempt to run pre and post install scripts
|
||||
execute_install_scripts: true
|
||||
# Disables uploading logs as a build artifact
|
||||
debug: false
|
||||
|
||||
- name: Cache cargo dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: "${{ matrix.os }}"
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run skipbundler
|
||||
|
||||
- if: matrix.os == 'windows-latest'
|
||||
name: Upload a Build Artifact (Windows)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
# Artifact name
|
||||
name: SlimeVR-GUI-Windows
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
path: target/release/slimevr.exe
|
||||
|
||||
- if: matrix.os == 'ubuntu-20.04'
|
||||
name: Upload a Build Artifact (Linux)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
# Artifact name
|
||||
name: SlimeVR-GUI-Linux
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
path: target/release/slimevr
|
||||
|
||||
- if: matrix.os == 'macos-latest'
|
||||
name: Upload a Build Artifact (macOS)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
# Artifact name
|
||||
name: SlimeVR-GUI-macOS
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
path: target/release/slimevr
|
||||
291
.github/workflows/build.yml
vendored
@@ -1,291 +0,0 @@
|
||||
name: SlimeVR Full Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ["v*"]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
setup-matrix:
|
||||
name: Configure Build Matrix
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- id: set-matrix
|
||||
shell: bash
|
||||
run: |
|
||||
BASE='[{"os":"ubuntu-22.04","platform":"linux"},{"os":"windows-latest","platform":"windows"},{"os":"macos-latest","platform":"macos"}]'
|
||||
EXTRA='[{"os":"ubuntu-22.04-arm","platform":"linux"},{"os":"windows-11-arm","platform":"windows"}]'
|
||||
|
||||
if [[ "${{ github.ref }}" == refs/tags/v* || "${{ github.ref }}" == refs/heads/main || "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
RESULT=$(echo "$BASE $EXTRA" | jq -c -s 'add')
|
||||
else
|
||||
RESULT=$(echo "$BASE" | jq -c '.')
|
||||
fi
|
||||
echo "matrix={\"include\":$RESULT}" >> "$GITHUB_OUTPUT"
|
||||
gui-checks:
|
||||
name: Gui Checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin --recurse-submodules=no --force
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
- name: GUI Lint
|
||||
run: pnpm i && cd gui && pnpm run lint
|
||||
|
||||
java-checks:
|
||||
name: Java Checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
- name: Java Spotless Check
|
||||
run: ./gradlew spotlessCheck --build-cache
|
||||
|
||||
|
||||
build-server-jar:
|
||||
name: Build Desktop Server
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin --recurse-submodules=no --force
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
- name: Build ShadowJar
|
||||
run: ./gradlew :server:desktop:shadowJar --build-cache
|
||||
- name: Test with Gradle
|
||||
run: ./gradlew :server:desktop:test
|
||||
- name: Upload Server Jar
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: server-jar
|
||||
path: |
|
||||
server/desktop/build/libs/slimevr.jar
|
||||
server/core/resources
|
||||
|
||||
build-gui-frontend:
|
||||
name: Build GUI Assets
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin --recurse-submodules=no --force
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
- name: Build JS
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
run: pnpm i && cd gui && pnpm run build
|
||||
- name: Tar GUI Dist
|
||||
run: tar -czf slimevr-gui-dist.tar.gz -C gui/out .
|
||||
- name: Upload GUI Dist
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: gui-dist
|
||||
path: slimevr-gui-dist.tar.gz
|
||||
|
||||
package-desktop:
|
||||
name: Package Desktop (${{ matrix.platform }} - ${{ matrix.os }})
|
||||
needs: [setup-matrix, build-server-jar, build-gui-frontend]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.setup-matrix.outputs.matrix) }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin --recurse-submodules=no --force
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v4
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
- name: Download Server Jar
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: server-jar
|
||||
path: server
|
||||
- name: Download GUI Dist
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: gui-dist
|
||||
- name: Extract GUI for Electron
|
||||
shell: bash
|
||||
run: mkdir -p gui/out && tar -xzf slimevr-gui-dist.tar.gz -C gui/out
|
||||
- name: Run Electron Builder
|
||||
shell: bash
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
run: |
|
||||
mkdir -p gui/dist/artifacts/linux/ \
|
||||
gui/dist/artifacts/win \
|
||||
gui/dist/artifacts/mac
|
||||
cd gui
|
||||
pnpm i
|
||||
pnpm exec electron-builder --${{ matrix.platform }} \
|
||||
${{ matrix.platform == 'macos' && '--universal' || '' }} \
|
||||
--publish never
|
||||
- name: Collect and Rename Artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
SRC_DIR="${{ github.workspace }}/gui/dist/artifacts"
|
||||
DEST_DIR="${{ github.workspace }}/release-out"
|
||||
mkdir -p "$DEST_DIR"
|
||||
|
||||
if [ "${{ matrix.platform }}" == "windows" ]; then
|
||||
[[ "${{ matrix.os }}" == *"arm"* ]] && SUFFIX="win-aarch64" || SUFFIX="win64"
|
||||
cp "$SRC_DIR"/win/*.zip "$DEST_DIR/SlimeVR-$SUFFIX.zip"
|
||||
|
||||
elif [ "${{ matrix.platform }}" == "linux" ]; then
|
||||
for f in "$SRC_DIR"/linux/*; do
|
||||
[ -d "$f" ] && continue
|
||||
BASE=$(basename "$f")
|
||||
NEW_NAME=$(echo "$BASE" | sed -e 's/x86_64/amd64/g' -e 's/arm64/aarch64/g')
|
||||
cp "$f" "$DEST_DIR/$NEW_NAME"
|
||||
done
|
||||
|
||||
elif [ "${{ matrix.platform }}" == "macos" ]; then
|
||||
cp "$SRC_DIR"/mac/*.dmg "$DEST_DIR/SlimeVR-mac.dmg"
|
||||
fi
|
||||
|
||||
echo "Collected files:"
|
||||
ls -lh "$DEST_DIR"
|
||||
- name: Upload For Release
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: release-${{ matrix.platform }}-${{ matrix.os }}
|
||||
path: release-out/*
|
||||
|
||||
bundle-android:
|
||||
name: Build Android APK
|
||||
needs: [build-gui-frontend]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin --recurse-submodules=no --force
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
- name: Download GUI Dist
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: gui-dist
|
||||
- name: Extract GUI for Android
|
||||
run: mkdir -p gui/out && tar -xzf slimevr-gui-dist.tar.gz -C gui/out
|
||||
- name: Build APK
|
||||
run: ./gradlew :server:android:build --build-cache
|
||||
env:
|
||||
ANDROID_STORE_FILE: ${{ secrets.ANDROID_STORE_FILE }}
|
||||
ANDROID_STORE_PASSWD: ${{ secrets.ANDROID_STORE_PASSWD }}
|
||||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
ANDROID_KEY_PASSWD: ${{ secrets.ANDROID_KEY_PASSWD }}
|
||||
- name: Test with Gradle
|
||||
run: ./gradlew test
|
||||
- name: Prepare APK
|
||||
run: cp server/android/build/outputs/apk/release/*.apk ./SlimeVR-android.apk
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: release-android
|
||||
path: SlimeVR-android.apk
|
||||
|
||||
- name: Build Google Play release bundle
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: ./gradlew :server:android:bundleRelease
|
||||
env:
|
||||
ANDROID_STORE_FILE: ${{ secrets.ANDROID_GPLAY_STORE_FILE }}
|
||||
ANDROID_STORE_PASSWD: ${{ secrets.ANDROID_GPLAY_STORE_PASSWD }}
|
||||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_GPLAY_KEY_ALIAS }}
|
||||
ANDROID_KEY_PASSWD: ${{ secrets.ANDROID_GPLAY_KEY_PASSWD }}
|
||||
|
||||
- name: Upload the Google Play artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
# Artifact name
|
||||
name: 'SlimeVR-Android-GPDev' # optional, default is artifact
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
path: server/android/build/outputs/bundle/release/*
|
||||
|
||||
create-release:
|
||||
name: Finalize Release Draft
|
||||
needs: [package-desktop, bundle-android, build-server-jar, build-gui-frontend]
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download All Release Artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: release-*
|
||||
path: release-out
|
||||
merge-multiple: true
|
||||
- name: Download Server Jar
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: server-jar
|
||||
path: server
|
||||
- name: Download GUI Dist
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: gui-dist
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
draft: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
release-out/*
|
||||
server/desktop/build/libs/slimevr.jar
|
||||
slimevr-gui-dist.tar.gz
|
||||
34
.github/workflows/generate-update-manifest.yml
vendored
@@ -1,34 +0,0 @@
|
||||
# This workflow will build the update manifest for the updater and update a GitHub release
|
||||
|
||||
name: Generate update manifest
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
generate-manifest:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22.x'
|
||||
|
||||
- name: Generate update-manifest.json
|
||||
run: |
|
||||
npx @slimevr/update-manifest-generator@latest
|
||||
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: "update-manifest.json"
|
||||
path: ./update-manifest.json
|
||||
|
||||
- name: Upload update-manifest.json to release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ secrets.UPDATE_MANIFEST_RELEASE_TAG }}
|
||||
files: ./update-manifest.json
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
291
.github/workflows/gradle.yaml
vendored
Normal file
@@ -0,0 +1,291 @@
|
||||
# This workflow will build a Java project with Gradle
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
|
||||
|
||||
name: SlimeVR Server
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
create:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin --recurse-submodules=no
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "adopt"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
- run: mkdir ./gui/dist && touch ./gui/dist/somefile
|
||||
shell: bash
|
||||
|
||||
- name: Check code formatting
|
||||
run: ./gradlew spotlessCheck
|
||||
|
||||
- name: Test with Gradle
|
||||
run: ./gradlew test
|
||||
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin --recurse-submodules=no
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "adopt"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew shadowJar
|
||||
|
||||
- name: Upload the Server JAR as a Build Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
# Artifact name
|
||||
name: "SlimeVR-Server" # optional, default is artifact
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
path: server/desktop/build/libs/*
|
||||
|
||||
- name: Upload to draft release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
draft: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
server/desktop/build/libs/*
|
||||
|
||||
|
||||
bundle-linux:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [build, test]
|
||||
if: contains(fromJSON('["workflow_dispatch", "create"]'), github.event_name)
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: "SlimeVR-Server"
|
||||
path: server/desktop/build/libs/
|
||||
|
||||
- name: Set up Linux dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf libfuse2
|
||||
# Increment to invalidate the cache
|
||||
version: 1.0
|
||||
# Enables a workaround to attempt to run pre and post install scripts
|
||||
execute_install_scripts: true
|
||||
# Disables uploading logs as a build artifact
|
||||
debug: false
|
||||
|
||||
- name: Cache cargo dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: "ubuntu-20.04"
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run tauri build
|
||||
|
||||
- uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SlimeVR-GUI-Deb
|
||||
path: target/release/bundle/deb/slimevr*.deb
|
||||
|
||||
- name: Install appimage-builder
|
||||
run: |
|
||||
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod a+x appimagetool-x86_64.AppImage
|
||||
sudo mv appimagetool-x86_64.AppImage /usr/local/bin/appimagetool
|
||||
|
||||
- name: Modify and Build AppImage
|
||||
run: |
|
||||
cd target/release/bundle/appimage
|
||||
chmod a+x slimevr*.AppImage
|
||||
./slimevr*.AppImage --appimage-extract
|
||||
cp $( git rev-parse --show-toplevel )/server/desktop/build/libs/slimevr.jar squashfs-root/slimevr.jar
|
||||
chmod 644 squashfs-root/slimevr.jar
|
||||
appimagetool squashfs-root slimevr*.AppImage
|
||||
|
||||
- uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SlimeVR-GUI-AppImage
|
||||
path: target/release/bundle/appimage/slimevr*.AppImage
|
||||
|
||||
- name: Prepare for release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
cp target/release/bundle/appimage/slimevr*.AppImage ./SlimeVR-amd64.appimage
|
||||
cp target/release/bundle/deb/slimevr*.deb ./SlimeVR-amd64.deb
|
||||
|
||||
- name: Upload to draft release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
draft: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
./SlimeVR-amd64.appimage
|
||||
./SlimeVR-amd64.deb
|
||||
|
||||
|
||||
bundle-mac:
|
||||
runs-on: macos-latest
|
||||
needs: [build, test]
|
||||
if: contains(fromJSON('["workflow_dispatch", "create"]'), github.event_name)
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: "SlimeVR-Server"
|
||||
path: server/desktop/build/libs/
|
||||
|
||||
- name: Cache cargo dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: "macos-latest"
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run tauri build -- "--target universal-apple-darwin"
|
||||
|
||||
- name: Modify Application
|
||||
run: |
|
||||
cd target/release/bundle/macos/slimevr.app/Contents/MacOS
|
||||
cp $( git rev-parse --show-toplevel )/server/desktop/build/libs/slimevr.jar ./
|
||||
cd ../../../../dmg/
|
||||
./bundle_dmg.sh --volname slimevr --icon slimevr 180 170 --app-drop-link 480 170 \
|
||||
--window-size 660 400 --hide-extension ../macos/slimevr.app \
|
||||
--volicon ../macos/slimevr.app/Contents/Resources/icon.icns --skip-jenkins \
|
||||
--eula ../../../../LICENSE-MIT slimevr.dmg ../macos/slimevr.app
|
||||
|
||||
- uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SlimeVR-GUI-MacApp
|
||||
path: target/release/bundle/macos/slimevr*.app
|
||||
|
||||
- uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SlimeVR-GUI-MacDmg
|
||||
path: target/release/bundle/dmg/slimevr.dmg
|
||||
|
||||
- name: Prepare for release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
cp target/release/bundle/dmg/slimevr.dmg ./SlimeVR-mac.dmg
|
||||
|
||||
- name: Upload to draft release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
draft: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
./SlimeVR-mac.dmg
|
||||
|
||||
|
||||
bundle-windows:
|
||||
runs-on: windows-latest
|
||||
needs: [build, test]
|
||||
if: contains(fromJSON('["workflow_dispatch", "create"]'), github.event_name)
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: "SlimeVR-Server"
|
||||
path: server/desktop/build/libs/
|
||||
|
||||
- name: Cache cargo dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: "windows-latest"
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
npm ci
|
||||
npm run skipbundler
|
||||
|
||||
- name: Bundle to zips
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir SlimeVR
|
||||
cp gui/src-tauri/icons/icon.ico ./SlimeVR/run.ico
|
||||
cp server/desktop/build/libs/slimevr.jar ./SlimeVR/slimevr.jar
|
||||
cp server/core/resources/* ./SlimeVR/
|
||||
cp target/release/slimevr.exe ./SlimeVR/
|
||||
7z a -tzip SlimeVR-win64.zip ./SlimeVR/
|
||||
|
||||
mv ./SlimeVR/slimevr.exe ./SlimeVR/slimevr-ui.exe
|
||||
7z a -tzip SlimeVR.zip ./SlimeVR/
|
||||
|
||||
- uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SlimeVR-GUI-Windows
|
||||
path: ./SlimeVR*.zip
|
||||
|
||||
- name: Upload to draft release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
draft: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
./SlimeVR-win64.zip
|
||||
./SlimeVR.zip
|
||||
22
.github/workflows/label.yml
vendored
@@ -1,22 +0,0 @@
|
||||
# This workflow will triage pull requests and apply a label based on the
|
||||
# paths that are modified in the pull request.
|
||||
#
|
||||
# To use this workflow, you will need to set up a .github/labeler.yml
|
||||
# file with configuration. For more information, see:
|
||||
# https://github.com/actions/labeler
|
||||
|
||||
name: Labeler
|
||||
on: [pull_request_target]
|
||||
|
||||
jobs:
|
||||
label:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/labeler@v6
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
17
.github/workflows/pontoon-pr.yml
vendored
@@ -3,19 +3,22 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- pontoon
|
||||
|
||||
jobs:
|
||||
pull_request:
|
||||
if: ${{ github.repository == 'SlimeVR/SlimeVR-Server' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: pull-request
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PONTOON_BOT_KEY }}
|
||||
run: |
|
||||
gh_pr_up() { gh pr create "$@" --label "Area: Translation" --base main || gh pr edit "$@"; }
|
||||
gh_pr_up --title "New Pontoon translations" --body "Please don't squash me 🥺"
|
||||
- uses: repo-sync/pull-request@v2
|
||||
with:
|
||||
destination_branch: "main"
|
||||
pr_title: "New Pontoon translations"
|
||||
pr_body: "Please don't squash me 🥺"
|
||||
pr_label: "Area: Translation"
|
||||
github_token: ${{ secrets.PONTOON_BOT_KEY }}
|
||||
|
||||
5
.github/workflows/rebase.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: pontoon
|
||||
submodules: recursive
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
- name: Rebase
|
||||
run: |
|
||||
git config --local user.name "slimevr-bot"
|
||||
git config --local user.email 'pantoon@slimevr.dev'
|
||||
git config --local user.email pantoon@slimevr.dev
|
||||
git fetch origin main
|
||||
git rebase origin/main
|
||||
git submodule update
|
||||
@@ -36,4 +36,3 @@ jobs:
|
||||
commit-message: "update"
|
||||
force-push: "true"
|
||||
name: "slimevr-bot"
|
||||
email: "pantoon@slimevr.dev"
|
||||
|
||||
14
.gitignore
vendored
@@ -17,7 +17,6 @@
|
||||
|
||||
# VSCode stuff
|
||||
/.vscode/settings.json
|
||||
/.vscode/launch.json
|
||||
|
||||
# Ignore eclipse stuff
|
||||
.project
|
||||
@@ -28,22 +27,15 @@
|
||||
/node_modules
|
||||
.husky
|
||||
|
||||
# kotlin stuff
|
||||
/.kotlin
|
||||
|
||||
# ignore gradle build folder
|
||||
build/
|
||||
|
||||
# Rust build artifacts
|
||||
/target
|
||||
|
||||
# direnv has been claimed for Nix usage
|
||||
.direnv/
|
||||
.devenv
|
||||
|
||||
# Ignore Android local properties
|
||||
local.properties
|
||||
|
||||
# Ignore temporary config
|
||||
vrconfig.yml.tmp
|
||||
|
||||
|
||||
# Nixos
|
||||
.bin/
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
YELLOW="\033[1;33m"
|
||||
GREEN="\033[1;32m"
|
||||
RESET="\033[0m"
|
||||
|
||||
if git rev-parse -q --verify MERGE_HEAD; then
|
||||
echo -e "${YELLOW}Skipping precommit hook because of merge${RESET}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
APP_PRE_COMMIT_OPTIONS="$(dirname "$0")/_/pre-commit.options"
|
||||
|
||||
if ! [ -f "$APP_PRE_COMMIT_OPTIONS" ]; then
|
||||
echo -e "${YELLOW}\nSkipping pre-commit hook."
|
||||
echo -e "If you want to use pre-commit for lint-staged, run:\n"
|
||||
echo -e " ${GREEN}echo -e 'APP_LINT=true;' > ${APP_PRE_COMMIT_OPTIONS}${RESET}"
|
||||
echo -e "${YELLOW}\nIt will add some delay before committing!\n${RESET}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
source $APP_PRE_COMMIT_OPTIONS
|
||||
|
||||
if [ -n "${APP_LINT}" ] && [ "${APP_LINT}" == "true" ]; then
|
||||
echo -e "${GREEN}[husky] [pre-commit] [lint-staged]${RESET}"
|
||||
case "$(uname -sr)" in
|
||||
CYGWIN*|MINGW*|MINGW32*|MSYS*)
|
||||
npx.cmd lint-staged
|
||||
;;
|
||||
|
||||
*)
|
||||
npx lint-staged
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
@@ -1,9 +0,0 @@
|
||||
export default {
|
||||
'server/**/*.{java,kt,kts}': (filenames) =>
|
||||
filenames.map(
|
||||
(filename) =>
|
||||
`./gradlew${
|
||||
process.platform === 'win32' ? '.bat' : ''
|
||||
} spotlessApply "-PspotlessIdeHook=${filename}"`
|
||||
),
|
||||
};
|
||||
@@ -1 +1 @@
|
||||
22.17.0
|
||||
18.12.1
|
||||
|
||||
2
.vscode/extensions.json
vendored
@@ -7,10 +7,10 @@
|
||||
"gaborv.flatbuffers",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"rust-lang.rust-analyzer",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"EditorConfig.EditorConfig",
|
||||
"macabeus.vscode-fluent",
|
||||
"redhat.vscode-yaml"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": []
|
||||
|
||||
@@ -6,7 +6,9 @@ This document describes essential knowledge required to contribute to the SlimeV
|
||||
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [Java v17+](https://adoptium.net/temurin/releases/)
|
||||
- [Node.js v16.9+](https://nodejs.org) (We recommend the use of `nvm` instead of installing Node.js directly)
|
||||
- [Node.js v16+](https://nodejs.org) (We recommend the use of `nvm` instead of installing Node.js directly)
|
||||
- [Microsoft Edge WebView2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section) or `webkit2gtk` for Linux
|
||||
- [Rust](https://rustup.rs)
|
||||
|
||||
## Cloning the code
|
||||
First, clone the codebase using git in a terminal in the folder you want.
|
||||
@@ -30,13 +32,12 @@ be at `server/build/libs/slimevr.jar` (you can ignore `server.jar`).
|
||||
|
||||
(Note: Your IDE may be able to do all of the above for you.)
|
||||
|
||||
### Electron (gui)
|
||||
### Tauri (gui)
|
||||
|
||||
- Activate corepack (included with Node.JS) via `corepack enable` (might require administrator permissions)
|
||||
- Run `pnpm i` in your IDE's terminal to download and install dependencies.
|
||||
- To launch the GUI in dev mode, run `pnpm gui`.
|
||||
- Finally, to compile for production, run `pnpm package:build`. The result
|
||||
will be at `dist/artifacts/` content will change depending of the platform.
|
||||
- Run `npm install` in your IDE's terminal to download and install dependencies.
|
||||
- To launch the GUI in dev mode, run `npm run gui`.
|
||||
- Finally, to compile for production, run `npm run tauri build`. The result
|
||||
will be at `target/release/slimevr.exe`.
|
||||
|
||||
## Code style
|
||||
|
||||
@@ -82,12 +83,12 @@ Import the formatting settings defined in `spotless.xml`, like this:
|
||||
Eclipse will only do a subset of the checks in `spotless`, so you may still want to do
|
||||
`./gradlew spotlessApply` if you ever see an error from spotless.
|
||||
|
||||
### Electron (gui)
|
||||
### Tauri (gui)
|
||||
|
||||
We use ESLint and Prettier to format GUI code.
|
||||
- First, go into the GUI's directory with your terminal by running `cd gui`.
|
||||
- To check code formatting, run `pnpm run lint`.
|
||||
- To fix code formatting, run `pnpm run lint:fix` and `pnpm run format`
|
||||
- To check code formatting, run `npm run lint`.
|
||||
- To fix code formatting, run `npm run lint:fix` and `npm run format`
|
||||
|
||||
Don't forget to run `cd ..` to return to the root directory.
|
||||
|
||||
@@ -103,7 +104,6 @@ When touching SolarXR:
|
||||
- After editing files, you should run `cd solarxr-protocol`, then either run
|
||||
`./generate-flatbuffer.ps1` (Windows) or `./generate-flatbuffer.sh` (Linux/OSX)
|
||||
- Make sure to commit your changes inside the submodule.
|
||||
- To make sure the gui use the latest generated code, run `pnpm i`.
|
||||
|
||||
## Code Licensing
|
||||
SlimeVR uses dual MIT and Apache-2.0 license. Be sure that any code that you reference,
|
||||
@@ -114,9 +114,3 @@ licensed under `GPL-v3`.
|
||||
## Discord
|
||||
We use discord *a lot* to coordinate and discuss development. Come join us at
|
||||
https://discord.gg/SlimeVR!
|
||||
|
||||
## Use of AI
|
||||
We DO NOT accept contributions that are generated with AI (for example, "vibe-coding").
|
||||
|
||||
If you do use AI, and you believe your usage of AI is reasonable, you must clearly disclose
|
||||
how you used AI in your submission.
|
||||
|
||||
4221
Cargo.lock
generated
Normal file
17
Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[workspace]
|
||||
# Use 2021 edition resolver, better resolves crate features.
|
||||
resolver = "2"
|
||||
|
||||
# A list of all rust crates in the workspace.
|
||||
members = ["gui/src-tauri"]
|
||||
|
||||
# These settings can be inherited by workspace members
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.65" # This version stabilized GATs and let-else
|
||||
repository = "https://github.com/SlimeVR/SlimeVR-Server"
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
strip = "debuginfo" # Only affects Unix binaries with DWARF
|
||||
@@ -47,11 +47,6 @@ on SlimeVR, you need to be aware of them:**
|
||||
Please refer to the [LICENSE-MIT] and [LICENSE-APACHE] files if you are at any point
|
||||
uncertain what the exact requirements are.
|
||||
|
||||
## Trademark and Logo use
|
||||
**SlimeVR is a trademark or a registered trademark of SlimeVR B.V. Usage of SlimeVR software, hardware, or other intellectual property in this or other repositories does not grant you the right to use SlimeVR trademark as your own.**
|
||||
|
||||
For more information, please refer to the [TRADEMARK].
|
||||
|
||||
## Contributions
|
||||
Any contributions submitted for inclusion in this repository will be dual-licensed under
|
||||
either:
|
||||
@@ -69,7 +64,3 @@ okay with this and that you are authorized to provide the above licenses.
|
||||
|
||||
[LICENSE-MIT]: LICENSE-MIT
|
||||
[LICENSE-APACHE]: LICENSE-APACHE
|
||||
[TRADEMARK]: TRADEMARK.md
|
||||
|
||||
|
||||
*if you read this, u cute*
|
||||
|
||||
33
TRADEMARK.md
@@ -1,33 +0,0 @@
|
||||
## SlimeVR is a trademark or a registered trademark of SlimeVR B.V.
|
||||
|
||||
**Usage of SlimeVR software, hardware, or other intellectual property in this or other repositories does not grant you the right to use SlimeVR trademark as your own.**
|
||||
|
||||
The purpose of a trademark is to remove uncertainty for users and customers regarding the product's manufacturer or endorsement. You're not allowed to market your product using SlimeVR name, and your usage of the name should be only factual and descriptive. For example, calling original SlimeVR products SlimeVR or describing compatibility of other products or derivatives. This applies to all products, including software, and hardware including non-official Full-Body Trackers.
|
||||
|
||||
**Here are a few _acceptable_ uses of SlimeVR name when selling unofficial Slime trackers:**
|
||||
* SlimeVR-compatible trackers
|
||||
* Unofficial SlimeVR trackers / Non-official SlimeVR trackers
|
||||
* DIY SlimeVR trackers
|
||||
* Third-party SlimeVR Trackers
|
||||
* Custom SlimeVR-compatible trackers
|
||||
* < Your Brand > Slime Trackers
|
||||
* Using "SlimeVR" as a search tag
|
||||
|
||||
**_Unacceptable_ uses include, but are not limited to:**
|
||||
* SlimeVR store
|
||||
* Buy SlimeVR
|
||||
* SlimeVR Trackers
|
||||
* Original SlimeVR
|
||||
* Official SlimeVR
|
||||
* SlimeVR BMI270 (or any other IMU model along with SlimeVR name)
|
||||
* < Your brand > SlimeVR / < your brand > SlimeVR Trackers
|
||||
|
||||
Use of the SlimeVR name that can cause confusion is not allowed in any part of the listing, including, but not limited to: product title, product description, product metadata, site title, site name, site metadata, site texts, social media posts, or other advertisement.
|
||||
|
||||
Also, please ensure you use the correct spelling and capitalization: only **"SlimeVR" is acceptable**. Not "Slimevr", "slimevr", or "Slime VR". You're allowed to use the word "slime" as you wish, it's not a trademark.
|
||||
|
||||
Please understand that we have an obligation to reduce confusion for the customers, and we believe that our usage terms are generous compared to many other companies and products. This applies to all sellers or derivative products, we do not make exceptions.
|
||||
|
||||
---
|
||||
|
||||
If you have any questions about SlimeVR trademark or copyrighted materials, you can reach out to us at *tm[at]slimevr.dev*.
|
||||
@@ -1,3 +1,3 @@
|
||||
plugins {
|
||||
id("org.ajoberstar.grgit")
|
||||
id("org.ajoberstar.grgit") version "5.2.0"
|
||||
}
|
||||
|
||||
84
dev.slimevr.SlimeVR.metainfo.xml
Normal file
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
dev.slimevr.SlimeVR.metainfo.xml by SlimeVR contributors
|
||||
|
||||
To the extent possible under law, the person who associated CC0 with
|
||||
dev.slimevr.SlimeVR.metainfo.xml has waived all copyright and related or neighboring rights
|
||||
to dev.slimevr.SlimeVR.metainfo.xml.
|
||||
|
||||
You should have received a copy of the CC0 legalcode along with this
|
||||
work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
-->
|
||||
<component type="desktop-application">
|
||||
<id>dev.slimevr.SlimeVR</id>
|
||||
|
||||
<name>SlimeVR</name>
|
||||
<summary>An app for facilitating full-body tracking in virtual reality</summary>
|
||||
<developer_name>SlimeVR Team</developer_name>
|
||||
|
||||
<!-- CC0 so attribution is not required -->
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>MIT OR Apache-2.0</project_license>
|
||||
|
||||
<content_rating type="oars-1.1" />
|
||||
<url type="homepage">https://slimevr.dev/</url>
|
||||
<url type="bugtracker">https://github.com/SlimeVR/SlimeVR-Server/issues</url>
|
||||
<url type="faq">https://docs.slimevr.dev/slimevr101.html</url>
|
||||
<url type="vcs-browser">https://github.com/SlimeVR/SlimeVR-Server</url>
|
||||
<url type="translate">https://i18n.slimevr.dev</url>
|
||||
<url type="help">https://docs.slimevr.dev/server-setup/slimevr-setup.html</url>
|
||||
<url type="contribute">https://github.com/SlimeVR/SlimeVR-Server/blob/main/CONTRIBUTING.md</url>
|
||||
<url type="contact">https://discord.gg/SlimeVR</url>
|
||||
<recommends>
|
||||
<display_length compare="ge">300</display_length>
|
||||
</recommends>
|
||||
<supports>
|
||||
<control>pointing</control>
|
||||
<control>keyboard</control>
|
||||
<control>touch</control>
|
||||
</supports>
|
||||
|
||||
<branding>
|
||||
<color type="primary">#663499</color>
|
||||
</branding>
|
||||
|
||||
<description>
|
||||
<p>
|
||||
SlimeVR is a set of open hardware sensors and open source software that facilitates full-body
|
||||
tracking (FBT) in virtual reality. With no base station required, SlimeVR makes wireless
|
||||
VR FBT affordable and comfortable.
|
||||
</p>
|
||||
|
||||
</description>
|
||||
|
||||
<launchable type="desktop-id">dev.slimevr.SlimeVR.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>The onboarding for the GUI</caption>
|
||||
<image>https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/main/assets/img/onboarding.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<releases>
|
||||
<release version="0.8.3" date="2023-07-09"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.8.3</url></release>
|
||||
<release version="0.8.2" date="2023-07-09"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.8.2</url></release>
|
||||
<release version="0.8.2-rc.1" type="development" date="2023-07-07"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.8.2-rc.1</url></release>
|
||||
<release version="0.8.1" date="2023-07-04"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.8.1</url></release>
|
||||
<release version="0.8.0" date="2023-06-22"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.8.0</url></release>
|
||||
<release version="0.8.0-rc.3" type="development" date="2023-06-20"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.8.0-rc.3</url></release>
|
||||
<release version="0.8.0-rc.2" type="development" date="2023-06-15"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.8.0-rc.2</url></release>
|
||||
<release version="0.8.0-rc.1" type="development" date="2023-06-01"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.8.0-rc.1</url></release>
|
||||
<release version="0.7.1" date="2023-04-14"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.7.1</url></release>
|
||||
<release version="0.7.0" date="2023-04-11"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.7.0</url></release>
|
||||
<release version="0.6.3" date="2023-02-22"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.6.3</url></release>
|
||||
<release version="0.6.2" date="2023-02-17"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.6.2</url></release>
|
||||
<release version="0.6.1" date="2023-02-12"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.6.1</url></release>
|
||||
<release version="0.6.0" date="2023-01-05"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.6.0</url></release>
|
||||
<release version="0.5.1" date="2022-12-12"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.5.1</url></release>
|
||||
<release version="0.5.0" date="2022-12-07"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.5.0</url></release>
|
||||
<release version="0.4.0" date="2022-11-24"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.4.0</url></release>
|
||||
<release version="0.3.1" date="2022-11-22"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.3.1</url></release>
|
||||
<release version="0.3.0" date="2022-11-16"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.3.0</url></release>
|
||||
<release version="0.2.1" date="2022-08-24"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.2.1</url></release>
|
||||
<release version="0.2.0" date="2022-06-28"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.2.0</url></release>
|
||||
</releases>
|
||||
</component>
|
||||
118
flake.lock
generated
@@ -1,58 +1,126 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-parts": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1762980239,
|
||||
"narHash": "sha256-8oNVE8TrD19ulHinjaqONf9QWCKK+w4url56cdStMpM=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "52a2caecc898d0b46b2b905f058ccc5081f842da",
|
||||
"lastModified": 1685518550,
|
||||
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1756787288,
|
||||
"narHash": "sha256-rw/PHa1cqiePdBxhF66V7R+WAP8WekQ0mCDG4CFqT8Y=",
|
||||
"lastModified": 1686412476,
|
||||
"narHash": "sha256-inl9SVk6o5h75XKC79qrDCAobTD1Jxh6kVYTZKHzewA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d0fc30899600b9b3466ddb260fd83deb486c32f1",
|
||||
"rev": "21951114383770f96ae528d0ae68824557768e81",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1761765539,
|
||||
"narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "719359f4562934ae99f5443f20aa06c2ffff91fc",
|
||||
"lastModified": 1681358109,
|
||||
"narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs"
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1686364106,
|
||||
"narHash": "sha256-h4gCQg+jizmAbdg6UPlhxQVk4A7Ar/zoLa0wx3wBya0=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "ba011dd1c5028dbb880bc3b0f427e0ff689e6203",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
125
flake.nix
@@ -1,50 +1,87 @@
|
||||
{
|
||||
description = "SlimeVR Server & GUI";
|
||||
description = "Server app for SlimeVR ecosystem";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
};
|
||||
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
systems = [ "x86_64-linux" ];
|
||||
inputs.rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
|
||||
perSystem = { pkgs, ... }:
|
||||
let
|
||||
runtimeLibs = pkgs: (with pkgs; [
|
||||
jdk17
|
||||
|
||||
alsa-lib at-spi2-atk at-spi2-core cairo cups dbus expat
|
||||
gdk-pixbuf glib gtk3 libdrm libgbm libglvnd libnotify
|
||||
libxkbcommon mesa nspr nss pango systemd vulkan-loader
|
||||
wayland xorg.libX11 xorg.libXcomposite xorg.libXdamage
|
||||
xorg.libXext xorg.libXfixes xorg.libXrandr xorg.libxcb
|
||||
xorg.libxshmfence libusb1 udev libxcrypt-legacy
|
||||
rpm fpm
|
||||
|
||||
wineWow64Packages.stable
|
||||
zlib squashfsTools fakeroot libarchive icu
|
||||
nodejs_22 pnpm pkg-config python3 gcc gnumake binutils git
|
||||
pkgs.nodePackages.node-gyp-build
|
||||
]);
|
||||
|
||||
slimeShell = pkgs.buildFHSEnv {
|
||||
name = "slimevr-env";
|
||||
targetPkgs = runtimeLibs;
|
||||
profile = ''
|
||||
export JAVA_HOME=${pkgs.jdk17}
|
||||
export PATH="${pkgs.jdk17}/bin:$PATH"
|
||||
|
||||
# Tell electron-builder to use system tools instead of downloading them
|
||||
export USE_SYSTEM_FPM=true
|
||||
export USE_SYSTEM_MKSQUASHFS=true
|
||||
'';
|
||||
runScript = "bash";
|
||||
};
|
||||
in
|
||||
{
|
||||
devShells.default = slimeShell.env;
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
rust-overlay,
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem
|
||||
(
|
||||
system: let
|
||||
overlays = [(import rust-overlay)];
|
||||
pkgs = import nixpkgs {
|
||||
inherit system overlays;
|
||||
};
|
||||
};
|
||||
rustTarget = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
|
||||
nativeBuildInputs = with pkgs; [
|
||||
curl
|
||||
gcc
|
||||
openssl
|
||||
pkgconfig
|
||||
which
|
||||
zlib
|
||||
|
||||
freetype
|
||||
expat
|
||||
];
|
||||
buildInputs = with pkgs; [
|
||||
appimagekit
|
||||
atk
|
||||
cairo
|
||||
dbus
|
||||
dbus.lib
|
||||
dprint
|
||||
gdk-pixbuf
|
||||
glib.out
|
||||
gobject-introspection
|
||||
gtk3
|
||||
harfbuzz
|
||||
libayatana-appindicator-gtk3
|
||||
libffi
|
||||
libsoup
|
||||
openssl.out
|
||||
pango
|
||||
pkg-config
|
||||
treefmt
|
||||
webkitgtk
|
||||
zlib
|
||||
gst_all_1.gstreamer
|
||||
gst_all_1.gst-plugins-base
|
||||
gst_all_1.gst-plugins-good
|
||||
gst_all_1.gst-plugins-bad
|
||||
|
||||
# Some nice things to have
|
||||
exa
|
||||
fd
|
||||
|
||||
jdk17 # JDK17
|
||||
nodejs
|
||||
gradle
|
||||
];
|
||||
in {
|
||||
devShells.default = pkgs.mkShell {
|
||||
nativeBuildInputs =
|
||||
nativeBuildInputs
|
||||
++ [
|
||||
];
|
||||
buildInputs =
|
||||
buildInputs
|
||||
++ [
|
||||
rustTarget
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
alias ls=exa
|
||||
alias find=fd
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,10 +13,7 @@ android.useAndroidX=true
|
||||
android.nonTransitiveRClass=true
|
||||
org.gradle.unsafe.configuration-cache=false
|
||||
|
||||
kotlinVersion=2.3.10
|
||||
spotlessVersion=8.2.1
|
||||
shadowJarVersion=9.3.1
|
||||
buildconfigVersion=6.0.7
|
||||
# We should probably stop using grgit, see:
|
||||
# https://andrewoberstar.com/posts/2024-04-02-dont-commit-to-grgit/
|
||||
grgitVersion=5.3.3
|
||||
kotlinVersion=1.9.0-RC
|
||||
spotlessVersion=6.12.0
|
||||
shadowJarVersion=8.1.1
|
||||
buildconfigVersion=3.1.0
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,8 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip
|
||||
distributionSha256Sum=f1771298a70f6db5a29daf62378c4e18a17fc33c9ba6b14362e0cdf40610380d
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
34
gradlew
vendored
@@ -15,8 +15,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -57,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -85,9 +83,10 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -134,13 +133,10 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
@@ -148,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@@ -156,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -201,15 +197,11 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
||||
22
gradlew.bat
vendored
Executable file → Normal file
@@ -13,8 +13,6 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -45,11 +43,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -59,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
|
||||
8
gui/.env
@@ -1,8 +0,0 @@
|
||||
VITE_FIRMWARE_TOOL_URL=https://fw-tool-api-v2.slimevr.io
|
||||
VITE_FIRMWARE_TOOL_S3_URL=https://fw-tool-bucket-v2.slimevr.io
|
||||
FIRMWARE_TOOL_SCHEMA_URL=https://fw-tool-api-v2.slimevr.io/api-json
|
||||
|
||||
|
||||
# VITE_FIRMWARE_TOOL_URL=http://localhost:3000
|
||||
# VITE_FIRMWARE_TOOL_S3_URL=http://localhost:9099
|
||||
# FIRMWARE_TOOL_SCHEMA_URL=http://localhost:3000/api-json
|
||||
43
gui/.eslintrc.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"jest": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["react-hooks", "@typescript-eslint"],
|
||||
"rules": {
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/prop-types": "off",
|
||||
"spaced-comment": "error",
|
||||
"quotes": ["error", "single"],
|
||||
"no-duplicate-imports": "error",
|
||||
"no-inline-styles": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"react/no-unescaped-entities": "off",
|
||||
"camelcase": "error",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"typescript": {}
|
||||
},
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
}
|
||||
11
gui/.gitignore
vendored
@@ -28,14 +28,3 @@ yarn-error.log*
|
||||
# vite
|
||||
/dist
|
||||
/stats.html
|
||||
vite.config.ts.timestamp*
|
||||
electron.vite.config.*.mjs
|
||||
|
||||
# eslint
|
||||
.eslintcache
|
||||
|
||||
# Sentry Config File
|
||||
.env.sentry-build-plugin
|
||||
|
||||
# electron
|
||||
out/
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export default {
|
||||
'**/*.{ts,tsx}': () => 'tsc -p tsconfig.json --noEmit',
|
||||
'src/**/*.{js,jsx,ts,tsx}': 'eslint --max-warnings=0 --no-warn-ignored --cache --fix',
|
||||
'**/*.{js,jsx,ts,tsx,css,scss,md,json}': 'prettier --write',
|
||||
};
|
||||
@@ -1,70 +0,0 @@
|
||||
appId: dev.slimevr.SlimeVR
|
||||
productName: SlimeVR
|
||||
# Global naming pattern
|
||||
artifactName: "${productName}-${version}-${os}-${arch}.${ext}"
|
||||
|
||||
directories:
|
||||
output: dist/artifacts/${os}
|
||||
|
||||
asar: true
|
||||
asarUnpack:
|
||||
- out/main/chunks/*.jar
|
||||
|
||||
electronLanguages:
|
||||
- en-US
|
||||
|
||||
files:
|
||||
- out/**/*
|
||||
- index.html
|
||||
- package.json
|
||||
- node_modules/**
|
||||
- "!node_modules/*/{README,readme,README.md,readme.md,CHANGELOG,CHANGELOG.md,changelog.md}"
|
||||
- "!node_modules/*/{test,tests,__tests__,docs,doc,example,examples}"
|
||||
- "!node_modules/*/.{git,github,vscode,editorconfig,eslintrc,prettierrc}"
|
||||
- "!node_modules/**/*.{map,ts,tsx,d.ts}"
|
||||
- "!**/.DS_Store"
|
||||
|
||||
|
||||
linux:
|
||||
category: Game
|
||||
artifactName: "${productName}-${arch}.${ext}"
|
||||
target:
|
||||
- target: AppImage
|
||||
- target: deb
|
||||
- target: rpm
|
||||
extraFiles:
|
||||
- from: "../server/desktop/build/libs/slimevr.jar"
|
||||
to: "."
|
||||
- from: "./electron/resources/69-slimevr-devices.rules"
|
||||
to: "."
|
||||
icon: "./electron/resources/icons"
|
||||
|
||||
deb:
|
||||
depends: [openjdk-17-jre-headless, udev]
|
||||
afterInstall: "./electron/resources/scripts/postinstall.sh"
|
||||
afterRemove: "./electron/resources/scripts/postremove.sh"
|
||||
|
||||
rpm:
|
||||
depends: [java-latest-openjdk, udev]
|
||||
afterInstall: "./electron/resources/scripts/postinstall.sh"
|
||||
afterRemove: "./electron/resources/scripts/postremove.sh"
|
||||
|
||||
win:
|
||||
artifactName: "${productName}-${os}-${arch}.${ext}"
|
||||
target: zip
|
||||
icon: "./electron/resources/icons/icon.ico"
|
||||
extraFiles:
|
||||
- from: "../server/desktop/build/libs/slimevr.jar"
|
||||
to: "."
|
||||
- from: "../server/core/resources"
|
||||
to: "."
|
||||
filter: ["**/*"]
|
||||
|
||||
mac:
|
||||
target: dmg
|
||||
artifactName: "SlimeVR-mac.${ext}"
|
||||
x64ArchFiles: "**/register-protocol-handler.node"
|
||||
icon: "./electron/resources/icons/icon.icns"
|
||||
extraFiles:
|
||||
- from: "../server/desktop/build/libs/slimevr.jar"
|
||||
to: "Resources/slimevr.jar"
|
||||
@@ -1,47 +0,0 @@
|
||||
import { defineConfig } from 'electron-vite'
|
||||
import { resolve } from 'path'
|
||||
import rendererConfig from './vite.config' // Import your existing React config
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: resolve(__dirname, 'electron/main/index.ts'),
|
||||
external: [
|
||||
'pino',
|
||||
'pino-pretty',
|
||||
'pino-roll',
|
||||
'commander',
|
||||
'open'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
preload: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: resolve(__dirname, 'electron/preload/index.ts'),
|
||||
output: {
|
||||
format: 'cjs', // Force CJS for the preload
|
||||
entryFileNames: 'index.js' // Change back to .js
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
renderer: {
|
||||
...rendererConfig,
|
||||
root: '.',
|
||||
build: {
|
||||
commonjsOptions: {
|
||||
// Force Rollup to treat the protocol directory as CommonJS
|
||||
// even though it's not in node_modules
|
||||
include: [/solarxr-protocol/, /node_modules/],
|
||||
// Required for Flatbuffers/Generated code interop
|
||||
transformMixedEsModules: true,
|
||||
},
|
||||
rollupOptions: {
|
||||
input: resolve(__dirname, 'index.html')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
1
gui/electron/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
resources/java-version/JavaVersion.class
|
||||
@@ -1,12 +0,0 @@
|
||||
import { program } from "commander";
|
||||
|
||||
program
|
||||
.option('-p --path <path>', 'set launch path')
|
||||
.option(
|
||||
'--skip-server-if-running',
|
||||
'gui will not launch the server if it is already running'
|
||||
)
|
||||
.allowUnknownOption();
|
||||
|
||||
program.parse(process.argv);
|
||||
export const options = program.opts();
|
||||
@@ -1,477 +0,0 @@
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
dialog,
|
||||
Menu,
|
||||
nativeImage,
|
||||
net,
|
||||
protocol,
|
||||
screen,
|
||||
shell,
|
||||
Tray,
|
||||
} from 'electron';
|
||||
import { IPC_CHANNELS } from '../shared';
|
||||
import path, { dirname, join } from 'path';
|
||||
import open from 'open';
|
||||
import trayIcon from '../resources/icons/icon.png?asset';
|
||||
import appleTrayIcon from '../resources/icons/Square30x30Logo.png?asset';
|
||||
import { readFile, stat } from 'fs/promises';
|
||||
import { getPlatform, handleIpc, isPortAvailable } from './utils';
|
||||
import {
|
||||
findServerJar,
|
||||
findSystemJRE,
|
||||
getGuiDataFolder,
|
||||
getLogsFolder,
|
||||
getServerDataFolder,
|
||||
getWindowStateFile,
|
||||
} from './paths';
|
||||
import { stores } from './store';
|
||||
import { closeLogger, logger } from './logger';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
|
||||
import { spawn } from 'node:child_process';
|
||||
import { discordPresence } from './presence';
|
||||
import { options } from './cli';
|
||||
import { ServerStatusEvent } from 'electron/preload/interface';
|
||||
import { mkdir } from 'node:fs/promises';
|
||||
import { MenuItem } from 'electron/main';
|
||||
|
||||
// Fixes colors looking washed on linux
|
||||
// Might affect hdr
|
||||
if (process.platform === 'linux') {
|
||||
app.commandLine.appendSwitch('disable-features', 'WaylandWpColorManagerV1');
|
||||
app.commandLine.appendSwitch('force-color-profile', 'srgb');
|
||||
}
|
||||
|
||||
app.setPath('userData', getGuiDataFolder());
|
||||
app.setPath('sessionData', join(getGuiDataFolder(), 'electron'));
|
||||
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
scheme: 'app',
|
||||
privileges: {
|
||||
standard: true,
|
||||
secure: true,
|
||||
supportFetchAPI: true,
|
||||
corsEnabled: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
|
||||
handleIpc(IPC_CHANNELS.GH_FETCH, async (e, options) => {
|
||||
if (options.type === 'fw-releases') {
|
||||
return fetch(
|
||||
'https://api.github.com/repos/SlimeVR/SlimeVR-Tracker-ESP/releases'
|
||||
).then((res) => res.json());
|
||||
}
|
||||
if (options.type === 'asset') {
|
||||
if (
|
||||
!options.url.startsWith(
|
||||
'https://github.com/SlimeVR/SlimeVR-Tracker-ESP/releases/download'
|
||||
)
|
||||
)
|
||||
return null;
|
||||
return fetch(options.url).then((res) => res.json());
|
||||
}
|
||||
});
|
||||
|
||||
handleIpc(IPC_CHANNELS.OS_STATS, async () => {
|
||||
return {
|
||||
type: getPlatform(),
|
||||
};
|
||||
});
|
||||
|
||||
handleIpc(IPC_CHANNELS.I18N_OVERRIDE, async () => {
|
||||
const overridefile = join(getServerDataFolder(), 'override.ftl');
|
||||
const exists = await stat(overridefile)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (!exists) return false;
|
||||
return readFile(overridefile, { encoding: 'utf-8' });
|
||||
});
|
||||
|
||||
handleIpc(IPC_CHANNELS.LOG, (e, type, ...args) => {
|
||||
let payload: Record<string, unknown> = {};
|
||||
const messageParts: unknown[] = [];
|
||||
|
||||
args.forEach((arg) => {
|
||||
if (arg instanceof Error) {
|
||||
payload.err = arg;
|
||||
} else if (typeof arg === 'object' && arg !== null) {
|
||||
payload = { ...payload, ...arg };
|
||||
} else {
|
||||
messageParts.push(arg);
|
||||
}
|
||||
});
|
||||
|
||||
const msg = messageParts.join(' ');
|
||||
|
||||
switch (type) {
|
||||
case 'error':
|
||||
logger.error(payload, msg);
|
||||
break;
|
||||
case 'warn':
|
||||
logger.warn(payload, msg);
|
||||
break;
|
||||
default:
|
||||
logger.info(payload, msg);
|
||||
}
|
||||
});
|
||||
|
||||
handleIpc(IPC_CHANNELS.OPEN_URL, (e, url) => {
|
||||
const allowed_urls = [
|
||||
/steam:\/\/.*/,
|
||||
/ms-settings:network$/,
|
||||
/https:\/\/.*\.slimevr\.dev.*/,
|
||||
/https:\/\/github\.com\/.*/,
|
||||
/https:\/\/discord\.gg\/slimevr$/,
|
||||
];
|
||||
if (allowed_urls.find((a) => url.match(a))) open(url);
|
||||
else logger.error({ url }, 'attempted to open non-whitelisted URL');
|
||||
});
|
||||
|
||||
handleIpc(IPC_CHANNELS.STORAGE, async (e, { type, method, key, value }) => {
|
||||
const store = stores[type];
|
||||
if (!store) throw new Error(`Storage type ${type} not found`);
|
||||
|
||||
switch (method) {
|
||||
case 'get':
|
||||
return store.get(key!);
|
||||
case 'set':
|
||||
return store.set(key!, value);
|
||||
case 'delete':
|
||||
return store.delete(key!);
|
||||
case 'save':
|
||||
return store.save();
|
||||
}
|
||||
});
|
||||
|
||||
handleIpc(IPC_CHANNELS.DISCORD_PRESENCE, async (e, options) => {
|
||||
if (options.enable && !discordPresence.state.ready) {
|
||||
await discordPresence.connect();
|
||||
discordPresence.updateActivity(options.activity);
|
||||
} else if (!options.enable && discordPresence.state.ready) {
|
||||
discordPresence.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
handleIpc(IPC_CHANNELS.OPEN_FILE, (e, folder) => {
|
||||
const requestedPath = path.resolve(folder);
|
||||
|
||||
const isAllowed = [getServerDataFolder(), getGuiDataFolder(), getLogsFolder()].some(
|
||||
(parent) => {
|
||||
const absoluteParent = path.resolve(parent);
|
||||
const relative = path.relative(absoluteParent, requestedPath);
|
||||
return !relative.includes('..') && !path.isAbsolute(relative);
|
||||
}
|
||||
);
|
||||
|
||||
if (isAllowed) {
|
||||
shell.openPath(requestedPath);
|
||||
} else {
|
||||
logger.error({ path: requestedPath }, 'Blocked unauthorized path');
|
||||
}
|
||||
});
|
||||
|
||||
handleIpc(IPC_CHANNELS.GET_FOLDER, (e, folder) => {
|
||||
switch (folder) {
|
||||
case 'config':
|
||||
return getGuiDataFolder();
|
||||
case 'logs':
|
||||
return getLogsFolder();
|
||||
}
|
||||
});
|
||||
|
||||
const windowStateFile = await readFile(getWindowStateFile(), {
|
||||
encoding: 'utf-8',
|
||||
}).catch(() => null);
|
||||
|
||||
const defaultWindowState: {
|
||||
width: number;
|
||||
height: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
} = {
|
||||
width: 1289.0,
|
||||
height: 709.0,
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
};
|
||||
const windowState = windowStateFile ? JSON.parse(windowStateFile) : defaultWindowState;
|
||||
|
||||
const MIN_WIDTH = 393;
|
||||
const MIN_HEIGHT = 667;
|
||||
|
||||
function validateWindowState(state: typeof defaultWindowState) {
|
||||
if (state.x === undefined || state.y === undefined) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const displays = screen.getAllDisplays();
|
||||
|
||||
const isVisible = displays.some((display) => {
|
||||
return (
|
||||
state.x! >= display.bounds.x &&
|
||||
state.y! >= display.bounds.y &&
|
||||
state.x! + state.width <= display.bounds.x + display.bounds.width &&
|
||||
state.y! + state.height <= display.bounds.y + display.bounds.height
|
||||
);
|
||||
});
|
||||
|
||||
const minWidth = MIN_WIDTH;
|
||||
const minHeight = MIN_HEIGHT;
|
||||
|
||||
if (!isVisible || state.width < minWidth || state.height < minHeight) {
|
||||
return defaultWindowState;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
const saveWindowState = async () => {
|
||||
await mkdir(dirname(getWindowStateFile()), { recursive: true });
|
||||
writeFileSync(getWindowStateFile(), JSON.stringify(windowState));
|
||||
};
|
||||
|
||||
function createWindow() {
|
||||
const validatedState = validateWindowState(windowState);
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
width: validatedState.width,
|
||||
height: validatedState.height,
|
||||
x: validatedState.x,
|
||||
y: validatedState.y,
|
||||
minHeight: MIN_HEIGHT,
|
||||
minWidth: MIN_WIDTH,
|
||||
movable: true,
|
||||
frame: false,
|
||||
roundedCorners: true,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
devTools: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (process.env.ELECTRON_RENDERER_URL) {
|
||||
mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL);
|
||||
mainWindow.webContents.openDevTools();
|
||||
} else {
|
||||
mainWindow.loadURL('app://./index.html');
|
||||
}
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
handleIpc('window-actions', (e, action) => {
|
||||
switch (action) {
|
||||
case 'close':
|
||||
mainWindow?.close();
|
||||
break;
|
||||
case 'hide':
|
||||
mainWindow?.hide();
|
||||
break;
|
||||
case 'minimize':
|
||||
mainWindow?.minimize();
|
||||
break;
|
||||
case 'maximize':
|
||||
mainWindow?.maximize();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
handleIpc('open-dialog', (e, options) => dialog.showOpenDialog(options));
|
||||
handleIpc('save-dialog', (e, options) => dialog.showSaveDialog(options));
|
||||
|
||||
const icon = nativeImage.createFromPath(
|
||||
getPlatform() === 'macos' ? appleTrayIcon : trayIcon
|
||||
);
|
||||
const tray = new Tray(icon);
|
||||
tray.setToolTip('SlimeVR');
|
||||
tray.on('click', () => {
|
||||
mainWindow?.show();
|
||||
});
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Show',
|
||||
click: () => {
|
||||
mainWindow?.show();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Hide',
|
||||
click: () => {
|
||||
mainWindow?.hide();
|
||||
},
|
||||
},
|
||||
{ role: 'quit' },
|
||||
]);
|
||||
tray.setContextMenu(contextMenu);
|
||||
|
||||
const updateWindowState = () => {
|
||||
if (!mainWindow) return;
|
||||
|
||||
windowState.minimized = mainWindow.isMinimized();
|
||||
if (!mainWindow.isMinimized() && !mainWindow.isMaximized()) {
|
||||
const bounds = mainWindow.getBounds();
|
||||
windowState.width = bounds.width;
|
||||
windowState.height = bounds.height;
|
||||
windowState.x = bounds.x;
|
||||
windowState.y = bounds.y;
|
||||
}
|
||||
};
|
||||
|
||||
mainWindow.on('move', updateWindowState);
|
||||
mainWindow.on('resize', updateWindowState);
|
||||
mainWindow.on('minimize', updateWindowState);
|
||||
mainWindow.on('maximize', updateWindowState);
|
||||
|
||||
mainWindow.webContents.on('context-menu', (event, params) => {
|
||||
const menu = new Menu();
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: 'Inspect Element',
|
||||
click: () => {
|
||||
mainWindow?.webContents.inspectElement(params.x, params.y);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(new MenuItem({ type: 'separator' }));
|
||||
menu.append(new MenuItem({ label: 'Copy', role: 'copy' }));
|
||||
menu.append(new MenuItem({ label: 'Paste', role: 'paste' }));
|
||||
|
||||
if (mainWindow) menu.popup({ window: mainWindow });
|
||||
});
|
||||
}
|
||||
|
||||
const checkEnvironmentVariables = () => {
|
||||
const to_check = ['_JAVA_OPTIONS', 'JAVA_TOOL_OPTIONS'];
|
||||
|
||||
const set = to_check.filter((env) => !!process.env[env]);
|
||||
if (set.length > 0) {
|
||||
dialog.showErrorBox(
|
||||
'SlimeVR',
|
||||
`You have environment variables ${set.join(', ')} set, which may cause the SlimeVR Server to fail to launch properly.`
|
||||
);
|
||||
app.quit();
|
||||
}
|
||||
};
|
||||
|
||||
const isServerRunning = async () => !(await isPortAvailable(21110));
|
||||
|
||||
const spawnServer = async () => {
|
||||
if (options.skipServerIfRunning && (await isServerRunning())) {
|
||||
logger.info(
|
||||
{ skipServerIfRunning: options.skipServerIfRunning },
|
||||
'Server is already running, skipping server start'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const serverJar = findServerJar();
|
||||
if (!serverJar) {
|
||||
logger.info('server jar not found, skipping');
|
||||
return;
|
||||
}
|
||||
const sharedDir = dirname(serverJar);
|
||||
const javaBin = await findSystemJRE(sharedDir);
|
||||
if (!javaBin) {
|
||||
dialog.showErrorBox(
|
||||
'SlimeVR',
|
||||
`Couldn't find a compatible Java version, please download Java 17 or higher`
|
||||
);
|
||||
app.quit()
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info({ javaBin, serverJar }, 'Found Java and server jar');
|
||||
const platform = getPlatform();
|
||||
const serverWorkdir = getServerDataFolder()
|
||||
const serverProcess = spawn(javaBin, ['-Xmx128M', '-jar', serverJar, 'run'], {
|
||||
cwd: serverWorkdir,
|
||||
shell: false,
|
||||
env:
|
||||
platform === 'windows'
|
||||
? {
|
||||
...process.env,
|
||||
APPDATA: app.getPath('appData'),
|
||||
LOCALAPPDATA: process.env['USERPROFILE'] ? path.join(process.env['USERPROFILE'], 'AppData', 'Local') : undefined,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const sendToWindow = (event: ServerStatusEvent) => {
|
||||
if (mainWindow && !mainWindow.webContents.isDestroyed()) {
|
||||
mainWindow.webContents.send(IPC_CHANNELS.SERVER_STATUS, event);
|
||||
}
|
||||
};
|
||||
|
||||
serverProcess.stdout?.on('data', (message) => {
|
||||
sendToWindow({ message: message.toString(), type: 'stdout' });
|
||||
});
|
||||
|
||||
serverProcess.stderr?.on('data', (message) => {
|
||||
sendToWindow({ message: message.toString(), type: 'stderr' });
|
||||
});
|
||||
|
||||
serverProcess.on('error', (err) => {
|
||||
logger.info({ err }, 'Error launching the java server');
|
||||
if (!isQuitting) app.quit();
|
||||
})
|
||||
|
||||
serverProcess.on('exit', () => {
|
||||
logger.info('Server process exiting');
|
||||
})
|
||||
|
||||
const exited = new Promise<void>((resolve) => serverProcess.once('exit', resolve));
|
||||
|
||||
return {
|
||||
process: serverProcess,
|
||||
close: () => serverProcess.kill(),
|
||||
waitForExit: () => exited,
|
||||
};
|
||||
};
|
||||
|
||||
let isQuitting = false;
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
// Register protocol handler for app:// scheme to handle assets with leading slashes
|
||||
protocol.handle('app', (request) => {
|
||||
const url = request.url.slice('app://'.length);
|
||||
const filePath = path.normalize(join(__dirname, '../renderer', url));
|
||||
return net.fetch('file://' + filePath);
|
||||
});
|
||||
|
||||
checkEnvironmentVariables();
|
||||
const server = await spawnServer();
|
||||
|
||||
createWindow();
|
||||
|
||||
logger.info('SlimeVR started!');
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
app.on('before-quit', async (event) => {
|
||||
if (isQuitting) return;
|
||||
isQuitting = true;
|
||||
event.preventDefault();
|
||||
logger.info('App quitting, saving...');
|
||||
server?.close();
|
||||
await server?.waitForExit();
|
||||
stores.settings.save();
|
||||
stores.cache.save();
|
||||
discordPresence.destroy();
|
||||
await saveWindowState();
|
||||
await closeLogger();
|
||||
app.exit(0);
|
||||
});
|
||||
});
|
||||
@@ -1,34 +0,0 @@
|
||||
import pino from 'pino';
|
||||
import { getLogsFolder } from './paths';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const transport = pino.transport({
|
||||
targets: [
|
||||
{
|
||||
target: 'pino-roll',
|
||||
options: {
|
||||
file: join(getLogsFolder(), 'slimevr-gui.log'),
|
||||
frequency: 'daily',
|
||||
size: '10m',
|
||||
mkdir: true,
|
||||
limit: { count: 7 },
|
||||
},
|
||||
level: 'info',
|
||||
},
|
||||
{
|
||||
target: 'pino-pretty',
|
||||
options: { colorize: true },
|
||||
level: 'debug',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const logger = pino(transport);
|
||||
|
||||
export const closeLogger = () =>
|
||||
new Promise<void>((resolve) => {
|
||||
logger.flush(() => {
|
||||
transport.once('close', resolve);
|
||||
transport.end();
|
||||
});
|
||||
});
|
||||
@@ -1,120 +0,0 @@
|
||||
import { app } from 'electron';
|
||||
import path, { join } from 'node:path';
|
||||
import { getPlatform } from './utils';
|
||||
import { glob } from 'glob';
|
||||
import { spawn } from 'node:child_process';
|
||||
import javaVersionJar from '../resources/java-version/JavaVersion.jar?asset&asarUnpack';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { options } from './cli';
|
||||
|
||||
const javaBin = getPlatform() === 'windows' ? 'java.exe' : 'java';
|
||||
export const CONFIG_IDENTIFIER = 'dev.slimevr.SlimeVR';
|
||||
|
||||
export const getGuiDataFolder = () => {
|
||||
const platform = getPlatform();
|
||||
|
||||
switch (platform) {
|
||||
case 'linux':
|
||||
if (process.env['XDG_DATA_HOME'])
|
||||
return join(process.env['XDG_DATA_HOME'], CONFIG_IDENTIFIER);
|
||||
return join(app.getPath('home'), '.local/share', CONFIG_IDENTIFIER);
|
||||
case 'windows':
|
||||
return join(app.getPath('appData'), CONFIG_IDENTIFIER);
|
||||
case 'macos':
|
||||
return join(
|
||||
app.getPath('home'),
|
||||
'Library/Application Support',
|
||||
CONFIG_IDENTIFIER
|
||||
);
|
||||
case 'unknown':
|
||||
throw 'error';
|
||||
}
|
||||
};
|
||||
|
||||
export const getServerDataFolder = () => {
|
||||
const platform = getPlatform();
|
||||
|
||||
switch (platform) {
|
||||
case 'linux':
|
||||
case 'windows':
|
||||
case 'macos':
|
||||
return join(app.getPath('appData'), CONFIG_IDENTIFIER);
|
||||
case 'unknown':
|
||||
throw 'error';
|
||||
}
|
||||
};
|
||||
|
||||
export const getLogsFolder = () => {
|
||||
return join(getGuiDataFolder(), 'logs');
|
||||
};
|
||||
|
||||
export const getWindowStateFile = () =>
|
||||
join(getServerDataFolder(), '.window-state.json');
|
||||
|
||||
const localJavaBin = (sharedDir: string) => {
|
||||
const jre = join(sharedDir, 'jre/bin', javaBin);
|
||||
return jre;
|
||||
};
|
||||
|
||||
const javaHomeBin = () => {
|
||||
const javaHome = process.env['JAVA_HOME'];
|
||||
if (!javaHome) return null;
|
||||
const javaHomeJre = join(javaHome, 'bin', javaBin);
|
||||
return javaHomeJre;
|
||||
};
|
||||
|
||||
export const findSystemJRE = async (sharedDir: string) => {
|
||||
const paths = [
|
||||
localJavaBin(sharedDir),
|
||||
javaHomeBin(),
|
||||
...(await glob('/usr/lib/jvm/*/bin/' + javaBin)),
|
||||
...(await glob('/Library/Java/JavaVirtualMachines/*/Contents/Home/bin/' + javaBin)),
|
||||
];
|
||||
|
||||
for (const path of paths) {
|
||||
if (!path) continue;
|
||||
|
||||
const version = await new Promise<number | null>((resolve) => {
|
||||
const process = spawn(path, ['-jar', javaVersionJar], {});
|
||||
|
||||
let version: number | null = null;
|
||||
|
||||
process.stdout?.once('data', (data) => {
|
||||
try {
|
||||
version = parseFloat(data.toString());
|
||||
} catch {
|
||||
version = null;
|
||||
}
|
||||
});
|
||||
|
||||
process.on('error', () => {
|
||||
resolve(null);
|
||||
});
|
||||
|
||||
process.on('exit', () => {
|
||||
resolve(version);
|
||||
});
|
||||
});
|
||||
if (version && version >= 17) return path;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const findServerJar = () => {
|
||||
const paths = [
|
||||
options.path ? path.resolve(options.path) : undefined,
|
||||
app.isPackaged ? path.resolve(process.resourcesPath) : undefined,
|
||||
// AppImage passes the fakeroot in `APPDIR` env var.
|
||||
process.env['APPDIR']
|
||||
? path.resolve(join(process.env['APPDIR'], 'usr/share/slimevr/'))
|
||||
: undefined,
|
||||
path.dirname(app.getPath('exe')),
|
||||
// For flatpack container
|
||||
path.resolve('/app/share/slimevr/'),
|
||||
path.resolve('/usr/share/slimevr/'),
|
||||
];
|
||||
return paths
|
||||
.filter((p) => !!p)
|
||||
.map((p) => join(p!, 'slimevr.jar'))
|
||||
.find((p) => existsSync(p));
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Client } from '@xhayper/discord-rpc';
|
||||
import { logger } from './logger';
|
||||
|
||||
export const richPresence = () => {
|
||||
const initialState = () => ({ ready: false, start: Date.now() });
|
||||
|
||||
const state = initialState();
|
||||
|
||||
const client = new Client({
|
||||
clientId: '1237970689009647639',
|
||||
transport: { type: 'ipc' },
|
||||
});
|
||||
client.on('ready', () => {
|
||||
state.ready = true;
|
||||
});
|
||||
|
||||
client.on('disconnected', () => {
|
||||
state.ready = false;
|
||||
});
|
||||
|
||||
return {
|
||||
state,
|
||||
connect: async () => {
|
||||
try {
|
||||
await client.login();
|
||||
} catch (e) {
|
||||
logger.error(e, 'unable to connect to discord rpc');
|
||||
}
|
||||
},
|
||||
updateActivity: (content: string) => {
|
||||
if (!state.ready) return;
|
||||
client.user
|
||||
?.setActivity({
|
||||
state: content,
|
||||
largeImageKey: 'icon',
|
||||
startTimestamp: state.start,
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e, 'unable to update rpc activity');
|
||||
});
|
||||
},
|
||||
destroy: () => {
|
||||
client.destroy();
|
||||
Object.assign(state, initialState());
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const discordPresence = richPresence();
|
||||
@@ -1,76 +0,0 @@
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { logger } from "./logger";
|
||||
import { getGuiDataFolder } from "./paths";
|
||||
|
||||
|
||||
export class CustomStore {
|
||||
private data: Record<string, unknown> = {};
|
||||
private saveTimeout: NodeJS.Timeout | null = null;
|
||||
private filePath: string;
|
||||
private debounceMs: number;
|
||||
|
||||
constructor(filePath: string, debounceMs: number = 2000) {
|
||||
this.filePath = filePath;
|
||||
this.debounceMs = debounceMs;
|
||||
this.load();
|
||||
}
|
||||
|
||||
private load() {
|
||||
try {
|
||||
if (existsSync(this.filePath)) {
|
||||
const raw = readFileSync(this.filePath, 'utf-8');
|
||||
this.data = JSON.parse(raw);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err, `Failed to load store at ${this.filePath}`);
|
||||
this.data = {};
|
||||
}
|
||||
}
|
||||
|
||||
/** Set a key and trigger the debounced auto-save */
|
||||
set(key: string, value: unknown) {
|
||||
this.data[key] = value;
|
||||
this.triggerAutoSave();
|
||||
}
|
||||
|
||||
get<T>(key: string): T | undefined {
|
||||
return this.data[key] as T;
|
||||
}
|
||||
|
||||
delete(key: string): boolean {
|
||||
if (key in this.data) {
|
||||
delete this.data[key];
|
||||
this.triggerAutoSave();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private triggerAutoSave() {
|
||||
if (this.saveTimeout) clearTimeout(this.saveTimeout);
|
||||
this.saveTimeout = setTimeout(() => {
|
||||
this.save();
|
||||
}, this.debounceMs);
|
||||
}
|
||||
|
||||
save(): boolean {
|
||||
try {
|
||||
if (this.saveTimeout) clearTimeout(this.saveTimeout);
|
||||
|
||||
const dir = dirname(this.filePath);
|
||||
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
||||
|
||||
writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), 'utf-8');
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error(err, 'Save failed', this.filePath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const stores = {
|
||||
settings: new CustomStore(join(getGuiDataFolder(), 'gui-settings.dat'), 1000),
|
||||
cache: new CustomStore(join(getGuiDataFolder(), 'gui-cache.dat'), 100),
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
import os from 'os'
|
||||
import { OSStats } from "../preload/interface";
|
||||
import { ipcMain, IpcMainInvokeEvent } from 'electron';
|
||||
import { IpcInvokeMap } from '../shared';
|
||||
import net from 'net'
|
||||
|
||||
export const getPlatform = (): OSStats['type'] => {
|
||||
switch (os.platform()) {
|
||||
case 'darwin':
|
||||
return 'macos';
|
||||
case 'win32':
|
||||
return 'windows';
|
||||
case 'linux':
|
||||
return 'linux';
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
};
|
||||
|
||||
export const isPortAvailable = (port: number) => {
|
||||
return new Promise((resolve) => {
|
||||
const s = net.createServer();
|
||||
s.once('error', (err) => {
|
||||
s.close();
|
||||
if ("code" in err && err["code"] == "EADDRINUSE") {
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
s.once('listening', () => {
|
||||
resolve(true);
|
||||
s.close();
|
||||
});
|
||||
s.listen(port);
|
||||
});
|
||||
};
|
||||
|
||||
export function handleIpc<K extends keyof IpcInvokeMap>(
|
||||
channel: K,
|
||||
handler: (
|
||||
event: IpcMainInvokeEvent,
|
||||
...args: Parameters<IpcInvokeMap[K]>
|
||||
) => ReturnType<IpcInvokeMap[K]>
|
||||
) {
|
||||
ipcMain.handle(channel, (event, ...args) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return handler(event, ...args as any);
|
||||
});
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
|
||||
import { IElectronAPI, ServerStatusEvent } from './interface';
|
||||
import { IPC_CHANNELS } from '../shared';
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
onServerStatus: (callback) => {
|
||||
const subscription = (_event: IpcRendererEvent, value: ServerStatusEvent) =>
|
||||
callback(value);
|
||||
ipcRenderer.on(IPC_CHANNELS.SERVER_STATUS, subscription);
|
||||
return () => ipcRenderer.removeListener(IPC_CHANNELS.SERVER_STATUS, subscription);
|
||||
},
|
||||
openUrl: (url) => ipcRenderer.invoke(IPC_CHANNELS.OPEN_URL, url),
|
||||
osStats: () => ipcRenderer.invoke(IPC_CHANNELS.OS_STATS),
|
||||
close: () => ipcRenderer.invoke(IPC_CHANNELS.WINDOW_ACTIONS, 'close'),
|
||||
hide: () => ipcRenderer.invoke(IPC_CHANNELS.WINDOW_ACTIONS, 'hide'),
|
||||
minimize: () => ipcRenderer.invoke(IPC_CHANNELS.WINDOW_ACTIONS, 'minimize'),
|
||||
maximize: () => ipcRenderer.invoke(IPC_CHANNELS.WINDOW_ACTIONS, 'maximize'),
|
||||
getStorage: async (type) => {
|
||||
return {
|
||||
get: (key) =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.STORAGE, { type, method: 'get', key }),
|
||||
set: (key, value) =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.STORAGE, { type, method: 'set', key, value }),
|
||||
delete: (key) =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.STORAGE, { type, method: 'delete', key }),
|
||||
save: () => ipcRenderer.invoke(IPC_CHANNELS.STORAGE, { type, method: 'save' }),
|
||||
};
|
||||
},
|
||||
log: (type, ...args) => ipcRenderer.invoke(IPC_CHANNELS.LOG, type, ...args),
|
||||
i18nOverride: async () => ipcRenderer.invoke(IPC_CHANNELS.I18N_OVERRIDE),
|
||||
showDecorations: () => {},
|
||||
setTranslations: () => {},
|
||||
openDialog: (options) => ipcRenderer.invoke(IPC_CHANNELS.OPEN_DIALOG, options),
|
||||
saveDialog: (options) => ipcRenderer.invoke(IPC_CHANNELS.SAVE_DIALOG, options),
|
||||
openConfigFolder: async () => ipcRenderer.invoke(IPC_CHANNELS.OPEN_FILE, await ipcRenderer.invoke(IPC_CHANNELS.GET_FOLDER, 'config')),
|
||||
openLogsFolder: async () => ipcRenderer.invoke(IPC_CHANNELS.OPEN_FILE, await ipcRenderer.invoke(IPC_CHANNELS.GET_FOLDER, 'logs')),
|
||||
openFile: (path) => ipcRenderer.invoke(IPC_CHANNELS.OPEN_FILE, path),
|
||||
ghGet: (req) => ipcRenderer.invoke(IPC_CHANNELS.GH_FETCH, req),
|
||||
setPresence: (options) => ipcRenderer.invoke(IPC_CHANNELS.DISCORD_PRESENCE, options)
|
||||
} satisfies IElectronAPI);
|
||||
65
gui/electron/preload/interface.d.ts
vendored
@@ -1,65 +0,0 @@
|
||||
import {
|
||||
OpenDialogOptions,
|
||||
OpenDialogReturnValue,
|
||||
SaveDialogOptions,
|
||||
SaveDialogReturnValue,
|
||||
} from 'electron';
|
||||
|
||||
export type ServerStatusEvent = {
|
||||
type: 'stdout' | 'stderr' | 'error' | 'terminated' | 'other';
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type OSStats = {
|
||||
type: 'linux' | 'windows' | 'macos' | 'unknown';
|
||||
};
|
||||
|
||||
export interface CrossStorage {
|
||||
set(key: string, value: unknown): Promise<void>;
|
||||
get<T>(key: string): Promise<T | undefined>;
|
||||
delete(key: string): Promise<boolean>;
|
||||
save(): Promise<boolean>;
|
||||
}
|
||||
|
||||
export type GHGet = { type: 'fw-releases' } | { type: 'asset'; url: string };
|
||||
export type GHReturn = {
|
||||
asset: [number, string][] | null;
|
||||
['fw-releases']:
|
||||
| {
|
||||
assets: { browser_download_url: string; name: string; digest: string }[];
|
||||
prerelease: boolean;
|
||||
tag_name: string;
|
||||
body: string;
|
||||
}[]
|
||||
| null;
|
||||
};
|
||||
|
||||
export type DiscordPresence = { enable: false } | { enable: true, activity: string }
|
||||
|
||||
export interface IElectronAPI {
|
||||
onServerStatus: (cb: (data: ServerStatusEvent) => void) => () => void;
|
||||
openUrl: (url: string) => Promise<void>;
|
||||
osStats: () => Promise<OSStats>;
|
||||
openLogsFolder: () => Promise<void>;
|
||||
openConfigFolder: () => Promise<void>;
|
||||
close: () => void;
|
||||
hide: () => void;
|
||||
minimize: () => void;
|
||||
maximize: () => void;
|
||||
showDecorations: (decorations: boolean) => void;
|
||||
setTranslations: (translations: Record<string, string>) => void;
|
||||
i18nOverride: () => Promise<string | false>;
|
||||
getStorage: (type: 'settings' | 'cache') => Promise<CrossStorage>;
|
||||
openDialog: (options: OpenDialogOptions) => Promise<OpenDialogReturnValue>;
|
||||
saveDialog: (options: SaveDialogOptions) => Promise<SaveDialogReturnValue>;
|
||||
log: (type: 'info' | 'error' | 'warn', ...args: unknown[]) => void;
|
||||
openFile: (path: string) => void;
|
||||
ghGet: <T extends GHGet>(options: T) => Promise<GHReturn[T['type']]>;
|
||||
setPresence: (options: DiscordPresence) => void;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: IElectronAPI;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
# Copyright 2025 Eiren Rain and SlimeVR Contributors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
## QinHeng
|
||||
# CH340
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1A86", ATTRS{idProduct}=="7522", MODE="0660", TAG+="uaccess"
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1A86", ATTRS{idProduct}=="7523", MODE="0660", TAG+="uaccess"
|
||||
# CH341
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1A86", ATTRS{idProduct}=="5523", MODE="0660", TAG+="uaccess"
|
||||
# CH343
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1A86", ATTRS{idProduct}=="55D3", MODE="0660", TAG+="uaccess"
|
||||
# CH9102x
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1A86", ATTRS{idProduct}=="55D4", MODE="0660", TAG+="uaccess"
|
||||
|
||||
## Silabs
|
||||
# CP210x
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="10C4", ATTRS{idProduct}=="EA60", MODE="0660", TAG+="uaccess"
|
||||
|
||||
## Espressif
|
||||
# ESP32-S3 / ESP32-C3 / ESP32-C5 / ESP32-C6 / ESP32-C61 / ESP32-H2 / ESP32-P4
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="303A", ATTRS{idProduct}=="1001", MODE="0660", TAG+="uaccess"
|
||||
# ESP32-S2
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="303A", ATTRS{idProduct}=="0002", MODE="0660", TAG+="uaccess"
|
||||
|
||||
## FTDI
|
||||
# FT232BM/L/Q, FT245BM/L/Q
|
||||
# FT232RL/Q, FT245RL/Q
|
||||
# VNC1L with VDPS Firmware
|
||||
# VNC2 with FT232Slave
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0660", TAG+="uaccess"
|
||||
|
||||
## SlimeVR
|
||||
# smol slime dongle
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="7690", MODE="0660", TAG+="uaccess"
|
||||
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="7690", MODE="0660", TAG+="uaccess"
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 747 B |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 698 B |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 1008 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 922 B |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" stroke-miterlimit="10" clip-rule="evenodd" version="1.1" viewBox="0 0 380 380" xml:space="preserve"><rect id="bg" width="380" height="380" fill="#663499" stroke-width="1" rx="76" /><g id="logo" fill="none" stroke="#fff"><path id="left" stroke-width="13.62" d="m72.867 191.74 37-39 39 36"/><path id="right" stroke-width="13.62" d="m208.87 187.74 38-35 36 38"/><path id="outer" stroke-linecap="square" stroke-width="17" d="m56.867 253.74s130.61-31.182 248 5c13.45 4.146 20.244 2.975 20-8s1.909-126.06-46-131"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 579 B |
|
Before Width: | Height: | Size: 555 B |
|
Before Width: | Height: | Size: 848 B |
|
Before Width: | Height: | Size: 848 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 645 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 848 B |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
SRC="/opt/SlimeVR/69-slimevr-devices.rules"
|
||||
DESTDIRS=("/lib" "/usr/lib")
|
||||
|
||||
if [[ ! -f "$SRC" ]]; then
|
||||
echo "SlimeVR udev rules not found, serial console and dongles may not work" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Configuring SlimeVR udev rules..."
|
||||
|
||||
for DIR in "${DESTDIRS[@]}"; do
|
||||
if [[ -d "$DIR" && ! -h "$DIR" ]]; then
|
||||
echo "Copying rules to $DIR"
|
||||
install -Dm644 "$SRC" "$DIR/udev/rules.d/69-slimevr-devices.rules"
|
||||
|
||||
if command -v udevadm >/dev/null 2>&1; then
|
||||
udevadm control --reload-rules
|
||||
udevadm trigger
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Couldn't copy SlimeVR udev rules, serial console and dongles may not work" >&2
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
echo "Removing SlimeVR udev rules..."
|
||||
rm -f "/lib/udev/rules.d/69-slimevr-devices.rules"
|
||||
rm -f "/usr/lib/udev/rules.d/69-slimevr-devices.rules"
|
||||
|
||||
if command -v udevadm >/dev/null 2>&1; then
|
||||
udevadm control --reload-rules
|
||||
fi
|
||||
@@ -1,49 +0,0 @@
|
||||
import {
|
||||
OpenDialogOptions,
|
||||
OpenDialogReturnValue,
|
||||
SaveDialogOptions,
|
||||
SaveDialogReturnValue,
|
||||
} from 'electron';
|
||||
import { DiscordPresence, GHGet, GHReturn, OSStats } from './preload/interface';
|
||||
|
||||
export const IPC_CHANNELS = {
|
||||
SERVER_STATUS: 'server-status',
|
||||
OPEN_URL: 'open-url',
|
||||
OS_STATS: 'os-stats',
|
||||
WINDOW_ACTIONS: 'window-actions',
|
||||
LOG: 'log',
|
||||
STORAGE: 'storage',
|
||||
OPEN_DIALOG: 'open-dialog',
|
||||
SAVE_DIALOG: 'save-dialog',
|
||||
I18N_OVERRIDE: 'i18n-override',
|
||||
OPEN_FILE: 'open-file',
|
||||
GET_FOLDER: 'get-folder',
|
||||
GH_FETCH: 'gh-fetch',
|
||||
DISCORD_PRESENCE: 'discord-presence'
|
||||
} as const;
|
||||
|
||||
export interface IpcInvokeMap {
|
||||
[IPC_CHANNELS.OPEN_URL]: (url: string) => void;
|
||||
[IPC_CHANNELS.OS_STATS]: () => Promise<OSStats>;
|
||||
[IPC_CHANNELS.WINDOW_ACTIONS]: (action: 'close' | 'minimize' | 'maximize' | 'hide') => void;
|
||||
[IPC_CHANNELS.LOG]: (type: 'info' | 'error' | 'warn', ...args: unknown[]) => void;
|
||||
[IPC_CHANNELS.OPEN_DIALOG]: (
|
||||
options: OpenDialogOptions
|
||||
) => Promise<OpenDialogReturnValue>;
|
||||
[IPC_CHANNELS.SAVE_DIALOG]: (
|
||||
options: SaveDialogOptions
|
||||
) => Promise<SaveDialogReturnValue>;
|
||||
[IPC_CHANNELS.I18N_OVERRIDE]: () => Promise<string | false>;
|
||||
[IPC_CHANNELS.STORAGE]: (args: {
|
||||
type: 'settings' | 'cache';
|
||||
method: 'get' | 'set' | 'delete' | 'save';
|
||||
key?: string;
|
||||
value?: unknown;
|
||||
}) => Promise<unknown>;
|
||||
[IPC_CHANNELS.OPEN_FILE]: (path: string) => void;
|
||||
[IPC_CHANNELS.GET_FOLDER]: (folder: 'config' | 'logs') => string;
|
||||
[IPC_CHANNELS.GH_FETCH]: <T extends GHGet>(
|
||||
options: T
|
||||
) => Promise<GHReturn[T['type']]>;
|
||||
[IPC_CHANNELS.DISCORD_PRESENCE]: (options: DiscordPresence) => void;
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import { FlatCompat } from '@eslint/eslintrc';
|
||||
import eslint from '@eslint/js';
|
||||
import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import stylistic from '@stylistic/eslint-plugin';
|
||||
|
||||
const compat = new FlatCompat();
|
||||
|
||||
export const gui = [
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
...compat.extends('plugin:@dword-design/import-alias/recommended'),
|
||||
...compat.plugins('eslint-plugin-react-hooks'),
|
||||
// Add import-alias rule inside compat because plugin doesn't like flat configs
|
||||
...compat.config({
|
||||
rules: {
|
||||
'@dword-design/import-alias/prefer-alias': [
|
||||
'error',
|
||||
{
|
||||
alias: {
|
||||
'@': './src/',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
parser: tseslint.parser,
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.jest,
|
||||
},
|
||||
},
|
||||
files: ['src/**/*.{js,jsx,ts,tsx,json}'],
|
||||
plugins: {
|
||||
'@typescript-eslint': tseslint.plugin,
|
||||
'@stylistic': stylistic,
|
||||
},
|
||||
rules: {
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'react/prop-types': 'off',
|
||||
'spaced-comment': 'error',
|
||||
quotes: ['error', 'single'],
|
||||
'no-duplicate-imports': 'error',
|
||||
'no-inline-styles': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'react/no-unescaped-entities': 'off',
|
||||
camelcase: 'error',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
ignoreRestSiblings: true,
|
||||
},
|
||||
],
|
||||
'@stylistic/jsx-self-closing-comp': 'error',
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {},
|
||||
},
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
},
|
||||
// Global ignore
|
||||
{
|
||||
ignores: ['**/firmware-tool-api/'],
|
||||
},
|
||||
];
|
||||
|
||||
export default gui;
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<title>SlimeVR GUI</title>
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import {
|
||||
generateSchemaTypes,
|
||||
generateReactQueryComponents,
|
||||
} from '@openapi-codegen/typescript';
|
||||
import { defineConfig } from '@openapi-codegen/cli';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config()
|
||||
|
||||
export default defineConfig({
|
||||
firmwareTool: {
|
||||
from: {
|
||||
source: 'url',
|
||||
url: process.env.FIRMWARE_TOOL_SCHEMA_URL ?? 'http://localhost:3000/api-json',
|
||||
},
|
||||
outputDir: 'src/firmware-tool-api',
|
||||
to: async (context) => {
|
||||
const filenamePrefix = 'firmwareTool';
|
||||
const { schemasFiles } = await generateSchemaTypes(context, {
|
||||
filenamePrefix,
|
||||
});
|
||||
await generateReactQueryComponents(context, {
|
||||
filenamePrefix,
|
||||
schemasFiles,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
191
gui/package.json
@@ -1,113 +1,94 @@
|
||||
{
|
||||
"name": "slimevr",
|
||||
"version": "0.0.0",
|
||||
"author": "SlimeVR Team <contact@slimevr.dev>",
|
||||
"homepage": "https://slimevr.dev",
|
||||
"type": "module",
|
||||
"name": "slimevr-ui",
|
||||
"version": "0.5.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ryuziii/discord-rpc": "1.0.1-rc.1",
|
||||
"@xhayper/discord-rpc": "^1.3.0",
|
||||
"commander": "^14.0.3",
|
||||
"discord-rich-presence": "^0.0.8",
|
||||
"glob": "^13.0.3",
|
||||
"open": "^11.0.0",
|
||||
"pino": "^10.3.1",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"pino-roll": "^4.0.0"
|
||||
"@fluent/bundle": "^0.17.1",
|
||||
"@fluent/react": "^0.14.1",
|
||||
"@fontsource/poppins": "^4.5.8",
|
||||
"@formatjs/intl-localematcher": "^0.2.32",
|
||||
"@react-three/fiber": "^8.10.0",
|
||||
"@tauri-apps/api": "^1.4.0",
|
||||
"@vitejs/plugin-react": "^3.0.0",
|
||||
"browserslist": "^4.18.1",
|
||||
"classnames": "^2.3.1",
|
||||
"eslint-config-react-app": "^7.0.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"flatbuffers": "^22.10.26",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"intl-pluralrules": "^1.3.1",
|
||||
"ip-num": "^1.4.1",
|
||||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-normalize": "^10.0.1",
|
||||
"postcss-preset-env": "^7.0.1",
|
||||
"prompts": "^2.4.2",
|
||||
"react": "^18.0.0",
|
||||
"react-dev-utils": "^12.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-hook-form": "^7.29.0",
|
||||
"react-modal": "3.15.1",
|
||||
"react-responsive": "^9.0.2",
|
||||
"react-router-dom": "^6.2.2",
|
||||
"semver": "^7.5.3",
|
||||
"solarxr-protocol": "file:../solarxr-protocol",
|
||||
"three": "^0.148.0",
|
||||
"ts-pattern": "^5.0.1",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite --force",
|
||||
"gui": "electron-vite dev --config electron.vite.config.ts --watch",
|
||||
"build": "electron-vite build --config electron.vite.config.ts",
|
||||
"package": "electron-builder",
|
||||
"package:build": "pnpm build && pnpm package",
|
||||
"preview": "electron-vite preview --config electron.vite.config.ts",
|
||||
"skipbundler": "vite build",
|
||||
"lint": "tsc --noEmit && eslint --max-warnings=0 \"src/**/*.{js,jsx,ts,tsx,json}\" && prettier --check \"src/**/*.{js,jsx,ts,tsx,css,scss,md,json}\"",
|
||||
"lint:fix": "tsc --noEmit && eslint --fix --max-warnings=0 \"src/**/*.{js,jsx,ts,tsx,json}\" && pnpm run format",
|
||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css,scss,md,json}\"",
|
||||
"javaversion-build": "cd electron/resources/java-version/ && javac JavaVersion.java && jar cvfe JavaVersion.jar JavaVersion JavaVersion.class",
|
||||
"gen:javaversion": "cd electron/resources/java-version/ && javac JavaVersion.java && jar cvfe JavaVersion.jar JavaVersion JavaVersion.class",
|
||||
"gen:firmware-tool": "openapi-codegen gen firmwareTool"
|
||||
"build": "vite build",
|
||||
"dev": "tauri dev",
|
||||
"skipbundler": "tauri build -b none",
|
||||
"tauri": "tauri",
|
||||
"lint": "eslint --max-warnings=0 \"src/**/*.{js,jsx,ts,tsx,json}\" && prettier --check \"src/**/*.{js,jsx,ts,tsx,css,md,json}\"",
|
||||
"lint:fix": "eslint --fix \"src/**/*.{js,jsx,ts,tsx,json}\"",
|
||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css,md,json}\"",
|
||||
"preview-vite": "vite preview",
|
||||
"javaversion-build": "cd src-tauri/src/ && javac JavaVersion.java && jar cvfe JavaVersion.jar JavaVersion JavaVersion.class"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dword-design/eslint-plugin-import-alias": "^4.0.9",
|
||||
"@electron/asar": "^4.0.1",
|
||||
"@fluent/bundle": "^0.18.0",
|
||||
"@fluent/react": "^0.15.2",
|
||||
"@fontsource/poppins": "^5.1.0",
|
||||
"@formatjs/intl-localematcher": "^0.2.32",
|
||||
"@hookform/resolvers": "^3.6.0",
|
||||
"@openapi-codegen/cli": "^3.1.0",
|
||||
"@openapi-codegen/typescript": "^8.0.2",
|
||||
"@react-hookz/deep-equal": "^3.0.3",
|
||||
"@sentry/react": "10.29.0",
|
||||
"@sentry/vite-plugin": "^2.22.7",
|
||||
"@stylistic/eslint-plugin": "^5.5.0",
|
||||
"@tailwindcss/forms": "^0.5.9",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tanstack/react-query": "^5.48.0",
|
||||
"@tweenjs/tween.js": "^25.0.0",
|
||||
"@twemoji/svg": "^15.0.0",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/node": "^24.3.1",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-helmet": "^6.1.11",
|
||||
"@types/react-modal": "3.16.3",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/three": "^0.163.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
||||
"@typescript-eslint/parser": "^8.48.1",
|
||||
"@vitejs/plugin-react": "^4.3.2",
|
||||
"ajv": "^8.17.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"browser-fs-access": "^0.35.0",
|
||||
"classnames": "^2.5.1",
|
||||
"convert": "^5.12.0",
|
||||
"dmg-license": "^1.0.11",
|
||||
"dotenv": "^16.4.5",
|
||||
"electron": "^40.3.0",
|
||||
"electron-builder": "^26.7.0",
|
||||
"electron-vite": "^5.0.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-import-resolver-typescript": "^3.10.1",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"flatbuffers": "22.10.26",
|
||||
"globals": "^15.10.0",
|
||||
"intl-pluralrules": "^2.0.1",
|
||||
"ip-num": "^1.5.1",
|
||||
"jotai": "^2.12.2",
|
||||
"prettier": "^3.3.3",
|
||||
"prompts": "^2.4.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-hook-form": "^7.63.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-responsive": "^10.0.0",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.79.4",
|
||||
"semver": "^7.6.3",
|
||||
"solarxr-protocol": "file:../solarxr-protocol",
|
||||
"spdx-satisfies": "^5.0.1",
|
||||
"tailwind-gradient-mask-image": "^1.2.0",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"three": "^0.163.0",
|
||||
"ts-pattern": "^5.4.0",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.46.2",
|
||||
"use-double-tap": "^1.3.6",
|
||||
"uuid": "^13.0.0",
|
||||
"vite": "^5.4.8",
|
||||
"yup": "^1.4.0"
|
||||
},
|
||||
"main": "./out/main/index.js"
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tauri-apps/cli": "^1.4.0",
|
||||
"@types/file-saver": "^2.0.5",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/react-modal": "3.13.1",
|
||||
"@types/three": "^0.148.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
||||
"@typescript-eslint/parser": "^5.60.1",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-import-resolver-typescript": "^3.5.5",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"postcss": "^8.4.24",
|
||||
"prettier": "^2.8.8",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"tailwind-gradient-mask-image": "^1.0.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"vite": "^4.3.9"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 3.8 KiB |
BIN
gui/public/fonts/NotoSansCJK-VF.otf.woff2
Normal file
BIN
gui/public/fonts/twemoji-glyf_colr_1.woff2
Normal file
BIN
gui/public/fonts/twemoji-picosvg.woff2
Normal file
@@ -7,13 +7,8 @@
|
||||
|
||||
## Websocket (server) status
|
||||
|
||||
websocket-connecting = جاري التحميل...
|
||||
websocket-connection_lost = تعطل الخادم!
|
||||
websocket-connection_lost-desc = يبدو أن خادم SlimeVR تعطل. تحقق من السجلات وأعد تشغيل البرنامج
|
||||
websocket-timedout = تعذر الاتصال بالخادم
|
||||
websocket-timedout-desc = يبدو أن خادم SlimeVR قد تعطل أو انتهت مهلته. تحقق من السجلات وأعد تشغيل البرنامج
|
||||
websocket-error-close = الخروج من SlimeVR
|
||||
websocket-error-logs = افتح مجلد السجلات
|
||||
websocket-connecting = جاري التوصيل بالسيرفر
|
||||
websocket-connection_lost = انقطع الاتصال بالسيرفر. يتم إعادة التوصيل...
|
||||
|
||||
## Update notification
|
||||
|
||||
@@ -28,11 +23,6 @@ tips-find_tracker = لست متأكد من أجهزة التعقب؟ قم بتح
|
||||
tips-do_not_move_heels = يرجى عدم تحريك كاحليك أثناء التسجيل!
|
||||
tips-file_select = اسحب الملفات وأفلتها لاستخدامها أو <u> تصفح </ u>
|
||||
tips-tap_setup = يمكنك النقر ببطء مرتين على جهاز التعقب لاختياره بدلاً من تحديده من القائمة.
|
||||
tips-turn_on_tracker = هل تستخدم أجهزة تعقب SlimeVR الرسمية؟ تذكر <b><em> أن تشغل أجهزة التعقب </em></b> بعد توصيلها بالكمبيوتر!
|
||||
tips-failed_webgl = فشل تهيئة WebGL.
|
||||
|
||||
## Units
|
||||
|
||||
|
||||
## Body parts
|
||||
|
||||
@@ -57,72 +47,12 @@ body_part-LEFT_HAND = اليد اليسرى
|
||||
body_part-LEFT_UPPER_LEG = الفخذ الأيسر
|
||||
body_part-LEFT_LOWER_LEG = الكاحل الأيسر
|
||||
body_part-LEFT_FOOT = القدم اليسرى
|
||||
body_part-LEFT_THUMB_METACARPAL = عظم مشط الإبهام الأيسر
|
||||
body_part-LEFT_THUMB_PROXIMAL = الإبهام الأيسر القريب
|
||||
body_part-LEFT_THUMB_DISTAL = الإبهام الأيسر البعيد
|
||||
body_part-LEFT_INDEX_PROXIMAL = السبابة اليسرى القريبة
|
||||
body_part-LEFT_INDEX_INTERMEDIATE = السبابة اليسرى المتوسطة
|
||||
body_part-LEFT_INDEX_DISTAL = السبابة اليسرى البعيدة
|
||||
body_part-LEFT_MIDDLE_PROXIMAL = الجزء الأوسط الأيسر القريب
|
||||
body_part-LEFT_MIDDLE_INTERMEDIATE = الوسط الأيسر المتوسط
|
||||
body_part-LEFT_MIDDLE_DISTAL = الجزء الأوسط الأيسر البعيد
|
||||
body_part-LEFT_RING_PROXIMAL = الحلقة اليسرى القريبة
|
||||
body_part-LEFT_RING_INTERMEDIATE = الحلقة اليسرى المتوسطة
|
||||
body_part-LEFT_RING_DISTAL = الحلقة اليسرى البعيدة
|
||||
body_part-LEFT_LITTLE_PROXIMAL = الجزء الأيسر الصغير القريب
|
||||
body_part-LEFT_LITTLE_INTERMEDIATE = اليسار الصغير المتوسط
|
||||
body_part-LEFT_LITTLE_DISTAL = الجزء البعيد الأيسر الصغير
|
||||
body_part-RIGHT_THUMB_METACARPAL = مشط الإبهام الأيمن
|
||||
body_part-RIGHT_THUMB_PROXIMAL = الإبهام الأيمن القريب
|
||||
body_part-RIGHT_THUMB_DISTAL = الإبهام الأيمن البعيد
|
||||
body_part-RIGHT_INDEX_PROXIMAL = السبابة اليمنى القريبة
|
||||
body_part-RIGHT_INDEX_INTERMEDIATE = السبابة اليمنى المتوسطة
|
||||
body_part-RIGHT_INDEX_DISTAL = السبابة اليمنى البعيدة
|
||||
body_part-RIGHT_MIDDLE_PROXIMAL = منتصف اليمين القريب
|
||||
body_part-RIGHT_MIDDLE_INTERMEDIATE = الوسط الأيمن المتوسط
|
||||
body_part-RIGHT_MIDDLE_DISTAL = منتصف اليمين البعيد
|
||||
body_part-RIGHT_RING_PROXIMAL = الحلقة اليمنى القريبة
|
||||
body_part-RIGHT_RING_INTERMEDIATE = الحلقة اليمنى المتوسطة
|
||||
body_part-RIGHT_RING_DISTAL = الحلقة اليمنى البعيدة
|
||||
body_part-RIGHT_LITTLE_PROXIMAL = الجزء القريب الأيمن الصغير
|
||||
body_part-RIGHT_LITTLE_INTERMEDIATE = المتوسط الصغير الأيمن
|
||||
body_part-RIGHT_LITTLE_DISTAL = الجزء البعيد الصغير الأيمن
|
||||
|
||||
## BoardType
|
||||
|
||||
board_type-UNKNOWN = مجهول
|
||||
board_type-NODEMCU = NodeMCU
|
||||
board_type-CUSTOM = لوحة مخصصة
|
||||
board_type-WROOM32 = WROOM32
|
||||
board_type-WEMOSD1MINI = Wemos D1 Mini
|
||||
board_type-TTGO_TBASE = TTGO T-Base
|
||||
board_type-ESP01 = ESP-01
|
||||
board_type-SLIMEVR = سلايم في آر
|
||||
board_type-LOLIN_C3_MINI = Lolin C3 Mini
|
||||
board_type-BEETLE32C3 = Beetle ESP32-C3
|
||||
board_type-ESP32C3DEVKITM1 = Espressif ESP32-C3 DevKitM-1
|
||||
board_type-OWOTRACK = owoTrack
|
||||
board_type-WRANGLER = رانجلر جويكونز
|
||||
board_type-MOCOPI = سوني موكوبي (Mocopi)
|
||||
board_type-WEMOSWROOM02 = Wemos Wroom-02 D1 Mini
|
||||
board_type-XIAO_ESP32C3 = Seeed Studio XIAO ESP32C3
|
||||
board_type-HARITORA = Haritora
|
||||
board_type-ESP32C6DEVKITC1 = Espressif ESP32-C6 DevKitC-1
|
||||
board_type-GLOVE_IMU_SLIMEVR_DEV = قفاز SlimeVR تطوير IMU
|
||||
|
||||
## Proportions
|
||||
|
||||
skeleton_bone-NONE = غير محدد
|
||||
skeleton_bone-HEAD = إمالة الرأس
|
||||
skeleton_bone-HEAD-desc =
|
||||
هذه هي المسافة من سماعة الرأس إلى منتصف رأسك.
|
||||
لضبط المسافة ، هز رأسك من اليسار إلى اليمين كما لو كنت لا توافق وقم بتعديله
|
||||
حتى أي حركة في أجهزة التتبع الأخرى لا تكاد تذكر.
|
||||
skeleton_bone-NECK = طول العنق
|
||||
skeleton_bone-NECK-desc =
|
||||
هذه هي المسافة من منتصف رأسك إلى قاعدة رقبتك.
|
||||
لضبطها ، حرك رأسك لأعلى ولأسفل كما لو كنت تومئ رأسك أو تميل رأسك
|
||||
إلى اليسار واليمين وقم بتعديله حتى تصبح أي حركة في أجهزة التتبع الأخرى ضئيلة.
|
||||
skeleton_bone-torso_group = طول الجذع
|
||||
skeleton_bone-UPPER_CHEST = طول أعلى الصدر
|
||||
skeleton_bone-CHEST_OFFSET = درجة تشريد الصدر
|
||||
@@ -149,14 +79,6 @@ skeleton_bone-ELBOW_OFFSET = درجة تشريد الكوع
|
||||
## Tracker reset buttons
|
||||
|
||||
reset-reset_all = إعادة تعيين جميع النسب
|
||||
reset-reset_all_warning-v2 =
|
||||
<b>تحذير:</b> ستتم إعادة تعيين النسب إلى الإعدادات الافتراضية التي تم تحجيمها إلى الارتفاع الذي تم تكوينه.
|
||||
هل أنت متأكد من أنك تريد القيام بذلك؟
|
||||
reset-reset_all_warning-reset = إعادة تعيين النسب
|
||||
reset-reset_all_warning-cancel = إلغاء
|
||||
reset-reset_all_warning_default-v2 =
|
||||
<b>تحذير:</b> لم يتم ضبط طولك، وسيتم إعادة تعيين نسبك إلى الإعدادات الافتراضية مع الارتفاع الافتراضي.
|
||||
هل أنت متأكد من أنك تريد القيام بذلك؟
|
||||
reset-full = اعاده تعيين
|
||||
reset-mounting = إعادة تعيين التركيب
|
||||
reset-yaw = إعادة تعيين الانعراج
|
||||
@@ -200,10 +122,6 @@ widget-overlay-is_mirrored_label = عكس تراكب الشاشة
|
||||
|
||||
widget-drift_compensation-clear = حذف تعويض الانجراف
|
||||
|
||||
## Widget: Clear Mounting calibration
|
||||
|
||||
widget-clear_mounting = مسح إعادة تعيين التركيب
|
||||
|
||||
## Widget: Developer settings
|
||||
|
||||
widget-developer_mode = وضع المطوّر
|
||||
@@ -218,17 +136,9 @@ widget-developer_mode-more_info = المزيد
|
||||
## Widget: IMU Visualizer
|
||||
|
||||
widget-imu_visualizer = دوران
|
||||
widget-imu_visualizer-preview = عرض
|
||||
widget-imu_visualizer-hide = إخفاء
|
||||
widget-imu_visualizer-rotation_raw = صافي
|
||||
widget-imu_visualizer-rotation_preview = عرض مسبق
|
||||
widget-imu_visualizer-acceleration = التسارع
|
||||
widget-imu_visualizer-position = الموضع
|
||||
|
||||
## Widget: Skeleton Visualizer
|
||||
|
||||
widget-skeleton_visualizer-preview = إظهار الهيكل العظمي
|
||||
widget-skeleton_visualizer-hide = إخفاء
|
||||
widget-imu_visualizer-rotation_hide = إخفاء
|
||||
|
||||
## Tracker status
|
||||
|
||||
@@ -238,7 +148,6 @@ tracker-status-error = خطأ
|
||||
tracker-status-disconnected = فقد الاتصال
|
||||
tracker-status-occluded = محجوب
|
||||
tracker-status-ok = حسنًا
|
||||
tracker-status-timed_out = انتهت المهله
|
||||
|
||||
## Tracker status columns
|
||||
|
||||
@@ -256,15 +165,9 @@ tracker-table-column-url = عنوان URL
|
||||
## Tracker rotation
|
||||
|
||||
tracker-rotation-front = المقدمة
|
||||
tracker-rotation-front_left = أمامي-يسار
|
||||
tracker-rotation-front_right = أمامي -يمين
|
||||
tracker-rotation-left = اليسار
|
||||
tracker-rotation-right = اليمين
|
||||
tracker-rotation-back = الخلف
|
||||
tracker-rotation-back_left = الخلف اليسار
|
||||
tracker-rotation-back_right = الخلف الأيمن
|
||||
tracker-rotation-custom = مخصص
|
||||
tracker-rotation-overriden = (تم تجاوزه عن طريق إعادة الضبط المتصاعد)
|
||||
|
||||
## Tracker information
|
||||
|
||||
@@ -275,17 +178,8 @@ tracker-infos-url = عنوان URL لجهاز التعقب
|
||||
tracker-infos-version = إصدار البرنامج الثابت
|
||||
tracker-infos-hardware_rev = مراجعة الأجهزة
|
||||
tracker-infos-hardware_identifier = معرف الجهاز
|
||||
tracker-infos-data_support = دعم البيانات
|
||||
tracker-infos-imu = مستشعر IMU
|
||||
tracker-infos-board_type = اللوحة الرئيسية
|
||||
tracker-infos-network_version = نسخة البروتوكول
|
||||
tracker-infos-magnetometer = المقياس المغناطيسي
|
||||
tracker-infos-magnetometer-status-v1 =
|
||||
{ $status ->
|
||||
[DISABLED] ملغي
|
||||
[ENABLED] فعال
|
||||
*[NOT_SUPPORTED] غير مدعوم
|
||||
}
|
||||
|
||||
## Tracker settings
|
||||
|
||||
@@ -300,25 +194,11 @@ tracker-settings-mounting_section-edit = تعديل التركيب
|
||||
tracker-settings-drift_compensation_section = السماح بتعويض الانجراف
|
||||
tracker-settings-drift_compensation_section-description = هل يجب أن يعوض جهاز التعقب عن انحرافه عند تمكين تعويض الانجراف؟
|
||||
tracker-settings-drift_compensation_section-edit = السماح بتعويض الانجراف
|
||||
tracker-settings-use_mag = اسمح بالمقياس المغناطيسي على هذا المتتبع
|
||||
# Multiline!
|
||||
tracker-settings-use_mag-description =
|
||||
هل يجب أن يستخدم هذا المتتبع مقياس المغناطيسية لتقليل الانجراف عند السماح باستخدام المقياس المغناطيسي؟ <b>من فضلا لا تغلق جهاز التتبع الخاص بك أثناء تبديل هذا!</b>
|
||||
|
||||
تحتاج إلى السماح باستخدام مقياس المغناطيسية أولا ، <magSetting>انقر هنا للذهاب إلى الإعداد</magSetting>.
|
||||
tracker-settings-use_mag-label = السماح بالمقياس المغناطيسي
|
||||
# The .<name> means it's an attribute and it's related to the top key.
|
||||
# In this case that is the settings for the assignment section.
|
||||
tracker-settings-name_section = اسم جهاز التعقب
|
||||
tracker-settings-name_section-description = أعطها لقب لطيف :)
|
||||
tracker-settings-name_section-placeholder = ساق نايتي بيست اليسرى
|
||||
tracker-settings-name_section-label = اسم جهاز التعقب
|
||||
tracker-settings-forget = انسي جهاز التعقب
|
||||
tracker-settings-forget-description = يزيل جهاز التعقب من خادم SlimeVR ويمنعه من الاتصال به حتى يتم إعادة تشغيل الخادم. لن تضيع تكوين جهاز التعقب.
|
||||
tracker-settings-forget-label = ننسى جهاز التعقب
|
||||
tracker-settings-update-up_to_date = حديث
|
||||
tracker-settings-update = التحديث الآن
|
||||
tracker-settings-update-title = إصدار البرنامج الثابت
|
||||
|
||||
## Tracker part card info
|
||||
|
||||
@@ -392,11 +272,6 @@ settings-sidebar-osc_router = راوتر أوه أس سي
|
||||
settings-sidebar-osc_trackers = أجهزة تعقب في ار تشات أوه أس سي
|
||||
settings-sidebar-utils = الأدوات المساعدة
|
||||
settings-sidebar-serial = وحدة التحكم التسلسلية
|
||||
settings-sidebar-appearance = مظهر
|
||||
settings-sidebar-notifications = إشعارات
|
||||
settings-sidebar-behavior = سلوك
|
||||
settings-sidebar-firmware-tool = أداة برامج الجهاز المصنوع بنفسك
|
||||
settings-sidebar-advanced = متقدم
|
||||
|
||||
## SteamVR settings
|
||||
|
||||
@@ -411,22 +286,10 @@ settings-general-steamvr-description =
|
||||
مفيد فقط للألعاب أو التطبيقات التي تدعم أجهزة تعقب معينة.
|
||||
settings-general-steamvr-trackers-waist = الخصر
|
||||
settings-general-steamvr-trackers-chest = الصدر
|
||||
settings-general-steamvr-trackers-left_foot = القدم اليسرى
|
||||
settings-general-steamvr-trackers-right_foot = القدم اليمنى
|
||||
settings-general-steamvr-trackers-left_knee = الركبة اليسرى
|
||||
settings-general-steamvr-trackers-right_knee = الركبة اليمنى
|
||||
settings-general-steamvr-trackers-left_elbow = الكوع الأيسر
|
||||
settings-general-steamvr-trackers-right_elbow = الكوع الأيمن
|
||||
settings-general-steamvr-trackers-left_hand = اليد اليسرى
|
||||
settings-general-steamvr-trackers-right_hand = اليد اليمنى
|
||||
settings-general-steamvr-trackers-tracker_toggling = تعيين جهاز التعقب تلقائي
|
||||
settings-general-steamvr-trackers-tracker_toggling-description = يتعامل تلقائيا مع تبديل أجهزة تعقب SteamVR أو إيقاف تشغيلها اعتمادا على مهام التعقب الحالية
|
||||
settings-general-steamvr-trackers-tracker_toggling-label = تعيين جهاز التعقب التلقائي
|
||||
settings-general-steamvr-trackers-hands-warning =
|
||||
<b>تحذير:</b> ستتجاوز أجهزة تعقب اليد وحدات التحكم الخاصة بك.
|
||||
هل أنت متأكد؟
|
||||
settings-general-steamvr-trackers-hands-warning-cancel = إلغاء
|
||||
settings-general-steamvr-trackers-hands-warning-done = نعم
|
||||
settings-general-steamvr-trackers-feet = القدمين
|
||||
settings-general-steamvr-trackers-knees = الركبتين
|
||||
settings-general-steamvr-trackers-elbows = الكوعين
|
||||
settings-general-steamvr-trackers-hands = اليدين
|
||||
|
||||
## Tracker mechanics
|
||||
|
||||
@@ -444,39 +307,14 @@ settings-general-tracker_mechanics-filtering-type-smoothing-description = ينع
|
||||
settings-general-tracker_mechanics-filtering-type-prediction = التنبؤ
|
||||
settings-general-tracker_mechanics-filtering-type-prediction-description = يقلل من وقت الإستجابة ويجعل الحركات أكثر سرعة ، ولكنه قد يزيد من التوتر.
|
||||
settings-general-tracker_mechanics-filtering-amount = المبلغ
|
||||
settings-general-tracker_mechanics-yaw-reset-smooth-time = إعادة ضبط الانعراج على نحو سلس (يتعطل التنعيم 0 ثوان)
|
||||
settings-general-tracker_mechanics-drift_compensation = تعويض الانجراف
|
||||
# This cares about multilines
|
||||
settings-general-tracker_mechanics-drift_compensation-description =
|
||||
يعوض عن انجراف انعراج وحدة IMU بتطبيق دوران عكسي.
|
||||
قم بتغيير كمية التعويض وعدد عمليات إعادة التعيين التي يتم أخذها في الاعتبار.
|
||||
settings-general-tracker_mechanics-drift_compensation-enabled-label = تعويض الانجراف
|
||||
settings-general-tracker_mechanics-drift_compensation-prediction = التنبؤ بتعويض الانحراف
|
||||
# This cares about multilines
|
||||
settings-general-tracker_mechanics-drift_compensation-prediction-description =
|
||||
يتنبأ بتعويض انحراف الانعراج خارج النطاق المقاس مسبقا.
|
||||
قم بتمكين هذا إذا كانت أجهزة التتبع تدور باستمرار على محور الانعراج.
|
||||
settings-general-tracker_mechanics-drift_compensation-prediction-label = التنبؤ بتعويض الانحراف
|
||||
settings-general-tracker_mechanics-drift_compensation_warning =
|
||||
<b>تحذير:</b> استخدم تعويض الانحراف فقط إذا كنت بحاجة إلى إعادة الضبط
|
||||
في كثير من الأحيان (كل ~ 5-10 دقائق).
|
||||
|
||||
تتضمن بعض وحدات IMU المعرضة لإعادة الضبط المتكرر ما يلي:
|
||||
Joy-Cons و owoTrack و MPUs (بدون برامج ثابتة حديثة).
|
||||
settings-general-tracker_mechanics-drift_compensation_warning-cancel = إلغاء
|
||||
settings-general-tracker_mechanics-drift_compensation_warning-done = أتفهم
|
||||
settings-general-tracker_mechanics-drift_compensation-amount-label = مبلغ التعويض
|
||||
settings-general-tracker_mechanics-drift_compensation-max_resets-label = استخدام ما يصل إلى x عمليات إعادة التعيين الأخيرة
|
||||
settings-general-tracker_mechanics-save_mounting_reset = حفظ معايرة إعادة ضبط التركيب التلقائي
|
||||
settings-general-tracker_mechanics-save_mounting_reset-description =
|
||||
يحفظ معايرة إعادة تعيين التثبيت التلقائي لأجهزة التتبع بين عمليات إعادة التشغيل. مفيد
|
||||
عند ارتداء بدلة حيث لا تتحرك المتتبعات بين الجلسات. <b>لا ينصح به للمستخدمين العاديين!</b>
|
||||
settings-general-tracker_mechanics-save_mounting_reset-enabled-label = حفظ إعادة تعيين التركيب
|
||||
settings-general-tracker_mechanics-use_mag_on_all_trackers = استخدم المقياس المغناطيسي على جميع أجهزة تتبع IMU التي تدعمه
|
||||
settings-general-tracker_mechanics-use_mag_on_all_trackers-description =
|
||||
يستخدم المقياس المغناطيسي على جميع أجهزة التعقب التي تحتوي على برامج ثابتة متوافقة له ، مما يقلل من الانحراف في البيئات المغناطيسية المستقرة.
|
||||
يمكن تعطيله لكل جهاز تعقب في إعدادات التعقب. <b>من فضلك لا تغلق أيا من أجهزة التعقب أثناء تبديل هذا!</b>
|
||||
settings-general-tracker_mechanics-use_mag_on_all_trackers-label = استخدم المقياس المغناطيسي على أجهزة التعقب
|
||||
|
||||
## FK/Tracking settings
|
||||
|
||||
@@ -498,42 +336,17 @@ settings-general-fk_settings-leg_tweak-floor_clip-description = يمكن أن ي
|
||||
settings-general-fk_settings-leg_tweak-toe_snap-description = الانجذاب إلى أصابع القدم يحاول تخمين دوران قدميك إذا لم تكن أجهزة تعقب القدم قيد الاستخدام.
|
||||
settings-general-fk_settings-leg_tweak-foot_plant-description = تثبيت اصبع القدم يحاول تخمين دوران قدميك إذا لم تكن أجهزة تعقب القدم قيد الاستخدام.
|
||||
settings-general-fk_settings-leg_fk = تعقب الساق
|
||||
settings-general-fk_settings-enforce_joint_constraints = حدود الهيكل العظمي
|
||||
settings-general-fk_settings-enforce_joint_constraints-enforce_constraints = فرض القيود
|
||||
settings-general-fk_settings-enforce_joint_constraints-enforce_constraints-description = منع المفاصل من الدوران إلى ما بعد الحد الأقصى
|
||||
settings-general-fk_settings-enforce_joint_constraints-correct_constraints = التصحيح مع قيود
|
||||
settings-general-fk_settings-enforce_joint_constraints-correct_constraints-description = تصحيح دوران المفاصل عندما تتجاوز حدودها
|
||||
settings-general-fk_settings-arm_fk = تعقب الذراع
|
||||
settings-general-fk_settings-arm_fk-description = تغيير طريقة تعقب الذراعين.
|
||||
settings-general-fk_settings-arm_fk-force_arms = إجبار الذراعين من ايتش أم دي
|
||||
settings-general-fk_settings-reset_settings = إعادة تعيين الإعدادات
|
||||
settings-general-fk_settings-reset_settings-reset_hmd_pitch-description = أعد تعيين درجة جهاز الرأس (الدوران الرأسي) عند إجراء إعادة تعيين كاملة. مفيد في حالة ارتداء جهاز الرأس على الجبهة ل VTubing أو mocap. لا تقم بتمكين لVR.
|
||||
settings-general-fk_settings-reset_settings-reset_hmd_pitch = إعادة تعيين درجة جهاز الرأس
|
||||
settings-general-fk_settings-arm_fk-reset_mode-description = قم بتغيير وضع الذراع المتوقع لإعادة ضبط المتصاعد.
|
||||
settings-general-fk_settings-arm_fk-back = العودة
|
||||
settings-general-fk_settings-arm_fk-back-description = الوضع الافتراضي، مع وضع الذراعين العلويين إلى الخلف والساعدين للأمام.
|
||||
settings-general-fk_settings-arm_fk-tpose_up = تي بوز (أعلى)
|
||||
settings-general-fk_settings-arm_fk-tpose_up-description = يتوقع أن تكون ذراعيك لأسفل على الجانبين أثناء إعادة الضبط الكامل ، و 90 درجة حتى الجانبين أثناء إعادة ضبط التركيب.
|
||||
settings-general-fk_settings-arm_fk-tpose_down = تي بوز (لأسفل)
|
||||
settings-general-fk_settings-arm_fk-tpose_down-description = يتوقع أن تكون ذراعيك 90 درجة لأعلى على الجانبين أثناء إعادة الضبط الكامل ، ولأسفل على الجانبين أثناء إعادة ضبط التركيب.
|
||||
settings-general-fk_settings-arm_fk-forward = أمامي
|
||||
settings-general-fk_settings-arm_fk-forward-description = يتوقع أن تكون ذراعيك 90 درجة للأمام. مفيد ل VTubing.
|
||||
settings-general-fk_settings-skeleton_settings-toggles = تبديل الهيكل العظمي
|
||||
settings-general-fk_settings-skeleton_settings = إعدادات الهيكل العظمي
|
||||
settings-general-fk_settings-skeleton_settings-description = تبديل إعدادات الهيكل العظمي أو إيقافه. يوصى بتركها شغالة.
|
||||
settings-general-fk_settings-skeleton_settings-extended_spine_model = نموذج العمود الفقري الممتد
|
||||
settings-general-fk_settings-skeleton_settings-extended_pelvis_model = نموذج الحوض الممتد
|
||||
settings-general-fk_settings-skeleton_settings-extended_knees_model = نموذج الركبة الممتدة
|
||||
settings-general-fk_settings-skeleton_settings-ratios = نسب الهيكل العظمي
|
||||
settings-general-fk_settings-skeleton_settings-ratios-description = تغيير قيم إعدادات الهيكل العظمي. قد تحتاج إلى ضبط النسب الخاصة بك بعد تغييرها.
|
||||
settings-general-fk_settings-skeleton_settings-impute_waist_from_chest_hip = خصص الخصر من الصدر إلى الورك
|
||||
settings-general-fk_settings-skeleton_settings-impute_waist_from_chest_legs = خصص الخصر من الصدر إلى الساقين
|
||||
settings-general-fk_settings-skeleton_settings-impute_hip_from_chest_legs = خصص الورك من الصدر إلى الساقين
|
||||
settings-general-fk_settings-skeleton_settings-impute_hip_from_waist_legs = خصص الورك من الخصر إلى الساقين
|
||||
settings-general-fk_settings-skeleton_settings-interp_hip_legs = متوسط انعراج الفخذ وتدحرج مع الساقين'
|
||||
settings-general-fk_settings-skeleton_settings-interp_knee_tracker_ankle = متوسط الانحراف وتدحرج مع الكاحلين
|
||||
settings-general-fk_settings-skeleton_settings-interp_knee_ankle = متوسط انحراف الركبتين ولفة مع الكاحلين
|
||||
settings-general-fk_settings-self_localization-title = وضع Mocap
|
||||
settings-general-fk_settings-self_localization-description = يسمح وضع Mocap للهيكل العظمي بتعقب موضعه تقريبا بدون سماعة رأس أو أجهزة تعقب أخرى. لاحظ أن هذا يتطلب أجهزة تعقب القدمين والرأس للعمل ولا تزال تجريبية.
|
||||
settings-general-fk_settings-skeleton_settings-extended_spine = العمود الفقري الممتد
|
||||
settings-general-fk_settings-skeleton_settings-extended_pelvis = الحوض الممتد
|
||||
settings-general-fk_settings-skeleton_settings-extended_knees = الركبة الممتدة
|
||||
settings-general-fk_settings-vive_emulation-title = محاكاة فايف
|
||||
settings-general-fk_settings-vive_emulation-description = محاكاة مشاكل تعقب الخصر التي تعاني منها أجهزة تعقب فايف. هذه مزحة وتجعل التتبع أسوأ.
|
||||
settings-general-fk_settings-vive_emulation-label = تمكين محاكاة فايف
|
||||
|
||||
## Gesture control settings (tracker tapping)
|
||||
|
||||
@@ -551,17 +364,6 @@ settings-general-gesture_control-taps =
|
||||
[many] { $amount } نقرات
|
||||
*[other] { $amount } نقرات
|
||||
}
|
||||
# This is a unit: 3 trackers, 2 trackers, 1 tracker
|
||||
# $amount (Number) - Amount of trackers
|
||||
settings-general-gesture_control-trackers =
|
||||
{ $amount ->
|
||||
[zero] لا أجهزة تعقب
|
||||
[one] جهاز تعقب واحد
|
||||
[two] جهازي تعقب
|
||||
[few] { "" }
|
||||
[many] { "" }
|
||||
*[other] { $amount } أجهزة تعقب
|
||||
}
|
||||
settings-general-gesture_control-yawResetEnabled = تمكين النقر لإعادة التعيين الانعراج
|
||||
settings-general-gesture_control-yawResetDelay = إعادة تعيين التأخير الانعراج
|
||||
settings-general-gesture_control-yawResetTaps = عدد النقرات لإعادة تعيين الانعراج
|
||||
@@ -571,38 +373,13 @@ settings-general-gesture_control-fullResetTaps = عدد النقرات لإعا
|
||||
settings-general-gesture_control-mountingResetEnabled = تمكين النقر لإعادة تعيين التركيب
|
||||
settings-general-gesture_control-mountingResetDelay = تأخير إعادة تعيين التركيب
|
||||
settings-general-gesture_control-mountingResetTaps = نقرات لإعادة تعيين التركيب
|
||||
# The number of trackers that can have higher acceleration before a tap is rejected
|
||||
settings-general-gesture_control-numberTrackersOverThreshold = أجهزة تعقب فوق قيمة القطع
|
||||
settings-general-gesture_control-numberTrackersOverThreshold-description = قم بزيادة هذه القيمة إذا كان اكتشاف النقر لا يعمل. لا تقم بزيادته فوق ما هو مطلوب لجعل اكتشاف النقر يعمل لأنه قد يتسبب في المزيد من الإيجابيات الخاطئة.
|
||||
|
||||
## Appearance settings
|
||||
## Interface settings
|
||||
|
||||
settings-interface-appearance = مظهر
|
||||
settings-general-interface = واجهة المستخدم
|
||||
settings-general-interface-dev_mode = وضع المطوّر
|
||||
settings-general-interface-dev_mode-description = يمكن أن يكون هذا الوضع مفيدًا إذا كنت بحاجة إلى بيانات متعمقة أو للتفاعل مع أجهزة التعقب المتصلة على مستوى أكثر تقدمًا.
|
||||
settings-general-interface-dev_mode-label = وضع المطوّر
|
||||
settings-general-interface-theme = موضوع اللون
|
||||
settings-general-interface-show-navbar-onboarding = إظهار "{ navbar-onboarding }" على قائمة التنقل
|
||||
settings-general-interface-show-navbar-onboarding-description = يغير هذا إذا ظهر الزر "{ navbar-onboarding }" على قائمة التنقل.
|
||||
settings-general-interface-show-navbar-onboarding-label = إظهار "{ navbar-onboarding }"
|
||||
settings-general-interface-lang = اختر اللغة
|
||||
settings-general-interface-lang-description = قم بتغيير اللغة الافتراضية التي تريد استخدامها.
|
||||
settings-general-interface-lang-placeholder = اختر اللغة التي تريد استخدامها
|
||||
# Keep the font name untranslated
|
||||
settings-interface-appearance-font = خط واجهة المستخدم الرسومية
|
||||
settings-interface-appearance-font-description = هذا يغير الخط المستخدم من قبل الواجهة.
|
||||
settings-interface-appearance-font-placeholder = الخط الافتراضي
|
||||
settings-interface-appearance-font-os_font = خط نظام التشغيل
|
||||
settings-interface-appearance-font-slime_font = الخط الافتراضي
|
||||
settings-interface-appearance-font_size = قياس الخط الأساسي
|
||||
settings-interface-appearance-font_size-description = يؤثر هذا على حجم خط الواجهة بأكملها باستثناء لوحة الإعدادات هذه.
|
||||
settings-interface-appearance-decorations = استخدم الزخارف الأصلية للنظام
|
||||
settings-interface-appearance-decorations-description = لن يؤدي هذا إلى عرض الشريط الأعلى للواجهة وسيستخدم نظام التشغيل بدلا من ذلك.
|
||||
settings-interface-appearance-decorations-label = استخدم الزخارف الأصلية
|
||||
|
||||
## Notification settings
|
||||
|
||||
settings-interface-notifications = إشعارات
|
||||
settings-general-interface-serial_detection = الكشف عن جهاز تسلسلي
|
||||
settings-general-interface-serial_detection-description = سيعرض هذا الخيار نافذة منبثقة في كل مرة تقوم فيها بتوصيل جهاز تسلسلي جديد يمكن أن يكون جهاز تعقب. يساعد في تحسين عملية إعداد جهاز التعقب.
|
||||
settings-general-interface-serial_detection-label = الكشف عن جهاز تسلسلي
|
||||
@@ -610,37 +387,10 @@ settings-general-interface-feedback_sound = صوت ردود الفعل
|
||||
settings-general-interface-feedback_sound-description = سيصدر هذا الخيار صوتًا عند تشغيل إعادة الضبط
|
||||
settings-general-interface-feedback_sound-label = صوت ردود الفعل
|
||||
settings-general-interface-feedback_sound-volume = حجم صوت ردود الفعل
|
||||
settings-general-interface-connected_trackers_warning = تحذير عن أجهزة التعقب المتصلة
|
||||
settings-general-interface-connected_trackers_warning-description = سيعرض هذا الخيار نافذة كل مرة تحاول فيها الخروج من SlimeVR أثناء وجود جهاز أو أكثر من أجهزة التعقب المتصلة. سيذكرك بإيقاف تشغيل أجهزة التعقب عند الانتهاء للحفاظ على عمر البطارية.
|
||||
settings-general-interface-connected_trackers_warning-label = تحذير عن أجهزة التعقب المتصلة عند الخروج
|
||||
|
||||
## Behavior settings
|
||||
|
||||
settings-interface-behavior = السلوك
|
||||
settings-general-interface-use_tray = تصغير إلى علبة النظام
|
||||
settings-general-interface-use_tray-description = يتيح لك إغلاق النافذة دون إغلاق خادم SlimeVR حتى تتمكن من الاستمرار في استخدامه دون إزعاجك من واجهة المستخدم الرسومية.
|
||||
settings-general-interface-use_tray-label = تصغير إلى علبة النظام
|
||||
settings-general-interface-discord_presence = مشاركة النشاط على Discord
|
||||
settings-general-interface-discord_presence-description = يخبر عميل Discord الخاص بك أنك تستخدم SlimeVR جنبا إلى جنب مع عدد أجهزة تعقب IMU التي تستخدمها.
|
||||
settings-general-interface-discord_presence-label = مشاركة النشاط على Discord
|
||||
settings-general-interface-discord_presence-message =
|
||||
{ $amount ->
|
||||
[0] صفر
|
||||
[zero] صفر
|
||||
[one] واحد
|
||||
[two] اثنان
|
||||
[few] قليل
|
||||
[many] كثيرة
|
||||
*[other] أخرى
|
||||
}
|
||||
settings-interface-behavior-error_tracking = جمع الأخطاء عبر Sentry.io
|
||||
settings-interface-behavior-error_tracking-description_v2 =
|
||||
<h1>هل توافق على جمع بيانات الخطأ مجهولة المصدر؟</h1>
|
||||
|
||||
<b>نحن لا نجمع معلومات شخصية</b> مثل عنوان IP الخاص بك أو بيانات الاعتماد اللاسلكية. يقدر SlimeVR خصوصيتك!
|
||||
|
||||
لتوفير أفضل تجربة للمستخدم، نقوم بجمع تقارير الأخطاء ومقاييس الأداء ومعلومات نظام التشغيل مجهولة المصدر. يساعدنا هذا في اكتشاف الأخطاء والمشكلات المتعلقة ب SlimeVR. يتم جمع هذه المقاييس عبر Sentry.io.
|
||||
settings-interface-behavior-error_tracking-label = إرسال الأخطاء إلى المطورين
|
||||
settings-general-interface-theme = موضوع اللون
|
||||
settings-general-interface-lang = اختر اللغة
|
||||
settings-general-interface-lang-description = قم بتغيير اللغة الافتراضية التي تريد استخدامها.
|
||||
settings-general-interface-lang-placeholder = اختر اللغة التي تريد استخدامها
|
||||
|
||||
## Serial settings
|
||||
|
||||
@@ -659,11 +409,9 @@ settings-serial-factory_reset-warning =
|
||||
مما يعني أن إعدادات واي فاي والمعايرة <b>ستفقد جميعا!</b>
|
||||
settings-serial-factory_reset-warning-ok = أنا أعرف ماذا أفعل
|
||||
settings-serial-factory_reset-warning-cancel = إلغاء
|
||||
settings-serial-get_infos = احصل على معلومات
|
||||
settings-serial-serial_select = اختر منفذ تسلسلي
|
||||
settings-serial-auto_dropdown_item = تلقائي
|
||||
settings-serial-get_wifi_scan = احصل على فحص WiFi
|
||||
settings-serial-file_type = نص عادي
|
||||
settings-serial-save_logs = حفظ في ملف
|
||||
|
||||
## OSC router settings
|
||||
|
||||
@@ -694,22 +442,14 @@ settings-osc-router-network-address-placeholder = عنوان آي بي في 4
|
||||
|
||||
settings-osc-vrchat = أجهزة تعقب "في ار تشات أوه أس سي"
|
||||
# This cares about multilines
|
||||
settings-osc-vrchat-description-v1 =
|
||||
تغيير الإعدادات الخاصة بمعيار أجهزة تعقب OSC المستخدم لإرسال
|
||||
بيانات التعقب إلى التطبيقات التي لا تحتوي على SteamVR (مثل Quest المستقل).
|
||||
تأكد من تمكين OSC في VRChat عبر قائمة الإجراءات ضمن OSC > ممكن.
|
||||
settings-osc-vrchat-description =
|
||||
قم بتغيير الإعدادات الخاصة ب في ار تشات لتلقي بيانات ايتش أم دي وإرسالها
|
||||
بيانات أجهزة تعقب لتعقب الجسم (يعمل على كوست مستقل).
|
||||
settings-osc-vrchat-enable = تمكين
|
||||
settings-osc-vrchat-enable-description = بتبديل إرسال واستقبال البيانات.
|
||||
settings-osc-vrchat-enable-label = تمكين
|
||||
settings-osc-vrchat-oscqueryEnabled = تمكين OSCQuery
|
||||
settings-osc-vrchat-oscqueryEnabled-description =
|
||||
يكتشف OSCQuery تلقائيا مثيلات VRChat قيد التشغيل ويرسل البيانات إليها.
|
||||
يمكنه أيضا الإعلان عن نفسه لهم من أجل تلقي بيانات HMD ووحدة التحكم.
|
||||
للسماح بتلقي بيانات HMD ووحدة التحكم من VRChat ، انتقل إلى إعدادات القائمة الرئيسية
|
||||
ضمن "التتبع و IK (الحركة العكسية)" وتمكين "السماح بإرسال بيانات OSC لتتبع الرأس والمعصم".
|
||||
settings-osc-vrchat-oscqueryEnabled-label = تمكين OSCQuery
|
||||
settings-osc-vrchat-network = منافذ الشبكة
|
||||
settings-osc-vrchat-network-description-v1 = ضبط المنافذ الاستماع إلى البيانات وإرسالها. يمكن تركها دون أن تمس ل VRChat.
|
||||
settings-osc-vrchat-network-description = قم بتعيين المنافذ للاستماع وإرسال البيانات إلى في ار تشات
|
||||
settings-osc-vrchat-network-port_in =
|
||||
.label = منفذ الدخول
|
||||
.placeholder = منفذ الدخول (الإفتراضي: 9001)
|
||||
@@ -717,7 +457,7 @@ settings-osc-vrchat-network-port_out =
|
||||
.label = منفذ الخروج
|
||||
.placeholder = منفذ الخروج (الإفتراضي: 9000)
|
||||
settings-osc-vrchat-network-address = عنوان الشبكة
|
||||
settings-osc-vrchat-network-address-description-v1 = اختر العنوان الذي تريد إرسال البيانات إليه. يمكن تركها دون أن تمس ل VRChat.
|
||||
settings-osc-vrchat-network-address-description = اختر العنوان الذي تريد إرسال البيانات إلى في ار تشات (تحقق من إعدادات واي فاي على جهازك)
|
||||
settings-osc-vrchat-network-address-placeholder = عنوان آي بي الخاص بفي ار تشات
|
||||
settings-osc-vrchat-network-trackers = أجهزة التعقب
|
||||
settings-osc-vrchat-network-trackers-description = تبديل إرسال أجهزة تتبع محددة عبر أوه أس سي.
|
||||
@@ -750,54 +490,16 @@ settings-osc-vmc-network-address-description = قم بتعيين العنوان
|
||||
settings-osc-vmc-network-address-placeholder = عنوان آي بي في 4
|
||||
settings-osc-vmc-vrm = نموذج في ار إم
|
||||
settings-osc-vmc-vrm-description = قم بتحميل نموذج في ار إم للسماح بتركيز الرأس وتمكين توافق أعلى مع تطبيقات الأخرى
|
||||
settings-osc-vmc-vrm-untitled_model = نموذج بدون عنوان
|
||||
settings-osc-vmc-vrm-model_unloaded = لم يتم تحميل أي نموذج
|
||||
settings-osc-vmc-vrm-model_loaded =
|
||||
{ $titled ->
|
||||
[true] تحميل النموذج: { $name }
|
||||
*[other] تم تحميل نموذج بدون عنوان
|
||||
}
|
||||
settings-osc-vmc-vrm-file_select = اسحب نموذج وأفلته لاستخدامه أو <u> تصفح </ u>
|
||||
settings-osc-vmc-anchor_hip = ثبت في الوركين
|
||||
settings-osc-vmc-anchor_hip-description = ثبت التعقب في الوركين، هو مفيد إن كنت تيوبنغ جالسًا. في حالة التعطيل، قم بتحميل نموذج في ار إم.
|
||||
settings-osc-vmc-anchor_hip-label = ثبت في الوركين
|
||||
settings-osc-vmc-mirror_tracking = اعكس التعقب
|
||||
settings-osc-vmc-mirror_tracking-description = اعكس التعقب أفقيا.
|
||||
settings-osc-vmc-mirror_tracking-label = اعكس التعقب
|
||||
|
||||
## Common OSC settings
|
||||
|
||||
|
||||
## Advanced settings
|
||||
|
||||
settings-utils-advanced = متقدم
|
||||
settings-utils-advanced-reset-gui = إعادة تعيين إعدادات واجهة المستخدم الرسومية (GUI)
|
||||
settings-utils-advanced-reset-gui-description = قم باستعادة الإعدادات الافتراضية للواجهة.
|
||||
settings-utils-advanced-reset-gui-label = إعادة تعيين واجهة المستخدم الرسومية
|
||||
settings-utils-advanced-reset-server = إعادة تعيين إعدادات التعقب
|
||||
settings-utils-advanced-reset-server-description = استعادة الإعدادات الافتراضية للتعقب.
|
||||
settings-utils-advanced-reset-server-label = إعادة تعيين التعقب
|
||||
settings-utils-advanced-reset-all = إعادة تعيين جميع الإعدادات
|
||||
settings-utils-advanced-reset-all-description = قم باستعادة الإعدادات الافتراضية لكل من الواجهة و التعقب.
|
||||
settings-utils-advanced-reset-all-label = إعادة تعيين الكل
|
||||
settings-utils-advanced-reset_warning =
|
||||
{ $type ->
|
||||
[gui]
|
||||
<b>تحذير:</b> سيؤدي هذا إلى إعادة تعيين جميع الإعدادات الخاصة بك إلى الإعدادات الافتراضية.
|
||||
هل أنت متأكد من أنك تريد القيام بذلك؟
|
||||
[server] <b>تحذير:</b> سيؤدي هذا إلى إعادة تعيين إعدادات التعقب إلى الإعدادات الافتراضية. هل أنت متأكد من أنك تريد القيام بذلك؟
|
||||
*[all]
|
||||
<b>تحذير:</b> سيؤدي هذا إلى إعادة تعيين جميع الإعدادات الخاصة بك إلى الإعدادات الافتراضية.
|
||||
هل أنت متأكد من أنك تريد القيام بذلك؟
|
||||
}
|
||||
settings-utils-advanced-reset_warning-reset = إعادة تعيين الإعدادات
|
||||
settings-utils-advanced-reset_warning-cancel = إلغاء
|
||||
settings-utils-advanced-open_data-v1 = مجلد التكوين
|
||||
settings-utils-advanced-open_data-description-v1 = فتح مجلد إعدادات SlimeVR في مستكشف الملفات ، والذي يحتوي على الإعدادات
|
||||
settings-utils-advanced-open_data-label = فتح المجلد
|
||||
settings-utils-advanced-open_logs = مجلد السجلات
|
||||
settings-utils-advanced-open_logs-description = افتح مجلد سجلات SlimeVR في مستكشف الملفات ، والذي يحتوي على سجلات التطبيق
|
||||
settings-utils-advanced-open_logs-label = فتح المجلد
|
||||
|
||||
## Home Screen
|
||||
|
||||
|
||||
## Tracking Checlist
|
||||
|
||||
|
||||
## Setup/onboarding menu
|
||||
|
||||
@@ -814,12 +516,16 @@ onboarding-setup_warning-cancel = متابعة الإعداد
|
||||
## Wi-Fi setup
|
||||
|
||||
onboarding-wifi_creds-back = العودة إلى المقدمة
|
||||
onboarding-wifi_creds = إدخل بيانات اعتماد واي فاي
|
||||
# This cares about multilines
|
||||
onboarding-wifi_creds-description =
|
||||
ستستخدم أجهزة التعقب بيانات الاعتماد هذه للاتصال لاسلكيًا.
|
||||
الرجاء استخدام بيانات الاعتماد التي تتصل بها حاليًا.
|
||||
onboarding-wifi_creds-skip = تخطى إعدادات واي فاي
|
||||
onboarding-wifi_creds-submit = إرسال!
|
||||
onboarding-wifi_creds-ssid =
|
||||
.label = اسم الواي فاي
|
||||
.placeholder = أدخل اسم الواي فاي
|
||||
onboarding-wifi_creds-ssid-required = مطلوب اسم Wi-Fi
|
||||
onboarding-wifi_creds-password =
|
||||
.label = كلمة السر
|
||||
.placeholder = أدخل كلمة السر
|
||||
@@ -854,6 +560,13 @@ onboarding-reset_tutorial-2 =
|
||||
onboarding-home = مرحبا بكم في سلايم في ار
|
||||
onboarding-home-start = هيا نتجهز!
|
||||
|
||||
## Enter VR part of setup
|
||||
|
||||
onboarding-enter_vr-back = العودة إلى تعيين أجهزة التعقب
|
||||
onboarding-enter_vr-title = حان وقت دخول في ار!
|
||||
onboarding-enter_vr-description = ضع كل أجهزة التعقب ثم أدخل في ار!
|
||||
onboarding-enter_vr-ready = أنا جاهز
|
||||
|
||||
## Setup done
|
||||
|
||||
onboarding-done-title = أنت جاهز تمامًا!
|
||||
@@ -864,13 +577,12 @@ onboarding-done-close = إغلاق الدليل
|
||||
|
||||
onboarding-connect_tracker-back = العودة إلى بيانات اعتماد الواي فاي
|
||||
onboarding-connect_tracker-title = ربط أجهزة التعقب
|
||||
onboarding-connect_tracker-description-p0-v1 = ننتقل الآن إلى الجزء الممتع ، ربط أجهزة التعقب!
|
||||
onboarding-connect_tracker-description-p1-v1 = قم بتوصيل كل جهاز تعقب واحدا تلو الآخر من خلال منفذ USB.
|
||||
onboarding-connect_tracker-description-p0 = ننتقل الآن إلى الجزء الممتع ، ربط جميع أجهزة التعقب!
|
||||
onboarding-connect_tracker-description-p1 = ما عليك سوى توصيل كل ما لم يتم توصيله بعد من خلال منفذ يو أس بي.
|
||||
onboarding-connect_tracker-issue-serial = أواجه مشكلة في الاتصال!
|
||||
onboarding-connect_tracker-usb = جهاز تعقب يو أس بي
|
||||
onboarding-connect_tracker-connection_status-none = نبحث عن أجهزة التعقب
|
||||
onboarding-connect_tracker-connection_status-serial_init = نتواصل بجهاز التسلسلي
|
||||
onboarding-connect_tracker-connection_status-obtaining_mac_address = الحصول على عنوان mac الخاص بجهاز التعقب
|
||||
onboarding-connect_tracker-connection_status-provisioning = نرسل بيانات اعتماد واي فاي
|
||||
onboarding-connect_tracker-connection_status-connecting = جارٍ إرسال بيانات اعتماد الواي فاي
|
||||
onboarding-connect_tracker-connection_status-looking_for_server = نبحث عن السرفر
|
||||
@@ -898,12 +610,12 @@ onboarding-connect_tracker-next = لقد قمت بتوصيل جميع أجهزة
|
||||
|
||||
onboarding-calibration_tutorial = برنامج تعليم معايرة IMU
|
||||
onboarding-calibration_tutorial-subtitle = سوف يساعد هذا في تقليل الانجراف التعقب!
|
||||
onboarding-calibration_tutorial-description = كل مرة تقوم بتشغيل أجهزة التعقب، يجب أن تستريح للحظة على سطح مستوٍ للمعايرة. لنفعل الشيء نفسه بالنقر فوق الزر "{ onboarding-calibration_tutorial-calibrate }" ، <b>لا تحركها!</b>
|
||||
onboarding-calibration_tutorial-calibrate = وضعت أجهزة التعقب على الطاولة
|
||||
onboarding-calibration_tutorial-status-waiting = بانتظارك
|
||||
onboarding-calibration_tutorial-status-calibrating = جاري المعايرة
|
||||
onboarding-calibration_tutorial-status-success = رائع!
|
||||
onboarding-calibration_tutorial-status-error = تم نقل جهاز التعقب
|
||||
onboarding-calibration_tutorial-skip = تخطي البرنامج التعليمي
|
||||
|
||||
## Tracker assignment tutorial
|
||||
|
||||
@@ -911,8 +623,8 @@ onboarding-assignment_tutorial = كيفية تحضير جهاز تعقب Slime
|
||||
onboarding-assignment_tutorial-first_step = 1. ضع ملصق جزء الجسم (إذا كان لديك واحد) على جهاز التعقب وفقا لاختيارك
|
||||
# This text has a character limit of around 11 characters, so please keep it short
|
||||
onboarding-assignment_tutorial-sticker = ملصق
|
||||
onboarding-assignment_tutorial-second_step-v2 = 2. قم بتوصيل الشريط بجهاز التعقب، مع الحفاظ على جانب الفيلكرو من الشريط في نفس اتجاه وجه السلايم لجهاز التعقب:
|
||||
onboarding-assignment_tutorial-second_step-continuation-v2 = يجب أن يكون جانب الفيلكرو للامتداد متجها للأعلى مثل الصورة التالية:
|
||||
onboarding-assignment_tutorial-second_step = 2. قم بتوصيل الشريط بجهاز التعقب الخاص بك ، مع الحفاظ على جانب الخطاف والحلقة من وجه الشريط في الاتجاه التالي:
|
||||
onboarding-assignment_tutorial-second_step-continuation = يجب أن يكون جانب الخطاف والحلقة للامتداد في هذا الاتجاه:
|
||||
onboarding-assignment_tutorial-done = وضعت الملصقات والأشرطة!
|
||||
|
||||
## Tracker assignment setup
|
||||
@@ -934,32 +646,6 @@ onboarding-assign_trackers-assigned =
|
||||
}
|
||||
onboarding-assign_trackers-advanced = إظهار مواقع التعيين المتقدمة
|
||||
onboarding-assign_trackers-next = لقد عينت جميع أجهزة التعقب
|
||||
onboarding-assign_trackers-mirror_view = عرض المرآة
|
||||
onboarding-assign_trackers-option-amount =
|
||||
{ $trackersCount ->
|
||||
[zero] صفر
|
||||
[one] واحد
|
||||
[two] اثنان
|
||||
[few] قليلة
|
||||
[many] كثيرة
|
||||
*[other] أخرى
|
||||
}
|
||||
onboarding-assign_trackers-option-label =
|
||||
{ $mode ->
|
||||
[lower-body] الجسم السفلي
|
||||
[core] أساس الجسم
|
||||
[enhanced-core] الأساس المحسن
|
||||
[full-body] الجسم الكامل
|
||||
*[all] الكل
|
||||
}
|
||||
onboarding-assign_trackers-option-description =
|
||||
{ $mode ->
|
||||
[lower-body] الحد الأدنى لتعقب الجسم الكامل في الواقع الافتراضي
|
||||
[core] + تحسين تعقب العمود الفقري
|
||||
[enhanced-core] + دوران القدم
|
||||
[full-body] + تعقب الكوع
|
||||
*[all] جميع مهام التعقب المتاحة
|
||||
}
|
||||
|
||||
## Tracker assignment warnings
|
||||
|
||||
@@ -1035,20 +721,13 @@ onboarding-choose_mounting = ما طريقة معايرة التركيب الم
|
||||
# Multiline text
|
||||
onboarding-choose_mounting-description = اتجاه التركيب يصحح وضع أجهزة التعقب على جسمك.
|
||||
onboarding-choose_mounting-auto_mounting = التركيب التلقائي
|
||||
# Italicized text
|
||||
onboarding-choose_mounting-auto_mounting-label-v2 = الموصى به
|
||||
# Italized text
|
||||
onboarding-choose_mounting-auto_mounting-label = تجريبي
|
||||
onboarding-choose_mounting-auto_mounting-description = سيكتشف هذا تلقائيًا اتجاهات التركيب لجميع أجهزة التعقب من وضعين
|
||||
onboarding-choose_mounting-manual_mounting = التركيب اليدوي
|
||||
# Italicized text
|
||||
onboarding-choose_mounting-manual_mounting-label-v2 = قد لا تكون الدقة كافية
|
||||
# Italized text
|
||||
onboarding-choose_mounting-manual_mounting-label = المستحسن
|
||||
onboarding-choose_mounting-manual_mounting-description = سيسمح لك باختيار اتجاه التثبيت يدويًا لكل جهاز تعقب
|
||||
# Multiline text
|
||||
onboarding-choose_mounting-manual_modal-title =
|
||||
هل أنت متأكد من أنك تريد
|
||||
معايرة التركيب التلقائي؟
|
||||
onboarding-choose_mounting-manual_modal-description = <b>يوصى بمعايرة التركيب اليدوي للمستخدمين الجدد</b> ، حيث قد يكون من الصعب الحصول على أوضاع معايرة التركيب التلقائي الصحيحة من اول مرة وقد تتطلب بعض التمرين.
|
||||
onboarding-choose_mounting-manual_modal-confirm = أنا أعرف ماذا أفعل
|
||||
onboarding-choose_mounting-manual_modal-cancel = إلغاء
|
||||
|
||||
## Tracker manual mounting setup
|
||||
|
||||
@@ -1073,18 +752,37 @@ onboarding-automatic_mounting-mounting_reset-title = إعادة تعيين ال
|
||||
onboarding-automatic_mounting-mounting_reset-step-0 = 1. قرفص في وضع "التزلج" مع ثني ساقيك ، وإمالة الجزء العلوي من جسمك إلى الأمام ، وثني ذراعيك.
|
||||
onboarding-automatic_mounting-mounting_reset-step-1 = 2. اضغط على زر "إعادة تعيين التركيب" وانتظر لمدة 3 ثوان قبل إعادة تعيين دوران تركيب أجهزة التعقب.
|
||||
onboarding-automatic_mounting-preparation-title = التحضير
|
||||
onboarding-automatic_mounting-preparation-step-0 = 1. قف بشكل مستقيم مع ذراعيك على جانبيك.
|
||||
onboarding-automatic_mounting-preparation-step-1 = اضغط على زر "إعادة ضبط" و انتظر لمدة 3 ثوانٍ قبل إعادة تعيين أجهزة التعقب.
|
||||
onboarding-automatic_mounting-put_trackers_on-title = ارتدي أجهزة التعقب
|
||||
onboarding-automatic_mounting-put_trackers_on-description = لمعايرة دوران التركيب، سنستخدم أجهزة التعقب التي قمت بتعيينها. ارتدي جميع أجهزة التعقب، يمكنك معرفة أي منها في المستند على اليمين.
|
||||
onboarding-automatic_mounting-put_trackers_on-next = ارتديت جميع أجهزة التعقب.
|
||||
|
||||
## Tracker manual proportions setupa
|
||||
## Tracker proportions method choose
|
||||
|
||||
onboarding-choose_proportions = ما هي طريقة معايرة النسب التي يجب استخدامها؟
|
||||
# Multiline string
|
||||
onboarding-choose_proportions-description =
|
||||
تستخدم نسب الجسم لمعرفة قياسات جسمك. هم مطلوبون لحساب مواقع أجهزة التعقب.
|
||||
عندما لا تتطابق نسب جسمك مع تلك المحفوظة ، ستكون دقة التتبع لديك أسوأ وستلاحظ أشياء مثل التزلج أو الانزلاق ، أو أن جسمك لا يتطابق مع الصورة الرمزية بشكل جيد.
|
||||
onboarding-choose_proportions-auto_proportions = النسب التلقائية
|
||||
# Italized text
|
||||
onboarding-choose_proportions-auto_proportions-subtitle = الموصى به
|
||||
onboarding-choose_proportions-auto_proportions-description = سيقدر هذا النسب الخاصة بك عن طريق تسجيل عينة من حركاتك وتمريرها من خلال برنامج
|
||||
onboarding-choose_proportions-manual_proportions = النسب اليدوية
|
||||
# Italized text
|
||||
onboarding-choose_proportions-manual_proportions-subtitle = للمسات الصغيرة
|
||||
onboarding-choose_proportions-manual_proportions-description = سيسمح لك بتعديل النسب يدويًا عن طريق تعديلها مباشرة
|
||||
onboarding-choose_proportions-export = تصدير النسب
|
||||
onboarding-choose_proportions-file_type = ملف نسب الجسم
|
||||
|
||||
## Tracker manual proportions setup
|
||||
|
||||
onboarding-manual_proportions-back = العودة إلى برنامج تعليم إعادة التعيين
|
||||
onboarding-manual_proportions-title = نسب الجسم اليدوية
|
||||
onboarding-manual_proportions-fine_tuning_button = ضبط النسب تلقائيا
|
||||
onboarding-manual_proportions-fine_tuning_button-disabled-tooltip = يرجى توصيل سماعة رأس VR لاستخدام الضبط الدقيق التلقائي
|
||||
onboarding-manual_proportions-export = تصدير النسب
|
||||
onboarding-manual_proportions-import = استيراد النسب
|
||||
onboarding-manual_proportions-file_type = ملف نسب الجسم
|
||||
onboarding-manual_proportions-precision = ضبط الدقة
|
||||
onboarding-manual_proportions-auto = المعايرة التلقائية
|
||||
onboarding-manual_proportions-ratio = اضبط حسب مجموعات النسب
|
||||
|
||||
## Tracker automatic proportions setup
|
||||
|
||||
@@ -1098,40 +796,14 @@ onboarding-automatic_proportions-put_trackers_on-description = لمعايرة ن
|
||||
onboarding-automatic_proportions-put_trackers_on-next = ارتديت جميع أجهزة التعقب.
|
||||
onboarding-automatic_proportions-requirements-title = المتطلبات
|
||||
# Each line of text is a different list item
|
||||
onboarding-automatic_proportions-requirements-descriptionv2 =
|
||||
لديك على الأقل ما يكفي من أجهزة التعقب لتتبع قدميك (بشكل عام 5 أجهزة تعقب).
|
||||
لديك أجهزة التعقب وجهاز الواقع الافتراضي الخاص بك وترتديهم.
|
||||
أجهزة التعقب وجهاز الواقع الافتراضي متصلة بخادم SlimeVR وتعمل بشكل صحيح (مثلاً، لا يوجد تأتأة أو قطع اتصال ، إلخ).
|
||||
يقوم جهاز الواقع الافتراضي بالإبلاغ عن البيانات الموضعية إلى خادم SlimeVR (وهذا يعني عموما تشغيل SteamVR وتوصيله ب SlimeVR باستخدام برنامج تشغيل SteamVR الخاص ب SlimeVR).
|
||||
يعمل التتبع الخاص بك ويمثل تحركاتك بدقة (على سبيل المثال ، لقد أجريت إعادة تعيين كاملة وتتحرك في الاتجاه الصحيح عند الركل, الانحناء, الجلوس, إلخ).
|
||||
onboarding-automatic_proportions-requirements-description =
|
||||
لديك على الأقل ما يكفي من أجهزة تعقب لتعقب قدميك (بشكل عام 5 أجهزة تعقب).
|
||||
ارتديت أجهزة التعقب وسماعة الرأس.
|
||||
شغلت أجهزة التعقب وسماعة الرأس.
|
||||
أجهزة التعقب وسماعات الرأس متصلة بسرفر سلايم في ار.
|
||||
تعمل أجهزة التتبع وسماعات الرأس بشكل صحيح داخل سرفر سلايم في ار .
|
||||
تقوم سماعة الرأس الخاصة بك بالإبلاغ عن بيانات الموقع إلى سرفر سلايم في ار (وهذا يعني بشكل عام تشغيل سلايم في ار وتوصيله بـ سلايم في ار باستخدام برنامج تشغيل ستيم في ار من سلايم في ار ).
|
||||
onboarding-automatic_proportions-requirements-next = لقد قرأت المتطلبات
|
||||
onboarding-automatic_proportions-check_height-title-v3 = قم بقياس ارتفاع سماعة الرأس
|
||||
onboarding-automatic_proportions-check_height-description-v2 = يجب أن يكون ارتفاع سماعة الرأس (HMD) أقل قليلا من طولك الكامل ، حيث تقيس سماعات الرأس ارتفاع عينيك. سيتم استخدام هذا القياس كخط أساس لنسب جسمك.
|
||||
# All the text is in bold!
|
||||
onboarding-automatic_proportions-check_height-calculation_warning-v3 = ابدأ في القياس أثناء الوقوف <u>في وضع مستقيم</u> لقياس طولك. احرص على عدم رفع يديك أعلى من سماعة الرأس ، لأنها قد تؤثر على القياس!
|
||||
onboarding-automatic_proportions-check_height-guardian_tip =
|
||||
إذا كنت تستخدم سماعة رأس VR مستقلة ، فتأكد من تشغيل حدود الحارس /
|
||||
لكي يكون طولك صحيحا!
|
||||
# Context is that the height is unknown
|
||||
onboarding-automatic_proportions-check_height-unknown = مجهول
|
||||
# Shows an element below it
|
||||
onboarding-automatic_proportions-check_height-hmd_height2 = ارتفاع سماعة الرأس هو:
|
||||
onboarding-automatic_proportions-check_height-measure-start = ابدأ القياس
|
||||
onboarding-automatic_proportions-check_height-measure-stop = توقف عن القياس
|
||||
onboarding-automatic_proportions-check_height-measure-reset = إعادة محاولة القياس
|
||||
onboarding-automatic_proportions-check_height-next_step = انهم بخير
|
||||
onboarding-automatic_proportions-check_floor_height-title = قم بقياس الارتفاع عن الأرض (اختياري)
|
||||
onboarding-automatic_proportions-check_floor_height-description = في بعض الحالات، قد لا يتم ضبط الارتفاع عن الأرض بشكل صحيح بواسطة سماعة الرأس، مما يتسبب في قياس ارتفاع سماعة الرأس على أنه أعلى مما ينبغي. يمكنك قياس "الارتفاع" عن الأرض لتصحيح ارتفاع سماعة الرأس.
|
||||
# All the text is in bold!
|
||||
onboarding-automatic_proportions-check_floor_height-calculation_warning-v2 = ابدأ بقياس و ضع وحدة التحكم على الأرض لقياس ارتفاعها. إذا كنت متأكدا من صحة الارتفاع عن الأرض ، فيمكنك تخطي هذه الخطوة.
|
||||
# Shows an element below it
|
||||
onboarding-automatic_proportions-check_floor_height-floor_height = الارتفاع عن الأرض هو:
|
||||
onboarding-automatic_proportions-check_floor_height-full_height = طولك الكامل المقدر هو:
|
||||
onboarding-automatic_proportions-check_floor_height-measure-start = ابدأ القياس
|
||||
onboarding-automatic_proportions-check_floor_height-measure-stop = توقف عن القياس
|
||||
onboarding-automatic_proportions-check_floor_height-measure-reset = إعد محاولة القياس
|
||||
onboarding-automatic_proportions-check_floor_height-skip_step = تخطي الخطوة وحفظ
|
||||
onboarding-automatic_proportions-check_floor_height-next_step = استخدم الارتفاع عن الأرض وحفظه
|
||||
onboarding-automatic_proportions-start_recording-title = استعد للتحرك
|
||||
onboarding-automatic_proportions-start_recording-description = سنقوم الآن بتسجيل بعض الوضعيات والحركات المحددة. ستتم مطالبتك بذلك في الشاشة التالية. كن مستعدا للبدء عند الضغط على الزر!
|
||||
onboarding-automatic_proportions-start_recording-next = بدء التسجيل
|
||||
@@ -1165,35 +837,11 @@ onboarding-automatic_proportions-verify_results-redo = إعادة التسجيل
|
||||
onboarding-automatic_proportions-verify_results-confirm = تبدو صحيحة
|
||||
onboarding-automatic_proportions-done-title = تم قياس الجسم و حفظه.
|
||||
onboarding-automatic_proportions-done-description = اكتملت معايرة نسب جسمك!
|
||||
onboarding-automatic_proportions-error_modal-v2 =
|
||||
<b>تحذير:</b> حدث خطأ أثناء تقدير النسب!
|
||||
من المحتمل أن تكون هذه مشكلة معايرة التركيب. تأكد من أن التعقب يعمل بشكل صحيح قبل المحاولة مرة أخرى.
|
||||
يرجى <docs>التحقق من التعليمات</docs> أو الانضمام إلى <discord>Discord</discord> للحصول على المساعدة ^_^
|
||||
onboarding-automatic_proportions-error_modal-confirm = مفهوم!
|
||||
onboarding-automatic_proportions-smol_warning =
|
||||
الارتفاع الذي تم تكوينه هو { $height } و هو أصغر من الحد الأدنى للارتفاع المقبول البالغ { $minHeight }.
|
||||
<b>يرجى إعادة القياسات والتأكد من صحتها.</b>
|
||||
onboarding-automatic_proportions-smol_warning-cancel = الرجوع
|
||||
|
||||
## User height calibration
|
||||
|
||||
|
||||
## Stay Aligned setup
|
||||
|
||||
|
||||
## Home
|
||||
|
||||
home-no_trackers = لم يتم الكشف أو تعيين عن أي جهاز تعقب
|
||||
|
||||
## Trackers Still On notification
|
||||
|
||||
trackers_still_on-modal-title = أجهزة التعقب لا تزال قيد التشغيل
|
||||
trackers_still_on-modal-description =
|
||||
لا يزال واحد أو أكثر من أجهزة التعقب قيد التشغيل.
|
||||
هل مازلت تريد الخروج من SlimeVR؟
|
||||
trackers_still_on-modal-confirm = الخروج من SlimeVR
|
||||
trackers_still_on-modal-cancel = انتظر...
|
||||
|
||||
## Status system
|
||||
|
||||
status_system-StatusTrackerReset = يوصى بإجراء إعادة تعيين كاملة نظرًا لعدم تعديل واحد أو أكثر من أجهزة التعقب.
|
||||
@@ -1203,67 +851,3 @@ status_system-StatusSteamVRDisconnected =
|
||||
*[other] حاليًا غير متصل بـ SteamVR عبر برنامج تشغيل SlimeVR.
|
||||
}
|
||||
status_system-StatusTrackerError = يحتوي جهاز التعقب { $trackerName } على خطأ.
|
||||
status_system-StatusUnassignedHMD = يجب تعيين سماعة رأس VR كجهاز تعقب للرأس.
|
||||
|
||||
## Firmware tool globals
|
||||
|
||||
firmware_tool-next_step = الخطوة التالية
|
||||
firmware_tool-previous_step = الخطوة السابقة
|
||||
firmware_tool-ok = تبدو جيدة
|
||||
firmware_tool-retry = اعادة المحاولة
|
||||
firmware_tool-loading = تحميل...
|
||||
|
||||
## Firmware tool Steps
|
||||
|
||||
firmware_tool = أداة البرامج الثابتة DIY
|
||||
firmware_tool-description = يسمح لك بتكوين و لتحديث أجهزة التعقب DIY الخاصة بك
|
||||
firmware_tool-not_available = عفوا ، أداة البرامج الثابتة غير متوفرة في الوقت الحالي. عد لاحقا!
|
||||
firmware_tool-not_compatible = أداة البرنامج الثابت غير متوافقة مع هذا الإصدار من الخادم. يرجى تحديث الخادم الخاص بك!
|
||||
firmware_tool-flash_method_step = طريقة التثبيت
|
||||
firmware_tool-flash_method_step-description = الرجاء حدد طريقة التثبيت التي تريد استخدامها
|
||||
firmware_tool-flashbtn_step = اضغط على زر التمهيد
|
||||
firmware_tool-flashbtn_step-description = قبل الانتقال إل الخطوة التالية، هناك بعض الأشياء التي عليك القيام بها
|
||||
firmware_tool-flashbtn_step-board_SLIMEVR = أوقف تشغيل جهاز التعقب، قم بإزالة العلبة (إن وجدت)، وقم بتوصيل كابل USB بهذا الكمبيوتر ، ثم قم بإحدى الخطوات التالية وفقا لمراجعة لوحة SlimeVR:
|
||||
|
||||
## firmware tool build status
|
||||
|
||||
|
||||
## Firmware update status
|
||||
|
||||
|
||||
## Dedicated Firmware Update Page
|
||||
|
||||
|
||||
## Tray Menu
|
||||
|
||||
tray_menu-show = عرض
|
||||
tray_menu-hide = إخفاء
|
||||
tray_menu-quit = انهاء
|
||||
|
||||
## First exit modal
|
||||
|
||||
tray_or_exit_modal-title = ماذا يجب أن يفعل زر الإغلاق؟
|
||||
# Multiline text
|
||||
tray_or_exit_modal-description =
|
||||
يتيح لك ذلك اختيار ما إذا كنت تريد الخروج من الخادم أو تصغيره إلى علبة النظام عند الضغط على زر الإغلاق.
|
||||
|
||||
يمكنك تغيير هذا لاحقا في إعدادات الواجهة!
|
||||
tray_or_exit_modal-radio-exit = الخروج عند الإغلاق
|
||||
tray_or_exit_modal-radio-tray = تصغير إلى علبة النظام
|
||||
tray_or_exit_modal-submit = احفظ
|
||||
tray_or_exit_modal-cancel = إلغاء
|
||||
|
||||
## Unknown device modal
|
||||
|
||||
unknown_device-modal-title = تم العثور على جهاز تعقب جديد!
|
||||
unknown_device-modal-description =
|
||||
هناك جهاز تعقب جديد مع عنوان MAC <b>{ $deviceId }</b>.
|
||||
هل تريد توصيله ب SlimeVR؟
|
||||
unknown_device-modal-confirm = أكيد
|
||||
unknown_device-modal-forget = تجاهلها
|
||||
|
||||
## Error collection consent modal
|
||||
|
||||
|
||||
## Tracking checklist section
|
||||
|
||||
|
||||