using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using UnityEditor.PackageManager; using UnityEditor.PackageManager.Requests; using UnityEngine; using Microsoft.Win32; [assembly: InternalsVisibleTo("UnityEditor.XR.OpenXR.Tests")] namespace UnityEditor.XR.OpenXR { internal class OpenXRRuntimeSelector { internal class RuntimeDetector { internal const string k_RuntimeEnvKey = "XR_RUNTIME_JSON"; public virtual string name { get; set; } public virtual string jsonPath { get; } public virtual string tooltip => jsonPath; public virtual bool detected { get { return File.Exists(jsonPath); } } public virtual void PrepareRuntime() { } public virtual void Activate() { if (detected) { Environment.SetEnvironmentVariable(k_RuntimeEnvKey, jsonPath); } } public virtual void Deactivate() { Environment.SetEnvironmentVariable(k_RuntimeEnvKey, ""); } public RuntimeDetector(string _name) { name = _name; } }; internal class SystemDefault : RuntimeDetector { public override string name { get { string ret = "System Default"; if (string.IsNullOrEmpty(tooltip)) { ret += " (None Set)"; } return ret; } set { } } public override string jsonPath => ""; public override string tooltip { get { string str = (string)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenXR\1", "ActiveRuntime", ""); #if UNITY_EDITOR_OSX str = File.Exists("/usr/local/share/openxr/1/active_runtime.json") ? "/usr/local/share/openxr/1/active_runtime.json" : ""; #endif return str; } } public override bool detected => true; public SystemDefault() : base(null) { } } internal class OtherRuntime : RuntimeDetector { public const string k_OtherRuntimeEnvKey = "OTHER_XR_RUNTIME_JSON"; private string runtimeJsonPath = ""; public override string jsonPath => runtimeJsonPath; public override void PrepareRuntime() { var selectedJson = EditorUtility.OpenFilePanel("Select OpenXR Runtime json", "", "json"); SetOtherRuntimeJsonPath(selectedJson); base.PrepareRuntime(); } public void SetOtherRuntimeJsonPath(string selectedJson) { if (!string.IsNullOrEmpty(selectedJson)) { runtimeJsonPath = selectedJson; Environment.SetEnvironmentVariable(k_OtherRuntimeEnvKey, selectedJson); } } public override bool detected => true; public OtherRuntime() : base("Other") { string otherJsonPath = Environment.GetEnvironmentVariable(k_OtherRuntimeEnvKey); if (otherJsonPath != null) { runtimeJsonPath = otherJsonPath; } } } internal class OculusDetector : RuntimeDetector { private const string installLocKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Oculus"; private const string installLocValue = "InstallLocation"; private const string jsonName = @"Support\oculus-runtime\oculus_openxr_64.json"; public override string jsonPath { get { var oculusPath = (string)Registry.GetValue(installLocKey, installLocValue, ""); if (string.IsNullOrEmpty(oculusPath)) return ""; return Path.Combine(oculusPath, jsonName); } } public OculusDetector() : base("Oculus") { } } internal class SteamVRDetector : RuntimeDetector { public override string jsonPath => @"C:\Program Files (x86)\Steam\steamapps\common\SteamVR\steamxr_win64.json"; public SteamVRDetector() : base("SteamVR") { } } internal class WindowsMRDetector : RuntimeDetector { public override string jsonPath => @"C:\WINDOWS\system32\MixedRealityRuntime.json"; public WindowsMRDetector() : base("Windows Mixed Reality") { } } internal class MetaXRSimDetector : RuntimeDetector { const string PackageName = "com.meta.xr.simulator"; string runtimePath = string.Empty; SearchRequest searchRequest = null; public override string jsonPath => runtimePath; public MetaXRSimDetector() : base("Meta XR Simulator") { SubmitPackageRequest(); } void SubmitPackageRequest() { searchRequest = Client.Search(PackageName); EditorApplication.update += OnEditorUpdate; } void OnEditorUpdate() { switch (searchRequest.Status) { case StatusCode.Failure: { EditorApplication.update -= OnEditorUpdate; return; } case StatusCode.InProgress: { return; } } EditorApplication.update -= OnEditorUpdate; foreach (var packageInfo in searchRequest.Result) { if (packageInfo.name == PackageName) { string packageAssetPath = Path.GetFullPath(packageInfo.assetPath); runtimePath = GetRuntimeJsonPath(packageAssetPath); break; } } } private static string GetRuntimeJsonPath(string packageAssetPath) { var runtimePath = Path.Combine(packageAssetPath, Path.Combine("MetaXRSimulator", "meta_openxr_simulator_win64.json")); #if UNITY_EDITOR_OSX runtimePath = Path.Combine(Path.GetFullPath(packageAssetPath), Path.Combine("MetaXRSimulator", "meta_openxr_simulator_posix.json")); #endif if (!File.Exists(runtimePath)) { runtimePath = Path.Combine(packageAssetPath, Path.Combine("MetaXRSimulator", "meta_openxr_simulator.json")); } return runtimePath; } } internal class DiscoveredDetector : RuntimeDetector { public DiscoveredDetector(string _name, string jsonPath) : base(_name) { m_jsonPath = jsonPath; } public override string jsonPath => m_jsonPath; private string m_jsonPath; } internal static List runtimeDetectors; internal static List RuntimeDetectors { get { if (runtimeDetectors == null) { runtimeDetectors = GenerateRuntimeDetectorList(); } return runtimeDetectors; } } internal static void RefreshRuntimeDetectorList() { runtimeDetectors = GenerateRuntimeDetectorList(); } const string k_availableRuntimesRegistryKey = @"SOFTWARE\Khronos\OpenXR\1\AvailableRuntimes"; internal static List GenerateRuntimeDetectorList() { RegistryKey availableRuntimesKey = Registry.LocalMachine.OpenSubKey(k_availableRuntimesRegistryKey, false); Dictionary runtimePathToValue = new Dictionary(); if (availableRuntimesKey != null) { foreach (string jsonPath in availableRuntimesKey.GetValueNames()) { var availableValue = (int)availableRuntimesKey.GetValue(jsonPath, ""); runtimePathToValue.Add(jsonPath, availableValue); } } return GenerateRuntimeDetectorList(runtimePathToValue); } private static void OverwriteRuntimeDetector(List runtimeList, string jsonPath, int registryValue) { int index = runtimeList.FindIndex(runtimeDetector => runtimeDetector.jsonPath.Equals(jsonPath)); if (index < 0) { return; } if (registryValue == 0) { string name = GetName(jsonPath) ?? jsonPath; if (name != null) { runtimeList[index] = new DiscoveredDetector(name, jsonPath); } } else { runtimeList.RemoveAt(index); } } internal static List GenerateRuntimeDetectorList(Dictionary runtimePathToValue) { WindowsMRDetector windowsMRDetector = new WindowsMRDetector(); SteamVRDetector steamVRDetector = new SteamVRDetector(); OculusDetector oculusDetector = new OculusDetector(); MetaXRSimDetector metaXRSimDetector = new MetaXRSimDetector(); List runtimeList = new List(5) { new SystemDefault(), windowsMRDetector, steamVRDetector, oculusDetector, metaXRSimDetector }; foreach (var pathValuePair in runtimePathToValue) { if (pathValuePair.Key.Equals(windowsMRDetector.jsonPath)) { OverwriteRuntimeDetector(runtimeList, windowsMRDetector.jsonPath, pathValuePair.Value); } else if (pathValuePair.Key.Equals(steamVRDetector.jsonPath)) { OverwriteRuntimeDetector(runtimeList, steamVRDetector.jsonPath, pathValuePair.Value); } else if (pathValuePair.Key.Equals(oculusDetector.jsonPath)) { OverwriteRuntimeDetector(runtimeList, oculusDetector.jsonPath, pathValuePair.Value); } else if (pathValuePair.Value == 0) { string name = GetName(pathValuePair.Key) ?? pathValuePair.Key; runtimeList.Add(new DiscoveredDetector(name, pathValuePair.Key)); } } runtimeList.Add(new OtherRuntime()); return runtimeList; } [System.Serializable] internal class RuntimeInformation { public string file_format_version; public RuntimeDetails runtime; [System.Serializable] internal class RuntimeDetails { public string library_path; public string name; } } internal static string GetName(string jsonPath) { if (!File.Exists(jsonPath)) { return null; } string text = File.ReadAllText(jsonPath); RuntimeInformation runtimeInformation = JsonUtility.FromJson(text); if (runtimeInformation != null && runtimeInformation.runtime != null) { return runtimeInformation.runtime.name; } return null; } static class Content { public static readonly GUIContent k_ActiveRuntimeLabel = new GUIContent("Play Mode OpenXR Runtime", "Changing this value will only affect this instance of the editor."); } internal const string k_SelectedRuntimeEnvKey = "XR_SELECTED_RUNTIME_JSON"; private static int GetActiveRuntimeIndex(List runtimes) { string envValue = Environment.GetEnvironmentVariable(k_SelectedRuntimeEnvKey); if (string.IsNullOrEmpty(envValue)) return 0; for (int i = 0; i < runtimes.Count; i++) { if (String.Compare(runtimes[i].jsonPath, envValue, StringComparison.InvariantCulture) == 0) return i; } return 0; } internal static void SetSelectedRuntime(string jsonPath) { Environment.SetEnvironmentVariable(k_SelectedRuntimeEnvKey, jsonPath); } public static void DrawSelector() { EditorGUIUtility.labelWidth = 200; GUILayout.BeginHorizontal(); var runtimes = OpenXRRuntimeSelector.RuntimeDetectors.Where(runtime => runtime.detected).ToList(); int selectedRuntimeIndex = GetActiveRuntimeIndex(runtimes); int index = EditorGUILayout.Popup(Content.k_ActiveRuntimeLabel, selectedRuntimeIndex, runtimes.Select(s => new GUIContent(s.name, s.tooltip)).ToArray()); if (selectedRuntimeIndex != index) { selectedRuntimeIndex = index; runtimes[selectedRuntimeIndex].PrepareRuntime(); SetSelectedRuntime(runtimes[selectedRuntimeIndex].jsonPath); } GUILayout.EndHorizontal(); EditorGUIUtility.labelWidth = 0; } internal static void ActivateOrDeactivateRuntime(PlayModeStateChange state) { var runtimes = OpenXRRuntimeSelector.RuntimeDetectors.Where(runtime => runtime.detected).ToList(); int runtimeIndex = GetActiveRuntimeIndex(runtimes); switch (state) { case PlayModeStateChange.ExitingEditMode: if (runtimeIndex >= 0) { runtimes[runtimeIndex].Activate(); } break; case PlayModeStateChange.EnteredEditMode: if (runtimeIndex >= 0) { runtimes[runtimeIndex].Deactivate(); } break; } } static OpenXRRuntimeSelector() { EditorApplication.playModeStateChanged -= ActivateOrDeactivateRuntime; EditorApplication.playModeStateChanged += ActivateOrDeactivateRuntime; } } }