mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Merge branch 'main' into fingertracking
This commit is contained in:
32
.github/CODEOWNERS
vendored
32
.github/CODEOWNERS
vendored
@@ -2,23 +2,23 @@
|
||||
* @Eirenliel
|
||||
|
||||
# Make everyone be able to approve SolarXR submodule changes
|
||||
/solarxr-protocol @ButterscotchV @Erimelowo @ImUrX @loucass003
|
||||
/solarxr-protocol @ButterscotchV @Erimelowo @loucass003
|
||||
|
||||
# Make Loucas and Uriel the owners of all GUI stuff
|
||||
/gui/ @ImUrX @loucass003
|
||||
/pnpm-lock.yaml @ImUrX @loucass003
|
||||
/pnpm-workspace.yaml @ImUrX @loucass003
|
||||
# Make Loucass the owner of all GUI stuff
|
||||
/gui/ @loucass003
|
||||
/pnpm-lock.yaml @loucass003
|
||||
/pnpm-workspace.yaml @loucass003
|
||||
|
||||
# Uriel and Erimel responsible for i18n
|
||||
/gui/public/i18n/ @ImUrX @Erimelowo
|
||||
/gui/src/i18n/ @ImUrX @Erimelowo
|
||||
/l10n.toml @ImUrX @Erimelowo
|
||||
# loucass003 and Erimel responsible for i18n
|
||||
/gui/public/i18n/ @loucass003 @Erimelowo
|
||||
/gui/src/i18n/ @loucass003 @Erimelowo
|
||||
/l10n.toml @loucass003 @Erimelowo
|
||||
|
||||
/gui/src/components/settings/ @Erimelowo @ImUrX @loucass003
|
||||
/gui/src/components/settings/ @Erimelowo @loucass003
|
||||
|
||||
# Rust part of the GUI
|
||||
/gui/src-tauri/ @ImUrX
|
||||
/Cargo.lock @ImUrX
|
||||
/gui/src-tauri/ @loucass003
|
||||
/Cargo.lock @loucass003
|
||||
|
||||
# Some server code~
|
||||
/server/ @ButterscotchV @Eirenliel @Erimelowo
|
||||
@@ -32,7 +32,7 @@
|
||||
/server/src/main/java/dev/slimevr/filtering/ @Erimelowo
|
||||
|
||||
# Linux files
|
||||
*.nix @ImUrX
|
||||
/flake.lock @ImUrX
|
||||
/dev.slimevr.SlimeVR.metainfo.xml @ImUrX
|
||||
/.envrc @ImUrX
|
||||
*.nix @loucass003
|
||||
/flake.lock @loucass003
|
||||
/dev.slimevr.SlimeVR.metainfo.xml @loucass003
|
||||
/.envrc @loucass003
|
||||
|
||||
10
.github/workflows/build-gui.yml
vendored
10
.github/workflows/build-gui.yml
vendored
@@ -21,13 +21,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
@@ -59,13 +59,13 @@ jobs:
|
||||
BUILD_ARCH: ${{ endsWith(matrix.os, 'arm') && 'aarch64' || 'amd64' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- if: startsWith(matrix.os, 'ubuntu')
|
||||
name: Set up Linux dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
|
||||
with:
|
||||
packages: libgtk-3-dev webkit2gtk-4.1 libappindicator3-dev librsvg2-dev patchelf
|
||||
# Increment to invalidate the cache
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '22.x'
|
||||
|
||||
|
||||
34
.github/workflows/gradle.yaml
vendored
34
.github/workflows/gradle.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
run: git fetch --tags origin --recurse-submodules=no --force
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
run: git fetch --tags origin --recurse-submodules=no --force
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
bundle-android:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
run: git fetch --tags origin --recurse-submodules=no --force
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
@@ -154,17 +154,17 @@ jobs:
|
||||
env:
|
||||
BUILD_ARCH: ${{ endsWith(matrix.os, 'arm') && 'aarch64' || 'amd64' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: 'SlimeVR-Server'
|
||||
path: server/desktop/build/libs/
|
||||
|
||||
- name: Set up Linux dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
|
||||
with:
|
||||
packages: |
|
||||
build-essential curl wget file libssl-dev libgtk-3-dev libappindicator3-dev librsvg2-dev
|
||||
@@ -190,7 +190,7 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
@@ -252,11 +252,11 @@ jobs:
|
||||
needs: [build, test]
|
||||
if: contains(fromJSON('["workflow_dispatch", "create"]'), github.event_name)
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: 'SlimeVR-Server'
|
||||
path: server/desktop/build/libs/
|
||||
@@ -266,7 +266,7 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
@@ -332,11 +332,11 @@ jobs:
|
||||
env:
|
||||
BUILD_ARCH: ${{ endsWith(matrix.os, 'arm') && 'win-aarch64' || 'win64' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: 'SlimeVR-Server'
|
||||
path: server/desktop/build/libs/
|
||||
@@ -351,7 +351,7 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
2
.github/workflows/label.yml
vendored
2
.github/workflows/label.yml
vendored
@@ -17,6 +17,6 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
- uses: actions/labeler@v6
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
2
.github/workflows/pontoon-pr.yml
vendored
2
.github/workflows/pontoon-pr.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: repo-sync/pull-request@v2
|
||||
|
||||
2
.github/workflows/rebase.yml
vendored
2
.github/workflows/rebase.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
ref: pontoon
|
||||
submodules: recursive
|
||||
|
||||
@@ -1125,6 +1125,7 @@ onboarding-automatic_mounting-preparation-v2-step-2 = 3. Hold the position until
|
||||
onboarding-automatic_mounting-put_trackers_on-title = Put on your trackers
|
||||
onboarding-automatic_mounting-put_trackers_on-description = To calibrate mounting orientations, we're gonna use the trackers you just assigned. Put on all your trackers, you can see which are which in the figure to the right.
|
||||
onboarding-automatic_mounting-put_trackers_on-next = I have all my trackers on
|
||||
onboarding-automatic_mounting-return-home = Done
|
||||
|
||||
## Tracker manual proportions setupa
|
||||
onboarding-manual_proportions-back = Go Back to Reset tutorial
|
||||
|
||||
@@ -65,6 +65,10 @@ work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
</provides>
|
||||
|
||||
<releases>
|
||||
<release version="0.16.2" date="2025-08-01"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.2</url></release>
|
||||
<release version="0.16.1" date="2025-07-27"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.1</url></release>
|
||||
<release version="0.16.1~rc.2" type="development" date="2025-07-17"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.1-rc.2</url></release>
|
||||
<release version="0.16.1~rc.1" type="development" date="2025-07-04"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.1-rc.1</url></release>
|
||||
<release version="0.16.0" date="2025-07-01"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.0</url></release>
|
||||
<release version="0.16.0~rc.2" type="development" date="2025-06-20"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.0-rc.2</url></release>
|
||||
<release version="0.16.0~rc.1" type="development" date="2025-05-27"><url>https://github.com/SlimeVR/SlimeVR-Server/releases/tag/v0.16.0-rc.1</url></release>
|
||||
|
||||
@@ -206,6 +206,11 @@ export default function App() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// don't show update stuff when on android
|
||||
if (window.__ANDROID__?.isThere()) {
|
||||
setUpdateFound('');
|
||||
return;
|
||||
}
|
||||
async function fetchReleases() {
|
||||
const releases = await fetch(
|
||||
`https://api.github.com/repos/${GH_REPO}/releases`
|
||||
|
||||
@@ -66,9 +66,7 @@ export function WidgetsComponent() {
|
||||
bodyPartsToReset="fingers"
|
||||
></ResetButton>
|
||||
<ClearMountingButton></ClearMountingButton>
|
||||
{(typeof __ANDROID__ === 'undefined' || !__ANDROID__?.isThere()) && (
|
||||
<BVHButton></BVHButton>
|
||||
)}
|
||||
{!window.__ANDROID__?.isThere() && <BVHButton></BVHButton>}
|
||||
<TrackingPauseButton></TrackingPauseButton>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
|
||||
@@ -28,7 +28,7 @@ export function ArrowLink({
|
||||
};
|
||||
return classNames(
|
||||
variantsMap[variant],
|
||||
'flex gap-2 hover:fill-background-10 hover:text-background-10 fill-background-30 text-background-30'
|
||||
'flex gap-2 hover:fill-background-10 fill-background-30 text-background-10'
|
||||
);
|
||||
}, [variant]);
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ export function CheckBox({
|
||||
className={classNames(
|
||||
{
|
||||
'rounded-lg': outlined,
|
||||
'text-background-30': !outlined || disabled,
|
||||
'text-background-10': !outlined || disabled,
|
||||
'bg-background-60': outlined && color === 'primary',
|
||||
'bg-background-70': outlined && color === 'secondary',
|
||||
'bg-background-50': outlined && color === 'tertiary',
|
||||
|
||||
@@ -12,6 +12,8 @@ import { FileIcon } from './icon/FileIcon';
|
||||
import { UploadFileIcon } from './icon/UploadFileIcon';
|
||||
import { Typography } from './Typography';
|
||||
import { CloseIcon } from './icon/CloseIcon';
|
||||
import { FolderIcon } from './icon/FolderIcon';
|
||||
import { UploadFolderIcon } from './icon/UploadFolderIcon';
|
||||
|
||||
interface InputProps {
|
||||
variant?: 'primary' | 'secondary';
|
||||
@@ -22,9 +24,11 @@ interface InputProps {
|
||||
export const FileInputContentBlank = ({
|
||||
isDragging,
|
||||
label,
|
||||
directory = false,
|
||||
}: {
|
||||
isDragging: boolean;
|
||||
label: string;
|
||||
directory?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
@@ -36,7 +40,11 @@ export const FileInputContentBlank = ({
|
||||
)}
|
||||
>
|
||||
<span className="flex items-center space-x-2 pointer-events-none">
|
||||
<UploadFileIcon isDragging={isDragging} />
|
||||
{directory ? (
|
||||
<UploadFolderIcon isDragging={isDragging} />
|
||||
) : (
|
||||
<UploadFileIcon isDragging={isDragging} />
|
||||
)}
|
||||
<div>
|
||||
<Localized
|
||||
id={label}
|
||||
@@ -57,10 +65,12 @@ export const FileInputContentBlank = ({
|
||||
|
||||
export const FileInputContentFile = ({
|
||||
importedFileName,
|
||||
directory = false,
|
||||
onClearPicker,
|
||||
}: {
|
||||
importedFileName: string;
|
||||
onClearPicker: () => any;
|
||||
directory?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
@@ -72,7 +82,7 @@ export const FileInputContentFile = ({
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center space-x-2 px-4">
|
||||
<FileIcon />
|
||||
{directory ? <FolderIcon /> : <FileIcon />}
|
||||
<span>{importedFileName}</span>
|
||||
</div>
|
||||
<span className="flex-grow"></span>
|
||||
|
||||
@@ -47,19 +47,19 @@ export const InputInside = forwardRef<
|
||||
const classes = useMemo(() => {
|
||||
const variantsMap = {
|
||||
primary: classNames({
|
||||
'placeholder:text-background-30 bg-background-60 border-background-60':
|
||||
'placeholder:text-background-10 placeholder:italic bg-background-60 border-background-60':
|
||||
!disabled,
|
||||
'text-background-30 placeholder:text-background-30 border-background-70 bg-background-70':
|
||||
disabled,
|
||||
}),
|
||||
secondary: classNames({
|
||||
'placeholder:text-background-30 bg-background-50 border-background-50':
|
||||
'placeholder:text-background-10 placeholder:italic bg-background-50 border-background-50':
|
||||
!disabled,
|
||||
'text-background-40 placeholder:text-background-40 border-background-70 bg-background-70':
|
||||
disabled,
|
||||
}),
|
||||
tertiary: classNames({
|
||||
'placeholder:text-background-30 bg-background-40 border-background-40':
|
||||
'placeholder:text-background-10 placeholder:italic bg-background-40 border-background-40':
|
||||
!disabled,
|
||||
'text-background-30 placeholder:text-background-30 border-background-70 bg-background-70':
|
||||
disabled,
|
||||
|
||||
@@ -51,9 +51,7 @@ export function Radio({
|
||||
<div className="flex flex-col gap-2 pointer-events-none">
|
||||
{children ? children : <Typography bold>{label}</Typography>}
|
||||
{description && (
|
||||
<Typography variant="standard" color="secondary">
|
||||
{description}
|
||||
</Typography>
|
||||
<Typography variant="standard">{description}</Typography>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
@@ -24,10 +24,11 @@ export function InnerTauriFileInput({
|
||||
<div ref={ref} onClick={async () => onChange(await open({ directory }))}>
|
||||
{value !== null
|
||||
? FileInputContentFile({
|
||||
directory,
|
||||
importedFileName: value,
|
||||
onClearPicker: () => onChange(null),
|
||||
})
|
||||
: FileInputContentBlank({ isDragging: false, label })}
|
||||
: FileInputContentBlank({ isDragging: false, label, directory })}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
12
gui/src/components/commons/icon/FolderIcon.tsx
Normal file
12
gui/src/components/commons/icon/FolderIcon.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export function FolderIcon({ width = 24 }: { width?: number }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
width={width}
|
||||
>
|
||||
<path d="M19.5 21a3 3 0 0 0 3-3v-4.5a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3V18a3 3 0 0 0 3 3h15ZM1.5 10.146V6a3 3 0 0 1 3-3h5.379a2.25 2.25 0 0 1 1.59.659l2.122 2.121c.14.141.331.22.53.22H19.5a3 3 0 0 1 3 3v1.146A4.483 4.483 0 0 0 19.5 9h-15a4.483 4.483 0 0 0-3 1.146Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
25
gui/src/components/commons/icon/UploadFolderIcon.tsx
Normal file
25
gui/src/components/commons/icon/UploadFolderIcon.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import classNames from 'classnames';
|
||||
|
||||
export function UploadFolderIcon({
|
||||
width = 24,
|
||||
isDragging = false,
|
||||
}: {
|
||||
width?: number;
|
||||
isDragging?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 5.5499997 4.7600007"
|
||||
width={width}
|
||||
fill="currentColor"
|
||||
className={classNames('transition-transform', isDragging && 'scale-150')}
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="m 4.76,4.76 c 0.44,0 0.79,-0.36 0.79,-0.79 v -2.39 c 0,-0.44 -0.36,-0.79 -0.79,-0.79 H 3.34 c -0.05,0 -0.1,-0.02 -0.14,-0.06 L 2.64,0.17 C 2.53,0.06 2.38,0 2.22,0 H 0.79 C 0.35,0 0,0.36 0,0.79 V 3.97 C 0,4.41 0.36,4.76 0.79,4.76 Z M 2.58,3.71 c 0,0.26 0.4,0.26 0.4,0 V 2.6 L 3.44,3.06 C 3.63,3.23 3.89,2.97 3.72,2.78 L 2.93,1.99 c -0.08,-0.08 -0.2,-0.08 -0.28,0 L 1.86,2.78 C 1.65,2.97 1.95,3.26 2.14,3.06 L 2.6,2.6 Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -240,7 +240,7 @@ export function AddImusStep({
|
||||
<>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex flex-col gap-4">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('firmware_tool-add_imus_step-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -297,7 +297,7 @@ export function AddImusStep({
|
||||
<div className="flex justify-center flex-col items-center gap-3 h-44">
|
||||
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
|
||||
<Localized id="firmware_tool-loading">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -53,7 +53,7 @@ export function BoardPinsStep({
|
||||
<>
|
||||
<div className="flex flex-col w-full justify-between text-background-10">
|
||||
<div className="flex flex-col gap-4">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('firmware_tool-board_pins_step-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -172,7 +172,7 @@ export function BoardPinsStep({
|
||||
<div className="flex justify-center flex-col items-center gap-3 h-44">
|
||||
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
|
||||
<Localized id="firmware_tool-loading">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -68,7 +68,7 @@ export function BuildStep({
|
||||
<>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex flex-grow flex-col gap-4">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('firmware_tool-build_step-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -82,7 +82,7 @@ export function BuildStep({
|
||||
: SlimeState.SAD
|
||||
}
|
||||
></LoaderIcon>
|
||||
<Typography variant="section-title" color="secondary">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('firmware_tool-build-' + buildStatus.status)}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -91,7 +91,7 @@ export function BuildStep({
|
||||
<div className="flex justify-center flex-col items-center gap-3 h-44">
|
||||
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
|
||||
<Localized id="firmware_tool-loading">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -85,9 +85,7 @@ function FirmwareToolContent() {
|
||||
.getString('firmware_tool-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ export function FlashBtnStep({
|
||||
<>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex flex-grow flex-col gap-4">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('firmware_tool-flashbtn_step-description')}
|
||||
</Typography>
|
||||
{defaultConfig?.boardConfig.type ===
|
||||
|
||||
@@ -160,7 +160,7 @@ function SerialDevicesList({
|
||||
</Localized>
|
||||
{Object.keys(devices).length === 0 ? (
|
||||
<Localized id="firmware_tool-flash_method_serial-no_devices">
|
||||
<Typography variant="standard" color="secondary"></Typography>
|
||||
<Typography variant="standard"></Typography>
|
||||
</Localized>
|
||||
) : (
|
||||
<Dropdown
|
||||
@@ -274,7 +274,7 @@ function OTADevicesList({
|
||||
</Localized>
|
||||
{devices.length === 0 && (
|
||||
<Localized id="firmware_tool-flash_method_ota-no_devices">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
)}
|
||||
<div className="grid xs-settings:grid-cols-2 mobile-settings:grid-cols-1 gap-2">
|
||||
@@ -347,7 +347,7 @@ export function FlashingMethodStep({
|
||||
<>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex flex-grow flex-col gap-4">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('firmware_tool-flash_method_step-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -416,7 +416,7 @@ export function FlashingMethodStep({
|
||||
<div className="flex justify-center flex-col items-center gap-3 h-44">
|
||||
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
|
||||
<Localized id="firmware_tool-loading">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -164,7 +164,7 @@ export function FlashingStep({
|
||||
<>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex flex-grow flex-col gap-4">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('firmware_tool-flashing_step-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@ export function SelectBoardStep({
|
||||
<>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex flex-grow flex-col gap-4">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('firmware_tool-board_step-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -91,7 +91,7 @@ export function SelectBoardStep({
|
||||
<div className="flex justify-center flex-col items-center gap-3 h-44">
|
||||
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
|
||||
<Localized id="firmware_tool-loading">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -40,7 +40,7 @@ export function SelectFirmwareStep({
|
||||
<>
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex justify-between items-center mobile:flex-col gap-4">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('firmware_tool-select_firmware_step-description')}
|
||||
</Typography>
|
||||
<div>
|
||||
@@ -109,7 +109,7 @@ export function SelectFirmwareStep({
|
||||
<div className="flex justify-center flex-col items-center gap-3 h-44">
|
||||
<LoaderIcon slimeState={SlimeState.JUMPY}></LoaderIcon>
|
||||
<Localized id="firmware_tool-loading">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -320,7 +320,7 @@ export function FirmwareUpdate() {
|
||||
<Typography variant="section-title"></Typography>
|
||||
</Localized>
|
||||
<Localized id="firmware_update-devices-description">
|
||||
<Typography variant="standard" color="secondary"></Typography>
|
||||
<Typography variant="standard"></Typography>
|
||||
</Localized>
|
||||
<div className="flex flex-col gap-4 overflow-y-auto xs:max-h-[530px]">
|
||||
{devices.length === 0 &&
|
||||
|
||||
@@ -28,8 +28,8 @@ export function SkipSetupButton({
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
'text-background-40 hover:text-background-30',
|
||||
'stroke-background-40 hover:stroke-background-30',
|
||||
'text-background-10 hover:text-background-20',
|
||||
'stroke-background-10 hover:stroke-background-20',
|
||||
'absolute xs:-top-10 xs:right-4 mobile:top-0 mobile:right-0'
|
||||
)}
|
||||
onClick={onClick}
|
||||
|
||||
@@ -132,9 +132,7 @@ export function CalibrationTutorialPage() {
|
||||
id="onboarding-calibration_tutorial-description-v1"
|
||||
elems={{ b: <b></b> }}
|
||||
>
|
||||
<Typography color="secondary">
|
||||
Description on calibration of IMU
|
||||
</Typography>
|
||||
<Typography>Description on calibration of IMU</Typography>
|
||||
</Localized>
|
||||
<div>
|
||||
<div className="xs:hidden flex flex-row justify-center">
|
||||
|
||||
@@ -221,10 +221,10 @@ export function ConnectTrackersPage() {
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('onboarding-connect_tracker-title')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-connect_tracker-description-p0-v1')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-connect_tracker-description-p1-v1')}
|
||||
</Typography>
|
||||
<div className="flex flex-col gap-2 py-5">
|
||||
@@ -281,7 +281,7 @@ export function ConnectTrackersPage() {
|
||||
{l10n.getString('onboarding-connect_tracker-usb')}
|
||||
</Typography>
|
||||
<div className="flex fill-background-10 gap-1">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(statusLabelMap[provisioningStatus])}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -319,7 +319,7 @@ export function ConnectTrackersPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ gridArea: 't' }} className="flex items-center px-5">
|
||||
<Typography color="secondary" bold>
|
||||
<Typography bold>
|
||||
{l10n.getString('onboarding-connect_tracker-connected_trackers', {
|
||||
amount: connectedIMUTrackers.length,
|
||||
})}
|
||||
|
||||
@@ -20,7 +20,7 @@ export function DonePage() {
|
||||
{l10n.getString('onboarding-done-title')}
|
||||
</Typography>
|
||||
<div className="flex flex-col items-center">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-done-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ export function EnterVRPage() {
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('onboarding-enter_vr-title')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-enter_vr-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -125,7 +125,7 @@ export function ResetTutorialPage() {
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('onboarding-reset_tutorial')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-reset_tutorial-explanation')}
|
||||
</Typography>
|
||||
<div className="flex">
|
||||
@@ -165,7 +165,7 @@ export function ResetTutorialPage() {
|
||||
curIndex >= order.length && 'hidden'
|
||||
)}
|
||||
>
|
||||
<Typography whitespace="whitespace-pre-line" color="secondary">
|
||||
<Typography whitespace="whitespace-pre-line">
|
||||
{l10n.getString(`onboarding-reset_tutorial-${curIndex}`, {
|
||||
taps: tapSettings[curIndex],
|
||||
})}
|
||||
@@ -184,7 +184,7 @@ export function ResetTutorialPage() {
|
||||
curIndex >= order.length && 'hidden'
|
||||
)}
|
||||
>
|
||||
<Typography whitespace="whitespace-pre-line" color="secondary">
|
||||
<Typography whitespace="whitespace-pre-line">
|
||||
{l10n.getString(`onboarding-reset_tutorial-${curIndex}`, {
|
||||
taps: tapSettings[curIndex],
|
||||
})}
|
||||
|
||||
@@ -35,9 +35,7 @@ export function WifiCredsPage() {
|
||||
.getString('onboarding-wifi_creds-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
</>
|
||||
{!state.alonePage && (
|
||||
|
||||
@@ -32,7 +32,7 @@ export function AutomaticProportionsPage() {
|
||||
{l10n.getString('onboarding-automatic_proportions-title')}
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_proportions-description'
|
||||
)}
|
||||
|
||||
@@ -52,7 +52,7 @@ export function ScaledProportionsPage() {
|
||||
{l10n.getString('onboarding-scaled_proportions-title')}
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-scaled_proportions-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +72,7 @@ export function CheckFloorHeightStep({
|
||||
)}
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_proportions-check_floor_height-description'
|
||||
)}
|
||||
@@ -81,7 +81,7 @@ export function CheckFloorHeightStep({
|
||||
id="onboarding-automatic_proportions-check_floor_height-calculation_warning-v2"
|
||||
elems={{ u: <span className="underline"></span> }}
|
||||
>
|
||||
<Typography color="secondary" bold>
|
||||
<Typography bold>
|
||||
Press the button to get your height!
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
@@ -60,7 +60,7 @@ export function CheckHeightStep({
|
||||
)}
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_proportions-check_height-description-v2'
|
||||
)}
|
||||
@@ -69,7 +69,7 @@ export function CheckHeightStep({
|
||||
id="onboarding-automatic_proportions-check_height-calculation_warning-v3"
|
||||
elems={{ u: <span className="underline"></span> }}
|
||||
>
|
||||
<Typography color="secondary" bold>
|
||||
<Typography bold>
|
||||
Press the button to get your height!
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
@@ -12,7 +12,7 @@ export function DoneStep({ variant }: { variant: 'onboarding' | 'alone' }) {
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('onboarding-automatic_proportions-done-title')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-automatic_proportions-done-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -26,13 +26,13 @@ export function PreparationStep({
|
||||
</Typography>
|
||||
<div>
|
||||
<Localized id="onboarding-automatic_mounting-preparation-v2-step-0">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
<Localized id="onboarding-automatic_mounting-preparation-v2-step-1">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
<Localized id="onboarding-automatic_mounting-preparation-v2-step-2">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ export function PutTrackersOnStep({
|
||||
)}
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_proportions-put_trackers_on-description'
|
||||
)}
|
||||
|
||||
@@ -97,7 +97,7 @@ export function Recording({
|
||||
'onboarding-automatic_proportions-recording-description-p0'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_proportions-recording-description-p1'
|
||||
)}
|
||||
@@ -110,7 +110,7 @@ export function Recording({
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<li key={i}>
|
||||
<Typography color="secondary">{line}</Typography>
|
||||
<Typography>{line}</Typography>
|
||||
</li>
|
||||
))}
|
||||
</>
|
||||
@@ -137,7 +137,7 @@ export function Recording({
|
||||
)
|
||||
.otherwise(() => undefined)}
|
||||
></ProgressBar>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{match([hasCalibration, hasRecording])
|
||||
.returnType<ReactNode>()
|
||||
.with([ProcessStatus.PENDING, ProcessStatus.FULFILLED], () =>
|
||||
|
||||
@@ -31,7 +31,7 @@ export function RequirementsStep({
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<li key={i}>
|
||||
<Typography color="secondary">{line}</Typography>
|
||||
<Typography>{line}</Typography>
|
||||
</li>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -63,7 +63,7 @@ export function StartRecording({
|
||||
)}
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_proportions-start_recording-description'
|
||||
)}
|
||||
@@ -76,7 +76,7 @@ export function StartRecording({
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<li key={i}>
|
||||
<Typography color="secondary">{line}</Typography>
|
||||
<Typography>{line}</Typography>
|
||||
</li>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -42,7 +42,7 @@ export function VerifyResultsStep({
|
||||
)}
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_proportions-verify_results-description'
|
||||
)}
|
||||
@@ -65,7 +65,7 @@ export function VerifyResultsStep({
|
||||
>
|
||||
{bodyParts?.map(({ bone, label, value }) => (
|
||||
<div className="flex justify-between" key={bone}>
|
||||
<Typography color="secondary">{label}</Typography>
|
||||
<Typography>{label}</Typography>
|
||||
<Typography bold sentryMask>
|
||||
{(value * 100).toFixed(2)} CM
|
||||
</Typography>
|
||||
|
||||
@@ -12,7 +12,7 @@ export function DoneStep({ variant }: { variant: 'onboarding' | 'alone' }) {
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('onboarding-scaled_proportions-done-title')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-scaled_proportions-done-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -90,7 +90,7 @@ export function ManualHeightStep({
|
||||
)}
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-scaled_proportions-manual_height-description-v2'
|
||||
)}
|
||||
|
||||
@@ -26,7 +26,7 @@ export function ResetProportionsStep({
|
||||
)}
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-scaled_proportions-reset_proportion-description'
|
||||
)}
|
||||
|
||||
@@ -33,7 +33,7 @@ export function AutomaticMountingPage() {
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('onboarding-automatic_mounting-title')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-automatic_mounting-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -99,7 +99,7 @@ export function ManualMountingPage() {
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('onboarding-manual_mounting')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-manual_mounting-description')}
|
||||
</Typography>
|
||||
<TipBox>{l10n.getString('tips-find_tracker')}</TipBox>
|
||||
|
||||
@@ -17,16 +17,12 @@ export function MountingChoose() {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-5 h-full items-center w-full xs:justify-center relative overflow-y-auto px-4 pb-4">
|
||||
<div className="flex flex-col gap-4 justify-center">
|
||||
<div className="flex flex-col gap-8 justify-center">
|
||||
<div className="xs:w-10/12 xs:max-w-[666px]">
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('onboarding-choose_mounting')}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="standard"
|
||||
color="secondary"
|
||||
whitespace="whitespace-pre-line"
|
||||
>
|
||||
<Typography variant="standard" whitespace="whitespace-pre-line">
|
||||
{l10n.getString('onboarding-choose_mounting-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -37,12 +33,19 @@ export function MountingChoose() {
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'rounded-lg p-4 flex',
|
||||
'rounded-lg p-4 flex relative',
|
||||
!state.alonePage && 'bg-background-70',
|
||||
state.alonePage && 'bg-background-60'
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="bg-accent-background-30 absolute -left-4 -top-5 p-1.5 rounded-lg">
|
||||
<Typography variant="vr-accessible" italic>
|
||||
{l10n.getString(
|
||||
'onboarding-choose_mounting-auto_mounting-label-v2'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 ">
|
||||
<div className="flex flex-grow flex-col gap-4 max-w-sm">
|
||||
<div>
|
||||
<Typography variant="main-title" bold>
|
||||
@@ -50,14 +53,9 @@ export function MountingChoose() {
|
||||
'onboarding-choose_mounting-auto_mounting'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography variant="vr-accessible" italic>
|
||||
{l10n.getString(
|
||||
'onboarding-choose_mounting-auto_mounting-label-v2'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-choose_mounting-auto_mounting-description'
|
||||
)}
|
||||
@@ -98,14 +96,9 @@ export function MountingChoose() {
|
||||
'onboarding-choose_mounting-manual_mounting'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography variant="vr-accessible" italic>
|
||||
{l10n.getString(
|
||||
'onboarding-choose_mounting-manual_mounting-label-v2'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-choose_mounting-manual_mounting-description'
|
||||
)}
|
||||
|
||||
@@ -20,7 +20,7 @@ export function DoneStep({
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('onboarding-automatic_mounting-done-title')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-automatic_mounting-done-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -38,6 +38,11 @@ export function DoneStep({
|
||||
{l10n.getString('onboarding-automatic_mounting-next')}
|
||||
</Button>
|
||||
)}
|
||||
{variant === 'alone' && (
|
||||
<Button className="flex gap-3" variant="primary" to="/">
|
||||
{l10n.getString('onboarding-automatic_mounting-return-home')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<SkeletonVisualizerWidget />
|
||||
|
||||
@@ -27,12 +27,12 @@ export function MountingResetStep({
|
||||
)}
|
||||
</Typography>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_mounting-mounting_reset-step-0'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_mounting-mounting_reset-step-1'
|
||||
)}
|
||||
|
||||
@@ -26,13 +26,13 @@ export function PreparationStep({
|
||||
</Typography>
|
||||
<div>
|
||||
<Localized id="onboarding-automatic_mounting-preparation-v2-step-0">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
<Localized id="onboarding-automatic_mounting-preparation-v2-step-1">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
<Localized id="onboarding-automatic_mounting-preparation-v2-step-2">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@ export function PutTrackersOnStep({
|
||||
)}
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_mounting-put_trackers_on-description'
|
||||
)}
|
||||
|
||||
@@ -136,7 +136,7 @@ export function StayAlignedSetup() {
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('onboarding-stay_aligned-title')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-stay_aligned-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ export function DoneStep({ goTo }: VerticalStepComponentProps) {
|
||||
<Typography variant="main-title"></Typography>
|
||||
</Localized>
|
||||
<Localized id="onboarding-stay_aligned-done-description-2">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
<div className="flex gap-3 justify-between">
|
||||
|
||||
@@ -16,13 +16,13 @@ export function PreparationStep({
|
||||
<div className="flex flex-col flex-grow justify-between py-2 gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Localized id="onboarding-automatic_mounting-preparation-v2-step-0">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
<Localized id="onboarding-automatic_mounting-preparation-v2-step-1">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
<Localized id="onboarding-automatic_mounting-preparation-v2-step-2">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
<Localized id="onboarding-stay_aligned-preparation-tip">
|
||||
|
||||
@@ -19,7 +19,7 @@ export function PutTrackersOnStep({ nextStep }: VerticalStepComponentProps) {
|
||||
<div className="flex flex-grow flex-col gap-4">
|
||||
<div>
|
||||
<Localized id="onboarding-stay_aligned-put_trackers_on-description">
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
<div className="flex">
|
||||
|
||||
@@ -30,9 +30,7 @@ function PosePage({
|
||||
<div className="flex flex-col py-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
{descriptionKeys.map((descriptionKey) => (
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(descriptionKey)}
|
||||
</Typography>
|
||||
<Typography>{l10n.getString(descriptionKey)}</Typography>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex pt-1 items-center fill-background-50 justify-center px-12">
|
||||
|
||||
@@ -11,16 +11,16 @@ export function VerifyMountingStep({
|
||||
<div className="flex flex-grow flex-col gap-4 py-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Localized id="onboarding-stay_aligned-verify_mounting-step-0">
|
||||
<Typography color="secondary" />
|
||||
<Typography />
|
||||
</Localized>
|
||||
<Localized id="onboarding-stay_aligned-verify_mounting-step-1">
|
||||
<Typography color="secondary" />
|
||||
<Typography />
|
||||
</Localized>
|
||||
<Localized id="onboarding-stay_aligned-verify_mounting-step-2">
|
||||
<Typography color="secondary" />
|
||||
<Typography />
|
||||
</Localized>
|
||||
<Localized id="onboarding-stay_aligned-verify_mounting-step-3">
|
||||
<Typography color="secondary" />
|
||||
<Typography />
|
||||
</Localized>
|
||||
</div>
|
||||
<div className="flex gap-3 justify-between">
|
||||
|
||||
@@ -43,7 +43,7 @@ const ItemContent = ({
|
||||
mode,
|
||||
})}
|
||||
</Typography>
|
||||
<Typography variant="standard" color="secondary">
|
||||
<Typography variant="standard">
|
||||
{l10n.getString('onboarding-assign_trackers-option-description', {
|
||||
mode,
|
||||
})}
|
||||
|
||||
@@ -284,11 +284,11 @@ export function TrackersAssignPage() {
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('onboarding-assign_trackers-title')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-assign_trackers-description')}
|
||||
</Typography>
|
||||
<div className="flex gap-1">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('onboarding-assign_trackers-assigned', {
|
||||
assigned: assignedTrackers.length,
|
||||
trackers: trackers.length,
|
||||
|
||||
@@ -63,11 +63,11 @@ export function AdvancedSettings() {
|
||||
<div className="grid gap-4">
|
||||
<div className="sm:grid sm:grid-cols-[1.75fr,_1fr] items-center">
|
||||
<div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-utils-advanced-reset-gui')}
|
||||
</Typography>
|
||||
<div className="flex flex-col">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-utils-advanced-reset-gui-description'
|
||||
)}
|
||||
@@ -95,11 +95,11 @@ export function AdvancedSettings() {
|
||||
|
||||
<div className="sm:grid sm:grid-cols-[1.75fr,_1fr] items-center">
|
||||
<div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-utils-advanced-reset-server')}
|
||||
</Typography>
|
||||
<div className="flex flex-col">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-utils-advanced-reset-server-description'
|
||||
)}
|
||||
@@ -132,11 +132,11 @@ export function AdvancedSettings() {
|
||||
|
||||
<div className="sm:grid sm:grid-cols-[1.75fr,_1fr] items-center">
|
||||
<div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-utils-advanced-reset-all')}
|
||||
</Typography>
|
||||
<div className="flex flex-col">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-utils-advanced-reset-all-description'
|
||||
)}
|
||||
@@ -168,11 +168,11 @@ export function AdvancedSettings() {
|
||||
|
||||
<div className="sm:grid sm:grid-cols-[1.75fr,_1fr] items-center">
|
||||
<div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-utils-advanced-open_data-v1')}
|
||||
</Typography>
|
||||
<div className="flex flex-col">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-utils-advanced-open_data-description-v1'
|
||||
)}
|
||||
@@ -188,11 +188,11 @@ export function AdvancedSettings() {
|
||||
|
||||
<div className="sm:grid sm:grid-cols-[1.75fr,_1fr] items-center">
|
||||
<div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-utils-advanced-open_logs')}
|
||||
</Typography>
|
||||
<div className="flex flex-col">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-utils-advanced-open_logs-description'
|
||||
)}
|
||||
|
||||
@@ -459,7 +459,7 @@ export function GeneralSettings() {
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('settings-general-steamvr')}
|
||||
</Typography>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-steamvr-subtitle')}
|
||||
</Typography>
|
||||
<div className="flex flex-col py-2">
|
||||
@@ -467,13 +467,11 @@ export function GeneralSettings() {
|
||||
.getString('settings-general-steamvr-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col pt-4"></div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString(
|
||||
'settings-general-steamvr-trackers-tracker_toggling'
|
||||
)}
|
||||
@@ -485,9 +483,7 @@ export function GeneralSettings() {
|
||||
)
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
</div>
|
||||
<CheckBox
|
||||
@@ -608,7 +604,7 @@ export function GeneralSettings() {
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('settings-general-tracker_mechanics')}
|
||||
</Typography>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-tracker_mechanics-filtering')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-2 pb-4">
|
||||
@@ -618,9 +614,7 @@ export function GeneralSettings() {
|
||||
)
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
</div>
|
||||
<Typography>
|
||||
@@ -690,7 +684,7 @@ export function GeneralSettings() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col pt-5 pb-3">
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString(
|
||||
'settings-general-tracker_mechanics-save_mounting_reset'
|
||||
)}
|
||||
@@ -699,7 +693,7 @@ export function GeneralSettings() {
|
||||
id="settings-general-tracker_mechanics-save_mounting_reset-description"
|
||||
elems={{ b: <b></b> }}
|
||||
>
|
||||
<Typography color="secondary"></Typography>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
<CheckBox
|
||||
@@ -725,19 +719,19 @@ export function GeneralSettings() {
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('settings-general-fk_settings')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-2 pb-4">
|
||||
<Typography bold>
|
||||
<div className="flex flex-col pt-2 pb-4 gap-2">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-leg_tweak-skating_correction'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-leg_tweak-skating_correction-description'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 gap-3 pb-4">
|
||||
<div className="grid sm:grid-cols-1 gap-2 pb-4">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
@@ -761,18 +755,18 @@ export function GeneralSettings() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col pt-2 pb-2">
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-fk_settings-leg_fk')}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 gap-3 pb-3">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-leg_tweak-floor_clip-description'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 gap-3 pb-3">
|
||||
<div className="grid sm:grid-cols-1 gap-2 pb-3">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
@@ -784,7 +778,7 @@ export function GeneralSettings() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col pt-2 pb-3">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-leg_tweak-foot_plant-description'
|
||||
)}
|
||||
@@ -802,7 +796,7 @@ export function GeneralSettings() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col pt-2 pb-3">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-leg_tweak-toe_snap-description'
|
||||
)}
|
||||
@@ -821,10 +815,10 @@ export function GeneralSettings() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col pt-2 pb-3">
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-fk_settings-arm_fk')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-description'
|
||||
)}
|
||||
@@ -843,110 +837,114 @@ export function GeneralSettings() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col pt-2">
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-fk_settings-reset_settings')}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex flex-col pt-2 pb-3">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-reset_settings-reset_hmd_pitch-description'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 gap-3 pb-3">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
control={control}
|
||||
name="resetsSettings.resetHmdPitch"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-reset_settings-reset_hmd_pitch'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col pt-2 pb-3">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-leg_fk-reset_mounting_feet-description-v1'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 gap-3 pb-3">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
control={control}
|
||||
name="resetsSettings.resetMountingFeet"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-leg_fk-reset_mounting_feet-v1'
|
||||
)}
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-reset_settings-reset_hmd_pitch-description'
|
||||
)}
|
||||
</Typography>
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
control={control}
|
||||
name="resetsSettings.resetHmdPitch"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-reset_settings-reset_hmd_pitch'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 justify-end">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-leg_fk-reset_mounting_feet-description-v1'
|
||||
)}
|
||||
</Typography>
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
outlined
|
||||
control={control}
|
||||
name="resetsSettings.resetMountingFeet"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-leg_fk-reset_mounting_feet-v1'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-reset_mode-description'
|
||||
)}
|
||||
</Typography>
|
||||
<div className="grid md:grid-cols-2 flex-col gap-3 pt-2 pb-3">
|
||||
<Radio
|
||||
control={control}
|
||||
name="resetsSettings.armsMountingResetMode"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-back'
|
||||
<div>
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-reset_mode-description'
|
||||
)}
|
||||
description={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-back-description'
|
||||
)}
|
||||
value={'0'}
|
||||
></Radio>
|
||||
<Radio
|
||||
control={control}
|
||||
name="resetsSettings.armsMountingResetMode"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-forward'
|
||||
)}
|
||||
description={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-forward-description'
|
||||
)}
|
||||
value={'1'}
|
||||
></Radio>
|
||||
<Radio
|
||||
control={control}
|
||||
name="resetsSettings.armsMountingResetMode"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-tpose_up'
|
||||
)}
|
||||
description={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-tpose_up-description'
|
||||
)}
|
||||
value={'2'}
|
||||
></Radio>
|
||||
<Radio
|
||||
control={control}
|
||||
name="resetsSettings.armsMountingResetMode"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-tpose_down'
|
||||
)}
|
||||
description={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-tpose_down-description'
|
||||
)}
|
||||
value={'3'}
|
||||
></Radio>
|
||||
</Typography>
|
||||
<div className="grid md:grid-cols-2 flex-col gap-3 pt-2 pb-3">
|
||||
<Radio
|
||||
control={control}
|
||||
name="resetsSettings.armsMountingResetMode"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-back'
|
||||
)}
|
||||
description={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-back-description'
|
||||
)}
|
||||
value={'0'}
|
||||
></Radio>
|
||||
<Radio
|
||||
control={control}
|
||||
name="resetsSettings.armsMountingResetMode"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-forward'
|
||||
)}
|
||||
description={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-forward-description'
|
||||
)}
|
||||
value={'1'}
|
||||
></Radio>
|
||||
<Radio
|
||||
control={control}
|
||||
name="resetsSettings.armsMountingResetMode"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-tpose_up'
|
||||
)}
|
||||
description={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-tpose_up-description'
|
||||
)}
|
||||
value={'2'}
|
||||
></Radio>
|
||||
<Radio
|
||||
control={control}
|
||||
name="resetsSettings.armsMountingResetMode"
|
||||
label={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-tpose_down'
|
||||
)}
|
||||
description={l10n.getString(
|
||||
'settings-general-fk_settings-arm_fk-tpose_down-description'
|
||||
)}
|
||||
value={'3'}
|
||||
></Radio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col pt-2 pb-3">
|
||||
<Typography bold>
|
||||
<div className="flex flex-col pt-2 pb-1">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-enforce_joint_constraints'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-enforce_joint_constraints-enforce_constraints-description'
|
||||
)}
|
||||
</Typography>
|
||||
<div className="pt-2">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-enforce_joint_constraints-enforce_constraints-description'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 pb-3">
|
||||
<CheckBox
|
||||
@@ -963,12 +961,12 @@ export function GeneralSettings() {
|
||||
{config?.debug && (
|
||||
<>
|
||||
<div className="flex flex-col pt-2 pb-3">
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-skeleton_settings-toggles'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-skeleton_settings-description'
|
||||
)}
|
||||
@@ -1003,14 +1001,14 @@ export function GeneralSettings() {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col pt-2 pb-3">
|
||||
<div className="flex flex-col pt-2 pb-3">
|
||||
<Typography bold>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col pt-2 pb-3 gap-2">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-skeleton_settings-ratios'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-skeleton_settings-ratios-description'
|
||||
)}
|
||||
@@ -1112,12 +1110,12 @@ export function GeneralSettings() {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col pt-2 pb-3">
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-self_localization-title'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-fk_settings-self_localization-description'
|
||||
)}
|
||||
@@ -1147,11 +1145,12 @@ export function GeneralSettings() {
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('settings-general-gesture_control')}
|
||||
</Typography>
|
||||
<Typography bold>
|
||||
{l10n.getString('settings-general-gesture_control-subtitle')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-2 pb-4">
|
||||
<Typography color="secondary">
|
||||
|
||||
<div className="flex flex-col pt-2 pb-4 gap-2">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-gesture_control-subtitle')}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{l10n.getString('settings-general-gesture_control-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -1267,12 +1266,12 @@ export function GeneralSettings() {
|
||||
/>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 gap-2 pt-2">
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString(
|
||||
'settings-general-gesture_control-numberTrackersOverThreshold'
|
||||
)}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-gesture_control-numberTrackersOverThreshold-description'
|
||||
)}
|
||||
|
||||
@@ -130,11 +130,14 @@ export function InterfaceSettings() {
|
||||
{l10n.getString('settings-interface-notifications')}
|
||||
</Typography>
|
||||
|
||||
<Typography bold>
|
||||
{l10n.getString('settings-general-interface-serial_detection')}
|
||||
</Typography>
|
||||
<div className="pt-2">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-interface-serial_detection')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-interface-serial_detection-description'
|
||||
)}
|
||||
@@ -152,11 +155,11 @@ export function InterfaceSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-interface-feedback_sound')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-interface-feedback_sound-description'
|
||||
)}
|
||||
@@ -187,13 +190,13 @@ export function InterfaceSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString(
|
||||
'settings-general-interface-connected_trackers_warning'
|
||||
)}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-interface-connected_trackers_warning-description'
|
||||
)}
|
||||
@@ -221,130 +224,134 @@ export function InterfaceSettings() {
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('settings-interface-behavior')}
|
||||
</Typography>
|
||||
<div className="pt-2">
|
||||
{isTrayAvailable && (
|
||||
<>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-interface-use_tray')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-interface-use_tray-description'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 pb-4">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="behavior.useTray"
|
||||
label={l10n.getString(
|
||||
'settings-general-interface-use_tray-label'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isTrayAvailable && (
|
||||
<>
|
||||
<Typography bold>
|
||||
{l10n.getString('settings-general-interface-use_tray')}
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-interface-discord_presence')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-interface-discord_presence-description'
|
||||
)}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography color="secondary">
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 pb-4">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="behavior.discordPresence"
|
||||
label={l10n.getString(
|
||||
'settings-general-interface-discord_presence-label'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-interface-dev_mode')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-interface-dev_mode-description'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 pb-4">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="behavior.devmode"
|
||||
label={l10n.getString(
|
||||
'settings-general-interface-dev_mode-label'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-interface-behavior-error_tracking')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Localized
|
||||
id={
|
||||
'settings-interface-behavior-error_tracking-description_v2'
|
||||
}
|
||||
elems={{
|
||||
b: <b></b>,
|
||||
}}
|
||||
>
|
||||
<Typography whitespace="whitespace-pre-line"></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 pb-4">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="behavior.errorTracking"
|
||||
label={l10n.getString(
|
||||
'settings-interface-behavior-error_tracking-label'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isTauri() && (
|
||||
<>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString(
|
||||
'settings-general-interface-use_tray-description'
|
||||
'settings-interface-behavior-bvh_directory'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 pb-4">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="behavior.useTray"
|
||||
label={l10n.getString(
|
||||
'settings-general-interface-use_tray-label'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Typography bold>
|
||||
{l10n.getString('settings-general-interface-discord_presence')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(
|
||||
'settings-general-interface-discord_presence-description'
|
||||
)}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Localized
|
||||
id={
|
||||
'settings-interface-behavior-bvh_directory-description'
|
||||
}
|
||||
>
|
||||
<Typography></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
<div className="grid gap-3 pb-5">
|
||||
<TauriFileInput
|
||||
name="behavior.bvhDirectory"
|
||||
rules={{
|
||||
required: false,
|
||||
}}
|
||||
control={control}
|
||||
label="settings-interface-behavior-bvh_directory-label"
|
||||
directory
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 pb-4">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="behavior.discordPresence"
|
||||
label={l10n.getString(
|
||||
'settings-general-interface-discord_presence-label'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Typography bold>
|
||||
{l10n.getString('settings-general-interface-dev_mode')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(
|
||||
'settings-general-interface-dev_mode-description'
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 pb-4">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="behavior.devmode"
|
||||
label={l10n.getString(
|
||||
'settings-general-interface-dev_mode-label'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Typography bold>
|
||||
{l10n.getString('settings-interface-behavior-error_tracking')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Localized
|
||||
id={'settings-interface-behavior-error_tracking-description_v2'}
|
||||
elems={{
|
||||
b: <b></b>,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
color="secondary"
|
||||
whitespace="whitespace-pre-line"
|
||||
></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-2 pb-4">
|
||||
<CheckBox
|
||||
variant="toggle"
|
||||
control={control}
|
||||
outlined
|
||||
name="behavior.errorTracking"
|
||||
label={l10n.getString(
|
||||
'settings-interface-behavior-error_tracking-label'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isTauri() && (
|
||||
<>
|
||||
<Typography bold>
|
||||
{l10n.getString('settings-interface-behavior-bvh_directory')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Localized
|
||||
id={'settings-interface-behavior-bvh_directory-description'}
|
||||
>
|
||||
<Typography color="secondary"></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
<div className="grid gap-3 pb-5">
|
||||
<TauriFileInput
|
||||
name="behavior.bvhDirectory"
|
||||
rules={{
|
||||
required: false,
|
||||
}}
|
||||
control={control}
|
||||
label="settings-interface-behavior-bvh_directory-label"
|
||||
directory
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</SettingsPagePaneLayout>
|
||||
|
||||
@@ -356,11 +363,13 @@ export function InterfaceSettings() {
|
||||
<Typography variant="main-title">
|
||||
{l10n.getString('settings-interface-appearance')}
|
||||
</Typography>
|
||||
<Typography bold>
|
||||
{l10n.getString('settings-interface-appearance-decorations')}
|
||||
</Typography>
|
||||
<div className="pt-2">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-interface-appearance-decorations')}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-interface-appearance-decorations-description'
|
||||
)}
|
||||
@@ -379,7 +388,7 @@ export function InterfaceSettings() {
|
||||
</div>
|
||||
|
||||
<div className="pb-4">
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-interface-theme')}
|
||||
</Typography>
|
||||
<div className="flex flex-wrap gap-3 pt-2">
|
||||
@@ -440,13 +449,13 @@ export function InterfaceSettings() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString(
|
||||
'settings-general-interface-show-navbar-onboarding'
|
||||
)}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-general-interface-show-navbar-onboarding-description'
|
||||
)}
|
||||
@@ -464,11 +473,11 @@ export function InterfaceSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-interface-appearance-font')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-interface-appearance-font-description'
|
||||
)}
|
||||
@@ -513,11 +522,11 @@ export function InterfaceSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-interface-appearance-font_size')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-interface-appearance-font_size-description'
|
||||
)}
|
||||
@@ -541,11 +550,11 @@ export function InterfaceSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-general-interface-lang')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pt-1 pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('settings-general-interface-lang-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -95,7 +95,7 @@ export function MagnetometerToggleSetting({
|
||||
return settingType === 'general' ? (
|
||||
<>
|
||||
<div className="flex flex-col pt-5 pb-3" id={id}>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString(
|
||||
'settings-general-tracker_mechanics-use_mag_on_all_trackers'
|
||||
)}
|
||||
@@ -104,10 +104,7 @@ export function MagnetometerToggleSetting({
|
||||
id="settings-general-tracker_mechanics-use_mag_on_all_trackers-description"
|
||||
elems={{ b: <b></b> }}
|
||||
>
|
||||
<Typography
|
||||
color="secondary"
|
||||
whitespace="whitespace-pre-line"
|
||||
></Typography>
|
||||
<Typography whitespace="whitespace-pre-line"></Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
<CheckBox
|
||||
@@ -139,10 +136,7 @@ export function MagnetometerToggleSetting({
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
color="secondary"
|
||||
whitespace="whitespace-pre-line"
|
||||
></Typography>
|
||||
<Typography whitespace="whitespace-pre-line"></Typography>
|
||||
</Localized>
|
||||
<div className="flex">
|
||||
<CheckBox
|
||||
|
||||
@@ -110,17 +110,15 @@ export function OSCRouterSettings() {
|
||||
.getString('settings-osc-router-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-router-enable')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('settings-osc-router-enable-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -133,7 +131,7 @@ export function OSCRouterSettings() {
|
||||
label={l10n.getString('settings-osc-router-enable-label')}
|
||||
/>
|
||||
</div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-router-network')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
@@ -142,9 +140,7 @@ export function OSCRouterSettings() {
|
||||
.getString('settings-osc-router-network-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
@@ -176,11 +172,11 @@ export function OSCRouterSettings() {
|
||||
></Input>
|
||||
</Localized>
|
||||
</div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-router-network-address')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-osc-router-network-address-description'
|
||||
)}
|
||||
|
||||
@@ -240,9 +240,7 @@ export function Serial() {
|
||||
.getString('settings-serial-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
|
||||
@@ -137,17 +137,15 @@ export function VMCSettings() {
|
||||
.getString('settings-osc-vmc-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-vmc-enable')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('settings-osc-vmc-enable-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -160,7 +158,7 @@ export function VMCSettings() {
|
||||
label={l10n.getString('settings-osc-vmc-enable-label')}
|
||||
/>
|
||||
</div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-vmc-network')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
@@ -169,9 +167,7 @@ export function VMCSettings() {
|
||||
.getString('settings-osc-vmc-network-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
@@ -203,11 +199,11 @@ export function VMCSettings() {
|
||||
></Input>
|
||||
</Localized>
|
||||
</div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-vmc-network-address')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('settings-osc-vmc-network-address-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -227,11 +223,11 @@ export function VMCSettings() {
|
||||
label=""
|
||||
></Input>
|
||||
</div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-vmc-vrm')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('settings-osc-vmc-vrm-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -254,11 +250,11 @@ export function VMCSettings() {
|
||||
></FileInput>
|
||||
{/* For some reason, linux (GNOME) is detecting the VRM file is a VRML */}
|
||||
</div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-vmc-anchor_hip')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('settings-osc-vmc-anchor_hip-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -271,11 +267,11 @@ export function VMCSettings() {
|
||||
label={l10n.getString('settings-osc-vmc-anchor_hip-label')}
|
||||
/>
|
||||
</div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-vmc-mirror_tracking')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('settings-osc-vmc-mirror_tracking-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -140,17 +140,15 @@ export function VRCOSCSettings() {
|
||||
.getString('settings-osc-vrchat-description-v1')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-vrchat-enable')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('settings-osc-vrchat-enable-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -164,18 +162,16 @@ export function VRCOSCSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-vrchat-oscqueryEnabled')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n
|
||||
.getString('settings-osc-vrchat-oscqueryEnabled-description')
|
||||
.split('\n')
|
||||
.map((line, i) => (
|
||||
<Typography color="secondary" key={i}>
|
||||
{line}
|
||||
</Typography>
|
||||
<Typography key={i}>{line}</Typography>
|
||||
))}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -191,11 +187,11 @@ export function VRCOSCSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-vrchat-network')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('settings-osc-vrchat-network-description-v1')}
|
||||
</Typography>
|
||||
</div>
|
||||
@@ -227,11 +223,11 @@ export function VRCOSCSettings() {
|
||||
></Input>
|
||||
</Localized>
|
||||
</div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-vrchat-network-address')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-osc-vrchat-network-address-description-v1'
|
||||
)}
|
||||
@@ -253,11 +249,11 @@ export function VRCOSCSettings() {
|
||||
label=""
|
||||
></Input>
|
||||
</div>
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-osc-vrchat-network-trackers')}
|
||||
</Typography>
|
||||
<div className="flex flex-col pb-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-osc-vrchat-network-trackers-description'
|
||||
)}
|
||||
|
||||
@@ -40,9 +40,7 @@ function StaAlignedPoseModal({
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{descriptionKeys.map((descriptionKey) => (
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(descriptionKey)}
|
||||
</Typography>
|
||||
<Typography>{l10n.getString(descriptionKey)}</Typography>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex pt-1 items-center fill-background-50 justify-center px-12">
|
||||
|
||||
@@ -182,10 +182,10 @@ export function StayAlignedSettings({
|
||||
{l10n.getString('settings-stay_aligned')}
|
||||
</Typography>
|
||||
<div className="mt-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('settings-stay_aligned-description')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('settings-stay_aligned-setup-description')}
|
||||
</Typography>
|
||||
<div className="flex mt-2">
|
||||
@@ -199,7 +199,7 @@ export function StayAlignedSettings({
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-stay_aligned-general-label')}
|
||||
</Typography>
|
||||
<div className="grid sm:grid-cols-2 gap-3 mt-2">
|
||||
@@ -224,11 +224,11 @@ export function StayAlignedSettings({
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-stay_aligned-relaxed_poses-label')}
|
||||
</Typography>
|
||||
<div className="mt-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'settings-stay_aligned-relaxed_poses-description'
|
||||
)}
|
||||
@@ -305,11 +305,11 @@ export function StayAlignedSettings({
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<Typography bold>
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('settings-stay_aligned-debug-label')}
|
||||
</Typography>
|
||||
<div className="mt-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('settings-stay_aligned-debug-description')}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@ export function SingleTrackerBodyAssignmentMenu({
|
||||
<Typography variant="mobile-title" bold>
|
||||
{l10n.getString('body_assignment_menu')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('body_assignment_menu-description')}
|
||||
</Typography>
|
||||
<div className="flex">
|
||||
|
||||
@@ -7,7 +7,7 @@ export function TrackerBattery({
|
||||
value,
|
||||
voltage,
|
||||
disabled,
|
||||
textColor = 'secondary',
|
||||
textColor = 'primary',
|
||||
}: {
|
||||
/**
|
||||
* a [0, 1] value range is expected
|
||||
|
||||
@@ -99,7 +99,7 @@ export function TrackerPartCard({
|
||||
<WarningIcon></WarningIcon>
|
||||
</div>
|
||||
)}
|
||||
<Typography color="secondary">
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('body_part-' + BodyPart[role])}
|
||||
</Typography>
|
||||
{td?.map(({ tracker }, index) => (
|
||||
@@ -110,7 +110,7 @@ export function TrackerPartCard({
|
||||
/>
|
||||
))}
|
||||
{!td && (
|
||||
<Typography>
|
||||
<Typography color="text-background-30">
|
||||
{l10n.getString('tracker-part_card-unassigned')}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
@@ -211,10 +211,10 @@ export function TrackerSettingsPage() {
|
||||
</Typography>
|
||||
</Localized>
|
||||
<div className="flex gap-2">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
v{tracker?.device?.hardwareInfo?.firmwareVersion}
|
||||
</Typography>
|
||||
<Typography color="secondary">-</Typography>
|
||||
<Typography>-</Typography>
|
||||
{updateUnavailable && (
|
||||
<Localized id="tracker-settings-update-unavailable">
|
||||
<Typography>Cannot be updated (DIY)</Typography>
|
||||
@@ -272,7 +272,7 @@ export function TrackerSettingsPage() {
|
||||
|
||||
<div className="flex flex-col bg-background-70 p-3 rounded-lg gap-2 overflow-x-auto">
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('tracker-infos-manufacturer')}
|
||||
</Typography>
|
||||
<Typography>
|
||||
@@ -280,13 +280,13 @@ export function TrackerSettingsPage() {
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('tracker-infos-display_name')}
|
||||
</Typography>
|
||||
<Typography>{tracker?.tracker.info?.displayName}</Typography>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('tracker-infos-custom_name')}
|
||||
</Typography>
|
||||
<Typography sentry-mask>
|
||||
@@ -294,9 +294,7 @@ export function TrackerSettingsPage() {
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('tracker-infos-url')}
|
||||
</Typography>
|
||||
<Typography>{l10n.getString('tracker-infos-url')}</Typography>
|
||||
<Typography>
|
||||
udp://
|
||||
{IPv4.fromNumber(
|
||||
@@ -305,7 +303,7 @@ export function TrackerSettingsPage() {
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('tracker-infos-hardware_identifier')}
|
||||
</Typography>
|
||||
<Typography>
|
||||
@@ -313,7 +311,7 @@ export function TrackerSettingsPage() {
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('tracker-infos-data_support')}
|
||||
</Typography>
|
||||
<Typography>
|
||||
@@ -323,9 +321,7 @@ export function TrackerSettingsPage() {
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('tracker-infos-imu')}
|
||||
</Typography>
|
||||
<Typography>{l10n.getString('tracker-infos-imu')}</Typography>
|
||||
<Typography>
|
||||
{tracker?.tracker.info?.imuType
|
||||
? ImuType[tracker?.tracker.info?.imuType]
|
||||
@@ -333,13 +329,13 @@ export function TrackerSettingsPage() {
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('tracker-infos-board_type')}
|
||||
</Typography>
|
||||
<Typography>{boardType}</Typography>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('tracker-infos-magnetometer')}
|
||||
</Typography>
|
||||
<Typography>
|
||||
@@ -352,7 +348,7 @@ export function TrackerSettingsPage() {
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('tracker-infos-network_version')}
|
||||
</Typography>
|
||||
<Typography>
|
||||
@@ -377,7 +373,7 @@ export function TrackerSettingsPage() {
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('tracker-settings-assignment_section')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'tracker-settings-assignment_section-description'
|
||||
)}
|
||||
@@ -419,7 +415,7 @@ export function TrackerSettingsPage() {
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('tracker-settings-mounting_section')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'tracker-settings-mounting_section-description'
|
||||
)}
|
||||
@@ -468,7 +464,7 @@ export function TrackerSettingsPage() {
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('tracker-settings-name_section')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('tracker-settings-name_section-description')}
|
||||
</Typography>
|
||||
<Input
|
||||
@@ -488,7 +484,7 @@ export function TrackerSettingsPage() {
|
||||
<Typography variant="section-title">
|
||||
{l10n.getString('tracker-settings-forget')}
|
||||
</Typography>
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('tracker-settings-forget-description')}
|
||||
</Typography>
|
||||
<Button
|
||||
|
||||
@@ -35,7 +35,7 @@ export function TrackerStatus({ status }: { status: number }) {
|
||||
<div className="flex flex-col justify-center">
|
||||
<div className={classNames('w-2 h-2 rounded-full', statusClass)}></div>
|
||||
</div>
|
||||
<Typography color="secondary" whitespace="whitespace-nowrap">
|
||||
<Typography whitespace="whitespace-nowrap">
|
||||
{l10n.getString(statusLabel)}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
@@ -130,7 +130,7 @@ export function VRCWarningsPage() {
|
||||
<Typography variant="main-title" />
|
||||
</Localized>
|
||||
<Localized id={'vrc_config-page-desc'}>
|
||||
<Typography variant="standard" color="secondary" />
|
||||
<Typography variant="standard" />
|
||||
</Localized>
|
||||
</div>
|
||||
<div className="w-full mt-4 gap-2 flex flex-col">
|
||||
@@ -142,7 +142,7 @@ export function VRCWarningsPage() {
|
||||
<Typography variant="section-title" />
|
||||
</Localized>
|
||||
<Localized id="vrc_config-page-big_menu-desc">
|
||||
<Typography color="secondary" />
|
||||
<Typography />
|
||||
</Localized>
|
||||
<Table>
|
||||
<SettingRow
|
||||
@@ -248,7 +248,7 @@ export function VRCWarningsPage() {
|
||||
<Typography variant="section-title" />
|
||||
</Localized>
|
||||
<Localized id="vrc_config-page-wrist_menu-desc">
|
||||
<Typography color="secondary" />
|
||||
<Typography />
|
||||
</Localized>
|
||||
<Table>
|
||||
<SettingRow
|
||||
@@ -304,7 +304,7 @@ export function VRCWarningsPage() {
|
||||
a: <A href="https://docs.slimevr.dev/tools/vrchat-config.html"></A>,
|
||||
}}
|
||||
>
|
||||
<Typography color="secondary" />
|
||||
<Typography />
|
||||
</Localized>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -75,9 +75,7 @@ export function DeveloperModeWidget() {
|
||||
return (
|
||||
<form className="bg-background-60 flex flex-col w-full rounded-md px-2">
|
||||
<div className="mt-2 px-1">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('widget-developer_mode')}
|
||||
</Typography>
|
||||
<Typography>{l10n.getString('widget-developer_mode')}</Typography>
|
||||
</div>
|
||||
{Object.entries(toggles).map(makeToggle)}
|
||||
</form>
|
||||
|
||||
@@ -128,7 +128,7 @@ export function IMUVisualizerWidget({ tracker }: { tracker: TrackerDataT }) {
|
||||
|
||||
{tracker.position && (
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('widget-imu_visualizer-position')}
|
||||
</Typography>
|
||||
<Typography>{formatVector3(tracker.position, 2)}</Typography>
|
||||
@@ -136,14 +136,14 @@ export function IMUVisualizerWidget({ tracker }: { tracker: TrackerDataT }) {
|
||||
)}
|
||||
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('widget-imu_visualizer-rotation_raw')}
|
||||
</Typography>
|
||||
<Typography>{formatVector3(rotationRaw, 2)}</Typography>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('widget-imu_visualizer-rotation_preview')}
|
||||
</Typography>
|
||||
<Typography>{formatVector3(rotationIdent, 2)}</Typography>
|
||||
@@ -151,7 +151,7 @@ export function IMUVisualizerWidget({ tracker }: { tracker: TrackerDataT }) {
|
||||
|
||||
{tracker.linearAcceleration && (
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('widget-imu_visualizer-acceleration')}
|
||||
</Typography>
|
||||
<Typography>
|
||||
@@ -162,7 +162,7 @@ export function IMUVisualizerWidget({ tracker }: { tracker: TrackerDataT }) {
|
||||
|
||||
{tracker.rawMagneticVector && (
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('tracker-infos-magnetometer')}
|
||||
</Typography>
|
||||
<Typography>{formatVector3(tracker.rawMagneticVector, 1)}</Typography>
|
||||
@@ -171,7 +171,7 @@ export function IMUVisualizerWidget({ tracker }: { tracker: TrackerDataT }) {
|
||||
|
||||
{!!tracker.stayAligned && (
|
||||
<div className="flex justify-between">
|
||||
<Typography color="secondary">
|
||||
<Typography>
|
||||
{l10n.getString('widget-imu_visualizer-stay_aligned')}
|
||||
</Typography>
|
||||
<StayAlignedInfo color="primary" tracker={tracker} />
|
||||
|
||||
@@ -68,9 +68,7 @@ export function OverlayWidget() {
|
||||
return !loading ? (
|
||||
<form className="bg-background-60 flex flex-col w-full rounded-md px-2">
|
||||
<div className="mt-2 px-1">
|
||||
<Typography color="secondary">
|
||||
{l10n.getString('widget-overlay')}
|
||||
</Typography>
|
||||
<Typography>{l10n.getString('widget-overlay')}</Typography>
|
||||
</div>
|
||||
<CheckBox
|
||||
control={control}
|
||||
|
||||
@@ -27,6 +27,14 @@ const hash = (str: string) => {
|
||||
const firstAsset = (assets: any[], name: string) =>
|
||||
assets.find((asset: any) => asset.name === name && asset.browser_download_url);
|
||||
|
||||
const todaysRange = (deployData: [number, Date][]): number => {
|
||||
let maxRange = 0;
|
||||
for (const [range, date] of deployData) {
|
||||
if (Date.now() >= date.getTime()) maxRange = range;
|
||||
}
|
||||
return maxRange;
|
||||
};
|
||||
|
||||
const checkUserCanUpdate = async (url: string, fwVersion: string) => {
|
||||
if (!url) return true;
|
||||
const deployDataJson = JSON.parse(
|
||||
@@ -57,11 +65,7 @@ const checkUserCanUpdate = async (url: string, fwVersion: string) => {
|
||||
)
|
||||
return false; // Dates in the wrong order / cancel
|
||||
|
||||
const todayUpdateRange = deployData.find(([, date], index) => {
|
||||
if (index === 0 && Date.now() < date.getTime()) return true;
|
||||
return Date.now() >= date.getTime();
|
||||
})?.[0];
|
||||
|
||||
const todayUpdateRange = todaysRange(deployData);
|
||||
if (!todayUpdateRange) return false;
|
||||
|
||||
const uniqueUserKey = `${await hostname()}-${await locale()}-${platform()}-${version()}`;
|
||||
|
||||
10
gui/src/vite-env.d.ts
vendored
10
gui/src/vite-env.d.ts
vendored
@@ -4,14 +4,14 @@
|
||||
declare const __COMMIT_HASH__: string;
|
||||
declare const __VERSION_TAG__: string;
|
||||
declare const __GIT_CLEAN__: boolean;
|
||||
declare const __ANDROID__:
|
||||
| {
|
||||
isThere: () => boolean;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
interface Window {
|
||||
readonly isTauri: boolean;
|
||||
readonly __ANDROID__:
|
||||
| {
|
||||
isThere: () => boolean;
|
||||
}
|
||||
| undefined;
|
||||
}
|
||||
|
||||
declare module 'tailwind-gradient-mask-image';
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-feature android:name="android.hardware.usb.host" android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
@@ -26,7 +28,11 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
android:resource="@xml/device_filter" />
|
||||
</activity>
|
||||
|
||||
<service
|
||||
|
||||
@@ -8,6 +8,8 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import dev.slimevr.Keybinding
|
||||
import dev.slimevr.VRServer
|
||||
import dev.slimevr.android.serial.AndroidSerialHandler
|
||||
import dev.slimevr.android.tracking.trackers.hid.AndroidHIDManager
|
||||
import dev.slimevr.tracking.trackers.Tracker
|
||||
import io.eiren.util.logging.LogManager
|
||||
import io.ktor.http.CacheControl
|
||||
import io.ktor.http.CacheControl.Visibility
|
||||
@@ -60,6 +62,15 @@ fun main(activity: AppCompatActivity) {
|
||||
},
|
||||
)
|
||||
vrServer.start()
|
||||
|
||||
// Start service for USB HID trackers
|
||||
val androidHidManager = AndroidHIDManager(
|
||||
"Sensors HID service",
|
||||
{ tracker: Tracker -> vrServer.registerTracker(tracker) },
|
||||
activity,
|
||||
)
|
||||
androidHidManager.start()
|
||||
|
||||
Keybinding(vrServer)
|
||||
vrServer.join()
|
||||
LogManager.closeLogger()
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package dev.slimevr.android.serial
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.hardware.usb.UsbManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.hoho.android.usbserial.driver.UsbSerialDriver
|
||||
import com.hoho.android.usbserial.driver.UsbSerialPort
|
||||
import com.hoho.android.usbserial.driver.UsbSerialProber
|
||||
@@ -15,10 +18,8 @@ import io.eiren.util.logging.LogManager
|
||||
import java.io.IOException
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.*
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.stream.Stream
|
||||
import kotlin.concurrent.timerTask
|
||||
import kotlin.streams.asSequence
|
||||
import kotlin.streams.asStream
|
||||
import dev.slimevr.serial.SerialPort as SlimeSerialPort
|
||||
@@ -43,12 +44,11 @@ class AndroidSerialHandler(val activity: AppCompatActivity) :
|
||||
private var usbIoManager: SerialInputOutputManager? = null
|
||||
|
||||
private val listeners: MutableList<SerialListener> = CopyOnWriteArrayList()
|
||||
private val getDevicesTimer = Timer("GetDevicesTimer")
|
||||
private var watchingNewDevices = false
|
||||
private var lastKnownPorts = setOf<SerialPortWrapper>()
|
||||
private val manager = activity.getSystemService(Context.USB_SERVICE) as UsbManager
|
||||
private var currentPort: SerialPortWrapper? = null
|
||||
private var requestingPermission: String = ""
|
||||
private var readBuffer: StringBuilder = StringBuilder(1024)
|
||||
|
||||
override val isConnected: Boolean
|
||||
get() = currentPort?.port?.isOpen ?: false
|
||||
@@ -60,37 +60,70 @@ class AndroidSerialHandler(val activity: AppCompatActivity) :
|
||||
.filter { isKnownBoard(it) }
|
||||
.asStream()
|
||||
|
||||
val usbReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
UsbManager.ACTION_USB_DEVICE_ATTACHED, UsbManager.ACTION_USB_DEVICE_DETACHED -> {
|
||||
// Use device from `UsbManager.EXTRA_DEVICE` if this is a problem
|
||||
detectNewPorts()
|
||||
}
|
||||
|
||||
ACTION_USB_PERMISSION -> {
|
||||
// TODO: We can probably receive this event in the server to avoid
|
||||
// polling, but for now we can just ignore it. (Note: This event is
|
||||
// not currently registered, so it will never fire.)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
startWatchingNewDevices()
|
||||
val intentFilter = IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED)
|
||||
intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
|
||||
|
||||
// Listen for USB device attach/detach
|
||||
ContextCompat.registerReceiver(
|
||||
activity,
|
||||
usbReceiver,
|
||||
intentFilter,
|
||||
ContextCompat.RECEIVER_NOT_EXPORTED,
|
||||
)
|
||||
|
||||
// Detect initial serial ports
|
||||
detectNewPorts()
|
||||
}
|
||||
|
||||
private fun getPorts(): List<UsbSerialDriver> = UsbSerialProber.getDefaultProber().findAllDrivers(manager)
|
||||
|
||||
private fun startWatchingNewDevices() {
|
||||
if (watchingNewDevices) return
|
||||
watchingNewDevices = true
|
||||
getDevicesTimer.scheduleAtFixedRate(
|
||||
timerTask {
|
||||
try {
|
||||
detectNewPorts()
|
||||
} catch (t: Throwable) {
|
||||
LogManager.severe(
|
||||
"[SerialHandler] Error while watching for new devices, cancelling the \"getDevicesTimer\".",
|
||||
t,
|
||||
)
|
||||
getDevicesTimer.cancel()
|
||||
}
|
||||
},
|
||||
0,
|
||||
3000,
|
||||
)
|
||||
}
|
||||
|
||||
private fun onNewDevice(port: SerialPortWrapper) {
|
||||
// If we missed clearing this on disconnect/close, clear it on discovery
|
||||
if (requestingPermission == port.portLocation) {
|
||||
requestingPermission = ""
|
||||
}
|
||||
|
||||
LogManager.info("[SerialHandler] Device added: ${port.descriptivePortName}")
|
||||
listeners.forEach { it.onNewSerialDevice(port) }
|
||||
}
|
||||
|
||||
private fun onDeviceDel(port: SerialPortWrapper) {
|
||||
// Remove permission request on disconnect so reconnecting re-requests
|
||||
if (requestingPermission == port.portLocation) {
|
||||
requestingPermission = ""
|
||||
}
|
||||
|
||||
// If we're currently using this port, close it
|
||||
currentPort?.portLocation.let { currentPortLocation ->
|
||||
if (currentPortLocation == port.portLocation) {
|
||||
closeSerial()
|
||||
}
|
||||
}
|
||||
|
||||
// If this port is still open for whatever reason, close it
|
||||
if (port.port.isOpen) {
|
||||
port.port.close()
|
||||
}
|
||||
|
||||
LogManager.info("[SerialHandler] Device removed: ${port.descriptivePortName}")
|
||||
listeners.forEach { it.onSerialDeviceDeleted(port) }
|
||||
}
|
||||
|
||||
@@ -154,9 +187,11 @@ class AndroidSerialHandler(val activity: AppCompatActivity) :
|
||||
flags,
|
||||
)
|
||||
if (requestingPermission != newPort.portLocation) {
|
||||
println("Requesting permission for ${newPort.portLocation}")
|
||||
LogManager.info("[SerialHandler] Requesting permission for ${newPort.portLocation}")
|
||||
manager.requestPermission(newPort.port.device, usbPermissionIntent)
|
||||
requestingPermission = newPort.portLocation
|
||||
} else {
|
||||
LogManager.info("[SerialHandler] Already requested permission for ${newPort.portLocation}, skipping")
|
||||
}
|
||||
LogManager.warning(
|
||||
"[SerialHandler] Can't open serial port ${newPort.descriptivePortName}, invalid permissions",
|
||||
@@ -164,11 +199,13 @@ class AndroidSerialHandler(val activity: AppCompatActivity) :
|
||||
return false
|
||||
}
|
||||
|
||||
// If we have permission, we aren't requesting anymore
|
||||
requestingPermission = ""
|
||||
|
||||
val connection = manager.openDevice(newPort.port.device)
|
||||
if (connection == null) {
|
||||
LogManager.warning(
|
||||
"[SerialHandler] Can't open serial port ${newPort.descriptivePortName}, connection failed",
|
||||
|
||||
)
|
||||
return false
|
||||
}
|
||||
@@ -186,7 +223,7 @@ class AndroidSerialHandler(val activity: AppCompatActivity) :
|
||||
@Synchronized
|
||||
private fun writeSerial(serialText: String, print: Boolean = false) {
|
||||
try {
|
||||
usbIoManager?.writeAsync("${serialText}\n".toByteArray())
|
||||
currentPort?.port?.write("${serialText}\n".toByteArray(), 0)
|
||||
if (print) {
|
||||
addLog("-> $serialText\n")
|
||||
}
|
||||
@@ -224,6 +261,8 @@ class AndroidSerialHandler(val activity: AppCompatActivity) :
|
||||
usbIoManager?.stop()
|
||||
usbIoManager = null
|
||||
currentPort = null
|
||||
requestingPermission = ""
|
||||
readBuffer.clear()
|
||||
} catch (e: Exception) {
|
||||
LogManager.warning(
|
||||
"[SerialHandler] Error closing port ${currentPort?.descriptivePortName}",
|
||||
@@ -233,7 +272,7 @@ class AndroidSerialHandler(val activity: AppCompatActivity) :
|
||||
}
|
||||
|
||||
override fun write(buff: ByteArray) {
|
||||
usbIoManager?.writeAsync(buff)
|
||||
currentPort?.port?.write(buff, 0)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@@ -251,14 +290,21 @@ class AndroidSerialHandler(val activity: AppCompatActivity) :
|
||||
|
||||
override fun onNewData(data: ByteArray?) {
|
||||
if (data != null) {
|
||||
val s = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(data)).toString()
|
||||
addLog(s, false)
|
||||
// Collect serial in a buffer until newline (or character limit)
|
||||
// This is somewhat of a workaround for Android serial buffer being smaller
|
||||
// than on desktop, so we don't read full lines and it causes parsing issues
|
||||
readBuffer.append(StandardCharsets.UTF_8.decode(ByteBuffer.wrap(data)))
|
||||
|
||||
if (readBuffer.contains('\n') || readBuffer.length >= 1024) {
|
||||
addLog(readBuffer.toString(), false)
|
||||
readBuffer.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRunError(e: java.lang.Exception?) {}
|
||||
|
||||
companion object {
|
||||
private val ACTION_USB_PERMISSION = "dev.slimevr.USB_PERMISSION"
|
||||
private const val ACTION_USB_PERMISSION = "dev.slimevr.USB_PERMISSION"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package dev.slimevr.android.tracking.trackers.hid
|
||||
|
||||
import android.hardware.usb.UsbConstants
|
||||
import android.hardware.usb.UsbDevice
|
||||
import android.hardware.usb.UsbDeviceConnection
|
||||
import android.hardware.usb.UsbEndpoint
|
||||
import android.hardware.usb.UsbInterface
|
||||
import android.hardware.usb.UsbManager
|
||||
import java.io.Closeable
|
||||
|
||||
/**
|
||||
* A wrapper over Android's [UsbDevice] for HID devices.
|
||||
*/
|
||||
class AndroidHIDDevice(hidDevice: UsbDevice, usbManager: UsbManager) : Closeable {
|
||||
|
||||
val deviceName = hidDevice.deviceName
|
||||
val serialNumber = hidDevice.serialNumber
|
||||
val manufacturerName = hidDevice.manufacturerName
|
||||
val productName = hidDevice.productName
|
||||
|
||||
val hidInterface: UsbInterface
|
||||
|
||||
val endpointIn: UsbEndpoint
|
||||
val endpointOut: UsbEndpoint?
|
||||
|
||||
val deviceConnection: UsbDeviceConnection
|
||||
|
||||
init {
|
||||
hidInterface = findHidInterface(hidDevice)!!
|
||||
|
||||
val (endpointIn, endpointOut) = findHidIO(hidInterface)
|
||||
this.endpointIn = endpointIn!!
|
||||
this.endpointOut = endpointOut
|
||||
|
||||
deviceConnection = usbManager.openDevice(hidDevice)!!
|
||||
|
||||
deviceConnection.claimInterface(hidInterface, true)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
deviceConnection.releaseInterface(hidInterface)
|
||||
deviceConnection.close()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Find the HID interface.
|
||||
*
|
||||
* @return
|
||||
* Return the HID interface if found, otherwise null.
|
||||
*/
|
||||
private fun findHidInterface(usbDevice: UsbDevice): UsbInterface? {
|
||||
val interfaceCount: Int = usbDevice.interfaceCount
|
||||
|
||||
for (interfaceIndex in 0 until interfaceCount) {
|
||||
val usbInterface = usbDevice.getInterface(interfaceIndex)
|
||||
|
||||
if (usbInterface.interfaceClass == UsbConstants.USB_CLASS_HID) {
|
||||
return usbInterface
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the HID endpoints.
|
||||
*
|
||||
* @return
|
||||
* Return the HID endpoints if found, otherwise null.
|
||||
*/
|
||||
private fun findHidIO(usbInterface: UsbInterface): Pair<UsbEndpoint?, UsbEndpoint?> {
|
||||
val endpointCount: Int = usbInterface.endpointCount
|
||||
|
||||
var usbEndpointIn: UsbEndpoint? = null
|
||||
var usbEndpointOut: UsbEndpoint? = null
|
||||
|
||||
for (endpointIndex in 0 until endpointCount) {
|
||||
val usbEndpoint = usbInterface.getEndpoint(endpointIndex)
|
||||
|
||||
if (usbEndpoint.type == UsbConstants.USB_ENDPOINT_XFER_INT) {
|
||||
if (usbEndpoint.direction == UsbConstants.USB_DIR_OUT) {
|
||||
usbEndpointOut = usbEndpoint
|
||||
} else {
|
||||
usbEndpointIn = usbEndpoint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(usbEndpointIn, usbEndpointOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package dev.slimevr.android.tracking.trackers.hid
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.hardware.usb.UsbDevice
|
||||
import android.hardware.usb.UsbManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import dev.slimevr.tracking.trackers.Device
|
||||
import dev.slimevr.tracking.trackers.Tracker
|
||||
import dev.slimevr.tracking.trackers.TrackerStatus
|
||||
import dev.slimevr.tracking.trackers.hid.HIDCommon
|
||||
import dev.slimevr.tracking.trackers.hid.HIDCommon.Companion.HID_TRACKER_RECEIVER_PID
|
||||
import dev.slimevr.tracking.trackers.hid.HIDCommon.Companion.HID_TRACKER_RECEIVER_VID
|
||||
import dev.slimevr.tracking.trackers.hid.HIDCommon.Companion.PACKET_SIZE
|
||||
import dev.slimevr.tracking.trackers.hid.HIDDevice
|
||||
import io.eiren.util.logging.LogManager
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.function.Consumer
|
||||
|
||||
const val ACTION_USB_PERMISSION = "dev.slimevr.USB_PERMISSION"
|
||||
|
||||
/**
|
||||
* Handles Android USB Host HID dongles and receives tracker data from them.
|
||||
*/
|
||||
class AndroidHIDManager(
|
||||
name: String,
|
||||
private val trackersConsumer: Consumer<Tracker>,
|
||||
private val context: Context,
|
||||
) : Thread(name) {
|
||||
private val devices: MutableList<HIDDevice> = mutableListOf()
|
||||
private val devicesBySerial: MutableMap<String, MutableList<Int>> = HashMap()
|
||||
private val devicesByHID: MutableMap<UsbDevice, MutableList<Int>> = HashMap()
|
||||
private val connByHID: MutableMap<UsbDevice, AndroidHIDDevice> = HashMap()
|
||||
private val lastDataByHID: MutableMap<UsbDevice, Int> = HashMap()
|
||||
private val usbManager: UsbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
|
||||
|
||||
val usbReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
|
||||
(intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) as UsbDevice?)?.let {
|
||||
checkConfigureDevice(it, requestPermission = true)
|
||||
}
|
||||
}
|
||||
|
||||
UsbManager.ACTION_USB_DEVICE_DETACHED -> {
|
||||
(intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) as UsbDevice?)?.let {
|
||||
removeDevice(it)
|
||||
}
|
||||
}
|
||||
|
||||
ACTION_USB_PERMISSION -> {
|
||||
deviceEnumerate(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithDeviceConfiguration(hidDevice: UsbDevice) {
|
||||
// This is the original logic from checkConfigureDevice after permission is confirmed
|
||||
LogManager.info("[TrackerServer] USB Permission granted for ${hidDevice.deviceName}. Proceeding with configuration.")
|
||||
|
||||
// Close any existing connection (do we still have one?)
|
||||
this.connByHID[hidDevice]?.close()
|
||||
// Open new HID connection with USB device
|
||||
this.connByHID[hidDevice] = AndroidHIDDevice(hidDevice, usbManager)
|
||||
|
||||
val serial = hidDevice.serialNumber ?: "Unknown USB Device ${hidDevice.deviceId}"
|
||||
this.devicesBySerial[serial]?.let {
|
||||
this.devicesByHID[hidDevice] = it
|
||||
synchronized(this.devices) {
|
||||
for (id in it) {
|
||||
val device = this.devices[id]
|
||||
for (value in device.trackers.values) {
|
||||
if (value.status == TrackerStatus.DISCONNECTED) value.status = TrackerStatus.OK
|
||||
}
|
||||
}
|
||||
}
|
||||
LogManager.info("[TrackerServer] Linked HID device reattached: $serial")
|
||||
return
|
||||
}
|
||||
|
||||
val list: MutableList<Int> = mutableListOf()
|
||||
this.devicesBySerial[serial] = list
|
||||
this.devicesByHID[hidDevice] = list
|
||||
this.lastDataByHID[hidDevice] = 0 // initialize last data received
|
||||
LogManager.info("[TrackerServer] (Probably) Compatible HID device detected: $serial")
|
||||
}
|
||||
|
||||
fun checkConfigureDevice(usbDevice: UsbDevice, requestPermission: Boolean = false) {
|
||||
if (usbDevice.vendorId == HID_TRACKER_RECEIVER_VID && usbDevice.productId == HID_TRACKER_RECEIVER_PID) {
|
||||
if (usbManager.hasPermission(usbDevice)) {
|
||||
LogManager.info("[TrackerServer] Already have permission for ${usbDevice.deviceName}")
|
||||
proceedWithDeviceConfiguration(usbDevice)
|
||||
} else if (requestPermission) {
|
||||
LogManager.info("[TrackerServer] Requesting permission for ${usbDevice.deviceName}")
|
||||
val permissionIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
0,
|
||||
Intent(ACTION_USB_PERMISSION).apply { setPackage(context.packageName) }, // Explicitly set package
|
||||
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
||||
)
|
||||
usbManager.requestPermission(usbDevice, permissionIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeDevice(hidDevice: UsbDevice) {
|
||||
this.devicesByHID[hidDevice]?.let {
|
||||
synchronized(this.devices) {
|
||||
for (id in it) {
|
||||
val device = this.devices[id]
|
||||
for (value in device.trackers.values) {
|
||||
if (value.status == TrackerStatus.OK) {
|
||||
value.status =
|
||||
TrackerStatus.DISCONNECTED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.devicesByHID.remove(hidDevice)
|
||||
|
||||
val oldConn = this.connByHID.remove(hidDevice)
|
||||
val serial = oldConn?.serialNumber ?: "Unknown"
|
||||
oldConn?.close()
|
||||
|
||||
LogManager.info("[TrackerServer] Linked HID device removed: $serial")
|
||||
}
|
||||
}
|
||||
|
||||
private fun dataRead() {
|
||||
synchronized(devicesByHID) {
|
||||
var devicesPresent = false
|
||||
var devicesDataReceived = false
|
||||
val q = intArrayOf(0, 0, 0, 0)
|
||||
val a = intArrayOf(0, 0, 0)
|
||||
val m = intArrayOf(0, 0, 0)
|
||||
for ((hidDevice, deviceList) in devicesByHID) {
|
||||
val dataReceived = ByteArray(64)
|
||||
val conn = connByHID[hidDevice]!!
|
||||
val dataRead = conn.deviceConnection.bulkTransfer(conn.endpointIn, dataReceived, dataReceived.size, 0)
|
||||
|
||||
// LogManager.info("[TrackerServer] HID data read ($dataRead bytes): ${dataReceived.contentToString()}")
|
||||
|
||||
devicesPresent = true // Even if the device has no data
|
||||
if (dataRead > 0) {
|
||||
// Process data
|
||||
// The data is always received as 64 bytes, this check no longer works
|
||||
if (dataRead % PACKET_SIZE != 0) {
|
||||
LogManager.info("[TrackerServer] Malformed HID packet, ignoring")
|
||||
continue // Don't continue with this data
|
||||
}
|
||||
devicesDataReceived = true // Data is received and is valid (not malformed)
|
||||
lastDataByHID[hidDevice] = 0 // reset last data received
|
||||
val packetCount = dataRead / PACKET_SIZE
|
||||
var i = 0
|
||||
while (i < packetCount * PACKET_SIZE) {
|
||||
// Common packet data
|
||||
val packetType = dataReceived[i].toUByte().toInt()
|
||||
val id = dataReceived[i + 1].toUByte().toInt()
|
||||
val deviceId = id
|
||||
|
||||
// Register device
|
||||
if (packetType == 255) { // device register packet from receiver
|
||||
val buffer = ByteBuffer.wrap(dataReceived, i + 2, 8)
|
||||
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN)
|
||||
val addr = buffer.getLong() and 0xFFFFFFFFFFFF
|
||||
val deviceName = String.format("%012X", addr)
|
||||
HIDCommon.deviceIdLookup(devices, hidDevice.serialNumber, deviceId, deviceName, deviceList) // register device
|
||||
// server wants tracker to be unique, so use combination of hid serial and full id
|
||||
i += PACKET_SIZE
|
||||
continue
|
||||
}
|
||||
|
||||
val device: HIDDevice? = HIDCommon.deviceIdLookup(devices, hidDevice.serialNumber, deviceId, null, deviceList)
|
||||
if (device == null) { // not registered yet
|
||||
i += PACKET_SIZE
|
||||
continue
|
||||
}
|
||||
|
||||
HIDCommon.processPacket(dataReceived, i, packetType, device, q, a, m, trackersConsumer)
|
||||
i += PACKET_SIZE
|
||||
}
|
||||
// LogManager.info("[TrackerServer] HID received $packetCount tracker packets")
|
||||
} else {
|
||||
lastDataByHID[hidDevice] = lastDataByHID[hidDevice]!! + 1 // increment last data received
|
||||
}
|
||||
}
|
||||
if (!devicesPresent) {
|
||||
sleep(10) // No hid device, "empty loop" so sleep to save the poor cpu
|
||||
} else if (!devicesDataReceived) {
|
||||
sleep(1) // read has no timeout, no data also causes an "empty loop"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deviceEnumerate(requestPermission: Boolean = false) {
|
||||
val hidDeviceList: MutableList<UsbDevice> = usbManager.deviceList.values.filter {
|
||||
it.vendorId == HID_TRACKER_RECEIVER_VID && it.productId == HID_TRACKER_RECEIVER_PID
|
||||
}.toMutableList()
|
||||
synchronized(devicesByHID) {
|
||||
// Work on devicesByHid and add/remove as necessary
|
||||
val removeList: MutableList<UsbDevice> = devicesByHID.keys.toMutableList()
|
||||
removeList.removeAll(hidDeviceList)
|
||||
for (device in removeList) {
|
||||
removeDevice(device)
|
||||
}
|
||||
|
||||
hidDeviceList.removeAll(devicesByHID.keys) // addList
|
||||
for (device in hidDeviceList) {
|
||||
// This will handle permission check/request
|
||||
checkConfigureDevice(device, requestPermission)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
val intentFilter = IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED)
|
||||
intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
|
||||
intentFilter.addAction(ACTION_USB_PERMISSION)
|
||||
|
||||
// Listen for USB device attach/detach
|
||||
ContextCompat.registerReceiver(
|
||||
context,
|
||||
usbReceiver,
|
||||
intentFilter,
|
||||
ContextCompat.RECEIVER_NOT_EXPORTED,
|
||||
)
|
||||
|
||||
// Enumerate existing devices
|
||||
deviceEnumerate(true)
|
||||
|
||||
// Data read loop
|
||||
while (true) {
|
||||
try {
|
||||
sleep(0) // Possible performance impact
|
||||
} catch (e: InterruptedException) {
|
||||
currentThread().interrupt()
|
||||
break
|
||||
}
|
||||
dataRead() // not in try catch?
|
||||
}
|
||||
}
|
||||
|
||||
fun getDevices(): List<Device> = devices
|
||||
|
||||
companion object {
|
||||
private const val resetSourceName = "TrackerServer"
|
||||
}
|
||||
}
|
||||
4
server/android/src/main/res/xml/device_filter.xml
Normal file
4
server/android/src/main/res/xml/device_filter.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<usb-device vendor-id="4617" product-id="30352" />
|
||||
</resources>
|
||||
@@ -34,7 +34,6 @@ import solarxr_protocol.rpc.*
|
||||
import kotlin.io.path.Path
|
||||
|
||||
class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeader>() {
|
||||
private var currTransactionId: Long = 0
|
||||
private val mainScope = CoroutineScope(SupervisorJob())
|
||||
|
||||
init {
|
||||
@@ -238,7 +237,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
val localIp = getLocalIp() ?: return
|
||||
val response = ServerInfosResponse
|
||||
.createServerInfosResponse(fbb, fbb.createString(localIp))
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.ServerInfosResponse, response)
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.ServerInfosResponse, response, messageHeader)
|
||||
fbb.finish(outbound)
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
@@ -251,7 +250,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
val config = api.server.configManager.vrConfig.overlay
|
||||
val response = OverlayDisplayModeResponse
|
||||
.createOverlayDisplayModeResponse(fbb, config.isVisible, config.isMirrored)
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.OverlayDisplayModeResponse, response)
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.OverlayDisplayModeResponse, response, messageHeader)
|
||||
fbb.finish(outbound)
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
@@ -283,7 +282,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
// might not be a good idea maybe let the client ask again
|
||||
val fbb = FlatBufferBuilder(300)
|
||||
val config = createSkeletonConfig(fbb, api.server.humanPoseManager)
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.SkeletonConfigResponse, config)
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.SkeletonConfigResponse, config, messageHeader)
|
||||
fbb.finish(outbound)
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
@@ -297,7 +296,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
|
||||
val fbb = FlatBufferBuilder(300)
|
||||
val config = createSkeletonConfig(fbb, api.server.humanPoseManager)
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.SkeletonConfigResponse, config)
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.SkeletonConfigResponse, config, messageHeader)
|
||||
fbb.finish(outbound)
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
@@ -332,7 +331,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
val fbb = FlatBufferBuilder(40)
|
||||
val status = RecordBVHStatus
|
||||
.createRecordBVHStatus(fbb, api.server.bvhRecorder.isRecording)
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.RecordBVHStatus, status)
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.RecordBVHStatus, status, messageHeader)
|
||||
fbb.finish(outbound)
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
@@ -343,7 +342,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
val fbb = FlatBufferBuilder(40)
|
||||
val status = RecordBVHStatus
|
||||
.createRecordBVHStatus(fbb, api.server.bvhRecorder.isRecording)
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.RecordBVHStatus, status)
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.RecordBVHStatus, status, messageHeader)
|
||||
fbb.finish(outbound)
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
@@ -495,13 +494,16 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
}
|
||||
}
|
||||
|
||||
fun createRPCMessage(fbb: FlatBufferBuilder, messageType: Byte, messageOffset: Int): Int {
|
||||
@JvmOverloads
|
||||
fun createRPCMessage(fbb: FlatBufferBuilder, messageType: Byte, messageOffset: Int, respondTo: RpcMessageHeader? = null): Int {
|
||||
val data = IntArray(1)
|
||||
|
||||
RpcMessageHeader.startRpcMessageHeader(fbb)
|
||||
RpcMessageHeader.addMessage(fbb, messageOffset)
|
||||
RpcMessageHeader.addMessageType(fbb, messageType)
|
||||
RpcMessageHeader.addTxId(fbb, TransactionId.createTransactionId(fbb, currTransactionId++))
|
||||
respondTo?.txId()?.let { txId ->
|
||||
RpcMessageHeader.addTxId(fbb, TransactionId.createTransactionId(fbb, txId.id()))
|
||||
}
|
||||
data[0] = RpcMessageHeader.endRpcMessageHeader(fbb)
|
||||
|
||||
val messages = MessageBundle.createRpcMsgsVector(fbb, data)
|
||||
@@ -525,7 +527,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
val response = StatusSystemResponseT()
|
||||
response.currentStatuses = statuses
|
||||
val offset = StatusSystemResponse.pack(fbb, response)
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.StatusSystemResponse, offset)
|
||||
val outbound = this.createRPCMessage(fbb, RpcMessage.StatusSystemResponse, offset, messageHeader)
|
||||
fbb.finish(outbound)
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
@@ -557,7 +559,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
0f,
|
||||
)
|
||||
}
|
||||
fbb.finish(createRPCMessage(fbb, RpcMessage.HeightResponse, response))
|
||||
fbb.finish(createRPCMessage(fbb, RpcMessage.HeightResponse, response, messageHeader))
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
|
||||
@@ -572,7 +574,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
0,
|
||||
api.server.configManager.vrConfig.server.useMagnetometerOnAllTrackers,
|
||||
)
|
||||
fbb.finish(createRPCMessage(fbb, RpcMessage.MagToggleResponse, response))
|
||||
fbb.finish(createRPCMessage(fbb, RpcMessage.MagToggleResponse, response, messageHeader))
|
||||
conn.send(fbb.dataBuffer())
|
||||
return
|
||||
}
|
||||
@@ -584,7 +586,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
trackerId,
|
||||
tracker.config.shouldHaveMagEnabled == true,
|
||||
)
|
||||
fbb.finish(createRPCMessage(fbb, RpcMessage.MagToggleResponse, response))
|
||||
fbb.finish(createRPCMessage(fbb, RpcMessage.MagToggleResponse, response, messageHeader))
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
|
||||
@@ -604,7 +606,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
0,
|
||||
api.server.configManager.vrConfig.server.useMagnetometerOnAllTrackers,
|
||||
)
|
||||
fbb.finish(createRPCMessage(fbb, RpcMessage.MagToggleResponse, response))
|
||||
fbb.finish(createRPCMessage(fbb, RpcMessage.MagToggleResponse, response, messageHeader))
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
return
|
||||
@@ -623,7 +625,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
trackerId,
|
||||
state,
|
||||
)
|
||||
fbb.finish(createRPCMessage(fbb, RpcMessage.MagToggleResponse, response))
|
||||
fbb.finish(createRPCMessage(fbb, RpcMessage.MagToggleResponse, response, messageHeader))
|
||||
conn.send(fbb.dataBuffer())
|
||||
return
|
||||
}
|
||||
@@ -640,7 +642,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
trackerId,
|
||||
state,
|
||||
)
|
||||
fbb.finish(createRPCMessage(fbb, RpcMessage.MagToggleResponse, response))
|
||||
fbb.finish(createRPCMessage(fbb, RpcMessage.MagToggleResponse, response, messageHeader))
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
}
|
||||
@@ -660,7 +662,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
|
||||
configManager.saveConfig()
|
||||
|
||||
sendSettingsChangedResponse(conn)
|
||||
sendSettingsChangedResponse(conn, messageHeader)
|
||||
}
|
||||
|
||||
private fun onDetectStayAlignedRelaxedPoseRequest(conn: GenericConnection, messageHeader: RpcMessageHeader) {
|
||||
@@ -692,7 +694,7 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
|
||||
LogManager.info("[detectStayAlignedRelaxedPose] pose=$pose $relaxedPose")
|
||||
|
||||
sendSettingsChangedResponse(conn)
|
||||
sendSettingsChangedResponse(conn, messageHeader)
|
||||
}
|
||||
|
||||
private fun onResetStayAlignedRelaxedPoseRequest(conn: GenericConnection, messageHeader: RpcMessageHeader) {
|
||||
@@ -720,13 +722,13 @@ class RPCHandler(private val api: ProtocolAPI) : ProtocolHandler<RpcMessageHeade
|
||||
|
||||
LogManager.info("[resetStayAlignedRelaxedPose] pose=$pose")
|
||||
|
||||
sendSettingsChangedResponse(conn)
|
||||
sendSettingsChangedResponse(conn, messageHeader)
|
||||
}
|
||||
|
||||
fun sendSettingsChangedResponse(conn: GenericConnection) {
|
||||
fun sendSettingsChangedResponse(conn: GenericConnection, messageHeader: RpcMessageHeader?) {
|
||||
val fbb = FlatBufferBuilder(32)
|
||||
val settings = RPCSettingsBuilder.createSettingsResponse(fbb, api.server)
|
||||
val outbound = createRPCMessage(fbb, RpcMessage.SettingsResponse, settings)
|
||||
val outbound = createRPCMessage(fbb, RpcMessage.SettingsResponse, settings, messageHeader)
|
||||
fbb.finish(outbound)
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class RPCSettingsHandler(var rpcHandler: RPCHandler, var api: ProtocolAPI) {
|
||||
}
|
||||
|
||||
fun onSettingsRequest(conn: GenericConnection, messageHeader: RpcMessageHeader?) {
|
||||
rpcHandler.sendSettingsChangedResponse(conn)
|
||||
rpcHandler.sendSettingsChangedResponse(conn, messageHeader)
|
||||
}
|
||||
|
||||
fun onChangeSettingsRequest(conn: GenericConnection?, messageHeader: RpcMessageHeader) {
|
||||
|
||||
@@ -28,16 +28,16 @@ class RPCTrackingPause(private val rpcHandler: RPCHandler, private val api: Prot
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPauseStateResponse(trackingPaused: Boolean): ByteBuffer {
|
||||
private fun getPauseStateResponse(trackingPaused: Boolean, messageHeader: RpcMessageHeader? = null): ByteBuffer {
|
||||
val fbb = FlatBufferBuilder(32)
|
||||
val state = TrackingPauseStateResponse.createTrackingPauseStateResponse(fbb, trackingPaused)
|
||||
val outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.TrackingPauseStateResponse, state)
|
||||
val outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.TrackingPauseStateResponse, state, messageHeader)
|
||||
fbb.finish(outbound)
|
||||
return fbb.dataBuffer()
|
||||
}
|
||||
|
||||
private fun onTrackingPauseStateRequest(conn: GenericConnection, messageHeader: RpcMessageHeader) {
|
||||
conn.send(getPauseStateResponse(currentPauseState))
|
||||
conn.send(getPauseStateResponse(currentPauseState, messageHeader))
|
||||
}
|
||||
|
||||
override fun onTrackingPause(trackingPaused: Boolean) {
|
||||
|
||||
@@ -52,11 +52,13 @@ class Constraint(
|
||||
if (constraintType == ConstraintType.COMPLETE) return thisBone.getGlobalRotation()
|
||||
|
||||
// If there is no parent and this is not a complete constraint accept the rotation as is.
|
||||
if (thisBone.parent == null) return rotation
|
||||
// TODO: This was changed due to a race condition with the RPC thread, see
|
||||
// https://github.com/SlimeVR/SlimeVR-Server/issues/1534 for more information.
|
||||
val parent = thisBone.parent ?: return rotation
|
||||
|
||||
val localRotation = getLocalRotation(rotation, thisBone)
|
||||
val localRotation = getLocalRotation(rotation, thisBone, parent)
|
||||
val constrainedRotation = constraintFunction(localRotation, swingRad, twistRad, allowedDeviationRad)
|
||||
return getWorldRotationFromLocal(constrainedRotation, thisBone)
|
||||
return getWorldRotationFromLocal(constrainedRotation, thisBone, parent)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,14 +92,12 @@ class Constraint(
|
||||
ConstraintType.LOOSE_HINGE -> looseHingeConstraint
|
||||
}
|
||||
|
||||
private fun getLocalRotation(rotation: Quaternion, thisBone: Bone): Quaternion {
|
||||
val parent = thisBone.parent!!
|
||||
private fun getLocalRotation(rotation: Quaternion, thisBone: Bone, parent: Bone): Quaternion {
|
||||
val localRotationOffset = parent.rotationOffset.inv() * thisBone.rotationOffset
|
||||
return (parent.getGlobalRotation() * localRotationOffset).inv() * rotation
|
||||
}
|
||||
|
||||
private fun getWorldRotationFromLocal(rotation: Quaternion, thisBone: Bone): Quaternion {
|
||||
val parent = thisBone.parent!!
|
||||
private fun getWorldRotationFromLocal(rotation: Quaternion, thisBone: Bone, parent: Bone): Quaternion {
|
||||
val localRotationOffset = parent.rotationOffset.inv() * thisBone.rotationOffset
|
||||
return (parent.getGlobalRotation() * localRotationOffset * rotation).unit()
|
||||
}
|
||||
|
||||
@@ -650,26 +650,30 @@ class LegTweaks(private val skeleton: HumanSkeleton) {
|
||||
var weightR = getFootPlantWeight(rightFootPosition)
|
||||
|
||||
// if foot trackers exist add to the weights
|
||||
val leftFootYaw = isolateYaw(leftFootRotation)
|
||||
if (leftFootTracker) {
|
||||
weightL *= getRotationalDistanceToPlant(
|
||||
leftFootRotation,
|
||||
leftFootYaw,
|
||||
)
|
||||
}
|
||||
val rightFootYaw = isolateYaw(rightFootRotation)
|
||||
if (rightFootTracker) {
|
||||
weightR *= getRotationalDistanceToPlant(
|
||||
rightFootRotation,
|
||||
rightFootYaw,
|
||||
)
|
||||
}
|
||||
|
||||
// perform the correction
|
||||
leftFootRotation = leftFootRotation
|
||||
.interpR(
|
||||
isolateYaw(leftFootRotation),
|
||||
leftFootYaw,
|
||||
weightL * masterWeightL,
|
||||
)
|
||||
rightFootRotation = rightFootRotation
|
||||
.interpR(
|
||||
isolateYaw(rightFootRotation),
|
||||
rightFootYaw,
|
||||
weightR * masterWeightR,
|
||||
)
|
||||
}
|
||||
@@ -805,8 +809,7 @@ class LegTweaks(private val skeleton: HumanSkeleton) {
|
||||
}
|
||||
|
||||
// returns the amount to slerp for foot plant when foot trackers are active
|
||||
private fun getRotationalDistanceToPlant(footRot: Quaternion): Float {
|
||||
val footRotYaw: Quaternion = isolateYaw(footRot)
|
||||
private fun getRotationalDistanceToPlant(footRot: Quaternion, footRotYaw: Quaternion): Float {
|
||||
var angle = footRot.angleToR(footRotYaw)
|
||||
angle = (angle / (2 * Math.PI)).toFloat()
|
||||
angle = FastMath.clamp(
|
||||
@@ -1005,12 +1008,9 @@ class LegTweaks(private val skeleton: HumanSkeleton) {
|
||||
}
|
||||
}
|
||||
|
||||
// remove the x and z components of the given quaternion
|
||||
private fun isolateYaw(quaternion: Quaternion): Quaternion = Quaternion(
|
||||
quaternion.w,
|
||||
0f,
|
||||
quaternion.y,
|
||||
0f,
|
||||
// isolate the euler yaw component of a given quaternion
|
||||
private fun isolateYaw(quaternion: Quaternion): Quaternion = Quaternion.rotationAroundYAxis(
|
||||
quaternion.toEulerAngles(EulerOrder.YZX).y,
|
||||
)
|
||||
|
||||
// return a quaternion that has been rotated by the new pitch amount
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
package dev.slimevr.tracking.trackers.hid
|
||||
|
||||
import com.jme3.math.FastMath
|
||||
import dev.slimevr.VRServer
|
||||
import dev.slimevr.tracking.trackers.Tracker
|
||||
import dev.slimevr.tracking.trackers.TrackerStatus
|
||||
import dev.slimevr.tracking.trackers.udp.BoardType
|
||||
import dev.slimevr.tracking.trackers.udp.IMUType
|
||||
import dev.slimevr.tracking.trackers.udp.MCUType
|
||||
import dev.slimevr.tracking.trackers.udp.MagnetometerStatus
|
||||
import io.eiren.util.logging.LogManager
|
||||
import io.github.axisangles.ktmath.Quaternion
|
||||
import io.github.axisangles.ktmath.Quaternion.Companion.fromRotationVector
|
||||
import io.github.axisangles.ktmath.Vector3
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.function.Consumer
|
||||
import kotlin.collections.set
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* A collection of shared HID functions between OS specific HID implementations.
|
||||
*/
|
||||
class HIDCommon {
|
||||
companion object {
|
||||
const val HID_TRACKER_RECEIVER_VID = 0x1209
|
||||
const val HID_TRACKER_RECEIVER_PID = 0x7690
|
||||
|
||||
const val PACKET_SIZE = 16
|
||||
|
||||
private val AXES_OFFSET = fromRotationVector(-FastMath.HALF_PI, 0f, 0f)
|
||||
|
||||
fun deviceIdLookup(
|
||||
hidDevices: MutableList<HIDDevice>,
|
||||
hidSerialNumber: String?,
|
||||
deviceId: Int,
|
||||
deviceName: String? = null,
|
||||
deviceList: MutableList<Int>,
|
||||
): HIDDevice? {
|
||||
synchronized(hidDevices) {
|
||||
deviceList.map { hidDevices[it] }.find { it.hidId == deviceId }?.let { return it }
|
||||
if (deviceName == null) { // not registered yet
|
||||
return null
|
||||
}
|
||||
val device = HIDDevice(deviceId)
|
||||
// server wants tracker to be unique, so use combination of hid serial and full id // TODO: use the tracker "address" instead
|
||||
// TODO: the server should not setup any device, only when the receiver associates the id with the tracker "address" and sends this packet (0xff?) which it will do occasionally
|
||||
// device.name = hidDevice.serialNumber ?: "Unknown HID Device"
|
||||
// device.name += "-$deviceId"
|
||||
device.name = deviceName
|
||||
device.manufacturer = "HID Device" // TODO:
|
||||
// device.manufacturer = hidDevice.manufacturer ?: "HID Device"
|
||||
// device.hardwareIdentifier = hidDevice.serialNumber // hardwareIdentifier is not used to identify the tracker, so also display the receiver serial
|
||||
// device.hardwareIdentifier += "-$deviceId/$deviceName" // receiver serial + assigned id in receiver + device address
|
||||
device.hardwareIdentifier = deviceName // the rest of identifier wont fit in gui
|
||||
hidDevices.add(device)
|
||||
deviceList.add(hidDevices.size - 1)
|
||||
VRServer.instance.deviceManager.addDevice(device) // actually add device to the server
|
||||
LogManager
|
||||
.info(
|
||||
"[TrackerServer] Added device $deviceName for ${hidSerialNumber ?: "Unknown HID Device"}, id $deviceId",
|
||||
)
|
||||
return device
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpSensor(
|
||||
device: HIDDevice,
|
||||
trackerId: Int,
|
||||
sensorType: IMUType,
|
||||
sensorStatus: TrackerStatus,
|
||||
magStatus: MagnetometerStatus,
|
||||
trackersConsumer: Consumer<Tracker>,
|
||||
) {
|
||||
// LogManager.info("[TrackerServer] Sensor $trackerId for ${device.name}, status $sensorStatus")
|
||||
var imuTracker = device.getTracker(trackerId)
|
||||
if (imuTracker == null) {
|
||||
var formattedHWID = device.hardwareIdentifier.replace(":", "").takeLast(5)
|
||||
imuTracker = Tracker(
|
||||
device,
|
||||
VRServer.getNextLocalTrackerId(),
|
||||
device.name + "/" + trackerId,
|
||||
"Tracker $formattedHWID",
|
||||
null,
|
||||
trackerNum = trackerId,
|
||||
hasRotation = true,
|
||||
hasAcceleration = true,
|
||||
userEditable = true,
|
||||
imuType = sensorType,
|
||||
allowFiltering = true,
|
||||
needsReset = true,
|
||||
needsMounting = true,
|
||||
usesTimeout = false,
|
||||
magStatus = magStatus,
|
||||
)
|
||||
// usesTimeout false because HID trackers aren't "Disconnected" unless receiver is physically removed probably
|
||||
// TODO: Could tracker maybe use "Timed out" status without marking as disconnecting?
|
||||
// TODO: can be marked as "Disconnected" by timeout if the tracker has enabled activity timeouts
|
||||
device.trackers[trackerId] = imuTracker
|
||||
trackersConsumer.accept(imuTracker)
|
||||
imuTracker.status = sensorStatus
|
||||
LogManager
|
||||
.info(
|
||||
"[TrackerServer] Added sensor $trackerId for ${device.name}, type $sensorType",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun processPacket(
|
||||
dataReceived: ByteArray,
|
||||
i: Int,
|
||||
packetType: Int,
|
||||
device: HIDDevice,
|
||||
q: IntArray,
|
||||
a: IntArray,
|
||||
m: IntArray,
|
||||
trackersConsumer: Consumer<Tracker>,
|
||||
) {
|
||||
val trackerId = 0 // no concept of extensions
|
||||
|
||||
// Register tracker
|
||||
if (packetType == 0) { // Tracker register packet (device info)
|
||||
val imu_id = dataReceived[i + 8].toUByte().toInt()
|
||||
val mag_id = dataReceived[i + 9].toUByte().toInt()
|
||||
val sensorType = IMUType.getById(imu_id.toUInt())
|
||||
// only able to register magnetometer status, not magnetometer type
|
||||
val magStatus = MagnetometerStatus.getById(mag_id.toUByte())
|
||||
if (sensorType != null && magStatus != null) {
|
||||
setUpSensor(device, trackerId, sensorType, TrackerStatus.OK, magStatus, trackersConsumer)
|
||||
}
|
||||
}
|
||||
|
||||
val tracker: Tracker? = device.getTracker(trackerId)
|
||||
if (tracker == null) { // not registered yet
|
||||
return
|
||||
}
|
||||
|
||||
// Packet data
|
||||
var batt: Int? = null
|
||||
var batt_v: Int? = null
|
||||
var temp: Int? = null
|
||||
var brd_id: Int? = null
|
||||
var mcu_id: Int? = null
|
||||
// var imu_id: Int? = null
|
||||
// var mag_id: Int? = null
|
||||
var fw_date: Int? = null
|
||||
var fw_major: Int? = null
|
||||
var fw_minor: Int? = null
|
||||
var fw_patch: Int? = null
|
||||
var svr_status: Int? = null
|
||||
// var status: Int? = null // raw status from tracker
|
||||
var rssi: Int? = null
|
||||
|
||||
// Tracker packets
|
||||
when (packetType) {
|
||||
0 -> { // device info
|
||||
batt = dataReceived[i + 2].toUByte().toInt()
|
||||
batt_v = dataReceived[i + 3].toUByte().toInt()
|
||||
temp = dataReceived[i + 4].toUByte().toInt()
|
||||
brd_id = dataReceived[i + 5].toUByte().toInt()
|
||||
mcu_id = dataReceived[i + 6].toUByte().toInt()
|
||||
// imu_id = dataReceived[i + 8].toUByte().toInt()
|
||||
// mag_id = dataReceived[i + 9].toUByte().toInt()
|
||||
// ushort little endian
|
||||
fw_date = dataReceived[i + 11].toUByte().toInt() shl 8 or dataReceived[i + 10].toUByte().toInt()
|
||||
fw_major = dataReceived[i + 12].toUByte().toInt()
|
||||
fw_minor = dataReceived[i + 13].toUByte().toInt()
|
||||
fw_patch = dataReceived[i + 14].toUByte().toInt()
|
||||
rssi = dataReceived[i + 15].toUByte().toInt()
|
||||
}
|
||||
|
||||
1 -> { // full precision quat and accel, no extra data
|
||||
// Q15: 1 is represented as 0x7FFF, -1 as 0x8000
|
||||
// The sender can use integer saturation to avoid overflow
|
||||
for (j in 0..3) { // quat received as fixed Q15
|
||||
// Q15 as short little endian
|
||||
q[j] = dataReceived[i + 2 + j * 2 + 1].toInt() shl 8 or dataReceived[i + 2 + j * 2].toUByte().toInt()
|
||||
}
|
||||
for (j in 0..2) { // accel received as fixed 7, in m/s^2
|
||||
// Q7 as short little endian
|
||||
a[j] = dataReceived[i + 10 + j * 2 + 1].toInt() shl 8 or dataReceived[i + 10 + j * 2].toUByte().toInt()
|
||||
}
|
||||
}
|
||||
|
||||
2 -> { // reduced precision quat and accel with data
|
||||
batt = dataReceived[i + 2].toUByte().toInt()
|
||||
batt_v = dataReceived[i + 3].toUByte().toInt()
|
||||
temp = dataReceived[i + 4].toUByte().toInt()
|
||||
// quaternion is quantized as exponential map
|
||||
// X = 10 bits, Y/Z = 11 bits
|
||||
val buffer = ByteBuffer.wrap(dataReceived, i + 5, 4)
|
||||
buffer.order(java.nio.ByteOrder.LITTLE_ENDIAN)
|
||||
val q_buf = buffer.getInt().toUInt()
|
||||
q[0] = (q_buf and 1023u).toInt()
|
||||
q[1] = (q_buf shr 10 and 2047u).toInt()
|
||||
q[2] = (q_buf shr 21 and 2047u).toInt()
|
||||
for (j in 0..2) { // accel received as fixed 7, in m/s^2
|
||||
// Q7 as short little endian
|
||||
a[j] = dataReceived[i + 9 + j * 2 + 1].toInt() shl 8 or dataReceived[i + 9 + j * 2].toUByte().toInt()
|
||||
}
|
||||
rssi = dataReceived[i + 15].toUByte().toInt()
|
||||
}
|
||||
|
||||
3 -> { // status
|
||||
svr_status = dataReceived[i + 2].toUByte().toInt()
|
||||
// status = dataReceived[i + 3].toUByte().toInt()
|
||||
rssi = dataReceived[i + 15].toUByte().toInt()
|
||||
}
|
||||
|
||||
4 -> { // full precision quat and mag, no extra data
|
||||
for (j in 0..3) { // quat received as fixed Q15
|
||||
// Q15 as short little endian
|
||||
q[j] = dataReceived[i + 2 + j * 2 + 1].toInt() shl 8 or dataReceived[i + 2 + j * 2].toUByte().toInt()
|
||||
}
|
||||
for (j in 0..2) { // mag received as fixed 10, in gauss
|
||||
// Q10 as short little endian
|
||||
m[j] = dataReceived[i + 10 + j * 2 + 1].toInt() shl 8 or dataReceived[i + 10 + j * 2].toUByte().toInt()
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
|
||||
// Assign data
|
||||
if (batt != null) {
|
||||
tracker.batteryLevel = if (batt == 128) 1f else (batt and 127).toFloat()
|
||||
}
|
||||
// Server still won't display battery at 0% at all
|
||||
if (batt_v != null) {
|
||||
tracker.batteryVoltage = (batt_v.toFloat() + 245f) / 100f
|
||||
}
|
||||
if (temp != null) {
|
||||
tracker.temperature = if (temp > 0) temp.toFloat() / 2f - 39f else null
|
||||
}
|
||||
// Range 1 - 255 -> -38.5 - +88.5 C
|
||||
if (brd_id != null) {
|
||||
val boardType = BoardType.getById(brd_id.toUInt())
|
||||
if (boardType != null) {
|
||||
device.boardType = boardType!!
|
||||
}
|
||||
}
|
||||
if (mcu_id != null) {
|
||||
val mcuType = MCUType.getById(mcu_id.toUInt())
|
||||
if (mcuType != null) {
|
||||
device.mcuType = mcuType!!
|
||||
}
|
||||
}
|
||||
if (fw_date != null && fw_major != null && fw_minor != null && fw_patch != null) {
|
||||
val firmwareYear = 2020 + (fw_date shr 9 and 127)
|
||||
val firmwareMonth = fw_date shr 5 and 15
|
||||
val firmwareDay = fw_date and 31
|
||||
val firmwareDate = String.format("%04d-%02d-%02d", firmwareYear, firmwareMonth, firmwareDay)
|
||||
device.firmwareVersion = "$fw_major.$fw_minor.$fw_patch (Build $firmwareDate)"
|
||||
}
|
||||
if (svr_status != null) {
|
||||
val status = TrackerStatus.getById(svr_status)
|
||||
if (status != null) {
|
||||
tracker.status = status!!
|
||||
}
|
||||
}
|
||||
if (rssi != null) {
|
||||
tracker.signalStrength = -rssi
|
||||
}
|
||||
|
||||
// Assign rotation and acceleration
|
||||
if (packetType == 1 || packetType == 4) {
|
||||
// The data comes in the same order as in the UDP protocol
|
||||
// x y z w -> w x y z
|
||||
var rot = Quaternion(q[3].toFloat(), q[0].toFloat(), q[1].toFloat(), q[2].toFloat())
|
||||
val scaleRot = 1 / (1 shl 15).toFloat() // compile time evaluation
|
||||
rot = AXES_OFFSET.times(scaleRot).times(rot) // no division
|
||||
tracker.setRotation(rot)
|
||||
}
|
||||
if (packetType == 2) {
|
||||
val v = floatArrayOf(q[0].toFloat(), q[1].toFloat(), q[2].toFloat()) // used q array for quantized data
|
||||
v[0] /= (1 shl 10).toFloat()
|
||||
v[1] /= (1 shl 11).toFloat()
|
||||
v[2] /= (1 shl 11).toFloat()
|
||||
for (i in 0..2) {
|
||||
v[i] = v[i] * 2 - 1
|
||||
}
|
||||
// http://marc-b-reynolds.github.io/quaternions/2017/05/02/QuatQuantPart1.html#fnref:pos:3
|
||||
// https://github.com/Marc-B-Reynolds/Stand-alone-junk/blob/559bd78893a3a95cdee1845834c632141b945a45/src/Posts/quatquant0.c#L898
|
||||
val d = v[0] * v[0] + v[1] * v[1] + v[2] * v[2]
|
||||
val invSqrtD = 1 / sqrt(d + 1e-6f)
|
||||
val a = (PI.toFloat() / 2) * d * invSqrtD
|
||||
val s = sin(a)
|
||||
val k = s * invSqrtD
|
||||
var rot = Quaternion(cos(a), k * v[0], k * v[1], k * v[2])
|
||||
rot = AXES_OFFSET.times(rot) // no division
|
||||
tracker.setRotation(rot)
|
||||
}
|
||||
if (packetType == 1 || packetType == 2) {
|
||||
// Acceleration is in local device frame
|
||||
// On flat surface / face up:
|
||||
// Right side of the device is +X
|
||||
// Front side (facing up) is +Z
|
||||
// Mounted on body / standing up:
|
||||
// Top side of the device is +Y
|
||||
// Front side (facing out) is +Z
|
||||
val scaleAccel = 1 / (1 shl 7).toFloat() // compile time evaluation
|
||||
val acceleration = Vector3(a[0].toFloat(), a[1].toFloat(), a[2].toFloat()).times(scaleAccel) // no division
|
||||
tracker.setAcceleration(acceleration)
|
||||
}
|
||||
if (packetType == 4) {
|
||||
// Magnetometer is in local device frame
|
||||
// On flat surface / face up:
|
||||
// Right side of the device is +X
|
||||
// Front side (facing up) is +Z
|
||||
// Mounted on body / standing up:
|
||||
// Top side of the device is +Y
|
||||
// Front side (facing out) is +Z
|
||||
val scaleMag = 1000 / (1 shl 10).toFloat() // compile time evaluation, and change gauss to milligauss
|
||||
val magnetometer = Vector3(m[0].toFloat(), m[1].toFloat(), m[2].toFloat()).times(scaleMag) // no division
|
||||
tracker.setMagVector(magnetometer)
|
||||
}
|
||||
if (packetType == 1 || packetType == 2 || packetType == 4) {
|
||||
tracker.dataTick() // only data tick if there is rotation data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package dev.slimevr.desktop.tracking.trackers.hid
|
||||
package dev.slimevr.tracking.trackers.hid
|
||||
|
||||
import dev.slimevr.tracking.trackers.Device
|
||||
import dev.slimevr.tracking.trackers.Tracker
|
||||
@@ -13,7 +13,7 @@ import dev.slimevr.desktop.platform.linux.UnixSocketBridge
|
||||
import dev.slimevr.desktop.platform.linux.UnixSocketRpcBridge
|
||||
import dev.slimevr.desktop.platform.windows.WindowsNamedPipeBridge
|
||||
import dev.slimevr.desktop.serial.DesktopSerialHandler
|
||||
import dev.slimevr.desktop.tracking.trackers.hid.TrackersHID
|
||||
import dev.slimevr.desktop.tracking.trackers.hid.DesktopHIDManager
|
||||
import dev.slimevr.tracking.trackers.Tracker
|
||||
import io.eiren.util.OperatingSystem
|
||||
import io.eiren.util.collections.FastList
|
||||
@@ -132,7 +132,7 @@ fun main(args: Array<String>) {
|
||||
NetworkProfileChecker(vrServer)
|
||||
|
||||
// Start service for USB HID trackers
|
||||
TrackersHID(
|
||||
DesktopHIDManager(
|
||||
"Sensors HID service",
|
||||
) { tracker: Tracker -> vrServer.registerTracker(tracker) }
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user