Compare commits

...

146 Commits

Author SHA1 Message Date
HannahPadd
bb0423457f Update keybinds form to reject duplicate submissions. 2026-04-01 11:20:12 +02:00
HannahPadd
13f2abb7ab lint 2026-04-01 08:07:35 +02:00
HannahPadd
f938d7e49c Should be done 2026-04-01 07:55:15 +02:00
HannahPadd
cbf59e0ec7 Styling update 2026-03-31 13:17:19 +02:00
HannahPadd
04eb4ce33d Added translations, fixed some modal logic 2026-03-31 10:51:21 +02:00
HannahPadd
9d26032460 It wokie 2026-03-30 17:48:50 +02:00
HannahPadd
cde42aa071 UI is functional now 2026-03-27 17:17:05 +01:00
HannahPadd
13e1265a51 Very big keybinds server refactor 2026-03-27 15:49:19 +01:00
HannahPadd
2ef6a3172d Rename .java to .kt 2026-03-27 15:49:19 +01:00
HannahPadd
934dd3dee2 Large update to keybindrecorder 2026-03-26 17:45:56 +01:00
HannahPadd
9c8cd8517e Update to form and ui. 2026-03-25 23:18:37 +01:00
HannahPadd
ef7e4b1550 Merge remote-tracking branch 'origin/hannah/steam' into hannah/keybinds 2026-03-20 14:10:46 +01:00
HannahPadd
4f84ccb399 Update keybinding logic 2026-03-19 16:42:09 +01:00
Hannah Lynn Lindrob
6c5c358805 Added DbusGlobalShortcutsWayland as a jitpack module 2026-03-18 21:33:44 +01:00
HannahPadd
810c7e5327 Started implementing proof of concept for keybind support on Wayland 2026-03-18 21:17:21 +01:00
HannahPadd
faa3da362e Merge remote-tracking branch 'origin/main' into hannah/keybinds 2026-03-16 18:54:30 +01:00
HannahPadd
22319a5c7e lint 2026-03-16 18:20:37 +01:00
HannahPadd
a83c7ff31e PR feedback 2026-03-16 18:02:38 +01:00
HannahPadd
be722624f1 PR feedback and small logging update. 2026-03-16 15:20:10 +01:00
HannahPadd
3fbe64b0c3 PR feedback 2026-03-16 14:24:56 +01:00
HannahPadd
1b299c499d Update to Linux socket locations 2026-03-16 12:47:25 +01:00
HannahPadd
3c945946e7 Update unix socket directory to be /tmp/ instead of user home. 2026-03-13 13:51:00 +01:00
HannahPadd
6c9341f333 Update udevadm to account for running in pressure vessel 2026-03-12 18:00:22 +01:00
HannahPadd
4409050359 Moved the installation of the usb drivers to steam the install script. 2026-03-12 16:11:26 +01:00
loucass003
95daec6814 Pass server jar properly 2026-03-12 02:26:13 +01:00
lucas lelievre
43b104fcc3 Electron build fix round 2 (#1783) 2026-03-12 02:11:42 +01:00
loucass003
a33d6b1c1a Set better folder for electron storage 2026-03-11 20:45:37 +01:00
Hannah Lynn Lindrob
8580e9a18b PR feedback and fix broken merge 2026-03-11 20:34:30 +01:00
loucass003
cf67b9306b Remove electron test from ci 2026-03-11 19:23:56 +01:00
lucas lelievre
238c86ee98 Electron CI fixes (#1780) 2026-03-11 19:12:47 +01:00
loucass003
7f56209ca9 Fix windows zip 2026-03-11 16:57:33 +01:00
Hannah Lynn Lindrob
77e0dab795 Remove no-udev command flag when on Windows 2026-03-11 13:09:59 +01:00
Hannah Lynn Lindrob
6bbf325b47 Set skipCheckUdev to true if not on linux. 2026-03-11 12:57:02 +01:00
loucass003
a3e50cdc02 fix server.jar output path 2026-03-11 01:39:41 +01:00
loucass003
b7c43d8373 Fix release out 2026-03-11 01:23:38 +01:00
lucas lelievre
cadc13f2e0 Revert "Bump actions/upload-artifact from 6 to 7" (#1779) 2026-03-11 01:02:49 +01:00
HannahPadd
db0b3d72b2 revert 2026-03-11 00:06:40 +01:00
HannahPadd
b55448c421 revert 2026-03-11 00:05:05 +01:00
HannahPadd
9bf559ab97 PR feedback 2026-03-11 00:02:03 +01:00
HannahPadd
e79dfd514c Pr feedback
lint
2026-03-10 23:42:34 +01:00
HannahPadd
8e98a2bab6 Merge remote-tracking branch 'origin/main' into hannah/steam 2026-03-10 23:40:22 +01:00
dependabot[bot]
7965d2e081 Bump actions/upload-artifact from 6 to 7 (#1770)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-10 23:33:56 +01:00
HannahPadd
b2aa3ab394 PR feedback 2026-03-10 22:53:55 +01:00
Butterscotch!
b829a20169 Bump Gradle, plugin, and package versions (#1746)
Co-authored-by: lucas lelievre <loucass003@gmail.com>
2026-03-10 22:40:41 +01:00
loucass003
653b8b4014 Make macos build 2026-03-10 22:04:43 +01:00
lucas lelievre
a891203204 Switch to Electron (#1747)
Co-authored-by: Hannah Lindrob <hannahlindrob@ourlook.com>
Co-authored-by: Sapphire <imsapphire0@gmail.com>
2026-03-10 21:38:02 +01:00
HannahPadd
6da2691f2b revert 2026-03-10 15:29:53 +01:00
HannahPadd
6227c682ee Merge branch 'llelievr/electron' into hannah/steam 2026-03-10 15:22:48 +01:00
HannahPadd
c1a864176f lint 2026-03-10 15:20:02 +01:00
HannahPadd
f5911f01b7 Update FeatureFlags 2026-03-10 15:07:29 +01:00
HannahPadd
daeeac2baa Added cli flags for running installer and udev warning 2026-03-10 13:15:47 +01:00
loucass003
13930a402e Udev rules files for deb | rpm 2026-03-10 09:51:22 +00:00
loucass003
dbc7f57274 fix matrix 2026-03-09 22:28:50 +01:00
loucass003
d4002114ef fix matrix 2026-03-09 22:27:46 +01:00
loucass003
f9653e941a fix matrix 2026-03-09 22:27:03 +01:00
loucass003
d2f4721b8a Reduce amount of builds on prs 2026-03-09 22:25:22 +01:00
loucass003
764b034abe try macos one more time 2026-03-09 21:44:40 +01:00
loucass003
a13e5e9650 try macos one more time 2026-03-09 18:32:53 +01:00
loucass003
a57c7fc1c3 try macos one more time 2026-03-09 18:25:46 +01:00
loucass003
d4c53086dc try macos one more time 2026-03-09 18:19:03 +01:00
loucass003
25a8bd144d better zips 2026-03-09 17:31:24 +01:00
HannahPadd
43544f0c86 Merge remote-tracking branch 'origin/llelievr/electron' into hannah/steam 2026-03-09 17:12:43 +01:00
HannahPadd
bfd5f02ff2 PR feedback, added launch arguments for steam and driver installer 2026-03-09 15:39:52 +01:00
HannahPadd
3172271ddd Updated registry logic in RegEdit.kt
Moved installer registry checks to RegEdit.kt
2026-03-09 14:38:08 +01:00
HannahPadd
a554b46263 Updated registry logic in RegEdit.kt
Moved installer registry checks to RegEdit.kt
2026-03-09 14:26:37 +01:00
loucass003
d52ea42d01 fix archives names 2026-03-09 13:30:56 +01:00
loucass003
903b59201d fix appimage 2026-03-09 13:17:32 +01:00
loucass003
c181d83245 temp enable draft release 2026-03-09 12:57:04 +01:00
loucass003
8ebaf51750 export dir 2026-03-09 12:44:10 +01:00
loucass003
76814e041c mkdir on win 2026-03-09 12:34:04 +01:00
loucass003
4e09bd26fb folders 2026-03-09 12:28:16 +01:00
loucass003
5084f27611 more tests 2026-03-09 12:23:58 +01:00
loucass003
b3932e3b0f Test without macos 2026-03-09 12:07:45 +01:00
loucass003
ffb4ca6f61 ???? 2026-03-09 12:00:59 +01:00
loucass003
28c241d6c5 release test 2026-03-09 11:53:55 +01:00
loucass003
d7735db8bf release test 2026-03-09 11:43:28 +01:00
loucass003
2d3f168b88 release test 2026-03-09 11:43:01 +01:00
loucass003
ca79d16420 Better? 2026-03-09 11:04:09 +01:00
loucass003
3fe4f8f94d forgot one recursive 2026-03-09 09:58:37 +01:00
loucass003
0b9b2394c0 unified workflow 2026-03-09 09:56:52 +01:00
loucass003
d84cb7f0bf Fix folders 2026-03-09 09:42:51 +01:00
loucass003
2c32fde7d1 Fix typos 2026-03-09 09:39:30 +01:00
loucass003
838fadf523 More archive files 2026-03-09 09:33:13 +01:00
loucass003
01a63a2e28 Pipeline optimization 2026-03-09 09:27:17 +01:00
loucass003
8f73937a52 Small fixes + maybe better ci? 2026-03-09 09:20:00 +01:00
loucass003
c46def9e31 Merge branch 'main' of github.com:SlimeVR/SlimeVR-Server into llelievr/electron 2026-03-09 09:00:19 +01:00
loucass003
3e0297a355 Merge branches 'llelievr/electron' and 'llelievr/electron' of github.com:SlimeVR/SlimeVR-Server into llelievr/electron 2026-03-09 08:59:51 +01:00
Hannah
e087d76781 Apply suggestion from @ImSapphire
Co-authored-by: Sapphire <imsapphire0@gmail.com>
2026-03-05 19:33:56 +01:00
HannahPadd
7b5f526fe6 PR Feedback 2026-03-05 19:33:29 +01:00
HannahPadd
51247f23bf Remove udev installation since it's now shown to the user in the gui.
Updated the logic to detect if udev rules are installed skip the check when running on SteamOS or NixOS.
Remove the unused ErrorConsentModal.
PR Feedback
2026-03-05 18:19:53 +01:00
HannahPadd
c87e3eaccb lint 2026-03-05 17:01:52 +01:00
HannahPadd
e477258d67 Revert paths.ts 2026-03-05 15:26:45 +01:00
HannahPadd
7132268d58 Moved usb driver install from bat file to powershell oneliner 2026-03-05 15:23:23 +01:00
Hannah
1ee4e79b4e Apply suggestions from code review
Co-authored-by: Sapphire <imsapphire0@gmail.com>
2026-03-05 10:50:05 +01:00
HannahPadd
bbfa45a5c9 Update submodule 2026-03-04 18:16:20 +01:00
HannahPadd
81f50f39c5 I forgor to stage files 2026-03-04 18:15:59 +01:00
HannahPadd
3b40c9ec06 Fixed some variable typing 2026-03-04 17:07:29 +01:00
HannahPadd
bbdf16ee42 moved keybinds branch from personal fork to slime repo 2026-03-04 14:54:21 +01:00
HannahPadd
2a4a72eaf7 PR feedback
Fixed mistake with disabling installer.
Update Windows usb driver install to read log from pnputil.
2026-03-04 10:39:09 +01:00
HannahPadd
6b227f8324 lint 2026-03-03 16:28:23 +01:00
HannahPadd
2247725ba5 revert package.json in solarXR 2026-03-03 16:22:58 +01:00
HannahPadd
be3494cdf8 Applied PR feedback
Added system env variable to disable installer
2026-03-03 16:09:15 +01:00
HannahPadd
9cf0a637d3 Merge remote-tracking branch 'origin/hannah/steam' into hannah/steam 2026-03-03 13:53:24 +01:00
HannahPadd
fd29f41276 update steamvr driver install
Update logging messages
2026-03-03 13:51:56 +01:00
HannahPadd
72d193e5e9 update Logging
Skip Installation of drivers if user is not using the steam version.
2026-03-03 10:54:11 +01:00
HannahPadd
c2f2fc219b Revert ProtobufBridge.kt ProtobufMessages.java SteamVRBridge.kt.
Remove wrong return string from executeShellCommand in RPCInstallInfoHandler.kt
2026-03-02 11:12:18 +01:00
HannahPadd
7e7e67631e Remove duplicate Updater.kt
Added specific Nix and SteamOS configs.
Updated Feeder install function on Linux.
Update Feeder install function on Windows.
Lint.
2026-03-02 10:53:24 +01:00
HannahPadd
a0e8e356bb PR Suggestions 2026-02-27 18:12:47 +01:00
HannahPadd
35955f77df Added modal to show user if udev rules aren't installed. Added function to get path to exe dir inside react. Moved the Error collecting consent modal to a page. 2026-02-27 17:50:58 +01:00
HannahPadd
cd7a58501c Added function to the server to install it's own drivers. 2026-02-27 17:48:11 +01:00
lucas lelievre
9353504b39 Apply suggestions from code review
Co-authored-by: Sapphire <imsapphire0@gmail.com>
2026-02-26 20:15:33 +01:00
loucass003
daf818b2cd Merge branch 'llelievr/electron' of github.com:SlimeVR/SlimeVR-Server into llelievr/electron 2026-02-25 17:24:49 +01:00
Hannah Lindrob
34d35fa6fd Merge remote-tracking branch 'origin/main' into llelievr/electron 2026-02-23 16:56:02 +01:00
loucass003
ca1c453451 Merge branch 'main' of github.com:SlimeVR/SlimeVR-Server into llelievr/electron 2026-02-23 16:55:13 +01:00
loucass003
84806c44db Small fixes 2026-02-20 09:11:22 +01:00
loucass003
24b2c8722b Remove unused assets + formating 2026-02-19 19:01:36 +01:00
loucass003
215228551d remove console logs 2026-02-18 13:23:49 +01:00
loucass003
f765ef0e4a Remove android icons from electron 2026-02-18 13:07:50 +01:00
loucass003
c956e5d1cb Revert pnpm dev to pnpm gui 2026-02-18 13:00:49 +01:00
loucass003
865ffc5543 Put back if in gradle.yml ci 2026-02-18 12:57:32 +01:00
loucass003
25e771f4f5 Moar lint 2026-02-18 12:55:43 +01:00
loucass003
74628d215a Lint 2026-02-18 12:50:54 +01:00
loucass003
f19f5cbe51 Re enable hardware accel 2026-02-18 12:50:12 +01:00
loucass003
711d0cae63 Fix resources 2026-02-18 12:45:58 +01:00
loucass003
60321891b8 Merge branch 'llelievr/electron' of github.com:SlimeVR/SlimeVR-Server into llelievr/electron 2026-02-18 12:45:39 +01:00
loucass003
b89a290ac9 Fix resources folder 2026-02-18 12:45:19 +01:00
lucas lelievre
c4d0fe59fd Update gui/electron/main/paths.ts
Co-authored-by: Sapphire <imsapphire0@gmail.com>
2026-02-18 12:45:07 +01:00
lucas lelievre
58a9d3b6eb Update gui/src/components/commons/SystemFileInput.tsx
Co-authored-by: Sapphire <imsapphire0@gmail.com>
2026-02-18 12:44:54 +01:00
loucass003
a3314fa4f9 icons and extraFiles 2026-02-18 12:23:27 +01:00
loucass003
778e0bde56 New flake 2026-02-18 11:07:48 +01:00
Hannah Lindrob
5a6ef6dee5 Fix issue with jar launching on steam 2026-02-17 13:05:57 +01:00
Hannah Lindrob
ba5adca358 Fix working dir 2026-02-17 11:55:57 +01:00
loucass003
67f1ac7684 Progress 2026-02-17 10:14:07 +01:00
loucass003
636514361d Server launched from electron + discord presence 2026-02-17 10:13:35 +01:00
loucass003
63d36db15e Final CI? 2026-02-16 18:49:07 +01:00
loucass003
9b8073153a Maybe progress 2026-02-16 17:34:13 +01:00
loucass003
7b40b8d315 Progress on workflow 2026-02-16 14:18:00 +01:00
loucass003
0f1a19e04b Progress on workflow 2026-02-16 14:16:49 +01:00
loucass003
d1387508e6 Progress 2026-02-16 14:11:19 +01:00
loucass003
50a2be81f7 Progress 2026-02-14 03:04:40 +01:00
loucass003
29598583a9 Lockfile 2026-02-14 01:38:18 +01:00
loucass003
e83adadf1f There is no way this works. RIGHT?! 2026-02-14 01:37:09 +01:00
loucass003
87699438b4 Progress? 2026-02-13 23:49:16 +01:00
loucass003
d1cf5dee24 MOAR 2026-02-12 20:52:51 +01:00
loucass003
b90f52ad7b WIP 2026-02-12 20:37:50 +01:00
loucass003
af917d7158 WIP 2026-02-12 18:46:54 +01:00
202 changed files with 6823 additions and 11654 deletions

1
.envrc
View File

@@ -2,7 +2,6 @@ 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 rust-toolchain.toml
nix_direnv_watch_file package.json
if ! use flake . --impure
then

3
.github/CODEOWNERS vendored
View File

@@ -17,8 +17,7 @@
/gui/src/components/settings/ @Erimelowo @loucass003
# Rust part of the GUI
/gui/src-tauri/ @loucass003
/Cargo.lock @loucass003
/gui/electron/ @loucass003
# Some server code~
/server/ @ButterscotchV @Eirenliel @Erimelowo

View File

@@ -1,129 +0,0 @@
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@v6
with:
submodules: recursive
- uses: pnpm/action-setup@v4
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.node-version'
cache: 'pnpm'
- name: Build
run: |
pnpm i
cd gui
pnpm run lint
build:
strategy:
fail-fast: false
matrix:
os:
[
ubuntu-22.04,
windows-latest,
macos-latest,
ubuntu-22.04-arm,
windows-11-arm,
]
runs-on: ${{ matrix.os }}
env:
# Don't mark warnings as errors
CI: false
BUILD_ARCH: ${{ endsWith(matrix.os, 'arm') && 'aarch64' || 'amd64' }}
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- if: startsWith(matrix.os, 'ubuntu')
name: Set up Linux dependencies
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
with:
packages: libgtk-3-dev webkit2gtk-4.1 libappindicator3-dev librsvg2-dev patchelf
# Increment to invalidate the cache
version: ${{ format('v1.0-{0}', env.BUILD_ARCH) }}
# 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
- if: matrix.os == 'windows-11-arm'
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false
- name: Cache cargo dependencies
uses: Swatinem/rust-cache@v2
- uses: pnpm/action-setup@v4
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.node-version'
cache: 'pnpm'
- name: Install dependencies
shell: bash
run: pnpm i
- name: Build
shell: bash
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
NODE_OPTIONS: ${{ matrix.os == 'macos-latest' && '--max-old-space-size=4096' || '' }}
run: pnpm run skipbundler --config $( ./gui/scripts/gitversion.mjs )
- if: startsWith(matrix.os, 'windows')
name: Upload a Build Artifact (Windows)
uses: actions/upload-artifact@v6
with:
# Artifact name
name: ${{ format('SlimeVR-GUI-Windows-{0}', env.BUILD_ARCH) }}
# A file, directory or wildcard pattern that describes what to upload
path: target/release/slimevr.exe
- if: startsWith(matrix.os, 'ubuntu')
name: Upload a Build Artifact (Linux)
uses: actions/upload-artifact@v6
with:
# Artifact name
name: ${{ format('SlimeVR-GUI-Linux-{0}', env.BUILD_ARCH) }}
# 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@v6
with:
# Artifact name
name: SlimeVR-GUI-macOS
# A file, directory or wildcard pattern that describes what to upload
path: target/release/slimevr

263
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,263 @@
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: 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: 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: 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: 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: 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
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

View File

@@ -1,413 +0,0 @@
# 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@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
- 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@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 with Gradle
run: ./gradlew :server:desktop:shadowJar
- name: Upload the Server JAR as a Build Artifact
uses: actions/upload-artifact@v6
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/slimevr.jar
- name: Upload to draft release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
draft: true
generate_release_notes: true
files: |
server/desktop/build/libs/slimevr.jar
bundle-android:
runs-on: ubuntu-latest
steps:
- 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
- uses: pnpm/action-setup@v4
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.node-version'
cache: 'pnpm'
- name: Install dependencies
run: pnpm i
- name: Build GUI
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: cd gui && pnpm run build
- name: Build with Gradle
run: ./gradlew :server:android:build
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: Upload the Android build artifact
uses: actions/upload-artifact@v6
with:
# Artifact name
name: 'SlimeVR-Android' # optional, default is artifact
# A file, directory or wildcard pattern that describes what to upload
path: server/android/build/outputs/apk/*
- name: Prepare for release
if: startsWith(github.ref, 'refs/tags/')
run: |
cp server/android/build/outputs/apk/release/android-release.apk ./SlimeVR-android.apk
- name: Upload to draft release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
draft: true
generate_release_notes: true
files: |
./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/*
bundle-linux:
strategy:
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm]
runs-on: ${{ matrix.os }}
needs: [build, test]
if: contains(fromJSON('["workflow_dispatch", "create"]'), github.event_name)
env:
BUILD_ARCH: ${{ endsWith(matrix.os, 'arm') && 'aarch64' || 'amd64' }}
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions/download-artifact@v7
with:
name: 'SlimeVR-Server'
path: server/desktop/build/libs/
- name: Set up Linux dependencies
uses: awalsh128/cache-apt-pkgs-action@v1.6.0
with:
packages: |
build-essential curl wget file libssl-dev libgtk-3-dev libappindicator3-dev librsvg2-dev xdg-utils
# Increment to invalidate the cache
version: ${{ format('v1.0-{0}', env.BUILD_ARCH) }}
# 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: Set up specific Linux versioned dependencies
run: |
sudo apt-get update && sudo apt-get install -y \
libwebkit2gtk-4.1-0=2.44.0-2 \
libwebkit2gtk-4.1-dev=2.44.0-2 \
libjavascriptcoregtk-4.1-0=2.44.0-2 \
libjavascriptcoregtk-4.1-dev=2.44.0-2 \
gir1.2-javascriptcoregtk-4.1=2.44.0-2 \
gir1.2-webkit2-4.1=2.44.0-2;
- name: Cache cargo dependencies
uses: Swatinem/rust-cache@v2
- uses: pnpm/action-setup@v4
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.node-version'
cache: 'pnpm'
- name: Install dependencies
run: pnpm i
- name: Build
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: pnpm run tauri build --config $( ./gui/scripts/gitversion.mjs )
- name: Make GUI tarball
run: |
tar czf slimevr-gui-dist.tar.gz -C gui/dist/ .
- uses: actions/upload-artifact@v6
if: matrix.os == 'ubuntu-latest'
with:
name: SlimeVR-GUI-Dist
path: ./slimevr-gui-dist.tar.gz
- uses: actions/upload-artifact@v6
with:
name: ${{ format('SlimeVR-GUI-Deb-{0}', env.BUILD_ARCH) }}
path: target/release/bundle/deb/slimevr*.deb
- uses: actions/upload-artifact@v6
with:
name: ${{ format('SlimeVR-GUI-AppImage-{0}', env.BUILD_ARCH) }}
path: target/release/bundle/appimage/slimevr*.AppImage
- uses: actions/upload-artifact@v6
with:
name: ${{ format('SlimeVR-GUI-RPM-{0}', env.BUILD_ARCH) }}
path: target/release/bundle/rpm/slimevr*.rpm
- name: Prepare for release
if: startsWith(github.ref, 'refs/tags/')
run: |
cp target/release/bundle/appimage/slimevr*.AppImage "./SlimeVR-$BUILD_ARCH.appimage"
cp target/release/bundle/deb/slimevr*.deb "./SlimeVR-$BUILD_ARCH.deb"
cp target/release/bundle/rpm/slimevr*.rpm "./SlimeVR-$BUILD_ARCH.rpm"
- name: Upload to draft release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
draft: true
generate_release_notes: true
files: |
./slimevr-gui-dist.tar.gz
./SlimeVR-*.appimage
./SlimeVR-*.deb
./SlimeVR-*.rpm
bundle-mac:
runs-on: macos-latest
needs: [build, test]
if: contains(fromJSON('["workflow_dispatch", "create"]'), github.event_name)
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions/download-artifact@v7
with:
name: 'SlimeVR-Server'
path: server/desktop/build/libs/
- name: Cache cargo dependencies
uses: Swatinem/rust-cache@v2
- uses: pnpm/action-setup@v4
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.node-version'
cache: 'pnpm'
- name: Install dependencies
run: |
rustup target add x86_64-apple-darwin
pnpm i
- name: Build
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
NODE_OPTIONS: --max-old-space-size=4096
run: pnpm run tauri build --target universal-apple-darwin --config $( ./gui/scripts/gitversion.mjs )
- name: Modify Application
run: |
cd target/universal-apple-darwin/release/bundle/macos/slimevr.app/Contents/MacOS
cp $( git rev-parse --show-toplevel )/server/desktop/build/libs/slimevr.jar ./
cd ../../../
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName SlimeVR" slimevr.app/Contents/Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleName SlimeVR" slimevr.app/Contents/Info.plist
codesign --sign - --deep --force slimevr.app
mv slimevr.app SlimeVR.app
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@v6
with:
name: SlimeVR-GUI-MacApp
path: target/universal-apple-darwin/release/bundle/macos/SlimeVR*.app
- uses: actions/upload-artifact@v6
with:
name: SlimeVR-GUI-MacDmg
path: target/universal-apple-darwin/release/bundle/dmg/slimevr.dmg
- name: Prepare for release
if: startsWith(github.ref, 'refs/tags/')
run: |
cp target/universal-apple-darwin/release/bundle/dmg/slimevr.dmg ./SlimeVR-mac.dmg
- name: Upload to draft release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
draft: true
generate_release_notes: true
files: |
./SlimeVR-mac.dmg
bundle-windows:
strategy:
matrix:
os: [windows-latest, windows-11-arm]
runs-on: ${{ matrix.os }}
needs: [build, test]
if: contains(fromJSON('["workflow_dispatch", "create"]'), github.event_name)
env:
BUILD_ARCH: ${{ endsWith(matrix.os, 'arm') && 'win-aarch64' || 'win64' }}
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions/download-artifact@v7
with:
name: 'SlimeVR-Server'
path: server/desktop/build/libs/
- if: matrix.os == 'windows-11-arm'
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false
- name: Cache cargo dependencies
uses: Swatinem/rust-cache@v2
- uses: pnpm/action-setup@v4
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.node-version'
cache: 'pnpm'
- name: Install dependencies
shell: bash
run: pnpm i
- name: Build
shell: bash
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: pnpm run skipbundler --config $( ./gui/scripts/gitversion.mjs )
- 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-$BUILD_ARCH.zip" ./SlimeVR/
- uses: actions/upload-artifact@v6
with:
name: ${{ format('SlimeVR-GUI-Windows-{0}', env.BUILD_ARCH) }}
path: ./SlimeVR*.zip
- name: Upload to draft release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
draft: true
generate_release_notes: true
files: ./SlimeVR-*.zip

7
.gitignore vendored
View File

@@ -34,9 +34,6 @@
# ignore gradle build folder
build/
# Rust build artifacts
/target
# direnv has been claimed for Nix usage
.direnv/
.devenv
@@ -46,3 +43,7 @@ local.properties
# Ignore temporary config
vrconfig.yml.tmp
# Nixos
.bin/

View File

@@ -7,7 +7,6 @@
"gaborv.flatbuffers",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"rust-lang.rust-analyzer",
"bradlc.vscode-tailwindcss",
"EditorConfig.EditorConfig",
"macabeus.vscode-fluent",

View File

@@ -7,8 +7,6 @@ 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)
- [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.
@@ -32,13 +30,13 @@ 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.)
### Tauri (gui)
### Electron (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 run tauri build`. The result
will be at `target/release/slimevr.exe`.
- Finally, to compile for production, run `pnpm package:build`. The result
will be at `dist/artifacts/` content will change depending of the platform.
## Code style
@@ -84,7 +82,7 @@ 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.
### Tauri (gui)
### Electron (gui)
We use ESLint and Prettier to format GUI code.
- First, go into the GUI's directory with your terminal by running `cd gui`.
@@ -116,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.

6898
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
[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.82" # Tauri's MSRV
repository = "https://github.com/SlimeVR/SlimeVR-Server"
[profile.release]
lto = "thin"

242
deny.toml
View File

@@ -1,242 +0,0 @@
# This template contains all of the possible sections and their default values
# Note that all fields that take a lint level have these possible values:
# * deny - An error will be produced and the check will fail
# * warn - A warning will be produced, but the check will not fail
# * allow - No warning or error will be produced, though in some cases a note
# will be
# The values provided in this template are the default values that will be used
# when any section or field is not specified in your own configuration
# Root options
# The graph table configures how the dependency graph is constructed and thus
# which crates the checks are performed against
[graph]
# If 1 or more target triples (and optionally, target_features) are specified,
# only the specified targets will be checked when running `cargo deny check`.
# This means, if a particular package is only ever used as a target specific
# dependency, such as, for example, the `nix` crate only being used via the
# `target_family = "unix"` configuration, that only having windows targets in
# this list would mean the nix crate, as well as any of its exclusive
# dependencies not shared by any other crates, would be ignored, as the target
# list here is effectively saying which targets you are building for.
targets = [
# The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions
#"x86_64-unknown-linux-musl",
# You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
]
# When creating the dependency graph used as the source of truth when checks are
# executed, this field can be used to prune crates from the graph, removing them
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
# is pruned from the graph, all of its dependencies will also be pruned unless
# they are connected to another crate in the graph that hasn't been pruned,
# so it should be used with care. The identifiers are [Package ID Specifications]
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
#exclude = []
# If true, metadata will be collected with `--all-features`. Note that this can't
# be toggled off if true, if you want to conditionally enable `--all-features` it
# is recommended to pass `--all-features` on the cmd line instead
all-features = false
# If true, metadata will be collected with `--no-default-features`. The same
# caveat with `all-features` applies
no-default-features = false
# If set, these feature will be enabled when collecting metadata. If `--features`
# is specified on the cmd line they will take precedence over this option.
#features = []
# The output table provides options for how/if diagnostics are outputted
[output]
# When outputting inclusion graphs in diagnostics that include features, this
# option can be used to specify the depth at which feature edges will be added.
# This option is included since the graphs can be quite large and the addition
# of features from the crate(s) to all of the graph roots can be far too verbose.
# This option can be overridden via `--feature-depth` on the cmd line
feature-depth = 1
# This section is considered when running `cargo deny check advisories`
# More documentation for the advisories section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
[advisories]
# The path where the advisory databases are cloned/fetched into
#db-path = "$CARGO_HOME/advisory-dbs"
# The url(s) of the advisory databases to use
#db-urls = ["https://github.com/rustsec/advisory-db"]
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
#"RUSTSEC-0000-0000",
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
]
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
# See Git Authentication for more information about setting up git authentication.
#git-fetch-with-cli = true
# This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
[licenses]
# List of explicitly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [
"MIT",
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"Unicode-3.0",
"Unicode-DFS-2016",
"MIT-0",
"ISC",
"BSD-3-Clause",
"Zlib",
"MPL-2.0",
]
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
# canonical license text of a valid SPDX license file.
# [possible values: any between 0.0 and 1.0].
confidence-threshold = 0.8
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
# aren't accepted for every possible crate as with the normal allow list
exceptions = [
# Each entry is the crate and version constraint, and its specific allow
# list
#{ allow = ["Zlib"], crate = "adler32" },
]
# Some crates don't have (easily) machine readable licensing information,
# adding a clarification entry for it allows you to manually specify the
# licensing information
#[[licenses.clarify]]
# The package spec the clarification applies to
#crate = "ring"
# The SPDX expression for the license requirements of the crate
#expression = "MIT AND ISC AND OpenSSL"
# One or more files in the crate's source used as the "source of truth" for
# the license expression. If the contents match, the clarification will be used
# when running the license check, otherwise the clarification will be ignored
# and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration
#license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents
#{ path = "LICENSE", hash = 0xbd0eed23 }
#]
[licenses.private]
# If true, ignores workspace crates that aren't published, or are only
# published to private registries.
# To see how to mark a crate as unpublished (to the official registry),
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
ignore = false
# One or more private registries that you might publish crates to, if a crate
# is only published to private registries, and ignore is true, the crate will
# not have its license(s) checked
registries = [
#"https://sekretz.com/registry
]
# This section is considered when running `cargo deny check bans`.
# More documentation about the 'bans' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
[bans]
# Lint level for when multiple versions of the same crate are detected
multiple-versions = "warn"
# Lint level for when a crate version requirement is `*`
wildcards = "allow"
# The graph highlighting used when creating dotgraphs for crates
# with multiple versions
# * lowest-version - The path to the lowest versioned duplicate is highlighted
# * simplest-path - The path to the version with the fewest edges is highlighted
# * all - Both lowest-version and simplest-path are used
highlight = "all"
# The default lint level for `default` features for crates that are members of
# the workspace that is being checked. This can be overridden by allowing/denying
# `default` on a crate-by-crate basis if desired.
workspace-default-features = "allow"
# The default lint level for `default` features for external crates that are not
# members of the workspace. This can be overridden by allowing/denying `default`
# on a crate-by-crate basis if desired.
external-default-features = "allow"
# List of crates that are allowed. Use with care!
allow = [
#"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
]
# List of crates to deny
deny = [
#"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
# Wrapper crates can optionally be specified to allow the crate when it
# is a direct dependency of the otherwise banned crate
#{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
]
# List of features to allow/deny
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#[[bans.features]]
#crate = "reqwest"
# Features to not allow
#deny = ["json"]
# Features to allow
#allow = [
# "rustls",
# "__rustls",
# "__tls",
# "hyper-rustls",
# "rustls",
# "rustls-pemfile",
# "rustls-tls-webpki-roots",
# "tokio-rustls",
# "webpki-roots",
#]
# If true, the allowed features must exactly match the enabled feature set. If
# this is set there is no point setting `deny`
#exact = true
# Certain crates/versions that will be skipped when doing duplicate detection.
skip = [
#"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
]
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite.
skip-tree = [
#"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
#{ crate = "ansi_term@0.11.0", depth = 20 },
]
# This section is considered when running `cargo deny check sources`.
# More documentation about the 'sources' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
[sources]
# Lint level for what to happen when a crate from a crate registry that is not
# in the allow list is encountered
unknown-registry = "warn"
# Lint level for what to happen when a crate from a git repository that is not
# in the allow list is encountered
unknown-git = "warn"
# List of URLs for allowed crate registries. Defaults to the crates.io index
# if not specified. If it is specified but empty, no registries are allowed.
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
# List of URLs for allowed Git repositories
allow-git = []
[sources.allow-org]
# 1 or more github.com organizations to allow git sources for
github = [""]
# 1 or more gitlab.com organizations to allow git sources for
gitlab = [""]
# 1 or more bitbucket.org organizations to allow git sources for
bitbucket = [""]

39
flake.lock generated
View File

@@ -1,26 +1,5 @@
{
"nodes": {
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1756795219,
"narHash": "sha256-tKBQtz1JLKWrCJUxVkHKR+YKmVpm0KZdJdPWmR2slQ8=",
"owner": "nix-community",
"repo": "fenix",
"rev": "80dbdab137f2809e3c823ed027e1665ce2502d74",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
@@ -72,27 +51,9 @@
},
"root": {
"inputs": {
"fenix": "fenix",
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1756597274,
"narHash": "sha256-wfaKRKsEVQDB7pQtAt04vRgFphkVscGRpSx3wG1l50E=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "21614ed2d3279a9aa1f15c88d293e65a98991b30",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
}
},
"root": "root",

View File

@@ -1,74 +1,50 @@
{
description = "Affordable full-body tracking for VR!";
description = "SlimeVR Server & GUI";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = inputs@{ self, nixpkgs, flake-parts, fenix, ... }:
outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
systems = [ "x86_64-linux" ];
perSystem = { system, lib, ... }:
perSystem = { pkgs, ... }:
let
pkgs = import nixpkgs { inherit system; };
runtimeLibs = pkgs: (with pkgs; [
jdk17
rust_toolchain = lib.importTOML ./rust-toolchain.toml;
fenixPkgs = fenix.packages.${system};
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
rustToolchainSet = fenixPkgs.fromToolchainName {
name = rust_toolchain.toolchain.channel;
sha256 = "sha256-+9FmLhAOezBZCOziO0Qct1NOrfpjNsXxc/8I0c7BdKE=";
};
in {
wineWow64Packages.stable
zlib squashfsTools fakeroot libarchive icu
nodejs_22 pnpm pkg-config python3 gcc gnumake binutils git
pkgs.nodePackages.node-gyp-build
]);
devShells.default = pkgs.mkShell {
name = "slimevr";
slimeShell = pkgs.buildFHSEnv {
name = "slimevr-env";
targetPkgs = runtimeLibs;
profile = ''
export JAVA_HOME=${pkgs.jdk17}
export PATH="${pkgs.jdk17}/bin:$PATH"
buildInputs =
(with pkgs; [
cacert
]) ++ lib.optionals pkgs.stdenv.isLinux (with pkgs; [
atk cairo dbus dbus.lib dprint gdk-pixbuf glib.out glib-networking
gobject-introspection gtk3 harfbuzz libffi libsoup_3 openssl.dev pango
pkg-config treefmt webkitgtk_4_1 zlib
gst_all_1.gstreamer gst_all_1.gst-plugins-base
gst_all_1.gst-plugins-good gst_all_1.gst-plugins-bad
librsvg freetype expat libayatana-appindicator udev libusb1
]) ++ lib.optionals pkgs.stdenv.isDarwin [
pkgs.darwin.apple_sdk.frameworks.Security
] ++ [
pkgs.jdk17
pkgs.kotlin
rustToolchainSet.rustc
rustToolchainSet.cargo
rustToolchainSet.rustfmt
];
nativeBuildInputs = with pkgs; [ pnpm nodejs_22 gradle ];
RUST_BACKTRACE = 1;
GIO_EXTRA_MODULES = "${pkgs.glib-networking}/lib/gio/modules:${pkgs.dconf.lib}/lib/gio/modules";
shellHook = ''
export SLIMEVR_RUST_LD_LIBRARY_PATH="$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH="${pkgs.udev}/lib:${pkgs.libayatana-appindicator}/lib:$LD_LIBRARY_PATH"
export GST_PLUGIN_SYSTEM_PATH_1_0="${pkgs.gst_all_1.gstreamer.out}/lib/gstreamer-1.0:${pkgs.gst_all_1.gst-plugins-base}/lib/gstreamer-1.0:${pkgs.gst_all_1.gst-plugins-good}/lib/gstreamer-1.0:${pkgs.gst_all_1.gst-plugins-bad}/lib/gstreamer-1.0"
# Force linker and pkg-config to use udev from nixpkgs so libgudev/hidapi
# resolve against the correct libudev implementation at link time.
export PKG_CONFIG_PATH="${pkgs.udev}/lib/pkgconfig:${pkgs.glib}/lib/pkgconfig:$PKG_CONFIG_PATH"
export LIBRARY_PATH="${pkgs.udev}/lib:$LIBRARY_PATH"
export LD_RUN_PATH="${pkgs.udev}/lib:$LD_RUN_PATH"
export NIX_LDFLAGS="-L${pkgs.udev}/lib -ludev $NIX_LDFLAGS"
export LDFLAGS="-L${pkgs.udev}/lib -Wl,-rpath,${pkgs.udev}/lib -ludev $LDFLAGS"
# 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;
};
};
}

View File

@@ -13,8 +13,10 @@ android.useAndroidX=true
android.nonTransitiveRClass=true
org.gradle.unsafe.configuration-cache=false
kotlinVersion=2.0.20
spotlessVersion=8.0.0
shadowJarVersion=8.3.2
buildconfigVersion=5.5.0
grgitVersion=5.2.2
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

View File

@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip
distributionSha256Sum=f1771298a70f6db5a29daf62378c4e18a17fc33c9ba6b14362e0cdf40610380d
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

4
gui/.gitignore vendored
View File

@@ -29,9 +29,13 @@ yarn-error.log*
/dist
/stats.html
vite.config.ts.timestamp*
electron.vite.config.*.mjs
# eslint
.eslintcache
# Sentry Config File
.env.sentry-build-plugin
# electron
out/

70
gui/electron-builder.yml Normal file
View File

@@ -0,0 +1,70 @@
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"

View File

@@ -0,0 +1,47 @@
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 Normal file
View File

@@ -0,0 +1 @@
resources/java-version/JavaVersion.class

20
gui/electron/main/cli.ts Normal file
View File

@@ -0,0 +1,20 @@
import { Option, program } from "commander";
program
.option('-p, --path <path>', 'set launch path')
.option('-s, --steam', 'steam mode')
.option('-i, --install', 'run the driver installer')
.option(
'--skip-server-if-running',
'gui will not launch the server if it is already running'
)
.allowUnknownOption();
if (process.platform === "linux") {
const noUdevOption = new Option('--no-udev', 'disable udev warning');
noUdevOption.negate = false;
program.addOption(noUdevOption)
}
program.parse(process.argv);
export const options = program.opts();

460
gui/electron/main/index.ts Normal file
View File

@@ -0,0 +1,460 @@
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/appleTrayIcon.png?asset';
import { readFile, stat } from 'fs/promises';
import { getPlatform, handleIpc, isPortAvailable } from './utils';
import {
findServerJar,
findSystemJRE,
getExeFolder,
getGuiDataFolder,
getLogsFolder,
getServerDataFolder,
getWindowStateFile,
} from './paths';
import { stores } from './store';
import { 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';
app.setPath('userData', getGuiDataFolder())
app.setPath('sessionData', join(getGuiDataFolder(), 'electron'))
// Register custom protocol to handle asset paths with leading slashes
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();
case 'exe':
return getExeFolder();
}
});
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 '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.exit(0);
}
};
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.exit(0);
return;
}
logger.info({ javaBin, serverJar }, 'Found Java and server jar');
const serverArgs = ['-Xmx128M', '-jar', serverJar]
if (options.steam) serverArgs.push(`--steam`)
if (options.install) serverArgs.push(`--install`)
if (options.noUdev) serverArgs.push(`--no-udev`)
serverArgs.push('run')
const process = spawn(javaBin, serverArgs)
logger.info(`Java start command: ${serverArgs.join(' ')})`)
process.stdout?.on('data', (message) => {
mainWindow?.webContents.send(IPC_CHANNELS.SERVER_STATUS, {
message: message.toString(),
type: 'stdout',
} satisfies ServerStatusEvent);
});
process.stderr?.on('data', (message) => {
mainWindow?.webContents.send(IPC_CHANNELS.SERVER_STATUS, {
message: message.toString(),
type: 'stderr',
} satisfies ServerStatusEvent);
});
return {
process: process,
close: () => {
process.kill('SIGTERM');
},
};
};
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', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
process.on('exit', () => {
server?.close();
});
app.on('before-quit', async () => {
logger.info('App quitting, saving...');
server?.close();
stores.settings.save();
stores.cache.save();
discordPresence.destroy();
await saveWindowState();
});
});

View File

@@ -0,0 +1,26 @@
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);

124
gui/electron/main/paths.ts Normal file
View File

@@ -0,0 +1,124 @@
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 getExeFolder = () => {
return path.dirname(app.getPath('exe'));
}
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,
// 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));
};

View File

@@ -0,0 +1,49 @@
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();

View File

@@ -0,0 +1,76 @@
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),
};

View File

@@ -0,0 +1,50 @@
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);
});
}

View File

@@ -0,0 +1,40 @@
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'),
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),
getInstallDir: () => ipcRenderer.invoke(IPC_CHANNELS.GET_FOLDER, 'exe')
} satisfies IElectronAPI);

65
gui/electron/preload/interface.d.ts vendored Normal file
View File

@@ -0,0 +1,65 @@
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;
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;
getInstallDir: () => Promise<string>;
}
declare global {
interface Window {
electronAPI: IElectronAPI;
}
}

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 747 B

After

Width:  |  Height:  |  Size: 747 B

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 698 B

After

Width:  |  Height:  |  Size: 698 B

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1008 B

After

Width:  |  Height:  |  Size: 1008 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 922 B

After

Width:  |  Height:  |  Size: 922 B

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 579 B

After

Width:  |  Height:  |  Size: 579 B

View File

Before

Width:  |  Height:  |  Size: 555 B

After

Width:  |  Height:  |  Size: 555 B

View File

Before

Width:  |  Height:  |  Size: 848 B

After

Width:  |  Height:  |  Size: 848 B

View File

Before

Width:  |  Height:  |  Size: 848 B

After

Width:  |  Height:  |  Size: 848 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 645 B

After

Width:  |  Height:  |  Size: 645 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 848 B

After

Width:  |  Height:  |  Size: 848 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

View File

@@ -1,8 +1,8 @@
public class JavaVersion {
public static void main(String[] args)
{
var version = Runtime.version().version().get(0);
System.exit(version);
System.out.println(version);
}
}

View File

@@ -0,0 +1,26 @@
#!/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

View File

@@ -0,0 +1,8 @@
#!/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

49
gui/electron/shared.ts Normal file
View File

@@ -0,0 +1,49 @@
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') => 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' | 'exe') => string;
[IPC_CHANNELS.GH_FETCH]: <T extends GHGet>(
options: T
) => Promise<GHReturn[T['type']]>;
[IPC_CHANNELS.DISCORD_PRESENCE]: (options: DiscordPresence) => void;
}

View File

@@ -1,82 +1,55 @@
{
"name": "slimevr-ui",
"version": "0.5.1",
"private": true,
"name": "slimevr",
"version": "0.0.0",
"author": "SlimeVR Team <contact@slimevr.dev>",
"homepage": "https://slimevr.dev",
"type": "module",
"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"
},
"scripts": {
"start": "vite --force",
"dev:clean": "rm -rf node_modules/.vite && pnpm run gui",
"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"
},
"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",
"@react-hookz/deep-equal": "^3.0.3",
"@react-three/drei": "^9.114.3",
"@react-three/fiber": "^8.17.10",
"@sentry/react": "10.29.0",
"@sentry/vite-plugin": "^2.22.7",
"@tailwindcss/typography": "^0.5.15",
"@tanstack/react-query": "^5.48.0",
"@tauri-apps/api": "^2.0.2",
"@tauri-apps/plugin-dialog": "^2.0.0",
"@tauri-apps/plugin-fs": "2.4.1",
"@tauri-apps/plugin-http": "^2.5.0",
"@tauri-apps/plugin-log": "~2",
"@tauri-apps/plugin-opener": "^2.4.0",
"@tauri-apps/plugin-os": "^2.0.0",
"@tauri-apps/plugin-shell": "^2.3.0",
"@tauri-apps/plugin-store": "^2.4.1",
"@tweenjs/tween.js": "^25.0.0",
"@twemoji/svg": "^15.0.0",
"ajv": "^8.17.1",
"browser-fs-access": "^0.35.0",
"classnames": "^2.5.1",
"convert": "^5.12.0",
"flatbuffers": "22.10.26",
"intl-pluralrules": "^2.0.1",
"ip-num": "^1.5.1",
"jotai": "^2.12.2",
"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",
"semver": "^7.6.3",
"solarxr-protocol": "file:../solarxr-protocol",
"three": "^0.163.0",
"ts-pattern": "^5.4.0",
"typescript": "^5.6.3",
"use-double-tap": "^1.3.6",
"uuid": "^13.0.0",
"yup": "^1.4.0"
},
"scripts": {
"start": "vite --force",
"build": "vite build",
"dev": "tauri dev",
"skipbundler": "tauri build --no-bundle",
"tauri": "tauri",
"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}\"",
"preview-vite": "vite preview",
"javaversion-build": "cd src-tauri/src/ && javac JavaVersion.java && jar cvfe JavaVersion.jar JavaVersion JavaVersion.class",
"gen:javaversion": "cd src-tauri/src/ && javac JavaVersion.java && jar cvfe JavaVersion.jar JavaVersion JavaVersion.class",
"gen:firmware-tool": "openapi-codegen gen firmwareTool",
"gen:icons": "tauri icon --ios-color '#663499' src-tauri/icons/icon.svg"
},
"devDependencies": {
"@dword-design/eslint-plugin-import-alias": "^4.0.9",
"@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",
"@tauri-apps/cli": "~2",
"@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",
@@ -88,23 +61,54 @@
"@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",
"cross-env": "^7.0.3",
"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",
"vite": "^5.4.8"
}
"use-double-tap": "^1.3.6",
"uuid": "^13.0.0",
"vite": "^5.4.8",
"yup": "^1.4.0"
},
"main": "./out/main/index.js"
}

Binary file not shown.

View File

@@ -603,6 +603,29 @@ settings-stay_aligned-debug-label = Debugging
settings-stay_aligned-debug-description = Please include your settings when reporting problems about Stay Aligned.
settings-stay_aligned-debug-copy-label = Copy settings to clipboard
settings-keybinds = Keybind settings
settings-keybinds_ = ''
settings-keybinds-description = Change keybinds for various shortcuts
keybind_config-keybind_name = Keybind
keybind_config-keybind_value = Combination
keybind_config-keybind_delay = Delay before trigger (s)
settings-keybinds_full-reset = Full Reset
settings-keybinds_yaw-reset = Yaw Reset
settings-keybinds_mounting-reset = Mounting Reset
settings-keybinds_feet-mounting-reset = Feet Mounting Reset
settings-keybinds_pause-tracking = Pause Tracking
settings-keybinds_record-keybind = Click to record
settings-keybinds_now-recording = Recording…
settings-keybinds_reset-button = Reset
settings-keybinds_reset-all-button = Reset all
settings-keybinds-wayland-description = You appear to be using wayland, Please change your shortcuts in your system settings.
settings-keybinds-wayland-open-system-settings-button = Open system settings
settings-sidebar-keybinds = Keybinds
settings-keybinds-recorder-modal-title = Assign keybind for
settings-keybinds-recorder-modal-reset-button = Reset
settings-keybinds-recorder-modal-unbind-button = Unbind
settings-keybinds-recorder-modal-done-button = Done
## FK/Tracking settings
settings-general-fk_settings = Tracking settings
@@ -980,6 +1003,11 @@ onboarding-reset_tutorial-2 = Tap the highlighted tracker { $taps } times to tri
You need to be in a pose like you are skiing as shown in the Automatic Mounting wizard, and you have a 3 second delay (configurable) before it gets triggered.
## Install info
install-info_udev-rules_modal_title = Hardware udev access rules not found
install-info_udev-rules_warning = Access rules via udev are required for serial console access & dongle connection. Paste the following command into your terminal to add the udev rules.
install-info_udev-rules_modal_button = Close
install-info_udev-rules_modal-dont-show-again_checkbox = Don't show again
## Setup start
onboarding-home = Welcome to SlimeVR
onboarding-home-start = Let's get set up!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,6 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
/target/
WixTools
src/JavaVersion.class
/gen/schemas

View File

@@ -1,3 +0,0 @@
export default {
'**/*.rs': 'cargo fmt --',
};

View File

@@ -1,66 +0,0 @@
[package]
name = "slimevr"
version = "0.0.0"
description = "SlimeVR GUI Application"
license.workspace = true
repository.workspace = true
edition.workspace = true
rust-version.workspace = true
default-run = "slimevr"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
# by default Tauri runs in production mode
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
default = ["custom-protocol"]
# this feature is used used for production builds where `devPath` points to the filesystem
# DO NOT remove this
custom-protocol = ["tauri/custom-protocol"]
[build-dependencies]
tauri-build = { version = "2.0", features = [] }
cfg_aliases = "0.2"
shadow-rs = "0.35"
[dependencies]
serde_json = "1"
serde = { version = "1", features = ["derive"] }
tauri = { version = "2.0", features = ["devtools", "tray-icon", "image-png", "rustls-tls"] }
tauri-runtime = "2.0"
tauri-plugin-dialog = "2.0"
tauri-plugin-fs = "2.4.1"
tauri-plugin-os = "2.0"
tauri-plugin-shell = "2.3.0"
tauri-plugin-store = "2.0"
flexi_logger = "0.29"
log-panics = { version = "2", features = ["with-backtrace"] }
log = "0.4"
clap = { version = "4.0.29", features = ["derive"] }
clap-verbosity-flag = "2"
rand = "0.8.5"
tempfile = "3"
which = "6.0"
glob = "0.3"
open = "5"
shadow-rs = { version = "0.35", default-features = false }
const_format = "0.2.30"
cfg-if = "1"
color-eyre = "0.6"
rfd = { version = "0.15", features = ["gtk3"], default-features = false }
dirs-next = "2.0.0"
discord-sdk = "0.3.6"
tokio = { version = "1.37.0", features = ["time"] }
itertools = "0.13.0"
tauri-plugin-opener = "2.4.0"
tauri-plugin-http = "2.5.0"
tauri-plugin-log = "2"
[target.'cfg(windows)'.dependencies]
win32job = "1"
winreg = "0.52"
[target.'cfg(target_os = "linux")'.dependencies]
libloading = "0.8"

View File

@@ -1,15 +0,0 @@
use cfg_aliases::cfg_aliases;
fn main() -> shadow_rs::SdResult<()> {
// Bypass for Nix script having libudev-zero and Tauri not liking it
if let Some(path) = option_env!("SLIMEVR_RUST_LD_LIBRARY_PATH") {
println!("cargo:rustc-env=LD_LIBRARY_PATH={path}");
}
tauri_build::build();
cfg_aliases! {
mobile: { any(target_os = "ios", target_os = "android") },
desktop: { not(any(target_os = "ios", target_os = "android")) }
}
shadow_rs::new()
}

View File

@@ -1,66 +0,0 @@
{
"identifier": "migrated",
"description": "permissions that were migrated from v1",
"local": true,
"windows": [
"main"
],
"permissions": [
"core:default",
"core:window:allow-close",
"core:window:allow-toggle-maximize",
"core:window:allow-minimize",
"core:window:allow-start-dragging",
"core:window:allow-hide",
"core:window:allow-show",
"core:window:allow-set-focus",
"core:window:allow-destroy",
"core:window:allow-request-user-attention",
"core:window:allow-set-decorations",
"store:default",
"os:allow-os-type",
"os:allow-hostname",
"os:allow-locale",
"dialog:allow-open",
"dialog:allow-save",
"shell:allow-open",
"store:allow-get",
"store:allow-set",
"store:allow-save",
"fs:allow-write-text-file",
"fs:allow-read-text-file",
"fs:allow-exists",
{
"identifier": "fs:scope",
"allow": [
{
"path": "$APPDATA"
},
{
"path": "$APPDATA/**"
}
]
},
{
"identifier": "opener:allow-open-url",
"allow": [
{
"url": "steam:*"
},
{
"url": "ms-settings:network"
}
]
},
{
"identifier": "http:default",
"allow": [
{
"url": "https://github.com/SlimeVR/SlimeVR-Tracker-ESP/releases/download/*"
}
]
},
"opener:default",
"log:default"
]
}

View File

@@ -1,13 +0,0 @@
[Desktop Entry]
Version=1.5
Categories=Game;GTK;
Exec={{exec}}
Icon={{icon}}
Name=SlimeVR
GenericName=Full-body tracking
Comment=An app for facilitating full-body tracking in virtual reality
Keywords=FBT;VR;Steam;VRChat;IMU
Terminal=false
Type=Application

View File

@@ -1,139 +0,0 @@
<?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>Accessible full-body tracking in VR</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="donation">https://github.com/sponsors/SlimeVR</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" scheme_preference="light">#BB8AE5</color>
<color type="primary" scheme_preference="dark">#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>
<launchable type="desktop-id">safe-mode.dev.slimevr.SlimeVR.desktop</launchable>
<screenshots>
<screenshot type="default" xml:lang="en">
<caption>The onboarding for the GUI</caption>
<image>https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/main/assets/img/onboarding.png</image>
</screenshot>
</screenshots>
<provides>
<binary>slimevr</binary>
</provides>
<releases>
<release version="0.16.2" date="2025-08-01"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.2</url></release>
<release version="0.16.1" date="2025-07-27"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.1</url></release>
<release version="0.16.1~rc.2" type="development" date="2025-07-17"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.1-rc.2</url></release>
<release version="0.16.1~rc.1" type="development" date="2025-07-04"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.1-rc.1</url></release>
<release version="0.16.0" date="2025-07-01"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.0</url></release>
<release version="0.16.0~rc.2" type="development" date="2025-06-20"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.0-rc.2</url></release>
<release version="0.16.0~rc.1" type="development" date="2025-05-27"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.0-rc.1</url></release>
<release version="0.15.0" date="2025-05-19"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.15.0</url></release>
<release version="0.15.0~rc.4" type="development" date="2025-05-12"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.15.0-rc.4</url></release>
<release version="0.15.0~rc.3" type="development" date="2025-04-28"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.15.0-rc.3</url></release>
<release version="0.15.0~rc.2" type="development" date="2025-04-25"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.15.0-rc.2</url></release>
<release version="0.15.0~rc.1" type="development" date="2025-04-23"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.15.0-rc.1</url></release>
<release version="0.14.1" date="2025-04-15"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.14.1</url></release>
<release version="0.14.0" date="2025-04-10"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.14.0</url></release>
<release version="0.14.0~rc.2" type="development" date="2025-03-25"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.14.0-rc.2</url></release>
<release version="0.14.0~rc.1" type="development" date="2025-02-12"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.14.0-rc1</url></release>
<release version="0.13.2" date="2024-11-06"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.2</url></release>
<release version="0.13.1" date="2024-11-05"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.1</url></release>
<release version="0.13.1~rc.3" type="development" date="2024-10-31"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.1-rc.3</url></release>
<release version="0.13.1~rc.2" type="development" date="2024-10-26"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.1-rc.2</url></release>
<release version="0.13.1~rc.1" type="development" date="2024-10-16"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.1-rc.1</url></release>
<release version="0.13.0" date="2024-09-20"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.0</url></release>
<release version="0.13.0~rc.4" type="development" date="2024-09-13"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.0-rc.4</url></release>
<release version="0.13.0~rc.3" type="development" date="2024-08-14"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.0-rc.3</url></release>
<release version="0.13.0~rc.2" type="development" date="2024-08-08"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.0-rc.2</url></release>
<release version="0.13.0~rc.1" type="development" date="2024-08-02"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.13.0-rc.1</url></release>
<release version="0.12.1" date="2024-04-29"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.12.1</url></release>
<release version="0.12.0" date="2024-04-26"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.12.0</url></release>
<release version="0.12.0~rc.4" type="development" date="2024-04-21"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.12.0-rc.4</url></release>
<release version="0.12.0~rc.3" type="development" date="2024-04-14"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.12.0-rc.3</url></release>
<release version="0.12.0~rc.2" type="development" date="2024-04-09"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.12.0-rc.2</url></release>
<release version="0.12.0~rc.1" type="development" date="2024-04-04"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.12.0-rc.1</url></release>
<release version="0.11.0" date="2023-12-23"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.11.0</url></release>
<release version="0.11.0~rc.2" type="development" date="2023-12-08"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.11.0-rc.2</url></release>
<release version="0.11.0~rc.1" type="development" date="2023-11-23"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.11.0-rc.1</url></release>
<release version="0.10.1" date="2023-09-30"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.10.1</url></release>
<release version="0.10.1~rc.1" type="development" date="2023-09-29"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.10.1-rc.1</url></release>
<release version="0.10.0" date="2023-09-22"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.10.0</url></release>
<release version="0.10.0~rc.2" type="development" date="2023-09-15"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.10.0-rc.2</url></release>
<release version="0.10.0~rc.1" type="development" date="2023-09-02"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.10.0-rc.1</url></release>
<release version="0.9.1" date="2023-08-30"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.9.1</url></release>
<release version="0.9.1~rc.4" type="development" date="2023-08-28"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.9.1-rc.4</url></release>
<release version="0.9.1~rc.3" type="development" date="2023-08-19"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.9.1-rc.3</url></release>
<release version="0.9.1~rc.2" type="development" date="2023-08-15"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.9.1-rc.2</url></release>
<release version="0.9.1~rc.1" type="development" date="2023-08-13"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.9.1-rc.1</url></release>
<release version="0.9.0" date="2023-08-05"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.9.0</url></release>
<release version="0.9.0~rc.2" type="development" date="2023-08-02"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.9.0-rc.2</url></release>
<release version="0.9.0~rc.1" type="development" date="2023-07-31"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.9.0-rc.1</url></release>
<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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -1,11 +0,0 @@
@echo off
setlocal enableextensions
echo "TEST"
cd /d "C:\Program Files (x86)\SlimeVR Server"
jre\bin\java.exe -Xmx512M -jar slimevr.jar --no-gui
if %errorlevel% NEQ 0 (
pause
)

Some files were not shown because too many files have changed in this diff Show More