using Godot; using System; using ETPreferences; using AlignmentHelper; using BSEnroll; using VRCFT; using ImGuiNET; using System.IO; using Microsoft.Win32; public partial class Main : Node { private Ui ui; private GazeEstimation gazeEstimation; private VRChatConsumer vrchatConsumer; private MemmapConsumer memmapConsumer; private VRCFTConsumer vrftConsumer; private DebugViewer debugViewer; private ModelDownloadManager modelDownloadManager = new(); private ET_Preferences etSettings = ET_Preferences.Instance; private AlignmentHelperProcess alignmentHelper = AlignmentHelperProcess.Instance; private static System.Threading.Mutex mutex; private const string MutexName = "com_bigscreen_et_process"; public override void _Ready() { // Force the working directory to the executable's location string exePath = System.IO.Path.GetDirectoryName(OS.GetExecutablePath()); Directory.SetCurrentDirectory(exePath); bool uriLaunched = false; // URI launch arguments string[] args = OS.GetCmdlineArgs(); foreach (string arg in args) { if (arg.StartsWith("bset://")) { uriLaunched = true; ParseTokenUri(arg); } } // Create mutex bool createdNew; mutex = new System.Threading.Mutex(true, MutexName, out createdNew); // Initialize UI ui = GetNode("UI"); if (!createdNew) { // If a process already exists from uri launch, close this one if (uriLaunched) { GetTree().Quit(); } ShowStartupError(); ui.hault_render = true; return; } Profiling.Enabled = true; Instrumentor.Get().BeginSession("Beyond Eyetracking"); // Fetch user preferences if (EyeTrackingPreferences.ReadSettingsFile()) { // Fetch user with saved token if (!string.IsNullOrWhiteSpace(etSettings.user_testing_token)) { ui.ChangeToken(etSettings.user_testing_token); } } // Initialize gaze estimation gazeEstimation = GetNode("GazeEstimation"); ui.EnableCamera += async () => { bool success = await gazeEstimation.cameraManager.InitializeCamera(); // Initial attempt fails, start search loop if (!success) { gazeEstimation.cameraManager.StartCameraSearch(); } // Start the alignment helper watch loop if (etSettings.enabled_alignment_helper) { alignmentHelper.StartAlignmentWatch(); } }; ui.DisableCamera += () => { gazeEstimation.cameraManager.ReleaseCamera(); // Close the alignment helper if (etSettings.enabled_alignment_helper) { alignmentHelper.EndAlignmentWatch(); } }; ui.gazeEstimation = gazeEstimation; ui.main = this; // register OnModelSelected callback ui.ModelSelected += OnModelSelected; modelDownloadManager.ModelReady += modelPath => { string fullPath = ProjectSettings.GlobalizePath(modelPath); if (!etSettings.downloaded_models.Contains(fullPath)) { etSettings.downloaded_models.Add(fullPath); EyeTrackingPreferences.WriteSettingsFile(); } gazeEstimation.LoadModel(modelPath); // Start the alignment helper watch loop if it is enabled if (etSettings.enabled_alignment_helper) { alignmentHelper.StartAlignmentWatch(); } }; CreateConsumers(); } private void ParseTokenUri(string uri) { try { var uriObj = new Uri(uri); string path = uriObj.AbsolutePath; var queryParams = System.Web.HttpUtility.ParseQueryString(uriObj.Query); string token = queryParams.Get("t"); GD.Print($"Got token from uri."); if (EyeTrackingPreferences.ReadSettingsFile()) { etSettings.user_testing_token = token; EyeTrackingPreferences.WriteSettingsFile(); } } catch (Exception ex) { GD.PrintErr("Failed to parse URI: " + ex.Message); } } private void OnModelSelected(string modelName) { CreateConsumers(); modelDownloadManager.OnModelSelected(modelName); } private void CreateConsumers() { ui.ConfigureError(Ui.process_error.NoFrustum, false); // Initialize consumers try { if (vrchatConsumer == null) { vrchatConsumer = new VRChatConsumer(); gazeEstimation.RegisterObserver(vrchatConsumer); ui.consumers.Add(vrchatConsumer); } if (memmapConsumer == null) { memmapConsumer = new MemmapConsumer(); gazeEstimation.RegisterObserver(memmapConsumer); ui.consumers.Add(memmapConsumer); } if (vrftConsumer == null) { vrftConsumer = new VRCFTConsumer(); gazeEstimation.RegisterObserver(vrftConsumer); ui.consumers.Add(vrftConsumer); } if (debugViewer == null) { debugViewer = new DebugViewer(); gazeEstimation.RegisterObserver(debugViewer); ui.consumers.Add(debugViewer); } } catch (Exception ex) { // Only show frustum error if the user has any models. if (ex.Message.ToLower().Contains("frustum") && ui.model_names.Count > 0) { ui.ConfigureError(Ui.process_error.NoFrustum, true); } } } private void ShowStartupError() { var dialog = new AcceptDialog { DialogText = UserDataManager.Instance.IsEnrollmentInstance ? "Failed to start enrollment!\nTry opening SteamVR beforehand or set \"OpenXR Runtime\" to SteamVR." : UserDataManager.Instance.IsAlignmentInstance ? "Failed to start alignment check!\nTry opening SteamVR beforehand or set \"OpenXR Runtime\" to SteamVR." : "Beyond Eyetracking is already running.", Name = "AlreadyRunningDialog", Title = "Error" }; dialog.Exclusive = true; AddChild(dialog); dialog.Connect("confirmed", new Callable(this, nameof(OnDialogClosed))); dialog.Connect("canceled", new Callable(this, nameof(OnDialogClosed))); dialog.PopupCentered(); } public bool ApplyQuadViewSettings() { try { string localAppData = System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData); string targetDirectory = Path.Combine(localAppData, "Quad-Views-Foveated"); if (!Directory.Exists(targetDirectory)) { Directory.CreateDirectory(targetDirectory); } string fields = $@"#[ Beyond specific settings ] horizontal_focus_section={etSettings.quad_horizontal_focus_section} vertical_focus_section={etSettings.quad_vertical_focus_section} peripheral_multiplier={etSettings.quad_peripheral_multiplier} smoothen_focus_view_edges={etSettings.quad_transition_thickness} focus_multiplier={etSettings.quad_focus_multiplier} debug_eye_gaze={etSettings.quad_debug_gaze} "; GD.Print("Applying new settings:\n" + fields); string newSettings = Path.Combine(targetDirectory, "settings.cfg"); GD.Print("Writing to new Quad View settings"); File.WriteAllText(newSettings, fields); return true; } catch (Exception ex) { GD.PrintErr("Failed to apply Quad View settings: " + ex.Message); return false; } } public int GetXRLayerCount() { string[] paths = { @"SOFTWARE\Khronos\OpenXR\1\ApiLayers\Implicit", @"SOFTWARE\Khronos\OpenXR\1\ApiLayers\Explicit" }; int count = 0; try { foreach (var path in paths) { using (RegistryKey key = Registry.LocalMachine.OpenSubKey(path)) { if (key != null) { count += key.GetValueNames().Length; } } } } catch(Exception ex) { GD.PrintErr("Could not fetch OpenXR layers: " + ex.Message); } return count; } private void OnDialogClosed() { GetTree().Quit(); } public override void _Process(double delta) { if (etSettings != null) { if (vrchatConsumer != null) vrchatConsumer.Enabled = etSettings.submit_to_vrchat_osc; if (memmapConsumer != null) memmapConsumer.Enabled = etSettings.submit_to_xr; if (vrftConsumer != null) vrftConsumer.Enabled = etSettings.submit_to_vrcft; if (debugViewer != null) debugViewer.Enabled = etSettings.submit_to_viewer; } } public override void _ExitTree() { Instrumentor.Get().EndSession(); GD.Print("Ending session"); mutex?.ReleaseMutex(); // Attempt to exit the alignment helper process alignmentHelper.EndAlignmentWatch(); } }