diff --git a/.github/workflows/gradle.yaml b/.github/workflows/gradle.yaml
index b6ac3309da..504430f109 100644
--- a/.github/workflows/gradle.yaml
+++ b/.github/workflows/gradle.yaml
@@ -143,6 +143,51 @@ jobs:
files: |
./SlimeVR-android.apk
+ bundle-ios:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Get tags
+ run: git fetch --tags origin --recurse-submodules=no --force
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: "17"
+ distribution: "adopt"
+
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v4
+
+ - uses: pnpm/action-setup@v4
+ - name: Use Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version-file: '.node-version'
+ cache: 'pnpm'
+
+ - run: pnpm i
+
+ - name: Build GUI
+ env:
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+ NODE_OPTIONS: --max-old-space-size=4096
+ run: cd gui && pnpm run build
+
+ - name: Build with Gradle
+ run: ./gradlew :server:ios:createIPA
+
+ - name: Upload the iOS Build Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ # Artifact name
+ name: "SlimeVR-iOS" # optional, default is artifact
+ # A file, directory or wildcard pattern that describes what to upload
+ path: server/ios/build/robovmx-build/tmp/Main.ipa
+
bundle-linux:
strategy:
matrix:
diff --git a/gradle.properties b/gradle.properties
index da78dfd873..42ef338e1f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -18,3 +18,4 @@ spotlessVersion=7.0.2
shadowJarVersion=8.3.2
buildconfigVersion=5.5.0
grgitVersion=5.2.2
+robovmVersion=10.2.2.4-SNAPSHOT
diff --git a/gui/src-tauri/icons/icon.icns b/gui/src-tauri/icons/icon.icns
index 753e44c34c..3ea09b45dd 100644
Binary files a/gui/src-tauri/icons/icon.icns and b/gui/src-tauri/icons/icon.icns differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-20x20@1x.png b/gui/src-tauri/icons/ios/AppIcon-20x20@1x.png
index 383c253527..d842573041 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-20x20@1x.png and b/gui/src-tauri/icons/ios/AppIcon-20x20@1x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/gui/src-tauri/icons/ios/AppIcon-20x20@2x-1.png
index 28f720c69a..c5b356d2f8 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-20x20@2x-1.png and b/gui/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-20x20@2x.png b/gui/src-tauri/icons/ios/AppIcon-20x20@2x.png
index 28f720c69a..c5b356d2f8 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-20x20@2x.png and b/gui/src-tauri/icons/ios/AppIcon-20x20@2x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-20x20@3x.png b/gui/src-tauri/icons/ios/AppIcon-20x20@3x.png
index 72267593bb..3d49086535 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-20x20@3x.png and b/gui/src-tauri/icons/ios/AppIcon-20x20@3x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-29x29@1x.png b/gui/src-tauri/icons/ios/AppIcon-29x29@1x.png
index 3582cf6ba7..258d802ca4 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-29x29@1x.png and b/gui/src-tauri/icons/ios/AppIcon-29x29@1x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/gui/src-tauri/icons/ios/AppIcon-29x29@2x-1.png
index 26a9a81efb..37ae061bad 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-29x29@2x-1.png and b/gui/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-29x29@2x.png b/gui/src-tauri/icons/ios/AppIcon-29x29@2x.png
index 26a9a81efb..37ae061bad 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-29x29@2x.png and b/gui/src-tauri/icons/ios/AppIcon-29x29@2x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-29x29@3x.png b/gui/src-tauri/icons/ios/AppIcon-29x29@3x.png
index 0aa9c6842c..5d85316ce5 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-29x29@3x.png and b/gui/src-tauri/icons/ios/AppIcon-29x29@3x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-40x40@1x.png b/gui/src-tauri/icons/ios/AppIcon-40x40@1x.png
index 28f720c69a..c5b356d2f8 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-40x40@1x.png and b/gui/src-tauri/icons/ios/AppIcon-40x40@1x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/gui/src-tauri/icons/ios/AppIcon-40x40@2x-1.png
index 9f3286ca2f..cfc94a0e7b 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-40x40@2x-1.png and b/gui/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-40x40@2x.png b/gui/src-tauri/icons/ios/AppIcon-40x40@2x.png
index 9f3286ca2f..cfc94a0e7b 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-40x40@2x.png and b/gui/src-tauri/icons/ios/AppIcon-40x40@2x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-40x40@3x.png b/gui/src-tauri/icons/ios/AppIcon-40x40@3x.png
index 21f126cf16..28bb14ec3c 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-40x40@3x.png and b/gui/src-tauri/icons/ios/AppIcon-40x40@3x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-512@2x.png b/gui/src-tauri/icons/ios/AppIcon-512@2x.png
index 204f8fa30a..4471a53300 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-512@2x.png and b/gui/src-tauri/icons/ios/AppIcon-512@2x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-60x60@2x.png b/gui/src-tauri/icons/ios/AppIcon-60x60@2x.png
index 21f126cf16..28bb14ec3c 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-60x60@2x.png and b/gui/src-tauri/icons/ios/AppIcon-60x60@2x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-60x60@3x.png b/gui/src-tauri/icons/ios/AppIcon-60x60@3x.png
index a8390e3161..6916323ca5 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-60x60@3x.png and b/gui/src-tauri/icons/ios/AppIcon-60x60@3x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-76x76@1x.png b/gui/src-tauri/icons/ios/AppIcon-76x76@1x.png
index 5486ec449b..19e5a1f2b8 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-76x76@1x.png and b/gui/src-tauri/icons/ios/AppIcon-76x76@1x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-76x76@2x.png b/gui/src-tauri/icons/ios/AppIcon-76x76@2x.png
index 84497bb0a6..548f993305 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-76x76@2x.png and b/gui/src-tauri/icons/ios/AppIcon-76x76@2x.png differ
diff --git a/gui/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/gui/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
index c20b91316a..9a9640e238 100644
Binary files a/gui/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png and b/gui/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ
diff --git a/gui/src-tauri/icons/ios/Contents.json b/gui/src-tauri/icons/ios/Contents.json
new file mode 100644
index 0000000000..9664c57335
--- /dev/null
+++ b/gui/src-tauri/icons/ios/Contents.json
@@ -0,0 +1,116 @@
+{
+ "images" : [
+ {
+ "filename" : "AppIcon-20x20@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-20x20@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-29x29@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-29x29@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-40x40@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-40x40@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-60x60@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "AppIcon-60x60@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "AppIcon-20x20@2x 1.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-29x29@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-29x29@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-40x40@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-40x40@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-76x76@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "AppIcon-76x76@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "AppIcon-83.5x83.5@2x 1.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "filename" : "AppIcon-512@2x 1.png",
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ },
+ {
+ "filename" : "AppIcon-20x20@1x.png",
+ "idiom" : "iphone",
+ "scale" : "1x",
+ "size" : "20x20"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/gui/src/components/EmptyLayout.tsx b/gui/src/components/EmptyLayout.tsx
index 443ea60a8c..423ed820a3 100644
--- a/gui/src/components/EmptyLayout.tsx
+++ b/gui/src/components/EmptyLayout.tsx
@@ -1,11 +1,15 @@
import { ReactNode } from 'react';
import { TopBar } from './TopBar';
import './EmptyLayout.scss';
+import classNames from 'classnames';
export function EmptyLayout({ children }: { children: ReactNode }) {
return (
-
+
diff --git a/gui/src/components/MainLayout.tsx b/gui/src/components/MainLayout.tsx
index 3cd488eaa3..a45e019e25 100644
--- a/gui/src/components/MainLayout.tsx
+++ b/gui/src/components/MainLayout.tsx
@@ -60,7 +60,10 @@ export function MainLayout({
return (
-
+
@@ -71,7 +74,8 @@ export function MainLayout({
className={classNames(
'overflow-y-auto mr-2 my-2 mobile:m-0',
'flex flex-col rounded-xl',
- background && 'bg-background-70'
+ background && 'bg-background-70',
+ window.__IOS__ && 'mobile:mt-10'
)}
>
{children}
diff --git a/gui/src/components/TopBar.tsx b/gui/src/components/TopBar.tsx
index 76b1fb183b..f408750ffb 100644
--- a/gui/src/components/TopBar.tsx
+++ b/gui/src/components/TopBar.tsx
@@ -232,19 +232,22 @@ export function TopBar({
>
)}
- {!isTauri && !showVersionMobile && !config?.decorations && (
-
+ {!isTauri &&
+ !showVersionMobile &&
+ !config?.decorations &&
+ !window.__IOS__ && (
-
- )}
+ )}
- {(typeof __ANDROID__ === 'undefined' || !__ANDROID__?.isThere()) && (
-
- )}
+ {(!window.__IOS__ ||
+ typeof __ANDROID__ === 'undefined' ||
+ !__ANDROID__?.isThere()) && }
diff --git a/gui/src/components/onboarding/OnboardingLayout.tsx b/gui/src/components/onboarding/OnboardingLayout.tsx
index 671ada5e6d..4599035514 100644
--- a/gui/src/components/onboarding/OnboardingLayout.tsx
+++ b/gui/src/components/onboarding/OnboardingLayout.tsx
@@ -6,6 +6,7 @@ import { useBreakpoint } from '@/hooks/breakpoint';
import { SkipSetupButton } from './SkipSetupButton';
import { SkipSetupWarningModal } from './SkipSetupWarningModal';
import './OnboardingLayout.scss';
+import classNames from 'classnames';
export function OnboardingLayout({ children }: { children: ReactNode }) {
const { isMobile } = useBreakpoint('mobile');
@@ -14,10 +15,19 @@ export function OnboardingLayout({ children }: { children: ReactNode }) {
return !state.alonePage ? (
-
+
-
+
+
-
+
@@ -118,7 +127,10 @@ export function SettingsLayout({ children }: { children: ReactNode }) {
{isMobile &&
}
{children}
diff --git a/gui/src/components/settings/pages/InterfaceSettings.tsx b/gui/src/components/settings/pages/InterfaceSettings.tsx
index 7973a8f2cb..690f7c4f75 100644
--- a/gui/src/components/settings/pages/InterfaceSettings.tsx
+++ b/gui/src/components/settings/pages/InterfaceSettings.tsx
@@ -248,27 +248,33 @@ export function InterfaceSettings() {
>
)}
-
- {l10n.getString('settings-general-interface-discord_presence')}
-
-
-
- {l10n.getString(
- 'settings-general-interface-discord_presence-description'
- )}
-
-
-
-
-
+ {isTauri() && (
+ <>
+
+ {l10n.getString(
+ 'settings-general-interface-discord_presence'
+ )}
+
+
+
+ {l10n.getString(
+ 'settings-general-interface-discord_presence-description'
+ )}
+
+
+
+
+
+ >
+ )}
{l10n.getString('settings-general-interface-dev_mode')}
diff --git a/gui/src/vite-env.d.ts b/gui/src/vite-env.d.ts
index ef7bc23d8e..3c5f94fa5d 100644
--- a/gui/src/vite-env.d.ts
+++ b/gui/src/vite-env.d.ts
@@ -12,6 +12,7 @@ declare const __ANDROID__:
interface Window {
readonly isTauri: boolean;
+ readonly __IOS__: boolean | undefined;
}
declare module 'tailwind-gradient-mask-image';
diff --git a/gui/vite.config.ts b/gui/vite.config.ts
index a09fed7b64..632b4bac12 100644
--- a/gui/vite.config.ts
+++ b/gui/vite.config.ts
@@ -36,6 +36,7 @@ export function i18nHotReload(): PluginOption {
// https://vitejs.dev/config/
export default defineConfig({
+ base: "./",
define: {
__COMMIT_HASH__: JSON.stringify(commitHash),
__VERSION_TAG__: JSON.stringify(versionTag),
diff --git a/server/core/build.gradle.kts b/server/core/build.gradle.kts
index 0da1661ad2..81f25380c6 100644
--- a/server/core/build.gradle.kts
+++ b/server/core/build.gradle.kts
@@ -64,8 +64,8 @@ dependencies {
// and not exposed to consumers on their own compile classpath.
implementation("com.google.flatbuffers:flatbuffers-java:22.10.26")
implementation("commons-cli:commons-cli:1.8.0")
- implementation("com.fasterxml.jackson.core:jackson-databind:2.15.1")
- implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.1")
+ implementation("com.fasterxml.jackson.core:jackson-databind:2.13.5")
+ implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.5")
implementation("com.github.jonpeterson:jackson-module-model-versioning:1.2.2")
implementation("org.apache.commons:commons-math3:3.6.1")
diff --git a/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/TrackersUDPServer.kt b/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/TrackersUDPServer.kt
index ac9f15d8d0..9cb045e014 100644
--- a/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/TrackersUDPServer.kt
+++ b/server/core/src/main/java/dev/slimevr/tracking/trackers/udp/TrackersUDPServer.kt
@@ -13,6 +13,7 @@ import io.github.axisangles.ktmath.Quaternion.Companion.fromRotationVector
import io.github.axisangles.ktmath.Vector3
import kotlinx.coroutines.*
import solarxr_protocol.rpc.ResetType
+import java.io.IOException
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetSocketAddress
@@ -329,7 +330,12 @@ class TrackersUDPServer(private val port: Int, name: String, private val tracker
bb.limit(bb.capacity())
bb.rewind()
parser.write(bb, conn, UDPPacket1Heartbeat)
- socket.send(DatagramPacket(rcvBuffer, bb.position(), conn.address))
+
+ try {
+ socket.send(DatagramPacket(rcvBuffer, bb.position(), conn.address))
+ } catch (e: IOException) {
+ LogManager.warning("[TrackerServer] Failed to send package to $conn", e)
+ }
if (conn.lastPacket + 1000 < System.currentTimeMillis()) {
if (!conn.timedOut) {
conn.timedOut = true
@@ -367,7 +373,11 @@ class TrackersUDPServer(private val port: Int, name: String, private val tracker
bb.putInt(10)
bb.putLong(0)
bb.putInt(conn.lastPingPacketId)
- socket.send(DatagramPacket(rcvBuffer, bb.position(), conn.address))
+ try {
+ socket.send(DatagramPacket(rcvBuffer, bb.position(), conn.address))
+ } catch (e: IOException) {
+ LogManager.warning("[TrackerServer] Failed to send package to $conn", e)
+ }
}
}
}
diff --git a/server/ios/.gitignore b/server/ios/.gitignore
new file mode 100644
index 0000000000..75cf992c40
--- /dev/null
+++ b/server/ios/.gitignore
@@ -0,0 +1,3 @@
+/build
+robovm-build
+/*local.properties
diff --git a/server/ios/Info.plist.xml b/server/ios/Info.plist.xml
new file mode 100644
index 0000000000..67cfac6cf0
--- /dev/null
+++ b/server/ios/Info.plist.xml
@@ -0,0 +1,52 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleDisplayName
+ ${app.name}
+ CFBundleExecutable
+ ${app.executable}
+ CFBundleIdentifier
+ ${app.id}
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ ${app.name}
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ ${app.version}
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ ${app.build}
+ LSRequiresIPhoneOS
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UILaunchStoryboardName
+ Launch Screen
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/server/ios/build.gradle.kts b/server/ios/build.gradle.kts
new file mode 100644
index 0000000000..edef04d913
--- /dev/null
+++ b/server/ios/build.gradle.kts
@@ -0,0 +1,62 @@
+plugins {
+ `java-library`
+ id("robovm")
+ id("org.ajoberstar.grgit")
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+}
+
+allprojects {
+ repositories {
+ mavenCentral()
+// mavenLocal()
+ maven(url = "https://jitpack.io")
+ maven {
+ url = uri("https://central.sonatype.com/repository/maven-snapshots/")
+ }
+ }
+}
+
+dependencies {
+ val robovmVersion = rootProject.properties["robovmVersion"] as String
+
+ implementation(project(":server:core"))
+ implementation("com.robovmx:robovm-rt:$robovmVersion")
+ implementation("com.robovmx:robovm-cocoatouch:$robovmVersion")
+ implementation("com.fasterxml.jackson.core:jackson-databind:2.13.5")
+ implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.5")
+ implementation("org.slf4j:slf4j-simple:2.0.7")
+}
+
+tasks.launchIPhoneSimulator {
+ dependsOn(tasks.build)
+}
+tasks.launchIPadSimulator {
+ dependsOn(tasks.build)
+}
+tasks.launchIOSDevice {
+ dependsOn(tasks.build)
+}
+tasks.robovmArchive {
+ dependsOn(tasks.build)
+}
+
+tasks.register("makeLocalProperties") {
+ File("${project.projectDir}/robovm.local.properties").writeText(
+ """
+ app.version=${grgit.describe(mapOf("tags" to true, "always" to true))}
+ app.build=${grgit.tag.list().size}
+ """.trimIndent(),
+ )
+}
+
+tasks.build {
+ dependsOn(":server:ios:makeLocalProperties")
+}
+
+robovm {
+ isIosSkipSigning = true
+}
diff --git a/server/ios/resources/Base.lproj/Launch Screen.storyboard b/server/ios/resources/Base.lproj/Launch Screen.storyboard
new file mode 100644
index 0000000000..737972c25e
--- /dev/null
+++ b/server/ios/resources/Base.lproj/Launch Screen.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png
new file mode 100644
index 0000000000..d842573041
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png
new file mode 100644
index 0000000000..c5b356d2f8
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png
new file mode 100644
index 0000000000..c5b356d2f8
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png
new file mode 100644
index 0000000000..3d49086535
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png
new file mode 100644
index 0000000000..258d802ca4
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png
new file mode 100644
index 0000000000..37ae061bad
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png
new file mode 100644
index 0000000000..37ae061bad
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png
new file mode 100644
index 0000000000..5d85316ce5
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png
new file mode 100644
index 0000000000..c5b356d2f8
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png
new file mode 100644
index 0000000000..cfc94a0e7b
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png
new file mode 100644
index 0000000000..cfc94a0e7b
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png
new file mode 100644
index 0000000000..28bb14ec3c
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-512@2x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-512@2x.png
new file mode 100644
index 0000000000..4471a53300
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-512@2x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png
new file mode 100644
index 0000000000..28bb14ec3c
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png
new file mode 100644
index 0000000000..6916323ca5
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png
new file mode 100644
index 0000000000..19e5a1f2b8
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png
new file mode 100644
index 0000000000..548f993305
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png
new file mode 100644
index 0000000000..9a9640e238
Binary files /dev/null and b/server/ios/resources/Images.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png differ
diff --git a/server/ios/resources/Images.xcassets/AppIcon.appiconset/Contents.json b/server/ios/resources/Images.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000000..9664c57335
--- /dev/null
+++ b/server/ios/resources/Images.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,116 @@
+{
+ "images" : [
+ {
+ "filename" : "AppIcon-20x20@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-20x20@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-29x29@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-29x29@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-40x40@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-40x40@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-60x60@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "AppIcon-60x60@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "AppIcon-20x20@2x 1.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "AppIcon-29x29@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-29x29@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "AppIcon-40x40@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-40x40@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "AppIcon-76x76@1x.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "AppIcon-76x76@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "AppIcon-83.5x83.5@2x 1.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "filename" : "AppIcon-512@2x 1.png",
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ },
+ {
+ "filename" : "AppIcon-20x20@1x.png",
+ "idiom" : "iphone",
+ "scale" : "1x",
+ "size" : "20x20"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/server/ios/resources/Images.xcassets/Contents.json b/server/ios/resources/Images.xcassets/Contents.json
new file mode 100644
index 0000000000..73c00596a7
--- /dev/null
+++ b/server/ios/resources/Images.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/server/ios/resources/dist b/server/ios/resources/dist
new file mode 120000
index 0000000000..5c39568289
--- /dev/null
+++ b/server/ios/resources/dist
@@ -0,0 +1 @@
+../../../gui/dist
\ No newline at end of file
diff --git a/server/ios/robovm.properties b/server/ios/robovm.properties
new file mode 100644
index 0000000000..4062671952
--- /dev/null
+++ b/server/ios/robovm.properties
@@ -0,0 +1,6 @@
+app.version=1.0
+app.id=dev.slimevr.ios
+app.mainclass=dev.slimevr.ios.Main
+app.executable=Main
+app.build=1
+app.name=SlimeVR
\ No newline at end of file
diff --git a/server/ios/robovm.xml b/server/ios/robovm.xml
new file mode 100644
index 0000000000..80b393a734
--- /dev/null
+++ b/server/ios/robovm.xml
@@ -0,0 +1,36 @@
+
+ ${app.executable}
+ ${app.mainclass}
+ ios
+ ios
+ Info.plist.xml
+
+
+ resources
+
+
+
+
+
+ dev.slimevr.**
+ io.eiren.**
+ io.github.axisangles.**
+ com.jme3.**
+ java.util.logging.SimpleFormatter
+ java.util.logging.LoggingProxyImpl
+ android.icu.impl.TimeZoneNamesFactoryImpl
+ com.fasterxml.jackson.databind.**
+ com.google.flatbuffers.**
+ kotlin.reflect.jvm.internal.ReflectionFactoryImpl
+
+
+ WebKit
+
+
+
+
+ -ld_classic
+
+
+
+
diff --git a/server/ios/src/main/java/dev/slimevr/ios/Main.java b/server/ios/src/main/java/dev/slimevr/ios/Main.java
new file mode 100644
index 0000000000..197130ee57
--- /dev/null
+++ b/server/ios/src/main/java/dev/slimevr/ios/Main.java
@@ -0,0 +1,166 @@
+package dev.slimevr.ios;
+
+import dev.slimevr.ios.logging.FoundationLogPrintStream;
+import org.robovm.apple.dispatch.DispatchQueue;
+import org.robovm.apple.foundation.*;
+import org.robovm.apple.uikit.UIApplication;
+import org.robovm.apple.uikit.UIApplicationDelegateAdapter;
+import org.robovm.apple.uikit.UIApplicationLaunchOptions;
+import org.robovm.apple.uikit.UIWindow;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import dev.slimevr.Keybinding;
+import dev.slimevr.VRServer;
+import io.eiren.util.logging.LogManager;
+import org.robovm.rt.bro.ptr.BytePtr;
+
+
+public class Main extends UIApplicationDelegateAdapter {
+ private UIWindow window;
+ private WebviewController rootViewController;
+
+ @Override
+ public boolean didFinishLaunching(
+ UIApplication application,
+ UIApplicationLaunchOptions launchOptions
+ ) {
+ // Set up the view controller.
+ rootViewController = new WebviewController();
+
+ // Create a new window at screen size.
+ window = new UIWindow();
+ // Set the view controller as the root controller for the window.
+ window.setRootViewController(rootViewController);
+ // Make the window visible.
+ window.makeKeyAndVisible();
+
+ return true;
+ }
+
+ private static NSURL getAppFolder() {
+ try {
+ return NSFileManager
+ .getDefaultManager()
+ .getURLForDirectory(
+ NSSearchPathDirectory.DocumentDirectory,
+ NSSearchPathDomainMask.UserDomainMask,
+ null,
+ false
+ );
+ } catch (NSErrorException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static String getString(NSURL url) {
+ var buffer = new BytePtr();
+ boolean test = url.getFileSystemRepresentation(buffer, 1024L);
+ if (!test)
+ throw new RuntimeException("Couldn't fit URL into buffer");
+ return buffer.toStringZ();
+ }
+
+ public static void main(String[] args) {
+ try (NSAutoreleasePool pool = new NSAutoreleasePool()) {
+ UIApplication.main(args, null, Main.class);
+ }
+ }
+
+ @Override
+ public void didEnterBackground(UIApplication application) {
+ if (VRServer.Companion.getInstanceInitialized()) {
+ VRServer.Companion.getInstance().interrupt();
+ try {
+ VRServer.Companion.getInstance().join();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ super.didEnterBackground(application);
+ }
+
+ @Override
+ public void willTerminate(UIApplication application) {
+ if (VRServer.Companion.getInstanceInitialized()) {
+ VRServer.Companion.getInstance().interrupt();
+ try {
+ VRServer.Companion.getInstance().join();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ super.willTerminate(application);
+ }
+
+ public static void runServer() {
+ var thread = new Thread(() -> {
+ try {
+ LogManager.initialize(new File(getString(getAppFolder())));
+ System.setErr(new FoundationLogPrintStream());
+ System.setOut(new FoundationLogPrintStream());
+ } catch (Exception e) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ String sStackTrace = sw.toString();
+ Foundation.log("%@\n%@", new NSString(e.toString()), new NSString(sStackTrace));
+ }
+ try {
+ var vrServer = new VRServer(
+ getString(
+ getAppFolder()
+ .newURLByAppendingPathComponent("vrserver.yml")
+ )
+ );
+ vrServer.start();
+ vrServer.addOnTick(new Runnable() {
+ int tick = 0;
+
+ @Override
+ public void run() {
+ if (tick++ >= 1000) {
+ tick = 0;
+ final boolean hasTrackers = vrServer
+ .getAllTrackers()
+ .stream()
+ .anyMatch(
+ (tracker) -> !tracker.isComputed()
+ && tracker.getStatus().getSendData()
+ );
+ DispatchQueue
+ .getMainQueue()
+ .sync(
+ () -> UIApplication
+ .getSharedApplication()
+ .setIdleTimerDisabled(hasTrackers)
+ );
+ }
+ }
+ });
+ new Keybinding(vrServer);
+ vrServer.join();
+ LogManager.closeLogger();
+ System.exit(0);
+ } catch (Exception e) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ String sStackTrace = sw.toString();
+ Foundation.log("%@\n%@", new NSString(e.toString()), new NSString(sStackTrace));
+ }
+ }, "SlimeVR Main Thread");
+ thread.setUncaughtExceptionHandler((th, e) -> {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ String sStackTrace = sw.toString();
+ Foundation.log("%@\n%@", new NSString(e.toString()), new NSString(sStackTrace));
+ });
+ thread.start();
+ }
+}
diff --git a/server/ios/src/main/java/dev/slimevr/ios/WebviewController.java b/server/ios/src/main/java/dev/slimevr/ios/WebviewController.java
new file mode 100644
index 0000000000..11cbed320d
--- /dev/null
+++ b/server/ios/src/main/java/dev/slimevr/ios/WebviewController.java
@@ -0,0 +1,116 @@
+package dev.slimevr.ios;
+
+import dev.slimevr.VRServer;
+import org.robovm.apple.foundation.*;
+import org.robovm.apple.uikit.*;
+import org.robovm.apple.uniformtypeid.UTType;
+import org.robovm.apple.webkit.*;
+
+import java.util.List;
+
+
+public class WebviewController extends UIViewController {
+ private WKWebView webView;
+
+ public WebviewController() {
+ UIView view = getView();
+
+ view.setBackgroundColor(UIColor.purple());
+ }
+
+ @Override
+ public void loadView() {
+ super.loadView();
+ UIView view = getView();
+
+ var config = new WKWebViewConfiguration();
+ config.setURLSchemeHandler(new WKURLSchemeHandler() {
+ @Override
+ public void startURLSchemeTask(WKWebView webView, WKURLSchemeTask urlSchemeTask) {
+ var url = urlSchemeTask.getRequest().getURL();
+ var fileUrl = fileUrlFromUrl(url);
+ if (fileUrl == null)
+ return;
+ var mimeType = mimeType(fileUrl);
+ var data = NSData.read(fileUrl);
+ if (data == null)
+ return;
+
+ var response = new NSHTTPURLResponse(url, mimeType, data.getLength(), null);
+
+ urlSchemeTask.didReceiveResponse(response);
+ urlSchemeTask.didReceiveData(data);
+ urlSchemeTask.didFinish();
+ }
+
+ @Override
+ public void stopURLSchemeTask(WKWebView webView, WKURLSchemeTask urlSchemeTask) {
+
+ }
+
+ private NSURL fileUrlFromUrl(NSURL url) {
+ List paths = url.getPathComponents();
+ if (paths.size() == 1) {
+ return NSBundle.getMainBundle().findResourceURL("index.html", "", "dist");
+ }
+ String last = paths.remove(paths.size() - 1);
+ paths.remove(0); // Remove "/"
+ StringBuilder joining = new StringBuilder();
+ for (String path : paths) {
+ joining.append("/").append(path);
+ }
+ return NSBundle.getMainBundle().findResourceURL(last, "", "dist" + joining);
+ }
+
+ private String mimeType(NSURL url) {
+ var type = UTType.createUsingFilenameExtension(url.getPathExtension());
+ if (type == null)
+ return null;
+ return type.getPreferredMIMEType();
+ }
+ }, "slimevr");
+ var userContent = new WKUserContentController();
+ userContent
+ .addUserScript(
+ new WKUserScript(
+ "window.__IOS__ = true;",
+ WKUserScriptInjectionTime.AtDocumentEnd,
+ false
+ )
+ );
+ userContent.addUserScript(makeZoomScale());
+ config.setUserContentController(userContent);
+ webView = new WKWebView(view.getFrame(), config);
+ if (webView != null) {
+ view.addSubview(webView);
+ }
+ }
+
+ @Override
+ public void viewDidLoad() {
+ super.viewDidLoad();
+
+ if (!VRServer.Companion.getInstanceInitialized()) {
+ Main.runServer();
+ }
+ webView
+ .getScrollView()
+ .setContentInsetAdjustmentBehavior(UIScrollViewContentInsetAdjustmentBehavior.Never);
+ webView.getScrollView().setScrollEnabled(false);
+ var req = new NSURLRequest(new NSURL("slimevr:///"));
+ webView.loadRequest(req);
+ }
+
+ private WKUserScript makeZoomScale() {
+ final String source = "var meta = document.createElement('meta');"
+ +
+ "meta.name = 'viewport';"
+ +
+ "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';"
+ +
+ "var head = document.getElementsByTagName('head')[0];"
+ +
+ "head.appendChild(meta);";
+ return new WKUserScript(source, WKUserScriptInjectionTime.AtDocumentEnd, true);
+ }
+}
diff --git a/server/ios/src/main/java/dev/slimevr/ios/logging/FoundationLogPrintStream.java b/server/ios/src/main/java/dev/slimevr/ios/logging/FoundationLogPrintStream.java
new file mode 100644
index 0000000000..812cb45529
--- /dev/null
+++ b/server/ios/src/main/java/dev/slimevr/ios/logging/FoundationLogPrintStream.java
@@ -0,0 +1,13 @@
+package dev.slimevr.ios.logging;
+
+import org.robovm.apple.foundation.Foundation;
+
+
+public class FoundationLogPrintStream extends LoggingPrintStream {
+
+ @Override
+ public void log(String text) {
+ Foundation.log(text);
+ }
+
+}
diff --git a/server/ios/src/main/java/dev/slimevr/ios/logging/LoggingPrintStream.java b/server/ios/src/main/java/dev/slimevr/ios/logging/LoggingPrintStream.java
new file mode 100644
index 0000000000..d12b2593a8
--- /dev/null
+++ b/server/ios/src/main/java/dev/slimevr/ios/logging/LoggingPrintStream.java
@@ -0,0 +1,142 @@
+package dev.slimevr.ios.logging;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+
+public abstract class LoggingPrintStream extends PrintStream {
+ StringBuffer st = new StringBuffer();
+
+ //
+
+ public LoggingPrintStream() {
+ super(new OutputStream() {
+ @Override
+ public void write(int arg0) throws IOException {
+ }
+ });
+ }
+
+ public abstract void log(String text);
+
+ public void write(char ch) {
+ if (ch == 0xa) {} else
+ st.append(ch);
+ }
+
+ @Override
+ public void flush() {
+ if (st.length() > 0) {
+ log(st.toString());
+ st.setLength(0);
+ }
+ }
+
+ //
+
+ @Override
+ public void print(char[] s) {
+ for (char ch : s)
+ write(ch);
+ }
+
+ @Override
+ public void print(boolean b) {
+ print(b + "");
+ }
+
+ @Override
+ public void print(char c) {
+ write(c);
+ }
+
+ @Override
+ public void print(double d) {
+ print(d + "");
+ }
+
+ @Override
+ public void print(float f) {
+ print(f + "");
+ }
+
+ @Override
+ public void print(int i) {
+ print(i + "");
+ }
+
+ public void print(long l) {
+ print(l + "");
+ }
+
+ @Override
+ public void print(Object obj) {
+ print((obj + "").toCharArray());
+ }
+
+ @Override
+ public void print(String s) {
+ print((s + "").toCharArray());
+ }
+
+ @Override
+ public void println() {
+ flush();
+ }
+
+ @Override
+ public void println(boolean x) {
+ print(x);
+ flush();
+ }
+
+ @Override
+ public void println(char x) {
+ print(x);
+ flush();
+ }
+
+ @Override
+ public void println(char[] x) {
+ print(x);
+ flush();
+ }
+
+ @Override
+ public void println(double x) {
+ print(x);
+ flush();
+ }
+
+ @Override
+ public void println(float x) {
+ print(x);
+ flush();
+ }
+
+ @Override
+ public void println(int x) {
+ print(x);
+ flush();
+ }
+
+ @Override
+ public void println(long x) {
+ print(x);
+ flush();
+ }
+
+ @Override
+ public void println(Object x) {
+ print(x);
+ flush();
+ }
+
+ @Override
+ public void println(String x) {
+ print(x);
+ flush();
+ }
+
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index e2691d0b82..1dafc0e222 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -10,11 +10,24 @@
rootProject.name = "SlimeVR Server"
pluginManagement {
- repositories {
- gradlePluginPortal()
- google()
- mavenCentral()
- }
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+// mavenLocal()
+ maven {
+ url = uri("https://central.sonatype.com/repository/maven-snapshots/")
+ }
+ }
+
+ val robovmVersion: String by settings
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.name == "robovm") {
+ useModule("com.robovmx:robovm-gradle-plugin:${requested.version ?: robovmVersion}")
+ }
+ }
+ }
val kotlinVersion: String by settings
val spotlessVersion: String by settings
@@ -29,6 +42,7 @@ pluginManagement {
id("com.gradleup.shadow") version shadowJarVersion
id("com.github.gmazzo.buildconfig") version buildconfigVersion
id("org.ajoberstar.grgit") version grgitVersion
+ id("robovm") version robovmVersion apply false
}
}
@@ -40,3 +54,4 @@ project(":server").projectDir = File("server")
include(":server:core")
include(":server:desktop")
include(":server:android")
+include(":server:ios")