diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a395318794..e77607b6ee 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -84,23 +84,25 @@ jobs:
- name: GUI Lint
run: pnpm i && cd gui && pnpm run lint
- java-checks:
- name: Java Checks
+ server-checks:
+ name: Server Checks
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
- - name: Set up JDK 17
+ - name: Set up JDK 24
uses: actions/setup-java@v5
with:
- java-version: '17'
+ java-version: '24'
distribution: 'adopt'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- - name: Java Spotless Check
+ - name: Server Spotless Check
run: ./gradlew spotlessCheck --build-cache
+ - name: Server Detekt Lint
+ run: ./gradlew :server:detekt --build-cache
build-server-jar:
@@ -113,10 +115,10 @@ jobs:
submodules: recursive
- name: Get tags
run: git fetch --tags origin --recurse-submodules=no --force
- - name: Set up JDK 17
+ - name: Set up JDK 24
uses: actions/setup-java@v5
with:
- java-version: '17'
+ java-version: '24'
distribution: 'adopt'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
@@ -260,10 +262,10 @@ jobs:
submodules: recursive
- name: Get tags
run: git fetch --tags origin --recurse-submodules=no --force
- - name: Set up JDK 17
+ - name: Set up JDK 24
uses: actions/setup-java@v5
with:
- java-version: '17'
+ java-version: '24'
distribution: 'adopt'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
diff --git a/.gitignore b/.gitignore
index fc9250e505..30eaf86c05 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,8 +4,10 @@
# Syncthing ignore file
.stignore
-# Ignore .idea
-.idea
+# Ignore .idea, except shared project inspection profiles
+.idea/*
+!.idea/inspectionProfiles/
+!.idea/inspectionProfiles/*.xml
# Ignore .fleet
.fleet
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000000..dd667131c1
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000000..d819ece1a2
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000000..e2fb6f9175
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,64 @@
+# OpenCode Agent Instructions
+
+## Environment & Tooling
+- **Server (Kotlin/Java)**: Use `gradle` (`./gradlew`).
+ - Run: `./gradlew run`
+ - Build: `./gradlew shadowJar`
+ - Format: `./gradlew spotlessApply`
+- **GUI (Electron/TS)**: Use `pnpm`.
+ - Install: `pnpm i`
+ - Dev: `pnpm gui`
+ - Build: `pnpm package:build`
+ - Format: `pnpm run lint:fix`
+- **SolarXR Protocol (Submodule)**: Use `bash` and `pnpm`.
+ - Edit: Only `schema/` files.
+ - Generate: `./generate-flatbuffer.sh` (Linux/macOS).
+ - Sync: Run `pnpm i` in root after generation.
+
+## Architecture: The Reducer/Behaviour Pattern
+The server uses a reactive reducer pattern for thread safety and observability.
+
+- **Core Primitives**:
+ - `Context`: Holds `StateFlow`, `CoroutineScope`, and `Behaviour`s.
+ - `Behaviour`:
+ - `reduce(state, action)`: **Pure function**. No side effects.
+ - `observe(receiver)`: **Side effects**. Launch coroutines using `receiver.context.scope`.
+- **Lifecycle**: `Module.create()` (inert construction) $\rightarrow$ `module.startObserving()` (side-effect activation).
+- **DI**: Uses `Phase1ContextProvider` $\rightarrow$ `AppContextProvider` hierarchy.
+- **Communication**: Modules communicate via `dispatch(action)` or by observing another module's `StateFlow`.
+
+## Design & Coding Preferences
+- **Function over Class**: Prefer plain functions or `object` over classes for single-purpose logic.
+- **Function over Extension**: Use plain functions instead of extension functions unless the receiver is the primary subject.
+- **Action Selection**:
+ - Use `Update` (with a lambda) for internal state changes that require no external reaction.
+ - Use **Named Actions** when multiple behaviours must react to a specific event.
+- **State Management**: Only include data in `StateFlow` if it is required for observation by other modules.
+- **Communication**: Decouple modules by using `dispatch` or observing `StateFlow` rather than direct method calls.
+
+## Agent Workflow
+- **Phase 1: Discovery**: Use the `Glob` $\rightarrow$ `Grep` $\rightarrow$ `Read` pattern. Never guess file paths or contents.
+- **Phase 2: Planning**: For complex tasks, use `todowrite` to create a checklist. Present the plan to the user before implementation.
+- **Phase 3: Implementation**: Use `edit` for existing files; `write` for new files.
+- **Phase 4: Verification**: Always run the relevant linting or testing tools before marking a task as complete.
+
+## Common Pitfalls
+- **Coroutine Leaks**: Never launch coroutines in `observe` using a global scope; **always** use `receiver.context.scope`.
+- **Direct State Mutation**: Never attempt to mutate state properties directly. Always use `dispatch(action)`.
+- **Incomplete Cleanup**: Ensure any module owning a `Context` implements a `dispose()` method.
+
+## Conventions
+- **Files**: `module.kt` (Class/State/Actions) and `behaviours.kt` (Implementations).
+- **Actions**: Use `sealed interface`.
+- **State**: Data classes must use `val` only.
+- **Cleanup**: We use a direct `dispose()` method on modules instead of a `Disposable` interface.
+- **Detailed Conventions**: See `server/README.md` for full coding style and architecture details.
+
+## Testing
+- **Reducers**: Test by creating a `Context` with specific `Behaviour`s.
+- **Modules**: Use `TestAppContext` and `buildTest...` helpers from `TestServer.kt`.
+
+## Verification Protocol
+- **Linting**: Run `./gradlew spotlessApply` (Server) or `pnpm run lint:fix` (GUI).
+- **Testing**: Identify and run the relevant test suite (e.g., `./gradlew test` or specific module tests).
+- **Final Audit**: Perform a final `git diff` check to ensure no unintended changes were made.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 64c75649dc..6c81cd7300 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -22,21 +22,24 @@ Now you can open the codebase in [IDEA](https://www.jetbrains.com/idea/download/
### Java (server)
+Before contributing to the server, read [server/README.md](server/README.md) for an overview of its architecture and design guidelines.
+
The Java code is built with `gradle`, a CLI tool that manages java projects and their
dependencies.
- You can run the server by running `./gradlew run` in your IDE's terminal.
- To compile the code, run `./gradlew shadowJar`. The result will
-be at `server/build/libs/slimevr.jar` (you can ignore `server.jar`).
+be at `server/desktop/build/libs/slimevr.jar` (you can ignore `server.jar`).
(Note: Your IDE may be able to do all of the above for you.)
### Electron (gui)
-- Activate corepack (included with Node.JS) via `corepack enable` (might require administrator permissions)
+- Activate corepack (included with Node.js) via `corepack enable`
+(might require launching Git Bash as administrator on Windows)
- Run `pnpm i` in your IDE's terminal to download and install dependencies.
- To launch the GUI in dev mode, run `pnpm gui`.
-- Finally, to compile for production, run `pnpm package:build`. The result
-will be at `dist/artifacts/` content will change depending of the platform.
+- Finally, to compile for production, run `pnpm package:build`.
+The result will be in `gui/dist/artifacts/`. Content will change depending on the platform.
## Code style
diff --git a/README.md b/README.md
index 409b21e1b3..fd5f0655ea 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,8 @@ Latest setup instructions are [in our docs](https://docs.slimevr.dev/server/inde
## Building & Contributing
For information on building and contributing to the codebase, see [CONTRIBUTING.md](CONTRIBUTING.md).
+For an overview of the server architecture and design guidelines, see [server/README.md](server/README.md).
+
## Translating
Translation is done via Pontoon at [i18n.slimevr.dev](https://i18n.slimevr.dev/). Please join our [Discord translation forum](https://discord.com/channels/817184208525983775/1050413434249949235) to coordinate.
diff --git a/flake.lock b/flake.lock
index 0bf2adca63..b5ed91e99d 100644
--- a/flake.lock
+++ b/flake.lock
@@ -34,6 +34,22 @@
"type": "github"
}
},
+ "nixpkgs-jdk24": {
+ "locked": {
+ "lastModified": 1756787288,
+ "narHash": "sha256-rw/PHa1cqiePdBxhF66V7R+WAP8WekQ0mCDG4CFqT8Y=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "d0fc30899600b9b3466ddb260fd83deb486c32f1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "d0fc30899600b9b3466ddb260fd83deb486c32f1",
+ "type": "github"
+ }
+ },
"nixpkgs-lib": {
"locked": {
"lastModified": 1777168982,
@@ -52,7 +68,8 @@
"root": {
"inputs": {
"flake-parts": "flake-parts",
- "nixpkgs": "nixpkgs"
+ "nixpkgs": "nixpkgs",
+ "nixpkgs-jdk24": "nixpkgs-jdk24"
}
}
},
diff --git a/flake.nix b/flake.nix
index 1808e39208..086bfd68e0 100644
--- a/flake.nix
+++ b/flake.nix
@@ -3,6 +3,8 @@
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ # Pinned to a revision that still ships temurin jdk-24
+ nixpkgs-jdk24.url = "github:NixOS/nixpkgs/d0fc30899600b9b3466ddb260fd83deb486c32f1";
flake-parts.url = "github:hercules-ci/flake-parts";
};
@@ -10,6 +12,7 @@
inputs@{
self,
nixpkgs,
+ nixpkgs-jdk24,
flake-parts,
...
}:
@@ -20,8 +23,15 @@
];
perSystem =
- { lib, pkgs, ... }:
+ {
+ system,
+ lib,
+ pkgs,
+ ...
+ }:
let
+ java = (import nixpkgs-jdk24 { inherit system; }).jdk24;
+
runtimeLibs = [
pkgs.alsa-lib
pkgs.libpulseaudio
@@ -114,7 +124,8 @@
devShells.default = pkgs.mkShell {
packages = [
# for running the jar
- pkgs.jdk17
+ java
+
# for build
pkgs.electron
pkgs.rpm
@@ -139,7 +150,7 @@
];
buildInputs = runtimeLibs;
- JAVA_HOME = "${pkgs.jdk17}/lib/openjdk";
+ JAVA_HOME = "${java}/lib/openjdk";
USE_SYSTEM_FPM = "true";
ELECTRON_BUILDER_7ZIP_PATH = "${pkgs.p7zip}/bin/7za";
APPIMAGE_TOOLS_PATH = "${appImageTools}";
diff --git a/gradle.properties b/gradle.properties
index 484976e451..97a27f5026 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -17,7 +17,4 @@ kotlinVersion=2.3.10
spotlessVersion=8.2.1
shadowJarVersion=9.3.1
buildconfigVersion=6.0.7
-
-# Fix gradle leaving multiple java processes running that accumulate and use multiple GBs of RAM
-# This mostly seems to affect it when running from console, and then sending a SIGINT (Ctrl + C)
-org.gradle.daemon.idletimeout=10000
+wireVersion=5.3.1
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index a4b76b9530..1b33c55baa 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index fefb12bccd..ed0b3c1ea4 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip
-distributionSha256Sum=f1771298a70f6db5a29daf62378c4e18a17fc33c9ba6b14362e0cdf40610380d
+distributionSha256Sum=bbaeb2fef8710818cf0e261201dab964c572f92b942812df0c3620d62a529a01
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index f5feea6d6b..23d15a9367 100755
--- a/gradlew
+++ b/gradlew
@@ -86,8 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
-' "$PWD" ) || exit
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -115,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -206,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
-# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
@@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
- org.gradle.wrapper.GradleWrapperMain \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
diff --git a/gradlew.bat b/gradlew.bat
index 9d21a21834..db3a6ac207 100755
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+set CLASSPATH=
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/gui/public/i18n/en/translation.ftl b/gui/public/i18n/en/translation.ftl
index ab43c26b63..2fef026c66 100644
--- a/gui/public/i18n/en/translation.ftl
+++ b/gui/public/i18n/en/translation.ftl
@@ -292,9 +292,6 @@ widget-overlay = Overlay
widget-overlay-is_visible_label = Show Overlay in SteamVR
widget-overlay-is_mirrored_label = Display Overlay as Mirror
-## Widget: Drift compensation
-widget-drift_compensation-clear = Clear drift compensation
-
## Widget: Clear Mounting calibration
widget-clear_mounting = Clear mounting calibration
@@ -389,9 +386,6 @@ tracker-settings-assignment_section-edit = Edit assignment
tracker-settings-mounting_section = Mounting orientation
tracker-settings-mounting_section-description = Where is the tracker mounted?
tracker-settings-mounting_section-edit = Edit mounting
-tracker-settings-drift_compensation_section = Allow drift compensation
-tracker-settings-drift_compensation_section-description = Should this tracker compensate for its drift when drift compensation is enabled?
-tracker-settings-drift_compensation_section-edit = Allow drift compensation
tracker-settings-use_mag = Allow magnetometer on this tracker
# Multiline!
tracker-settings-use_mag-description =
@@ -481,13 +475,12 @@ mounting_selection_menu-close = Close
## Sidebar settings
settings-sidebar-title = Settings
settings-sidebar-general = General
-settings-sidebar-steamvr = SteamVR
+settings-sidebar-output = Output
settings-sidebar-tracker_mechanics = Tracker mechanics
settings-sidebar-stay_aligned = Stay Aligned
settings-sidebar-fk_settings = Tracking settings
settings-sidebar-gesture_control = Gesture control
settings-sidebar-interface = Interface
-settings-sidebar-osc_router = OSC router
settings-sidebar-osc_trackers = VRChat OSC Trackers
settings-sidebar-osc_vmc = VMC
settings-sidebar-utils = Utilities
@@ -501,15 +494,16 @@ settings-sidebar-firmware-tool = DIY Firmware Tool
settings-sidebar-vrc_warnings = VRChat Config Warnings
settings-sidebar-advanced = Advanced
-## SteamVR settings
-settings-general-steamvr = SteamVR
-settings-general-steamvr-subtitle = SteamVR trackers
+## Output settings
+# Some keys have steamvr in them as we switched to output but want to keep existing translations
+settings-general-output = Output
+settings-general-output_trackers = Output trackers
# Not all translation keys support multiline, only the ones that specify it will actually
# split it in lines (that also means you can split in lines however you want in those).
# The first spaces (not tabs) for indentation will be ignored, just to make the file look nice when writing.
# This one is one of this cases that cares about multilines
-settings-general-steamvr-description =
- Enable or disable specific SteamVR trackers.
+settings-general-output_trackers-description =
+ Enable or disable specific trackers to output.
Useful for games or apps that only support certain trackers.
settings-general-steamvr-trackers-waist = Waist
settings-general-steamvr-trackers-chest = Chest
@@ -522,10 +516,9 @@ settings-general-steamvr-trackers-right_elbow = Right elbow
settings-general-steamvr-trackers-left_hand = Left hand
settings-general-steamvr-trackers-right_hand = Right hand
settings-general-steamvr-trackers-tracker_toggling = Automatic tracker assignment
-settings-general-steamvr-trackers-tracker_toggling-description = Automatically handles toggling SteamVR trackers on or off depending on your current tracker assignments.
+settings-general-output-trackers-tracker_toggling-description = Automatically handles toggling output trackers on or off depending on your current tracker assignments.
settings-general-steamvr-trackers-tracker_toggling-label = Automatic tracker assignment
-settings-general-steamvr-trackers-hands-warning = Warning: Enabling the SteamVR hand trackers will disable inputs from real controllers.
- This should only be enabled if you are using SlimeVR for hand tracking.
+settings-general-output-trackers-hands-warning = Warning: Enabling the hand trackers will disable inputs from real controllers in SteamVR.
Are you sure you want to do this?
settings-general-steamvr-trackers-hands-warning-cancel = Cancel
@@ -547,25 +540,6 @@ settings-general-tracker_mechanics-filtering-type-prediction = Prediction
settings-general-tracker_mechanics-filtering-type-prediction-description = Reduces latency and makes movements more snappy, but may increase jitter.
settings-general-tracker_mechanics-filtering-amount = Amount
settings-general-tracker_mechanics-yaw-reset-smooth-time = Yaw reset smooth time (0s disables smoothing)
-settings-general-tracker_mechanics-drift_compensation = Drift compensation
-# This cares about multilines
-settings-general-tracker_mechanics-drift_compensation-description =
- Compensates for IMU yaw drift by applying an inverse rotation.
- Change the amount of compensation and the number of resets taken into account.
- This should only be used if you need to reset very often!
-settings-general-tracker_mechanics-drift_compensation-enabled-label = Drift compensation
-settings-general-tracker_mechanics-drift_compensation-prediction = Drift compensation prediction
-# This cares about multilines
-settings-general-tracker_mechanics-drift_compensation-prediction-description =
- Predicts yaw drift compensation beyond previously measured range.
- Enable this if your trackers are continuously spinning on the yaw axis.
-settings-general-tracker_mechanics-drift_compensation-prediction-label = Drift compensation prediction
-settings-general-tracker_mechanics-drift_compensation_warning =
- Warning: Only use drift compensation if you need to reset
- very often (every ~5-10 minutes).
-
- Some IMUs prone to frequent resets include:
- Joy-Cons, owoTrack, and MPUs (without recent firmware).
settings-general-tracker_mechanics-drift_compensation_warning-cancel = Cancel
settings-general-tracker_mechanics-drift_compensation_warning-done = I understand
settings-general-tracker_mechanics-drift_compensation-amount-label = Compensation amount
@@ -589,9 +563,8 @@ settings-stay_aligned = Stay Aligned
settings-stay_aligned-description = Stay Aligned reduces drift by gradually adjusting your trackers to match your relaxed poses.
settings-stay_aligned-setup-label = Setup Stay Aligned
settings-stay_aligned-setup-description = You must complete "Setup Stay Aligned" to enable Stay Aligned.
-settings-stay_aligned-warnings-drift_compensation = ⚠ Please turn off Drift Compensation! Drift Compensation will conflict with Stay Aligned.
settings-stay_aligned-enabled-label = Adjust trackers
-settings-stay_aligned-hide_yaw_correction-label = Hide adjustment (to compare with no Stay Aligned)
+settings-stay_aligned-hide_yaw_correction-label = Hide adjustment (to compare without Stay Aligned)
settings-stay_aligned-general-label = General
settings-stay_aligned-relaxed_poses-label = Relaxed Poses
settings-stay_aligned-relaxed_poses-description = Stay Aligned uses your relaxed poses to keep the trackers aligned. Use "Setup Stay Aligned" to update these poses.
@@ -653,17 +626,10 @@ settings-general-fk_settings-arm_fk-tpose_down = T-pose (down)
settings-general-fk_settings-arm_fk-tpose_down-description = Expects your arms to be 90 degrees up to the sides during Full Reset, and down at your sides during Mounting Calibration.
settings-general-fk_settings-arm_fk-forward = Forward
settings-general-fk_settings-arm_fk-forward-description = Expects your arms to be raised forward at 90 degrees. Useful for VTubing.
-settings-general-fk_settings-skeleton_settings-toggles = Skeleton toggles
-settings-general-fk_settings-skeleton_settings-description = Toggle skeleton settings on or off. It is recommended to leave these on.
-settings-general-fk_settings-skeleton_settings-extended_spine_model = Extended spine model
-settings-general-fk_settings-skeleton_settings-extended_pelvis_model = Extended pelvis model
-settings-general-fk_settings-skeleton_settings-extended_knees_model = Extended knee model
settings-general-fk_settings-skeleton_settings-ratios = Skeleton ratios
settings-general-fk_settings-skeleton_settings-ratios-description = Change the values of skeleton settings. You may need to adjust your proportions after changing these.
-settings-general-fk_settings-skeleton_settings-impute_waist_from_chest_hip = Impute waist from chest to hip
-settings-general-fk_settings-skeleton_settings-impute_waist_from_chest_legs = Impute waist from chest to legs
-settings-general-fk_settings-skeleton_settings-impute_hip_from_chest_legs = Impute hip from chest to legs
-settings-general-fk_settings-skeleton_settings-impute_hip_from_waist_legs = Impute hip from waist to legs
+settings-general-fk_settings-skeleton_settings-impute_spine_from_top_down = Impute missing spine trackers from upper to lower
+settings-general-fk_settings-skeleton_settings-impute_spine_curvature = How much to curve the spine with at least 2 adjacent spine trackers
settings-general-fk_settings-skeleton_settings-interp_hip_legs = Average the hip's yaw and roll with the legs'
settings-general-fk_settings-skeleton_settings-interp_knee_tracker_ankle = Average the knee trackers' yaw and roll with the ankles'
settings-general-fk_settings-skeleton_settings-interp_knee_ankle = Average the knees' yaw and roll with the ankles'
@@ -695,7 +661,7 @@ settings-general-gesture_control-fullResetEnabled = Enable tap to full reset
settings-general-gesture_control-fullResetDelay = Full reset delay
settings-general-gesture_control-fullResetTaps = Taps for full reset
settings-general-gesture_control-fullResetTracker = Full Reset Tracker
-settings-general-gesture_control-mountingResetEnabled = Enable tap to perform mounting calibration
+settings-general-gesture_control-mountingResetEnabled = Enable tap to mounting reset
settings-general-gesture_control-mountingResetDelay = Mounting calibration delay
settings-general-gesture_control-mountingResetTaps = Taps for mounting calibration
settings-general-gesture_control-mountingResetTracker = Mounting Reset Tracker
@@ -805,30 +771,6 @@ settings-serial-send_command-warning =
settings-serial-send_command-warning-ok = I know what I'm doing
settings-serial-send_command-warning-cancel = Cancel
-## OSC router settings
-settings-osc-router = OSC router
-# This cares about multilines
-settings-osc-router-description =
- Forward OSC messages from another program.
- Useful for using another OSC program with VRChat, for example.
-settings-osc-router-enable = Enable
-settings-osc-router-enable-description = Toggle the forwarding of messages.
-settings-osc-router-enable-label = Enable
-settings-osc-router-network = Network ports
-# This cares about multilines
-settings-osc-router-network-description =
- Set the ports for listening and sending data.
- These can be the same as other ports used in the SlimeVR server.
-settings-osc-router-network-port_in =
- .label = Port In
- .placeholder = Port in (default: 9002)
-settings-osc-router-network-port_out =
- .label = Port Out
- .placeholder = Port out (default: 9000)
-settings-osc-router-network-address = Network address
-settings-osc-router-network-address-description = Set the address to send out data at.
-settings-osc-router-network-address-placeholder = IPV4 address
-
## OSC VRChat settings
settings-osc-vrchat = VRChat OSC Trackers
# This cares about multilines
@@ -857,13 +799,40 @@ settings-osc-vrchat-network-port_out =
settings-osc-vrchat-network-address = Network address
settings-osc-vrchat-network-address-description-v1 = Choose which address to send out data to. Can be left untouched for VRChat.
settings-osc-vrchat-network-address-placeholder = VRChat ip address
-settings-osc-vrchat-network-trackers = Trackers
-settings-osc-vrchat-network-trackers-description = Toggle the sending of specific trackers via OSC.
-settings-osc-vrchat-network-trackers-chest = Chest
-settings-osc-vrchat-network-trackers-hip = Hip
-settings-osc-vrchat-network-trackers-knees = Knees
-settings-osc-vrchat-network-trackers-feet = Feet
-settings-osc-vrchat-network-trackers-elbows = Elbows
+
+## VRChat OSC status
+settings-osc-vrchat-status-title = Status
+settings-osc-vrchat-status-disabled = Disabled
+settings-osc-vrchat-status-input = Input
+settings-osc-vrchat-status-output = Output
+settings-osc-vrchat-status-oscquery = OSCQuery
+settings-osc-vrchat-status-input-idle = Not listening
+settings-osc-vrchat-status-input-listening = Listening on port {$port}
+settings-osc-vrchat-status-input-last-data = Last data from VRChat: {$elapsed}
+settings-osc-vrchat-status-input-no-data = No data received from VRChat yet
+settings-osc-vrchat-status-output-idle = No target
+settings-osc-vrchat-status-output-sending = Sending to {$address}:{$port} ({$source})
+settings-osc-vrchat-status-output-target = Target {$address}:{$port} ({$source})
+settings-osc-vrchat-status-output-last-frame = Last frame sent: {$elapsed}
+settings-osc-vrchat-status-output-no-frame = No frame sent yet
+settings-osc-vrchat-status-source-manual = manual
+settings-osc-vrchat-status-source-auto = auto-detected
+settings-osc-vrchat-status-oscquery-disabled = OSCQuery off (manual network mode)
+settings-osc-vrchat-status-oscquery-advertising = Advertising on port {$port}
+settings-osc-vrchat-status-oscquery-searching = No VRChat clients found yet
+settings-osc-vrchat-status-oscquery-discovered-title = Discovered VRChat clients:
+settings-osc-vrchat-status-oscquery-switch = Switch
+settings-osc-vrchat-status-network-mode = Network mode
+settings-osc-vrchat-status-network-mode-description = Automatic mode discovers VRChat through OSCQuery. Manual mode uses the configured address and ports.
+settings-osc-vrchat-status-network-mode-toggle = Manual network settings
+settings-osc-vrchat-status-network-manual-description = Configure the local input port and the VRChat OSC target manually.
+settings-osc-vrchat-status-badge-idle = Idle
+settings-osc-vrchat-status-badge-listening = Listening
+settings-osc-vrchat-status-badge-ready = Ready
+settings-osc-vrchat-status-badge-found = Found
+settings-osc-vrchat-status-badge-searching = Searching
+settings-osc-vrchat-status-badge-disabled = Disabled
+settings-osc-vrchat-status-badge-error = Error
## VMC OSC settings
settings-osc-vmc = Virtual Motion Capture
@@ -897,7 +866,7 @@ settings-osc-vmc-mirror_tracking-description = Mirror the tracking horizontally.
settings-osc-vmc-mirror_tracking-label = Mirror tracking
## Common OSC settings
-settings-osc-common-network-ports_match_error = The OSC Router in and out ports can't be the same!
+settings-osc-common-network-ports_match_error-v2 = The in and out ports can't be the same!
settings-osc-common-network-port_banned_error = The port { $port } can't be used!
## Advanced settings
diff --git a/gui/src/App.tsx b/gui/src/App.tsx
index 9674789d43..2d7524707d 100644
--- a/gui/src/App.tsx
+++ b/gui/src/App.tsx
@@ -28,7 +28,6 @@ import { SerialDetectionModal } from './components/SerialDetectionModal';
import { VRCOSCSettings } from './components/settings/pages/VRCOSCSettings';
import { TopBar } from './components/TopBar';
import { TrackerSettingsPage } from './components/tracker/TrackerSettings';
-import { OSCRouterSettings } from './components/settings/pages/OSCRouterSettings';
import { VMCSettings } from './components/settings/pages/VMCSettings';
import { MountingChoose } from './components/onboarding/pages/mounting/MountingChoose';
import { VersionUpdateModal } from './components/VersionUpdateModal';
@@ -139,7 +138,6 @@ function Layout() {
} />
} />
} />
- } />
} />
} />
} />
diff --git a/gui/src/components/commons/icon/HorizontalAlignIcon.tsx b/gui/src/components/commons/icon/HorizontalAlignIcon.tsx
new file mode 100644
index 0000000000..508b334d45
--- /dev/null
+++ b/gui/src/components/commons/icon/HorizontalAlignIcon.tsx
@@ -0,0 +1,13 @@
+export function HorizontalAlignIcon({ size = 22 }) {
+ return (
+
+ );
+}
diff --git a/gui/src/components/commons/icon/MartialArtsIcon.tsx b/gui/src/components/commons/icon/MartialArtsIcon.tsx
new file mode 100644
index 0000000000..d89c53ecf1
--- /dev/null
+++ b/gui/src/components/commons/icon/MartialArtsIcon.tsx
@@ -0,0 +1,13 @@
+export function MartialArtsIcon({ size = 23 }) {
+ return (
+
+ );
+}
diff --git a/gui/src/components/commons/icon/OutputIcon.tsx b/gui/src/components/commons/icon/OutputIcon.tsx
new file mode 100644
index 0000000000..ea85ef0075
--- /dev/null
+++ b/gui/src/components/commons/icon/OutputIcon.tsx
@@ -0,0 +1,13 @@
+export function OutputIcon({ size = 22 }) {
+ return (
+
+ );
+}
diff --git a/gui/src/components/commons/icon/TouchDoubleIcon.tsx b/gui/src/components/commons/icon/TouchDoubleIcon.tsx
new file mode 100644
index 0000000000..fc5a097253
--- /dev/null
+++ b/gui/src/components/commons/icon/TouchDoubleIcon.tsx
@@ -0,0 +1,13 @@
+export function TouchDoubleIcon({ size = 22 }) {
+ return (
+
+ );
+}
diff --git a/gui/src/components/commons/icon/WrenchIcons.tsx b/gui/src/components/commons/icon/WrenchIcon.tsx
similarity index 100%
rename from gui/src/components/commons/icon/WrenchIcons.tsx
rename to gui/src/components/commons/icon/WrenchIcon.tsx
diff --git a/gui/src/components/onboarding/pages/mounting/ManualMounting.tsx b/gui/src/components/onboarding/pages/mounting/ManualMounting.tsx
index fc23c2a857..08cefbb129 100644
--- a/gui/src/components/onboarding/pages/mounting/ManualMounting.tsx
+++ b/gui/src/components/onboarding/pages/mounting/ManualMounting.tsx
@@ -56,7 +56,6 @@ export function ManualMountingPage() {
mountingOrientationDegrees
);
assignreq.trackerId = td.tracker.trackerId;
- assignreq.allowDriftCompensation = false;
sendRPCPacket(RpcMessage.AssignTrackerRequest, assignreq);
Sentry.metrics.count('manual_mounting_set', 1, {
@@ -177,7 +176,6 @@ export function ManualMountingPageStayAligned({
mountingOrientationDegrees
);
assignreq.trackerId = td.tracker.trackerId;
- assignreq.allowDriftCompensation = false;
sendRPCPacket(RpcMessage.AssignTrackerRequest, assignreq);
Sentry.metrics.count('manual_mounting_set', 1, {
diff --git a/gui/src/components/onboarding/pages/trackers-assign/TrackerAssignment.tsx b/gui/src/components/onboarding/pages/trackers-assign/TrackerAssignment.tsx
index 5655585d37..3c209bfa5e 100644
--- a/gui/src/components/onboarding/pages/trackers-assign/TrackerAssignment.tsx
+++ b/gui/src/components/onboarding/pages/trackers-assign/TrackerAssignment.tsx
@@ -109,32 +109,14 @@ export function TrackersAssignPage() {
sendRPCPacket(
RpcMessage.ChangeSettingsRequest,
- new ChangeSettingsRequestT(
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- newTapSettings
- )
+ new ChangeSettingsRequestT(null, null, null, null, null, newTapSettings)
);
return () => {
newTapSettings.setupMode = false;
sendRPCPacket(
RpcMessage.ChangeSettingsRequest,
- new ChangeSettingsRequestT(
- null,
- null,
- null,
- null,
- null,
- null,
- null,
- newTapSettings
- )
+ new ChangeSettingsRequestT(null, null, null, null, null, newTapSettings)
);
};
}, [tapDetectionSettings]);
@@ -220,7 +202,6 @@ export function TrackersAssignPage() {
assignreq.bodyPosition = role;
assignreq.mountingOrientation = rotation;
assignreq.trackerId = trackerId;
- assignreq.allowDriftCompensation = false;
sendRPCPacket(RpcMessage.AssignTrackerRequest, assignreq);
};
diff --git a/gui/src/components/settings/SettingsSidebar.tsx b/gui/src/components/settings/SettingsSidebar.tsx
index 1095073394..094d741d97 100644
--- a/gui/src/components/settings/SettingsSidebar.tsx
+++ b/gui/src/components/settings/SettingsSidebar.tsx
@@ -50,8 +50,8 @@ export function SettingsSidebar() {