├── .editorconfig ├── .envrc ├── .gitattributes ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── dependabot.yml ├── labeler.yml └── workflows │ ├── build-gui.yml │ ├── gradle.yaml │ ├── label.yml │ ├── pontoon-pr.yml │ └── rebase.yml ├── .gitignore ├── .gitmodules ├── .husky └── pre-commit ├── .imgbotconfig ├── .lintstagedrc.mjs ├── .node-version ├── .npmrc ├── .prettierrc ├── .vscode └── extensions.json ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── TRADEMARK.md ├── assets └── img │ └── onboarding.png ├── build.gradle.kts ├── deny.toml ├── dev.slimevr.SlimeVR.metainfo.xml ├── flake.lock ├── flake.nix ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── gui ├── .env ├── .gitattributes ├── .gitignore ├── .lintstagedrc.mjs ├── eslint.config.js ├── index.html ├── openapi-codegen.config.ts ├── package.json ├── postcss.config.cjs ├── public │ ├── favicon.ico │ ├── fonts │ │ ├── Lexend[HEXP,wght].woff2 │ │ ├── NotoSansCJK-VF.otf.woff2 │ │ ├── OpenDyslexic-Bold-Italic.woff │ │ ├── OpenDyslexic-Bold.woff │ │ ├── OpenDyslexic-Italic.woff │ │ ├── OpenDyslexic-Regular.woff │ │ └── Ubuntu-R.woff2 │ ├── i18n │ │ ├── ar │ │ │ └── translation.ftl │ │ ├── cs │ │ │ └── translation.ftl │ │ ├── da │ │ │ └── translation.ftl │ │ ├── de │ │ │ └── translation.ftl │ │ ├── el │ │ │ └── translation.ftl │ │ ├── en-x-owo │ │ │ └── translation.ftl │ │ ├── en │ │ │ └── translation.ftl │ │ ├── es-419 │ │ │ └── translation.ftl │ │ ├── es-ES │ │ │ └── translation.ftl │ │ ├── et │ │ │ └── translation.ftl │ │ ├── fi │ │ │ └── translation.ftl │ │ ├── fr │ │ │ └── translation.ftl │ │ ├── he │ │ │ └── translation.ftl │ │ ├── it │ │ │ └── translation.ftl │ │ ├── ja │ │ │ └── translation.ftl │ │ ├── ko │ │ │ └── translation.ftl │ │ ├── nb-NO │ │ │ └── translation.ftl │ │ ├── nl │ │ │ └── translation.ftl │ │ ├── pl │ │ │ └── translation.ftl │ │ ├── pt-BR │ │ │ └── translation.ftl │ │ ├── ru │ │ │ └── translation.ftl │ │ ├── th │ │ │ └── translation.ftl │ │ ├── tr │ │ │ └── translation.ftl │ │ ├── uk │ │ │ └── translation.ftl │ │ ├── vi │ │ │ └── translation.ftl │ │ ├── zh-Hans │ │ │ └── translation.ftl │ │ └── zh-Hant │ │ │ └── translation.ftl │ ├── images │ │ ├── R11_board_reset.webp │ │ ├── R12_board_reset.webp │ │ ├── R14_board_reset_sw.webp │ │ ├── autobone-poster.webp │ │ ├── boxslime.webp │ │ ├── curious-slime.gif │ │ ├── front-standing-pose.webp │ │ ├── happy-slime.gif │ │ ├── jumping-slime.gif │ │ ├── mounting-reset-pose.webp │ │ ├── relaxed_pose_flat.webp │ │ ├── relaxed_pose_sitting.webp │ │ ├── relaxed_pose_standing.webp │ │ ├── reset-pose.webp │ │ ├── reset │ │ │ ├── FullResetPose.webp │ │ │ ├── FullResetPoseSide.webp │ │ │ └── FullResetPoseWrong.webp │ │ ├── sad-slime.gif │ │ ├── slime-girl.webp │ │ ├── slimes.webp │ │ ├── slimetower.webp │ │ └── stay-aligned │ │ │ ├── StayAlignedFloor.webp │ │ │ ├── StayAlignedSitting.webp │ │ │ └── StayAlignedStanding.webp │ ├── logo.svg │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── models │ │ ├── extension.gltf │ │ └── tracker.gltf │ ├── robots.txt │ └── videos │ │ ├── autobone.webm │ │ └── turn-on-tracker.webm ├── scripts │ ├── check-missing.js │ ├── convert-fluent.js │ ├── gitversion.mjs │ └── license-list.mjs ├── src-tauri │ ├── .gitignore │ ├── .lintstagedrc.mjs │ ├── Cargo.toml │ ├── build.rs │ ├── capabilities │ │ └── migrated.json │ ├── dev.slimevr.SlimeVR.desktop │ ├── icons │ │ ├── 128x128.png │ │ ├── 128x128@2x.png │ │ ├── 32x32.png │ │ ├── Square107x107Logo.png │ │ ├── Square142x142Logo.png │ │ ├── Square150x150Logo.png │ │ ├── Square284x284Logo.png │ │ ├── Square30x30Logo.png │ │ ├── Square310x310Logo.png │ │ ├── Square44x44Logo.png │ │ ├── Square71x71Logo.png │ │ ├── Square89x89Logo.png │ │ ├── StoreLogo.png │ │ ├── android │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ │ └── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ └── ic_launcher_round.png │ │ ├── appleTrayIcon.png │ │ ├── icon.icns │ │ ├── icon.ico │ │ ├── icon.png │ │ ├── icon.svg │ │ └── ios │ │ │ ├── AppIcon-20x20@1x.png │ │ │ ├── AppIcon-20x20@2x-1.png │ │ │ ├── AppIcon-20x20@2x.png │ │ │ ├── AppIcon-20x20@3x.png │ │ │ ├── AppIcon-29x29@1x.png │ │ │ ├── AppIcon-29x29@2x-1.png │ │ │ ├── AppIcon-29x29@2x.png │ │ │ ├── AppIcon-29x29@3x.png │ │ │ ├── AppIcon-40x40@1x.png │ │ │ ├── AppIcon-40x40@2x-1.png │ │ │ ├── AppIcon-40x40@2x.png │ │ │ ├── AppIcon-40x40@3x.png │ │ │ ├── AppIcon-512@2x.png │ │ │ ├── AppIcon-60x60@2x.png │ │ │ ├── AppIcon-60x60@3x.png │ │ │ ├── AppIcon-76x76@1x.png │ │ │ ├── AppIcon-76x76@2x.png │ │ │ └── AppIcon-83.5x83.5@2x.png │ ├── run.bat.old │ ├── src │ │ ├── JavaVersion.jar │ │ ├── JavaVersion.java │ │ ├── main.rs │ │ ├── presence.rs │ │ ├── state.rs │ │ ├── tray.rs │ │ └── util.rs │ └── tauri.conf.json ├── src │ ├── App.tsx │ ├── AppLayout.tsx │ ├── components │ │ ├── BVHButton.tsx │ │ ├── ClearMountingButton.tsx │ │ ├── EmptyLayout.scss │ │ ├── EmptyLayout.tsx │ │ ├── ErrorConsentModal.tsx │ │ ├── MainLayout.scss │ │ ├── MainLayout.tsx │ │ ├── Navbar.tsx │ │ ├── Preload.tsx │ │ ├── SerialDetectionModal.tsx │ │ ├── TopBar.tsx │ │ ├── TrackersStillOnModal.tsx │ │ ├── TrackingPauseButton.tsx │ │ ├── TrayOrExitModal.tsx │ │ ├── UnknownDeviceModal.tsx │ │ ├── VersionUpdateModal.tsx │ │ ├── WidgetsComponent.tsx │ │ ├── commons │ │ │ ├── A.tsx │ │ │ ├── ArrowLink.tsx │ │ │ ├── BaseModal.tsx │ │ │ ├── BigButton.tsx │ │ │ ├── BodyDisplay.tsx │ │ │ ├── BodyInteractions.tsx │ │ │ ├── BodyPartIcon.tsx │ │ │ ├── Button.tsx │ │ │ ├── Checkbox.tsx │ │ │ ├── Dropdown.tsx │ │ │ ├── FileInput.tsx │ │ │ ├── Input.tsx │ │ │ ├── LangSelector.tsx │ │ │ ├── Modal.tsx │ │ │ ├── NumberSelector.tsx │ │ │ ├── PersonFrontIcon.tsx │ │ │ ├── ProgressBar.tsx │ │ │ ├── Radio.tsx │ │ │ ├── Range.tsx │ │ │ ├── ThemeSelector.tsx │ │ │ ├── TipBox.tsx │ │ │ ├── Tooltip.tsx │ │ │ ├── Typography.tsx │ │ │ ├── VerticalStepper.tsx │ │ │ └── icon │ │ │ │ ├── AnkleIcon.tsx │ │ │ │ ├── ArrowIcons.tsx │ │ │ │ ├── BatteryIcon.tsx │ │ │ │ ├── BellIcon.tsx │ │ │ │ ├── BugIcon.tsx │ │ │ │ ├── BulbIcon.tsx │ │ │ │ ├── CheckIcon.tsx │ │ │ │ ├── ChestIcon.tsx │ │ │ │ ├── CircleIcon.tsx │ │ │ │ ├── CloseIcon.tsx │ │ │ │ ├── ControllerIcon.tsx │ │ │ │ ├── CrossIcon.tsx │ │ │ │ ├── CubeIcon.tsx │ │ │ │ ├── DownloadIcon.tsx │ │ │ │ ├── EscapeIcon.tsx │ │ │ │ ├── EyeIcon.tsx │ │ │ │ ├── FileIcon.tsx │ │ │ │ ├── FingersIcon.tsx │ │ │ │ ├── FootIcon.tsx │ │ │ │ ├── FrontOfChair.tsx │ │ │ │ ├── GearIcon.tsx │ │ │ │ ├── HeadIcon.tsx │ │ │ │ ├── HeadsetIcon.tsx │ │ │ │ ├── HipIcon.tsx │ │ │ │ ├── HumanIcon.tsx │ │ │ │ ├── ImportIcon.tsx │ │ │ │ ├── LoaderIcon.tsx │ │ │ │ ├── LowerArmIcon.tsx │ │ │ │ ├── MaximiseIcon.tsx │ │ │ │ ├── MinimiseIcon.tsx │ │ │ │ ├── NeckIcon.tsx │ │ │ │ ├── PauseIcon.tsx │ │ │ │ ├── PawIcon.tsx │ │ │ │ ├── PercentIcon.tsx │ │ │ │ ├── PlayIcon.tsx │ │ │ │ ├── QuestionIcon.tsx │ │ │ │ ├── RecordIcon.tsx │ │ │ │ ├── ResetIcon.tsx │ │ │ │ ├── RouterIcon.tsx │ │ │ │ ├── RulerIcon.tsx │ │ │ │ ├── ShoulderIcon.tsx │ │ │ │ ├── SimevrIcon.tsx │ │ │ │ ├── SlimeUpIcon.tsx │ │ │ │ ├── SlimeVRIcon.tsx │ │ │ │ ├── SparkleIcon.tsx │ │ │ │ ├── SpinIcon.tsx │ │ │ │ ├── SquaresIcon.tsx │ │ │ │ ├── SteamIcon.tsx │ │ │ │ ├── TaybolIcon.tsx │ │ │ │ ├── TrashIcon.tsx │ │ │ │ ├── UploadFileIcon.tsx │ │ │ │ ├── UpperArmIcon.tsx │ │ │ │ ├── UpperChestIcon.tsx │ │ │ │ ├── UpperLegIcon.tsx │ │ │ │ ├── UsbIcon.tsx │ │ │ │ ├── VMCIcon.tsx │ │ │ │ ├── VRCIcon.tsx │ │ │ │ ├── WaistIcon.tsx │ │ │ │ ├── WarningIcon.tsx │ │ │ │ ├── WifiIcon.tsx │ │ │ │ └── WrenchIcons.tsx │ │ ├── firmware-tool │ │ │ ├── AddImusStep.tsx │ │ │ ├── BoardPinsStep.tsx │ │ │ ├── BuildStep.tsx │ │ │ ├── DeviceCard.tsx │ │ │ ├── FirmwareTool.tsx │ │ │ ├── FlashBtnStep.tsx │ │ │ ├── FlashingMethodStep.tsx │ │ │ ├── FlashingStep.tsx │ │ │ ├── SelectBoardStep.tsx │ │ │ └── SelectFirmwareStep.tsx │ │ ├── firmware-update │ │ │ └── FirmwareUpdate.tsx │ │ ├── home │ │ │ ├── Home.tsx │ │ │ └── ResetButton.tsx │ │ ├── onboarding │ │ │ ├── BodyAssignment.tsx │ │ │ ├── NeckWarningModal.tsx │ │ │ ├── OnboardingContextProvicer.tsx │ │ │ ├── OnboardingLayout.scss │ │ │ ├── OnboardingLayout.tsx │ │ │ ├── SkipSetupButton.tsx │ │ │ ├── SkipSetupWarningModal.tsx │ │ │ ├── StepperSlider.tsx │ │ │ └── pages │ │ │ │ ├── CalibrationTutorial.tsx │ │ │ │ ├── ConnectTracker.scss │ │ │ │ ├── ConnectTracker.tsx │ │ │ │ ├── ConnectionLost.tsx │ │ │ │ ├── Done.tsx │ │ │ │ ├── EnterVR.tsx │ │ │ │ ├── Home.tsx │ │ │ │ ├── ResetTutorial.tsx │ │ │ │ ├── WifiCreds.tsx │ │ │ │ ├── assignment-preparation │ │ │ │ ├── AssignmentTutorial.tsx │ │ │ │ ├── ExtensionArrow.tsx │ │ │ │ ├── StickerSlime.tsx │ │ │ │ └── TrackerArrow.tsx │ │ │ │ ├── body-proportions │ │ │ │ ├── AutomaticProportions.tsx │ │ │ │ ├── BodyProportions.tsx │ │ │ │ ├── ManualProportions.tsx │ │ │ │ ├── ProportionsResetModal.tsx │ │ │ │ ├── ScaledProportions.tsx │ │ │ │ ├── autobone-steps │ │ │ │ │ ├── AutoboneErrorModal.tsx │ │ │ │ │ ├── CheckFloorHeight.tsx │ │ │ │ │ ├── CheckHeight.tsx │ │ │ │ │ ├── Done.tsx │ │ │ │ │ ├── Preparation.tsx │ │ │ │ │ ├── PutTrackersOn.tsx │ │ │ │ │ ├── Recording.tsx │ │ │ │ │ ├── Requirements.tsx │ │ │ │ │ ├── StartRecording.tsx │ │ │ │ │ ├── TooSmolModal.tsx │ │ │ │ │ └── VerifyResults.tsx │ │ │ │ └── scaled-steps │ │ │ │ │ ├── Done.tsx │ │ │ │ │ ├── ManualHeightStep.tsx │ │ │ │ │ └── ResetProportions.tsx │ │ │ │ ├── mounting │ │ │ │ ├── AutomaticMounting.tsx │ │ │ │ ├── ManualMounting.tsx │ │ │ │ ├── MountingChoose.tsx │ │ │ │ ├── MountingSelectionMenu.tsx │ │ │ │ └── mounting-steps │ │ │ │ │ ├── Done.tsx │ │ │ │ │ ├── MountingReset.tsx │ │ │ │ │ ├── Preparation.tsx │ │ │ │ │ └── PutTrackersOn.tsx │ │ │ │ ├── stay-aligned │ │ │ │ ├── StayAlignedSetup.tsx │ │ │ │ └── stay-aligned-steps │ │ │ │ │ ├── Done.tsx │ │ │ │ │ ├── PreparationStep.tsx │ │ │ │ │ ├── PutTrackersOnStep.tsx │ │ │ │ │ ├── RelaxedPoseSteps.tsx │ │ │ │ │ └── VerifyMounting.tsx │ │ │ │ └── trackers-assign │ │ │ │ ├── TrackerAssignOptions.tsx │ │ │ │ ├── TrackerAssignment.tsx │ │ │ │ └── TrackerSelectionMenu.tsx │ │ ├── providers │ │ │ ├── AppContext.tsx │ │ │ ├── ConfigContext.tsx │ │ │ └── StatusSystemContext.tsx │ │ ├── settings │ │ │ ├── HandsWarningModal.tsx │ │ │ ├── SettingsLayout.scss │ │ │ ├── SettingsLayout.tsx │ │ │ ├── SettingsPageLayout.tsx │ │ │ ├── SettingsResetModal.tsx │ │ │ ├── SettingsSidebar.tsx │ │ │ └── pages │ │ │ │ ├── AdvancedSettings.tsx │ │ │ │ ├── GeneralSettings.tsx │ │ │ │ ├── InterfaceSettings.tsx │ │ │ │ ├── MagnetometerToggleSetting.tsx │ │ │ │ ├── OSCRouterSettings.tsx │ │ │ │ ├── Serial.tsx │ │ │ │ ├── VMCSettings.tsx │ │ │ │ ├── VRCOSCSettings.tsx │ │ │ │ └── components │ │ │ │ └── StayAlignedSettings.tsx │ │ ├── stay-aligned │ │ │ ├── RelaxedPose.tsx │ │ │ └── StayAlignedInfo.tsx │ │ ├── tracker │ │ │ ├── SingleTrackerBodyAssignmentMenu.tsx │ │ │ ├── TrackerBattery.tsx │ │ │ ├── TrackerCard.tsx │ │ │ ├── TrackerPartCard.tsx │ │ │ ├── TrackerSettings.tsx │ │ │ ├── TrackerStatus.tsx │ │ │ ├── TrackerWifi.tsx │ │ │ └── TrackersTable.tsx │ │ ├── vr-mode │ │ │ └── VRModePage.tsx │ │ ├── vrc │ │ │ └── VRCWarningsPage.tsx │ │ └── widgets │ │ │ ├── DeveloperModeWidget.tsx │ │ │ ├── IMUVisualizerWidget.tsx │ │ │ ├── OverlayWidget.tsx │ │ │ └── SkeletonVisualizerWidget.tsx │ ├── firmware-tool-api │ │ ├── firmwareToolComponents.ts │ │ ├── firmwareToolContext.ts │ │ ├── firmwareToolFetcher.ts │ │ ├── firmwareToolSchemas.ts │ │ └── firmwareToolUtils.ts │ ├── hooks │ │ ├── app.ts │ │ ├── autobone.ts │ │ ├── breakpoint.ts │ │ ├── cache.ts │ │ ├── choker-warning.ts │ │ ├── config.ts │ │ ├── countdown.ts │ │ ├── datafeed-config.ts │ │ ├── discord-presence.ts │ │ ├── firmware-tool.ts │ │ ├── height.ts │ │ ├── imu-logic.ts │ │ ├── layout.ts │ │ ├── manual-proportions.ts │ │ ├── onboarding.ts │ │ ├── previous.ts │ │ ├── pubSub.ts │ │ ├── status-system.ts │ │ ├── timeout.ts │ │ ├── tracker.ts │ │ ├── vrc-config.ts │ │ ├── websocket-api.ts │ │ └── wifi-form.tsx │ ├── i18n │ │ ├── config.tsx │ │ └── names.ts │ ├── index.scss │ ├── index.tsx │ ├── logo.svg │ ├── maths │ │ ├── angle.ts │ │ ├── quaternion.ts │ │ └── vector3.ts │ ├── setupTests.ts │ ├── sounds │ │ ├── sounds.ts │ │ └── xylophone.ts │ ├── store │ │ └── app-store.ts │ ├── utils │ │ ├── a11y.ts │ │ ├── formatting.ts │ │ ├── logging.ts │ │ ├── sentry.ts │ │ ├── skeletonHelper.ts │ │ └── tauri.ts │ └── vite-env.d.ts ├── tailwind.config.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── l10n.toml ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── rust-toolchain.toml ├── rustfmt.toml ├── server ├── .gitattributes ├── .gitignore ├── LICENSE.md ├── android │ ├── .gitignore │ ├── build.gradle.kts │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ ├── dev │ │ │ └── slimevr │ │ │ │ └── android │ │ │ │ ├── ForegroundService.kt │ │ │ │ ├── Main.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ └── serial │ │ │ │ └── AndroidSerialHandler.kt │ │ └── java │ │ │ └── awt │ │ │ └── Color.java │ │ ├── res │ │ ├── drawable │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout │ │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ │ └── themes.xml │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ └── xml │ │ │ ├── backup_rules.xml │ │ │ └── data_extraction_rules.xml │ │ └── resources │ │ ├── icon128.png │ │ ├── icon16.png │ │ ├── icon256.png │ │ ├── icon32.png │ │ ├── icon48.png │ │ └── icon64.png ├── build.gradle.kts ├── core │ ├── .gitignore │ ├── build.gradle.kts │ ├── protobuf_update.bat │ ├── resources │ │ ├── ThirdPartyNotices.txt │ │ ├── firewall.bat │ │ └── firewall_uninstall.bat │ └── src │ │ ├── main │ │ └── java │ │ │ ├── com │ │ │ └── jme3 │ │ │ │ ├── math │ │ │ │ └── FastMath.kt │ │ │ │ └── system │ │ │ │ ├── NanoTimer.java │ │ │ │ └── Timer.java │ │ │ ├── dev │ │ │ └── slimevr │ │ │ │ ├── Keybinding.kt │ │ │ │ ├── NetworkProtocol.java │ │ │ │ ├── VRServer.kt │ │ │ │ ├── autobone │ │ │ │ ├── AutoBone.kt │ │ │ │ ├── AutoBoneHandler.kt │ │ │ │ ├── AutoBoneListener.kt │ │ │ │ ├── AutoBoneProcessType.kt │ │ │ │ ├── AutoBoneStep.kt │ │ │ │ ├── BoneContribution.kt │ │ │ │ ├── PoseFrameIterator.kt │ │ │ │ ├── PoseFrameStep.kt │ │ │ │ ├── StatsCalculator.kt │ │ │ │ └── errors │ │ │ │ │ ├── AutoBoneException.kt │ │ │ │ │ ├── BodyProportionError.kt │ │ │ │ │ ├── FootHeightOffsetError.kt │ │ │ │ │ ├── HeightError.kt │ │ │ │ │ ├── IAutoBoneError.kt │ │ │ │ │ ├── OffsetSlideError.kt │ │ │ │ │ ├── PositionError.kt │ │ │ │ │ ├── PositionOffsetError.kt │ │ │ │ │ ├── SlideError.kt │ │ │ │ │ └── proportions │ │ │ │ │ ├── HardProportionLimiter.kt │ │ │ │ │ ├── ProportionLimiter.kt │ │ │ │ │ └── RangeProportionLimiter.kt │ │ │ │ ├── bridge │ │ │ │ ├── Bridge.kt │ │ │ │ └── BridgeThread.java │ │ │ │ ├── config │ │ │ │ ├── AutoBoneConfig.kt │ │ │ │ ├── BridgeConfig.java │ │ │ │ ├── ConfigManager.java │ │ │ │ ├── CurrentVRConfigConverter.java │ │ │ │ ├── DriftCompensationConfig.kt │ │ │ │ ├── FiltersConfig.kt │ │ │ │ ├── KeybindingsConfig.java │ │ │ │ ├── LegTweaksConfig.kt │ │ │ │ ├── OSCConfig.kt │ │ │ │ ├── OverlayConfig.java │ │ │ │ ├── ResetsConfig.kt │ │ │ │ ├── ServerConfig.kt │ │ │ │ ├── SkeletonConfig.java │ │ │ │ ├── StayAlignedConfig.kt │ │ │ │ ├── StayAlignedRelaxedPoseConfig.kt │ │ │ │ ├── TapDetectionConfig.kt │ │ │ │ ├── TrackerConfig.kt │ │ │ │ ├── VMCConfig.kt │ │ │ │ ├── VRCOSCConfig.kt │ │ │ │ ├── VRConfig.kt │ │ │ │ └── serializers │ │ │ │ │ ├── BooleanMapDeserializer.java │ │ │ │ │ ├── BridgeConfigMapDeserializer.java │ │ │ │ │ ├── FloatMapDeserializer.java │ │ │ │ │ ├── MapDeserializer.java │ │ │ │ │ ├── QuaternionDeserializer.java │ │ │ │ │ ├── QuaternionSerializer.java │ │ │ │ │ └── TrackerConfigMapDeserializer.java │ │ │ │ ├── filtering │ │ │ │ ├── CircularArrayList.java │ │ │ │ ├── QuaternionMovingAverage.kt │ │ │ │ └── TrackerFilters.kt │ │ │ │ ├── firmware │ │ │ │ ├── FirmwareUpdateHandler.kt │ │ │ │ ├── FirmwareUpdateListener.kt │ │ │ │ ├── FirmwareUpdateMethod.kt │ │ │ │ ├── FirmwareUpdateStatus.kt │ │ │ │ ├── OTAUpdateTask.kt │ │ │ │ ├── SerialFlashingHandler.kt │ │ │ │ ├── SerialRebootHandler.kt │ │ │ │ ├── UpdateDeviceId.kt │ │ │ │ └── UpdateStatusEvent.kt │ │ │ │ ├── games │ │ │ │ └── vrchat │ │ │ │ │ └── VRCConfigHandler.kt │ │ │ │ ├── math │ │ │ │ ├── Angle.kt │ │ │ │ ├── AngleAverage.kt │ │ │ │ └── AngleErrors.kt │ │ │ │ ├── osc │ │ │ │ ├── OSCHandler.java │ │ │ │ ├── OSCRouter.java │ │ │ │ ├── UnityArmature.kt │ │ │ │ ├── UnityBone.kt │ │ │ │ ├── VMCHandler.kt │ │ │ │ ├── VRCOSCHandler.kt │ │ │ │ ├── VRCOSCQueryHandler.kt │ │ │ │ └── VRMReader.kt │ │ │ │ ├── poseframeformat │ │ │ │ ├── PfrIO.kt │ │ │ │ ├── PfsIO.kt │ │ │ │ ├── PfsPackets.kt │ │ │ │ ├── PoseFrames.kt │ │ │ │ ├── PoseRecorder.kt │ │ │ │ ├── player │ │ │ │ │ ├── PlayerTracker.kt │ │ │ │ │ └── TrackerFramesPlayer.kt │ │ │ │ └── trackerdata │ │ │ │ │ ├── TrackerFrame.kt │ │ │ │ │ ├── TrackerFrameData.kt │ │ │ │ │ └── TrackerFrames.kt │ │ │ │ ├── posestreamer │ │ │ │ ├── BVHFileStream.kt │ │ │ │ ├── BVHRecorder.java │ │ │ │ ├── BVHSettings.java │ │ │ │ ├── PoseDataStream.java │ │ │ │ ├── PoseFrameStreamer.kt │ │ │ │ ├── PoseStreamer.java │ │ │ │ ├── ServerPoseStreamer.java │ │ │ │ └── TickPoseStreamer.java │ │ │ │ ├── protocol │ │ │ │ ├── ConnectionContext.java │ │ │ │ ├── GenericConnection.java │ │ │ │ ├── ProtocolAPI.java │ │ │ │ ├── ProtocolAPIServer.java │ │ │ │ ├── ProtocolHandler.kt │ │ │ │ ├── datafeed │ │ │ │ │ ├── DataFeedBuilder.java │ │ │ │ │ ├── DataFeedBuilderKotlin.kt │ │ │ │ │ └── DataFeedHandler.java │ │ │ │ ├── pubsub │ │ │ │ │ ├── HashedTopicId.java │ │ │ │ │ └── PubSubHandler.java │ │ │ │ └── rpc │ │ │ │ │ ├── RPCBuilder.java │ │ │ │ │ ├── RPCBuilder.kt │ │ │ │ │ ├── RPCHandler.kt │ │ │ │ │ ├── autobone │ │ │ │ │ └── RPCAutoBoneHandler.kt │ │ │ │ │ ├── firmware │ │ │ │ │ └── RPCFirmwareUpdateHandler.kt │ │ │ │ │ ├── games │ │ │ │ │ └── vrchat │ │ │ │ │ │ ├── RPCVRCBuilder.kt │ │ │ │ │ │ └── RPCVRChatHandler.kt │ │ │ │ │ ├── reset │ │ │ │ │ └── RPCResetHandler.java │ │ │ │ │ ├── serial │ │ │ │ │ ├── RPCProvisioningHandler.java │ │ │ │ │ └── RPCSerialHandler.java │ │ │ │ │ ├── settings │ │ │ │ │ ├── RPCSettingsBuilder.java │ │ │ │ │ ├── RPCSettingsBuilderKotlin.kt │ │ │ │ │ └── RPCSettingsHandler.kt │ │ │ │ │ ├── setup │ │ │ │ │ ├── RPCHandshakeHandler.kt │ │ │ │ │ ├── RPCTapSetupHandler.kt │ │ │ │ │ └── RPCUtil.kt │ │ │ │ │ ├── status │ │ │ │ │ └── RPCStatusHandler.kt │ │ │ │ │ └── trackingpause │ │ │ │ │ └── RPCTrackingPause.kt │ │ │ │ ├── reset │ │ │ │ ├── ResetHandler.java │ │ │ │ └── ResetListener.java │ │ │ │ ├── serial │ │ │ │ ├── ProvisioningHandler.java │ │ │ │ ├── ProvisioningListener.java │ │ │ │ ├── ProvisioningStatus.kt │ │ │ │ ├── SerialHandler.kt │ │ │ │ └── SerialListener.kt │ │ │ │ ├── setup │ │ │ │ ├── HandshakeHandler.kt │ │ │ │ └── TapSetupHandler.kt │ │ │ │ ├── status │ │ │ │ └── StatusSystem.kt │ │ │ │ ├── tracking │ │ │ │ ├── processor │ │ │ │ │ ├── Bone.kt │ │ │ │ │ ├── BoneType.java │ │ │ │ │ ├── Constraint.kt │ │ │ │ │ ├── HumanPoseManager.kt │ │ │ │ │ ├── TransformNode.kt │ │ │ │ │ ├── config │ │ │ │ │ │ ├── SkeletonConfigManager.kt │ │ │ │ │ │ ├── SkeletonConfigOffsets.java │ │ │ │ │ │ ├── SkeletonConfigToggles.java │ │ │ │ │ │ └── SkeletonConfigValues.java │ │ │ │ │ ├── skeleton │ │ │ │ │ │ ├── HumanSkeleton.kt │ │ │ │ │ │ ├── LegTweaks.kt │ │ │ │ │ │ ├── LegTweaksBuffer.kt │ │ │ │ │ │ ├── Localizer.kt │ │ │ │ │ │ ├── TapDetection.kt │ │ │ │ │ │ └── TapDetectionManager.java │ │ │ │ │ └── stayaligned │ │ │ │ │ │ ├── StayAligned.kt │ │ │ │ │ │ ├── StayAlignedDefaults.kt │ │ │ │ │ │ ├── adjust │ │ │ │ │ │ ├── AdjustTrackerYaw.kt │ │ │ │ │ │ ├── CenterErrorVisitor.kt │ │ │ │ │ │ ├── CenterYaw.kt │ │ │ │ │ │ ├── LockedErrorVisitor.kt │ │ │ │ │ │ ├── NeighborErrorVisitor.kt │ │ │ │ │ │ └── TrackerYaw.kt │ │ │ │ │ │ ├── poses │ │ │ │ │ │ ├── PlayerPose.kt │ │ │ │ │ │ ├── RelaxedPose.kt │ │ │ │ │ │ └── TrackerPose.kt │ │ │ │ │ │ └── trackers │ │ │ │ │ │ ├── RestDetector.kt │ │ │ │ │ │ ├── Side.kt │ │ │ │ │ │ ├── StayAlignedTrackerState.kt │ │ │ │ │ │ ├── TrackerSkeleton.kt │ │ │ │ │ │ └── YawErrors.kt │ │ │ │ └── trackers │ │ │ │ │ ├── Device.kt │ │ │ │ │ ├── DeviceManager.kt │ │ │ │ │ ├── Tracker.kt │ │ │ │ │ ├── TrackerFilteringHandler.kt │ │ │ │ │ ├── TrackerFlexHandler.kt │ │ │ │ │ ├── TrackerPosition.kt │ │ │ │ │ ├── TrackerResetsHandler.kt │ │ │ │ │ ├── TrackerRole.kt │ │ │ │ │ ├── TrackerStatus.kt │ │ │ │ │ ├── TrackerStatusListener.kt │ │ │ │ │ ├── TrackerUtils.kt │ │ │ │ │ └── udp │ │ │ │ │ ├── FeatureFlags.kt │ │ │ │ │ ├── FirmwareConstants.kt │ │ │ │ │ ├── SensorTap.kt │ │ │ │ │ ├── TrackersUDPServer.kt │ │ │ │ │ ├── UDPDevice.kt │ │ │ │ │ ├── UDPPacket.kt │ │ │ │ │ └── UDPProtocolParser.kt │ │ │ │ ├── trackingpause │ │ │ │ ├── TrackingPauseHandler.kt │ │ │ │ └── TrackingPauseListener.kt │ │ │ │ ├── util │ │ │ │ └── ann │ │ │ │ │ └── VRServerThread.java │ │ │ │ └── websocketapi │ │ │ │ ├── WebSocketVRBridge.kt │ │ │ │ ├── WebsocketAPI.java │ │ │ │ └── WebsocketConnection.java │ │ │ └── io │ │ │ ├── eiren │ │ │ ├── math │ │ │ │ └── FloatMath.kt │ │ │ └── util │ │ │ │ ├── BufferedTimer.java │ │ │ │ ├── OperatingSystem.kt │ │ │ │ ├── StringUtils.java │ │ │ │ ├── Util.java │ │ │ │ ├── ann │ │ │ │ ├── AWTThread.java │ │ │ │ ├── DebugSwitch.java │ │ │ │ ├── NativeUnsafe.java │ │ │ │ ├── Synchronize.java │ │ │ │ ├── ThreadSafe.java │ │ │ │ ├── ThreadSafeSingle.java │ │ │ │ ├── ThreadSecure.java │ │ │ │ └── Transient.java │ │ │ │ ├── collections │ │ │ │ ├── FastList.java │ │ │ │ ├── RemoveAtSwapFastList.java │ │ │ │ ├── RemoveAtSwapList.java │ │ │ │ ├── ResettableIterator.java │ │ │ │ └── SkipIterator.java │ │ │ │ └── logging │ │ │ │ ├── DefaultGLog.java │ │ │ │ ├── FileLogFormatter.java │ │ │ │ ├── FileLogHandler.kt │ │ │ │ ├── IGLog.java │ │ │ │ ├── LogManager.java │ │ │ │ ├── LoggerOutputStream.java │ │ │ │ ├── LoggerRecorder.java │ │ │ │ ├── PreciseConsoleLogFormatter.java │ │ │ │ └── ShortConsoleLogFormatter.java │ │ │ └── github │ │ │ └── axisangles │ │ │ └── ktmath │ │ │ ├── EulerAngles.kt │ │ │ ├── LICENSE-APACHE │ │ │ ├── LICENSE-MIT │ │ │ ├── Matrix3.kt │ │ │ ├── Quaternion.kt │ │ │ ├── Transform.kt │ │ │ └── Vector3.kt │ │ └── test │ │ └── java │ │ ├── dev │ │ └── slimevr │ │ │ └── unit │ │ │ ├── LegTweaksTests.kt │ │ │ ├── MountingResetTests.kt │ │ │ ├── ReferenceAdjustmentsTests.kt │ │ │ ├── SkeletonResetTests.kt │ │ │ ├── TestTrackerSet.kt │ │ │ ├── TrackerTestUtils.kt │ │ │ ├── TrackingPauseTests.kt │ │ │ └── TwinExtendedBackTests.kt │ │ └── io │ │ └── github │ │ └── axisangles │ │ └── ktmath │ │ └── QuaternionTest.kt ├── desktop │ ├── .gitignore │ ├── build.gradle.kts │ ├── config │ │ └── README.md │ └── src │ │ └── main │ │ └── java │ │ └── dev │ │ └── slimevr │ │ └── desktop │ │ ├── Main.kt │ │ ├── NetworkProfileChecker.kt │ │ ├── firmware │ │ └── DesktopSerialFlashingHandler.kt │ │ ├── games │ │ └── vrchat │ │ │ └── DesktopVRCConfigHandler.kt │ │ ├── platform │ │ ├── ProtobufBridge.kt │ │ ├── ProtobufMessages.java │ │ ├── SteamVRBridge.kt │ │ ├── linux │ │ │ ├── UnixSocketBridge.java │ │ │ ├── UnixSocketConnection.java │ │ │ └── UnixSocketRpcBridge.java │ │ └── windows │ │ │ ├── PipeState.java │ │ │ ├── WindowsNamedPipeBridge.java │ │ │ └── WindowsPipe.java │ │ ├── serial │ │ └── DesktopSerialHandler.kt │ │ └── tracking │ │ └── trackers │ │ └── hid │ │ ├── HIDDevice.kt │ │ └── TrackersHID.kt └── spotless.xml ├── settings.gradle.kts └── shell.nix /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | charset = utf-8 7 | 8 | # Fluent files only support spaces for indentation 9 | [*.ftl] 10 | indent_size = 4 11 | indent_style = space 12 | 13 | # Current config in eslint uses 2 spaces for indentation 14 | [*.{.tsx,ts,jsx,js}] 15 | indent_size = 2 16 | indent_style = space 17 | max_line_length = 88 18 | 19 | # This is how everything should actually be 20 | [*.{kt,kts,java,rs}] 21 | indent_size = 4 22 | indent_style = tab 23 | max_line_length = 88 24 | ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlin.math.* 25 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then 2 | source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs=" 3 | fi 4 | 5 | nix_direnv_watch_file rust-toolchain.toml 6 | nix_direnv_watch_file package.json 7 | if ! use flake . --impure 8 | then 9 | echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2 10 | fi 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.png binary 4 | *.webp binary 5 | *.gif binary 6 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Global code owner 2 | * @Eirenliel 3 | 4 | # Make everyone be able to approve SolarXR submodule changes 5 | /solarxr-protocol @ButterscotchV @Erimelowo @ImUrX @loucass003 6 | 7 | # Make Loucas and Uriel the owners of all GUI stuff 8 | /gui/ @ImUrX @loucass003 9 | /pnpm-lock.yaml @ImUrX @loucass003 10 | /pnpm-workspace.yaml @ImUrX @loucass003 11 | 12 | # Uriel and Erimel responsible for i18n 13 | /gui/public/i18n/ @ImUrX @Erimelowo 14 | /gui/src/i18n/ @ImUrX @Erimelowo 15 | /l10n.toml @ImUrX @Erimelowo 16 | 17 | /gui/src/components/settings/ @Erimelowo @ImUrX 18 | 19 | # Rust part of the GUI 20 | /gui/src-tauri/ @ImUrX 21 | /Cargo.lock @ImUrX 22 | 23 | # Some server code~ 24 | /server/ @ButterscotchV @Eirenliel @Erimelowo 25 | 26 | /server/src/main/java/dev/slimevr/autobone/ @ButterscotchV 27 | /server/src/main/java/dev/slimevr/poserecorder/ @ButterscotchV 28 | /server/src/main/java/dev/slimevr/posestreamer/ @ButterscotchV 29 | 30 | /server/src/main/java/dev/slimevr/osc/ @Erimelowo 31 | /server/src/main/java/dev/slimevr/tracking/processor/ @Erimelowo 32 | /server/src/main/java/dev/slimevr/filtering/ @Erimelowo 33 | 34 | # Linux files 35 | *.nix @ImUrX 36 | /flake.lock @ImUrX 37 | /dev.slimevr.SlimeVR.metainfo.xml @ImUrX 38 | /.envrc @ImUrX 39 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: SlimeVR 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | # Check for updates to GitHub Actions every week 8 | interval: "weekly" 9 | -------------------------------------------------------------------------------- /.github/workflows/label.yml: -------------------------------------------------------------------------------- 1 | # This workflow will triage pull requests and apply a label based on the 2 | # paths that are modified in the pull request. 3 | # 4 | # To use this workflow, you will need to set up a .github/labeler.yml 5 | # file with configuration. For more information, see: 6 | # https://github.com/actions/labeler 7 | 8 | name: Labeler 9 | on: [pull_request_target] 10 | 11 | jobs: 12 | label: 13 | 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: read 17 | pull-requests: write 18 | 19 | steps: 20 | - uses: actions/labeler@v5 21 | with: 22 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 23 | -------------------------------------------------------------------------------- /.github/workflows/pontoon-pr.yml: -------------------------------------------------------------------------------- 1 | name: Update translations in main 2 | on: 3 | push: 4 | branches: 5 | - pontoon 6 | 7 | jobs: 8 | pull_request: 9 | if: ${{ github.repository == 'SlimeVR/SlimeVR-Server' }} 10 | runs-on: ubuntu-latest 11 | permissions: 12 | pull-requests: write 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | submodules: recursive 18 | - uses: repo-sync/pull-request@v2 19 | with: 20 | destination_branch: "main" 21 | pr_title: "New Pontoon translations" 22 | pr_body: "Please don't squash me 🥺" 23 | pr_label: "Area: Translation" 24 | github_token: ${{ secrets.PONTOON_BOT_KEY }} 25 | -------------------------------------------------------------------------------- /.github/workflows/rebase.yml: -------------------------------------------------------------------------------- 1 | # This workflow will rebase `pontoon` with `main` changes, it's for making the 2 | # Pontoon bot not try making commits to main 3 | 4 | name: Rebase pontoon branch to main 5 | on: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | rebase: 12 | if: ${{ github.repository == 'SlimeVR/SlimeVR-Server' }} 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | ref: pontoon 21 | submodules: recursive 22 | # Get all the git history for rebasing 23 | fetch-depth: 0 24 | - name: Rebase 25 | run: | 26 | git config --local user.name "slimevr-bot" 27 | git config --local user.email 'pantoon@slimevr.dev' 28 | git fetch origin main 29 | git rebase origin/main 30 | git submodule update 31 | - name: Push rebase 32 | uses: github-actions-x/commit@v2.9 33 | with: 34 | github-token: ${{ secrets.PONTOON_BOT_KEY }} 35 | push-branch: "pontoon" 36 | commit-message: "update" 37 | force-push: "true" 38 | name: "slimevr-bot" 39 | email: "pantoon@slimevr.dev" 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Syncthing ignore file 5 | .stignore 6 | 7 | # Ignore .idea 8 | .idea 9 | 10 | # Ignore .fleet 11 | .fleet 12 | 13 | # Ignore eclipse stuff 14 | .project 15 | .classpath 16 | .settings 17 | 18 | # VSCode stuff 19 | /.vscode/settings.json 20 | /.vscode/launch.json 21 | 22 | # Ignore eclipse stuff 23 | .project 24 | .classpath 25 | .settings 26 | 27 | # Node Stuff 28 | /node_modules 29 | .husky 30 | 31 | # kotlin stuff 32 | /.kotlin 33 | 34 | # ignore gradle build folder 35 | build/ 36 | 37 | # Rust build artifacts 38 | /target 39 | 40 | # direnv has been claimed for Nix usage 41 | .direnv/ 42 | .devenv 43 | 44 | # Ignore Android local properties 45 | local.properties 46 | 47 | # Ignore temporary config 48 | vrconfig.yml.tmp 49 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "solarxr-protocol"] 2 | path = solarxr-protocol 3 | url = https://github.com/SlimeVR/SolarXR-Protocol.git 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | YELLOW="\033[1;33m" 2 | GREEN="\033[1;32m" 3 | RESET="\033[0m" 4 | 5 | if git rev-parse -q --verify MERGE_HEAD; then 6 | echo -e "${YELLOW}Skipping precommit hook because of merge${RESET}" 7 | exit 0 8 | fi 9 | 10 | APP_PRE_COMMIT_OPTIONS="$(dirname "$0")/_/pre-commit.options" 11 | 12 | if ! [ -f "$APP_PRE_COMMIT_OPTIONS" ]; then 13 | echo -e "${YELLOW}\nSkipping pre-commit hook." 14 | echo -e "If you want to use pre-commit for lint-staged, run:\n" 15 | echo -e " ${GREEN}echo -e 'APP_LINT=true;' > ${APP_PRE_COMMIT_OPTIONS}${RESET}" 16 | echo -e "${YELLOW}\nIt will add some delay before committing!\n${RESET}" 17 | exit 0 18 | fi 19 | 20 | source $APP_PRE_COMMIT_OPTIONS 21 | 22 | if [ -n "${APP_LINT}" ] && [ "${APP_LINT}" == "true" ]; then 23 | echo -e "${GREEN}[husky] [pre-commit] [lint-staged]${RESET}" 24 | case "$(uname -sr)" in 25 | CYGWIN*|MINGW*|MINGW32*|MSYS*) 26 | npx.cmd lint-staged 27 | ;; 28 | 29 | *) 30 | npx lint-staged 31 | ;; 32 | esac 33 | fi 34 | -------------------------------------------------------------------------------- /.imgbotconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignoredFiles": ["gui/src-tauri/icons/*"] 3 | } 4 | -------------------------------------------------------------------------------- /.lintstagedrc.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | 'server/**/*.{java,kt,kts}': (filenames) => 3 | filenames.map( 4 | (filename) => 5 | `./gradlew${ 6 | process.platform === 'win32' ? '.bat' : '' 7 | } spotlessApply "-PspotlessIdeHook=${filename}"` 8 | ), 9 | }; 10 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 18.12.1 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | update-notifier=false 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "richardwillis.vscode-spotless-gradle", 7 | "gaborv.flatbuffers", 8 | "dbaeumer.vscode-eslint", 9 | "esbenp.prettier-vscode", 10 | "rust-lang.rust-analyzer", 11 | "bradlc.vscode-tailwindcss", 12 | "EditorConfig.EditorConfig", 13 | "macabeus.vscode-fluent", 14 | "redhat.vscode-yaml" 15 | ], 16 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 17 | "unwantedRecommendations": [] 18 | } 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | # Use 2021 edition resolver, better resolves crate features. 3 | resolver = "2" 4 | 5 | # A list of all rust crates in the workspace. 6 | members = ["gui/src-tauri"] 7 | 8 | # These settings can be inherited by workspace members 9 | [workspace.package] 10 | edition = "2021" 11 | license = "MIT OR Apache-2.0" 12 | rust-version = "1.75" # Tauri's MSRV 13 | repository = "https://github.com/SlimeVR/SlimeVR-Server" 14 | 15 | [profile.release] 16 | lto = "thin" 17 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Eiren Rain and SlimeVR Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/img/onboarding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/assets/img/onboarding.png -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.ajoberstar.grgit") 3 | } 4 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Fixes bug with spotless. See https://github.com/diffplug/spotless/issues/834#issuecomment-819118761 2 | org.gradle.jvmargs=--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ 3 | --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ 4 | --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ 5 | --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ 6 | --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED 7 | 8 | kotlin.code.style=official 9 | # https://github.com/Kotlin/kotlinx-atomicfu#atomicfu-compiler-plugin 10 | kotlinx.atomicfu.enableJvmIrTransformation=true 11 | 12 | android.useAndroidX=true 13 | android.nonTransitiveRClass=true 14 | org.gradle.unsafe.configuration-cache=false 15 | 16 | kotlinVersion=2.0.20 17 | spotlessVersion=7.0.2 18 | shadowJarVersion=8.3.2 19 | buildconfigVersion=5.5.0 20 | grgitVersion=5.2.2 21 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gui/.env: -------------------------------------------------------------------------------- 1 | VITE_FIRMWARE_TOOL_URL=https://fw-tool-api.slimevr.io 2 | VITE_FIRMWARE_TOOL_S3_URL=https://fw-tool-bucket.slimevr.io 3 | FIRMWARE_TOOL_SCHEMA_URL=https://fw-tool-api.slimevr.io/api-json 4 | 5 | 6 | # VITE_FIRMWARE_TOOL_URL=http://localhost:3000 7 | # VITE_FIRMWARE_TOOL_S3_URL=http://localhost:9000 8 | # FIRMWARE_TOOL_SCHEMA_URL=http://localhost:3000/api-json 9 | -------------------------------------------------------------------------------- /gui/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf -------------------------------------------------------------------------------- /gui/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # JS/TS dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # Build artifacts 12 | /build 13 | /target 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | *.log 27 | 28 | # vite 29 | /dist 30 | /stats.html 31 | vite.config.ts.timestamp* 32 | 33 | # eslint 34 | .eslintcache 35 | 36 | # Sentry Config File 37 | .env.sentry-build-plugin 38 | -------------------------------------------------------------------------------- /gui/.lintstagedrc.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | '**/*.{ts,tsx}': () => 'tsc -p tsconfig.json --noEmit', 3 | 'src/**/*.{js,jsx,ts,tsx}': 'eslint --max-warnings=0 --no-warn-ignored --cache --fix', 4 | '**/*.{js,jsx,ts,tsx,css,scss,md,json}': 'prettier --write', 5 | }; 6 | -------------------------------------------------------------------------------- /gui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | SlimeVR GUI 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /gui/openapi-codegen.config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | generateSchemaTypes, 3 | generateReactQueryComponents, 4 | } from '@openapi-codegen/typescript'; 5 | import { defineConfig } from '@openapi-codegen/cli'; 6 | import dotenv from 'dotenv'; 7 | 8 | dotenv.config() 9 | 10 | export default defineConfig({ 11 | firmwareTool: { 12 | from: { 13 | source: 'url', 14 | url: process.env.FIRMWARE_TOOL_SCHEMA_URL ?? 'http://localhost:3000/api-json', 15 | }, 16 | outputDir: 'src/firmware-tool-api', 17 | to: async (context) => { 18 | const filenamePrefix = 'firmwareTool'; 19 | const { schemasFiles } = await generateSchemaTypes(context, { 20 | filenamePrefix, 21 | }); 22 | await generateReactQueryComponents(context, { 23 | filenamePrefix, 24 | schemasFiles, 25 | }); 26 | }, 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /gui/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /gui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/favicon.ico -------------------------------------------------------------------------------- /gui/public/fonts/Lexend[HEXP,wght].woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/fonts/Lexend[HEXP,wght].woff2 -------------------------------------------------------------------------------- /gui/public/fonts/NotoSansCJK-VF.otf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/fonts/NotoSansCJK-VF.otf.woff2 -------------------------------------------------------------------------------- /gui/public/fonts/OpenDyslexic-Bold-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/fonts/OpenDyslexic-Bold-Italic.woff -------------------------------------------------------------------------------- /gui/public/fonts/OpenDyslexic-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/fonts/OpenDyslexic-Bold.woff -------------------------------------------------------------------------------- /gui/public/fonts/OpenDyslexic-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/fonts/OpenDyslexic-Italic.woff -------------------------------------------------------------------------------- /gui/public/fonts/OpenDyslexic-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/fonts/OpenDyslexic-Regular.woff -------------------------------------------------------------------------------- /gui/public/fonts/Ubuntu-R.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/fonts/Ubuntu-R.woff2 -------------------------------------------------------------------------------- /gui/public/images/R11_board_reset.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/R11_board_reset.webp -------------------------------------------------------------------------------- /gui/public/images/R12_board_reset.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/R12_board_reset.webp -------------------------------------------------------------------------------- /gui/public/images/R14_board_reset_sw.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/R14_board_reset_sw.webp -------------------------------------------------------------------------------- /gui/public/images/autobone-poster.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/autobone-poster.webp -------------------------------------------------------------------------------- /gui/public/images/boxslime.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/boxslime.webp -------------------------------------------------------------------------------- /gui/public/images/curious-slime.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/curious-slime.gif -------------------------------------------------------------------------------- /gui/public/images/front-standing-pose.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/front-standing-pose.webp -------------------------------------------------------------------------------- /gui/public/images/happy-slime.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/happy-slime.gif -------------------------------------------------------------------------------- /gui/public/images/jumping-slime.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/jumping-slime.gif -------------------------------------------------------------------------------- /gui/public/images/mounting-reset-pose.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/mounting-reset-pose.webp -------------------------------------------------------------------------------- /gui/public/images/relaxed_pose_flat.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/relaxed_pose_flat.webp -------------------------------------------------------------------------------- /gui/public/images/relaxed_pose_sitting.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/relaxed_pose_sitting.webp -------------------------------------------------------------------------------- /gui/public/images/relaxed_pose_standing.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/relaxed_pose_standing.webp -------------------------------------------------------------------------------- /gui/public/images/reset-pose.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/reset-pose.webp -------------------------------------------------------------------------------- /gui/public/images/reset/FullResetPose.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/reset/FullResetPose.webp -------------------------------------------------------------------------------- /gui/public/images/reset/FullResetPoseSide.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/reset/FullResetPoseSide.webp -------------------------------------------------------------------------------- /gui/public/images/reset/FullResetPoseWrong.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/reset/FullResetPoseWrong.webp -------------------------------------------------------------------------------- /gui/public/images/sad-slime.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/sad-slime.gif -------------------------------------------------------------------------------- /gui/public/images/slime-girl.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/slime-girl.webp -------------------------------------------------------------------------------- /gui/public/images/slimes.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/slimes.webp -------------------------------------------------------------------------------- /gui/public/images/slimetower.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/slimetower.webp -------------------------------------------------------------------------------- /gui/public/images/stay-aligned/StayAlignedFloor.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/stay-aligned/StayAlignedFloor.webp -------------------------------------------------------------------------------- /gui/public/images/stay-aligned/StayAlignedSitting.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/stay-aligned/StayAlignedSitting.webp -------------------------------------------------------------------------------- /gui/public/images/stay-aligned/StayAlignedStanding.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/images/stay-aligned/StayAlignedStanding.webp -------------------------------------------------------------------------------- /gui/public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/logo192.png -------------------------------------------------------------------------------- /gui/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/logo512.png -------------------------------------------------------------------------------- /gui/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SlimeVR GUI", 3 | "short_name": "SlimeVR GUI", 4 | "description": "A web interface for controlling the SlimeVR Server software", 5 | "display": "standalone", 6 | "theme_color": "#663499", 7 | "background_color": "#663499", 8 | "icons": [ 9 | { 10 | "src": "logo.svg", 11 | "type": "image/svg+xml", 12 | "sizes": "any 512x512 192x192" 13 | }, 14 | { 15 | "src": "favicon.ico", 16 | "sizes": "64x64 32x32 24x24 16x16", 17 | "type": "image/x-icon" 18 | }, 19 | { 20 | "src": "logo192.png", 21 | "type": "image/png", 22 | "sizes": "192x192" 23 | }, 24 | { 25 | "src": "logo512.png", 26 | "type": "image/png", 27 | "sizes": "512x512" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /gui/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /gui/public/videos/autobone.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/videos/autobone.webm -------------------------------------------------------------------------------- /gui/public/videos/turn-on-tracker.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/public/videos/turn-on-tracker.webm -------------------------------------------------------------------------------- /gui/scripts/gitversion.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { promisify } from 'node:util'; 3 | import { exec as execCallback } from 'node:child_process'; 4 | const exec = promisify(execCallback); 5 | 6 | const [commitHash, versionTag, gitClean] = await Promise.all([ 7 | exec('git rev-parse --verify --short HEAD').then((res) => res.stdout.trim()), 8 | exec('git --no-pager tag --sort -taggerdate --points-at HEAD').then((res) => 9 | res.stdout.split('\n')[0].trim().substring(1) 10 | ), 11 | // If not empty then it's not clean 12 | exec('git status --porcelain').then((res) => (res.stdout ? false : true)), 13 | ]); 14 | 15 | console.log( 16 | JSON.stringify({ 17 | version: `${versionTag || `0.0.0-${commitHash}`}${gitClean ? '' : '-dirty'}`, 18 | }) 19 | ); 20 | -------------------------------------------------------------------------------- /gui/src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | WixTools 5 | src/JavaVersion.class 6 | /gen/schemas 7 | -------------------------------------------------------------------------------- /gui/src-tauri/.lintstagedrc.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | '**/*.rs': 'cargo fmt --', 3 | }; 4 | -------------------------------------------------------------------------------- /gui/src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | use cfg_aliases::cfg_aliases; 2 | 3 | fn main() -> shadow_rs::SdResult<()> { 4 | // Bypass for Nix script having libudev-zero and Tauri not liking it 5 | if let Some(path) = option_env!("SLIMEVR_RUST_LD_LIBRARY_PATH") { 6 | println!("cargo:rustc-env=LD_LIBRARY_PATH={path}"); 7 | } 8 | 9 | tauri_build::build(); 10 | cfg_aliases! { 11 | mobile: { any(target_os = "ios", target_os = "android") }, 12 | desktop: { not(any(target_os = "ios", target_os = "android")) } 13 | } 14 | shadow_rs::new() 15 | } 16 | -------------------------------------------------------------------------------- /gui/src-tauri/capabilities/migrated.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "migrated", 3 | "description": "permissions that were migrated from v1", 4 | "local": true, 5 | "windows": [ 6 | "main" 7 | ], 8 | "permissions": [ 9 | "core:default", 10 | "core:window:allow-close", 11 | "core:window:allow-toggle-maximize", 12 | "core:window:allow-minimize", 13 | "core:window:allow-start-dragging", 14 | "core:window:allow-hide", 15 | "core:window:allow-show", 16 | "core:window:allow-set-focus", 17 | "core:window:allow-destroy", 18 | "core:window:allow-request-user-attention", 19 | "core:window:allow-set-decorations", 20 | "store:default", 21 | "os:allow-os-type", 22 | "dialog:allow-save", 23 | "shell:allow-open", 24 | "store:allow-get", 25 | "store:allow-set", 26 | "store:allow-save", 27 | "fs:allow-write-text-file", 28 | "fs:allow-read-text-file", 29 | "fs:allow-exists", 30 | { 31 | "identifier": "fs:scope", 32 | "allow": [{ "path": "$APPDATA" }, { "path": "$APPDATA/**" }] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /gui/src-tauri/dev.slimevr.SlimeVR.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.5 3 | Categories=Game;Development;GTK; 4 | Exec={{exec}} 5 | Icon={{icon}} 6 | 7 | Name=SlimeVR 8 | GenericName=Full-body tracking 9 | Comment=An app for facilitating full-body tracking in virtual reality 10 | Keywords=FBT;VR;Steam;VRChat;IMU 11 | 12 | Terminal=false 13 | Type=Application 14 | -------------------------------------------------------------------------------- /gui/src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/appleTrayIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/appleTrayIcon.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /gui/src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /gui/src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-20x20@1x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-20x20@2x-1.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-20x20@2x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-20x20@3x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-29x29@1x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-29x29@2x-1.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-29x29@2x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-29x29@3x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-40x40@1x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-40x40@2x-1.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-40x40@2x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-40x40@3x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-512@2x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-60x60@2x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-60x60@3x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-76x76@1x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-76x76@2x.png -------------------------------------------------------------------------------- /gui/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png -------------------------------------------------------------------------------- /gui/src-tauri/run.bat.old: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal enableextensions 3 | 4 | echo "TEST" 5 | 6 | cd /d "C:\Program Files (x86)\SlimeVR Server" 7 | 8 | jre\bin\java.exe -Xmx512M -jar slimevr.jar --no-gui 9 | if %errorlevel% NEQ 0 ( 10 | pause 11 | ) -------------------------------------------------------------------------------- /gui/src-tauri/src/JavaVersion.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/gui/src-tauri/src/JavaVersion.jar -------------------------------------------------------------------------------- /gui/src-tauri/src/JavaVersion.java: -------------------------------------------------------------------------------- 1 | public class JavaVersion { 2 | 3 | public static void main(String[] args) 4 | { 5 | var version = Runtime.version().version().get(0); 6 | System.exit(version); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /gui/src/AppLayout.tsx: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from 'react'; 2 | import { useConfig } from './hooks/config'; 3 | import { Outlet, useNavigate } from 'react-router-dom'; 4 | 5 | export function AppLayout() { 6 | const { config } = useConfig(); 7 | const navigate = useNavigate(); 8 | 9 | useLayoutEffect(() => { 10 | if (!config) return; 11 | if (config.theme !== undefined) { 12 | document.documentElement.dataset.theme = config.theme; 13 | } 14 | 15 | if (config.fonts !== undefined) { 16 | document.documentElement.style.setProperty( 17 | '--font-name', 18 | config.fonts.map((x) => `"${x}"`).join(',') 19 | ); 20 | } 21 | 22 | if (config.textSize !== undefined) { 23 | document.documentElement.style.setProperty( 24 | '--font-size', 25 | `${config.textSize}rem` 26 | ); 27 | } 28 | }, [config]); 29 | 30 | useLayoutEffect(() => { 31 | if (config && !config.doneOnboarding) { 32 | navigate('/onboarding/home'); 33 | } 34 | }, [config?.doneOnboarding]); 35 | 36 | return ( 37 | <> 38 | 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /gui/src/components/EmptyLayout.scss: -------------------------------------------------------------------------------- 1 | .empty-layout { 2 | display: grid; 3 | grid-template: 4 | 't' var(--topbar-h) 5 | 'c' calc(100% - var(--topbar-h)) 6 | / 100%; 7 | } 8 | -------------------------------------------------------------------------------- /gui/src/components/EmptyLayout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { TopBar } from './TopBar'; 3 | import './EmptyLayout.scss'; 4 | 5 | export function EmptyLayout({ children }: { children: ReactNode }) { 6 | return ( 7 |
8 |
9 | 10 |
11 |
12 | {children} 13 |
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /gui/src/components/MainLayout.scss: -------------------------------------------------------------------------------- 1 | .main-layout { 2 | display: grid; 3 | grid-template: 4 | 't t' var(--topbar-h) 5 | 's c' calc(100% - var(--topbar-h)) 6 | / var(--navbar-w) calc(100% - var(--navbar-w)); 7 | 8 | &:has(.widgets) { 9 | grid-template: 10 | 't t t' var(--topbar-h) 11 | 's c w' calc(100% - var(--topbar-h)) 12 | / var(--navbar-w) calc(100% - var(--navbar-w) - var(--widget-w)) var(--widget-w); 13 | } 14 | 15 | @screen mobile { 16 | grid-template: 17 | 't' var(--topbar-h) 18 | 'c' calc(100% - var(--topbar-h) - var(--navbar-h)) 19 | 's' calc(var(--navbar-h)) 20 | / 100%; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gui/src/components/Preload.tsx: -------------------------------------------------------------------------------- 1 | import { Helmet } from 'react-helmet'; 2 | export function Preload() { 3 | return ( 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 19 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /gui/src/components/commons/A.tsx: -------------------------------------------------------------------------------- 1 | import { open } from '@tauri-apps/plugin-shell'; 2 | import { ReactNode } from 'react'; 3 | 4 | export function A({ href, children }: { href?: string; children?: ReactNode }) { 5 | return ( 6 | 9 | href && open(href).catch(() => window.open(href, '_blank')) 10 | } 11 | className="underline" 12 | > 13 | {children} 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /gui/src/components/commons/BaseModal.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { ReactNode } from 'react'; 3 | import ReactModal from 'react-modal'; 4 | 5 | export function BaseModal({ 6 | children, 7 | important = false, 8 | closeable = true, 9 | ...props 10 | }: { 11 | isOpen: boolean; 12 | children: ReactNode; 13 | appendClasses?: string; 14 | important?: boolean; 15 | closeable?: boolean; 16 | } & ReactModal.Props) { 17 | return ( 18 | 40 | {children} 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /gui/src/components/commons/BigButton.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import React, { ReactNode } from 'react'; 3 | 4 | export function BigButton({ 5 | icon, 6 | disabled, 7 | children, 8 | onClick, 9 | ...props 10 | }: { 11 | disabled?: boolean; 12 | icon: ReactNode; 13 | children?: ReactNode; 14 | } & React.HTMLAttributes) { 15 | return ( 16 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /gui/src/components/commons/Modal.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { ReactNode } from 'react'; 3 | import ReactModal from 'react-modal'; 4 | 5 | export function EmptyModal({ 6 | children, 7 | ...props 8 | }: { children?: ReactNode } & ReactModal.Props) { 9 | return ( 10 | 22 | {children} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /gui/src/components/commons/ThemeSelector.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { Control, Controller } from 'react-hook-form'; 3 | 4 | export function ThemeSelector({ 5 | control, 6 | name, 7 | value, 8 | // input props 9 | disabled, 10 | colors, 11 | ...props 12 | }: { 13 | control: Control; 14 | name: string; 15 | colors: string | undefined; 16 | value: string; 17 | } & React.HTMLProps) { 18 | return ( 19 | ( 23 | 42 | )} 43 | /> 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/BellIcon.tsx: -------------------------------------------------------------------------------- 1 | export function BellIcon({ width = 24 }: { width?: number }) { 2 | return ( 3 | 9 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/BulbIcon.tsx: -------------------------------------------------------------------------------- 1 | export function BulbIcon() { 2 | return ( 3 | 9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/CircleIcon.tsx: -------------------------------------------------------------------------------- 1 | export function CircleIcon(props: any) { 2 | return ( 3 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/CloseIcon.tsx: -------------------------------------------------------------------------------- 1 | export function CloseIcon({ 2 | className = 'stroke-window-icon', 3 | size = 35, 4 | }: { 5 | className?: string; 6 | size?: number; 7 | }) { 8 | return ( 9 | 17 | 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/CrossIcon.tsx: -------------------------------------------------------------------------------- 1 | export function CrossIcon({ 2 | size = 20, 3 | className, 4 | }: { 5 | size?: number; 6 | className?: string; 7 | }) { 8 | return ( 9 | 16 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/DownloadIcon.tsx: -------------------------------------------------------------------------------- 1 | export function DownloadIcon({ width = 22 }: { width?: number }) { 2 | return ( 3 | 9 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/EscapeIcon.tsx: -------------------------------------------------------------------------------- 1 | export function EscapeIcon({ 2 | size = 24, 3 | className = '', 4 | }: { 5 | size?: number; 6 | className?: string; 7 | }) { 8 | return ( 9 | 17 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/FileIcon.tsx: -------------------------------------------------------------------------------- 1 | export function FileIcon({ width = 24 }: { width?: number }) { 2 | return ( 3 | 10 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/FootIcon.tsx: -------------------------------------------------------------------------------- 1 | export function FootIcon({ 2 | width = 28, 3 | flipped = false, 4 | }: { 5 | width?: number; 6 | flipped?: boolean; 7 | }) { 8 | return ( 9 | 16 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/HeadIcon.tsx: -------------------------------------------------------------------------------- 1 | export function HeadIcon({ width = 24 }: { width?: number }) { 2 | return ( 3 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/HeadsetIcon.tsx: -------------------------------------------------------------------------------- 1 | export function HeadsetIcon({ width = 24 }: { width?: number }) { 2 | return ( 3 | 4 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/HumanIcon.tsx: -------------------------------------------------------------------------------- 1 | export function HumanIcon({ width = 20 }: { width?: number }) { 2 | return ( 3 | 4 | 5 | 6 | 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/ImportIcon.tsx: -------------------------------------------------------------------------------- 1 | export function ImportIcon({ size = 24 }: { size?: number }) { 2 | return ( 3 | 9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/LoaderIcon.tsx: -------------------------------------------------------------------------------- 1 | export enum SlimeState { 2 | HAPPY, 3 | SAD, 4 | JUMPY, 5 | CURIOUS, 6 | } 7 | 8 | export function LoaderIcon({ 9 | slimeState = SlimeState.HAPPY, 10 | size = 85, 11 | }: { 12 | slimeState: SlimeState; 13 | size?: number | string; 14 | }) { 15 | return ( 16 | <> 17 | 24 | 31 | 38 | 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/LowerArmIcon.tsx: -------------------------------------------------------------------------------- 1 | export function LowerArmIcon({ 2 | width = 24, 3 | flipped = false, 4 | }: { 5 | width?: number; 6 | flipped?: boolean; 7 | }) { 8 | return ( 9 | 16 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/MaximiseIcon.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | 3 | export function MaximiseIcon({ className }: { className?: string }) { 4 | return ( 5 | 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/MinimiseIcon.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | 3 | export function MinimiseIcon({ className }: { className?: string }) { 4 | return ( 5 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/NeckIcon.tsx: -------------------------------------------------------------------------------- 1 | export function NeckIcon({ width = 24 }: { width?: number }) { 2 | return ( 3 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/PauseIcon.tsx: -------------------------------------------------------------------------------- 1 | export function PauseIcon({ width = 33 }: { width?: number }) { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/PercentIcon.tsx: -------------------------------------------------------------------------------- 1 | export function PercentIcon({ size = 24 }: { size?: number }) { 2 | return ( 3 | 9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/PlayIcon.tsx: -------------------------------------------------------------------------------- 1 | export function PlayIcon({ width = 33 }: { width?: number }) { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | 9 | export function PlayCircleIcon({ width = 24 }: { width?: number }) { 10 | return ( 11 | 17 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/QuestionIcon.tsx: -------------------------------------------------------------------------------- 1 | export function QuestionIcon({ width = 23 }: { width?: number }) { 2 | return ( 3 | 10 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/RecordIcon.tsx: -------------------------------------------------------------------------------- 1 | export function RecordIcon({ width = 33 }: { width?: number }) { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/RouterIcon.tsx: -------------------------------------------------------------------------------- 1 | export function RouterIcon() { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/RulerIcon.tsx: -------------------------------------------------------------------------------- 1 | export function RulerIcon({ width = 20 }: { width?: number }) { 2 | return ( 3 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/SimevrIcon.tsx: -------------------------------------------------------------------------------- 1 | export function SlimeVRIcon({ drag }: { drag?: boolean }) { 2 | return ( 3 | 11 | 17 | 23 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/SlimeUpIcon.tsx: -------------------------------------------------------------------------------- 1 | export function SlimeUpIcon({ width = 60 }: { width?: number }) { 2 | return ( 3 | 9 | 10 | 19 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/SlimeVRIcon.tsx: -------------------------------------------------------------------------------- 1 | export function SlimeVRIcon({ width = 28 }: { width?: number }) { 2 | return ( 3 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/SparkleIcon.tsx: -------------------------------------------------------------------------------- 1 | export function SparkleIcon({ width = 20 }: { width?: number }) { 2 | return ( 3 | 9 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/SquaresIcon.tsx: -------------------------------------------------------------------------------- 1 | export function SquaresIcon() { 2 | return ( 3 | 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/TrashIcon.tsx: -------------------------------------------------------------------------------- 1 | export function TrashIcon({ size = 33 }: { size?: number }) { 2 | return ( 3 | 9 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/UploadFileIcon.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | 3 | export function UploadFileIcon({ 4 | width = 24, 5 | isDragging = false, 6 | }: { 7 | width?: number; 8 | isDragging?: boolean; 9 | }) { 10 | return ( 11 | 19 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/UpperArmIcon.tsx: -------------------------------------------------------------------------------- 1 | export function UpperArmIcon({ 2 | width = 24, 3 | flipped = false, 4 | }: { 5 | width?: number; 6 | flipped?: boolean; 7 | }) { 8 | return ( 9 | 16 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/UsbIcon.tsx: -------------------------------------------------------------------------------- 1 | export function USBIcon() { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/WaistIcon.tsx: -------------------------------------------------------------------------------- 1 | export function WaistIcon({ width = 24 }: { width?: number }) { 2 | return ( 3 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/WarningIcon.tsx: -------------------------------------------------------------------------------- 1 | export function WarningIcon(props: any) { 2 | return ( 3 | 11 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /gui/src/components/commons/icon/WrenchIcons.tsx: -------------------------------------------------------------------------------- 1 | export function WrenchIcon({ width = 20 }: { width?: number }) { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /gui/src/components/onboarding/OnboardingContextProvicer.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { OnboardingContextC, useProvideOnboarding } from '@/hooks/onboarding'; 3 | 4 | export function OnboardingContextProvider({ 5 | children, 6 | }: { 7 | children: ReactNode; 8 | }) { 9 | const context = useProvideOnboarding(); 10 | 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /gui/src/components/onboarding/OnboardingLayout.scss: -------------------------------------------------------------------------------- 1 | .onboarding-layout { 2 | display: grid; 3 | grid-template: 4 | 't' var(--topbar-h) 5 | 'c' calc(100% - var(--topbar-h)) 6 | / 100%; 7 | 8 | @screen mobile { 9 | --topbar-h: 74px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gui/src/components/onboarding/SkipSetupButton.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import { useEffect } from 'react'; 3 | import { EscapeIcon } from '@/components/commons/icon/EscapeIcon'; 4 | 5 | export function SkipSetupButton({ 6 | modalVisible, 7 | onClick, 8 | visible, 9 | }: { 10 | onClick: () => void; 11 | modalVisible: boolean; 12 | visible: boolean; 13 | }) { 14 | if (!visible) return <>; 15 | useEffect(() => { 16 | if (modalVisible) return; 17 | 18 | function onEscape(ev: KeyboardEvent) { 19 | if (ev.key === 'Escape') onClick(); 20 | } 21 | 22 | document.addEventListener('keydown', onEscape, { passive: true }); 23 | 24 | return () => document.removeEventListener('keydown', onEscape); 25 | }, [modalVisible]); 26 | 27 | return ( 28 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /gui/src/components/onboarding/pages/ConnectTracker.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --connect-tracker-layout-sidebar: 350px; 3 | --connect-tracker-layout-top: 20px; 4 | 5 | @screen lg { 6 | --connect-tracker-layout-sidebar: 400px; 7 | } 8 | } 9 | 10 | .connect-tracker-layout { 11 | display: grid; 12 | grid-template: 13 | 's t' var(--connect-tracker-layout-top) 14 | 's c' calc(100% - var(--connect-tracker-layout-top)) 15 | / calc(var(--connect-tracker-layout-sidebar)) calc(100% - var( 16 | --connect-tracker-layout-sidebar 17 | )); 18 | 19 | @screen mobile { 20 | grid-template: 21 | 's' auto 22 | 't' auto 23 | 'c' auto 24 | / 100%; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gui/src/components/onboarding/pages/body-proportions/autobone-steps/Done.tsx: -------------------------------------------------------------------------------- 1 | import { Typography } from '@/components/commons/Typography'; 2 | import { useLocalization } from '@fluent/react'; 3 | import { Button } from '@/components/commons/Button'; 4 | import { SkeletonVisualizerWidget } from '@/components/widgets/SkeletonVisualizerWidget'; 5 | 6 | export function DoneStep({ variant }: { variant: 'onboarding' | 'alone' }) { 7 | const { l10n } = useLocalization(); 8 | 9 | return ( 10 |
11 |
12 | 13 | {l10n.getString('onboarding-automatic_proportions-done-title')} 14 | 15 | 16 | {l10n.getString('onboarding-automatic_proportions-done-description')} 17 | 18 |
19 | 20 |
21 | {variant === 'onboarding' && ( 22 | 25 | )} 26 |
27 |
28 | 29 |
30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /gui/src/components/onboarding/pages/body-proportions/scaled-steps/Done.tsx: -------------------------------------------------------------------------------- 1 | import { Typography } from '@/components/commons/Typography'; 2 | import { useLocalization } from '@fluent/react'; 3 | import { Button } from '@/components/commons/Button'; 4 | import { SkeletonVisualizerWidget } from '@/components/widgets/SkeletonVisualizerWidget'; 5 | 6 | export function DoneStep({ variant }: { variant: 'onboarding' | 'alone' }) { 7 | const { l10n } = useLocalization(); 8 | 9 | return ( 10 |
11 |
12 | 13 | {l10n.getString('onboarding-scaled_proportions-done-title')} 14 | 15 | 16 | {l10n.getString('onboarding-scaled_proportions-done-description')} 17 | 18 |
19 | 20 |
21 | {variant === 'onboarding' && ( 22 | 25 | )} 26 |
27 | 28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /gui/src/components/onboarding/pages/stay-aligned/stay-aligned-steps/Done.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/commons/Button'; 2 | import { Typography } from '@/components/commons/Typography'; 3 | import { VerticalStepComponentProps } from '@/components/commons/VerticalStepper'; 4 | import { Localized } from '@fluent/react'; 5 | 6 | export function DoneStep({ goTo }: VerticalStepComponentProps) { 7 | return ( 8 |
9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | 22 | 27 | 28 |
29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /gui/src/components/providers/AppContext.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { AppContextC, useProvideAppContext } from '@/hooks/app'; 3 | 4 | export function AppContextProvider({ children }: { children: ReactNode }) { 5 | const context = useProvideAppContext(); 6 | 7 | return ( 8 | {children} 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /gui/src/components/providers/ConfigContext.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, useContext, useLayoutEffect } from 'react'; 2 | import { ConfigContextC, loadConfig, useConfigProvider } from '@/hooks/config'; 3 | import { DEFAULT_LOCALE, LangContext } from '@/i18n/config'; 4 | import { getSentryOrCompute } from '@/utils/sentry'; 5 | 6 | const config = await loadConfig(); 7 | 8 | if (config?.errorTracking !== undefined) { 9 | // load sentry ASAP to catch early errors 10 | getSentryOrCompute(config.errorTracking ?? false); 11 | } 12 | 13 | export function ConfigContextProvider({ children }: { children: ReactNode }) { 14 | const context = useConfigProvider(config); 15 | const { changeLocales } = useContext(LangContext); 16 | 17 | useLayoutEffect(() => { 18 | changeLocales([config?.lang || DEFAULT_LOCALE]); 19 | }, []); 20 | 21 | useLayoutEffect(() => { 22 | if (config?.errorTracking !== undefined) { 23 | // Alows for sentry to refresh if user change the setting once the gui 24 | // is initialized 25 | getSentryOrCompute(config.errorTracking ?? false); 26 | } 27 | }, [config?.errorTracking]); 28 | 29 | return ( 30 | 31 | {children} 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /gui/src/components/providers/StatusSystemContext.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { StatusSystemC, useProvideStatusContext } from '@/hooks/status-system'; 3 | 4 | export function StatusProvider({ children }: { children: ReactNode }) { 5 | const context = useProvideStatusContext(); 6 | 7 | return ( 8 | {children} 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /gui/src/components/settings/SettingsLayout.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --settings-sidebar-w: 200px; 3 | } 4 | 5 | .settings-layout { 6 | display: grid; 7 | grid-template: 8 | 't t t' var(--topbar-h) 9 | 'n s c' calc(100% - var(--topbar-h)) 10 | / var(--navbar-w) var(--settings-sidebar-w) calc(100% - var(--navbar-w) - var( 11 | --settings-sidebar-w 12 | )); 13 | 14 | @screen lg { 15 | --settings-sidebar-w: 270px; 16 | } 17 | 18 | @screen mobile { 19 | grid-template: 20 | 't' var(--topbar-h) 21 | 'c' calc(100% - var(--topbar-h) - var(--navbar-h)) 22 | 'n' calc(var(--navbar-h)) 23 | / 100%; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gui/src/components/stay-aligned/StayAlignedInfo.tsx: -------------------------------------------------------------------------------- 1 | import { Typography } from '@/components/commons/Typography'; 2 | import { useLocaleConfig } from '@/i18n/config'; 3 | import { TrackerDataT } from 'solarxr-protocol'; 4 | 5 | export function StayAlignedInfo({ 6 | color, 7 | tracker, 8 | }: { 9 | color: 'primary' | 'secondary'; 10 | tracker: TrackerDataT; 11 | }) { 12 | const { currentLocales } = useLocaleConfig(); 13 | const degreeFormat = new Intl.NumberFormat(currentLocales, { 14 | style: 'unit', 15 | unit: 'degree', 16 | minimumFractionDigits: 1, 17 | maximumFractionDigits: 1, 18 | }); 19 | 20 | const stayAligned = tracker.stayAligned; 21 | if (!stayAligned) { 22 | return <>; 23 | } 24 | 25 | const locked = stayAligned.locked ? '🔒' : ''; 26 | const delta = degreeFormat.format(stayAligned.yawCorrectionInDeg); 27 | 28 | return ( 29 | 30 | {locked} {delta} 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /gui/src/components/tracker/TrackerWifi.tsx: -------------------------------------------------------------------------------- 1 | import { WifiIcon } from '@/components/commons/icon/WifiIcon'; 2 | import { Typography } from '@/components/commons/Typography'; 3 | 4 | export function TrackerWifi({ 5 | rssi, 6 | ping, 7 | rssiShowNumeric, 8 | disabled, 9 | textColor = 'secondary', 10 | }: { 11 | rssi: number | null; 12 | ping: number | null; 13 | rssiShowNumeric?: boolean; 14 | disabled?: boolean; 15 | textColor?: string; 16 | }) { 17 | return ( 18 |
19 |
20 | 21 |
22 | {(!disabled && (ping != null || (rssiShowNumeric && rssi != null)) && ( 23 |
24 | {ping != null && ( 25 | 26 | {ping} ms 27 | 28 | )} 29 | {rssiShowNumeric && rssi != null && ( 30 | 31 | {rssi} dBm 32 | 33 | )} 34 |
35 | )) || ( 36 |
37 |
38 |
39 | )} 40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /gui/src/components/vr-mode/VRModePage.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useBreakpoint } from '@/hooks/breakpoint'; 3 | import { WidgetsComponent } from '@/components/WidgetsComponent'; 4 | import { useNavigate } from 'react-router-dom'; 5 | 6 | export function VRModePage() { 7 | const nav = useNavigate(); 8 | const { isMobile } = useBreakpoint('mobile'); 9 | 10 | useEffect(() => { 11 | if (!isMobile) nav('/'); 12 | }, [isMobile]); 13 | 14 | return ( 15 |
16 | 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /gui/src/firmware-tool-api/firmwareToolUtils.ts: -------------------------------------------------------------------------------- 1 | type ComputeRange< 2 | N extends number, 3 | Result extends Array = [], 4 | > = Result['length'] extends N 5 | ? Result 6 | : ComputeRange; 7 | 8 | export type ClientErrorStatus = Exclude< 9 | ComputeRange<500>[number], 10 | ComputeRange<400>[number] 11 | >; 12 | export type ServerErrorStatus = Exclude< 13 | ComputeRange<600>[number], 14 | ComputeRange<500>[number] 15 | >; 16 | -------------------------------------------------------------------------------- /gui/src/hooks/breakpoint.ts: -------------------------------------------------------------------------------- 1 | import resolveConfig from 'tailwindcss/resolveConfig'; 2 | import { useMediaQuery } from 'react-responsive'; 3 | import tailwindConfig from '../../tailwind.config'; 4 | 5 | const fullConfig = resolveConfig(tailwindConfig as any); 6 | 7 | type BreakpointKey = keyof typeof tailwindConfig.theme.screens; 8 | 9 | export function useBreakpoint(breakpointKey: K) { 10 | // FIXME There is a flickering issue caused by this, because isMobile is not resolved fast enough 11 | // one solution would be to have this solved only once on the appProvider and reuse the value all the time 12 | const bool = useMediaQuery({ 13 | query: fullConfig.theme.screens[breakpointKey].raw 14 | ? fullConfig.theme.screens[breakpointKey].raw 15 | : `(min-width: ${fullConfig.theme.screens[breakpointKey]})`, 16 | }); 17 | const capitalizedKey = 18 | breakpointKey.toString()[0].toUpperCase() + breakpointKey.toString().substring(1); 19 | type Key = `is${Capitalize}`; 20 | return { 21 | [`is${capitalizedKey}`]: bool, 22 | } as Record; 23 | } 24 | 25 | export function useIsTauri() { 26 | return window.isTauri; 27 | } 28 | -------------------------------------------------------------------------------- /gui/src/hooks/choker-warning.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { BodyPart } from 'solarxr-protocol'; 3 | 4 | /** 5 | * I dedicate this hook to @uriel ;) 6 | */ 7 | export function useChokerWarning({ next }: { next: (role: T) => void }) { 8 | const [shouldShowChokerWarn, setShouldShowChokerWarn] = useState(false); 9 | const [currentBodyPart, setCurrentBodyPart] = useState(null); 10 | 11 | return { 12 | shouldShowChokerWarn, 13 | closeChokerWarning: (cancel: boolean) => { 14 | setShouldShowChokerWarn(false); 15 | if (!cancel) { 16 | sessionStorage.setItem('neckWarning', 'true'); 17 | if (currentBodyPart) next(currentBodyPart); 18 | } 19 | }, 20 | tryOpenChokerWarning: (role: T) => { 21 | if (role === BodyPart.NECK && !sessionStorage.getItem('neckWarning')) { 22 | setCurrentBodyPart(role); 23 | setShouldShowChokerWarn(true); 24 | } else { 25 | next(role); 26 | } 27 | }, 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /gui/src/hooks/countdown.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from 'react'; 2 | 3 | export function useCountdown({ 4 | duration = 3, 5 | onCountdownEnd = () => { 6 | return; 7 | }, 8 | }: { 9 | duration?: number; 10 | onCountdownEnd: () => void; 11 | }) { 12 | const [isCounting, setIsCounting] = useState(false); 13 | const [timer, setDisplayTimer] = useState(0); 14 | const countdownTimer = useRef>(); 15 | const counter = useRef(0); 16 | 17 | const startCountdown = () => { 18 | setIsCounting(true); 19 | setDisplayTimer(duration); 20 | counter.current = 0; 21 | countdownTimer.current = setInterval( 22 | () => { 23 | counter.current++; 24 | setDisplayTimer(duration - counter.current); 25 | if (counter.current >= duration) { 26 | clearInterval(countdownTimer.current); 27 | resetEnd(); 28 | } 29 | }, 30 | duration > 1 ? 1000 : duration * 1000 31 | ); 32 | }; 33 | 34 | const resetEnd = () => { 35 | setIsCounting(false); 36 | clearInterval(countdownTimer.current); 37 | onCountdownEnd(); 38 | }; 39 | 40 | const abortCountdown = () => { 41 | setIsCounting(false); 42 | clearInterval(countdownTimer.current); 43 | }; 44 | 45 | return { 46 | timer, 47 | isCounting, 48 | startCountdown, 49 | abortCountdown, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /gui/src/hooks/imu-logic.ts: -------------------------------------------------------------------------------- 1 | import { FlatDeviceTracker } from '@/store/app-store'; 2 | import { useMemo } from 'react'; 3 | 4 | const IGNORED_BOARDS = new Set(['Sony Mocopi', 'Haritora']); 5 | 6 | export function useIsRestCalibrationTrackers( 7 | connectedTrackers: FlatDeviceTracker[] 8 | ): boolean { 9 | const imuExists = useMemo( 10 | () => 11 | connectedTrackers.some( 12 | (tracker) => 13 | tracker.tracker.info?.isImu && 14 | !( 15 | tracker.device?.hardwareInfo?.boardType && 16 | IGNORED_BOARDS.has(tracker.device?.hardwareInfo?.boardType as string) 17 | ) 18 | ), 19 | [connectedTrackers] 20 | ); 21 | 22 | return imuExists; 23 | } 24 | 25 | export function useRestCalibrationTrackers( 26 | connectedTrackers: FlatDeviceTracker[] 27 | ): FlatDeviceTracker[] { 28 | const restTrackers = useMemo( 29 | () => connectedTrackers.filter((tracker) => tracker.tracker.info?.isImu), 30 | [connectedTrackers] 31 | ); 32 | 33 | return restTrackers; 34 | } 35 | -------------------------------------------------------------------------------- /gui/src/hooks/layout.ts: -------------------------------------------------------------------------------- 1 | import { MutableRefObject, useLayoutEffect, useRef, useState } from 'react'; 2 | 3 | export function useElemSize( 4 | forwardRef?: MutableRefObject 5 | ) { 6 | const innerRef = useRef(null); 7 | const ref = forwardRef || innerRef; 8 | const [height, setHeight] = useState(0); 9 | const [width, setWidth] = useState(0); 10 | 11 | const observer = useRef( 12 | new ResizeObserver((entries) => { 13 | const { width, height } = entries[0].contentRect; 14 | setWidth(width); 15 | setHeight(height); 16 | }) 17 | ); 18 | 19 | useLayoutEffect(() => { 20 | if (ref.current) { 21 | observer.current.observe(ref.current); 22 | } 23 | 24 | return () => { 25 | if (!ref.current) return; 26 | observer.current.unobserve(ref.current); 27 | }; 28 | }, [ref, observer]); 29 | 30 | return { 31 | ref, 32 | height, 33 | width, 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /gui/src/hooks/previous.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | export function usePrevious(value: T) { 4 | const ref = useRef(); 5 | useEffect(() => { 6 | ref.current = value; // assign the value of ref to the argument 7 | }, [value]); // this code will run when the value of 'value' changes 8 | return ref.current; // in the end, return the current ref value. 9 | } 10 | -------------------------------------------------------------------------------- /gui/src/hooks/wifi-form.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useForm } from 'react-hook-form'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import { useOnboarding } from './onboarding'; 5 | 6 | export interface WifiFormData { 7 | ssid: string; 8 | password?: string; 9 | } 10 | 11 | export function useWifiForm() { 12 | const navigate = useNavigate(); 13 | const { state, setWifiCredentials } = useOnboarding(); 14 | const { register, reset, handleSubmit, formState, control } = 15 | useForm({ 16 | defaultValues: {}, 17 | reValidateMode: 'onSubmit', 18 | }); 19 | 20 | useEffect(() => { 21 | if (state.wifi) { 22 | reset({ 23 | ssid: state.wifi.ssid, 24 | password: state.wifi.password, 25 | }); 26 | } 27 | }, []); 28 | 29 | const submitWifiCreds = (value: WifiFormData) => { 30 | setWifiCredentials(value.ssid, value.password ?? ''); 31 | navigate('/onboarding/connect-trackers', { 32 | state: { alonePage: state.alonePage }, 33 | }); 34 | }; 35 | 36 | return { 37 | submitWifiCreds, 38 | handleSubmit, 39 | register, 40 | formState, 41 | hasWifiCreds: !!state.wifi, 42 | control, 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /gui/src/index.tsx: -------------------------------------------------------------------------------- 1 | import '@fontsource/poppins/500.css'; 2 | import '@fontsource/poppins/700.css'; 3 | import React from 'react'; 4 | import * as ReactDOMClient from 'react-dom/client'; 5 | import Modal from 'react-modal'; 6 | import App from './App'; 7 | import { AppLocalizationProvider } from './i18n/config'; 8 | import './index.scss'; 9 | 10 | Modal.setAppElement('#root'); 11 | 12 | const container = document.getElementById('root'); 13 | 14 | if (container) { 15 | const root = ReactDOMClient.createRoot(container); 16 | root.render( 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /gui/src/maths/angle.ts: -------------------------------------------------------------------------------- 1 | export const DEG_TO_RAD = Math.PI / 180.0; 2 | 3 | export const RAD_TO_DEG = 180.0 / Math.PI; 4 | 5 | export function angleIsNearZero(angle: number, maxError = 1e-6): boolean { 6 | return Math.abs(angle) < maxError; 7 | } 8 | -------------------------------------------------------------------------------- /gui/src/maths/vector3.ts: -------------------------------------------------------------------------------- 1 | import { Vec3fT } from 'solarxr-protocol'; 2 | import { Vector3 } from 'three'; 3 | 4 | export type Vector3Object = { x: number; y: number; z: number }; 5 | 6 | export function averageVector(vecs: Vector3[]) { 7 | if (vecs.length === 0) return new Vector3(); 8 | const sum = vecs.reduce((prev, curr) => prev.add(curr), new Vector3()); 9 | return sum.divideScalar(vecs.length); 10 | } 11 | 12 | export function Vector3FromVec3fT(vec?: Vector3Object | null) { 13 | return vec ? new Vector3(vec.x, vec.y, vec.z) : new Vector3(); 14 | } 15 | 16 | export function Vector3ToVec3fT(q: Vector3Object) { 17 | const vec = new Vec3fT(); 18 | vec.x = q.x; 19 | vec.y = q.y; 20 | vec.z = q.z; 21 | return vec; 22 | } 23 | -------------------------------------------------------------------------------- /gui/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /gui/src/utils/a11y.ts: -------------------------------------------------------------------------------- 1 | import { error } from './logging'; 2 | 3 | export function a11yClick(event: React.KeyboardEvent | React.MouseEvent) { 4 | if (event.type === 'click') { 5 | return true; 6 | } else if (event.type === 'keydown') { 7 | const keyboard = event as React.KeyboardEvent; 8 | return keyboard.key === 'Enter' || keyboard.key === ' '; 9 | } 10 | } 11 | 12 | export function waitUntil( 13 | condition: (() => boolean) | (() => Promise), 14 | time: number, 15 | tries?: number 16 | ): Promise { 17 | return new Promise((resolve, rej) => { 18 | const isPromise = typeof condition() !== 'boolean'; 19 | const interval = setInterval(() => { 20 | if (tries && --tries === 0) { 21 | error(new Error('waitUntil ran out of tries')); 22 | clearInterval(interval); 23 | resolve(); 24 | } 25 | const boolPromise = condition(); 26 | if (!isPromise && boolPromise) { 27 | clearInterval(interval); 28 | resolve(); 29 | } else if (isPromise) { 30 | (boolPromise as Promise) 31 | .then((bool) => { 32 | if (!bool) return; 33 | clearInterval(interval); 34 | resolve(); 35 | }) 36 | .catch(rej); 37 | } 38 | }, time); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /gui/src/utils/formatting.ts: -------------------------------------------------------------------------------- 1 | import { BodyPart } from 'solarxr-protocol'; 2 | 3 | export const bodypartToString = (id: BodyPart) => BodyPart[id].replace(/_/g, ' '); 4 | 5 | type Vector3 = { x: number; y: number; z: number }; 6 | export const formatVector3 = ({ x, y, z }: Vector3, precision = 0) => 7 | `${x.toFixed(precision)} / ${y.toFixed(precision)} / ${z.toFixed(precision)}`; 8 | 9 | /** 10 | * Convert an ASCII string to a number with it's bytes represented in little endian 11 | */ 12 | export function magic(strings: TemplateStringsArray): number { 13 | return ( 14 | strings 15 | // joins strings 16 | .join('') 17 | // splits per character 18 | .split('') 19 | .reduce((prev, cur, i) => prev + (cur.charCodeAt(0) << (i * 8)), 0) 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /gui/src/utils/logging.ts: -------------------------------------------------------------------------------- 1 | import { invoke, isTauri } from '@tauri-apps/api/core'; 2 | 3 | export function log(...msgs: any[]) { 4 | console.log(...msgs); 5 | if (isTauri()) invoke('logging', { msg: msgs.join() }); 6 | } 7 | 8 | export function error(...msgs: any[]) { 9 | console.error(...msgs); 10 | if (isTauri()) invoke('erroring', { msg: msgs.join() }); 11 | } 12 | 13 | export function warn(...msgs: any[]) { 14 | console.warn(...msgs); 15 | if (isTauri()) invoke('warning', { msg: msgs.join() }); 16 | } 17 | -------------------------------------------------------------------------------- /gui/src/utils/tauri.ts: -------------------------------------------------------------------------------- 1 | import { invoke, isTauri } from '@tauri-apps/api/core'; 2 | import { type } from '@tauri-apps/plugin-os'; 3 | 4 | /** 5 | * Fetches the resource as a blob if necessary because of https://github.com/tauri-apps/tauri/issues/3725 6 | * @param url static asset to fetch 7 | * @returns URL 8 | */ 9 | export async function fetchResourceUrl(url: string) { 10 | if (!isTauri() || type() !== 'linux') return url; 11 | return URL.createObjectURL(await fetch(url).then((res) => res.blob())); 12 | } 13 | 14 | // FIXME: For some fucking reason, you can't top-level await on a react component file 15 | // on Chromium on developments builds specifically -Uriel 16 | export const AUTOBONE_VIDEO = await fetchResourceUrl('/videos/autobone.webm'); 17 | export const CONNECT_TRACKER = await fetchResourceUrl('/videos/turn-on-tracker.webm'); 18 | 19 | export const isTrayAvailable = 20 | isTauri() && (await invoke('is_tray_available')); 21 | -------------------------------------------------------------------------------- /gui/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | 4 | declare const __COMMIT_HASH__: string; 5 | declare const __VERSION_TAG__: string; 6 | declare const __GIT_CLEAN__: boolean; 7 | 8 | interface Window { 9 | readonly isTauri: boolean; 10 | } 11 | 12 | declare module 'tailwind-gradient-mask-image'; 13 | -------------------------------------------------------------------------------- /gui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "baseUrl": ".", 19 | "paths": { 20 | "@/*": ["./src/*"] 21 | } 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /gui/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /l10n.toml: -------------------------------------------------------------------------------- 1 | # File format info: https://moz-l10n-config.readthedocs.io/en/latest/fileformat.html 2 | basepath = "." 3 | 4 | [[paths]] 5 | reference = "gui/public/i18n/en/*.ftl" 6 | l10n = "gui/public/i18n/{locale}/*.ftl" 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slimevr-ui", 3 | "version": "0.5.1", 4 | "private": true, 5 | "packageManager": "pnpm@9.12.2", 6 | "workspaces": [ 7 | "solarxr-protocol", 8 | "gui" 9 | ], 10 | "scripts": { 11 | "gui": "pnpm run update-solarxr && cd gui && pnpm run dev", 12 | "tauri": "cd gui && pnpm run tauri", 13 | "skipbundler": "cd gui && pnpm run skipbundler", 14 | "build": "pnpm run tauri build", 15 | "update-solarxr": "cd solarxr-protocol && pnpm run build", 16 | "prepare": "husky && pnpm run update-solarxr", 17 | "preinstall": "npx only-allow pnpm" 18 | }, 19 | "devDependencies": { 20 | "husky": "^9.1.6", 21 | "lint-staged": "^15.2.10" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "solarxr-protocol" 3 | - "gui" 4 | - "!solarxr-protocol/lib/flatbuffers/**" 5 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.81" 3 | profile = "default" 4 | components = ["rustc", "cargo", "clippy", "rustfmt", "rust-analyzer", "rust-src"] 5 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # 88 comes from python's black as inspiration. 2 | # Note that rust by default treats a tab as having a width of 4 spaces when doing the 3 | # calculation for line wrapping. 4 | max_width = 88 5 | # Tabs allow for enhanced accessibility and the ability to choose indent size. 6 | hard_tabs = true 7 | -------------------------------------------------------------------------------- /server/.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle build output directory 2 | build 3 | 4 | /bin/ 5 | */bin/ 6 | 7 | MagnetoLib.dll 8 | vrconfig.yml 9 | 10 | # BVH 11 | BVH Recordings 12 | 13 | # AutoBone 14 | Recordings 15 | AutoBone Recordings 16 | Load AutoBone Recordings 17 | 18 | # Logs 19 | *.log.* 20 | *.log.lck 21 | *.log 22 | logs/ 23 | 24 | android/release 25 | android/debug 26 | 27 | -------------------------------------------------------------------------------- /server/LICENSE.md: -------------------------------------------------------------------------------- 1 | This directory and all subdirectories is licensed under the MIT License ([LICENSE-MIT]). 2 | 3 | New contributions still must be dual licensed under both [LICENSE-MIT] and [LICENSE-APACHE]. 4 | 5 | [LICENSE-MIT]: /LICENSE-MIT 6 | [LICENSE-APACHE]: /LICENSE-APACHE 7 | -------------------------------------------------------------------------------- /server/android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /src/main/resources/web-gui 3 | -------------------------------------------------------------------------------- /server/android/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /server/android/src/main/java/java/awt/Color.java: -------------------------------------------------------------------------------- 1 | package java.awt; 2 | 3 | public class Color { 4 | } 5 | -------------------------------------------------------------------------------- /server/android/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 16 | 22 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /server/android/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /server/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /server/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /server/android/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /server/android/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /server/android/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /server/android/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /server/android/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /server/android/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /server/android/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /server/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /server/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /server/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /server/android/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | -------------------------------------------------------------------------------- /server/android/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /server/android/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #663499 4 | -------------------------------------------------------------------------------- /server/android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SlimeVR 3 | -------------------------------------------------------------------------------- /server/android/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | -------------------------------------------------------------------------------- /server/android/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /server/android/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /server/android/src/main/resources/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/resources/icon128.png -------------------------------------------------------------------------------- /server/android/src/main/resources/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/resources/icon16.png -------------------------------------------------------------------------------- /server/android/src/main/resources/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/resources/icon256.png -------------------------------------------------------------------------------- /server/android/src/main/resources/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/resources/icon32.png -------------------------------------------------------------------------------- /server/android/src/main/resources/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/resources/icon48.png -------------------------------------------------------------------------------- /server/android/src/main/resources/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SlimeVR/SlimeVR-Server/26f330c762f0490a4b212276554527448dc447fa/server/android/src/main/resources/icon64.png -------------------------------------------------------------------------------- /server/core/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /server/core/protobuf_update.bat: -------------------------------------------------------------------------------- 1 | protoc --proto_path=../SlimeVR-OpenVR-Driver/src/bridge --java_out=./src/main/java ProtobufMessages.proto -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/NetworkProtocol.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr; 2 | 3 | public enum NetworkProtocol { 4 | OWO_LEGACY, 5 | SLIMEVR_RAW, 6 | SLIMEVR_FLATBUFFER, 7 | SLIMEVR_WEBSOCKET 8 | } 9 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/autobone/AutoBoneListener.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.autobone 2 | 3 | import dev.slimevr.autobone.AutoBone.Epoch 4 | import dev.slimevr.poseframeformat.PoseFrames 5 | import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets 6 | import java.util.* 7 | 8 | interface AutoBoneListener { 9 | fun onAutoBoneProcessStatus( 10 | processType: AutoBoneProcessType, 11 | message: String?, 12 | current: Long, 13 | total: Long, 14 | eta: Float, 15 | completed: Boolean, 16 | success: Boolean, 17 | ) 18 | 19 | fun onAutoBoneRecordingEnd(recording: PoseFrames) 20 | fun onAutoBoneEpoch(epoch: Epoch) 21 | fun onAutoBoneEnd(configValues: EnumMap) 22 | } 23 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/autobone/AutoBoneProcessType.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.autobone 2 | 3 | enum class AutoBoneProcessType(val id: Int) { 4 | NONE(0), 5 | RECORD(1), 6 | SAVE(2), 7 | PROCESS(3), 8 | ; 9 | 10 | companion object { 11 | fun getById(id: Int): AutoBoneProcessType? = byId[id] 12 | } 13 | } 14 | 15 | private val byId = AutoBoneProcessType.values().associateBy { it.id } 16 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/autobone/AutoBoneStep.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.autobone 2 | 3 | class AutoBoneStep( 4 | var hmdHeight: Float = 1f, 5 | val targetHmdHeight: Float = 1f, 6 | var adjustRate: Float = 0f, 7 | ) { 8 | 9 | val errorStats = StatsCalculator() 10 | 11 | val heightOffset: Float 12 | get() = targetHmdHeight - hmdHeight 13 | } 14 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/autobone/StatsCalculator.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.autobone 2 | 3 | import kotlin.math.* 4 | 5 | /** 6 | * This is a stat calculator based on Welford's online algorithm 7 | * https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford%27s_online_algorithm 8 | */ 9 | class StatsCalculator { 10 | private var count = 0 11 | var mean = 0f 12 | private set 13 | private var m2 = 0f 14 | 15 | fun reset() { 16 | count = 0 17 | mean = 0f 18 | m2 = 0f 19 | } 20 | 21 | fun addValue(newValue: Float) { 22 | count += 1 23 | val delta = newValue - mean 24 | mean += delta / count 25 | val delta2 = newValue - mean 26 | m2 += delta * delta2 27 | } 28 | 29 | val variance: Float 30 | get() = if (count < 1) { 31 | Float.NaN 32 | } else { 33 | m2 / count 34 | } 35 | val sampleVariance: Float 36 | get() = if (count < 2) { 37 | Float.NaN 38 | } else { 39 | m2 / (count - 1) 40 | } 41 | val standardDeviation: Float 42 | get() = sqrt(variance) 43 | } 44 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/autobone/errors/AutoBoneException.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.autobone.errors 2 | 3 | class AutoBoneException : Exception { 4 | constructor() 5 | constructor(message: String?) : super(message) 6 | constructor(cause: Throwable?) : super(cause) 7 | constructor(message: String?, cause: Throwable?) : super(message, cause) 8 | constructor( 9 | message: String?, 10 | cause: Throwable?, 11 | enableSuppression: Boolean, 12 | writableStackTrace: Boolean, 13 | ) : super(message, cause, enableSuppression, writableStackTrace) 14 | } 15 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/autobone/errors/HeightError.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.autobone.errors 2 | 3 | import dev.slimevr.autobone.AutoBoneStep 4 | import dev.slimevr.autobone.PoseFrameStep 5 | import kotlin.math.* 6 | 7 | // The difference from the current height to the target height 8 | class HeightError : IAutoBoneError { 9 | @Throws(AutoBoneException::class) 10 | override fun getStepError(step: PoseFrameStep): Float = getHeightError( 11 | step.data.hmdHeight, 12 | step.data.targetHmdHeight, 13 | ) 14 | 15 | fun getHeightError(currentHeight: Float, targetHeight: Float): Float = abs(targetHeight - currentHeight) 16 | } 17 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/autobone/errors/IAutoBoneError.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.autobone.errors 2 | 3 | import dev.slimevr.autobone.AutoBoneStep 4 | import dev.slimevr.autobone.PoseFrameStep 5 | 6 | interface IAutoBoneError { 7 | @Throws(AutoBoneException::class) 8 | fun getStepError(step: PoseFrameStep): Float 9 | } 10 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/autobone/errors/proportions/HardProportionLimiter.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.autobone.errors.proportions 2 | 3 | import dev.slimevr.tracking.processor.HumanPoseManager 4 | import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets 5 | 6 | /** 7 | * @param targetRatio The bone to height ratio to target 8 | * @param skeletonConfigOffset The SkeletonConfigOffset to use for the length 9 | */ 10 | open class HardProportionLimiter( 11 | override val targetRatio: Float = 0f, 12 | override val skeletonConfigOffset: SkeletonConfigOffsets, 13 | ) : ProportionLimiter { 14 | override fun getProportionError(humanPoseManager: HumanPoseManager, height: Float): Float { 15 | val boneLength = humanPoseManager.getOffset(skeletonConfigOffset) 16 | return targetRatio - boneLength / height 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/autobone/errors/proportions/ProportionLimiter.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.autobone.errors.proportions 2 | 3 | import dev.slimevr.tracking.processor.HumanPoseManager 4 | import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets 5 | 6 | interface ProportionLimiter { 7 | fun getProportionError(humanPoseManager: HumanPoseManager, height: Float): Float 8 | val targetRatio: Float 9 | val skeletonConfigOffset: SkeletonConfigOffsets 10 | } 11 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/bridge/BridgeThread.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.bridge; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | 7 | @Retention(value = RetentionPolicy.SOURCE) 8 | public @interface BridgeThread { 9 | } 10 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/AutoBoneConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config 2 | 3 | class AutoBoneConfig { 4 | var cursorIncrement = 2 5 | var minDataDistance = 1 6 | var maxDataDistance = 1 7 | var numEpochs = 50 8 | var printEveryNumEpochs = 25 9 | var initialAdjustRate = 10.0f 10 | var adjustRateDecay = 1.0f 11 | var slideErrorFactor = 1.0f 12 | var offsetSlideErrorFactor = 0.0f 13 | var footHeightOffsetErrorFactor = 0.0f 14 | var bodyProportionErrorFactor = 0.05f 15 | var heightErrorFactor = 0.0f 16 | var positionErrorFactor = 0.0f 17 | var positionOffsetErrorFactor = 0.0f 18 | var calcInitError = false 19 | var randomizeFrameOrder = true 20 | var scaleEachStep = true 21 | var sampleCount = 1500 22 | var sampleRateMs = 20L 23 | var saveRecordings = false 24 | var useSkeletonHeight = false 25 | var randSeed = 4L 26 | var useFrameFiltering = false 27 | var maxFinalError = 0.03f 28 | } 29 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/BridgeConfig.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config; 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 4 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 5 | import com.fasterxml.jackson.databind.ser.std.StdKeySerializers; 6 | import dev.slimevr.config.serializers.BooleanMapDeserializer; 7 | import dev.slimevr.tracking.trackers.TrackerRole; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | 12 | public class BridgeConfig { 13 | 14 | @JsonDeserialize(using = BooleanMapDeserializer.class) 15 | @JsonSerialize(keyUsing = StdKeySerializers.StringKeySerializer.class) 16 | public Map trackers = new HashMap<>(); 17 | public boolean automaticSharedTrackersToggling = true; 18 | 19 | public BridgeConfig() { 20 | } 21 | 22 | public boolean getBridgeTrackerRole(TrackerRole role, boolean def) { 23 | return trackers.getOrDefault(role.name().toLowerCase(), def); 24 | } 25 | 26 | public void setBridgeTrackerRole(TrackerRole role, boolean val) { 27 | this.trackers.put(role.name().toLowerCase(), val); 28 | } 29 | 30 | public Map getTrackers() { 31 | return trackers; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/DriftCompensationConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config 2 | 3 | import dev.slimevr.VRServer 4 | 5 | class DriftCompensationConfig { 6 | 7 | // Is drift compensation enabled 8 | var enabled = false 9 | 10 | // Is drift prediction enabled 11 | var prediction = false 12 | 13 | // Amount of drift compensation applied 14 | var amount = 0.8f 15 | 16 | // Max resets for the calculated average drift 17 | var maxResets = 6 18 | 19 | fun updateTrackersDriftCompensation() { 20 | for (t in VRServer.instance.allTrackers) { 21 | if (t.isImu()) { 22 | t.resetsHandler.readDriftCompensationConfig(this) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/FiltersConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config 2 | 3 | import dev.slimevr.VRServer 4 | 5 | class FiltersConfig { 6 | 7 | // Type of filtering applied (none, smoothing or prediction) 8 | var type = "prediction" 9 | 10 | // Amount/Intensity of the specified filtering (0 to 1) 11 | var amount = 0.2f 12 | 13 | fun updateTrackersFilters() { 14 | for (tracker in VRServer.instance.allTrackers) { 15 | if (tracker.allowFiltering) { 16 | tracker.filteringHandler.readFilteringConfig(this, tracker.getRotation()) 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/LegTweaksConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config 2 | 3 | class LegTweaksConfig { 4 | var correctionStrength = 0.3f 5 | var alwaysUseFloorclip = false 6 | } 7 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/OSCConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config 2 | 3 | open class OSCConfig { 4 | 5 | // Are the OSC receiver and sender enabled? 6 | var enabled = false 7 | 8 | // Port to receive OSC messages from 9 | var portIn = 0 10 | 11 | // Port to send out OSC messages at 12 | var portOut = 0 13 | 14 | // Address to send out OSC messages at 15 | var address = "127.0.0.1" 16 | } 17 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/OverlayConfig.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config; 2 | 3 | public class OverlayConfig { 4 | 5 | private boolean isMirrored = false; 6 | private boolean isVisible = false; 7 | 8 | 9 | public boolean isMirrored() { 10 | return isMirrored; 11 | } 12 | 13 | public boolean isVisible() { 14 | return isVisible; 15 | } 16 | 17 | public void setMirrored(boolean mirrored) { 18 | isMirrored = mirrored; 19 | } 20 | 21 | public void setVisible(boolean visible) { 22 | isVisible = visible; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/ResetsConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config 2 | 3 | import dev.slimevr.VRServer 4 | 5 | enum class ArmsResetModes(val id: Int) { 6 | // Upper arm going back and forearm going forward 7 | BACK(0), 8 | 9 | // Arms going forward 10 | FORWARD(1), 11 | 12 | // Arms going up to the sides into a tpose 13 | TPOSE_UP(2), 14 | 15 | // Arms going down to the sides from a tpose 16 | TPOSE_DOWN(3), 17 | ; 18 | 19 | companion object { 20 | val values = entries.toTypedArray() 21 | 22 | @JvmStatic 23 | fun fromId(id: Int): ArmsResetModes? { 24 | for (filter in values) { 25 | if (filter.id == id) return filter 26 | } 27 | return null 28 | } 29 | } 30 | } 31 | 32 | class ResetsConfig { 33 | 34 | // Enable mounting reset for feet? 35 | var resetMountingFeet = false 36 | 37 | // Reset mode used for the arms 38 | var mode = ArmsResetModes.BACK 39 | 40 | // Yaw reset smoothing time in seconds 41 | var yawResetSmoothTime = 0.0f 42 | 43 | // Save automatic mounting reset calibration 44 | var saveMountingReset = false 45 | 46 | // Reset the HMD's pitch upon full reset 47 | var resetHmdPitch = false 48 | 49 | fun updateTrackersResetsSettings() { 50 | for (t in VRServer.instance.allTrackers) { 51 | t.resetsHandler.readResetConfig(this) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/StayAlignedConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore 4 | 5 | class StayAlignedConfig { 6 | 7 | /** 8 | * Apply yaw correction 9 | */ 10 | var enabled = false 11 | 12 | /** 13 | * Temporarily hide the yaw correction from Stay Aligned. 14 | * 15 | * Players can enable this to compare to when Stay Aligned is not enabled. Useful to 16 | * verify if Stay Aligned improved the situation. Also useful to prevent players 17 | * from saying "Stay Aligned screwed up my trackers!!" when it's actually a tracker 18 | * that is drifting extremely badly. 19 | * 20 | * Do not serialize to config so that when the server restarts, it is always false. 21 | */ 22 | @JsonIgnore 23 | var hideYawCorrection = false 24 | 25 | /** 26 | * Standing relaxed pose 27 | */ 28 | val standingRelaxedPose = StayAlignedRelaxedPoseConfig() 29 | 30 | /** 31 | * Sitting relaxed pose 32 | */ 33 | val sittingRelaxedPose = StayAlignedRelaxedPoseConfig() 34 | 35 | /** 36 | * Flat relaxed pose 37 | */ 38 | val flatRelaxedPose = StayAlignedRelaxedPoseConfig() 39 | 40 | /** 41 | * Whether setup has been completed 42 | */ 43 | var setupComplete = false 44 | } 45 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/StayAlignedRelaxedPoseConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config 2 | 3 | class StayAlignedRelaxedPoseConfig { 4 | 5 | /** 6 | * Whether Stay Aligned should adjust the tracker yaws when the player is in this 7 | * pose. 8 | */ 9 | var enabled = false 10 | 11 | /** 12 | * Angle between the upper leg yaw and the center yaw. 13 | */ 14 | var upperLegAngleInDeg = 0.0f 15 | 16 | /** 17 | * Angle between the lower leg yaw and the center yaw. 18 | */ 19 | var lowerLegAngleInDeg = 0.0f 20 | 21 | /** 22 | * Angle between the foot and the center yaw. 23 | */ 24 | var footAngleInDeg = 0.0f 25 | } 26 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/TapDetectionConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config 2 | 3 | import com.jme3.math.FastMath 4 | 5 | // handles the tap detection config 6 | // this involves the number of taps, the delay, and whether or not the feature is enabled 7 | // for each reset type 8 | class TapDetectionConfig { 9 | var yawResetDelay = 0.2f 10 | var fullResetDelay = 1.0f 11 | var mountingResetDelay = 1.0f 12 | var yawResetEnabled = true 13 | var fullResetEnabled = true 14 | var mountingResetEnabled = true 15 | var setupMode = false 16 | var yawResetTaps = 2 17 | // clamp to 2-3 to prevent errors 18 | set(yawResetTaps) { 19 | field = FastMath.clamp(yawResetTaps.toFloat(), 2f, 10f).toInt() 20 | field = yawResetTaps 21 | } 22 | var fullResetTaps = 3 23 | set(fullResetTaps) { 24 | field = FastMath.clamp(fullResetTaps.toFloat(), 2f, 10f).toInt() 25 | field = fullResetTaps 26 | } 27 | var mountingResetTaps = 3 28 | set(mountingResetTaps) { 29 | field = FastMath.clamp(mountingResetTaps.toFloat(), 2f, 10f).toInt() 30 | field = mountingResetTaps 31 | } 32 | var numberTrackersOverThreshold = 1 33 | } 34 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/VMCConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config 2 | 3 | class VMCConfig : OSCConfig() { 4 | 5 | // Anchor the tracking at the hip? 6 | var anchorHip = true 7 | 8 | // JSON part of the VRM to be used 9 | var vrmJson: String? = null 10 | 11 | // Mirror the tracking before sending it (turn left <=> turn right, left leg <=> right leg) 12 | var mirrorTracking = false 13 | } 14 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/VRCOSCConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize 4 | import com.fasterxml.jackson.databind.annotation.JsonSerialize 5 | import com.fasterxml.jackson.databind.ser.std.StdKeySerializers 6 | import dev.slimevr.config.serializers.BooleanMapDeserializer 7 | import dev.slimevr.tracking.trackers.TrackerRole 8 | import java.util.* 9 | 10 | class VRCOSCConfig : OSCConfig() { 11 | 12 | // Which trackers' data to send 13 | @JsonDeserialize(using = BooleanMapDeserializer::class) 14 | @JsonSerialize(keyUsing = StdKeySerializers.StringKeySerializer::class) 15 | var trackers: MutableMap = HashMap() 16 | 17 | var oscqueryEnabled: Boolean = true 18 | 19 | fun getOSCTrackerRole(role: TrackerRole, def: Boolean): Boolean = trackers.getOrDefault(role.name.lowercase(Locale.getDefault()), def) 20 | 21 | fun setOSCTrackerRole(role: TrackerRole, `val`: Boolean) { 22 | trackers[role.name.lowercase(Locale.getDefault())] = `val` 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/serializers/BooleanMapDeserializer.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config.serializers; 2 | 3 | /** 4 | * This class allows the use of the utility super class MapDeserializer that 5 | * takes the Value of a map as its Generic parameter. It is so you can use that 6 | * class in a @JsonDeserialize annotation on the Map field inside the config 7 | * instance 8 | * 9 | * @see dev.slimevr.config.VRConfig 10 | */ 11 | public class BooleanMapDeserializer extends MapDeserializer { 12 | public BooleanMapDeserializer() { 13 | super(Boolean.class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/serializers/BridgeConfigMapDeserializer.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config.serializers; 2 | 3 | import dev.slimevr.config.BridgeConfig; 4 | 5 | 6 | /** 7 | * This class allows the use of the utility super class MapDeserializer that 8 | * takes the Value of a map as its Generic parameter. It is so you can use that 9 | * class in a @JsonDeserialize annotation on the Map field inside the config 10 | * instance 11 | * 12 | * @see dev.slimevr.config.VRConfig 13 | */ 14 | public class BridgeConfigMapDeserializer extends MapDeserializer { 15 | public BridgeConfigMapDeserializer() { 16 | super(BridgeConfig.class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/serializers/FloatMapDeserializer.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config.serializers; 2 | 3 | /** 4 | * This class allows the use of the utility super class MapDeserializer that 5 | * takes the Value of a map as its Generic parameter. It is so you can use that 6 | * class in a @JsonDeserialize annotation on the Map field inside the config 7 | * instance 8 | * 9 | * @see dev.slimevr.config.VRConfig 10 | */ 11 | public class FloatMapDeserializer extends MapDeserializer { 12 | public FloatMapDeserializer() { 13 | super(Float.class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/serializers/MapDeserializer.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config.serializers; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.databind.DeserializationContext; 5 | import com.fasterxml.jackson.databind.JsonDeserializer; 6 | import com.fasterxml.jackson.databind.type.MapType; 7 | import com.fasterxml.jackson.databind.type.TypeFactory; 8 | 9 | import java.io.IOException; 10 | import java.util.HashMap; 11 | 12 | 13 | /** 14 | * This class is a utility class that allows to write Map serializers easily to 15 | * be used in the VRConfig (@see {@link dev.slimevr.config.VRConfig}) 16 | * 17 | * @see BooleanMapDeserializer to see how it is used 18 | */ 19 | public abstract class MapDeserializer extends JsonDeserializer> { 20 | 21 | private final Class valueClass; 22 | 23 | public MapDeserializer(Class valueClass) { 24 | super(); 25 | this.valueClass = valueClass; 26 | } 27 | 28 | @Override 29 | public HashMap deserialize(JsonParser p, DeserializationContext dc) 30 | throws IOException { 31 | TypeFactory typeFactory = dc.getTypeFactory(); 32 | MapType mapType = typeFactory 33 | .constructMapType(HashMap.class, String.class, valueClass); 34 | return dc.readValue(p, mapType); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/serializers/QuaternionDeserializer.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config.serializers; 2 | 3 | import com.fasterxml.jackson.core.JacksonException; 4 | import com.fasterxml.jackson.core.JsonParser; 5 | import com.fasterxml.jackson.databind.DeserializationContext; 6 | import com.fasterxml.jackson.databind.JsonDeserializer; 7 | import com.fasterxml.jackson.databind.JsonNode; 8 | import io.github.axisangles.ktmath.ObjectQuaternion; 9 | 10 | import java.io.IOException; 11 | 12 | 13 | public class QuaternionDeserializer extends JsonDeserializer { 14 | @Override 15 | public ObjectQuaternion deserialize(JsonParser p, DeserializationContext ctxt) 16 | throws IOException, JacksonException { 17 | JsonNode node = p.getCodec().readTree(p); 18 | 19 | return new ObjectQuaternion( 20 | (float) node.get("w").asDouble(), 21 | (float) node.get("x").asDouble(), 22 | (float) node.get("y").asDouble(), 23 | (float) node.get("z").asDouble() 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/serializers/QuaternionSerializer.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config.serializers; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | import io.github.axisangles.ktmath.ObjectQuaternion; 7 | 8 | import java.io.IOException; 9 | 10 | 11 | public class QuaternionSerializer extends JsonSerializer { 12 | 13 | @Override 14 | public void serialize(ObjectQuaternion value, JsonGenerator gen, SerializerProvider serializers) 15 | throws IOException { 16 | gen.writeStartObject(); 17 | gen.writeNumberField("x", value.getX()); 18 | gen.writeNumberField("y", value.getY()); 19 | gen.writeNumberField("z", value.getZ()); 20 | gen.writeNumberField("w", value.getW()); 21 | gen.writeEndObject(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/config/serializers/TrackerConfigMapDeserializer.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.config.serializers; 2 | 3 | import dev.slimevr.config.TrackerConfig; 4 | 5 | 6 | /** 7 | * This class allows the use of the utility super class MapDeserializer that 8 | * takes the Value of a map as its Generic parameter. It is so you can use that 9 | * class in a @JsonDeserialize annotation on the Map field inside the config 10 | * instance 11 | * 12 | * @see dev.slimevr.config.VRConfig 13 | */ 14 | public class TrackerConfigMapDeserializer extends MapDeserializer { 15 | public TrackerConfigMapDeserializer() { 16 | super(TrackerConfig.class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/filtering/TrackerFilters.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.filtering 2 | 3 | import java.util.* 4 | 5 | enum class TrackerFilters(val id: Int, val configKey: String) { 6 | NONE(0, "none"), 7 | SMOOTHING(1, "smoothing"), 8 | PREDICTION(2, "prediction"), 9 | ; 10 | 11 | companion object { 12 | private val byConfigkey: MutableMap = HashMap() 13 | 14 | init { 15 | for (configVal in values()) { 16 | byConfigkey[configVal.configKey.lowercase(Locale.getDefault())] = 17 | configVal 18 | } 19 | } 20 | 21 | val values = values() 22 | 23 | @JvmStatic 24 | fun fromId(id: Int): TrackerFilters? { 25 | for (filter in values) { 26 | if (filter.id == id) return filter 27 | } 28 | return null 29 | } 30 | 31 | @JvmStatic 32 | fun getByConfigkey(configKey: String?): TrackerFilters? = if (configKey == null) null else byConfigkey[configKey.lowercase(Locale.getDefault())] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateListener.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.firmware 2 | 3 | interface FirmwareUpdateListener { 4 | fun onUpdateStatusChange(event: UpdateStatusEvent<*>) 5 | } 6 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/firmware/FirmwareUpdateMethod.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.firmware 2 | 3 | enum class FirmwareUpdateMethod(val id: Byte) { 4 | NONE(solarxr_protocol.rpc.FirmwareUpdateMethod.NONE), 5 | OTA(solarxr_protocol.rpc.FirmwareUpdateMethod.OTAFirmwareUpdate), 6 | SERIAL(solarxr_protocol.rpc.FirmwareUpdateMethod.SerialFirmwareUpdate), 7 | ; 8 | 9 | companion object { 10 | fun getById(id: Byte): FirmwareUpdateMethod? = byId[id] 11 | } 12 | } 13 | 14 | private val byId = FirmwareUpdateMethod.entries.associateBy { it.id } 15 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/firmware/SerialFlashingHandler.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.firmware 2 | 3 | import dev.llelievr.espflashkotlin.FlasherSerialInterface 4 | 5 | interface SerialFlashingHandler : FlasherSerialInterface 6 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/firmware/UpdateDeviceId.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.firmware 2 | 3 | data class UpdateDeviceId( 4 | val type: FirmwareUpdateMethod, 5 | val id: T, 6 | ) { 7 | override fun equals(other: Any?): Boolean { 8 | if (this === other) return true 9 | if (javaClass != other?.javaClass) return false 10 | 11 | other as UpdateDeviceId<*> 12 | 13 | if (type != other.type) return false 14 | if (id != other.id) return false 15 | 16 | return true 17 | } 18 | 19 | override fun hashCode(): Int { 20 | var result = type.hashCode() 21 | result = 31 * result + (id?.hashCode() ?: 0) 22 | return result 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/firmware/UpdateStatusEvent.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.firmware 2 | 3 | data class UpdateStatusEvent( 4 | val deviceId: UpdateDeviceId, 5 | val status: FirmwareUpdateStatus, 6 | val progress: Int = 0, 7 | val time: Long = System.currentTimeMillis(), 8 | ) 9 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/math/AngleAverage.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.math 2 | 3 | import kotlin.math.* 4 | 5 | /** 6 | * Averages angles by summing vectors. 7 | * 8 | * See https://www.themathdoctors.org/averaging-angles/ 9 | */ 10 | class AngleAverage { 11 | 12 | private var sumX = 0.0f 13 | private var sumY = 0.0f 14 | 15 | /** 16 | * Adds another angle to the average. 17 | */ 18 | fun add(angle: Angle, weight: Float = 1.0f) { 19 | sumX += cos(angle.toRad()) * weight 20 | sumY += sin(angle.toRad()) * weight 21 | } 22 | 23 | /** 24 | * Gets the average angle. 25 | */ 26 | fun toAngle(): Angle = 27 | if (isEmpty()) { 28 | Angle.ZERO 29 | } else { 30 | Angle.ofRad(atan2(sumY, sumX)) 31 | } 32 | 33 | /** 34 | * Whether there are any angles to average. 35 | */ 36 | fun isEmpty() = 37 | sumX == 0.0f && sumY == 0.0f 38 | } 39 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/math/AngleErrors.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.math 2 | 3 | import kotlin.math.* 4 | 5 | class AngleErrors { 6 | 7 | private var sumSqrErrors = 0.0f 8 | 9 | fun add(error: Angle) { 10 | sumSqrErrors += error.toRad() * error.toRad() 11 | } 12 | 13 | fun toL2Norm() = 14 | Angle.ofRad(sqrt(sumSqrErrors)) 15 | } 16 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/osc/OSCHandler.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.osc; 2 | 3 | import com.illposed.osc.transport.OSCPortIn; 4 | import com.illposed.osc.transport.OSCPortOut; 5 | 6 | import java.net.InetAddress; 7 | 8 | 9 | public interface OSCHandler { 10 | 11 | public void refreshSettings(boolean refreshRouterSettings); 12 | 13 | public void updateOscReceiver(int portIn, String[] args); 14 | 15 | public void updateOscSender(int portOut, String address); 16 | 17 | public void update(); 18 | 19 | public OSCPortOut getOscSender(); 20 | 21 | public int getPortOut(); 22 | 23 | public InetAddress getAddress(); 24 | 25 | public OSCPortIn getOscReceiver(); 26 | 27 | public int getPortIn(); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/poseframeformat/PfsPackets.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.poseframeformat 2 | 3 | /** 4 | * Packet ID ([UByte]), 5 | * Packet data (see [PfsPackets], implemented in [PfsIO]) 6 | */ 7 | enum class PfsPackets(val id: Int) { 8 | /** 9 | * Frame interval ([Float] seconds) 10 | */ 11 | RECORDING_DEFINITION(0), 12 | 13 | /** 14 | * Tracker ID ([UByte]), 15 | * Tracker name (UTF-8 [String]) 16 | */ 17 | TRACKER_DEFINITION(1), 18 | 19 | /** 20 | * Tracker ID ([UByte]), 21 | * Frame number ([UInt]), 22 | * PFR frame data (see [PfrIO.writeFrame] & [PfrIO.readFrame]) 23 | */ 24 | TRACKER_FRAME(2), 25 | 26 | /** 27 | * Hmd height ([Float]), 28 | * Floor height ([Float]), 29 | * Body proportion configs (Count ([UShort]) x (Key (UTF-8 [String]), Value ([Float])) 30 | */ 31 | PROPORTIONS_CONFIG(3), 32 | ; 33 | 34 | val byteId = id.toUByte() 35 | 36 | companion object { 37 | val byId = entries.associateBy { it.id } 38 | val byByteId = entries.associateBy { it.byteId } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/poseframeformat/trackerdata/TrackerFrameData.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.poseframeformat.trackerdata 2 | 3 | enum class TrackerFrameData(val id: Int) { 4 | DESIGNATION_STRING(0), 5 | ROTATION(1), 6 | POSITION(2), 7 | TRACKER_POSITION_ENUM(3), 8 | ACCELERATION(4), 9 | RAW_ROTATION(5), 10 | ; 11 | 12 | val flag: Int = 1 shl id 13 | 14 | /* 15 | * Inline is fine for these, there's no negative to inlining them as they'll never 16 | * change, so any warning about it can be safely ignored 17 | */ 18 | inline fun check(dataFlags: Int): Boolean = dataFlags and flag != 0 19 | 20 | inline fun add(dataFlags: Int): Int = dataFlags or flag 21 | 22 | inline fun remove(dataFlags: Int): Int = dataFlags xor flag 23 | } 24 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/posestreamer/BVHSettings.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.posestreamer; 2 | 3 | public class BVHSettings { 4 | private float offsetScale = 100f; 5 | private float positionScale = 100f; 6 | private boolean writeEndNodes = false; 7 | 8 | public static final BVHSettings DEFAULT = new BVHSettings(); 9 | public static final BVHSettings BLENDER = new BVHSettings(DEFAULT) 10 | .setOffsetScale(1f) 11 | .setPositionScale(1f); 12 | 13 | public BVHSettings() { 14 | } 15 | 16 | public BVHSettings(BVHSettings source) { 17 | this.offsetScale = source.offsetScale; 18 | this.positionScale = source.positionScale; 19 | this.writeEndNodes = source.writeEndNodes; 20 | } 21 | 22 | public float getOffsetScale() { 23 | return offsetScale; 24 | } 25 | 26 | public BVHSettings setOffsetScale(float offsetScale) { 27 | this.offsetScale = offsetScale; 28 | return this; 29 | } 30 | 31 | public float getPositionScale() { 32 | return positionScale; 33 | } 34 | 35 | public BVHSettings setPositionScale(float positionScale) { 36 | this.positionScale = positionScale; 37 | return this; 38 | } 39 | 40 | public boolean shouldWriteEndNodes() { 41 | return writeEndNodes; 42 | } 43 | 44 | public BVHSettings setWriteEndNodes(boolean writeEndNodes) { 45 | this.writeEndNodes = writeEndNodes; 46 | return this; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/posestreamer/PoseDataStream.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.posestreamer; 2 | 3 | import dev.slimevr.tracking.processor.skeleton.HumanSkeleton; 4 | 5 | import java.io.*; 6 | 7 | 8 | public abstract class PoseDataStream implements AutoCloseable { 9 | 10 | protected final OutputStream outputStream; 11 | protected boolean closed = false; 12 | 13 | protected PoseDataStream(OutputStream outputStream) { 14 | this.outputStream = outputStream; 15 | } 16 | 17 | protected PoseDataStream(File file) throws FileNotFoundException { 18 | this(new FileOutputStream(file)); 19 | } 20 | 21 | protected PoseDataStream(String file) throws FileNotFoundException { 22 | this(new FileOutputStream(file)); 23 | } 24 | 25 | public void writeHeader(HumanSkeleton skeleton, PoseStreamer streamer) throws IOException { 26 | } 27 | 28 | abstract void writeFrame(HumanSkeleton skeleton) throws IOException; 29 | 30 | public void writeFooter(HumanSkeleton skeleton) throws IOException { 31 | } 32 | 33 | public boolean isClosed() { 34 | return closed; 35 | } 36 | 37 | @Override 38 | public void close() throws IOException { 39 | outputStream.close(); 40 | closed = true; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/posestreamer/PoseFrameStreamer.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.posestreamer 2 | 3 | import dev.slimevr.poseframeformat.PfrIO.readFromFile 4 | import dev.slimevr.poseframeformat.PoseFrames 5 | import dev.slimevr.poseframeformat.player.TrackerFramesPlayer 6 | import dev.slimevr.tracking.processor.HumanPoseManager 7 | import java.io.File 8 | 9 | class PoseFrameStreamer(poseFrames: PoseFrames) : PoseStreamer() { 10 | val trackerFramesPlayer: TrackerFramesPlayer = TrackerFramesPlayer(poseFrames) 11 | val humanPoseManager: HumanPoseManager = HumanPoseManager(trackerFramesPlayer.trackers.toList()) 12 | 13 | constructor(path: String) : this(File(path)) 14 | constructor(file: File) : this(readFromFile(file)) 15 | 16 | init { 17 | skeleton = humanPoseManager.skeleton 18 | } 19 | 20 | @Synchronized 21 | fun streamAllFrames() { 22 | for (i in 0 until trackerFramesPlayer.maxFrameCount) { 23 | trackerFramesPlayer.setCursors(i) 24 | humanPoseManager.update() 25 | captureFrame() 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/posestreamer/ServerPoseStreamer.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.posestreamer; 2 | 3 | import dev.slimevr.VRServer; 4 | import dev.slimevr.util.ann.VRServerThread; 5 | import dev.slimevr.tracking.processor.skeleton.HumanSkeleton; 6 | 7 | 8 | public class ServerPoseStreamer extends TickPoseStreamer { 9 | 10 | protected final VRServer server; 11 | 12 | public ServerPoseStreamer(VRServer server) { 13 | super(null); // Skeleton is registered later 14 | this.server = server; 15 | 16 | // Register callbacks/events 17 | server.addSkeletonUpdatedCallback(this::onSkeletonUpdated); 18 | server.addOnTick(this::onTick); 19 | } 20 | 21 | @VRServerThread 22 | public void onSkeletonUpdated(HumanSkeleton skeleton) { 23 | this.skeleton = skeleton; 24 | } 25 | 26 | @VRServerThread 27 | public void onTick() { 28 | super.doTick(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/posestreamer/TickPoseStreamer.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.posestreamer; 2 | 3 | import dev.slimevr.tracking.processor.skeleton.HumanSkeleton; 4 | 5 | import java.io.IOException; 6 | 7 | 8 | public class TickPoseStreamer extends PoseStreamer { 9 | 10 | protected long nextFrameTimeMs = -1L; 11 | 12 | public TickPoseStreamer(HumanSkeleton skeleton) { 13 | super(skeleton); 14 | } 15 | 16 | public void doTick() { 17 | PoseDataStream poseFileStream = this.poseFileStream; 18 | if (poseFileStream == null) { 19 | return; 20 | } 21 | 22 | HumanSkeleton skeleton = this.skeleton; 23 | if (skeleton == null) { 24 | return; 25 | } 26 | 27 | long curTime = System.currentTimeMillis(); 28 | if (curTime < nextFrameTimeMs) { 29 | return; 30 | } 31 | 32 | nextFrameTimeMs += frameRecordingInterval; 33 | 34 | // To prevent duplicate frames, make sure the frame time is always in 35 | // the future 36 | if (nextFrameTimeMs <= curTime) { 37 | nextFrameTimeMs = curTime + frameRecordingInterval; 38 | } 39 | 40 | captureFrame(); 41 | } 42 | 43 | @Override 44 | public synchronized void setOutput(PoseDataStream poseFileStream) throws IOException { 45 | super.setOutput(poseFileStream); 46 | nextFrameTimeMs = -1L; // Reset the frame timing 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/protocol/GenericConnection.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.protocol; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.UUID; 5 | 6 | 7 | public interface GenericConnection { 8 | 9 | UUID getConnectionId(); 10 | 11 | ConnectionContext getContext(); 12 | 13 | void send(ByteBuffer bytes); 14 | } 15 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/protocol/ProtocolAPIServer.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.protocol; 2 | 3 | import java.util.stream.Stream; 4 | 5 | 6 | public interface ProtocolAPIServer { 7 | 8 | Stream getAPIConnections(); 9 | } 10 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/protocol/ProtocolHandler.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.protocol 2 | 3 | import java.util.function.BiConsumer 4 | 5 | abstract class ProtocolHandler { 6 | @JvmField 7 | val handlers: Array?> 8 | 9 | init { 10 | this.handlers = arrayOfNulls(this.messagesCount()) 11 | } 12 | 13 | abstract fun onMessage(conn: GenericConnection, message: H) 14 | 15 | abstract fun messagesCount(): Int 16 | 17 | fun registerPacketListener(packetType: Byte, consumer: BiConsumer) { 18 | val packetInt = packetType.toInt() 19 | if (handlers[packetInt] != null) { 20 | handlers[packetInt] = handlers[packetInt]!!.andThen(consumer) 21 | } else { 22 | handlers[packetInt] = consumer 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/protocol/pubsub/HashedTopicId.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.protocol.pubsub; 2 | 3 | import solarxr_protocol.pub_sub.TopicIdT; 4 | 5 | import java.util.Objects; 6 | 7 | 8 | // This class is so the HashMap referencing the TopicId as key works 9 | // it needs a unique hashcode based on the topicId and also an equals function 10 | // because equals hashcode does not mean equals strings 11 | public class HashedTopicId { 12 | 13 | private final TopicIdT inner; 14 | private final int hashcode; 15 | 16 | public HashedTopicId(TopicIdT topicIdT) { 17 | this.inner = topicIdT; 18 | this.hashcode = (inner.getAppName() 19 | + "." 20 | + inner.getOrganization() 21 | + "." 22 | + inner.getTopic()).hashCode(); 23 | } 24 | 25 | public TopicIdT getInner() { 26 | return inner; 27 | } 28 | 29 | @Override 30 | public int hashCode() { 31 | return hashcode; 32 | } 33 | 34 | @Override 35 | public boolean equals(Object o) { 36 | if (this == o) 37 | return true; 38 | if (o == null || getClass() != o.getClass()) 39 | return false; 40 | HashedTopicId that = (HashedTopicId) o; 41 | return Objects.equals(inner.getOrganization(), that.getInner().getOrganization()) 42 | && Objects.equals(inner.getAppName(), that.getInner().getAppName()) 43 | && Objects.equals(inner.getTopic(), that.getInner().getTopic()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/protocol/rpc/RPCBuilder.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.protocol.rpc; 2 | 3 | import com.google.flatbuffers.FlatBufferBuilder; 4 | import dev.slimevr.tracking.processor.HumanPoseManager; 5 | import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets; 6 | import solarxr_protocol.rpc.SkeletonConfigResponse; 7 | import solarxr_protocol.rpc.SkeletonPart; 8 | 9 | 10 | public class RPCBuilder { 11 | 12 | public static int createSkeletonConfig( 13 | FlatBufferBuilder fbb, 14 | HumanPoseManager humanPoseManager 15 | ) { 16 | int[] partsOffsets = new int[SkeletonConfigOffsets.values().length]; 17 | 18 | for (int index = 0; index < SkeletonConfigOffsets.values().length; index++) { 19 | SkeletonConfigOffsets val = SkeletonConfigOffsets.values[index]; 20 | int part = SkeletonPart 21 | .createSkeletonPart(fbb, val.id, humanPoseManager.getOffset(val)); 22 | partsOffsets[index] = part; 23 | } 24 | 25 | int parts = SkeletonConfigResponse.createSkeletonPartsVector(fbb, partsOffsets); 26 | return SkeletonConfigResponse.createSkeletonConfigResponse(fbb, parts, 0); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/protocol/rpc/RPCBuilder.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.protocol.rpc 2 | 3 | import com.google.flatbuffers.FlatBufferBuilder 4 | import dev.slimevr.tracking.processor.HumanPoseManager 5 | import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets 6 | import solarxr_protocol.rpc.SkeletonConfigResponse 7 | import solarxr_protocol.rpc.SkeletonPart 8 | 9 | fun createSkeletonConfig( 10 | fbb: FlatBufferBuilder, 11 | humanPoseManager: HumanPoseManager, 12 | ): Int { 13 | val partsOffsets = IntArray(SkeletonConfigOffsets.entries.size) 14 | 15 | for (index in SkeletonConfigOffsets.entries.toTypedArray().indices) { 16 | val `val` = SkeletonConfigOffsets.values[index] 17 | val part = SkeletonPart 18 | .createSkeletonPart(fbb, `val`.id, humanPoseManager.getOffset(`val`)) 19 | partsOffsets[index] = part 20 | } 21 | 22 | val parts = SkeletonConfigResponse.createSkeletonPartsVector(fbb, partsOffsets) 23 | val userHeight = humanPoseManager.userHeightFromConfig 24 | return SkeletonConfigResponse.createSkeletonConfigResponse(fbb, parts, userHeight) 25 | } 26 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/protocol/rpc/settings/RPCSettingsBuilderKotlin.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.protocol.rpc.settings 2 | 3 | import com.google.flatbuffers.FlatBufferBuilder 4 | import dev.slimevr.config.StayAlignedConfig 5 | import solarxr_protocol.rpc.StayAlignedSettings 6 | 7 | object RPCSettingsBuilderKotlin { 8 | 9 | fun createStayAlignedSettings( 10 | fbb: FlatBufferBuilder, 11 | config: StayAlignedConfig, 12 | ): Int = 13 | StayAlignedSettings 14 | .createStayAlignedSettings( 15 | fbb, 16 | config.enabled, 17 | false, // deprecated 18 | config.hideYawCorrection, 19 | config.standingRelaxedPose.enabled, 20 | config.standingRelaxedPose.upperLegAngleInDeg, 21 | config.standingRelaxedPose.lowerLegAngleInDeg, 22 | config.standingRelaxedPose.footAngleInDeg, 23 | config.sittingRelaxedPose.enabled, 24 | config.sittingRelaxedPose.upperLegAngleInDeg, 25 | config.sittingRelaxedPose.lowerLegAngleInDeg, 26 | config.sittingRelaxedPose.footAngleInDeg, 27 | config.flatRelaxedPose.enabled, 28 | config.flatRelaxedPose.upperLegAngleInDeg, 29 | config.flatRelaxedPose.lowerLegAngleInDeg, 30 | config.flatRelaxedPose.footAngleInDeg, 31 | config.setupComplete, 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/protocol/rpc/setup/RPCUtil.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.protocol.rpc.setup 2 | 3 | import java.net.NetworkInterface 4 | 5 | object RPCUtil { 6 | @JvmStatic 7 | fun getLocalIp(): String? { 8 | for (netInt in NetworkInterface.getNetworkInterfaces()) { 9 | if (netInt.isUp && !netInt.isLoopback && !netInt.isVirtual) { 10 | for (netAddr in netInt.interfaceAddresses) { 11 | if (netAddr.address.isSiteLocalAddress && netAddr.broadcast != null) { 12 | return netAddr.address.hostAddress 13 | } 14 | } 15 | } 16 | } 17 | return null 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/reset/ResetHandler.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.reset; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.CopyOnWriteArrayList; 5 | 6 | 7 | public class ResetHandler { 8 | 9 | private final List listeners = new CopyOnWriteArrayList<>(); 10 | 11 | public ResetHandler() { 12 | 13 | } 14 | 15 | public void sendStarted(int resetType) { 16 | this.listeners.forEach((listener) -> listener.onStarted(resetType)); 17 | } 18 | 19 | public void sendFinished(int resetType) { 20 | this.listeners.forEach((listener) -> listener.onFinished(resetType)); 21 | } 22 | 23 | public void addListener(ResetListener listener) { 24 | this.listeners.add(listener); 25 | } 26 | 27 | public void removeListener(ResetListener l) { 28 | listeners.removeIf(listener -> l == listener); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/reset/ResetListener.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.reset; 2 | 3 | public interface ResetListener { 4 | 5 | void onStarted(int resetType); 6 | 7 | void onFinished(int resetType); 8 | } 9 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/serial/ProvisioningListener.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.serial; 2 | 3 | public interface ProvisioningListener { 4 | 5 | void onProvisioningStatusChange(ProvisioningStatus status, SerialPort port); 6 | } 7 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/serial/ProvisioningStatus.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.serial 2 | 3 | import solarxr_protocol.rpc.WifiProvisioningStatus 4 | 5 | enum class ProvisioningStatus(@JvmField val id: Int, val isError: Boolean, val timeout: Int = 10_000) { 6 | NONE(WifiProvisioningStatus.NONE, false, 3_000), 7 | SERIAL_INIT(WifiProvisioningStatus.SERIAL_INIT, false, 3_000), 8 | PROVISIONING(WifiProvisioningStatus.PROVISIONING, false), 9 | OBTAINING_MAC_ADDRESS(WifiProvisioningStatus.OBTAINING_MAC_ADDRESS, false), 10 | CONNECTING(WifiProvisioningStatus.CONNECTING, false, 30_000), 11 | CONNECTION_ERROR(WifiProvisioningStatus.CONNECTION_ERROR, true), 12 | LOOKING_FOR_SERVER(WifiProvisioningStatus.LOOKING_FOR_SERVER, false), 13 | COULD_NOT_FIND_SERVER(WifiProvisioningStatus.COULD_NOT_FIND_SERVER, true), 14 | NO_SERIAL_LOGS_ERROR(WifiProvisioningStatus.NO_SERIAL_LOGS_ERROR, true), 15 | NO_SERIAL_DEVICE_FOUND(WifiProvisioningStatus.NO_SERIAL_DEVICE_FOUND, true), 16 | DONE(WifiProvisioningStatus.DONE, false), 17 | } 18 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/serial/SerialListener.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.serial 2 | 3 | import java.util.* 4 | 5 | abstract class SerialPort { 6 | abstract val portLocation: String 7 | abstract val descriptivePortName: String 8 | abstract val vendorId: Int? 9 | abstract val productId: Int? 10 | 11 | override fun equals(other: Any?): Boolean { 12 | val other: SerialPort = other as? SerialPort ?: return super.equals(other) 13 | 14 | return this.portLocation == other.portLocation && 15 | this.descriptivePortName == other.descriptivePortName && 16 | this.vendorId == other.vendorId && 17 | this.productId == other.productId 18 | } 19 | 20 | override fun hashCode(): Int = Objects.hash(portLocation, descriptivePortName, vendorId, productId) 21 | } 22 | 23 | interface SerialListener { 24 | fun onSerialConnected(port: SerialPort) 25 | fun onSerialDisconnected() 26 | 27 | // var server indicates if the log is injected by the server (not an actual serial log) 28 | fun onSerialLog(str: String, server: Boolean) 29 | fun onNewSerialDevice(port: SerialPort) 30 | 31 | // This is called when the serial diver does not see the device anymore 32 | fun onSerialDeviceDeleted(port: SerialPort) 33 | } 34 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/setup/HandshakeHandler.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.setup 2 | 3 | import java.util.concurrent.CopyOnWriteArrayList 4 | 5 | class HandshakeHandler { 6 | private val listeners: MutableList = CopyOnWriteArrayList() 7 | 8 | fun addListener(listener: HandshakeListener) { 9 | listeners.add(listener) 10 | } 11 | 12 | fun removeListener(listener: HandshakeListener) { 13 | listeners.remove(listener) 14 | } 15 | 16 | fun sendUnknownHandshake(macAddress: String) { 17 | listeners.forEach { it.onUnknownHandshake(macAddress) } 18 | } 19 | } 20 | 21 | interface HandshakeListener { 22 | fun onUnknownHandshake(macAddress: String) 23 | } 24 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/setup/TapSetupHandler.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.setup 2 | 3 | import dev.slimevr.tracking.trackers.Tracker 4 | import java.util.concurrent.CopyOnWriteArrayList 5 | 6 | class TapSetupHandler { 7 | private val listeners: MutableList = CopyOnWriteArrayList() 8 | 9 | fun addListener(listener: TapSetupListener) { 10 | listeners.add(listener) 11 | } 12 | 13 | fun removeListener(listener: TapSetupListener) { 14 | listeners.remove(listener) 15 | } 16 | 17 | fun sendTap(tracker: Tracker) { 18 | listeners.forEach { it.onStarted(tracker) } 19 | } 20 | } 21 | 22 | interface TapSetupListener { 23 | fun onStarted(tracker: Tracker) 24 | } 25 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/tracking/processor/stayaligned/trackers/Side.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.tracking.processor.stayaligned.trackers 2 | 3 | enum class Side { 4 | LEFT, 5 | RIGHT, 6 | } 7 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/tracking/processor/stayaligned/trackers/StayAlignedTrackerState.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.tracking.processor.stayaligned.trackers 2 | 3 | import dev.slimevr.math.Angle 4 | import dev.slimevr.tracking.processor.stayaligned.StayAlignedDefaults 5 | import dev.slimevr.tracking.trackers.Tracker 6 | import io.github.axisangles.ktmath.Quaternion 7 | 8 | class StayAlignedTrackerState( 9 | val tracker: Tracker, 10 | ) { 11 | // Whether to hide the yaw correction 12 | var hideCorrection = false 13 | 14 | // Detects whether the tracker is at rest 15 | val restDetector = StayAlignedDefaults.makeRestDetector() 16 | 17 | // Rotation of the tracker when it was locked 18 | var lockedRotation: Quaternion? = null 19 | 20 | // Yaw correction to apply to tracker rotation 21 | var yawCorrection = Angle.ZERO 22 | 23 | // Alignment error that yaw correction attempts to minimize 24 | var yawErrors = YawErrors() 25 | 26 | fun update() { 27 | restDetector.update(tracker.getRawRotation()) 28 | if (restDetector.state == RestDetector.State.AT_REST) { 29 | if (lockedRotation == null) { 30 | lockedRotation = tracker.getAdjustedRotationForceStayAligned() 31 | } 32 | } else { 33 | lockedRotation = null 34 | } 35 | } 36 | 37 | fun reset() { 38 | restDetector.reset() 39 | lockedRotation = null 40 | yawCorrection = Angle.ZERO 41 | yawErrors = YawErrors() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/tracking/processor/stayaligned/trackers/YawErrors.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.tracking.processor.stayaligned.trackers 2 | 3 | import dev.slimevr.math.AngleErrors 4 | 5 | /** 6 | * Aggregates the yaw errors from multiple forces. 7 | */ 8 | class YawErrors { 9 | var lockedError = AngleErrors() 10 | var centerError = AngleErrors() 11 | var neighborError = AngleErrors() 12 | } 13 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/tracking/trackers/DeviceManager.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.tracking.trackers 2 | 3 | import dev.slimevr.VRServer 4 | import io.eiren.util.collections.FastList 5 | 6 | class DeviceManager(private val server: VRServer) { 7 | val devices = FastList() 8 | fun createDevice(name: String?, version: String?, manufacturer: String?): Device { 9 | val device = Device() 10 | device.name = name 11 | device.firmwareVersion = version 12 | device.manufacturer = manufacturer 13 | return device 14 | } 15 | 16 | fun addDevice(device: Device) { 17 | server.queueTask { devices.add(device) } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerStatus.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.tracking.trackers 2 | 3 | enum class TrackerStatus(val id: Int, val sendData: Boolean, val reset: Boolean) { 4 | 5 | DISCONNECTED(0, false, true), 6 | OK(1, true, false), 7 | BUSY(2, true, false), 8 | ERROR(3, false, true), 9 | OCCLUDED(4, false, false), 10 | TIMED_OUT(5, false, false), 11 | ; 12 | 13 | companion object { 14 | 15 | private val byId = entries.associateBy { it.id } 16 | 17 | @JvmStatic 18 | fun getById(id: Int): TrackerStatus? = byId[id] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/tracking/trackers/TrackerStatusListener.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.tracking.trackers 2 | 3 | interface TrackerStatusListener { 4 | 5 | fun onTrackerStatusChanged(tracker: Tracker, oldStatus: TrackerStatus, newStatus: TrackerStatus) 6 | } 7 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/tracking/trackers/udp/SensorTap.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.tracking.trackers.udp 2 | 3 | data class SensorTap(val tapBits: Int) { 4 | val doubleTap = tapBits and 0x40 > 0 5 | 6 | enum class TapAxis { 7 | X, 8 | Y, 9 | Z, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/trackingpause/TrackingPauseHandler.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.trackingpause 2 | 3 | import java.util.concurrent.CopyOnWriteArrayList 4 | 5 | class TrackingPauseHandler { 6 | private val listeners: MutableList = CopyOnWriteArrayList() 7 | 8 | fun sendTrackingPauseState(trackingPaused: Boolean) { 9 | listeners.forEach { it.onTrackingPause(trackingPaused) } 10 | } 11 | 12 | fun addListener(listener: TrackingPauseListener) { 13 | listeners.add(listener) 14 | } 15 | 16 | fun removeListener(listener: TrackingPauseListener) { 17 | listeners.removeIf { it == listener } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/trackingpause/TrackingPauseListener.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.trackingpause 2 | 3 | interface TrackingPauseListener { 4 | fun onTrackingPause(trackingPaused: Boolean) 5 | } 6 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/util/ann/VRServerThread.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.util.ann; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | 7 | @Retention(value = RetentionPolicy.SOURCE) 8 | public @interface VRServerThread { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /server/core/src/main/java/dev/slimevr/websocketapi/WebsocketConnection.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.websocketapi; 2 | 3 | import dev.slimevr.protocol.ConnectionContext; 4 | import dev.slimevr.protocol.GenericConnection; 5 | import org.java_websocket.WebSocket; 6 | 7 | import java.nio.ByteBuffer; 8 | import java.util.UUID; 9 | 10 | 11 | public class WebsocketConnection implements GenericConnection { 12 | 13 | public final ConnectionContext context; 14 | public final WebSocket conn; 15 | public UUID id; 16 | 17 | public WebsocketConnection(WebSocket conn) { 18 | this.context = new ConnectionContext(); 19 | this.conn = conn; 20 | this.id = UUID.randomUUID(); 21 | } 22 | 23 | @Override 24 | public ConnectionContext getContext() { 25 | return this.context; 26 | } 27 | 28 | @Override 29 | public void send(ByteBuffer bytes) { 30 | if (this.conn.isOpen()) 31 | this.conn.send(bytes.slice()); 32 | } 33 | 34 | @Override 35 | public UUID getConnectionId() { 36 | return id; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util; 2 | 3 | import java.text.DecimalFormatSymbols; 4 | import java.util.Locale; 5 | 6 | 7 | public class StringUtils { 8 | 9 | private static char DECIMAL_SEP; 10 | 11 | public static char getDecimalSeparator() { 12 | if (DECIMAL_SEP == '\u0000') { 13 | final Locale l = Locale.getDefault(Locale.Category.FORMAT); 14 | // Formatter.java always use "." in the Locale.US 15 | DECIMAL_SEP = (l == null || l.equals(Locale.US) 16 | ? '.' 17 | : DecimalFormatSymbols.getInstance(l).getDecimalSeparator()); 18 | } 19 | return DECIMAL_SEP; 20 | } 21 | 22 | public static String prettyNumber(float f) { 23 | return prettyNumber(f, 4); 24 | } 25 | 26 | public static String prettyNumber(float f, int numDigits) { 27 | String str = String.format("%." + numDigits + "f", f); 28 | if (numDigits != 0) 29 | str = org.apache.commons.lang3.StringUtils.stripEnd(str, "0"); 30 | char lastChar = str.charAt(str.length() - 1); 31 | if (lastChar == getDecimalSeparator()) 32 | str = str.substring(0, str.length() - 1); 33 | return str; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/ann/AWTThread.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.ann; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | 7 | @Retention(value = RetentionPolicy.SOURCE) 8 | public @interface AWTThread { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/ann/DebugSwitch.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.ann; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Target; 5 | 6 | 7 | @Target({ ElementType.FIELD, ElementType.METHOD }) 8 | public @interface DebugSwitch { 9 | } 10 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/ann/NativeUnsafe.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.ann; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | /** 10 | * Marks methods and classes that use unsafe or direct access to memory. Proceed 11 | * with caution. 12 | * 13 | * @author Rena 14 | */ 15 | @Retention(value = RetentionPolicy.RUNTIME) 16 | @Target({ ElementType.METHOD, ElementType.TYPE }) 17 | public @interface NativeUnsafe { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/ann/Synchronize.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.ann; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | 7 | /** 8 | *

9 | * Означает необходимость обязательной синхронизации этого меcта во внешних 10 | * методах. В аргументах передаётся название поля для синхронизации. 11 | *

12 | *

13 | * Методы, помеченные данной аннотацией могут вызывать только Thread-Safe 14 | * методы, либо методы, помеченные такой же аннотацией с тем же полем 15 | * синхронизации. 16 | *

17 | *

18 | * Поля, помеченные данной аннотацией должны быть синхронизированны на указанное 19 | * поле при чтении или записи. 20 | *

21 | * 22 | * @see {@link ThreadSafe}, {@link ThreadSecure}, {@link ThreadSafeSingle} 23 | * @author Rena 24 | */ 25 | @Retention(value = RetentionPolicy.SOURCE) 26 | public @interface Synchronize { 27 | 28 | String[] value(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/ann/ThreadSafe.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.ann; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | 7 | /** 8 | *

9 | * Методы, помеченные этой аннотацией должны быть Thread-Safe. 10 | *

11 | *

12 | * Важно: данные методы гарантированно должны обеспечивать потоковую 13 | * безопасность, но не обязаны обеспечивать концессивность (полноту данных или 14 | * точность синхронизации). 15 | *

16 | *

17 | * Для полностью потоко-безопасных методов можно использовать аннотацию 18 | * {@link ThreadSecure}. 19 | *

20 | * 21 | * @see {@link ThreadSecure}, {@link Synchronize}, {@link ThreadSafeSingle} 22 | * @author Rena 23 | */ 24 | @Retention(value = RetentionPolicy.SOURCE) 25 | public @interface ThreadSafe { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/ann/ThreadSafeSingle.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.ann; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | 7 | /** 8 | * Соблюдает те же требования что и {@link ThreadSafe} но при условии, что сам 9 | * метод вызывается только из одного потока одновременно. 10 | * 11 | * @see {@link ThreadSafe}, {@link ThreadSecure}, {@link Synchronize} 12 | * @author Rena 13 | */ 14 | @Retention(value = RetentionPolicy.SOURCE) 15 | public @interface ThreadSafeSingle { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/ann/ThreadSecure.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.ann; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | 7 | /** 8 | *

9 | * Методы, помеченные этой аннотацией должны быть полностью Thread-Safe. 10 | *

11 | *

12 | * Важно: данные методы гарантированно должны обеспечивать потоковую 13 | * безопасность и консистентность (полноту данных и точность синхронизации). 14 | *

15 | * 16 | * @see {@link ThreadSafe}, {@link Synchronize}, {@link ThreadSafeSingle} 17 | * @author Rena 18 | */ 19 | @Retention(value = RetentionPolicy.SOURCE) 20 | public @interface ThreadSecure { 21 | 22 | } 23 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/collections/RemoveAtSwapFastList.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.collections; 2 | 3 | import java.util.Collection; 4 | 5 | 6 | /** 7 | * FastList that performs Remove-At-Swap on stanard remove() operations. 8 | * 9 | *

10 | * Remove operations breaks ordering of this list 11 | * 12 | * @author Rena 13 | * 14 | * @param 15 | */ 16 | public class RemoveAtSwapFastList extends FastList { 17 | 18 | public RemoveAtSwapFastList(int capacity) { 19 | super(capacity); 20 | } 21 | 22 | public RemoveAtSwapFastList() { 23 | } 24 | 25 | public RemoveAtSwapFastList(Collection source) { 26 | super(source); 27 | } 28 | 29 | public RemoveAtSwapFastList(E[] source) { 30 | super(source); 31 | } 32 | 33 | public RemoveAtSwapFastList(E source) { 34 | super(source); 35 | } 36 | 37 | @Override 38 | protected void removeInternal(int i) { 39 | super.removeAtSwapInternal(i); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/collections/RemoveAtSwapList.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.collections; 2 | 3 | import java.util.List; 4 | 5 | 6 | public interface RemoveAtSwapList extends List { 7 | 8 | E removeAtSwap(int i); 9 | 10 | boolean removeAtSwap(Object object); 11 | } 12 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/collections/ResettableIterator.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.collections; 2 | 3 | import java.util.Iterator; 4 | 5 | 6 | /** 7 | * {@link Iterator} that can be reset and iterated from the start by using 8 | * {@link #reset()} 9 | * 10 | * @author Rena 11 | * 12 | * @param 13 | */ 14 | public interface ResettableIterator extends Iterator { 15 | 16 | void reset(); 17 | } 18 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/collections/SkipIterator.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.collections; 2 | 3 | import java.util.Iterator; 4 | 5 | 6 | /** 7 | * {@link Iterator} that can return null on {@link #next()} or can lie on 8 | * {@link #hasNext()}. It is not thread-secure! 9 | * 10 | * @param the type of elements returned by this iterator 11 | */ 12 | public interface SkipIterator extends Iterator { 13 | 14 | @Override 15 | E next(); 16 | } 17 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/logging/IGLog.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.logging; 2 | 3 | import java.util.logging.Level; 4 | 5 | 6 | public interface IGLog { 7 | 8 | void info(String message); 9 | 10 | void severe(String message); 11 | 12 | void warning(String message); 13 | 14 | void debug(String message); 15 | 16 | default void info(String message, Throwable t) { 17 | log(Level.INFO, message, t); 18 | } 19 | 20 | default void severe(String message, Throwable t) { 21 | log(Level.SEVERE, message, t); 22 | } 23 | 24 | default void warning(String message, Throwable t) { 25 | log(Level.WARNING, message, t); 26 | } 27 | 28 | default void debug(String message, Throwable t) { 29 | log(Level.INFO, "[DBG] " + message, t); 30 | } 31 | 32 | void log(Level level, String message); 33 | 34 | void log(Level level, String message, Throwable t); 35 | 36 | void setRecorder(LoggerRecorder recorder); 37 | 38 | LoggerRecorder removeRecorder(); 39 | 40 | class GLevel extends Level { 41 | 42 | private static final long serialVersionUID = -539856764608026895L; 43 | 44 | private GLevel(String s, int i) { 45 | super(s, i); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/logging/LoggerRecorder.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.logging; 2 | 3 | import java.util.List; 4 | 5 | import io.eiren.util.collections.FastList; 6 | import io.eiren.util.logging.DefaultGLog.LogEntry; 7 | 8 | 9 | public class LoggerRecorder { 10 | 11 | private final List recorded = new FastList<>(); 12 | 13 | public LoggerRecorder() { 14 | } 15 | 16 | public synchronized void addEntry(LogEntry e) { 17 | recorded.add(e); 18 | } 19 | 20 | public List getEntries() { 21 | return recorded; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/eiren/util/logging/PreciseConsoleLogFormatter.java: -------------------------------------------------------------------------------- 1 | package io.eiren.util.logging; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.logging.LogRecord; 5 | 6 | 7 | /** 8 | * Format message timestamp as time passed from the start with milliseconds. 9 | */ 10 | public class PreciseConsoleLogFormatter extends ShortConsoleLogFormatter { 11 | 12 | private final long startMills; 13 | 14 | public PreciseConsoleLogFormatter() { 15 | startMills = System.currentTimeMillis(); 16 | } 17 | 18 | @Override 19 | protected SimpleDateFormat createDateFormat() { 20 | return new SimpleDateFormat("mm:ss.SSS"); 21 | } 22 | 23 | @Override 24 | protected void buildMessage(StringBuilder builder, LogRecord record) { 25 | builder.append(date.format(record.getMillis() - startMills)); 26 | builder.append(" ["); 27 | builder.append(record.getLevel().getLocalizedName().toUpperCase()); 28 | builder.append("] "); 29 | builder.append(record.getMessage()); 30 | builder.append('\n'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/github/axisangles/ktmath/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Donald F Reynolds 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /server/core/src/main/java/io/github/axisangles/ktmath/Transform.kt: -------------------------------------------------------------------------------- 1 | package io.github.axisangles.ktmath 2 | 3 | data class Transform( 4 | var rotation: Quaternion = Quaternion.IDENTITY, 5 | var translation: Vector3 = Vector3.NULL, 6 | var scale: Vector3 = Vector3(1f, 1f, 1f), 7 | ) { 8 | companion object { 9 | val IDENTITY = Transform() 10 | } 11 | 12 | fun set(transform: Transform): Transform { 13 | // Quaternions and vectors are immutable, so we're copying links 14 | this.rotation = transform.rotation 15 | this.translation = transform.translation 16 | this.scale = transform.scale 17 | return this 18 | } 19 | 20 | /** 21 | * Changes the values of this matrix according to its parent. Very similar 22 | * to the concept of Node/Spatial transforms. 23 | * 24 | * @param parent The parent matrix. 25 | * @return This matrix, after combining. 26 | */ 27 | fun combineWithParent(parent: Transform): Transform { 28 | this.scale = this.scale hadamard parent.scale 29 | 30 | this.rotation = parent.rotation * this.rotation 31 | 32 | val scaledTranslation = this.translation hadamard parent.scale 33 | this.translation = parent.rotation.sandwich(scaledTranslation) + parent.translation 34 | 35 | return this 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /server/desktop/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /server/desktop/config/README.md: -------------------------------------------------------------------------------- 1 | This file is here so server does portable configuration mode! 2 | What that means, is that it will save the config next to it, instead of in the user's config folder 3 | -------------------------------------------------------------------------------- /server/desktop/src/main/java/dev/slimevr/desktop/platform/windows/PipeState.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.desktop.platform.windows; 2 | 3 | public enum PipeState { 4 | CREATED, 5 | OPEN, 6 | ERROR 7 | } 8 | -------------------------------------------------------------------------------- /server/desktop/src/main/java/dev/slimevr/desktop/platform/windows/WindowsPipe.java: -------------------------------------------------------------------------------- 1 | package dev.slimevr.desktop.platform.windows; 2 | 3 | import com.sun.jna.platform.win32.Kernel32; 4 | import com.sun.jna.platform.win32.WinNT.HANDLE; 5 | 6 | 7 | public class WindowsPipe { 8 | 9 | public final String name; 10 | public final HANDLE pipeHandle; 11 | public PipeState state = PipeState.CREATED; 12 | 13 | public WindowsPipe(HANDLE pipeHandle, String name) { 14 | this.pipeHandle = pipeHandle; 15 | this.name = name; 16 | } 17 | 18 | public static void safeDisconnect(WindowsPipe pipe) { 19 | try { 20 | if (pipe != null && pipe.pipeHandle != null) 21 | Kernel32.INSTANCE.DisconnectNamedPipe(pipe.pipeHandle); 22 | } catch (Exception ignored) {} 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/desktop/src/main/java/dev/slimevr/desktop/tracking/trackers/hid/HIDDevice.kt: -------------------------------------------------------------------------------- 1 | package dev.slimevr.desktop.tracking.trackers.hid 2 | 3 | import dev.slimevr.tracking.trackers.Device 4 | import dev.slimevr.tracking.trackers.Tracker 5 | import dev.slimevr.tracking.trackers.udp.BoardType 6 | import dev.slimevr.tracking.trackers.udp.MCUType 7 | 8 | class HIDDevice(val hidId: Int) : Device() { 9 | override var hardwareIdentifier: String = "Unknown" 10 | override var boardType: BoardType = BoardType.UNKNOWN 11 | override var mcuType: MCUType = MCUType.UNKNOWN 12 | fun getTracker(id: Int): Tracker? = trackers[id] 13 | } 14 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | # Just uses the flake. For the nix-env addon (which is kind of dead) users, I use the direnv addon. 2 | (builtins.getFlake ("git+file://" + toString ./.)).devShells.${builtins.currentSystem}.default 3 | --------------------------------------------------------------------------------