Skip to content

Commit e180298

Browse files
committed
Port #1763
1 parent 3f7580a commit e180298

11 files changed

Lines changed: 205 additions & 0 deletions

File tree

server/core/src/main/java/dev/slimevr/app-context.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ data class Phase1Context(
2727
) : Phase1ContextProvider
2828

2929
interface AppContextProvider : Phase1ContextProvider {
30+
val featureFlags: FeatureFlags
3031
val skeleton: Skeleton
3132
val firmwareManager: FirmwareManager
3233
val vrcConfigManager: VRCConfigManager?
@@ -46,6 +47,7 @@ class AppContext(
4647
override val server: VRServer,
4748
override val config: AppConfig,
4849
override val serialServer: SerialServer,
50+
override val featureFlags: FeatureFlags,
4951
override val skeleton: Skeleton,
5052
override val firmwareManager: FirmwareManager,
5153
override val vrcConfigManager: VRCConfigManager?,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package dev.slimevr
2+
3+
data class FeatureFlags(
4+
var steam: Boolean = false,
5+
var skipCheckUdev: Boolean = false,
6+
var udevRulesInstalled: Boolean? = null,
7+
)

server/core/src/main/java/dev/slimevr/logger.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ object AppLogger {
2121
val bvh = logger("BVH")
2222
val vmc = logger("VMC")
2323
val oscQuery = logger("OSCQuery")
24+
val install = logger("Install")
2425
val coroutines = noCoLogger("Coroutines")
2526

2627
init {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package dev.slimevr.solarxr
2+
3+
import solarxr_protocol.rpc.InstalledInfoRequest
4+
import solarxr_protocol.rpc.InstalledInfoResponse
5+
6+
object InstalledInfoBehaviour : SolarXRBridgeBehaviour {
7+
override fun observe(receiver: SolarXRBridge) {
8+
receiver.rpcDispatcher.on<InstalledInfoRequest> {
9+
val udevRulesInstalled = receiver.appContext.featureFlags.udevRulesInstalled ?: return@on
10+
receiver.sendRpc(InstalledInfoResponse(isudevinstalled = udevRulesInstalled))
11+
}
12+
}
13+
}

server/core/src/main/java/dev/slimevr/solarxr/module.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class SolarXRBridge(
8080
add(MagBehaviour(appContext))
8181
add(KnownTrackersBehaviour(appContext.config.settings))
8282
add(BvhBehaviour(appContext.bvhManager))
83+
add(InstalledInfoBehaviour)
8384
}
8485

8586
fun create(

server/core/src/test/java/dev/slimevr/TestServer.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ private object NoopConfigStorage : ConfigStorage {
103103
}
104104

105105
abstract class TestAppContext : AppContextProvider {
106+
override val featureFlags: FeatureFlags = FeatureFlags()
106107
override val config: AppConfig get() = error("not used in test")
107108
override val serialServer: SerialServer get() = error("not used in test")
108109
override val firmwareManager: FirmwareManager get() = error("not used in test")

server/desktop/src/main/java/dev/slimevr/desktop/Main.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package dev.slimevr.desktop
44

55
import dev.slimevr.AppContext
66
import dev.slimevr.CURRENT_PLATFORM
7+
import dev.slimevr.FeatureFlags
78
import dev.slimevr.Phase1Context
89
import dev.slimevr.Platform
910
import dev.slimevr.VRServer
@@ -12,6 +13,8 @@ import dev.slimevr.config.AppConfig
1213
import dev.slimevr.context.debug.ContextDebug
1314
import dev.slimevr.desktop.config.DesktopConfigStorage
1415
import dev.slimevr.desktop.hid.createDesktopHIDManager
16+
import dev.slimevr.desktop.install.executeShellCommand
17+
import dev.slimevr.desktop.install.runInstaller
1518
import dev.slimevr.desktop.ipc.createIpcServers
1619
import dev.slimevr.desktop.ipc.createSolarXRWebsocketServer
1720
import dev.slimevr.desktop.networkprofile.setupDesktopNetworkProfileChecker
@@ -38,6 +41,28 @@ fun main(args: Array<String>) = runBlocking<Unit> {
3841
ContextDebug.enabled = System.getProperty("slimevr.debug.context") == "true" ||
3942
System.getenv("SLIMEVR_DEBUG_CONTEXT") == "true"
4043

44+
val featureFlags = FeatureFlags()
45+
for (arg in args) {
46+
when (arg) {
47+
"--steam", "-s" -> featureFlags.steam = true
48+
"--install", "-i" -> { runInstaller(featureFlags); return@runBlocking }
49+
"--no-udev", "-u" -> featureFlags.skipCheckUdev = true
50+
}
51+
}
52+
if (CURRENT_PLATFORM != Platform.LINUX) featureFlags.skipCheckUdev = true
53+
54+
val isInstallDisabled = System.getenv("SLIME_SERVER_DISABLE_INSTALLER")?.toIntOrNull()
55+
if (featureFlags.steam && isInstallDisabled != 1) runInstaller(featureFlags)
56+
57+
if (!featureFlags.skipCheckUdev) {
58+
val command = if (featureFlags.steam) {
59+
arrayOf("steam-runtime-launch-client", "--alongside-steam", "--", "udevadm", "cat")
60+
} else {
61+
arrayOf("udevadm", "cat")
62+
}
63+
featureFlags.udevRulesInstalled = executeShellCommand(*command)?.contains("slime")
64+
}
65+
4166
val configFolder = resolveConfigDirectory() ?: error("Unable to resolve config folder")
4267
val storage = DesktopConfigStorage(configFolder.toFile())
4368
val config = AppConfig.create(this, storage = storage)
@@ -66,6 +91,7 @@ fun main(args: Array<String>) = runBlocking<Unit> {
6691
server = server,
6792
config = config,
6893
serialServer = serialServer,
94+
featureFlags = featureFlags,
6995
skeleton = skeleton,
7096
firmwareManager = firmwareManager,
7197
vrcConfigManager = vrcConfigManager,
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package dev.slimevr.desktop.install
2+
3+
import dev.slimevr.AppLogger
4+
import dev.slimevr.CURRENT_PLATFORM
5+
import dev.slimevr.FeatureFlags
6+
import dev.slimevr.Platform
7+
import kotlinx.coroutines.Dispatchers
8+
import kotlinx.coroutines.withContext
9+
import java.io.IOException
10+
11+
suspend fun runInstaller(featureFlags: FeatureFlags) {
12+
when (CURRENT_PLATFORM) {
13+
Platform.LINUX -> installLinux(featureFlags)
14+
Platform.WINDOWS -> installWindows()
15+
else -> AppLogger.install.warn("Updater doesn't support operating system '$CURRENT_PLATFORM'")
16+
}
17+
}
18+
19+
suspend fun executeShellCommand(vararg command: String): String? = try {
20+
val process = withContext(Dispatchers.IO) {
21+
ProcessBuilder(*command)
22+
.redirectErrorStream(true)
23+
.start()
24+
}
25+
process.inputStream.bufferedReader().readText().also {
26+
process.waitFor()
27+
}
28+
} catch (e: IOException) {
29+
AppLogger.install.warn("Error executing shell command: ${e.message}")
30+
null
31+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package dev.slimevr.desktop.install
2+
3+
import dev.slimevr.AppLogger
4+
import dev.slimevr.FeatureFlags
5+
6+
private const val LINUX_STEAM_DRIVER_DIRECTORY = "slimevr-openvr-driver-x64-linux"
7+
private const val LINUX_FEEDER_DIRECTORY = "SlimeVR-Feeder-App-Linux"
8+
9+
suspend fun installLinux(featureFlags: FeatureFlags) {
10+
installLinuxSteamVRDriver()
11+
installLinuxFeeder(featureFlags)
12+
}
13+
14+
private suspend fun installLinuxSteamVRDriver() {
15+
val path = System.getProperty("user.dir")
16+
val vrPathReg = "${System.getProperty("user.home")}/.steam/steam/steamapps/common/SteamVR/bin/vrpathreg.sh"
17+
val existing = executeShellCommand(vrPathReg)
18+
if (existing == null) {
19+
AppLogger.install.warn("SteamVR driver installation failed")
20+
return
21+
}
22+
if (existing.contains("slimevr")) {
23+
AppLogger.install.info("SteamVR driver is already installed")
24+
return
25+
}
26+
executeShellCommand(vrPathReg, "adddriver", "$path/$LINUX_STEAM_DRIVER_DIRECTORY")
27+
if (executeShellCommand(vrPathReg)?.contains("slimevr") != true) {
28+
AppLogger.install.warn("Failed to install SteamVR driver")
29+
return
30+
}
31+
AppLogger.install.info("SteamVR driver successfully installed")
32+
}
33+
34+
private suspend fun installLinuxFeeder(featureFlags: FeatureFlags) {
35+
val path = System.getProperty("user.dir")
36+
executeShellCommand("chmod", "+x", "$path/$LINUX_FEEDER_DIRECTORY/SlimeVR-Feeder-App")
37+
val command = if (featureFlags.steam) {
38+
arrayOf("steam-runtime-launch-client", "--alongside-steam", "--", "$path/$LINUX_FEEDER_DIRECTORY/SlimeVR-Feeder-App", "--install")
39+
} else {
40+
arrayOf("$path/$LINUX_FEEDER_DIRECTORY/SlimeVR-Feeder-App", "--install")
41+
}
42+
val output = executeShellCommand(*command)
43+
if (output == null) {
44+
AppLogger.install.warn("Error installing feeder")
45+
return
46+
}
47+
if (output.lowercase().contains("manifest is not installed")) {
48+
AppLogger.install.warn("Could not install feeder application")
49+
} else {
50+
AppLogger.install.info("Successfully installed feeder application")
51+
}
52+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package dev.slimevr.desktop.install
2+
3+
import com.sun.jna.platform.win32.Advapi32Util
4+
import com.sun.jna.platform.win32.WinReg
5+
import dev.slimevr.AppLogger
6+
7+
private const val WINDOWS_STEAMVR_DRIVER_DIRECTORY = "slimevr-openvr-driver-win64"
8+
private const val WINDOWS_FEEDER_DIRECTORY = "SlimeVR-Feeder-App-win64"
9+
10+
suspend fun installWindows() {
11+
installWindowsSteamVRDriver()
12+
installWindowsFeeder()
13+
}
14+
15+
private suspend fun getKeyByPath(hkey: WinReg.HKEY, path: String): Map<String, String> {
16+
val keysMap = mutableMapOf<String, String>()
17+
try {
18+
Advapi32Util.registryGetValues(hkey, path).forEach {
19+
keysMap[it.key.replace("""_h\d+$""".toRegex(), "")] = it.value.toString()
20+
}
21+
} catch (e: Exception) {
22+
AppLogger.install.error("[RegEdit] Error reading values from registry: ${e.message}")
23+
}
24+
return keysMap
25+
}
26+
27+
private suspend fun installWindowsSteamVRDriver() {
28+
val path = System.getProperty("user.dir")
29+
val regQuery = getKeyByPath(WinReg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Steam App 250820")
30+
val steamVRLocation = regQuery["InstallLocation"]
31+
if (steamVRLocation == null || !steamVRLocation.contains("SteamVR")) {
32+
AppLogger.install.warn("Can't find SteamVR, unable to install SteamVR driver")
33+
return
34+
}
35+
val vrPathReg = "${steamVRLocation}\\bin\\win64\\vrpathreg.exe"
36+
val existing = executeShellCommand(vrPathReg, "finddriver", "slimevr")
37+
if (existing == null) {
38+
AppLogger.install.warn("Error installing SteamVR driver")
39+
return
40+
}
41+
if (existing.contains("slimevr")) {
42+
AppLogger.install.info("SteamVR driver is already installed")
43+
return
44+
}
45+
executeShellCommand(vrPathReg, "adddriver", "${path}\\${WINDOWS_STEAMVR_DRIVER_DIRECTORY}")
46+
if (executeShellCommand(vrPathReg, "finddriver", "slimevr")?.contains("slimevr") != true) {
47+
AppLogger.install.warn("Failed to install SlimeVR driver")
48+
return
49+
}
50+
AppLogger.install.info("SteamVR driver successfully installed")
51+
}
52+
53+
private suspend fun installWindowsFeeder() {
54+
val path = System.getProperty("user.dir")
55+
val output = executeShellCommand("${path}\\${WINDOWS_FEEDER_DIRECTORY}\\SlimeVR-Feeder-App.exe", "--install")
56+
if (output == null) {
57+
AppLogger.install.warn("Error installing feeder")
58+
return
59+
}
60+
if (output.lowercase().contains("manifest is not installed")) {
61+
AppLogger.install.warn("Could not install feeder application")
62+
} else {
63+
AppLogger.install.info("Successfully installed feeder application")
64+
}
65+
}

0 commit comments

Comments
 (0)