From 735c9eda2e0eff6c9780a0d1d51d6b9510ff457a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E5=BD=A6=E8=B5=A4=E5=B1=8B=E5=85=88?= Date: Sun, 10 Nov 2024 01:27:34 +0900 Subject: [PATCH] Custom actions part impl --- plugin_OpenVR/ConfirmationFlyout.cs | 80 ++- plugin_OpenVR/CoreSetup.cs | 8 +- plugin_OpenVR/EvrInput.cs | 592 +++++++--------- plugin_OpenVR/OpenVR.cs | 655 ++---------------- plugin_OpenVR/Pages/SettingsPage.xaml | 142 ++++ plugin_OpenVR/Pages/SettingsPage.xaml.cs | 626 +++++++++++++++++ .../PublishProfiles/FolderProfile.pubxml | 2 +- plugin_OpenVR/Utils/VRPaths.cs | 4 +- plugin_OpenVR/plugin_OpenVR.csproj | 20 +- 9 files changed, 1139 insertions(+), 990 deletions(-) create mode 100644 plugin_OpenVR/Pages/SettingsPage.xaml create mode 100644 plugin_OpenVR/Pages/SettingsPage.xaml.cs diff --git a/plugin_OpenVR/ConfirmationFlyout.cs b/plugin_OpenVR/ConfirmationFlyout.cs index 9ebb78a..1539d7d 100644 --- a/plugin_OpenVR/ConfirmationFlyout.cs +++ b/plugin_OpenVR/ConfirmationFlyout.cs @@ -1,20 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Data; -using Microsoft.UI.Xaml.Documents; -using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Media; using System; -using System.Collections.Generic; using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; using System.Threading; using System.Threading.Tasks; using Amethyst.Plugins.Contract; using Microsoft.UI.Text; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; // To learn more about WinUI, the WinUI project structure, @@ -24,45 +18,18 @@ namespace plugin_OpenVR; public sealed class ConfirmationFlyout : Flyout { - private Button ConfirmButton { get; } - private Button CancelButton { get; } - private bool ConfirmationFlyoutResult { get; set; } - - private static SemaphoreSlim FlyoutExitSemaphore { get; } = new(0); - - public static async Task HandleButtonConfirmationFlyout( - UIElement showAtElement, IAmethystHost host, - string content, string confirmButtonText, string cancelButtonText) - { - var flyout = new ConfirmationFlyout(content, confirmButtonText, cancelButtonText); - - flyout.Closed += (_, _) => FlyoutExitSemaphore.Release(); - flyout.Opening += (_, _) => host?.PlayAppSound(SoundType.Show); - flyout.Closing += (_, _) => host?.PlayAppSound(SoundType.Hide); - - // Show the confirmation flyout - flyout.ShowAt(showAtElement, new FlyoutShowOptions { Placement = FlyoutPlacementMode.Bottom }); - - // Wait for the flyout to close - await FlyoutExitSemaphore.WaitAsync(); - - // Wait a bit - await Task.Delay(1200); - - // Return the result - return flyout.ConfirmationFlyoutResult; - } - public ConfirmationFlyout(string content, string confirmButtonText, string cancelButtonText) { ConfirmButton = new Button { Content = confirmButtonText, Visibility = (Visibility)Convert.ToInt32(string.IsNullOrEmpty(confirmButtonText)), - FontSize = 15, FontWeight = FontWeights.SemiBold, + FontSize = 15, + FontWeight = FontWeights.SemiBold, HorizontalContentAlignment = HorizontalAlignment.Center, VerticalContentAlignment = VerticalAlignment.Center, - Height = 33, Margin = new Thickness(0, 10, 5, 0), + Height = 33, + Margin = new Thickness(0, 10, 5, 0), CornerRadius = new CornerRadius(4), HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch, @@ -73,10 +40,12 @@ public ConfirmationFlyout(string content, string confirmButtonText, string cance { Content = cancelButtonText, Visibility = (Visibility)Convert.ToInt32(string.IsNullOrEmpty(cancelButtonText)), - FontSize = 15, FontWeight = FontWeights.SemiBold, + FontSize = 15, + FontWeight = FontWeights.SemiBold, HorizontalContentAlignment = HorizontalAlignment.Center, VerticalContentAlignment = VerticalAlignment.Center, - Height = 33, Margin = new Thickness(5, 10, 0, 0), + Height = 33, + Margin = new Thickness(5, 10, 0, 0), CornerRadius = new CornerRadius(4), HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch @@ -125,4 +94,33 @@ public ConfirmationFlyout(string content, string confirmButtonText, string cance Grid.SetColumn(ConfirmButton, 0); Grid.SetColumn(CancelButton, 1); } + + private Button ConfirmButton { get; } + private Button CancelButton { get; } + private bool ConfirmationFlyoutResult { get; set; } + + private static SemaphoreSlim FlyoutExitSemaphore { get; } = new(0); + + public static async Task HandleButtonConfirmationFlyout( + UIElement showAtElement, IAmethystHost host, + string content, string confirmButtonText, string cancelButtonText) + { + var flyout = new ConfirmationFlyout(content, confirmButtonText, cancelButtonText); + + flyout.Closed += (_, _) => FlyoutExitSemaphore.Release(); + flyout.Opening += (_, _) => host?.PlayAppSound(SoundType.Show); + flyout.Closing += (_, _) => host?.PlayAppSound(SoundType.Hide); + + // Show the confirmation flyout + flyout.ShowAt(showAtElement, new FlyoutShowOptions { Placement = FlyoutPlacementMode.Bottom }); + + // Wait for the flyout to close + await FlyoutExitSemaphore.WaitAsync(); + + // Wait a bit + await Task.Delay(1200); + + // Return the result + return flyout.ConfirmationFlyoutResult; + } } \ No newline at end of file diff --git a/plugin_OpenVR/CoreSetup.cs b/plugin_OpenVR/CoreSetup.cs index 423106f..1d52cd1 100644 --- a/plugin_OpenVR/CoreSetup.cs +++ b/plugin_OpenVR/CoreSetup.cs @@ -34,17 +34,17 @@ internal class DriverInstaller : IDependencyInstaller public List ListDependencies() { - return new List - { + return + [ new VrDriver { Host = Host, Name = Host?.RequestLocalizedString("/Dependencies/Driver") ?? "OpenVR Driver" } - }; + ]; } - public List ListFixes() => new(); + public List ListFixes() => []; } internal class VrDriver : IDependency diff --git a/plugin_OpenVR/EvrInput.cs b/plugin_OpenVR/EvrInput.cs index 5f1ed9e..ac93256 100644 --- a/plugin_OpenVR/EvrInput.cs +++ b/plugin_OpenVR/EvrInput.cs @@ -1,426 +1,332 @@ -using System.Diagnostics.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; +using System.Numerics; using System.Reflection; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Amethyst.Plugins.Contract; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; +using Newtonsoft.Json; +using plugin_OpenVR.Utils; using Valve.VR; namespace plugin_OpenVR; -public static class EvrInput +public class ActionsManifest(bool wasNull = false) { - // Action strings set in action_manifest.json + [JsonIgnore] public bool WasNull { get; set; } = wasNull; - // Required - private const string KActionSetDefault = "/actions/default"; // Default - - private const string KActionLeftJoystick = "/actions/default/in/LeftJoystick"; // Left-hand Move/Rotate Controls - private const string KActionRightJoystick = "/actions/default/in/RightJoystick"; // Right-hand Move/Rotate Controls - - private const string KActionConfirmAndSave = "/actions/default/in/ConfirmAndSave"; // Confirm and Save - private const string KActionModeSwap = "/actions/default/in/ModeSwap"; // Swap Move/Rotate Modes - private const string KActionFineTune = "/actions/default/in/FineTune"; // Fine-tuning - - // Optional - private const string KActionTrackerFreeze = "/actions/default/in/TrackerFreeze"; // Freeze Trackers - private const string KActionFlipToggle = "/actions/default/in/FlipToggle"; // Toggle Flip - - // Main SteamEVRInput class - public class SteamEvrInput + [JsonIgnore] + public bool IsValid { - private IAmethystHost Host { get; set; } - - [SetsRequiredMembers] - public SteamEvrInput(IAmethystHost host) + get { - Host = host; + var defaults = new ActionsManifest(); + return defaults.Defaults.All(Defaults.Contains) && + defaults.Actions.All(Actions.Contains) && + defaults.Sets.All(Sets.Contains) && + defaults.Localization.All(Localization.Contains); } + } - private static (uint Left, uint Right) VrControllerIndexes => ( - SteamVR.Initialized - ? OpenVR.System?.GetTrackedDeviceIndexForControllerRole(ETrackedControllerRole.LeftHand) ?? - OpenVR.k_unTrackedDeviceIndexInvalid - : OpenVR.k_unTrackedDeviceIndexInvalid, - SteamVR.Initialized - ? OpenVR.System?.GetTrackedDeviceIndexForControllerRole(ETrackedControllerRole.RightHand) ?? - OpenVR.k_unTrackedDeviceIndexInvalid - : OpenVR.k_unTrackedDeviceIndexInvalid - ); - - // Action manifest path - private const string MActionManifestPath = "action_manifest.json"; - - // Buttons data - private InputDigitalActionData_t - _mConfirmAndSaveData, - _mModeSwapData, - _mFineTuneData, - _mTrackerFreezeData, - _mFlipToggleData; - - private ulong _mConfirmAndSaveHandler; - - // The action sets - private VRActiveActionSet_t - _mDefaultActionSet; - - // Tracking Default set - private ulong _mDefaultSetHandler; - private ulong _mFineTuneHandler; - private ulong _mFlipToggleHandler; - - // Calibration actions - private ulong _mLeftJoystickHandler; - - // Analogs data - private InputAnalogActionData_t - _mLeftJoystickHandlerData, - _mRightJoystickHandlerData; - - private ulong _mModeSwapHandler; - private ulong _mRightJoystickHandler; - - // Tracking freeze actions - private ulong _mTrackerFreezeHandler; - - // Analog data poll - public InputAnalogActionData_t LeftJoystickActionData => _mLeftJoystickHandlerData; - - public InputAnalogActionData_t RightJoystickActionData => _mRightJoystickHandlerData; - - // Digital data poll - public InputDigitalActionData_t ConfirmAndSaveActionData => _mConfirmAndSaveData; - - public InputDigitalActionData_t ModeSwapActionData => _mModeSwapData; - - public InputDigitalActionData_t FineTuneActionData => _mFineTuneData; - - public InputDigitalActionData_t TrackerFreezeActionData => _mTrackerFreezeData; - - public InputDigitalActionData_t TrackerFlipToggleData => _mFlipToggleData; - - // Note: SteamVR must be initialized beforehand. - // Preferred type is (vr::VRApplication_Scene) - public bool InitInputActions() + [JsonProperty("default_bindings")] + public List Defaults { get; set; } = + [ + new("oculus_touch", "input_profiles/k2vr.amethyst_oculus_touch.json"), + new("knuckles", "input_profiles/k2vr.amethyst_knuckles.json"), + new("vive_controller", "input_profiles/k2vr.amethyst_vive_controller.json"), + new("hpmotioncontroller", "input_profiles/k2vr.amethyst_hpmotioncontroller.json"), + new("holographic_controller", "input_profiles/k2vr.amethyst_holographic_controller.json") + ]; + + [JsonProperty("actions")] + public List Actions { get; set; } = + [ + new("/actions/default/in/TrackerFreeze", "boolean", "optional"), + new("/actions/default/in/FlipToggle", "boolean", "optional"), + new("/actions/default/in/LeftJoystick", "vector2"), + new("/actions/default/in/RightJoystick", "vector2"), + new("/actions/default/in/ConfirmAndSave"), + new("/actions/default/in/ModeSwap"), + new("/actions/default/in/FineTune") + ]; + + [JsonProperty("action_sets")] + public List> Sets { get; set; } = + [ + new() { - if (!SteamVR.Initialized || OpenVR.Input is null) return false; // Sanity check - - // Find the absolute path of manifest - var absoluteManifestPath = - Path.Join(GetProgramLocation().DirectoryName, MActionManifestPath); - - if (!File.Exists(absoluteManifestPath)) - { - Host.Log("Action manifest was not found in the program " + - $"({GetProgramLocation().Directory}) directory.", LogSeverity.Error); - return false; // Return failure status - } - - // Set the action manifest. This should be in the executable directory. - // Defined by m_actionManifestPath. - var error = OpenVR.Input.SetActionManifestPath(absoluteManifestPath); - if (error != EVRInputError.None) - { - Host.Log($"Action manifest error: {error}", LogSeverity.Error); - return false; - } + { "name", "/actions/default" }, + { "usage", "leftright" } + } + ]; - /**********************************************/ - // Here, setup every action with its handler - /**********************************************/ + [JsonProperty("localization")] + public List> Localization { get; set; } = + [ + new() + { + { "language_tag", "en_US" }, + { "/actions/default", "Input Actions" }, + { "/actions/default/in/TrackerFreeze", "Freeze Trackers" }, + { "/actions/default/in/FlipToggle", "Toggle Flip" }, + { "/actions/default/in/LeftJoystick", "Left-hand Move/Rotate Controls" }, + { "/actions/default/in/RightJoystick", "Right-hand Move/Rotate Controls" }, + { "/actions/default/in/ConfirmAndSave", "Confirm and Save" }, + { "/actions/default/in/ModeSwap", "Swap Move/Rotate Modes" }, + { "/actions/default/in/FineTune", "Fine-tuning" } + } + ]; - // Get action handle for Left Joystick - error = OpenVR.Input.GetActionHandle(KActionLeftJoystick, ref _mLeftJoystickHandler); - if (error != EVRInputError.None) - { - Host.Log("Action handle error: {error}", LogSeverity.Error); - return false; - } + public class DefaultBindings(string controllerType = "", string path = "") + { + [JsonProperty("controller_type")] public string ControllerType { get; set; } = controllerType; + [JsonProperty("binding_url")] public string Binding { get; set; } = path; + } - // Get action handle for Right Joystick - error = OpenVR.Input.GetActionHandle(KActionRightJoystick, ref _mRightJoystickHandler); - if (error != EVRInputError.None) - { - Host.Log("Action handle error: {error}", LogSeverity.Error); - return false; - } + public class Action(string name = "", string type = "boolean", string requirement = null, IAmethystHost host = null) + { + [JsonProperty("name")] public string Name { get; set; } = name; + [JsonProperty("type")] public string Type { get; set; } = type; + + [JsonProperty("requirement", NullValueHandling = NullValueHandling.Ignore)] + public string Requirement { get; set; } = requirement; + + [JsonIgnore] public IAmethystHost Host { get; set; } = host; + [JsonIgnore] private ulong Handle { get; set; } + [JsonIgnore] public bool Data => DataDigital.bState; + [JsonIgnore] public Vector2 State => new(DataAnalog.x, DataAnalog.y); + [JsonIgnore] private InputDigitalActionData_t DataDigital { get; set; } + [JsonIgnore] private InputAnalogActionData_t DataAnalog { get; set; } + [JsonIgnore] public bool Valid => Host is not null && !string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Type); + + [JsonIgnore] + public string Code + { + get => Host?.PluginSettings.GetSetting(Handle, string.Empty); + set => Host?.PluginSettings.SetSetting(Handle, value); + } - // Get action handle for Confirm And Save - error = OpenVR.Input.GetActionHandle(KActionConfirmAndSave, ref _mConfirmAndSaveHandler); - if (error != EVRInputError.None) - { - Host.Log("Action handle error: {error}", LogSeverity.Error); - return false; - } + public async Task Invoke(object data) + { + // Exit with a custom message to be shown by the crash handler + Host?.Log($"Trying to evaluate expression \"{Code}\" for data \"{data}\"..."); - // Get action handle for Mode Swap - error = OpenVR.Input.GetActionHandle(KActionModeSwap, ref _mModeSwapHandler); - if (error != EVRInputError.None) + try { - Host.Log("Action handle error: {error}", LogSeverity.Error); - return false; + return (await CSharpScript.EvaluateAsync($"var data = {data};{Code.Trim()}", + ScriptOptions.Default //.WithImports("Amethyst.Classes") + .WithReferences(typeof(IAmethystHost).Assembly) + .WithReferences(typeof(SteamVR).Assembly) + .AddImports("System.Linq"))).ToString(); } - - // Get action handle for Fine-tuning - error = OpenVR.Input.GetActionHandle(KActionFineTune, ref _mFineTuneHandler); - if (error != EVRInputError.None) + catch (Exception ex) { - Host.Log("Action handle error: {error}", LogSeverity.Error); - return false; + return $"Evaluation error: '{ex}'"; } + } - // Get action handle for Tracker Freeze - error = OpenVR.Input.GetActionHandle(KActionTrackerFreeze, ref _mTrackerFreezeHandler); - if (error != EVRInputError.None) - { - Host.Log("Action handle error: {error}", LogSeverity.Error); - return false; - } + public string GetName(ActionsManifest json, string languageCode) + { + return json?.Localization + .FirstOrDefault(x => x.TryGetValue("language_tag", out var language) && + language.Contains(languageCode), json?.Localization.FirstOrDefault()) + ?.TryGetValue(Name ?? string.Empty, out var name) ?? false + ? name + : Name ?? string.Empty; + } - // Get action handle for Flip Toggle - error = OpenVR.Input.GetActionHandle(KActionFlipToggle, ref _mFlipToggleHandler); - if (error != EVRInputError.None) - { - Host.Log("Action handle error: {error}", LogSeverity.Error); - return false; - } + public EVRInputError Register() + { + var pHandle = Handle; + var error = OpenVR.Input.GetActionHandle(Name, ref pHandle); - /**********************************************/ - // Here, setup every action set handle - /**********************************************/ + Handle = pHandle; + return error; + } - // Get set handle Default Set - error = OpenVR.Input.GetActionSetHandle(KActionSetDefault, ref _mDefaultSetHandler); - if (error != EVRInputError.None) + public bool UpdateState(IAmethystHost host) + { + return Type switch { - Host.Log("ActionSet handle error: {error}", LogSeverity.Error); - return false; - } - - /**********************************************/ - // Here, setup action-set handler - /**********************************************/ - - // Default Set - _mDefaultActionSet.ulActionSet = _mDefaultSetHandler; - _mDefaultActionSet.ulRestrictedToDevice = OpenVR.k_ulInvalidInputValueHandle; - _mDefaultActionSet.nPriority = 0; - - // Return OK - Host.Log("EVR Input Actions initialized OK"); - return true; + "boolean" => GetDigitalState(host), + "vector2" => GetAnalogState(host), + _ => false + }; } - // Update Left Joystick Action - private bool GetLeftJoystickState() + private bool GetDigitalState(IAmethystHost host) { if (!SteamVR.Initialized || OpenVR.Input is null) return false; // Sanity check - // Update the action and grab data - var error = OpenVR.Input.GetAnalogActionData( - _mLeftJoystickHandler, - ref _mLeftJoystickHandlerData, + var pData = DataDigital; + var error = OpenVR.Input.GetDigitalActionData( + Handle, ref pData, (uint)Marshal.SizeOf(), OpenVR.k_ulInvalidInputValueHandle); - // Return OK - if (error == EVRInputError.None) return true; + DataDigital = pData; - Host.Log($"GetAnalogActionData call error: {error}", LogSeverity.Error); + if (error == EVRInputError.None) return DataDigital.bState; + host.Log($"GetDigitalActionData call error: {error}", LogSeverity.Error); return false; } - // Update Right Joystick Action - private bool GetRightJoystickState() + private bool GetAnalogState(IAmethystHost host) { if (!SteamVR.Initialized || OpenVR.Input is null) return false; // Sanity check - // Update the action and grab data + var pData = DataAnalog; var error = OpenVR.Input.GetAnalogActionData( - _mRightJoystickHandler, - ref _mRightJoystickHandlerData, + Handle, ref pData, (uint)Marshal.SizeOf(), OpenVR.k_ulInvalidInputValueHandle); - // Return OK - if (error == EVRInputError.None) return true; + DataAnalog = pData; - Host.Log($"GetAnalogActionData call error: {error}", LogSeverity.Error); + if (error == EVRInputError.None) return DataDigital.bState; + host.Log($"GetAnalogActionData call error: {error}", LogSeverity.Error); return false; } + } - // Update Confirm And Save Action - private bool GetConfirmAndSaveState() - { - if (!SteamVR.Initialized || OpenVR.Input is null) return false; // Sanity check + public Action this[string path] => Actions.FirstOrDefault(x => x.Name == path); +} - // Update the action and grab data - var error = OpenVR.Input.GetDigitalActionData( - _mConfirmAndSaveHandler, - ref _mConfirmAndSaveData, - (uint)Marshal.SizeOf(), - OpenVR.k_ulInvalidInputValueHandle); +[method: SetsRequiredMembers] +public class SteamEvrInput(IAmethystHost host) +{ + private IAmethystHost Host { get; set; } = host; + public ActionsManifest RegisteredActions { get; set; } = new(); + + public bool TrackerFreezeActionData => RegisteredActions["/actions/default/in/TrackerFreeze"]?.Data ?? false; + public bool TrackerFlipToggleData => RegisteredActions["/actions/default/in/FlipToggle"]?.Data ?? false; + public bool ConfirmAndSaveActionData => RegisteredActions["/actions/default/in/ConfirmAndSave"]?.Data ?? false; + public bool ModeSwapActionData => RegisteredActions["/actions/default/in/ModeSwap"]?.Data ?? false; + public bool FineTuneActionData => RegisteredActions["/actions/default/in/FineTune"]?.Data ?? false; + public Vector2 LeftJoystickActionData => RegisteredActions["/actions/default/in/LeftJoystick"]?.State ?? Vector2.Zero; + public Vector2 RightJoystickActionData => RegisteredActions["/actions/default/in/RightJoystick"]?.State ?? Vector2.Zero; + + private static (uint Left, uint Right) VrControllerIndexes => ( + SteamVR.Initialized + ? OpenVR.System?.GetTrackedDeviceIndexForControllerRole(ETrackedControllerRole.LeftHand) ?? + OpenVR.k_unTrackedDeviceIndexInvalid + : OpenVR.k_unTrackedDeviceIndexInvalid, + SteamVR.Initialized + ? OpenVR.System?.GetTrackedDeviceIndexForControllerRole(ETrackedControllerRole.RightHand) ?? + OpenVR.k_unTrackedDeviceIndexInvalid + : OpenVR.k_unTrackedDeviceIndexInvalid + ); + + // The action sets + private VRActiveActionSet_t _mDefaultActionSet; + + // Note: SteamVR must be initialized beforehand. + // Preferred type is (vr::VRApplication_Scene) + public bool InitInputActions() + { + if (!SteamVR.Initialized || OpenVR.Input is null || Host is null) return false; // Sanity check - // Return OK - if (error == EVRInputError.None) return true; + var manifestPath = Path.Join(PackageUtils.GetAmethystAppDataPath(), "Amethyst", "actions.json"); + RegisteredActions = JsonConvert.DeserializeObject( + File.ReadAllText(manifestPath)) ?? new ActionsManifest(true); - Host.Log($"GetDigitalActionData call error: {error}", LogSeverity.Error); - return false; - } + if (RegisteredActions.WasNull || !RegisteredActions.IsValid) // Re-generate the action manifest if it's not found + File.WriteAllText(manifestPath, JsonConvert.SerializeObject(RegisteredActions, Formatting.Indented)); - // Update Mode Swap Action - private bool GetModeSwapState() + if (!File.Exists(manifestPath)) { - if (!SteamVR.Initialized || OpenVR.Input is null) return false; // Sanity check - - // Update the action and grab data - var error = OpenVR.Input.GetDigitalActionData( - _mModeSwapHandler, - ref _mModeSwapData, - (uint)Marshal.SizeOf(), - OpenVR.k_ulInvalidInputValueHandle); - - // Return OK - if (error == EVRInputError.None) return true; - - Host.Log($"GetDigitalActionData call error: {error}", LogSeverity.Error); - return false; + Host.Log("Action manifest was not found in the program " + + $"({GetProgramLocation().Directory}) directory.", LogSeverity.Error); + return false; // Return failure status } - // Update Fine Tune Action - private bool GetFineTuneState() + // Set the action manifest. This should be in the executable directory. + // Defined by m_actionManifestPath. + var error = OpenVR.Input.SetActionManifestPath(manifestPath); + if (error != EVRInputError.None) { - if (!SteamVR.Initialized || OpenVR.Input is null) return false; // Sanity check - - // Update the action and grab data - var error = OpenVR.Input.GetDigitalActionData( - _mFineTuneHandler, - ref _mFineTuneData, - (uint)Marshal.SizeOf(typeof(InputDigitalActionData_t)), - OpenVR.k_ulInvalidInputValueHandle); - - // Return OK - if (error == EVRInputError.None) return true; - - Host.Log($"GetDigitalActionData call error: {error}", LogSeverity.Error); + Host.Log($"Action manifest error: {error}", LogSeverity.Error); return false; } - // Update Tracker Freeze Action - private bool GetTrackerFreezeState() - { - if (!SteamVR.Initialized || OpenVR.Input is null) return false; // Sanity check + /**********************************************/ + // Here, setup every action with its handler + /**********************************************/ - // Update the action and grab data - var error = OpenVR.Input.GetDigitalActionData( - _mTrackerFreezeHandler, - ref _mTrackerFreezeData, - (uint)Marshal.SizeOf(), - OpenVR.k_ulInvalidInputValueHandle); + // Get action handles for all actions + RegisteredActions.Actions.ForEach(x => x.Register()); - // Return OK - if (error == EVRInputError.None) return true; + /**********************************************/ + // Here, setup every action set handle + /**********************************************/ - Host.Log($"GetDigitalActionData call error: {error}", LogSeverity.Error); - return false; - } - - // Update Tracker Freeze Action - private bool GetFlipToggleState() + // Get set handle Default Set + ulong defaultSetHandler = 0; + error = OpenVR.Input.GetActionSetHandle("/actions/default", ref defaultSetHandler); + if (error != EVRInputError.None) { - if (!SteamVR.Initialized || OpenVR.Input is null) return false; // Sanity check - - // Update the action and grab data - var error = OpenVR.Input.GetDigitalActionData( - _mFlipToggleHandler, - ref _mFlipToggleData, - (uint)Marshal.SizeOf(), - OpenVR.k_ulInvalidInputValueHandle); - - // Return OK - if (error == EVRInputError.None) return true; - - Host.Log($"GetDigitalActionData call error: {error}", LogSeverity.Error); + Host.Log("ActionSet handle error: {error}", LogSeverity.Error); return false; } - public bool UpdateActionStates() - { - if (!SteamVR.Initialized || OpenVR.Input is null) return false; // Sanity check + /**********************************************/ + // Here, setup action-set handler + /**********************************************/ - /**********************************************/ - // Check if VR controllers are valid - /**********************************************/ + // Default Set + _mDefaultActionSet.ulActionSet = defaultSetHandler; + _mDefaultActionSet.ulRestrictedToDevice = OpenVR.k_ulInvalidInputValueHandle; + _mDefaultActionSet.nPriority = 0; - if (VrControllerIndexes.Left == OpenVR.k_unTrackedDeviceIndexInvalid || - VrControllerIndexes.Right == OpenVR.k_unTrackedDeviceIndexInvalid) - return true; // Say it's all good, refuse to elaborate, leave - - /**********************************************/ - // Here, update main action sets' handles - /**********************************************/ + // Return OK + Host.Log("EVR Input Actions initialized OK"); + return true; + } - // Update Default ActionSet states - var error = OpenVR.Input.UpdateActionState( - new[] { _mDefaultActionSet }, - (uint)Marshal.SizeOf()); + public bool UpdateActionStates() + { + if (!SteamVR.Initialized || OpenVR.Input is null) return false; // Sanity check - if (error != EVRInputError.None) - { - Host.Log($"ActionSet (Default) state update error: {error}", LogSeverity.Error); - return false; - } + /**********************************************/ + // Check if VR controllers are valid + /**********************************************/ - /**********************************************/ - // Here, update the actions and grab data-s - /**********************************************/ + if (VrControllerIndexes.Left == OpenVR.k_unTrackedDeviceIndexInvalid || + VrControllerIndexes.Right == OpenVR.k_unTrackedDeviceIndexInvalid) + return true; // Say it's all good, refuse to elaborate, leave - // Update the left joystick - if (!GetLeftJoystickState()) - { - Host.Log("Left Joystick Action is not active, can't update!", LogSeverity.Error); - return false; - } + /**********************************************/ + // Here, update main action sets' handles + /**********************************************/ - // Update the right joystick - if (!GetRightJoystickState()) - { - Host.Log("Right Joystick Action is not active, can't update!", LogSeverity.Error); - return false; - } + // Update Default ActionSet states + var error = OpenVR.Input.UpdateActionState( + [_mDefaultActionSet], + (uint)Marshal.SizeOf()); - // Update the confirm and save - if (!GetConfirmAndSaveState()) - { - Host.Log("Confirm And Save Action is not active, can't update!", LogSeverity.Error); - return false; - } - - // Update the mode swap - if (!GetModeSwapState()) - { - Host.Log("Mode Swap Action is not active, can't update!", LogSeverity.Error); - return false; - } - - // Update the fine tune - if (!GetFineTuneState()) - { - Host.Log("Fine-tuning Action is not active, can't update!", LogSeverity.Error); - return false; - } + // ReSharper disable once InvertIf + if (error != EVRInputError.None) + { + Host.Log($"ActionSet (Default) state update error: {error}", LogSeverity.Error); + return false; + } - // Update the freeze - // This time without checks, since this one is optional - GetTrackerFreezeState(); + /**********************************************/ + // Here, update the actions and grab data-s + /**********************************************/ - // Return OK - return true; - } + return RegisteredActions.Actions.All(x => x.UpdateState(Host) || x.Requirement is "optional"); + } - public static FileInfo GetProgramLocation() - { - return new FileInfo(Assembly.GetExecutingAssembly().Location); - } + public static FileInfo GetProgramLocation() + { + return new FileInfo(Assembly.GetExecutingAssembly().Location); } } \ No newline at end of file diff --git a/plugin_OpenVR/OpenVR.cs b/plugin_OpenVR/OpenVR.cs index 266f6ae..f54f680 100644 --- a/plugin_OpenVR/OpenVR.cs +++ b/plugin_OpenVR/OpenVR.cs @@ -6,27 +6,19 @@ using System.ComponentModel; using System.ComponentModel.Composition; using System.Diagnostics; -using System.IO; -using System.IO.Pipes; using System.Linq; using System.Numerics; -using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; -using Windows.ApplicationModel; -using Windows.Data.Json; using Amethyst.Plugins.Contract; -using Microsoft.UI.Text; -using Microsoft.UI.Xaml; +using com.driver_Amethyst; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Controls.Primitives; -using Newtonsoft.Json; +using plugin_OpenVR.MVVM; +using plugin_OpenVR.Pages; using plugin_OpenVR.Utils; using Valve.VR; -using Windows.Storage; -using com.driver_Amethyst; using Vanara.PInvoke; #pragma warning disable VSTHRD002 // Avoid problematic synchronous waits @@ -46,7 +38,7 @@ namespace plugin_OpenVR; public class SteamVR : IServiceEndpoint { private IDriverService _driverService; - private EvrInput.SteamEvrInput _evrInput; + private SteamEvrInput _evrInput; private uint _vrNotificationId; private ulong _vrOverlayHandle = OpenVR.k_ulOverlayHandleInvalid; @@ -55,10 +47,7 @@ public class SteamVR : IServiceEndpoint private bool PluginLoaded { get; set; } private Page InterfaceRoot { get; set; } - private Button ReManifestButton { get; set; } - private Button ReRegisterButton { get; set; } - private Flyout ActionFailedFlyout { get; set; } - private ProgressBar ReRegisterButtonBar { get; set; } + private SettingsPage Settings { get; set; } private Vector3 VrPlayspaceTranslation => OpenVR.System.GetRawZeroPoseToStandingAbsoluteTrackingPose().GetPosition(); @@ -117,6 +106,16 @@ 1 when VrHelper.IsCurrentProcessElevated() => } : $"Undefined: {ServiceStatus}\nE_UNDEFINED\nSomething weird has happened, though we can't tell what."; + public List CustomInputActions = + [ + new() { DataType = typeof(bool), Handle = "/actions/default/in/UserBooleanAction1" }, + new() { DataType = typeof(bool), Handle = "/actions/default/in/UserBooleanAction2" }, + new() { DataType = typeof(bool), Handle = "/actions/default/in/UserBooleanAction3" }, + new() { DataType = typeof(bool), Handle = "/actions/default/in/UserBooleanAction4" }, + new() { DataType = typeof(Vector2), Handle = "/actions/default/in/UserCoordAction1" }, + new() { DataType = typeof(Vector2), Handle = "/actions/default/in/UserCoordAction2" } + ]; + public Uri ErrorDocsUri => new(ServiceStatus switch { -10 => $"https://docs.k2vr.tech/{Host?.DocsLanguageCode ?? "en"}/app/steamvr-driver-codes/#2", @@ -124,23 +123,24 @@ 1 when VrHelper.IsCurrentProcessElevated() => _ => $"https://docs.k2vr.tech/{Host?.DocsLanguageCode ?? "en"}/app/steamvr-driver-codes/#6" }); + public Dictionary> SupportedInputActions => []; + public SortedSet AdditionalSupportedTrackerTypes => - new() - { - TrackerType.TrackerHanded, - // TrackerType.TrackerLeftFoot, // Already OK - // TrackerType.TrackerRightFoot, // Already OK - TrackerType.TrackerLeftShoulder, - TrackerType.TrackerRightShoulder, - TrackerType.TrackerLeftElbow, - TrackerType.TrackerRightElbow, - TrackerType.TrackerLeftKnee, - TrackerType.TrackerRightKnee, - // TrackerType.TrackerWaist, // Already OK - TrackerType.TrackerChest, - TrackerType.TrackerCamera, - TrackerType.TrackerKeyboard - }; + [ + TrackerType.TrackerHanded, + // TrackerType.TrackerLeftFoot, // Already OK + // TrackerType.TrackerRightFoot, // Already OK + TrackerType.TrackerLeftShoulder, + TrackerType.TrackerRightShoulder, + TrackerType.TrackerLeftElbow, + TrackerType.TrackerRightElbow, + TrackerType.TrackerLeftKnee, + TrackerType.TrackerRightKnee, + // TrackerType.TrackerWaist, // Already OK + TrackerType.TrackerChest, + TrackerType.TrackerCamera, + TrackerType.TrackerKeyboard + ]; public bool IsRestartOnChangesNeeded => true; @@ -158,7 +158,7 @@ public bool AutoStartAmethyst set { if (!Initialized || OpenVR.Applications is null) return; // Sanity check - InstallVrApplicationManifest(); // Just in case + Settings?.InstallVrApplicationManifest(); // Just in case var appError = OpenVR.Applications.SetApplicationAutoLaunch("K2VR.Amethyst", value); if (appError != EVRApplicationError.None) @@ -224,115 +224,14 @@ public string TrackingSystemName public void OnLoad() { - _evrInput ??= new EvrInput.SteamEvrInput(Host); - - ReManifestButton = new Button - { - Content = new TextBlock - { - Text = Host.RequestLocalizedString("/SettingsPage/Buttons/ReManifest"), - TextTrimming = TextTrimming.CharacterEllipsis, - FontSize = 16, FontWeight = FontWeights.SemiBold - }, - Height = 40, HorizontalAlignment = HorizontalAlignment.Stretch, - Margin = new Thickness { Right = 6 } - }; - - ReRegisterButtonBar = new ProgressBar - { - IsIndeterminate = true, Opacity = 0.0, - Margin = new Thickness(-11, 0, -11, -9), - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Bottom, - OpacityTransition = new ScalarTransition() - }; - - ReRegisterButton = new Button - { - Content = new Grid - { - Children = - { - new TextBlock - { - Text = Host.RequestLocalizedString("/SettingsPage/Buttons/ReRegister"), - TextTrimming = TextTrimming.CharacterEllipsis, - FontSize = 16, FontWeight = FontWeights.SemiBold, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - }, - ReRegisterButtonBar - }, - HorizontalAlignment = HorizontalAlignment.Stretch, - VerticalAlignment = VerticalAlignment.Bottom - }, - Height = 40, Margin = new Thickness { Left = 6 }, - HorizontalAlignment = HorizontalAlignment.Stretch, - HorizontalContentAlignment = HorizontalAlignment.Stretch - }; - - Grid.SetColumn(ReManifestButton, 0); - ReManifestButton.Click += (_, _) => - { - switch (InstallVrApplicationManifest()) - { - // Not found failure - case -2: - { - ActionFailedFlyout.Content = new TextBlock - { - FontWeight = FontWeights.SemiBold, - Text = Host.RequestLocalizedString("/SettingsPage/ReManifest/Error/NotFound") - }; - - ActionFailedFlyout.ShowAt(ReManifestButton, new FlyoutShowOptions - { - Placement = FlyoutPlacementMode.BottomEdgeAlignedLeft - }); - break; - } - - // SteamVR failure - case 1: - { - ActionFailedFlyout.Content = new TextBlock - { - FontWeight = FontWeights.SemiBold, - Text = Host.RequestLocalizedString("/SettingsPage/ReManifest/Error/Other") - }; - - ActionFailedFlyout.ShowAt(ReManifestButton, new FlyoutShowOptions - { - Placement = FlyoutPlacementMode.BottomEdgeAlignedRight - }); - break; - } - } - - // Play a sound - Host?.PlayAppSound(SoundType.Invoke); - }; - - Grid.SetColumn(ReRegisterButton, 1); - ReRegisterButton.Click += ReRegisterButton_Click; - + _evrInput ??= new SteamEvrInput(Host); + Settings ??= new SettingsPage { DataParent = this, Host = Host }; InterfaceRoot = new Page { - Content = new Grid - { - Children = { ReManifestButton, ReRegisterButton }, - ColumnDefinitions = - { - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, - new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) } - } - } + Content = Settings }; - ActionFailedFlyout = new Flyout(); - ActionFailedFlyout.Opening += (_, _) => Host?.PlayAppSound(SoundType.Show); - ActionFailedFlyout.Closing += (_, _) => Host?.PlayAppSound(SoundType.Hide); - + CustomInputActions.ForEach(x => x.Host ??= Host); PluginLoaded = true; } @@ -352,7 +251,7 @@ public int Initialize() } // Install the manifest - InstallVrApplicationManifest(); + Settings?.InstallVrApplicationManifest(); // Update bindings UpdateBindingTexts(); @@ -565,6 +464,12 @@ public TrackerBase GetTrackerPose(string contains, bool canBeFromAmethyst = true } } + public Task ProcessKeyInput(IKeyInputAction action, object data, TrackerType? receiver, CancellationToken? token = null) + { + Host?.Log("Input actions are not supported with emulation disabled."); + return Task.CompletedTask; + } + public async Task<(int Status, string StatusMessage, long PingTime)> TestConnection() { try @@ -598,351 +503,6 @@ public TrackerBase GetTrackerPose(string contains, bool canBeFromAmethyst = true } } - private async void ReRegisterButton_Click(object o, RoutedEventArgs routedEventArgs) - { - // Play a sound - Host?.PlayAppSound(SoundType.Invoke); - - VrHelper helper = new(); - OpenVrPaths openVrPaths; - var resultPaths = helper.UpdateSteamPaths(); - - // Check if SteamVR was found - if (!resultPaths.Exists.SteamExists) - { - // Critical, cry about it - await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, - Host?.RequestLocalizedString("/CrashHandler/ReRegister/SteamVRNotFound"), "", ""); - return; - } - - try // Try-Catch it - { - // Read the OpenVRPaths - openVrPaths = OpenVrPaths.Read(); - } - catch (Exception) - { - // Critical, cry about it - await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, - Host?.RequestLocalizedString("/CrashHandler/ReRegister/OpenVRPathsError"), "", ""); - return; - } - - /* - * ReRegister Logic: - * - * Search for Amethyst VRDriver in the crash handler's directory - * and 2 folders up in tree, recursively. (Find the manifest) - * - * If the manifest & dll are found, check and ask to close SteamVR - * - * With closed SteamVR, search for all remaining 'driver_Amethyst' instances: - * copied inside /drivers/ or registered. If found, ask to delete them - * - * When everything is purified, we can register the 'driver_Amethyst' - * via OpenVRPaths and then check twice if it's there ready to go - * - * If the previous steps succeeded, we can enable the 'driver_Amethyst' - * in VRSettings. A run failure/exception of this one isn't critical - */ - - // Show the "working" progress bar - ReRegisterButtonBar.Opacity = 1.0; - - /* 1 */ - - // Create a placeholder for the driver path - var localAmethystDriverPath = ""; - - // Check whether Amethyst is installed as a package - if (!PackageUtils.IsAmethystPackaged) - { - // Optionally change to the other variant - if (!new DirectoryInfo(localAmethystDriverPath).Exists) - { - // Get plugin_OpenVR.dll parent path - var parentPath = Directory.GetParent(Assembly.GetExecutingAssembly().Location); - - // Search for driver manifests, try max 2 times - for (var i = 0; i < 2; i++) - { - // Double that to get Amethyst exe path - if (parentPath?.Parent != null) parentPath = parentPath.Parent; - if (parentPath is null) goto p_search_loop_end; - - // Find all vr driver manifests there - var allLocalDriverManifests = Directory.GetFiles(parentPath.ToString(), - "driver.vrdrivermanifest", SearchOption.AllDirectories); - - // For each found manifest, check if there is an ame driver dll inside - foreach (var localDriverManifest in allLocalDriverManifests) - if (File.Exists(Path.Combine(Directory.GetParent(localDriverManifest).ToString(), "bin", - "win64", - "driver_Amethyst.dll"))) - { - // We've found it! Now cache it and break free - localAmethystDriverPath = Directory.GetParent(localDriverManifest).ToString(); - goto p_search_loop_end; - } - // Else redo once more & then check - } - } - - // End of the searching loop - p_search_loop_end: - - // If there's none (still), cry about it and abort - if (string.IsNullOrEmpty(localAmethystDriverPath) || !new DirectoryInfo(localAmethystDriverPath).Exists) - { - // Hide the "working" progress bar - ReRegisterButtonBar.Opacity = 0.0; - - await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, - Host?.RequestLocalizedString("/CrashHandler/ReRegister/DriverNotFound"), "", ""); - return; // Hide and exit the handler - } - } - - /* 2 */ - - // Force exit (kill) SteamVR - if (Process.GetProcesses().FirstOrDefault(proc => proc.ProcessName is "vrserver" or "vrmonitor") != null) - { - // Check for privilege mismatches - if (VrHelper.IsOpenVrElevated() && !VrHelper.IsCurrentProcessElevated()) - { - // Hide the "working" progress bar - ReRegisterButtonBar.Opacity = 0.0; - - await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, - Host?.RequestLocalizedString("/CrashHandler/ReRegister/Elevation"), "", ""); - return; // Hide and exit the handler - } - - // Finally kill - if (await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, - Host?.RequestLocalizedString("/CrashHandler/ReRegister/KillSteamVR/Content"), - Host?.RequestLocalizedString("/CrashHandler/ReRegister/KillSteamVR/PrimaryButton"), - Host?.RequestLocalizedString("/CrashHandler/ReRegister/KillSteamVR/SecondaryButton"))) - { - await Task.Factory.StartNew(() => - { - Shutdown(); // Exit not to be killed - Host.RefreshStatusInterface(); - return helper.CloseSteamVr(); - }); - } - else - { - ReRegisterButtonBar.Opacity = 0.0; - return; // Hide and exit the handler - } - } - - /* 1.1 Copy packaged Amethyst drivers */ - - // Check whether Amethyst is installed as a package - if (PackageUtils.IsAmethystPackaged) - { - // Copy all driver files to Amethyst's local data folder - new DirectoryInfo(Path.Join(Directory.GetParent( - Assembly.GetExecutingAssembly().Location)!.FullName, "Driver", "Amethyst")) - .CopyToFolder((await ApplicationData.Current.LocalFolder.CreateFolderAsync( - "Amethyst", CreationCollisionOption.OpenIfExists)).Path); - - // Assume it's done now and get the path - localAmethystDriverPath = Path.Join(PackageUtils.GetAmethystAppDataPath(), "Amethyst"); - - // If there's none (still), cry about it and abort - if (string.IsNullOrEmpty(localAmethystDriverPath) || !Directory.Exists(localAmethystDriverPath)) - { - // Hide the "working" progress bar - ReRegisterButtonBar.Opacity = 0.0; - - Host?.Log($"Copied driver not present at expectant path of: {localAmethystDriverPath}"); - await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, - Host?.RequestLocalizedString("/CrashHandler/ReRegister/DriverNotFound"), "", ""); - return; // Hide and exit the handler - } - } - - /* 2.5 */ - - // Search for all K2EX instances and either unregister or delete them - - var isDriverK2Present = resultPaths.Exists.CopiedDriverExists; // is ame copied? - var driverK2PathsList = new List(); // ame external list - - foreach (var externalDriver in openVrPaths.external_drivers.Where(externalDriver => - externalDriver.Contains("KinectToVR"))) - { - isDriverK2Present = true; - driverK2PathsList.Add(externalDriver); - } - - // Remove (or delete) the existing K2EX Drivers - if (isDriverK2Present && await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, - Host?.RequestLocalizedString("/CrashHandler/ReRegister/ExistingDrivers/Content_K2EX"), - Host?.RequestLocalizedString("/CrashHandler/ReRegister/ExistingDrivers/PrimaryButton_K2EX"), - Host?.RequestLocalizedString("/CrashHandler/ReRegister/ExistingDrivers/SecondaryButton_K2EX"))) return; - - // Try-Catch it - try - { - if (isDriverK2Present || resultPaths.Exists.CopiedDriverExists) - { - // Delete the copied K2EX Driver (if exists) - if (resultPaths.Exists.CopiedDriverExists) - Directory.Delete(resultPaths.Path.CopiedDriverPath, true); // Delete - - // Un-register any remaining K2EX Drivers (if exist) - if (driverK2PathsList.Any()) - { - foreach (var driverK2Path in driverK2PathsList) openVrPaths.external_drivers.Remove(driverK2Path); - - // Save it - openVrPaths.Write(); - } - } - } - catch (Exception) - { - // Hide the "working" progress bar - ReRegisterButtonBar.Opacity = 0.0; - - // Critical, cry about it - await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, - Host?.RequestLocalizedString("/CrashHandler/ReRegister/FatalRemoveException_K2EX"), "", ""); - return; // Hide and exit the handler - } - - /* 3 */ - - // Search for all remaining (registered or copied) Amethyst Driver instances - - var isAmethystDriverPresent = resultPaths.Exists.CopiedDriverExists; // is ame copied? - var amethystDriverPathsList = new List(); // ame external list - - var isLocalAmethystDriverRegistered = false; // is our local ame registered? - - foreach (var externalDriver in openVrPaths.external_drivers.Where(externalDriver => - externalDriver.Contains("Amethyst"))) - { - // Don't un-register the already-existent one - if (externalDriver == localAmethystDriverPath) - { - isLocalAmethystDriverRegistered = true; - continue; // Don't report it - } - - isAmethystDriverPresent = true; - amethystDriverPathsList.Add(externalDriver); - } - - // Remove (or delete) the existing Amethyst Drivers - if (isAmethystDriverPresent && !await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, - Host?.RequestLocalizedString("/CrashHandler/ReRegister/ExistingDrivers/Content"), - Host?.RequestLocalizedString("/CrashHandler/ReRegister/ExistingDrivers/PrimaryButton"), - Host?.RequestLocalizedString("/CrashHandler/ReRegister/ExistingDrivers/SecondaryButton"))) return; - - // Try-Catch it - try - { - if (isAmethystDriverPresent || resultPaths.Exists.CopiedDriverExists) - { - // Delete the copied Amethyst Driver (if exists) - if (resultPaths.Exists.CopiedDriverExists) - Directory.Delete(resultPaths.Path.CopiedDriverPath, true); // Delete - - // Un-register any remaining Amethyst Drivers (if exist) - if (amethystDriverPathsList.Any()) - { - foreach (var amethystDriverPath in amethystDriverPathsList.Where(amethystDriverPath => - amethystDriverPath != localAmethystDriverPath)) - openVrPaths.external_drivers.Remove(amethystDriverPath); // Un-register - - // Save it - openVrPaths.Write(); - } - } - } - catch (Exception) - { - // Hide the "working" progress bar - ReRegisterButtonBar.Opacity = 0.0; - - // Critical, cry about it - await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, - Host?.RequestLocalizedString("/CrashHandler/ReRegister/FatalRemoveException"), "", ""); - return; // Hide and exit the handler - } - - /* 4 */ - - // If out local amethyst driver was already registered, skip this step - if (!isLocalAmethystDriverRegistered) - try // Try-Catch it - { - // Register the local Amethyst Driver via OpenVRPaths - openVrPaths.external_drivers.Add(localAmethystDriverPath); - openVrPaths.Write(); // Save it - - // If failed, cry about it and abort - var openVrPathsCheck = OpenVrPaths.Read(); - if (!openVrPathsCheck.external_drivers.Contains(localAmethystDriverPath)) - { - // Hide the "working" progress bar - ReRegisterButtonBar.Opacity = 0.0; - - await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, - Host?.RequestLocalizedString("/CrashHandler/ReRegister/OpenVRPathsWriteError"), "", ""); - return; // Hide and exit the handler - } - } - catch (Exception) - { - // Hide the "working" progress bar - ReRegisterButtonBar.Opacity = 0.0; - - // Critical, cry about it - await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, - Host?.RequestLocalizedString("/CrashHandler/ReRegister/FatalRegisterException"), "", ""); - return; // Hide and exit the handler - } - - /* 5 */ - - // Try-Catch it - try - { - // Read the vr settings - var steamVrSettings = JsonObject.Parse(await File.ReadAllTextAsync(resultPaths.Path.VrSettingsPath)); - - // Enable & unblock the Amethyst Driver - steamVrSettings.Remove("driver_Amethyst"); - steamVrSettings.Add("driver_Amethyst", - new JsonObject - { - new("enable", JsonValue.CreateBooleanValue(true)), - new("blocked_by_safe_mode", JsonValue.CreateBooleanValue(false)) - }); - - await File.WriteAllTextAsync(resultPaths.Path.VrSettingsPath, steamVrSettings.ToString()); - } - catch (Exception) - { - // Not critical - } - - // Hide the "working" progress bar - ReRegisterButtonBar.Opacity = 0.0; - - // Winning it! - await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, - Host?.RequestLocalizedString("/CrashHandler/ReRegister/Finished"), "", ""); - } - #region Amethyst VRDriver Methods private int InitAmethystServerAsync(string target = "BA32B754-20E3-4C8C-913B-28BBAC30531B") @@ -1110,107 +670,6 @@ private bool OpenVrStartup() return true; // OK } - private int InstallVrApplicationManifest() - { - if (!Initialized || OpenVR.Applications is null) return 0; // Sanity check - - // Prepare the manifest by copying it to a shared directory - // Check whether Amethyst is installed as a package - if (PackageUtils.IsAmethystPackaged) - { - // Copy all driver files to Amethyst's local data folder - Directory.CreateDirectory(Path.Join(ApplicationData.Current.LocalFolder.Path, "Amethyst")); - - // Copy the manifest - new FileInfo(Path.Join( - Directory.GetParent(Assembly.GetExecutingAssembly().Location)!.FullName, "Amethyst.vrmanifest")) - .CopyTo(Path.Join(ApplicationData.Current.LocalFolder.Path, "Amethyst", "Amethyst.vrmanifest"), true); - - // Copy the icon - var icon = new FileInfo(Path.Join( - Directory.GetParent(Environment.ProcessPath!)!.FullName, "Assets", "ktvr.png")); - - if (icon.Exists) - icon.CopyTo(Path.Join(ApplicationData.Current.LocalFolder.Path, "Amethyst", "ktvr.png"), true); - - // Assume it's done now and get the path - var copiedManifestPath = - Path.Join(ApplicationData.Current.LocalFolder.Path, "Amethyst", "Amethyst.vrmanifest"); - - // If there's none (still), cry about it and abort - if (string.IsNullOrEmpty(copiedManifestPath) || !File.Exists(copiedManifestPath)) - { - // Hide the "working" progress bar - ReRegisterButtonBar.Opacity = 0.0; - - Host?.Log($"Copied driver not present at expectant path of: {copiedManifestPath}"); - Host?.Log($"Amethyst vr manifest ({copiedManifestPath}) not found!", LogSeverity.Warning); - return -2; - } - } - - if (OpenVR.Applications.IsApplicationInstalled("K2VR.Amethyst")) - { - Host.Log("Amethyst manifest is already installed, removing..."); - - OpenVR.Applications.RemoveApplicationManifest( - "C:/Program Files/ModifiableWindowsApps/K2VRTeam.Amethyst.App/Plugins/plugin_OpenVR/Amethyst.vrmanifest"); - OpenVR.Applications.RemoveApplicationManifest("../../Plugins/plugin_OpenVR/Amethyst.vrmanifest"); - } - - // Compose the manifest path depending on where our plugin is - var manifestPath = PackageUtils.IsAmethystPackaged - ? Path.Join(ApplicationData.Current.LocalFolder.Path, "Amethyst", "Amethyst.vrmanifest") - : Path.Join(Directory.GetParent( - Assembly.GetAssembly(GetType())!.Location)?.FullName, "Amethyst.vrmanifest"); - - try - { - if (File.Exists(manifestPath)) - { - var manifestJson = JsonConvert.DeserializeObject(File.ReadAllText(manifestPath)); - if (manifestJson?.applications?.FirstOrDefault() is null) - { - Host.Log($"Amethyst vr manifest ({manifestPath}) was invalid!", LogSeverity.Warning); - return -2; // Give up on registering the application vr manifest - } - - try - { - manifestJson.applications.FirstOrDefault()!.launch_type = - Package.Current is not null ? "url" : "binary"; // Modify the manifest - } - catch (InvalidOperationException e) - { - // In case of any issues, replace the computed path with the default, relative one - manifestJson.applications.FirstOrDefault()!.launch_type = "binary"; // Launch exe - Host?.Log(e); // This will throw in case of not packaged apps, so don't care too much - } - - // Write the modified manifest data to the actual file - File.WriteAllText(manifestPath, JsonConvert.SerializeObject(manifestJson, Formatting.Indented)); - - // Finally register the manifest - var appError = OpenVR.Applications.AddApplicationManifest(manifestPath, false); - if (appError != EVRApplicationError.None) - { - Host?.Log($"Amethyst manifest not installed! Error: {appError}", LogSeverity.Warning); - return -1; - } - - Host?.Log("Amethyst manifest installed at: " + $"{manifestPath}"); - return 0; - } - } - catch (Exception e) - { - Host?.Log(e, LogSeverity.Error); - } - - Host?.Log($"Amethyst vr manifest ({manifestPath}) not found!", LogSeverity.Warning); - return -2; - } - private bool EvrActionsStartup() { Host.Log("Attempting to set up EVR Input Actions..."); @@ -1234,8 +693,8 @@ private void UpdateInputBindings() if (!Initialized || OpenVR.System is null) return; // Backup the current (OLD) data - var bFreezeState = _evrInput.TrackerFreezeActionData.bState; - var bFlipToggleState = _evrInput.TrackerFlipToggleData.bState; + var bFreezeState = _evrInput.TrackerFreezeActionData; + var bFlipToggleState = _evrInput.TrackerFlipToggleData; // Update all input actions if (!_evrInput.UpdateActionStates()) @@ -1244,7 +703,7 @@ private void UpdateInputBindings() // Update the Tracking Freeze : toggle // Only if the state has changed from 1 to 0: button was clicked - if (!_evrInput.TrackerFreezeActionData.bState && bFreezeState) + if (!_evrInput.TrackerFreezeActionData && bFreezeState) { Host.Log("[Input Actions] Input: Tracking freeze toggled"); ControllerInputActions.TrackingFreezeToggled?.Invoke(this, EventArgs.Empty); @@ -1252,7 +711,7 @@ private void UpdateInputBindings() // Update the Flip Toggle : toggle // Only if the state has changed from 1 to 0: button was clicked - if (!_evrInput.TrackerFlipToggleData.bState && bFlipToggleState) + if (!_evrInput.TrackerFlipToggleData && bFlipToggleState) { Host.Log("[Input Actions] Input: Flip toggled"); ControllerInputActions.SkeletonFlipToggled?.Invoke(this, EventArgs.Empty); @@ -1260,27 +719,27 @@ private void UpdateInputBindings() // Update the Calibration:Confirm : one-time switch // Only one-way switch this time, reset at calibration's end - if (_evrInput.ConfirmAndSaveActionData.bState) + if (_evrInput.ConfirmAndSaveActionData) ControllerInputActions.CalibrationConfirmed?.Invoke(this, EventArgs.Empty); // Update the Calibration:ModeSwap : one-time switch // Only if the state has changed from 1 to 0: chord was done - if (_evrInput.ModeSwapActionData.bState) + if (_evrInput.ModeSwapActionData) ControllerInputActions.CalibrationModeChanged?.Invoke(this, EventArgs.Empty); // Update the Calibration:FineTune : held switch - var posMultiplexer = _evrInput.FineTuneActionData.bState ? .0015f : .015f; - var rotMultiplexer = _evrInput.FineTuneActionData.bState ? .1f : 1f; + var posMultiplexer = _evrInput.FineTuneActionData ? .0015f : .015f; + var rotMultiplexer = _evrInput.FineTuneActionData ? .1f : 1f; // Update the Calibration:Joystick : vector2 x2 ControllerInputActions.MovePositionValues = - new Vector3(_evrInput.LeftJoystickActionData.x * posMultiplexer, - _evrInput.RightJoystickActionData.y * posMultiplexer, - -_evrInput.LeftJoystickActionData.y * posMultiplexer); + new Vector3(_evrInput.LeftJoystickActionData.X * posMultiplexer, + _evrInput.RightJoystickActionData.Y * posMultiplexer, + -_evrInput.LeftJoystickActionData.Y * posMultiplexer); ControllerInputActions.AdjustRotationValues = - new Vector2(_evrInput.RightJoystickActionData.y * MathF.PI / 280f * rotMultiplexer, - -_evrInput.LeftJoystickActionData.x * MathF.PI / 280f * rotMultiplexer); + new Vector2(_evrInput.RightJoystickActionData.Y * MathF.PI / 280f * rotMultiplexer, + -_evrInput.LeftJoystickActionData.X * MathF.PI / 280f * rotMultiplexer); } private void ParseVrEvents() diff --git a/plugin_OpenVR/Pages/SettingsPage.xaml b/plugin_OpenVR/Pages/SettingsPage.xaml new file mode 100644 index 0000000..4e6ebdc --- /dev/null +++ b/plugin_OpenVR/Pages/SettingsPage.xaml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugin_OpenVR/Pages/SettingsPage.xaml.cs b/plugin_OpenVR/Pages/SettingsPage.xaml.cs new file mode 100644 index 0000000..44f2a07 --- /dev/null +++ b/plugin_OpenVR/Pages/SettingsPage.xaml.cs @@ -0,0 +1,626 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Windows.ApplicationModel; +using Windows.ApplicationModel.Resources.Core; +using Windows.Data.Json; +using Windows.Storage; +using Amethyst.Plugins.Contract; +using Microsoft.UI.Text; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Newtonsoft.Json; +using plugin_OpenVR.Utils; +using Valve.VR; +using Microsoft.UI.Xaml.Media.Animation; + +namespace plugin_OpenVR.Pages; + +public sealed partial class SettingsPage : UserControl +{ + public SettingsPage() + { + var pluginDir = Directory.GetParent(Assembly.GetAssembly(GetType())!.Location); + + var priFile = StorageFile.GetFileFromPathAsync( + Path.Join(pluginDir!.FullName, "resources.pri")).GetAwaiter().GetResult(); + + ResourceManager.Current.LoadPriFiles([priFile]); + ResourceManager.Current.LoadPriFiles([priFile]); + + Application.LoadComponent(this, new Uri($"ms-appx:///{Path.Join(pluginDir!.FullName, "Pages", $"{GetType().Name}.xaml")}"), + ComponentResourceLocation.Application); + } + + public IAmethystHost Host { get; set; } + public SteamVR DataParent { get; set; } + public CustomInputAction TreeSelectedAction { get; set; } + + private string GetString(string key) + { + return Host?.RequestLocalizedString(key) ?? key; + } + + private void ReManifestButton_OnClick(object sender, RoutedEventArgs e) + { + switch (InstallVrApplicationManifest()) + { + // Not found failure + case -2: + { + ActionFailedFlyout.Content = new TextBlock + { + FontWeight = FontWeights.SemiBold, + Text = Host.RequestLocalizedString("/SettingsPage/ReManifest/Error/NotFound") + }; + + ActionFailedFlyout.ShowAt(ReManifestButton); + break; + } + + // SteamVR failure + case 1: + { + ActionFailedFlyout.Content = new TextBlock + { + FontWeight = FontWeights.SemiBold, + Text = Host.RequestLocalizedString("/SettingsPage/ReManifest/Error/Other") + }; + + ActionFailedFlyout.ShowAt(ReManifestButton); + break; + } + } + + // Play a sound + Host?.PlayAppSound(SoundType.Invoke); + } + + public int InstallVrApplicationManifest() + { + if (!SteamVR.Initialized || OpenVR.Applications is null) return 0; // Sanity check + + try + { + // Prepare the manifest by copying it to a shared directory + // Check whether Amethyst is installed as a package + if (PackageUtils.IsAmethystPackaged) + { + // Copy all driver files to Amethyst's local data folder + Directory.CreateDirectory(Path.Join(ApplicationData.Current.LocalFolder.Path, "Amethyst")); + + // Copy the manifest + new FileInfo(Path.Join( + Directory.GetParent(Assembly.GetExecutingAssembly().Location)!.FullName, "Amethyst.vrmanifest")) + .CopyTo(Path.Join(ApplicationData.Current.LocalFolder.Path, "Amethyst", "Amethyst.vrmanifest"), true); + + // Copy the icon + var icon = new FileInfo(Path.Join( + Directory.GetParent(Environment.ProcessPath!)!.FullName, "Assets", "ktvr.png")); + + if (icon.Exists) + icon.CopyTo(Path.Join(ApplicationData.Current.LocalFolder.Path, "Amethyst", "ktvr.png"), true); + + // Assume it's done now and get the path + var copiedManifestPath = + Path.Join(ApplicationData.Current.LocalFolder.Path, "Amethyst", "Amethyst.vrmanifest"); + + // If there's none (still), cry about it and abort + if (string.IsNullOrEmpty(copiedManifestPath) || !File.Exists(copiedManifestPath)) + { + // Hide the "working" progress bar + ReRegisterButtonBar.Opacity = 0.0; + + Host?.Log($"Copied driver not present at expectant path of: {copiedManifestPath}"); + Host?.Log($"Amethyst vr manifest ({copiedManifestPath}) not found!", LogSeverity.Warning); + return -2; + } + } + + if (OpenVR.Applications.IsApplicationInstalled("K2VR.Amethyst")) + { + Host.Log("Amethyst manifest is already installed, removing..."); + + OpenVR.Applications.RemoveApplicationManifest( + "C:/Program Files/ModifiableWindowsApps/K2VRTeam.Amethyst.App/Plugins/plugin_OpenVR/Amethyst.vrmanifest"); + OpenVR.Applications.RemoveApplicationManifest("../../Plugins/plugin_OpenVR/Amethyst.vrmanifest"); + } + + // Compose the manifest path depending on where our plugin is + var manifestPath = PackageUtils.IsAmethystPackaged + ? Path.Join(ApplicationData.Current.LocalFolder.Path, "Amethyst", "Amethyst.vrmanifest") + : Path.Join(Directory.GetParent( + Assembly.GetAssembly(GetType())!.Location)?.FullName, "Amethyst.vrmanifest"); + + if (File.Exists(manifestPath)) + { + var manifestJson = JsonConvert.DeserializeObject(File.ReadAllText(manifestPath)); + if (manifestJson?.applications?.FirstOrDefault() is null) + { + Host.Log($"Amethyst vr manifest ({manifestPath}) was invalid!", LogSeverity.Warning); + return -2; // Give up on registering the application vr manifest + } + + try + { + manifestJson.applications.FirstOrDefault()!.launch_type = + Package.Current is not null ? "url" : "binary"; // Modify the manifest + } + catch (InvalidOperationException e) + { + // In case of any issues, replace the computed path with the default, relative one + manifestJson.applications.FirstOrDefault()!.launch_type = "binary"; // Launch exe + Host?.Log(e); // This will throw in case of not packaged apps, so don't care too much + } + + // Write the modified manifest data to the actual file + File.WriteAllText(manifestPath, JsonConvert.SerializeObject(manifestJson, Formatting.Indented)); + + // Finally register the manifest + var appError = OpenVR.Applications.AddApplicationManifest(manifestPath, false); + if (appError != EVRApplicationError.None) + { + Host?.Log($"Amethyst manifest not installed! Error: {appError}", LogSeverity.Warning); + return -1; + } + + Host?.Log("Amethyst manifest installed at: " + $"{manifestPath}"); + return 0; + } + } + catch (Exception e) + { + Host?.Log(e, LogSeverity.Error); + } + + Host?.Log("Amethyst vr manifest not found!", LogSeverity.Warning); + return -2; + } + + public async void ReRegisterButton_OnClick(SplitButton sender, SplitButtonClickEventArgs args) + { + // Play a sound + Host?.PlayAppSound(SoundType.Invoke); + + VrHelper helper = new(); + OpenVrPaths openVrPaths; + var resultPaths = helper.UpdateSteamPaths(); + + // Check if SteamVR was found + if (!resultPaths.Exists.SteamExists) + { + // Critical, cry about it + await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, + Host?.RequestLocalizedString("/CrashHandler/ReRegister/SteamVRNotFound"), "", ""); + return; + } + + try // Try-Catch it + { + // Read the OpenVRPaths + openVrPaths = OpenVrPaths.Read(); + } + catch (Exception) + { + // Critical, cry about it + await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, + Host?.RequestLocalizedString("/CrashHandler/ReRegister/OpenVRPathsError"), "", ""); + return; + } + + /* + * ReRegister Logic: + * + * Search for Amethyst VRDriver in the crash handler's directory + * and 2 folders up in tree, recursively. (Find the manifest) + * + * If the manifest & dll are found, check and ask to close SteamVR + * + * With closed SteamVR, search for all remaining 'driver_Amethyst' instances: + * copied inside /drivers/ or registered. If found, ask to delete them + * + * When everything is purified, we can register the 'driver_Amethyst' + * via OpenVRPaths and then check twice if it's there ready to go + * + * If the previous steps succeeded, we can enable the 'driver_Amethyst' + * in VRSettings. A run failure/exception of this one isn't critical + */ + + // Show the "working" progress bar + ReRegisterButtonBar.Opacity = 1.0; + + /* 1 */ + + // Create a placeholder for the driver path + var localAmethystDriverPath = ""; + + // Check whether Amethyst is installed as a package + if (!PackageUtils.IsAmethystPackaged) + { + // Optionally change to the other variant + if (!new DirectoryInfo(localAmethystDriverPath).Exists) + { + // Get plugin_OpenVR.dll parent path + var parentPath = Directory.GetParent(Assembly.GetExecutingAssembly().Location); + + // Search for driver manifests, try max 2 times + for (var i = 0; i < 2; i++) + { + // Double that to get Amethyst exe path + if (parentPath?.Parent != null) parentPath = parentPath.Parent; + if (parentPath is null) goto p_search_loop_end; + + // Find all vr driver manifests there + var allLocalDriverManifests = Directory.GetFiles(parentPath.ToString(), + "driver.vrdrivermanifest", SearchOption.AllDirectories); + + // For each found manifest, check if there is an ame driver dll inside + foreach (var localDriverManifest in allLocalDriverManifests) + if (File.Exists(Path.Combine(Directory.GetParent(localDriverManifest).ToString(), "bin", + "win64", + "driver_Amethyst.dll"))) + { + // We've found it! Now cache it and break free + localAmethystDriverPath = Directory.GetParent(localDriverManifest).ToString(); + goto p_search_loop_end; + } + // Else redo once more & then check + } + } + + // End of the searching loop + p_search_loop_end: + + // If there's none (still), cry about it and abort + if (string.IsNullOrEmpty(localAmethystDriverPath) || !new DirectoryInfo(localAmethystDriverPath).Exists) + { + // Hide the "working" progress bar + ReRegisterButtonBar.Opacity = 0.0; + + await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, + Host?.RequestLocalizedString("/CrashHandler/ReRegister/DriverNotFound"), "", ""); + return; // Hide and exit the handler + } + } + + /* 2 */ + + // Force exit (kill) SteamVR + if (Process.GetProcesses().FirstOrDefault(proc => proc.ProcessName is "vrserver" or "vrmonitor") != null) + { + // Check for privilege mismatches + if (VrHelper.IsOpenVrElevated() && !VrHelper.IsCurrentProcessElevated()) + { + // Hide the "working" progress bar + ReRegisterButtonBar.Opacity = 0.0; + + await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, + Host?.RequestLocalizedString("/CrashHandler/ReRegister/Elevation"), "", ""); + return; // Hide and exit the handler + } + + // Finally kill + if (await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, + Host?.RequestLocalizedString("/CrashHandler/ReRegister/KillSteamVR/Content"), + Host?.RequestLocalizedString("/CrashHandler/ReRegister/KillSteamVR/PrimaryButton"), + Host?.RequestLocalizedString("/CrashHandler/ReRegister/KillSteamVR/SecondaryButton"))) + { + await Task.Factory.StartNew(() => + { + DataParent?.Shutdown(); // Exit not to be killed + Host.RefreshStatusInterface(); + return helper.CloseSteamVr(); + }); + } + else + { + ReRegisterButtonBar.Opacity = 0.0; + return; // Hide and exit the handler + } + } + + /* 1.1 Copy packaged Amethyst drivers */ + + // Check whether Amethyst is installed as a package + if (PackageUtils.IsAmethystPackaged) + { + // Copy all driver files to Amethyst's local data folder + new DirectoryInfo(Path.Join(Directory.GetParent( + Assembly.GetExecutingAssembly().Location)!.FullName, "Driver", "Amethyst")) + .CopyToFolder((await ApplicationData.Current.LocalFolder.CreateFolderAsync( + "Amethyst", CreationCollisionOption.OpenIfExists)).Path); + + // Assume it's done now and get the path + localAmethystDriverPath = Path.Join(PackageUtils.GetAmethystAppDataPath(), "Amethyst"); + + // If there's none (still), cry about it and abort + if (string.IsNullOrEmpty(localAmethystDriverPath) || !Directory.Exists(localAmethystDriverPath)) + { + // Hide the "working" progress bar + ReRegisterButtonBar.Opacity = 0.0; + + Host?.Log($"Copied driver not present at expectant path of: {localAmethystDriverPath}"); + await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, + Host?.RequestLocalizedString("/CrashHandler/ReRegister/DriverNotFound"), "", ""); + return; // Hide and exit the handler + } + } + + /* 2.5 */ + + // Search for all K2EX instances and either unregister or delete them + + var isDriverK2Present = resultPaths.Exists.CopiedDriverExists; // is ame copied? + var driverK2PathsList = new List(); // ame external list + + foreach (var externalDriver in openVrPaths.external_drivers.Where(externalDriver => + externalDriver.Contains("KinectToVR"))) + { + isDriverK2Present = true; + driverK2PathsList.Add(externalDriver); + } + + // Remove (or delete) the existing K2EX Drivers + if (isDriverK2Present && await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, + Host?.RequestLocalizedString("/CrashHandler/ReRegister/ExistingDrivers/Content_K2EX"), + Host?.RequestLocalizedString("/CrashHandler/ReRegister/ExistingDrivers/PrimaryButton_K2EX"), + Host?.RequestLocalizedString("/CrashHandler/ReRegister/ExistingDrivers/SecondaryButton_K2EX"))) return; + + // Try-Catch it + try + { + if (isDriverK2Present || resultPaths.Exists.CopiedDriverExists) + { + // Delete the copied K2EX Driver (if exists) + if (resultPaths.Exists.CopiedDriverExists) + Directory.Delete(resultPaths.Path.CopiedDriverPath, true); // Delete + + // Un-register any remaining K2EX Drivers (if exist) + if (driverK2PathsList.Any()) + { + foreach (var driverK2Path in driverK2PathsList) openVrPaths.external_drivers.Remove(driverK2Path); + + // Save it + openVrPaths.Write(); + } + } + } + catch (Exception) + { + // Hide the "working" progress bar + ReRegisterButtonBar.Opacity = 0.0; + + // Critical, cry about it + await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, + Host?.RequestLocalizedString("/CrashHandler/ReRegister/FatalRemoveException_K2EX"), "", ""); + return; // Hide and exit the handler + } + + /* 3 */ + + // Search for all remaining (registered or copied) Amethyst Driver instances + + var isAmethystDriverPresent = resultPaths.Exists.CopiedDriverExists; // is ame copied? + var amethystDriverPathsList = new List(); // ame external list + + var isLocalAmethystDriverRegistered = false; // is our local ame registered? + + foreach (var externalDriver in openVrPaths.external_drivers.Where(externalDriver => + externalDriver.Contains("Amethyst"))) + { + // Don't un-register the already-existent one + if (externalDriver == localAmethystDriverPath) + { + isLocalAmethystDriverRegistered = true; + continue; // Don't report it + } + + isAmethystDriverPresent = true; + amethystDriverPathsList.Add(externalDriver); + } + + // Remove (or delete) the existing Amethyst Drivers + if (isAmethystDriverPresent && !await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, + Host?.RequestLocalizedString("/CrashHandler/ReRegister/ExistingDrivers/Content"), + Host?.RequestLocalizedString("/CrashHandler/ReRegister/ExistingDrivers/PrimaryButton"), + Host?.RequestLocalizedString("/CrashHandler/ReRegister/ExistingDrivers/SecondaryButton"))) return; + + // Try-Catch it + try + { + if (isAmethystDriverPresent || resultPaths.Exists.CopiedDriverExists) + { + // Delete the copied Amethyst Driver (if exists) + if (resultPaths.Exists.CopiedDriverExists) + Directory.Delete(resultPaths.Path.CopiedDriverPath, true); // Delete + + // Un-register any remaining Amethyst Drivers (if exist) + if (amethystDriverPathsList.Any()) + { + foreach (var amethystDriverPath in amethystDriverPathsList.Where(amethystDriverPath => + amethystDriverPath != localAmethystDriverPath)) + openVrPaths.external_drivers.Remove(amethystDriverPath); // Un-register + + // Save it + openVrPaths.Write(); + } + } + } + catch (Exception) + { + // Hide the "working" progress bar + ReRegisterButtonBar.Opacity = 0.0; + + // Critical, cry about it + await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, + Host?.RequestLocalizedString("/CrashHandler/ReRegister/FatalRemoveException"), "", ""); + return; // Hide and exit the handler + } + + /* 4 */ + + // If out local amethyst driver was already registered, skip this step + if (!isLocalAmethystDriverRegistered) + try // Try-Catch it + { + // Register the local Amethyst Driver via OpenVRPaths + openVrPaths.external_drivers.Add(localAmethystDriverPath); + openVrPaths.Write(); // Save it + + // If failed, cry about it and abort + var openVrPathsCheck = OpenVrPaths.Read(); + if (!openVrPathsCheck.external_drivers.Contains(localAmethystDriverPath)) + { + // Hide the "working" progress bar + ReRegisterButtonBar.Opacity = 0.0; + + await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, + Host?.RequestLocalizedString("/CrashHandler/ReRegister/OpenVRPathsWriteError"), "", ""); + return; // Hide and exit the handler + } + } + catch (Exception) + { + // Hide the "working" progress bar + ReRegisterButtonBar.Opacity = 0.0; + + // Critical, cry about it + await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, + Host?.RequestLocalizedString("/CrashHandler/ReRegister/FatalRegisterException"), "", ""); + return; // Hide and exit the handler + } + + /* 5 */ + + // Try-Catch it + try + { + // Read the vr settings + var steamVrSettings = JsonObject.Parse(await File.ReadAllTextAsync(resultPaths.Path.VrSettingsPath)); + + // Enable & unblock the Amethyst Driver + steamVrSettings.Remove("driver_Amethyst"); + steamVrSettings.Add("driver_Amethyst", + new JsonObject + { + new KeyValuePair("enable", JsonValue.CreateBooleanValue(true)), + new KeyValuePair("blocked_by_safe_mode", JsonValue.CreateBooleanValue(false)) + }); + + await File.WriteAllTextAsync(resultPaths.Path.VrSettingsPath, steamVrSettings.ToString()); + } + catch (Exception) + { + // Not critical + } + + // Hide the "working" progress bar + ReRegisterButtonBar.Opacity = 0.0; + + // Winning it! + await ConfirmationFlyout.HandleButtonConfirmationFlyout(ReRegisterButton, Host, + Host?.RequestLocalizedString("/CrashHandler/ReRegister/Finished"), "", ""); + } + + private void ActionFailedFlyout_OnOpening(object sender, object e) + { + Host?.PlayAppSound(SoundType.Show); + } + + private void ActionFailedFlyout_OnClosing(FlyoutBase sender, FlyoutBaseClosingEventArgs args) + { + Host?.PlayAppSound(SoundType.Hide); + } + + private void ActionsFlyout_OnOpening(object sender, object e) + { + Host?.PlayAppSound(SoundType.Show); + if (!ActionsTreeView.IsLoaded) return; + + ActionsTreeView.RootNodes.Clear(); // Remove everything first + DataParent.CustomInputActions + .Select(x => new TreeViewNodeEx(x)) + .ToList().ForEach(ActionsTreeView.RootNodes.Add); + } + + private void ActionsFlyout_OnClosing(FlyoutBase sender, FlyoutBaseClosingEventArgs args) + { + Host?.PlayAppSound(SoundType.Hide); + } + + private async void ActionTestButton_OnClick(object sender, RoutedEventArgs e) + { + if (!TestResultsBox.IsLoaded || TreeSelectedAction is null) return; + TestResultsBox.Text = await TreeSelectedAction.Invoke(null); + } + + private async void ActionsTreeView_OnItemInvoked(TreeView sender, TreeViewItemInvokedEventArgs args) + { + if (!sender.IsLoaded) return; + if (args.InvokedItem is not TreeViewNodeEx node) + { + await Tree_LaunchTransition(sender); + Host?.PlayAppSound(SoundType.Focus); + return; // Give up now... + } + + sender.SelectedNode = node; + + var shouldAnimate = TreeSelectedAction != node.Action; + TreeSelectedAction = node.Action; + + if (!shouldAnimate) return; + await Tree_LaunchTransition(sender); + Host?.PlayAppSound(SoundType.Invoke); + } + + private async Task Tree_LaunchTransition(TreeView tree) + { + // Hide and save + if (((tree.Parent as ScrollViewer)?.Parent as Grid)?.Parent + is not Grid innerGrid || innerGrid.Children.Last() is not Grid previewGrid) return; + + // Action stuff reload animation + try + { + // Remove the only one child of our outer main content grid + // (What a bestiality it is to do that!!1) + innerGrid.Children.Remove(previewGrid); + previewGrid.Transitions.Add( + new EntranceThemeTransition { IsStaggeringEnabled = false }); + + // Sleep peacefully pretending that noting happened + await Task.Delay(10); + + // Re-add the child for it to play our funky transition + // (Though it's not the same as before...) + innerGrid.Children.Add(previewGrid); + + // Remove the transition + await Task.Delay(100); + previewGrid.Transitions.Clear(); + } + catch (Exception e) + { + Host?.Log(e); + } + } +} + +internal class TreeViewNodeEx : TreeViewNode +{ + public ActionsManifest.Action Action { get; set; } + + public bool HasData => Action?.Valid ?? false; + + public TreeViewNodeEx(ActionsManifest.Action action) + { + Content = action.Name; + Action = action; + } +} \ No newline at end of file diff --git a/plugin_OpenVR/Properties/PublishProfiles/FolderProfile.pubxml b/plugin_OpenVR/Properties/PublishProfiles/FolderProfile.pubxml index 40fb54a..41d47d2 100644 --- a/plugin_OpenVR/Properties/PublishProfiles/FolderProfile.pubxml +++ b/plugin_OpenVR/Properties/PublishProfiles/FolderProfile.pubxml @@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. --> - Release + Debug x64 bin\Release\net7.0\win10-x64\publish\ FileSystem diff --git a/plugin_OpenVR/Utils/VRPaths.cs b/plugin_OpenVR/Utils/VRPaths.cs index 6bb97ae..0599ed3 100644 --- a/plugin_OpenVR/Utils/VRPaths.cs +++ b/plugin_OpenVR/Utils/VRPaths.cs @@ -15,7 +15,7 @@ internal class OpenVrPaths public static OpenVrPaths Read() { var temp = JsonFile.Read(Path); - temp.external_drivers ??= new List(); + temp.external_drivers ??= []; return temp; } @@ -28,7 +28,7 @@ public void Write() // Prevent Warning CS0649: Field '...' is never assigned to, and will always have its default value null: #pragma warning disable 0649 public List config; - public List external_drivers = new(); + public List external_drivers = []; public string jsonid; public List log; public List runtime; diff --git a/plugin_OpenVR/plugin_OpenVR.csproj b/plugin_OpenVR/plugin_OpenVR.csproj index 2757171..373969e 100644 --- a/plugin_OpenVR/plugin_OpenVR.csproj +++ b/plugin_OpenVR/plugin_OpenVR.csproj @@ -10,6 +10,7 @@ x64 true true + latest @@ -20,7 +21,8 @@ - + + @@ -33,6 +35,7 @@ Always + Never @@ -97,4 +100,19 @@ + + + + MSBuild:Compile + + + + + + <_CustomFiles1 Include="$(ProjectDir)$(OutDir)$(ProjectName)\Pages\*.*" /> + <_CustomFiles2 Include="$(ProjectDir)$(OutDir)$(ProjectName).pri" /> + + + + \ No newline at end of file