From d2fd2054766ce6f2f85514c378e388b3876b8710 Mon Sep 17 00:00:00 2001 From: Natsumi <11171153+Natsumi-sama@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:31:10 +1300 Subject: [PATCH] Merge overlays, move overlay to separate process (#44) * refactor: merge two overlay offScreenBrowser into one * Electron support for shared overlay * Separate overlay into its own process * fix: invalid overlay texture size * Handle duplicate processes * Remove logging --------- Co-authored-by: pa Co-authored-by: rs189 <35667100+rs189@users.noreply.github.com> --- Dotnet/AppApi/Cef/AppApiCef.cs | 34 +- Dotnet/AppApi/Cef/GameHandler.cs | 6 +- Dotnet/AppApi/Common/AppApiCommonBase.cs | 3 - Dotnet/AppApi/Electron/AppApiElectron.cs | 15 - Dotnet/Cef/CefService.cs | 18 +- Dotnet/Cef/JavascriptBindings.cs | 6 - .../Common => Overlay}/AppApiVrCommon.cs | 1 - Dotnet/{AppApi => Overlay}/Cef/AppApiVrCef.cs | 17 +- Dotnet/Overlay/Cef/OffScreenBrowser.cs | 362 +++++--- Dotnet/Overlay/Cef/OffScreenBrowserLegacy.cs | 212 ----- Dotnet/Overlay/Cef/OverlayClient.cs | 120 +++ Dotnet/Overlay/Cef/OverlayProgram.cs | 51 ++ Dotnet/Overlay/Cef/VRCXVRCef.cs | 263 +++--- Dotnet/Overlay/Cef/VRCXVRLegacy.cs | 856 ------------------ Dotnet/Overlay/Cef/VRForm.Designer.cs | 104 --- Dotnet/Overlay/Cef/VRForm.cs | 64 -- Dotnet/Overlay/Cef/VRForm.resx | 123 --- .../Electron/AppApiVrElectron.cs | 10 - Dotnet/Overlay/Electron/VRCXVRElectron.cs | 197 ++-- Dotnet/{ => Overlay}/OpenVR/openvr_api.cs | 0 Dotnet/Overlay/VRCXVRInterface.cs | 7 +- Dotnet/OverlayWebSocket/OverlayManager.cs | 53 ++ Dotnet/OverlayWebSocket/OverlayMessage.cs | 17 + Dotnet/OverlayWebSocket/OverlayMessageType.cs | 9 + Dotnet/OverlayWebSocket/OverlayServer.cs | 212 +++++ Dotnet/OverlayWebSocket/OverlayVars.cs | 10 + Dotnet/Program.cs | 39 +- Dotnet/StartupArgs.cs | 21 +- Dotnet/VRCX-Cef.csproj | 7 +- Dotnet/VRCX-Electron-arm64.csproj | 5 +- Dotnet/VRCX-Electron.csproj | 5 +- src-electron/main.js | 219 ++--- src-electron/preload.js | 4 +- src/stores/friend.js | 6 +- src/stores/game.js | 17 +- src/stores/sharedFeed.js | 2 +- src/stores/updateLoop.js | 3 +- src/stores/vr.js | 9 +- src/types/globals.d.ts | 7 +- src/vr/Vr.vue | 92 +- src/vr/vr.css | 24 + 41 files changed, 1122 insertions(+), 2108 deletions(-) rename Dotnet/{AppApi/Common => Overlay}/AppApiVrCommon.cs (86%) rename Dotnet/{AppApi => Overlay}/Cef/AppApiVrCef.cs (81%) delete mode 100644 Dotnet/Overlay/Cef/OffScreenBrowserLegacy.cs create mode 100644 Dotnet/Overlay/Cef/OverlayClient.cs create mode 100644 Dotnet/Overlay/Cef/OverlayProgram.cs delete mode 100644 Dotnet/Overlay/Cef/VRCXVRLegacy.cs delete mode 100644 Dotnet/Overlay/Cef/VRForm.Designer.cs delete mode 100644 Dotnet/Overlay/Cef/VRForm.cs delete mode 100644 Dotnet/Overlay/Cef/VRForm.resx rename Dotnet/{AppApi => Overlay}/Electron/AppApiVrElectron.cs (88%) rename Dotnet/{ => Overlay}/OpenVR/openvr_api.cs (100%) create mode 100644 Dotnet/OverlayWebSocket/OverlayManager.cs create mode 100644 Dotnet/OverlayWebSocket/OverlayMessage.cs create mode 100644 Dotnet/OverlayWebSocket/OverlayMessageType.cs create mode 100644 Dotnet/OverlayWebSocket/OverlayServer.cs create mode 100644 Dotnet/OverlayWebSocket/OverlayVars.cs diff --git a/Dotnet/AppApi/Cef/AppApiCef.cs b/Dotnet/AppApi/Cef/AppApiCef.cs index 3bf360c6..be6e9509 100644 --- a/Dotnet/AppApi/Cef/AppApiCef.cs +++ b/Dotnet/AppApi/Cef/AppApiCef.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.IO; using System.Security.Cryptography; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -31,17 +32,15 @@ namespace VRCX public override void SetVR(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand) { - Program.VRCXVRInstance.SetActive(active, hmdOverlay, wristOverlay, menuButton, overlayHand); - } - - public override void RefreshVR() - { - Program.VRCXVRInstance.Restart(); - } - - public override void RestartVR() - { - Program.VRCXVRInstance.Restart(); + var updateVars = new OverlayVars + { + Active = active, + HmdOverlay = hmdOverlay, + WristOverlay = wristOverlay, + MenuButton = menuButton, + OverlayHand = overlayHand + }; + OverlayServer.Instance.UpdateVars(updateVars); } public override void SetZoom(double zoomLevel) @@ -116,14 +115,15 @@ namespace VRCX return File.Exists(Path.Join(Program.AppDataDirectory, "update.exe")); } - public override void ExecuteVrFeedFunction(string function, string json) - { - Program.VRCXVRInstance.ExecuteVrFeedFunction(function, json); - } - public override void ExecuteVrOverlayFunction(string function, string json) { - Program.VRCXVRInstance.ExecuteVrOverlayFunction(function, json); + var message = new OverlayMessage + { + Type = OverlayMessageType.JsFunctionCall, + FunctionName = function, + Data = json + }; + OverlayServer.Instance.SendMessage(message); } public override void FocusWindow() diff --git a/Dotnet/AppApi/Cef/GameHandler.cs b/Dotnet/AppApi/Cef/GameHandler.cs index 066e782c..1d4c35bf 100644 --- a/Dotnet/AppApi/Cef/GameHandler.cs +++ b/Dotnet/AppApi/Cef/GameHandler.cs @@ -23,7 +23,6 @@ namespace VRCX { var isGameRunning = false; var isSteamVRRunning = false; - var isHmdAfk = false; if (ProcessMonitor.Instance.IsProcessRunning("VRChat")) isGameRunning = true; @@ -31,12 +30,9 @@ namespace VRCX if (ProcessMonitor.Instance.IsProcessRunning("vrserver")) isSteamVRRunning = true; - if (Program.VRCXVRInstance != null) - isHmdAfk = Program.VRCXVRInstance.IsHmdAfk; - // TODO: fix this throwing an exception for being called before the browser is ready. somehow it gets past the checks if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame) - MainForm.Instance.Browser.ExecuteScriptAsync("window?.$pinia?.game.updateIsGameRunning", isGameRunning, isSteamVRRunning, isHmdAfk); + MainForm.Instance.Browser.ExecuteScriptAsync("window?.$pinia?.game.updateIsGameRunning", isGameRunning, isSteamVRRunning); } public override bool IsGameRunning() diff --git a/Dotnet/AppApi/Common/AppApiCommonBase.cs b/Dotnet/AppApi/Common/AppApiCommonBase.cs index 27336122..d9737253 100644 --- a/Dotnet/AppApi/Common/AppApiCommonBase.cs +++ b/Dotnet/AppApi/Common/AppApiCommonBase.cs @@ -9,8 +9,6 @@ namespace VRCX // AppApi public abstract void ShowDevTools(); public abstract void SetVR(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand); - public abstract void RefreshVR(); - public abstract void RestartVR(); public abstract void SetZoom(double zoomLevel); public abstract Task GetZoom(); public abstract void DesktopNotification(string BoldText, string Text = "", string Image = ""); @@ -18,7 +16,6 @@ namespace VRCX public abstract void RestartApplication(bool isUpgrade); public abstract bool CheckForUpdateExe(); - public abstract void ExecuteVrFeedFunction(string function, string json); public abstract void ExecuteVrOverlayFunction(string function, string json); public abstract void FocusWindow(); public abstract void ChangeTheme(int value); diff --git a/Dotnet/AppApi/Electron/AppApiElectron.cs b/Dotnet/AppApi/Electron/AppApiElectron.cs index c59dab33..1c14498e 100644 --- a/Dotnet/AppApi/Electron/AppApiElectron.cs +++ b/Dotnet/AppApi/Electron/AppApiElectron.cs @@ -19,16 +19,6 @@ namespace VRCX Program.VRCXVRInstance.SetActive(active, hmdOverlay, wristOverlay, menuButton, overlayHand); } - public override void RefreshVR() - { - Program.VRCXVRInstance.Restart(); - } - - public override void RestartVR() - { - Program.VRCXVRInstance.Restart(); - } - public override void SetZoom(double zoomLevel) { } @@ -51,11 +41,6 @@ namespace VRCX return false; } - public override void ExecuteVrFeedFunction(string function, string json) - { - Program.VRCXVRInstance.ExecuteVrFeedFunction(function, json); - } - public override void ExecuteVrOverlayFunction(string function, string json) { Program.VRCXVRInstance.ExecuteVrOverlayFunction(function, json); diff --git a/Dotnet/Cef/CefService.cs b/Dotnet/Cef/CefService.cs index 8ea38f21..0efed496 100644 --- a/Dotnet/Cef/CefService.cs +++ b/Dotnet/Cef/CefService.cs @@ -23,7 +23,10 @@ namespace VRCX internal void Init() { + var isOverlay = StartupArgs.LaunchArguments.IsOverlay; var userDataDir = Path.Join(Program.AppDataDirectory, "userdata"); + if (isOverlay) + userDataDir = Path.Join(Program.AppDataDirectory, "overlay/userdata"); // delete userdata if Cef version has been downgraded, fixes VRCX not opening after a downgrade CheckCefVersion(userDataDir); @@ -39,6 +42,8 @@ namespace VRCX BrowserSubprocessPath = Environment.ProcessPath, BackgroundColor = 0xFF0A0A0A }; + if (isOverlay) + cefSettings.LogFile = Path.Join(Program.AppDataDirectory, "overlay/logs/cef.log"); cefSettings.RegisterScheme(new CefCustomScheme { @@ -58,7 +63,7 @@ namespace VRCX cefSettings.CefCommandLineArgs.Add("disable-pdf-extension"); cefSettings.CefCommandLineArgs["autoplay-policy"] = "no-user-gesture-required"; cefSettings.CefCommandLineArgs.Add("disable-web-security"); - cefSettings.CefCommandLineArgs.Add("disk-cache-size", "2147483647"); + // cefSettings.CefCommandLineArgs.Add("disk-cache-size", "2147483647"); cefSettings.CefCommandLineArgs.Add("unsafely-disable-devtools-self-xss-warnings"); cefSettings.CefCommandLineArgs.Add("do-not-de-elevate"); // fix program failing to start when running as admin @@ -78,9 +83,13 @@ namespace VRCX // Discover network targets, Configure... // Add Remote Target: localhost:8089 logger.Info("Debug mode enabled"); - cefSettings.RemoteDebuggingPort = 8089; + cefSettings.RemoteDebuggingPort = !isOverlay ? 8089 : 8090; cefSettings.CefCommandLineArgs["remote-allow-origins"] = "*"; + } + // load extensions in debug mode + if (Program.LaunchDebug && !isOverlay) + { var extensionsPath = Path.Join(Program.AppDataDirectory, "extensions"); Directory.CreateDirectory(extensionsPath); @@ -111,10 +120,11 @@ namespace VRCX } } - CefSharpSettings.ShutdownOnExit = false; + CefSharpSettings.ShutdownOnExit = true; + CefSharpSettings.SubprocessExitIfParentProcessClosed = true; CefSharpSettings.ConcurrentTaskExecution = true; - if (Cef.Initialize(cefSettings, false) == false) + if (!Cef.Initialize(cefSettings, false)) { logger.Error("Cef failed to initialize"); throw new Exception("Cef.Initialize()"); diff --git a/Dotnet/Cef/JavascriptBindings.cs b/Dotnet/Cef/JavascriptBindings.cs index f6e32d62..c67dc39b 100644 --- a/Dotnet/Cef/JavascriptBindings.cs +++ b/Dotnet/Cef/JavascriptBindings.cs @@ -15,11 +15,5 @@ namespace VRCX repository.Register("Discord", Discord.Instance); repository.Register("AssetBundleManager", AssetBundleManager.Instance); } - - public static void ApplyVrJavascriptBindings(IJavascriptObjectRepository repository) - { - repository.NameConverter = null; - repository.Register("AppApiVr", AppApiVr.Instance); - } } } diff --git a/Dotnet/AppApi/Common/AppApiVrCommon.cs b/Dotnet/Overlay/AppApiVrCommon.cs similarity index 86% rename from Dotnet/AppApi/Common/AppApiVrCommon.cs rename to Dotnet/Overlay/AppApiVrCommon.cs index 954c89d2..2d37314b 100644 --- a/Dotnet/AppApi/Common/AppApiVrCommon.cs +++ b/Dotnet/Overlay/AppApiVrCommon.cs @@ -13,6 +13,5 @@ public abstract partial class AppApiVr public abstract double GetUptime(); public abstract string CurrentCulture(); public abstract string CustomVrScript(); - public abstract List> GetExecuteVrFeedFunctionQueue(); public abstract List> GetExecuteVrOverlayFunctionQueue(); } \ No newline at end of file diff --git a/Dotnet/AppApi/Cef/AppApiVrCef.cs b/Dotnet/Overlay/Cef/AppApiVrCef.cs similarity index 81% rename from Dotnet/AppApi/Cef/AppApiVrCef.cs rename to Dotnet/Overlay/Cef/AppApiVrCef.cs index ef586d7e..370af17f 100644 --- a/Dotnet/AppApi/Cef/AppApiVrCef.cs +++ b/Dotnet/Overlay/Cef/AppApiVrCef.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using CefSharp; +using VRCX.Overlay; namespace VRCX { @@ -20,8 +20,12 @@ namespace VRCX public override void VrInit() { - if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame) - MainForm.Instance.Browser.ExecuteScriptAsync("window?.$pinia?.vr.vrInit();"); + // IPC to main process that VR has initialized + var message = new OverlayMessage + { + Type = OverlayMessageType.OverlayConnected + }; + OverlayClient.SendMessage(message); } public override void ToggleSystemMonitor(bool enabled) @@ -45,7 +49,7 @@ namespace VRCX /// An array of arrays containing information about the connected VR devices. public override string[][] GetVRDevices() { - return Program.VRCXVRInstance.GetDevices(); + return OverlayProgram.VRCXVRInstance.GetDevices(); } /// @@ -75,11 +79,6 @@ namespace VRCX return string.Empty; } - public override List> GetExecuteVrFeedFunctionQueue() - { - throw new NotImplementedException("GetExecuteVrFeedFunctionQueue is not implemented in AppApiVrCef."); - } - public override List> GetExecuteVrOverlayFunctionQueue() { throw new NotImplementedException("GetExecuteVrOverlayFunctionQueue is not implemented in AppApiVrCef."); diff --git a/Dotnet/Overlay/Cef/OffScreenBrowser.cs b/Dotnet/Overlay/Cef/OffScreenBrowser.cs index e272fff3..05f55adf 100644 --- a/Dotnet/Overlay/Cef/OffScreenBrowser.cs +++ b/Dotnet/Overlay/Cef/OffScreenBrowser.cs @@ -3,151 +3,273 @@ using CefSharp.Enums; using CefSharp.OffScreen; using CefSharp.Structs; using System; +using System.Runtime.InteropServices; using System.Threading; using NLog; using Silk.NET.Core.Native; using Silk.NET.Direct3D11; using Range = CefSharp.Structs.Range; -namespace VRCX +namespace VRCX; + +public class OffScreenBrowser : ChromiumWebBrowser, IRenderHandler { - public class OffScreenBrowser : ChromiumWebBrowser, IRenderHandler + // new + private ComPtr _device1; + private ComPtr _deviceContext; + private ComPtr _query; + private ComPtr _renderTarget; + + // legacy + private readonly bool _isLegacy; + private readonly ReaderWriterLockSlim _paintBufferLock = new(); + private GCHandle _paintBuffer; + private int _width; + private int _height; + + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + public OffScreenBrowser(string address, int width, int height, bool isLegacy) + : base(address, automaticallyCreateBrowser: false) { - private ComPtr _device1; - private ComPtr _deviceContext; - private ComPtr _query; - private ComPtr _renderTarget; + _isLegacy = isLegacy; + var windowInfo = new WindowInfo(); + windowInfo.SetAsWindowless(IntPtr.Zero); + windowInfo.WindowlessRenderingEnabled = true; + windowInfo.SharedTextureEnabled = !_isLegacy; + windowInfo.Width = width; + windowInfo.Height = height; - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); - - public OffScreenBrowser(string address, int width, int height) - : base(address, automaticallyCreateBrowser: false) + var browserSettings = new BrowserSettings() { - var windowInfo = new WindowInfo(); - windowInfo.SetAsWindowless(IntPtr.Zero); - windowInfo.WindowlessRenderingEnabled = true; - windowInfo.SharedTextureEnabled = true; - windowInfo.Width = width; - windowInfo.Height = height; + DefaultEncoding = "UTF-8", + WindowlessFrameRate = !_isLegacy ? 60 : 24 + }; - var browserSettings = new BrowserSettings() - { - DefaultEncoding = "UTF-8", - WindowlessFrameRate = 60 - }; + CreateBrowser(windowInfo, browserSettings); - CreateBrowser(windowInfo, browserSettings); + Size = new System.Drawing.Size(width, height); + RenderHandler = this; - Size = new System.Drawing.Size(width, height); - RenderHandler = this; + JavascriptObjectRepository.NameConverter = null; + JavascriptObjectRepository.Register("AppApiVr", AppApiVr.Instance); + } - JavascriptBindings.ApplyVrJavascriptBindings(JavascriptObjectRepository); + // new + public void UpdateRender(ComPtr device, ComPtr deviceContext, ComPtr renderTarget) + { + _device1.Dispose(); + _device1 = device.QueryInterface(); + _deviceContext = deviceContext; + _renderTarget = renderTarget; + + _query.Dispose(); + device.CreateQuery(new QueryDesc + { + Query = Query.Event, + MiscFlags = 0 + }, ref _query); + } + + // legacy + public void RenderToTexture(ComPtr deviceContext, ComPtr texture) + { + // Safeguard against uninitialized texture + unsafe + { + if ((IntPtr)texture.Handle == IntPtr.Zero) + return; } - public void UpdateRender(ComPtr device, ComPtr deviceContext, ComPtr renderTarget) + _paintBufferLock.EnterReadLock(); + try { - _device1.Dispose(); - _device1 = device.QueryInterface(); - _deviceContext = deviceContext; - _renderTarget = renderTarget; - - _query.Dispose(); - device.CreateQuery(new QueryDesc - { - Query = Query.Event, - MiscFlags = 0 - }, ref _query); - } - - public new void Dispose() - { - RenderHandler = null; - base.Dispose(); - } - - ScreenInfo? IRenderHandler.GetScreenInfo() - { - return new ScreenInfo - { - DeviceScaleFactor = 1.0F - }; - } - - bool IRenderHandler.GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY) - { - screenX = viewX; - screenY = viewY; - return false; - } - - Rect IRenderHandler.GetViewRect() - { - return new Rect(0, 0, Size.Width, Size.Height); - } - - void IRenderHandler.OnAcceleratedPaint(PaintElementType type, Rect dirtyRect, AcceleratedPaintInfo paintInfo) - { - if (type != PaintElementType.View) + if (_width <= 0 || + _height <= 0) return; + MappedSubresource mappedSubresource = default; + deviceContext.Map(texture, 0, Map.WriteDiscard, 0, ref mappedSubresource); unsafe { - if ((IntPtr)_device1.Handle == IntPtr.Zero) - return; - - if ((IntPtr)_deviceContext.Handle == IntPtr.Zero) - return; - - if ((IntPtr)_query.Handle == IntPtr.Zero) - return; - - if ((IntPtr)_renderTarget.Handle == IntPtr.Zero) - return; - - using ComPtr cefTexture = - _device1.OpenSharedResource1(paintInfo.SharedTextureHandle.ToPointer()); - _deviceContext.CopyResource(_renderTarget, cefTexture); - _deviceContext.End(_query); - _deviceContext.Flush(); - - while (_deviceContext.GetData(_query, IntPtr.Zero.ToPointer(), 0, 0) == 1) + if ((IntPtr)mappedSubresource.PData != IntPtr.Zero) { - Thread.Yield(); + var sourcePtr = _paintBuffer.AddrOfPinnedObject(); + var destinationPtr = (IntPtr)mappedSubresource.PData; + var pitch = _width * 4; + var rowPitch = mappedSubresource.RowPitch; + if (pitch == rowPitch) + { + WinApi.RtlCopyMemory( + destinationPtr, + sourcePtr, + (uint)(_width * _height * 4) + ); + } + else + { + for (var y = _height; y > 0; --y) + { + WinApi.RtlCopyMemory( + destinationPtr, + sourcePtr, + (uint)pitch + ); + sourcePtr += pitch; + destinationPtr += (IntPtr)rowPitch; + } + } } + + deviceContext.Unmap(texture, 0); } } - - void IRenderHandler.OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo) - { - } - - void IRenderHandler.OnImeCompositionRangeChanged(Range selectedRange, Rect[] characterBounds) - { - } - - void IRenderHandler.OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buffer, int width, int height) - { - } - - void IRenderHandler.OnPopupShow(bool show) - { - } - - void IRenderHandler.OnPopupSize(Rect rect) - { - } - - void IRenderHandler.OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode) - { - } - - bool IRenderHandler.StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y) - { - return false; - } - - void IRenderHandler.UpdateDragCursor(DragOperationsMask operation) + finally { + _paintBufferLock.ExitReadLock(); } } -} + + public new void Dispose() + { + RenderHandler = null; + base.Dispose(); + + // legacy + _paintBufferLock.EnterWriteLock(); + try + { + if (_paintBuffer.IsAllocated) + _paintBuffer.Free(); + } + finally + { + _paintBufferLock.ExitWriteLock(); + } + _paintBufferLock.Dispose(); + } + + ScreenInfo? IRenderHandler.GetScreenInfo() + { + return new ScreenInfo + { + DeviceScaleFactor = 1.0F + }; + } + + bool IRenderHandler.GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY) + { + screenX = viewX; + screenY = viewY; + return false; + } + + Rect IRenderHandler.GetViewRect() + { + return new Rect(0, 0, Size.Width, Size.Height); + } + + // new + void IRenderHandler.OnAcceleratedPaint(PaintElementType type, Rect dirtyRect, AcceleratedPaintInfo paintInfo) + { + if (_isLegacy) + return; + + if (type != PaintElementType.View) + return; + + unsafe + { + if ((IntPtr)_device1.Handle == IntPtr.Zero) + return; + + if ((IntPtr)_deviceContext.Handle == IntPtr.Zero) + return; + + if ((IntPtr)_query.Handle == IntPtr.Zero) + return; + + if ((IntPtr)_renderTarget.Handle == IntPtr.Zero) + return; + + using ComPtr cefTexture = + _device1.OpenSharedResource1(paintInfo.SharedTextureHandle.ToPointer()); + _deviceContext.CopyResource(_renderTarget, cefTexture); + _deviceContext.End(_query); + _deviceContext.Flush(); + + while (_deviceContext.GetData(_query, IntPtr.Zero.ToPointer(), 0, 0) == 1) + { + Thread.Yield(); + } + } + } + + void IRenderHandler.OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo) + { + } + + void IRenderHandler.OnImeCompositionRangeChanged(Range selectedRange, Rect[] characterBounds) + { + } + + // legacy + void IRenderHandler.OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buffer, int width, int height) + { + if (!_isLegacy) + return; + + if (type != PaintElementType.View) + return; + + _paintBufferLock.EnterWriteLock(); + try + { + if (_width != width || + _height != height) + { + _width = width; + _height = height; + if (_paintBuffer.IsAllocated) + { + _paintBuffer.Free(); + } + _paintBuffer = GCHandle.Alloc( + new byte[_width * _height * 4], + GCHandleType.Pinned + ); + } + + WinApi.RtlCopyMemory( + _paintBuffer.AddrOfPinnedObject(), + buffer, + (uint)(width * height * 4) + ); + } + finally + { + _paintBufferLock.ExitWriteLock(); + } + } + + void IRenderHandler.OnPopupShow(bool show) + { + } + + void IRenderHandler.OnPopupSize(Rect rect) + { + } + + void IRenderHandler.OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode) + { + } + + bool IRenderHandler.StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y) + { + return false; + } + + void IRenderHandler.UpdateDragCursor(DragOperationsMask operation) + { + } +} \ No newline at end of file diff --git a/Dotnet/Overlay/Cef/OffScreenBrowserLegacy.cs b/Dotnet/Overlay/Cef/OffScreenBrowserLegacy.cs deleted file mode 100644 index 8447aeed..00000000 --- a/Dotnet/Overlay/Cef/OffScreenBrowserLegacy.cs +++ /dev/null @@ -1,212 +0,0 @@ -using CefSharp; -using CefSharp.Enums; -using CefSharp.OffScreen; -using CefSharp.Structs; -using System; -using System.Runtime.InteropServices; -using System.Threading; -using Silk.NET.Core.Native; -using Silk.NET.Direct3D11; -using Range = CefSharp.Structs.Range; - -namespace VRCX -{ - public class OffScreenBrowserLegacy : ChromiumWebBrowser, IRenderHandler - { - private readonly ReaderWriterLockSlim _paintBufferLock; - private GCHandle _paintBuffer; - private int _width; - private int _height; - - public OffScreenBrowserLegacy(string address, int width, int height) - : base(address, automaticallyCreateBrowser: false) - { - _paintBufferLock = new ReaderWriterLockSlim(); - - var windowInfo = new WindowInfo(); - windowInfo.SetAsWindowless(IntPtr.Zero); - windowInfo.WindowlessRenderingEnabled = true; - windowInfo.SharedTextureEnabled = false; - windowInfo.Width = width; - windowInfo.Height = height; - - var browserSettings = new BrowserSettings() - { - DefaultEncoding = "UTF-8", - WindowlessFrameRate = 24 - }; - - CreateBrowser(windowInfo, browserSettings); - - Size = new System.Drawing.Size(width, height); - RenderHandler = this; - - JavascriptBindings.ApplyVrJavascriptBindings(JavascriptObjectRepository); - } - - public new void Dispose() - { - RenderHandler = null; - base.Dispose(); - - _paintBufferLock.EnterWriteLock(); - try - { - if (_paintBuffer.IsAllocated == true) - { - _paintBuffer.Free(); - } - } - finally - { - _paintBufferLock.ExitWriteLock(); - } - - _paintBufferLock.Dispose(); - } - - public void RenderToTexture(ComPtr deviceContext, ComPtr texture) - { - // Safeguard against uninitialized texture - unsafe - { - if ((IntPtr)texture.Handle == IntPtr.Zero) - return; - } - - _paintBufferLock.EnterReadLock(); - try - { - if (_width > 0 && - _height > 0) - { - MappedSubresource mappedSubresource = default; - deviceContext.Map(texture, 0, Map.WriteDiscard, 0, ref mappedSubresource); - unsafe - { - if ((IntPtr)mappedSubresource.PData != IntPtr.Zero) - { - var sourcePtr = _paintBuffer.AddrOfPinnedObject(); - var destinationPtr = (IntPtr)mappedSubresource.PData; - var pitch = _width * 4; - var rowPitch = mappedSubresource.RowPitch; - if (pitch == rowPitch) - { - WinApi.RtlCopyMemory( - destinationPtr, - sourcePtr, - (uint)(_width * _height * 4) - ); - } - else - { - for (var y = _height; y > 0; --y) - { - WinApi.RtlCopyMemory( - destinationPtr, - sourcePtr, - (uint)pitch - ); - sourcePtr += pitch; - destinationPtr += (IntPtr)rowPitch; - } - } - } - - deviceContext.Unmap(texture, 0); - } - } - } - finally - { - _paintBufferLock.ExitReadLock(); - } - } - - ScreenInfo? IRenderHandler.GetScreenInfo() - { - return null; - } - - bool IRenderHandler.GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY) - { - screenX = viewX; - screenY = viewY; - return false; - } - - Rect IRenderHandler.GetViewRect() - { - return new Rect(0, 0, Size.Width, Size.Height); - } - - void IRenderHandler.OnAcceleratedPaint(PaintElementType type, Rect dirtyRect, AcceleratedPaintInfo paintInfo) - { - // NOT USED - } - - void IRenderHandler.OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo) - { - } - - void IRenderHandler.OnImeCompositionRangeChanged(Range selectedRange, Rect[] characterBounds) - { - } - - void IRenderHandler.OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buffer, int width, int height) - { - if (type == PaintElementType.View) - { - _paintBufferLock.EnterWriteLock(); - try - { - if (_width != width || - _height != height) - { - _width = width; - _height = height; - if (_paintBuffer.IsAllocated == true) - { - _paintBuffer.Free(); - } - _paintBuffer = GCHandle.Alloc( - new byte[_width * _height * 4], - GCHandleType.Pinned - ); - } - - WinApi.RtlCopyMemory( - _paintBuffer.AddrOfPinnedObject(), - buffer, - (uint)(width * height * 4) - ); - } - finally - { - _paintBufferLock.ExitWriteLock(); - } - } - } - - void IRenderHandler.OnPopupShow(bool show) - { - } - - void IRenderHandler.OnPopupSize(Rect rect) - { - } - - void IRenderHandler.OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode) - { - } - - bool IRenderHandler.StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y) - { - return false; - } - - void IRenderHandler.UpdateDragCursor(DragOperationsMask operation) - { - } - } -} diff --git a/Dotnet/Overlay/Cef/OverlayClient.cs b/Dotnet/Overlay/Cef/OverlayClient.cs new file mode 100644 index 00000000..19ab28b4 --- /dev/null +++ b/Dotnet/Overlay/Cef/OverlayClient.cs @@ -0,0 +1,120 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Net.WebSockets; +using System.Text.Json; +using System.Threading.Tasks; +using NLog; +using VRCX.Overlay; +using Websocket.Client; + +namespace VRCX; + +[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")] +public static class OverlayClient +{ + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + private static readonly Uri WebsocketUri = new("ws://127.0.0.1:34582"); + private static WebsocketClient? _websocketClient; + + public static bool ConnectedAndActive => + _websocketClient != null && _websocketClient.IsRunning && + OverlayProgram.VRCXVRInstance.IsActive(); + + public static async Task Init() + { + if (_websocketClient != null) + return; + + var clientFactory = new Func(() => + { + var client = new ClientWebSocket(); + client.Options.KeepAliveInterval = TimeSpan.FromSeconds(5); + client.Options.SetRequestHeader("user-agent", "VRCX Overlay Client"); + return client; + }); + _websocketClient = new WebsocketClient(WebsocketUri, clientFactory) + { + Name = "VRCX Overlay Client", + ReconnectTimeout = null, + ErrorReconnectTimeout = TimeSpan.FromMilliseconds(300) + }; + + _websocketClient.ReconnectionHappened.Subscribe(info => + { + logger.Info("Connection happened, type: {0}", info?.Type.ToString()); + var message = new OverlayMessage + { + Type = OverlayMessageType.OverlayConnected, + Data = "VRCX Overlay Client Connected" + }; + _websocketClient.Send(JsonSerializer.Serialize(message)); + }); + _websocketClient.DisconnectionHappened.Subscribe(info => + { + logger.Info("Disconnection happened, type: {0}", info?.Type.ToString()); + }); + _websocketClient.MessageReceived.Subscribe(msg => + { + try + { + var message = JsonSerializer.Deserialize(msg.Text!); + HandleMessage(message); + } + catch (Exception e) + { + logger.Error(e, "Error handling message"); + } + }); + + _websocketClient.Start(); + logger.Info("VRCX overlay client initialized"); + } + + private static void HandleMessage(OverlayMessage message) + { + logger.Trace("Message received: {0}", message.Type.ToString()); + switch (message.Type) + { + case OverlayMessageType.OverlayConnected: + break; + + case OverlayMessageType.JsFunctionCall: + OverlayProgram.VRCXVRInstance.ExecuteVrOverlayFunction(message.FunctionName, message.Data); + break; + + case OverlayMessageType.UpdateVars: + var overlayVars = message.OverlayVars; + if (overlayVars == null) + { + logger.Error("UpdateVars is null"); + return; + } + OverlayProgram.VRCXVRInstance.SetActive(overlayVars.Active, overlayVars.HmdOverlay, overlayVars.WristOverlay, + overlayVars.MenuButton, overlayVars.OverlayHand); + break; + + default: + throw new ArgumentOutOfRangeException(); + } + + } + + public static async Task Exit() + { + if (_websocketClient == null) + return; + + _websocketClient.Stop(WebSocketCloseStatus.NormalClosure, "Exiting"); + _websocketClient.Dispose(); + _websocketClient = null; + } + + public static void SendMessage(OverlayMessage message) + { + if (_websocketClient == null || !_websocketClient.IsRunning) + return; + + _websocketClient.Send(JsonSerializer.Serialize(message)); + } +} diff --git a/Dotnet/Overlay/Cef/OverlayProgram.cs b/Dotnet/Overlay/Cef/OverlayProgram.cs new file mode 100644 index 00000000..5f88b72c --- /dev/null +++ b/Dotnet/Overlay/Cef/OverlayProgram.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Forms; +using NLog; + +namespace VRCX.Overlay; + +internal static class OverlayProgram +{ + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + public static VRCXVRInterface VRCXVRInstance; + + public static void OverlayMain() + { + logger.Info("VRCX Overlay starting..."); + CefService.Instance.Init(); + AppApiVr.Instance = new AppApiVrCef(); + var isLegacy = VRCXStorage.Instance.Get("VRCX_DisableVrOverlayGpuAcceleration") == "true"; + VRCXVRInstance = new VRCXVRCef(isLegacy); + VRCXVRInstance.Init(); + + OverlayClient.Init(); + + logger.Info("VRCX Overlay started..."); + QuitProcess(); + ApplicationConfiguration.Initialize(); + var context = new ApplicationContext(); + Application.Run(context); + Exit(); + } + + private static void Exit() + { + logger.Info("VRCX Overlay exiting..."); + // CefService.Instance.Exit(); + OverlayClient.Exit(); + VRCXVRInstance.Exit(); + Environment.Exit(0); + } + + private static async Task QuitProcess() + { + await Task.Delay(5000); + while (OverlayClient.ConnectedAndActive) + { + await Task.Delay(500); + } + Exit(); + } +} \ No newline at end of file diff --git a/Dotnet/Overlay/Cef/VRCXVRCef.cs b/Dotnet/Overlay/Cef/VRCXVRCef.cs index 10667f13..08c8d6c5 100644 --- a/Dotnet/Overlay/Cef/VRCXVRCef.cs +++ b/Dotnet/Overlay/Cef/VRCXVRCef.cs @@ -12,12 +12,12 @@ using Silk.NET.Core.Native; using Silk.NET.Direct3D11; using Silk.NET.DXGI; using Valve.VR; +using VRCX.Overlay; namespace VRCX { public class VRCXVRCef : VRCXVRInterface { - public static VRCXVRInterface Instance; private static readonly Logger logger = LogManager.GetCurrentClassLogger(); private static readonly float[] _rotation = { 0f, 0f, 0f }; private static readonly float[] _translation = { 0f, 0f, 0f }; @@ -25,11 +25,11 @@ namespace VRCX private static readonly float[] _translationRight = { 7f / 100f, -5f / 100f, 6f / 100f }; private static readonly float[] _rotationLeft = { 90f * (float)(Math.PI / 180f), 90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f) }; private static readonly float[] _rotationRight = { -90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f) }; - private static OffScreenBrowser _wristOverlay; - private static OffScreenBrowser _hmdOverlay; + private static OffScreenBrowser _sharedOverlay; private readonly List _deviceList; private readonly ReaderWriterLockSlim _deviceListLock; + private readonly bool _isLegacy; private bool _active; private bool _menuButton; private int _overlayHand; @@ -44,6 +44,11 @@ namespace VRCX private bool _wristOverlayActive; private bool _wristOverlayWasActive; + private const int HMD_HEIGHT = 1024; + private const int WRIST_SIZE = 512; + private const int TOTAL_WIDTH = 1024; + private const int TOTAL_HEIGHT = HMD_HEIGHT + WRIST_SIZE; // 1024 + 512 = 1536 + private DXGI _dxgi; private D3D11 _d3d11; private ComPtr _factory; @@ -52,16 +57,11 @@ namespace VRCX private ComPtr _multithread; private ComPtr _deviceContext; - private ComPtr _texture1; - private ComPtr _texture2; + private ComPtr _sharedTexture; - static VRCXVRCef() - { - Instance = new VRCXVRCef(); - } - - public VRCXVRCef() + public VRCXVRCef(bool isLegacy) { + _isLegacy = isLegacy; _deviceListLock = new ReaderWriterLockSlim(); _deviceList = new List(); _thread = new Thread(ThreadLoop) @@ -70,8 +70,6 @@ namespace VRCX }; } - // NOTE - // 메모리 릭 때문에 미리 생성해놓고 계속 사용함 public override void Init() { _thread.Start(); @@ -88,10 +86,8 @@ namespace VRCX public override void Restart() { Exit(); - Instance = new VRCXVRCef(); - Instance.Init(); - Program.VRCXVRInstance = Instance; - MainForm.Instance.Browser.ExecuteScriptAsync("console.log('VRCXVR Restarted');"); + OverlayProgram.VRCXVRInstance = new VRCXVRCef(_isLegacy); + OverlayProgram.VRCXVRInstance.Init(); } private void SetupTextures() @@ -111,6 +107,7 @@ namespace VRCX _device.Dispose(); _deviceContext.Dispose(); + SilkMarshal.ThrowHResult ( _d3d11.CreateDevice @@ -128,51 +125,39 @@ namespace VRCX ) ); + if ((IntPtr)_sharedTexture.Handle != IntPtr.Zero) + { + _sharedTexture.Dispose(); + } + + SilkMarshal.ThrowHResult + ( + _device.CreateTexture2D(new Texture2DDesc + { + Width = TOTAL_WIDTH, + Height = TOTAL_HEIGHT, + MipLevels = 1, + ArraySize = 1, + Format = Format.FormatB8G8R8A8Unorm, + SampleDesc = new SampleDesc + { + Count = 1, + Quality = 0 + }, + BindFlags = (uint)BindFlag.ShaderResource, + CPUAccessFlags = _isLegacy ? (uint)CpuAccessFlag.Write : (uint)CpuAccessFlag.None, + Usage = _isLegacy ? Usage.Dynamic : Usage.Default + }, null, ref _sharedTexture) + ); + + // new + _sharedOverlay?.UpdateRender(_device, _deviceContext, _sharedTexture); + _multithread = _device.QueryInterface(); _multithread.SetMultithreadProtected(true); if (Program.LaunchDebug) _device.SetInfoQueueCallback(msg => logger.Info(SilkMarshal.PtrToString((nint)msg.PDescription)!)); - - _texture1.Dispose(); - SilkMarshal.ThrowHResult - ( - _device.CreateTexture2D(new Texture2DDesc - { - Width = 512, - Height = 512, - MipLevels = 1, - ArraySize = 1, - Format = Format.FormatB8G8R8A8Unorm, - SampleDesc = new SampleDesc - { - Count = 1, - Quality = 0 - }, - BindFlags = (uint)BindFlag.ShaderResource - }, null, ref _texture1) - ); - _wristOverlay?.UpdateRender(_device, _deviceContext, _texture1); - - _texture2.Dispose(); - SilkMarshal.ThrowHResult - ( - _device.CreateTexture2D(new Texture2DDesc - { - Width = 1024, - Height = 1024, - MipLevels = 1, - ArraySize = 1, - Format = Format.FormatB8G8R8A8Unorm, - SampleDesc = new SampleDesc - { - Count = 1, - Quality = 0 - }, - BindFlags = (uint)BindFlag.ShaderResource - }, null, ref _texture2) - ); - _hmdOverlay?.UpdateRender(_device, _deviceContext, _texture2); } } @@ -188,20 +173,17 @@ namespace VRCX var overlayVisible2 = false; var dashboardHandle = 0UL; - _wristOverlay = new OffScreenBrowser( - Program.LaunchDebug ? "http://localhost:9000/vr.html?wrist" : "file://vrcx/vr.html?wrist", - 512, - 512 - ); - - _hmdOverlay = new OffScreenBrowser( - Program.LaunchDebug ? "http://localhost:9000/vr.html?hmd" : "file://vrcx/vr.html?hmd", - 1024, - 1024 + _sharedOverlay = new OffScreenBrowser( + Program.LaunchDebug ? "http://localhost:9000/vr.html" : "file://vrcx/vr.html", + TOTAL_WIDTH, + TOTAL_HEIGHT, + _isLegacy ); while (_thread != null) { + if (_isLegacy && (_wristOverlayActive || _hmdOverlayActive)) + _sharedOverlay.RenderToTexture(_deviceContext, _sharedTexture); try { Thread.Sleep(32); @@ -267,18 +249,18 @@ namespace VRCX if (overlay != null) { var dashboardVisible = overlay.IsDashboardVisible(); - var err = ProcessDashboard(overlay, ref dashboardHandle, dashboardVisible); - if (err != EVROverlayError.None && - dashboardHandle != 0) - { - overlay.DestroyOverlay(dashboardHandle); - dashboardHandle = 0; - logger.Error(err); - } + // var err = ProcessDashboard(overlay, ref dashboardHandle, dashboardVisible); + // if (err != EVROverlayError.None && + // dashboardHandle != 0) + // { + // overlay.DestroyOverlay(dashboardHandle); + // dashboardHandle = 0; + // logger.Error(err); + // } if (_wristOverlayActive) { - err = ProcessOverlay1(overlay, ref _wristOverlayHandle, ref overlayVisible1, + var err = ProcessOverlay1(overlay, ref _wristOverlayHandle, ref overlayVisible1, dashboardVisible, overlayIndex); if (err != EVROverlayError.None && _wristOverlayHandle != 0) @@ -291,7 +273,7 @@ namespace VRCX if (_hmdOverlayActive) { - err = ProcessOverlay2(overlay, ref _hmdOverlayHandle, ref overlayVisible2, + var err = ProcessOverlay2(overlay, ref _hmdOverlayHandle, ref overlayVisible2, dashboardVisible); if (err != EVROverlayError.None && _hmdOverlayHandle != 0) @@ -321,13 +303,18 @@ namespace VRCX } } - _hmdOverlay?.Dispose(); - _wristOverlay?.Dispose(); - _texture2.Dispose(); - _texture1.Dispose(); _device.Dispose(); _adapter.Dispose(); _factory.Dispose(); + + _sharedOverlay?.Dispose(); + _sharedOverlay = null; + _sharedTexture.Dispose(); + _sharedTexture = default; + _deviceContext.Dispose(); + _multithread.Dispose(); + _dxgi?.Dispose(); + _d3d11?.Dispose(); } public override void SetActive(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand) @@ -355,10 +342,14 @@ namespace VRCX _wristOverlayWasActive = _wristOverlayActive; } + public override bool IsActive() + { + return _active; + } + public override void Refresh() { - _wristOverlay.Reload(); - _hmdOverlay.Reload(); + _sharedOverlay.Reload(); } public override string[][] GetDevices() @@ -405,7 +396,12 @@ namespace VRCX if (isHmdAfk != IsHmdAfk) { IsHmdAfk = isHmdAfk; - Program.AppApiInstance.CheckGameRunning(); + var message = new OverlayMessage + { + Type = OverlayMessageType.IsHmdAfk, + Data = IsHmdAfk.ToString() + }; + OverlayClient.SendMessage(message); } var headsetErr = ETrackedPropertyError.TrackedProp_Success; @@ -593,37 +589,22 @@ namespace VRCX var e = new VREvent_t(); - while (overlay.PollNextOverlayEvent(dashboardHandle, ref e, (uint)Marshal.SizeOf(e))) - { - var type = (EVREventType)e.eventType; - if (type == EVREventType.VREvent_MouseMove) - { - var m = e.data.mouse; - var s = _wristOverlay.Size; - _wristOverlay.GetBrowserHost().SendMouseMoveEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), false, CefEventFlags.None); - } - else if (type == EVREventType.VREvent_MouseButtonDown) - { - var m = e.data.mouse; - var s = _wristOverlay.Size; - _wristOverlay.GetBrowserHost().SendMouseClickEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), MouseButtonType.Left, false, 1, CefEventFlags.LeftMouseButton); - } - else if (type == EVREventType.VREvent_MouseButtonUp) - { - var m = e.data.mouse; - var s = _wristOverlay.Size; - _wristOverlay.GetBrowserHost().SendMouseClickEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), MouseButtonType.Left, true, 1, CefEventFlags.None); - } - } - if (dashboardVisible) { unsafe { var texture = new Texture_t { - handle = (IntPtr)_texture1.Handle + handle = (IntPtr)_sharedTexture.Handle }; + var bounds = new VRTextureBounds_t + { + uMin = 0f, + uMax = 1f, + vMin = (float)(TOTAL_HEIGHT - HMD_HEIGHT) / TOTAL_HEIGHT, + vMax = 1f + }; + overlay.SetOverlayTextureBounds(dashboardHandle, ref bounds); err = overlay.SetOverlayTexture(dashboardHandle, ref texture); if (err != EVROverlayError.None) { @@ -707,30 +688,29 @@ namespace VRCX } } - if (!dashboardVisible && - DateTime.UtcNow.CompareTo(_nextOverlayUpdate) <= 0) + if (!dashboardVisible && DateTime.UtcNow.CompareTo(_nextOverlayUpdate) <= 0) { unsafe - { + { var texture = new Texture_t { - handle = (IntPtr)_texture1.Handle + handle = (IntPtr)_sharedTexture.Handle }; err = overlay.SetOverlayTexture(overlayHandle, ref texture); - if (err != EVROverlayError.None) - { - return err; - } } + var bounds = new VRTextureBounds_t + { + uMin = 0f, + uMax = 0.5f, + vMin = 0f, + vMax = (float)WRIST_SIZE / TOTAL_HEIGHT + }; + overlay.SetOverlayTextureBounds(overlayHandle, ref bounds); + if (!overlayVisible) { err = overlay.ShowOverlay(overlayHandle); - if (err != EVROverlayError.None) - { - return err; - } - overlayVisible = true; } } @@ -818,23 +798,23 @@ namespace VRCX { var texture = new Texture_t { - handle = (IntPtr)_texture2.Handle + handle = (IntPtr)_sharedTexture.Handle }; err = overlay.SetOverlayTexture(overlayHandle, ref texture); - if (err != EVROverlayError.None) - { - return err; - } } + var bounds = new VRTextureBounds_t + { + uMin = 0f, + uMax = 1f, + vMin = (float)(TOTAL_HEIGHT - HMD_HEIGHT) / TOTAL_HEIGHT, + vMax = 1f + }; + overlay.SetOverlayTextureBounds(overlayHandle, ref bounds); + if (!overlayVisible) { err = overlay.ShowOverlay(overlayHandle); - if (err != EVROverlayError.None) - { - return err; - } - overlayVisible = true; } } @@ -852,19 +832,6 @@ namespace VRCX return err; } - public override ConcurrentQueue> GetExecuteVrFeedFunctionQueue() - { - throw new NotImplementedException(); - } - - public override void ExecuteVrFeedFunction(string function, string json) - { - if (_wristOverlay == null) return; - // if (_wristOverlay.IsLoading) - // Restart(); - _wristOverlay.ExecuteScriptAsync($"$vr.{function}", json); - } - public override ConcurrentQueue> GetExecuteVrOverlayFunctionQueue() { throw new NotImplementedException(); @@ -872,10 +839,10 @@ namespace VRCX public override void ExecuteVrOverlayFunction(string function, string json) { - if (_hmdOverlay == null) return; - // if (_hmdOverlay.IsLoading) - // Restart(); - _hmdOverlay.ExecuteScriptAsync($"$vr.{function}", json); + if (_sharedOverlay == null || _sharedOverlay.IsLoading || !_sharedOverlay.CanExecuteJavascriptInMainFrame) + return; + + _sharedOverlay.ExecuteScriptAsync($"$vr.{function}", json); } } } \ No newline at end of file diff --git a/Dotnet/Overlay/Cef/VRCXVRLegacy.cs b/Dotnet/Overlay/Cef/VRCXVRLegacy.cs deleted file mode 100644 index 4f23c768..00000000 --- a/Dotnet/Overlay/Cef/VRCXVRLegacy.cs +++ /dev/null @@ -1,856 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Numerics; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using CefSharp; -using NLog; -using Silk.NET.Core.Native; -using Silk.NET.Direct3D11; -using Silk.NET.DXGI; -using Valve.VR; - -namespace VRCX -{ - public class VRCXVRLegacy : VRCXVRInterface - { - public static VRCXVRInterface Instance; - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); - private static readonly float[] _rotation = { 0f, 0f, 0f }; - private static readonly float[] _translation = { 0f, 0f, 0f }; - private static readonly float[] _translationLeft = { -7f / 100f, -5f / 100f, 6f / 100f }; - private static readonly float[] _translationRight = { 7f / 100f, -5f / 100f, 6f / 100f }; - private static readonly float[] _rotationLeft = { 90f * (float)(Math.PI / 180f), 90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f) }; - private static readonly float[] _rotationRight = { -90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f), -90f * (float)(Math.PI / 180f) }; - private static OffScreenBrowserLegacy _wristOverlay; - private static OffScreenBrowserLegacy _hmdOverlay; - - private readonly List _deviceList; - private readonly ReaderWriterLockSlim _deviceListLock; - private bool _active; - private bool _hmdOverlayActive; - private bool _menuButton; - private int _overlayHand; - private Thread _thread; - private bool _wristOverlayActive; - private DateTime _nextOverlayUpdate; - - private DXGI _dxgi; - private D3D11 _d3d11; - private ComPtr _factory; - private ComPtr _adapter; - private ComPtr _device; - private ComPtr _multithread; - private ComPtr _deviceContext; - - private ComPtr _texture1; - private ComPtr _texture2; - - static VRCXVRLegacy() - { - Instance = new VRCXVRLegacy(); - } - - public VRCXVRLegacy() - { - _deviceListLock = new ReaderWriterLockSlim(); - _deviceList = new List(); - _thread = new Thread(ThreadLoop) - { - IsBackground = true - }; - } - - // NOTE - // 메모리 릭 때문에 미리 생성해놓고 계속 사용함 - public override void Init() - { - _thread.Start(); - } - - public override void Exit() - { - var thread = _thread; - _thread = null; - thread?.Interrupt(); - thread?.Join(); - } - - public override void Restart() - { - Exit(); - Instance = new VRCXVRLegacy(); - Instance.Init(); - Program.VRCXVRInstance = Instance; - MainForm.Instance.Browser.ExecuteScriptAsync("console.log('VRCXVR Restarted');"); - } - - private void SetupTextures() - { - unsafe - { - _dxgi?.Dispose(); - _dxgi = DXGI.GetApi(null); - _d3d11?.Dispose(); - _d3d11 = D3D11.GetApi(null); - - _factory.Dispose(); - SilkMarshal.ThrowHResult(_dxgi.CreateDXGIFactory(out _factory)); - _adapter.Dispose(); - SilkMarshal.ThrowHResult(_factory.EnumAdapters((uint)OpenVR.System.GetD3D9AdapterIndex(), - ref _adapter)); - - _device.Dispose(); - _deviceContext.Dispose(); - SilkMarshal.ThrowHResult - ( - _d3d11.CreateDevice - ( - _adapter, - D3DDriverType.Unknown, - Software: default, - (uint)(CreateDeviceFlag.BgraSupport | (Program.LaunchDebug ? CreateDeviceFlag.Debug : 0)), - null, - 0, - D3D11.SdkVersion, - ref _device, - null, - ref _deviceContext - ) - ); - - _multithread = _device.QueryInterface(); - _multithread.SetMultithreadProtected(true); - - if (Program.LaunchDebug) - _device.SetInfoQueueCallback(msg => logger.Info(SilkMarshal.PtrToString((nint)msg.PDescription)!)); - - _texture1.Dispose(); - SilkMarshal.ThrowHResult - ( - _device.CreateTexture2D(new Texture2DDesc - { - Width = 512, - Height = 512, - MipLevels = 1, - ArraySize = 1, - Format = Format.FormatB8G8R8A8Unorm, - SampleDesc = new SampleDesc - { - Count = 1, - Quality = 0 - }, - BindFlags = (uint)BindFlag.ShaderResource, - CPUAccessFlags = (uint)CpuAccessFlag.Write, - Usage = Usage.Dynamic - }, null, ref _texture1) - ); - - _texture2.Dispose(); - SilkMarshal.ThrowHResult - ( - _device.CreateTexture2D(new Texture2DDesc - { - Width = 1024, - Height = 1024, - MipLevels = 1, - ArraySize = 1, - Format = Format.FormatB8G8R8A8Unorm, - SampleDesc = new SampleDesc - { - Count = 1, - Quality = 0 - }, - BindFlags = (uint)BindFlag.ShaderResource, - CPUAccessFlags = (uint)CpuAccessFlag.Write, - Usage = Usage.Dynamic - }, null, ref _texture2) - ); - } - } - - private void ThreadLoop() - { - var active = false; - var e = new VREvent_t(); - var nextInit = DateTime.MinValue; - var nextDeviceUpdate = DateTime.MinValue; - _nextOverlayUpdate = DateTime.MinValue; - var overlayIndex = OpenVR.k_unTrackedDeviceIndexInvalid; - var overlayVisible1 = false; - var overlayVisible2 = false; - var dashboardHandle = 0UL; - var overlayHandle1 = 0UL; - var overlayHandle2 = 0UL; - - _wristOverlay = new OffScreenBrowserLegacy( - Program.LaunchDebug ? "http://localhost:9000/vr.html?wrist" : "file://vrcx/vr.html?wrist", - 512, - 512 - ); - - _hmdOverlay = new OffScreenBrowserLegacy( - Program.LaunchDebug ? "http://localhost:9000/vr.html?hmd" : "file://vrcx/vr.html?hmd", - 1024, - 1024 - ); - - while (_thread != null) - { - if (_wristOverlayActive) - _wristOverlay.RenderToTexture(_deviceContext, _texture1); - if (_hmdOverlayActive) - _hmdOverlay.RenderToTexture(_deviceContext, _texture2); - try - { - Thread.Sleep(32); - } - catch (ThreadInterruptedException) - { - } - - if (_active) - { - var system = OpenVR.System; - if (system == null) - { - if (DateTime.UtcNow.CompareTo(nextInit) <= 0) - { - continue; - } - - var _err = EVRInitError.None; - system = OpenVR.Init(ref _err, EVRApplicationType.VRApplication_Background); - nextInit = DateTime.UtcNow.AddSeconds(5); - if (system == null) - { - continue; - } - - active = true; - SetupTextures(); - } - - while (system.PollNextEvent(ref e, (uint)Marshal.SizeOf(e))) - { - var type = (EVREventType)e.eventType; - if (type == EVREventType.VREvent_Quit) - { - active = false; - IsHmdAfk = false; - OpenVR.Shutdown(); - nextInit = DateTime.UtcNow.AddSeconds(10); - system = null; - break; - } - } - - if (system != null) - { - if (DateTime.UtcNow.CompareTo(nextDeviceUpdate) >= 0) - { - overlayIndex = OpenVR.k_unTrackedDeviceIndexInvalid; - UpdateDevices(system, ref overlayIndex); - if (overlayIndex != OpenVR.k_unTrackedDeviceIndexInvalid) - { - _nextOverlayUpdate = DateTime.UtcNow.AddSeconds(10); - } - - nextDeviceUpdate = DateTime.UtcNow.AddSeconds(0.1); - } - - var overlay = OpenVR.Overlay; - if (overlay != null) - { - var dashboardVisible = overlay.IsDashboardVisible(); - var err = ProcessDashboard(overlay, ref dashboardHandle, dashboardVisible); - if (err != EVROverlayError.None && - dashboardHandle != 0) - { - overlay.DestroyOverlay(dashboardHandle); - dashboardHandle = 0; - logger.Error(err); - } - - err = ProcessOverlay1(overlay, ref overlayHandle1, ref overlayVisible1, dashboardVisible, overlayIndex); - if (err != EVROverlayError.None && - overlayHandle1 != 0) - { - overlay.DestroyOverlay(overlayHandle1); - overlayHandle1 = 0; - logger.Error(err); - } - - err = ProcessOverlay2(overlay, ref overlayHandle2, ref overlayVisible2, dashboardVisible); - if (err != EVROverlayError.None && - overlayHandle2 != 0) - { - overlay.DestroyOverlay(overlayHandle2); - overlayHandle2 = 0; - logger.Error(err); - } - } - } - } - else if (active) - { - active = false; - IsHmdAfk = false; - OpenVR.Shutdown(); - _deviceListLock.EnterWriteLock(); - try - { - _deviceList.Clear(); - } - finally - { - _deviceListLock.ExitWriteLock(); - } - } - } - - _hmdOverlay?.Dispose(); - _wristOverlay?.Dispose(); - _texture2.Dispose(); - _texture1.Dispose(); - _device.Dispose(); - _adapter.Dispose(); - _factory.Dispose(); - } - - public override void SetActive(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand) - { - _active = active; - _hmdOverlayActive = hmdOverlay; - _wristOverlayActive = wristOverlay; - _menuButton = menuButton; - _overlayHand = overlayHand; - } - - public override void Refresh() - { - _wristOverlay.Reload(); - _hmdOverlay.Reload(); - } - - public override string[][] GetDevices() - { - _deviceListLock.EnterReadLock(); - try - { - return _deviceList.ToArray(); - } - finally - { - _deviceListLock.ExitReadLock(); - } - } - - private void UpdateDevices(CVRSystem system, ref uint overlayIndex) - { - _deviceListLock.EnterWriteLock(); - try - { - _deviceList.Clear(); - } - finally - { - _deviceListLock.ExitWriteLock(); - } - - var sb = new StringBuilder(256); - var state = new VRControllerState_t(); - var poses = new TrackedDevicePose_t[OpenVR.k_unMaxTrackedDeviceCount]; - system.GetDeviceToAbsoluteTrackingPose(ETrackingUniverseOrigin.TrackingUniverseStanding, 0, poses); - for (var i = 0u; i < OpenVR.k_unMaxTrackedDeviceCount; ++i) - { - var devClass = system.GetTrackedDeviceClass(i); - switch (devClass) - { - case ETrackedDeviceClass.HMD: - var success = system.GetControllerState(i, ref state, (uint)Marshal.SizeOf(state)); - if (!success) - break; // this fails while SteamVR overlay is open - - var prox = state.ulButtonPressed & (1UL << ((int)EVRButtonId.k_EButton_ProximitySensor)); - var isHmdAfk = prox == 0; - if (isHmdAfk != IsHmdAfk) - { - IsHmdAfk = isHmdAfk; - Program.AppApiInstance.CheckGameRunning(); - } - - var headsetErr = ETrackedPropertyError.TrackedProp_Success; - var headsetBatteryPercentage = system.GetFloatTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float, ref headsetErr); - if (headsetErr != ETrackedPropertyError.TrackedProp_Success) - { - // Headset has no battery, skip displaying it - break; - } - - var headset = new[] - { - "headset", - system.IsTrackedDeviceConnected(i) - ? "connected" - : "disconnected", - // Currently neither VD or SteamLink report charging state - "discharging", - (headsetBatteryPercentage * 100).ToString(), - poses[i].eTrackingResult.ToString() - }; - _deviceListLock.EnterWriteLock(); - try - { - _deviceList.Add(headset); - } - finally - { - _deviceListLock.ExitWriteLock(); - } - - break; - case ETrackedDeviceClass.Controller: - case ETrackedDeviceClass.GenericTracker: - case ETrackedDeviceClass.TrackingReference: - { - var err = ETrackedPropertyError.TrackedProp_Success; - var batteryPercentage = system.GetFloatTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_DeviceBatteryPercentage_Float, ref err); - if (err != ETrackedPropertyError.TrackedProp_Success) - { - batteryPercentage = 1f; - } - - err = ETrackedPropertyError.TrackedProp_Success; - var isCharging = system.GetBoolTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_DeviceIsCharging_Bool, ref err); - if (err != ETrackedPropertyError.TrackedProp_Success) - { - isCharging = false; - } - - sb.Clear(); - system.GetStringTrackedDeviceProperty(i, ETrackedDeviceProperty.Prop_TrackingSystemName_String, sb, (uint)sb.Capacity, ref err); - var isOculus = sb.ToString().IndexOf("oculus", StringComparison.OrdinalIgnoreCase) >= 0; - // Oculus : B/Y, Bit 1, Mask 2 - // Oculus : A/X, Bit 7, Mask 128 - // Vive : Menu, Bit 1, Mask 2, - // Vive : Grip, Bit 2, Mask 4 - var role = system.GetControllerRoleForTrackedDeviceIndex(i); - if (role == ETrackedControllerRole.LeftHand || role == ETrackedControllerRole.RightHand) - { - if (_overlayHand == 0 || - (_overlayHand == 1 && role == ETrackedControllerRole.LeftHand) || - (_overlayHand == 2 && role == ETrackedControllerRole.RightHand)) - { - if (system.GetControllerState(i, ref state, (uint)Marshal.SizeOf(state)) && - (state.ulButtonPressed & (_menuButton ? 2u : isOculus ? 128u : 4u)) != 0) - { - _nextOverlayUpdate = DateTime.MinValue; - if (role == ETrackedControllerRole.LeftHand) - { - Array.Copy(_translationLeft, _translation, 3); - Array.Copy(_rotationLeft, _rotation, 3); - } - else - { - Array.Copy(_translationRight, _translation, 3); - Array.Copy(_rotationRight, _rotation, 3); - } - - overlayIndex = i; - } - } - } - - var type = string.Empty; - if (devClass == ETrackedDeviceClass.Controller) - { - if (role == ETrackedControllerRole.LeftHand) - { - type = "leftController"; - } - else if (role == ETrackedControllerRole.RightHand) - { - type = "rightController"; - } - else - { - type = "controller"; - } - } - else if (devClass == ETrackedDeviceClass.GenericTracker) - { - type = "tracker"; - } - else if (devClass == ETrackedDeviceClass.TrackingReference) - { - type = "base"; - } - - var item = new[] - { - type, - system.IsTrackedDeviceConnected(i) - ? "connected" - : "disconnected", - isCharging - ? "charging" - : "discharging", - (batteryPercentage * 100).ToString(), - poses[i].eTrackingResult.ToString() - }; - _deviceListLock.EnterWriteLock(); - try - { - _deviceList.Add(item); - } - finally - { - _deviceListLock.ExitWriteLock(); - } - - break; - } - } - } - } - - internal EVROverlayError ProcessDashboard(CVROverlay overlay, ref ulong dashboardHandle, bool dashboardVisible) - { - var err = EVROverlayError.None; - - if (dashboardHandle == 0) - { - err = overlay.FindOverlay("VRCX", ref dashboardHandle); - if (err != EVROverlayError.None) - { - if (err != EVROverlayError.UnknownOverlay) - { - return err; - } - - ulong thumbnailHandle = 0; - err = overlay.CreateDashboardOverlay("VRCX", "VRCX", ref dashboardHandle, ref thumbnailHandle); - if (err != EVROverlayError.None) - { - return err; - } - - var iconPath = Path.Join(Program.BaseDirectory, "VRCX.png"); - err = overlay.SetOverlayFromFile(thumbnailHandle, iconPath); - if (err != EVROverlayError.None) - { - return err; - } - - err = overlay.SetOverlayWidthInMeters(dashboardHandle, 1.5f); - if (err != EVROverlayError.None) - { - return err; - } - - err = overlay.SetOverlayInputMethod(dashboardHandle, VROverlayInputMethod.Mouse); - if (err != EVROverlayError.None) - { - return err; - } - - err = overlay.SetOverlayFlag(dashboardHandle, VROverlayFlags.NoDashboardTab, true); - if (err != EVROverlayError.None) - { - return err; - } - } - } - - var e = new VREvent_t(); - - while (overlay.PollNextOverlayEvent(dashboardHandle, ref e, (uint)Marshal.SizeOf(e))) - { - var type = (EVREventType)e.eventType; - if (type == EVREventType.VREvent_MouseMove) - { - var m = e.data.mouse; - var s = _wristOverlay.Size; - _wristOverlay.GetBrowserHost().SendMouseMoveEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), false, CefEventFlags.None); - } - else if (type == EVREventType.VREvent_MouseButtonDown) - { - var m = e.data.mouse; - var s = _wristOverlay.Size; - _wristOverlay.GetBrowserHost().SendMouseClickEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), MouseButtonType.Left, false, 1, CefEventFlags.LeftMouseButton); - } - else if (type == EVREventType.VREvent_MouseButtonUp) - { - var m = e.data.mouse; - var s = _wristOverlay.Size; - _wristOverlay.GetBrowserHost().SendMouseClickEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), MouseButtonType.Left, true, 1, CefEventFlags.None); - } - } - - if (dashboardVisible) - { - unsafe - { - var texture = new Texture_t - { - handle = (IntPtr)_texture1.Handle - }; - err = overlay.SetOverlayTexture(dashboardHandle, ref texture); - if (err != EVROverlayError.None) - { - return err; - } - } - } - - return err; - } - - internal EVROverlayError ProcessOverlay1(CVROverlay overlay, ref ulong overlayHandle, ref bool overlayVisible, bool dashboardVisible, uint overlayIndex) - { - var err = EVROverlayError.None; - - if (overlayHandle == 0) - { - err = overlay.FindOverlay("VRCX1", ref overlayHandle); - if (err != EVROverlayError.None) - { - if (err != EVROverlayError.UnknownOverlay) - { - return err; - } - - overlayVisible = false; - err = overlay.CreateOverlay("VRCX1", "VRCX1", ref overlayHandle); - if (err != EVROverlayError.None) - { - return err; - } - - err = overlay.SetOverlayAlpha(overlayHandle, 0.9f); - if (err != EVROverlayError.None) - { - return err; - } - - err = overlay.SetOverlayWidthInMeters(overlayHandle, 1f); - if (err != EVROverlayError.None) - { - return err; - } - - err = overlay.SetOverlayInputMethod(overlayHandle, VROverlayInputMethod.None); - if (err != EVROverlayError.None) - { - return err; - } - } - } - - if (overlayIndex != OpenVR.k_unTrackedDeviceIndexInvalid) - { - // http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices - // Scaling-Rotation-Translation - var m = Matrix4x4.CreateScale(0.25f); - m *= Matrix4x4.CreateRotationX(_rotation[0]); - m *= Matrix4x4.CreateRotationY(_rotation[1]); - m *= Matrix4x4.CreateRotationZ(_rotation[2]); - m *= Matrix4x4.CreateTranslation(new Vector3(_translation[0], _translation[1], _translation[2])); - var hm34 = new HmdMatrix34_t - { - m0 = m.M11, - m1 = m.M21, - m2 = m.M31, - m3 = m.M41, - m4 = m.M12, - m5 = m.M22, - m6 = m.M32, - m7 = m.M42, - m8 = m.M13, - m9 = m.M23, - m10 = m.M33, - m11 = m.M43 - }; - err = overlay.SetOverlayTransformTrackedDeviceRelative(overlayHandle, overlayIndex, ref hm34); - if (err != EVROverlayError.None) - { - return err; - } - } - - if (!dashboardVisible && - DateTime.UtcNow.CompareTo(_nextOverlayUpdate) <= 0) - { - unsafe - { - var texture = new Texture_t - { - handle = (IntPtr)_texture1.Handle - }; - err = overlay.SetOverlayTexture(overlayHandle, ref texture); - if (err != EVROverlayError.None) - { - return err; - } - } - - if (!overlayVisible) - { - err = overlay.ShowOverlay(overlayHandle); - if (err != EVROverlayError.None) - { - return err; - } - - overlayVisible = true; - } - } - else if (overlayVisible) - { - err = overlay.HideOverlay(overlayHandle); - if (err != EVROverlayError.None) - { - return err; - } - - overlayVisible = false; - } - - return err; - } - - internal EVROverlayError ProcessOverlay2(CVROverlay overlay, ref ulong overlayHandle, ref bool overlayVisible, bool dashboardVisible) - { - var err = EVROverlayError.None; - - if (overlayHandle == 0) - { - err = overlay.FindOverlay("VRCX2", ref overlayHandle); - if (err != EVROverlayError.None) - { - if (err != EVROverlayError.UnknownOverlay) - { - return err; - } - - overlayVisible = false; - err = overlay.CreateOverlay("VRCX2", "VRCX2", ref overlayHandle); - if (err != EVROverlayError.None) - { - return err; - } - - err = overlay.SetOverlayAlpha(overlayHandle, 0.9f); - if (err != EVROverlayError.None) - { - return err; - } - - err = overlay.SetOverlayWidthInMeters(overlayHandle, 1f); - if (err != EVROverlayError.None) - { - return err; - } - - err = overlay.SetOverlayInputMethod(overlayHandle, VROverlayInputMethod.None); - if (err != EVROverlayError.None) - { - return err; - } - - var m = Matrix4x4.CreateScale(1f); - m *= Matrix4x4.CreateTranslation(0, -0.3f, -1.5f); - var hm34 = new HmdMatrix34_t - { - m0 = m.M11, - m1 = m.M21, - m2 = m.M31, - m3 = m.M41, - m4 = m.M12, - m5 = m.M22, - m6 = m.M32, - m7 = m.M42, - m8 = m.M13, - m9 = m.M23, - m10 = m.M33, - m11 = m.M43 - }; - err = overlay.SetOverlayTransformTrackedDeviceRelative(overlayHandle, OpenVR.k_unTrackedDeviceIndex_Hmd, ref hm34); - if (err != EVROverlayError.None) - { - return err; - } - } - } - - if (!dashboardVisible) - { - unsafe - { - var texture = new Texture_t - { - handle = (IntPtr)_texture2.Handle - }; - err = overlay.SetOverlayTexture(overlayHandle, ref texture); - if (err != EVROverlayError.None) - { - return err; - } - } - - if (!overlayVisible) - { - err = overlay.ShowOverlay(overlayHandle); - if (err != EVROverlayError.None) - { - return err; - } - - overlayVisible = true; - } - } - else if (overlayVisible) - { - err = overlay.HideOverlay(overlayHandle); - if (err != EVROverlayError.None) - { - return err; - } - - overlayVisible = false; - } - - return err; - } - - public override ConcurrentQueue> GetExecuteVrFeedFunctionQueue() - { - throw new NotImplementedException(); - } - - public override void ExecuteVrFeedFunction(string function, string json) - { - if (_wristOverlay == null) return; - if (_wristOverlay.IsLoading) - Restart(); - _wristOverlay.ExecuteScriptAsync($"$vr.{function}", json); - } - - public override ConcurrentQueue> GetExecuteVrOverlayFunctionQueue() - { - throw new NotImplementedException(); - } - - public override void ExecuteVrOverlayFunction(string function, string json) - { - if (_hmdOverlay == null) return; - if (_hmdOverlay.IsLoading) - Restart(); - _hmdOverlay.ExecuteScriptAsync($"$vr.{function}", json); - } - } -} \ No newline at end of file diff --git a/Dotnet/Overlay/Cef/VRForm.Designer.cs b/Dotnet/Overlay/Cef/VRForm.Designer.cs deleted file mode 100644 index 3da4f40f..00000000 --- a/Dotnet/Overlay/Cef/VRForm.Designer.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace VRCX -{ - partial class VRForm - { - /// - /// 필수 디자이너 변수입니다. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// 사용 중인 모든 리소스를 정리합니다. - /// - /// 관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form 디자이너에서 생성한 코드 - - /// - /// 디자이너 지원에 필요한 메서드입니다. - /// 이 메서드의 내용을 코드 편집기로 수정하지 마세요. - /// - private void InitializeComponent() - { - this.components = new System.ComponentModel.Container(); - this.timer = new System.Windows.Forms.Timer(this.components); - this.panel1 = new System.Windows.Forms.Panel(); - this.panel2 = new System.Windows.Forms.Panel(); - this.button_refresh = new System.Windows.Forms.Button(); - this.button_devtools = new System.Windows.Forms.Button(); - this.SuspendLayout(); - // - // panel1 - // - this.panel1.Location = new System.Drawing.Point(0, 0); - this.panel1.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); - this.panel1.Name = "panel1"; - this.panel1.Size = new System.Drawing.Size(731, 768); - this.panel1.TabIndex = 0; - // - // panel2 - // - this.panel2.Location = new System.Drawing.Point(740, 0); - this.panel2.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); - this.panel2.Name = "panel2"; - this.panel2.Size = new System.Drawing.Size(731, 768); - this.panel2.TabIndex = 1; - // - // button_refresh - // - this.button_refresh.Location = new System.Drawing.Point(17, 777); - this.button_refresh.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); - this.button_refresh.Name = "button_refresh"; - this.button_refresh.Size = new System.Drawing.Size(107, 34); - this.button_refresh.TabIndex = 27; - this.button_refresh.Text = "Refresh"; - this.button_refresh.UseVisualStyleBackColor = true; - this.button_refresh.Click += new System.EventHandler(this.button_refresh_Click); - // - // button_devtools - // - this.button_devtools.Location = new System.Drawing.Point(133, 777); - this.button_devtools.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); - this.button_devtools.Name = "button_devtools"; - this.button_devtools.Size = new System.Drawing.Size(107, 34); - this.button_devtools.TabIndex = 27; - this.button_devtools.Text = "DevTools"; - this.button_devtools.UseVisualStyleBackColor = true; - this.button_devtools.Click += new System.EventHandler(this.button_devtools_Click); - // - // VRForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(144F, 144F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; - this.ClientSize = new System.Drawing.Size(1483, 830); - this.Controls.Add(this.button_devtools); - this.Controls.Add(this.button_refresh); - this.Controls.Add(this.panel2); - this.Controls.Add(this.panel1); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; - this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); - this.MaximizeBox = false; - this.Name = "VRForm"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "VR"; - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.Timer timer; - private System.Windows.Forms.Panel panel1; - private System.Windows.Forms.Panel panel2; - private System.Windows.Forms.Button button_refresh; - private System.Windows.Forms.Button button_devtools; - } -} \ No newline at end of file diff --git a/Dotnet/Overlay/Cef/VRForm.cs b/Dotnet/Overlay/Cef/VRForm.cs deleted file mode 100644 index c62043e3..00000000 --- a/Dotnet/Overlay/Cef/VRForm.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.IO; -using System.Windows.Forms; -using CefSharp; -using CefSharp.WinForms; - -namespace VRCX -{ - public partial class VRForm : WinformBase - { - public static VRForm Instance; - private ChromiumWebBrowser _browser1; - private ChromiumWebBrowser _browser2; - - public VRForm() - { - Instance = this; - InitializeComponent(); - - _browser1 = new ChromiumWebBrowser( - Path.Join(Program.BaseDirectory, "html/vr.html?wrist") - ) - { - DragHandler = new CefNoopDragHandler(), - RequestHandler = new CustomRequestHandler(), - BrowserSettings = - { - DefaultEncoding = "UTF-8", - }, - Dock = DockStyle.Fill - }; - - _browser2 = new ChromiumWebBrowser( - Path.Join(Program.BaseDirectory, "html/vr.html?hmd") - ) - { - DragHandler = new CefNoopDragHandler(), - BrowserSettings = - { - DefaultEncoding = "UTF-8", - }, - Dock = DockStyle.Fill - }; - - JavascriptBindings.ApplyVrJavascriptBindings(_browser1.JavascriptObjectRepository); - JavascriptBindings.ApplyVrJavascriptBindings(_browser2.JavascriptObjectRepository); - - panel1.Controls.Add(_browser1); - panel2.Controls.Add(_browser2); - } - - private void button_refresh_Click(object sender, System.EventArgs e) - { - _browser1.ExecuteScriptAsync("location.reload()"); - _browser2.ExecuteScriptAsync("location.reload()"); - Program.VRCXVRInstance.Refresh(); - } - - private void button_devtools_Click(object sender, System.EventArgs e) - { - _browser1.ShowDevTools(); - _browser2.ShowDevTools(); - } - } -} diff --git a/Dotnet/Overlay/Cef/VRForm.resx b/Dotnet/Overlay/Cef/VRForm.resx deleted file mode 100644 index e22c5ac6..00000000 --- a/Dotnet/Overlay/Cef/VRForm.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - \ No newline at end of file diff --git a/Dotnet/AppApi/Electron/AppApiVrElectron.cs b/Dotnet/Overlay/Electron/AppApiVrElectron.cs similarity index 88% rename from Dotnet/AppApi/Electron/AppApiVrElectron.cs rename to Dotnet/Overlay/Electron/AppApiVrElectron.cs index 1f85d87d..7f118dde 100644 --- a/Dotnet/AppApi/Electron/AppApiVrElectron.cs +++ b/Dotnet/Overlay/Electron/AppApiVrElectron.cs @@ -19,16 +19,6 @@ namespace VRCX { } - public override List> GetExecuteVrFeedFunctionQueue() - { - var list = new List>(); - while (Program.VRCXVRInstance.GetExecuteVrFeedFunctionQueue().TryDequeue(out var item)) - { - list.Add(item); - } - return list; - } - public override List> GetExecuteVrOverlayFunctionQueue() { var list = new List>(); diff --git a/Dotnet/Overlay/Electron/VRCXVRElectron.cs b/Dotnet/Overlay/Electron/VRCXVRElectron.cs index 6db7f1d5..7542afe5 100644 --- a/Dotnet/Overlay/Electron/VRCXVRElectron.cs +++ b/Dotnet/Overlay/Electron/VRCXVRElectron.cs @@ -27,8 +27,7 @@ namespace VRCX private bool _active; private bool _menuButton; private int _overlayHand; - private GLTextureWriter _wristOverlayTextureWriter; - private GLTextureWriter _hmdOverlayTextureWriter; + private GLTextureWriter _overlayTextureWriter; private Thread _thread; private DateTime _nextOverlayUpdate; @@ -40,22 +39,22 @@ namespace VRCX private bool _wristOverlayActive; private bool _wristOverlayWasActive; - private const string WRIST_OVERLAY_SHM_PATH = "/dev/shm/vrcx_wrist_overlay"; - private const string HMD_OVERLAY_SHM_PATH = "/dev/shm/vrcx_hmd_overlay"; + private const string OVERLAY_SHM_PATH = "/dev/shm/vrcx_overlay"; private const int WRIST_FRAME_WIDTH = 512; private const int WRIST_FRAME_HEIGHT = 512; private const int WRIST_FRAME_SIZE = WRIST_FRAME_WIDTH * WRIST_FRAME_HEIGHT * 4; // RGBA - private byte[] wristFrameBuffer = new byte[WRIST_FRAME_SIZE]; private const int HMD_FRAME_WIDTH = 1024; private const int HMD_FRAME_HEIGHT = 1024; private const int HMD_FRAME_SIZE = HMD_FRAME_WIDTH * HMD_FRAME_HEIGHT * 4; // RGBA - private byte[] hmdFrameBuffer = new byte[HMD_FRAME_SIZE]; - private MemoryMappedFile _wristOverlayMMF; - private MemoryMappedViewAccessor _wristOverlayAccessor; - private MemoryMappedFile _hmdOverlayMMF; - private MemoryMappedViewAccessor _hmdOverlayAccessor; - private readonly ConcurrentQueue> _wristFeedFunctionQueue = new ConcurrentQueue>(); - private readonly ConcurrentQueue> _hmdFeedFunctionQueue = new ConcurrentQueue>(); + + private const int SHARED_FRAME_SIZE = SHARED_FRAME_WIDTH * SHARED_FRAME_HEIGHT * 4; + private const int SHARED_FRAME_WIDTH = 1024; + private const int SHARED_FRAME_HEIGHT = WRIST_FRAME_HEIGHT + HMD_FRAME_HEIGHT; + private byte[] frameBuffer = new byte[SHARED_FRAME_SIZE]; + + private MemoryMappedFile _overlayMMF; + private MemoryMappedViewAccessor _overlayAccessor; + private readonly ConcurrentQueue> _overlayFunctionQueue = new ConcurrentQueue>(); static VRCXVRElectron() { @@ -86,15 +85,10 @@ namespace VRCX thread?.Interrupt(); thread?.Join(); - _wristOverlayAccessor?.Dispose(); - _wristOverlayAccessor = null; - _wristOverlayMMF?.Dispose(); - _wristOverlayMMF = null; - - _hmdOverlayAccessor?.Dispose(); - _hmdOverlayAccessor = null; - _hmdOverlayMMF?.Dispose(); - _hmdOverlayMMF = null; + _overlayAccessor?.Dispose(); + _overlayAccessor = null; + _overlayMMF?.Dispose(); + _overlayMMF = null; GLContextX11.Cleanup(); GLContextWayland.Cleanup(); @@ -135,11 +129,8 @@ namespace VRCX throw new Exception("Failed to initialise OpenGL context"); } - _wristOverlayTextureWriter = new GLTextureWriter(512, 512); - _wristOverlayTextureWriter.UpdateTexture(); - - _hmdOverlayTextureWriter = new GLTextureWriter(1024, 1024); - _hmdOverlayTextureWriter.UpdateTexture(); + _overlayTextureWriter = new GLTextureWriter(SHARED_FRAME_WIDTH, SHARED_FRAME_HEIGHT); + _overlayTextureWriter.UpdateTexture(); } private void UpgradeDevice() @@ -147,33 +138,20 @@ namespace VRCX } - public byte[] GetLatestWristOverlayFrame() + public byte[] GetLatestOverlayFrame() { - if (_wristOverlayAccessor == null) return null; - byte ready = _wristOverlayAccessor.ReadByte(0); + if (_overlayAccessor == null) return null; + byte ready = _overlayAccessor.ReadByte(0); if (ready == 1) { - _wristOverlayAccessor.ReadArray(1, wristFrameBuffer, 0, WRIST_FRAME_SIZE); - _wristOverlayAccessor.Write(0, (byte)0); // reset flag - return wristFrameBuffer; + _overlayAccessor.ReadArray(1, frameBuffer, 0, SHARED_FRAME_SIZE); + _overlayAccessor.Write(0, (byte)0); // reset flag + return frameBuffer; } return null; } - public byte[] GetLatestHmdOverlayFrame() - { - if (_hmdOverlayAccessor == null) return null; - byte ready = _hmdOverlayAccessor.ReadByte(0); - if (ready == 1) - { - _hmdOverlayAccessor.ReadArray(1, hmdFrameBuffer, 0, HMD_FRAME_SIZE); - _hmdOverlayAccessor.Write(0, (byte)0); // reset flag - return hmdFrameBuffer; - } - return null; - } - - void FlipImageVertically(byte[] imageData, int width, int height) + private static void FlipImageVertically(ref byte[] imageData, int width, int height) { int stride = width * 4; // 4 bytes per pixel (RGBA) byte[] tempRow = new byte[stride]; @@ -326,15 +304,10 @@ namespace VRCX } } - _wristOverlayAccessor?.Dispose(); - _wristOverlayAccessor = null; - _wristOverlayMMF?.Dispose(); - _wristOverlayMMF = null; - - _hmdOverlayAccessor?.Dispose(); - _hmdOverlayAccessor = null; - _hmdOverlayMMF?.Dispose(); - _hmdOverlayMMF = null; + _overlayAccessor?.Dispose(); + _overlayAccessor = null; + _overlayMMF?.Dispose(); + _overlayMMF = null; GLContextX11.Cleanup(); GLContextWayland.Cleanup(); @@ -352,35 +325,32 @@ namespace VRCX { OpenVR.Overlay.DestroyOverlay(_hmdOverlayHandle); _hmdOverlayHandle = 0; - - _hmdOverlayAccessor?.Dispose(); - _hmdOverlayAccessor = null; - _hmdOverlayMMF?.Dispose(); - _hmdOverlayMMF = null; } - _hmdOverlayWasActive = _hmdOverlayActive; if (_wristOverlayActive != _wristOverlayWasActive && _wristOverlayHandle != 0) { OpenVR.Overlay.DestroyOverlay(_wristOverlayHandle); _wristOverlayHandle = 0; - - _wristOverlayAccessor?.Dispose(); - _wristOverlayAccessor = null; - _wristOverlayMMF?.Dispose(); - _wristOverlayMMF = null; } - _wristOverlayWasActive = _wristOverlayActive; - if (!_active) + if (!_active || (!_hmdOverlayActive && !_wristOverlayActive)) { + _overlayAccessor?.Dispose(); + _overlayAccessor = null; + _overlayMMF?.Dispose(); + _overlayMMF = null; GLContextX11.Cleanup(); GLContextWayland.Cleanup(); } } + public override bool IsActive() + { + return _active; + } + public override void Refresh() { //_wristOverlay.Reload(); @@ -606,31 +576,6 @@ namespace VRCX } } - var e = new VREvent_t(); - - while (overlay.PollNextOverlayEvent(dashboardHandle, ref e, (uint)Marshal.SizeOf(e))) - { - var type = (EVREventType)e.eventType; - if (type == EVREventType.VREvent_MouseMove) - { - var m = e.data.mouse; - //var s = _wristOverlay.Size; - //_wristOverlay.GetBrowserHost().SendMouseMoveEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), false, CefEventFlags.None); - } - else if (type == EVREventType.VREvent_MouseButtonDown) - { - var m = e.data.mouse; - //var s = _wristOverlay.Size; - //_wristOverlay.GetBrowserHost().SendMouseClickEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), MouseButtonType.Left, false, 1, CefEventFlags.LeftMouseButton); - } - else if (type == EVREventType.VREvent_MouseButtonUp) - { - var m = e.data.mouse; - //var s = _wristOverlay.Size; - //_wristOverlay.GetBrowserHost().SendMouseClickEvent((int)(m.x * s.Width), s.Height - (int)(m.y * s.Height), MouseButtonType.Left, true, 1, CefEventFlags.None); - } - } - if (dashboardVisible) { //var texture = new Texture_t @@ -686,8 +631,8 @@ namespace VRCX return err; } - _wristOverlayMMF = MemoryMappedFile.CreateFromFile(WRIST_OVERLAY_SHM_PATH, FileMode.Open, null, WRIST_FRAME_SIZE + 1); - _wristOverlayAccessor = _wristOverlayMMF.CreateViewAccessor(); + _overlayMMF = MemoryMappedFile.CreateFromFile(OVERLAY_SHM_PATH, FileMode.Open, null, SHARED_FRAME_SIZE + 1); + _overlayAccessor = _overlayMMF.CreateViewAccessor(); } } @@ -725,16 +670,24 @@ namespace VRCX if (!dashboardVisible && DateTime.UtcNow.CompareTo(_nextOverlayUpdate) <= 0) { - if (_wristOverlayTextureWriter != null) + if (_overlayTextureWriter != null) { - byte[] imageData = GetLatestWristOverlayFrame(); + byte[] imageData = GetLatestOverlayFrame(); if (imageData != null) { - FlipImageVertically(imageData, WRIST_FRAME_WIDTH, WRIST_FRAME_HEIGHT); - _wristOverlayTextureWriter.WriteImageToBuffer(imageData); - _wristOverlayTextureWriter.UpdateTexture(); + FlipImageVertically(ref imageData, SHARED_FRAME_WIDTH, SHARED_FRAME_HEIGHT); + _overlayTextureWriter.WriteImageToBuffer(imageData); + _overlayTextureWriter.UpdateTexture(); - Texture_t texture = _wristOverlayTextureWriter.AsTextureT(); + Texture_t texture = _overlayTextureWriter.AsTextureT(); + var bounds = new VRTextureBounds_t + { + uMin = 0f, + uMax = 0.5f, + vMin = 0f, + vMax = (float)WRIST_FRAME_HEIGHT / SHARED_FRAME_HEIGHT + }; + overlay.SetOverlayTextureBounds(overlayHandle, ref bounds); err = OpenVR.Overlay.SetOverlayTexture(overlayHandle, ref texture); if (err != EVROverlayError.None) { @@ -830,23 +783,31 @@ namespace VRCX return err; } - _hmdOverlayMMF = MemoryMappedFile.CreateFromFile(HMD_OVERLAY_SHM_PATH, FileMode.Open, null, HMD_FRAME_SIZE + 1); - _hmdOverlayAccessor = _hmdOverlayMMF.CreateViewAccessor(); + _overlayMMF = MemoryMappedFile.CreateFromFile(OVERLAY_SHM_PATH, FileMode.Open, null, SHARED_FRAME_SIZE + 1); + _overlayAccessor = _overlayMMF.CreateViewAccessor(); } } if (!dashboardVisible) { - if (_hmdOverlayTextureWriter != null) + if (_overlayTextureWriter != null) { - byte[] imageData = GetLatestHmdOverlayFrame(); + byte[] imageData = GetLatestOverlayFrame(); if (imageData != null) { - FlipImageVertically(imageData, HMD_FRAME_WIDTH, HMD_FRAME_HEIGHT); - _hmdOverlayTextureWriter.WriteImageToBuffer(imageData); - _hmdOverlayTextureWriter.UpdateTexture(); + FlipImageVertically(ref imageData, SHARED_FRAME_WIDTH, SHARED_FRAME_HEIGHT); + _overlayTextureWriter.WriteImageToBuffer(imageData); + _overlayTextureWriter.UpdateTexture(); - Texture_t texture = _hmdOverlayTextureWriter.AsTextureT(); + Texture_t texture = _overlayTextureWriter.AsTextureT(); + var bounds = new VRTextureBounds_t + { + uMin = 0f, + uMax = 1f, + vMin = (float)(SHARED_FRAME_HEIGHT - HMD_FRAME_HEIGHT) / SHARED_FRAME_HEIGHT, + vMax = 1f + }; + overlay.SetOverlayTextureBounds(overlayHandle, ref bounds); err = OpenVR.Overlay.SetOverlayTexture(overlayHandle, ref texture); if (err != EVROverlayError.None) { @@ -880,23 +841,9 @@ namespace VRCX return err; } - public override ConcurrentQueue> GetExecuteVrFeedFunctionQueue() - { - return _wristFeedFunctionQueue; - } - - public override void ExecuteVrFeedFunction(string function, string json) - { - //if (_hmdOverlaySocket == null || !_hmdOverlaySocket.Connected) return; - // if (_wristOverlay.IsLoading) - // Restart(); - - _wristFeedFunctionQueue.Enqueue(new KeyValuePair(function, json)); - } - public override ConcurrentQueue> GetExecuteVrOverlayFunctionQueue() { - return _hmdFeedFunctionQueue; + return _overlayFunctionQueue; } public override void ExecuteVrOverlayFunction(string function, string json) @@ -905,7 +852,7 @@ namespace VRCX // if (_hmdOverlay.IsLoading) // Restart(); - _hmdFeedFunctionQueue.Enqueue(new KeyValuePair(function, json)); + _overlayFunctionQueue.Enqueue(new KeyValuePair(function, json)); } } } \ No newline at end of file diff --git a/Dotnet/OpenVR/openvr_api.cs b/Dotnet/Overlay/OpenVR/openvr_api.cs similarity index 100% rename from Dotnet/OpenVR/openvr_api.cs rename to Dotnet/Overlay/OpenVR/openvr_api.cs diff --git a/Dotnet/Overlay/VRCXVRInterface.cs b/Dotnet/Overlay/VRCXVRInterface.cs index 55b5edb5..5b5af5c8 100644 --- a/Dotnet/Overlay/VRCXVRInterface.cs +++ b/Dotnet/Overlay/VRCXVRInterface.cs @@ -1,8 +1,8 @@ -namespace VRCX; - using System.Collections.Generic; using System.Collections.Concurrent; +namespace VRCX; + public abstract class VRCXVRInterface { public bool IsHmdAfk; @@ -11,9 +11,8 @@ public abstract class VRCXVRInterface public abstract void Refresh(); public abstract void Restart(); public abstract void SetActive(bool active, bool hmdOverlay, bool wristOverlay, bool menuButton, int overlayHand); + public abstract bool IsActive(); public abstract string[][] GetDevices(); - public abstract void ExecuteVrFeedFunction(string function, string json); public abstract void ExecuteVrOverlayFunction(string function, string json); - public abstract ConcurrentQueue> GetExecuteVrFeedFunctionQueue(); public abstract ConcurrentQueue> GetExecuteVrOverlayFunctionQueue(); } \ No newline at end of file diff --git a/Dotnet/OverlayWebSocket/OverlayManager.cs b/Dotnet/OverlayWebSocket/OverlayManager.cs new file mode 100644 index 00000000..7c1a6fe2 --- /dev/null +++ b/Dotnet/OverlayWebSocket/OverlayManager.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using NLog; + +namespace VRCX; + +public class OverlayManager +{ + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + private static Process _process; + + public static void StartOverlay() + { + if (OverlayServer.IsConnected() || + _process != null && !_process.HasExited) + { + logger.Error("Overlay server already started"); + return; + } + + StartOverlayProcess(); + } + + private static void StartOverlayProcess() + { + if (Environment.ProcessPath == null) + { + logger.Error("Cannot start Overlay process without a process path"); + return; + } + + var args = new List(); + args.Add(StartupArgs.VrcxLaunchArguments.Overlay); + if (Program.LaunchDebug) + args.Add(StartupArgs.VrcxLaunchArguments.IsDebugPrefix); + if (StartupArgs.LaunchArguments.ConfigDirectory != null) + args.Add($"{StartupArgs.VrcxLaunchArguments.ConfigDirectoryPrefix}={StartupArgs.LaunchArguments.ConfigDirectory}"); + + var startInfo = new ProcessStartInfo + { + FileName = Environment.ProcessPath, + Arguments = string.Join(' ', args), + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = Program.BaseDirectory + }; + _process = Process.Start(startInfo); + logger.Info("Overlay process started"); + } +} \ No newline at end of file diff --git a/Dotnet/OverlayWebSocket/OverlayMessage.cs b/Dotnet/OverlayWebSocket/OverlayMessage.cs new file mode 100644 index 00000000..00467bb3 --- /dev/null +++ b/Dotnet/OverlayWebSocket/OverlayMessage.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; + +namespace VRCX; + +public class OverlayMessage +{ + public OverlayMessageType Type { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? FunctionName { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Data { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public OverlayVars? OverlayVars { get; set; } +} \ No newline at end of file diff --git a/Dotnet/OverlayWebSocket/OverlayMessageType.cs b/Dotnet/OverlayWebSocket/OverlayMessageType.cs new file mode 100644 index 00000000..3172865a --- /dev/null +++ b/Dotnet/OverlayWebSocket/OverlayMessageType.cs @@ -0,0 +1,9 @@ +namespace VRCX; + +public enum OverlayMessageType +{ + OverlayConnected, + JsFunctionCall, + UpdateVars, + IsHmdAfk +} \ No newline at end of file diff --git a/Dotnet/OverlayWebSocket/OverlayServer.cs b/Dotnet/OverlayWebSocket/OverlayServer.cs new file mode 100644 index 00000000..642a24d2 --- /dev/null +++ b/Dotnet/OverlayWebSocket/OverlayServer.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Net; +using System.Net.WebSockets; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using CefSharp; +using NLog; + +namespace VRCX; + +public class OverlayServer +{ + public static OverlayServer Instance { get; private set; } + + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + private static readonly Lock SendLock = new(); + private static readonly ConcurrentDictionary ConnectedWebSockets = new(); + private static CancellationTokenSource _cancellationToken; + + private static OverlayVars _overlayVars; + + static OverlayServer() + { + Instance = new OverlayServer(); + } + + public async Task Init() + { + if (_cancellationToken != null && _cancellationToken.IsCancellationRequested) + return; + + _cancellationToken = new CancellationTokenSource(); + try + { + var listener = new HttpListener(); + listener.Prefixes.Add("http://127.0.0.1:34582/"); + listener.Start(); + logger.Info("Overlay IPC server started"); + while (_cancellationToken != null && !_cancellationToken.IsCancellationRequested) + { + var listenerContext = await listener.GetContextAsync(); + if (listenerContext.Request.IsWebSocketRequest) + { + ProcessRequest(listenerContext); + } + else + { + listenerContext.Response.StatusCode = 400; + listenerContext.Response.Close(); + } + } + } + catch (Exception e) + { + logger.Error(e); + } + } + + public async Task Exit() + { + if (_cancellationToken == null || _cancellationToken.IsCancellationRequested) + return; + + foreach (var webSocket in ConnectedWebSockets.Keys) + { + if (webSocket == null || webSocket.State != WebSocketState.Open) + continue; + + try + { + await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Server shutting down", CancellationToken.None); + } + catch (Exception e) + { + logger.Error(e); + } + } + await _cancellationToken.CancelAsync(); + ConnectedWebSockets.Clear(); + _cancellationToken = null; + } + + private async Task ProcessRequest(HttpListenerContext listenerContext) + { + WebSocketContext webSocketContext; + try + { + webSocketContext = await listenerContext.AcceptWebSocketAsync(null); + } + catch (Exception e) + { + listenerContext.Response.StatusCode = 500; + listenerContext.Response.Close(); + logger.Error(e); + return; + } + + var webSocket = webSocketContext.WebSocket; + try + { + ConnectedWebSockets.TryAdd(webSocket, 0); + logger.Info("Overlay IPC connected, count: {0}", ConnectedWebSockets.Count); + var receiveBuffer = new byte[1024 * 5]; + while (webSocket.State == WebSocketState.Open) + { + var receiveResult = await webSocket.ReceiveAsync(new ArraySegment(receiveBuffer), CancellationToken.None); + switch (receiveResult.MessageType) + { + case WebSocketMessageType.Text: + var text = Encoding.UTF8.GetString(receiveBuffer, 0, receiveResult.Count); + var message = JsonSerializer.Deserialize(text); + HandleMessage(message); + continue; + + case WebSocketMessageType.Close: + await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); + break; + + case WebSocketMessageType.Binary: + default: + await webSocket.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "Invalid message type", CancellationToken.None); + break; + } + } + } + catch (Exception e) + { + logger.Error(e); + } + finally + { + webSocket.Dispose(); + ConnectedWebSockets.TryRemove(webSocket, out _); + logger.Info("Overlay IPC disconnected, count: {0}", ConnectedWebSockets.Count); + } + } + + private async Task HandleMessage(OverlayMessage message) + { + logger.Trace($"Overlay IPC message received: {message.Type.ToString()}"); + switch (message.Type) + { + case OverlayMessageType.OverlayConnected: + var helloMessage = new OverlayMessage + { + Type = OverlayMessageType.UpdateVars, + OverlayVars = _overlayVars + }; + SendMessage(helloMessage); + if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && + MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame) + MainForm.Instance.Browser.ExecuteScriptAsync("window?.$pinia?.vr.vrInit();"); + break; + + case OverlayMessageType.IsHmdAfk: + var isHmdAfk = string.Equals(message.Data, "true", StringComparison.OrdinalIgnoreCase); + if (MainForm.Instance?.Browser != null && !MainForm.Instance.Browser.IsLoading && MainForm.Instance.Browser.CanExecuteJavascriptInMainFrame) + MainForm.Instance.Browser.ExecuteScriptAsync("window?.$pinia?.game.updateIsHmdAfk", isHmdAfk); + break; + + case OverlayMessageType.JsFunctionCall: + case OverlayMessageType.UpdateVars: + default: + throw new ArgumentOutOfRangeException(); + } + } + + public void SendMessage(OverlayMessage message) + { + lock (SendLock) + { + var buffer = new ArraySegment(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(message))); + var connectedWebSockets = ConnectedWebSockets.Keys; + logger.Trace($"Sending message to overlay Clients: {connectedWebSockets.Count}, IPC: {message.Type.ToString()}"); + foreach (var webSocket in connectedWebSockets) + { + if (webSocket == null || webSocket.State != WebSocketState.Open) + continue; + + webSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); + } + } + } + + public void UpdateVars(OverlayVars overlayVars) + { + _overlayVars = overlayVars; + if (!IsConnected() && overlayVars.Active) + { + OverlayManager.StartOverlay(); + return; + } + + var helloMessage = new OverlayMessage + { + Type = OverlayMessageType.UpdateVars, + OverlayVars = overlayVars + }; + SendMessage(helloMessage); + } + + public static bool IsConnected() + { + return ConnectedWebSockets.Keys.Any(webSocket => webSocket != null && webSocket.State == WebSocketState.Open); + } +} \ No newline at end of file diff --git a/Dotnet/OverlayWebSocket/OverlayVars.cs b/Dotnet/OverlayWebSocket/OverlayVars.cs new file mode 100644 index 00000000..72d386ce --- /dev/null +++ b/Dotnet/OverlayWebSocket/OverlayVars.cs @@ -0,0 +1,10 @@ +namespace VRCX; + +public class OverlayVars +{ + public bool Active { get; set; } + public bool HmdOverlay { get; set; } + public bool WristOverlay { get; set; } + public bool MenuButton { get; set; } + public int OverlayHand { get; set; } +} \ No newline at end of file diff --git a/Dotnet/Program.cs b/Dotnet/Program.cs index 24bd0090..66cfba8a 100644 --- a/Dotnet/Program.cs +++ b/Dotnet/Program.cs @@ -8,6 +8,7 @@ using System.Text.Json; using System.Threading; #if !LINUX using System.Windows.Forms; +using VRCX.Overlay; #endif namespace VRCX @@ -20,9 +21,7 @@ namespace VRCX public static string Version { get; private set; } public static bool LaunchDebug; private static readonly Logger logger = LogManager.GetCurrentClassLogger(); - public static VRCXVRInterface VRCXVRInstance { get; set; } public static AppApi AppApiInstance { get; private set; } - public static AppApiVr AppApiVrInstance { get; private set; } private static void SetProgramDirectories() { @@ -86,11 +85,15 @@ namespace VRCX private static void ConfigureLogger() { + var fileName = Path.Join(AppDataDirectory, "logs", "VRCX.log"); + if (StartupArgs.LaunchArguments.IsOverlay) + fileName = Path.Join(AppDataDirectory, "logs", "VRCX.Overlay.log"); + LogManager.Setup().LoadConfiguration(builder => { var fileTarget = new FileTarget("fileTarget") { - FileName = Path.Join(AppDataDirectory, "logs", "VRCX.log"), + FileName = fileName, //Layout = "${longdate} [${level:uppercase=true}] ${logger} - ${message} ${exception:format=tostring}", // Layout with padding between the level/logger and message so that the message always starts at the same column Layout = @@ -183,7 +186,7 @@ namespace VRCX AppApiInstance.OpenLink("https://github.com/vrcx-team/VRCX/wiki#how-to-repair-vrcx-database"); } } - + #endregion catch (Exception e) @@ -215,6 +218,9 @@ namespace VRCX VRCXStorage.Instance.Load(); ConfigureLogger(); GetVersion(); + if (StartupArgs.LaunchArguments.IsOverlay) + OverlayProgram.OverlayMain(); + Update.Check(); Application.EnableVisualStyles(); @@ -225,43 +231,36 @@ namespace VRCX if (!string.IsNullOrEmpty(StartupArgs.LaunchArguments.LaunchCommand)) logger.Info("Launch Command: {0}", StartupArgs.LaunchArguments.LaunchCommand); logger.Debug("Wine detection: {0}", Wine.GetIfWine()); - + + IPCServer.Instance.Init(); SQLite.Instance.Init(); AppApiInstance = new AppApiCef(); - - AppApiVrInstance = new AppApiVrCef(); - AppApiVrInstance.Init(); + ProcessMonitor.Instance.Init(); Discord.Instance.Init(); WebApi.Instance.Init(); LogWatcher.Instance.Init(); AutoAppLaunchManager.Instance.Init(); CefService.Instance.Init(); - IPCServer.Instance.Init(); - - if (VRCXStorage.Instance.Get("VRCX_DisableVrOverlayGpuAcceleration") == "true") - VRCXVRInstance = new VRCXVRLegacy(); - else - VRCXVRInstance = new VRCXVRCef(); - VRCXVRInstance.Init(); + OverlayServer.Instance.Init(); Application.Run(new MainForm()); + logger.Info("{0} Exiting...", Version); WebApi.Instance.SaveCookies(); - VRCXVRInstance.Exit(); + OverlayServer.Instance.Exit(); CefService.Instance.Exit(); - AutoAppLaunchManager.Instance.Exit(); LogWatcher.Instance.Exit(); WebApi.Instance.Exit(); - Discord.Instance.Exit(); - SystemMonitorCef.Instance.Exit(); VRCXStorage.Instance.Save(); SQLite.Instance.Exit(); ProcessMonitor.Instance.Exit(); } #else + public static VRCXVRInterface VRCXVRInstance; + public static void PreInit(string version, string[] args) { Version = version; @@ -280,7 +279,7 @@ namespace VRCX logger.Info("Launch Command: {0}", StartupArgs.LaunchArguments.LaunchCommand); AppApiInstance = new AppApiElectron(); - + VRCXVRInstance = new VRCXVRElectron(); VRCXVRInstance.Init(); } diff --git a/Dotnet/StartupArgs.cs b/Dotnet/StartupArgs.cs index d9fb1f56..fcb5a1a4 100644 --- a/Dotnet/StartupArgs.cs +++ b/Dotnet/StartupArgs.cs @@ -49,7 +49,7 @@ namespace VRCX #if !LINUX var disableClosing = LaunchArguments.IsUpgrade || // we're upgrading, allow it - !string.IsNullOrEmpty(CommandLineArgsParser.GetArgumentValue(args, CefSharpArguments.SubProcessTypeArgument)); // we're launching a subprocess, allow it + !string.IsNullOrEmpty(CommandLineArgsParser.GetArgumentValue(args, CefSharpArguments.SubProcessTypeArgument)); // we're launching a subprocess, allow it // if we're launching a second instance with same config directory, focus the first instance then exit if (!disableClosing && IsDuplicateProcessRunning(LaunchArguments)) @@ -74,6 +74,9 @@ namespace VRCX if (arg.StartsWith(VrcxLaunchArguments.IsDebugPrefix)) arguments.IsDebug = true; + + if (arg == VrcxLaunchArguments.Overlay) + arguments.IsOverlay = true; if (arg.StartsWith(VrcxLaunchArguments.LaunchCommandPrefix) && arg.Length > VrcxLaunchArguments.LaunchCommandPrefix.Length) arguments.LaunchCommand = arg.Substring(VrcxLaunchArguments.LaunchCommandPrefix.Length); @@ -100,6 +103,9 @@ namespace VRCX public const string IsDebugPrefix = "--debug"; public bool IsDebug { get; set; } = false; + + public const string Overlay = "--overlay"; + public bool IsOverlay { get; set; } = false; public const string LaunchCommandPrefix = "/uri=vrcx://"; public const string LinuxLaunchCommandPrefix = "vrcx://"; @@ -138,6 +144,19 @@ namespace VRCX if (commandLine.Contains(SubProcessTypeArgument)) // ignore subprocesses continue; + if (launchArguments.IsOverlay) + { + if (commandLine.Contains(VrcxLaunchArguments.Overlay)) + { + Console.WriteLine(@"Another overlay instance is already running. Exiting this instance."); + Environment.Exit(0); + } + continue; // we are an overlay, ignore non-overlay instances + } + + if (commandLine.Contains(VrcxLaunchArguments.Overlay)) + continue; // we aren't an overlay, ignore overlay instances + var processArguments = ParseArgs(commandLine.Split(' ')); if (processArguments.ConfigDirectory == launchArguments.ConfigDirectory) { diff --git a/Dotnet/VRCX-Cef.csproj b/Dotnet/VRCX-Cef.csproj index c29fb7d5..11b20d41 100644 --- a/Dotnet/VRCX-Cef.csproj +++ b/Dotnet/VRCX-Cef.csproj @@ -107,10 +107,7 @@ - - - @@ -125,6 +122,10 @@ + + + + diff --git a/Dotnet/VRCX-Electron-arm64.csproj b/Dotnet/VRCX-Electron-arm64.csproj index 0f672d4c..62bd7593 100644 --- a/Dotnet/VRCX-Electron-arm64.csproj +++ b/Dotnet/VRCX-Electron-arm64.csproj @@ -81,10 +81,7 @@ - - - @@ -97,6 +94,8 @@ + + diff --git a/Dotnet/VRCX-Electron.csproj b/Dotnet/VRCX-Electron.csproj index a4e20da2..e8aaae49 100644 --- a/Dotnet/VRCX-Electron.csproj +++ b/Dotnet/VRCX-Electron.csproj @@ -86,10 +86,7 @@ - - - @@ -102,6 +99,8 @@ + + diff --git a/src-electron/main.js b/src-electron/main.js index d9de2c7c..d6e49934 100644 --- a/src-electron/main.js +++ b/src-electron/main.js @@ -84,22 +84,22 @@ if (fs.existsSync(armPath)) { const InteropApi = require('./InteropApi'); const interopApi = new InteropApi(); -const WRIST_FRAME_WIDTH = 512; -const WRIST_FRAME_HEIGHT = 512; -const WRIST_FRAME_SIZE = WRIST_FRAME_WIDTH * WRIST_FRAME_HEIGHT * 4; -const WRIST_SHM_PATH = '/dev/shm/vrcx_wrist_overlay'; +const OVERLAY_WRIST_FRAME_WIDTH = 512; +const OVERLAY_WRIST_FRAME_HEIGHT = 512; +const OVERLAY_HMD_FRAME_WIDTH = 1024; +const OVERLAY_HMD_FRAME_HEIGHT = 1024; +const OVERLAY_SHARED_HEIGHT = + OVERLAY_WRIST_FRAME_HEIGHT + OVERLAY_HMD_FRAME_HEIGHT; +const OVERLAY_SHARED_WIDTH = Math.max( + OVERLAY_WRIST_FRAME_WIDTH, + OVERLAY_HMD_FRAME_WIDTH +); +const OVERLAY_FRAME_SIZE = + OVERLAY_SHARED_WIDTH * OVERLAY_SHARED_HEIGHT * 4; +const OVERLAY_SHM_PATH = '/dev/shm/vrcx_overlay'; -function createWristOverlayWindowShm() { - fs.writeFileSync(WRIST_SHM_PATH, Buffer.alloc(WRIST_FRAME_SIZE + 1)); -} - -const HMD_FRAME_WIDTH = 1024; -const HMD_FRAME_HEIGHT = 1024; -const HMD_FRAME_SIZE = HMD_FRAME_WIDTH * HMD_FRAME_HEIGHT * 4; -const HMD_SHM_PATH = '/dev/shm/vrcx_hmd_overlay'; - -function createHmdOverlayWindowShm() { - fs.writeFileSync(HMD_SHM_PATH, Buffer.alloc(HMD_FRAME_SIZE + 1)); +function createOverlayWindowShm() { + fs.writeFileSync(OVERLAY_SHM_PATH, Buffer.alloc(OVERLAY_FRAME_SIZE + 1)); } const version = getVersion(); @@ -218,21 +218,11 @@ ipcMain.handle('app:restart', () => { } }); -ipcMain.handle('app:getWristOverlayWindow', () => { - if (wristOverlayWindow && wristOverlayWindow.webContents) { +ipcMain.handle('app:getOverlayWindow', () => { + if (overlayWindow && overlayWindow.webContents) { return ( - !wristOverlayWindow.webContents.isLoading() && - wristOverlayWindow.webContents.isPainting() - ); - } - return false; -}); - -ipcMain.handle('app:getHmdOverlayWindow', () => { - if (hmdOverlayWindow && hmdOverlayWindow.webContents) { - return ( - !hmdOverlayWindow.webContents.isLoading() && - hmdOverlayWindow.webContents.isPainting() + !overlayWindow.webContents.isLoading() && + overlayWindow.webContents.isPainting() ); } return false; @@ -241,22 +231,16 @@ ipcMain.handle('app:getHmdOverlayWindow', () => { ipcMain.handle( 'app:updateVr', (event, active, hmdOverlay, wristOverlay, menuButton, overlayHand) => { - if (!active) { + if (!active || (!hmdOverlay && !wristOverlay)) { disposeOverlay(); return; } - isOverlayActive = true; - - if (!hmdOverlay) { - destroyHmdOverlayWindow(); - } else if (active && !hmdOverlayWindow) { - createHmdOverlayWindowOffscreen(); - } - - if (!wristOverlay) { - destroyWristOverlayWindow(); - } else if (active && !wristOverlayWindow) { - createWristOverlayWindowOffscreen(); + if (active && !overlayWindow) { + try { + createOverlayWindowOffscreen(); + } catch (err) { + console.error('Error creating overlay windows:', err); + } } } ); @@ -412,19 +396,24 @@ function createWindow() { }); } -let wristOverlayWindow = undefined; +let overlayWindow = undefined; -function createWristOverlayWindowOffscreen() { - if (!fs.existsSync(WRIST_SHM_PATH)) { - createWristOverlayWindowShm(); +function createOverlayWindowOffscreen() { + if (process.platform !== 'linux') { + console.error('Offscreen overlay is only supported on Linux.'); + return; + } + isOverlayActive = true; + if (!fs.existsSync(OVERLAY_SHM_PATH)) { + createOverlayWindowShm(); } const x = parseInt(VRCXStorage.Get('VRCX_LocationX')) || 0; const y = parseInt(VRCXStorage.Get('VRCX_LocationY')) || 0; - const width = WRIST_FRAME_WIDTH; - const height = WRIST_FRAME_HEIGHT; + const width = OVERLAY_SHARED_WIDTH; + const height = OVERLAY_SHARED_HEIGHT; - wristOverlayWindow = new BrowserWindow({ + overlayWindow = new BrowserWindow({ x, y, width, @@ -440,112 +429,36 @@ function createWristOverlayWindowOffscreen() { preload: path.join(__dirname, 'preload.js') } }); - wristOverlayWindow.webContents.setFrameRate(2); + overlayWindow.webContents.setFrameRate(48); - let indexPath = path.join(rootDir, 'build/html/vr.html'); + let fileUrl = `file://${path.join(rootDir, 'build/html/vr.html')}`; if (debug) { - indexPath = 'http://localhost:9000/vr.html'; + fileUrl = 'http://localhost:9000/vr.html'; } - const fileUrl = `file://${indexPath}?wrist`; - wristOverlayWindow.loadURL(fileUrl, { userAgent: version }); - + overlayWindow.loadURL(fileUrl, { userAgent: version }); // Use paint event for offscreen rendering - wristOverlayWindow.webContents.on('paint', (event, dirty, image) => { + overlayWindow.webContents.on('paint', (event, dirty, image) => { const buffer = image.toBitmap(); - //console.log('Captured wrist frame via paint event, size:', buffer.length); - writeWristFrame(buffer); + //console.log('Captured frame via paint event, size:', buffer.length); + writeOverlayFrame(buffer); }); } -function writeWristFrame(imageBuffer) { +function writeOverlayFrame(imageBuffer) { try { - const fd = fs.openSync(WRIST_SHM_PATH, 'r+'); - const buffer = Buffer.alloc(WRIST_FRAME_SIZE + 1); + const fd = fs.openSync(OVERLAY_SHM_PATH, 'r+'); + const buffer = Buffer.alloc(OVERLAY_FRAME_SIZE + 1); buffer[0] = 0; // not ready - imageBuffer.copy(buffer, 1, 0, WRIST_FRAME_SIZE); + imageBuffer.copy(buffer, 1, 0, OVERLAY_FRAME_SIZE); buffer[0] = 1; // ready fs.writeSync(fd, buffer); fs.closeSync(fd); - //console.log('Wrote wrist frame to shared memory'); + //console.log('Wrote frame to shared memory'); } catch (err) { - console.error('Error writing wrist frame to shared memory:', err); + console.error('Error writing frame to shared memory:', err); } } -function destroyWristOverlayWindow() { - if (wristOverlayWindow && !wristOverlayWindow.isDestroyed()) { - wristOverlayWindow.close(); - } - wristOverlayWindow = undefined; -} - -let hmdOverlayWindow = undefined; - -function createHmdOverlayWindowOffscreen() { - if (!fs.existsSync(HMD_SHM_PATH)) { - createHmdOverlayWindowShm(); - } - - const x = parseInt(VRCXStorage.Get('VRCX_LocationX')) || 0; - const y = parseInt(VRCXStorage.Get('VRCX_LocationY')) || 0; - const width = HMD_FRAME_WIDTH; - const height = HMD_FRAME_HEIGHT; - - hmdOverlayWindow = new BrowserWindow({ - x, - y, - width, - height, - icon: path.join(rootDir, 'images/VRCX.png'), - autoHideMenuBar: true, - transparent: true, - frame: false, - show: false, - webPreferences: { - partition: 'vrcx-vr-overlay', - offscreen: true, - preload: path.join(__dirname, 'preload.js') - } - }); - hmdOverlayWindow.webContents.setFrameRate(48); - - let indexPath = path.join(rootDir, 'build/html/vr.html'); - if (debug) { - indexPath = 'http://localhost:9000/vr.html'; - } - const fileUrl = `file://${indexPath}?hmd`; - hmdOverlayWindow.loadURL(fileUrl, { userAgent: version }); - - // Use paint event for offscreen rendering - hmdOverlayWindow.webContents.on('paint', (event, dirty, image) => { - const buffer = image.toBitmap(); - //console.log('Captured HMD frame via paint event, size:', buffer.length); - writeHmdFrame(buffer); - }); -} - -function writeHmdFrame(imageBuffer) { - try { - const fd = fs.openSync(HMD_SHM_PATH, 'r+'); - const buffer = Buffer.alloc(HMD_FRAME_SIZE + 1); - buffer[0] = 0; // not ready - imageBuffer.copy(buffer, 1, 0, HMD_FRAME_SIZE); - buffer[0] = 1; // ready - fs.writeSync(fd, buffer); - fs.closeSync(fd); - //console.log('Wrote HMD frame to shared memory'); - } catch (err) { - console.error('Error writing HMD frame to shared memory:', err); - } -} - -function destroyHmdOverlayWindow() { - if (hmdOverlayWindow && !hmdOverlayWindow.isDestroyed()) { - hmdOverlayWindow.close(); - } - hmdOverlayWindow = undefined; -} - let tray = null; let trayIcon = null; let trayIconNotify = null; @@ -931,16 +844,6 @@ function applyWindowState() { app.whenReady().then(() => { createWindow(); createTray(); - - if (process.platform === 'linux') { - try { - createWristOverlayWindowOffscreen(); - createHmdOverlayWindowOffscreen(); - } catch (err) { - console.error('Error creating overlay windows:', err); - } - } - installVRCX(); app.on('activate', function () { @@ -959,21 +862,13 @@ function disposeOverlay() { if (!isOverlayActive) { return; } + if (overlayWindow && !overlayWindow.isDestroyed()) { + overlayWindow.close(); + } + overlayWindow = undefined; isOverlayActive = false; - if (wristOverlayWindow) { - wristOverlayWindow.close(); - wristOverlayWindow = undefined; - } - if (hmdOverlayWindow) { - hmdOverlayWindow.close(); - hmdOverlayWindow = undefined; - } - - if (fs.existsSync(WRIST_SHM_PATH)) { - fs.unlinkSync(WRIST_SHM_PATH); - } - if (fs.existsSync(HMD_SHM_PATH)) { - fs.unlinkSync(HMD_SHM_PATH); + if (fs.existsSync(OVERLAY_SHM_PATH)) { + fs.unlinkSync(OVERLAY_SHM_PATH); } } diff --git a/src-electron/preload.js b/src-electron/preload.js index 204585fd..8d457ede 100644 --- a/src-electron/preload.js +++ b/src-electron/preload.js @@ -39,9 +39,7 @@ contextBridge.exposeInMainWorld('electron', { desktopNotification: (title, body, icon) => ipcRenderer.invoke('notification:showNotification', title, body, icon), restartApp: () => ipcRenderer.invoke('app:restart'), - getWristOverlayWindow: () => - ipcRenderer.invoke('app:getWristOverlayWindow'), - getHmdOverlayWindow: () => ipcRenderer.invoke('app:getHmdOverlayWindow'), + getOverlayWindow: () => ipcRenderer.invoke('app:getOverlayWindow'), updateVr: (active, hmdOverlay, wristOverlay, menuButton, overlayHand) => ipcRenderer.invoke( 'app:updateVr', diff --git a/src/stores/friend.js b/src/stores/friend.js index b8045f13..66087a44 100644 --- a/src/stores/friend.js +++ b/src/stores/friend.js @@ -853,11 +853,11 @@ export const useFriendStore = defineStore('Friend', () => { reconnectWebSocket(); } - function updateOnlineFriendCounter() { + function updateOnlineFriendCounter(forceUpdate = false) { const onlineFriendCounts = vipFriends.value.length + onlineFriends.value.length; - if (onlineFriendCounts !== onlineFriendCount.value) { - AppApi.ExecuteVrFeedFunction( + if (onlineFriendCounts !== onlineFriendCount.value || forceUpdate) { + AppApi.ExecuteVrOverlayFunction( 'updateOnlineFriendCount', `${onlineFriendCounts}` ); diff --git a/src/stores/game.js b/src/stores/game.js index cb8dd1f7..dce2f88f 100644 --- a/src/stores/game.js +++ b/src/stores/game.js @@ -146,11 +146,7 @@ export const useGameStore = defineStore('Game', () => { } // use in C# - async function updateIsGameRunning( - isGameRunningArg, - isSteamVRRunningArg, - isHmdAfkArg - ) { + async function updateIsGameRunning(isGameRunningArg, isSteamVRRunningArg) { const avatarStore = useAvatarStore(); if (advancedSettingsStore.gameLogDisabled) { return; @@ -186,11 +182,15 @@ export const useGameStore = defineStore('Game', () => { isSteamVRRunning.value = isSteamVRRunningArg; console.log('isSteamVRRunning:', isSteamVRRunningArg); } + vrStore.updateOpenVR(); + } + + // use in C# + function updateIsHmdAfk(isHmdAfkArg) { if (isHmdAfkArg !== isHmdAfk.value) { isHmdAfk.value = isHmdAfkArg; - console.log('isHmdAfk:', isHmdAfkArg); + console.log('isHmdAfk', isHmdAfkArg); } - vrStore.updateOpenVR(); } async function checkVRChatDebugLogging() { @@ -258,6 +258,7 @@ export const useGameStore = defineStore('Game', () => { getVRChatCacheSize, updateIsGameRunning, getVRChatRegistryKey, - checkVRChatDebugLogging + checkVRChatDebugLogging, + updateIsHmdAfk }; }); diff --git a/src/stores/sharedFeed.js b/src/stores/sharedFeed.js index 781a66a3..7fd96374 100644 --- a/src/stores/sharedFeed.js +++ b/src/stores/sharedFeed.js @@ -233,7 +233,7 @@ export const useSharedFeedStore = defineStore('SharedFeed', () => { } } } - AppApi.ExecuteVrFeedFunction( + AppApi.ExecuteVrOverlayFunction( 'wristFeedUpdate', JSON.stringify(wristFeed) ); diff --git a/src/stores/updateLoop.js b/src/stores/updateLoop.js index e69cd84e..9feee8eb 100644 --- a/src/stores/updateLoop.js +++ b/src/stores/updateLoop.js @@ -131,8 +131,7 @@ export const useUpdateLoopStore = defineStore('UpdateLoop', () => { state.nextGameRunningCheck = 1; gameStore.updateIsGameRunning( await AppApi.IsGameRunning(), - await AppApi.IsSteamVRRunning(), - false + await AppApi.IsSteamVRRunning() ); vrStore.vrInit(); // TODO: make this event based } diff --git a/src/stores/vr.js b/src/stores/vr.js index d0c89032..013ecf23 100644 --- a/src/stores/vr.js +++ b/src/stores/vr.js @@ -45,8 +45,7 @@ export const useVrStore = defineStore('Vr', () => { updateVrNowPlaying(); // run these methods again to send data to the overlay sharedFeedStore.updateSharedFeed(true); - friendStore.onlineFriendCount = 0; // force an update - friendStore.updateOnlineFriendCounter(); + friendStore.updateOnlineFriendCounter(true); // force an update } async function saveOpenVROption() { @@ -59,7 +58,6 @@ export const useVrStore = defineStore('Vr', () => { function updateVrNowPlaying() { const json = JSON.stringify(gameLogStore.nowPlaying); - AppApi.ExecuteVrFeedFunction('nowPlayingUpdate', json); AppApi.ExecuteVrOverlayFunction('nowPlayingUpdate', json); } @@ -91,7 +89,6 @@ export const useVrStore = defineStore('Vr', () => { onlineFor }; const json = JSON.stringify(lastLocation); - AppApi.ExecuteVrFeedFunction('lastLocationUpdate', json); AppApi.ExecuteVrOverlayFunction('lastLocationUpdate', json); } @@ -142,7 +139,6 @@ export const useVrStore = defineStore('Vr', () => { /** @type {string} */ const json = JSON.stringify(VRConfigVars); - AppApi.ExecuteVrFeedFunction('configUpdate', json); AppApi.ExecuteVrOverlayFunction('configUpdate', json); } @@ -185,6 +181,9 @@ export const useVrStore = defineStore('Vr', () => { newState.menuButton, newState.overlayHand ); + if (!newState.active) { + gameStore.updateIsHmdAfk(false); + } if (LINUX) { window.electron.updateVr( diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts index dc3cb0bb..13884847 100644 --- a/src/types/globals.d.ts +++ b/src/types/globals.d.ts @@ -65,8 +65,7 @@ declare global { ) => void; onBrowserFocus: (Function: (event: any) => void) => void; restartApp: () => Promise; - getWristOverlayWindow: () => Promise; - getHmdOverlayWindow: () => Promise; + getOverlayWindow: () => Promise; updateVr: ( active: bool, hmdOverlay: bool, @@ -168,8 +167,6 @@ declare global { menuButton: boolean, overlayHand: number ): Promise; - RefreshVR(): Promise; - RestartVR(): Promise; SetZoom(zoomLevel: number): Promise; GetZoom(): Promise; DesktopNotification( @@ -179,7 +176,6 @@ declare global { ): Promise; RestartApplication(isUpgrade: boolean): Promise; CheckForUpdateExe(): Promise; - ExecuteVrFeedFunction(key: string, json: string): Promise; ExecuteVrOverlayFunction(key: string, json: string): Promise; FocusWindow(): Promise; ChangeTheme(value: number): Promise; @@ -367,7 +363,6 @@ declare global { GetUptime(): Promise; CurrentCulture(): Promise; CustomVrScript(): Promise; - GetExecuteVrFeedFunctionQueue(): Promise>; GetExecuteVrOverlayFunctionQueue(): Promise>; }; diff --git a/src/vr/Vr.vue b/src/vr/Vr.vue index 2d177d60..d9fd1e70 100644 --- a/src/vr/Vr.vue +++ b/src/vr/Vr.vue @@ -1,9 +1,6 @@