mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-06 00:32:01 +02:00
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 <maplenagisa@gmail.com> Co-authored-by: rs189 <35667100+rs189@users.noreply.github.com>
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<double> 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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,5 @@ public abstract partial class AppApiVr
|
||||
public abstract double GetUptime();
|
||||
public abstract string CurrentCulture();
|
||||
public abstract string CustomVrScript();
|
||||
public abstract List<KeyValuePair<string, string>> GetExecuteVrFeedFunctionQueue();
|
||||
public abstract List<KeyValuePair<string, string>> GetExecuteVrOverlayFunctionQueue();
|
||||
}
|
||||
@@ -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
|
||||
/// <returns>An array of arrays containing information about the connected VR devices.</returns>
|
||||
public override string[][] GetVRDevices()
|
||||
{
|
||||
return Program.VRCXVRInstance.GetDevices();
|
||||
return OverlayProgram.VRCXVRInstance.GetDevices();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -75,11 +79,6 @@ namespace VRCX
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public override List<KeyValuePair<string, string>> GetExecuteVrFeedFunctionQueue()
|
||||
{
|
||||
throw new NotImplementedException("GetExecuteVrFeedFunctionQueue is not implemented in AppApiVrCef.");
|
||||
}
|
||||
|
||||
public override List<KeyValuePair<string, string>> GetExecuteVrOverlayFunctionQueue()
|
||||
{
|
||||
throw new NotImplementedException("GetExecuteVrOverlayFunctionQueue is not implemented in AppApiVrCef.");
|
||||
@@ -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<ID3D11Device1> _device1;
|
||||
private ComPtr<ID3D11DeviceContext> _deviceContext;
|
||||
private ComPtr<ID3D11Query> _query;
|
||||
private ComPtr<ID3D11Texture2D> _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<ID3D11Device1> _device1;
|
||||
private ComPtr<ID3D11DeviceContext> _deviceContext;
|
||||
private ComPtr<ID3D11Query> _query;
|
||||
private ComPtr<ID3D11Texture2D> _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<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext, ComPtr<ID3D11Texture2D> renderTarget)
|
||||
{
|
||||
_device1.Dispose();
|
||||
_device1 = device.QueryInterface<ID3D11Device1>();
|
||||
_deviceContext = deviceContext;
|
||||
_renderTarget = renderTarget;
|
||||
|
||||
_query.Dispose();
|
||||
device.CreateQuery(new QueryDesc
|
||||
{
|
||||
Query = Query.Event,
|
||||
MiscFlags = 0
|
||||
}, ref _query);
|
||||
}
|
||||
|
||||
// legacy
|
||||
public void RenderToTexture(ComPtr<ID3D11DeviceContext> deviceContext, ComPtr<ID3D11Texture2D> texture)
|
||||
{
|
||||
// Safeguard against uninitialized texture
|
||||
unsafe
|
||||
{
|
||||
if ((IntPtr)texture.Handle == IntPtr.Zero)
|
||||
return;
|
||||
}
|
||||
|
||||
public void UpdateRender(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext, ComPtr<ID3D11Texture2D> renderTarget)
|
||||
_paintBufferLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
_device1.Dispose();
|
||||
_device1 = device.QueryInterface<ID3D11Device1>();
|
||||
_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<ID3D11Texture2D> cefTexture =
|
||||
_device1.OpenSharedResource1<ID3D11Texture2D>(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<ID3D11Texture2D> cefTexture =
|
||||
_device1.OpenSharedResource1<ID3D11Texture2D>(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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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<ID3D11DeviceContext> deviceContext, ComPtr<ID3D11Texture2D> 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
120
Dotnet/Overlay/Cef/OverlayClient.cs
Normal file
120
Dotnet/Overlay/Cef/OverlayClient.cs
Normal file
@@ -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<ClientWebSocket>(() =>
|
||||
{
|
||||
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<OverlayMessage>(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));
|
||||
}
|
||||
}
|
||||
51
Dotnet/Overlay/Cef/OverlayProgram.cs
Normal file
51
Dotnet/Overlay/Cef/OverlayProgram.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<string[]> _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<IDXGIFactory2> _factory;
|
||||
@@ -52,16 +57,11 @@ namespace VRCX
|
||||
private ComPtr<ID3D11Multithread> _multithread;
|
||||
private ComPtr<ID3D11DeviceContext> _deviceContext;
|
||||
|
||||
private ComPtr<ID3D11Texture2D> _texture1;
|
||||
private ComPtr<ID3D11Texture2D> _texture2;
|
||||
private ComPtr<ID3D11Texture2D> _sharedTexture;
|
||||
|
||||
static VRCXVRCef()
|
||||
{
|
||||
Instance = new VRCXVRCef();
|
||||
}
|
||||
|
||||
public VRCXVRCef()
|
||||
public VRCXVRCef(bool isLegacy)
|
||||
{
|
||||
_isLegacy = isLegacy;
|
||||
_deviceListLock = new ReaderWriterLockSlim();
|
||||
_deviceList = new List<string[]>();
|
||||
_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<ID3D11Multithread>();
|
||||
_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<KeyValuePair<string, string>> 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<KeyValuePair<string, string>> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<string[]> _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<IDXGIFactory2> _factory;
|
||||
private ComPtr<IDXGIAdapter> _adapter;
|
||||
private ComPtr<ID3D11Device> _device;
|
||||
private ComPtr<ID3D11Multithread> _multithread;
|
||||
private ComPtr<ID3D11DeviceContext> _deviceContext;
|
||||
|
||||
private ComPtr<ID3D11Texture2D> _texture1;
|
||||
private ComPtr<ID3D11Texture2D> _texture2;
|
||||
|
||||
static VRCXVRLegacy()
|
||||
{
|
||||
Instance = new VRCXVRLegacy();
|
||||
}
|
||||
|
||||
public VRCXVRLegacy()
|
||||
{
|
||||
_deviceListLock = new ReaderWriterLockSlim();
|
||||
_deviceList = new List<string[]>();
|
||||
_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<IDXGIFactory2>(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<ID3D11Multithread>();
|
||||
_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<KeyValuePair<string, string>> 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<KeyValuePair<string, string>> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
104
Dotnet/Overlay/Cef/VRForm.Designer.cs
generated
104
Dotnet/Overlay/Cef/VRForm.Designer.cs
generated
@@ -1,104 +0,0 @@
|
||||
namespace VRCX
|
||||
{
|
||||
partial class VRForm
|
||||
{
|
||||
/// <summary>
|
||||
/// 필수 디자이너 변수입니다.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// 사용 중인 모든 리소스를 정리합니다.
|
||||
/// </summary>
|
||||
/// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form 디자이너에서 생성한 코드
|
||||
|
||||
/// <summary>
|
||||
/// 디자이너 지원에 필요한 메서드입니다.
|
||||
/// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="timer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -19,16 +19,6 @@ namespace VRCX
|
||||
{
|
||||
}
|
||||
|
||||
public override List<KeyValuePair<string, string>> GetExecuteVrFeedFunctionQueue()
|
||||
{
|
||||
var list = new List<KeyValuePair<string, string>>();
|
||||
while (Program.VRCXVRInstance.GetExecuteVrFeedFunctionQueue().TryDequeue(out var item))
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public override List<KeyValuePair<string, string>> GetExecuteVrOverlayFunctionQueue()
|
||||
{
|
||||
var list = new List<KeyValuePair<string, string>>();
|
||||
@@ -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<KeyValuePair<string, string>> _wristFeedFunctionQueue = new ConcurrentQueue<KeyValuePair<string, string>>();
|
||||
private readonly ConcurrentQueue<KeyValuePair<string, string>> _hmdFeedFunctionQueue = new ConcurrentQueue<KeyValuePair<string, string>>();
|
||||
|
||||
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<KeyValuePair<string, string>> _overlayFunctionQueue = new ConcurrentQueue<KeyValuePair<string, string>>();
|
||||
|
||||
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<KeyValuePair<string, string>> 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<string, string>(function, json));
|
||||
}
|
||||
|
||||
public override ConcurrentQueue<KeyValuePair<string, string>> 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<string, string>(function, json));
|
||||
_overlayFunctionQueue.Enqueue(new KeyValuePair<string, string>(function, json));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<KeyValuePair<string, string>> GetExecuteVrFeedFunctionQueue();
|
||||
public abstract ConcurrentQueue<KeyValuePair<string, string>> GetExecuteVrOverlayFunctionQueue();
|
||||
}
|
||||
53
Dotnet/OverlayWebSocket/OverlayManager.cs
Normal file
53
Dotnet/OverlayWebSocket/OverlayManager.cs
Normal file
@@ -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<string>();
|
||||
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");
|
||||
}
|
||||
}
|
||||
17
Dotnet/OverlayWebSocket/OverlayMessage.cs
Normal file
17
Dotnet/OverlayWebSocket/OverlayMessage.cs
Normal file
@@ -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; }
|
||||
}
|
||||
9
Dotnet/OverlayWebSocket/OverlayMessageType.cs
Normal file
9
Dotnet/OverlayWebSocket/OverlayMessageType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace VRCX;
|
||||
|
||||
public enum OverlayMessageType
|
||||
{
|
||||
OverlayConnected,
|
||||
JsFunctionCall,
|
||||
UpdateVars,
|
||||
IsHmdAfk
|
||||
}
|
||||
212
Dotnet/OverlayWebSocket/OverlayServer.cs
Normal file
212
Dotnet/OverlayWebSocket/OverlayServer.cs
Normal file
@@ -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<WebSocket, byte> 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<byte>(receiveBuffer), CancellationToken.None);
|
||||
switch (receiveResult.MessageType)
|
||||
{
|
||||
case WebSocketMessageType.Text:
|
||||
var text = Encoding.UTF8.GetString(receiveBuffer, 0, receiveResult.Count);
|
||||
var message = JsonSerializer.Deserialize<OverlayMessage>(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<byte>(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);
|
||||
}
|
||||
}
|
||||
10
Dotnet/OverlayWebSocket/OverlayVars.cs
Normal file
10
Dotnet/OverlayWebSocket/OverlayVars.cs
Normal file
@@ -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; }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -107,10 +107,7 @@
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
|
||||
<PackageReference Include="SourceGear.sqlite3" Version="3.50.4.5" />
|
||||
<PackageReference Include="System.Data.SQLite" Version="2.0.2" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.1" />
|
||||
<PackageReference Include="System.Management" Version="10.0.1" />
|
||||
<PackageReference Include="System.Text.Json" Version="10.0.1" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="Websocket.Client" Version="5.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -125,6 +122,10 @@
|
||||
<ItemGroup>
|
||||
<Content Remove="obj1\**" />
|
||||
<Compile Remove="obj1\**" />
|
||||
<Content Remove="AppApi\Electron\**" />
|
||||
<Compile Remove="AppApi\Electron\**" />
|
||||
<Content Remove="Overlay\Electron\**" />
|
||||
<Compile Remove="Overlay\Electron\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
|
||||
@@ -81,10 +81,7 @@
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
|
||||
<PackageReference Include="SourceGear.sqlite3" Version="3.50.4.5" />
|
||||
<PackageReference Include="System.Data.SQLite" Version="2.0.2" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.1" />
|
||||
<PackageReference Include="System.Management" Version="10.0.1" />
|
||||
<PackageReference Include="System.Text.Json" Version="10.0.1" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="Websocket.Client" Version="5.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -97,6 +94,8 @@
|
||||
<Compile Remove="AppApi\Cef\**" />
|
||||
<Content Remove="Overlay\Cef\**" />
|
||||
<Compile Remove="Overlay\Cef\**" />
|
||||
<Content Remove="OverlayWebSocket\**" />
|
||||
<Compile Remove="OverlayWebSocket\**" />
|
||||
<Content Remove="DBMerger\**" />
|
||||
<Compile Remove="DBMerger\**" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -86,10 +86,7 @@
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
|
||||
<PackageReference Include="SourceGear.sqlite3" Version="3.50.4.5" />
|
||||
<PackageReference Include="System.Data.SQLite" Version="1.0.119" /> <!--DO NOT UPGRADE-->
|
||||
<PackageReference Include="System.Drawing.Common" Version="10.0.1" />
|
||||
<PackageReference Include="System.Management" Version="10.0.1" />
|
||||
<PackageReference Include="System.Text.Json" Version="10.0.1" />
|
||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageReference Include="Websocket.Client" Version="5.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -102,6 +99,8 @@
|
||||
<Compile Remove="AppApi\Cef\**" />
|
||||
<Content Remove="Overlay\Cef\**" />
|
||||
<Compile Remove="Overlay\Cef\**" />
|
||||
<Content Remove="OverlayWebSocket\**" />
|
||||
<Compile Remove="OverlayWebSocket\**" />
|
||||
<Content Remove="DBMerger\**" />
|
||||
<Compile Remove="DBMerger\**" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}`
|
||||
);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
});
|
||||
|
||||
@@ -233,7 +233,7 @@ export const useSharedFeedStore = defineStore('SharedFeed', () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
AppApi.ExecuteVrFeedFunction(
|
||||
AppApi.ExecuteVrOverlayFunction(
|
||||
'wristFeedUpdate',
|
||||
JSON.stringify(wristFeed)
|
||||
);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
7
src/types/globals.d.ts
vendored
7
src/types/globals.d.ts
vendored
@@ -65,8 +65,7 @@ declare global {
|
||||
) => void;
|
||||
onBrowserFocus: (Function: (event: any) => void) => void;
|
||||
restartApp: () => Promise<void>;
|
||||
getWristOverlayWindow: () => Promise<boolean>;
|
||||
getHmdOverlayWindow: () => Promise<boolean>;
|
||||
getOverlayWindow: () => Promise<boolean>;
|
||||
updateVr: (
|
||||
active: bool,
|
||||
hmdOverlay: bool,
|
||||
@@ -168,8 +167,6 @@ declare global {
|
||||
menuButton: boolean,
|
||||
overlayHand: number
|
||||
): Promise<void>;
|
||||
RefreshVR(): Promise<void>;
|
||||
RestartVR(): Promise<void>;
|
||||
SetZoom(zoomLevel: number): Promise<void>;
|
||||
GetZoom(): Promise<number>;
|
||||
DesktopNotification(
|
||||
@@ -179,7 +176,6 @@ declare global {
|
||||
): Promise<void>;
|
||||
RestartApplication(isUpgrade: boolean): Promise<void>;
|
||||
CheckForUpdateExe(): Promise<boolean>;
|
||||
ExecuteVrFeedFunction(key: string, json: string): Promise<void>;
|
||||
ExecuteVrOverlayFunction(key: string, json: string): Promise<void>;
|
||||
FocusWindow(): Promise<void>;
|
||||
ChangeTheme(value: number): Promise<void>;
|
||||
@@ -367,7 +363,6 @@ declare global {
|
||||
GetUptime(): Promise<number>;
|
||||
CurrentCulture(): Promise<string>;
|
||||
CustomVrScript(): Promise<string>;
|
||||
GetExecuteVrFeedFunctionQueue(): Promise<Map<string, string>>;
|
||||
GetExecuteVrOverlayFunctionQueue(): Promise<Map<string, string>>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
id="x-app"
|
||||
class="x-app x-app-type"
|
||||
:class="{ background: appType === 'wrist' && config && config.backgroundEnabled }">
|
||||
<template v-if="appType === 'wrist' && !vrState.isWristDisabled">
|
||||
<div id="x-app" class="x-app x-app-type">
|
||||
<div class="wrist" :class="{ background: config && config.backgroundEnabled }">
|
||||
<div class="x-container" style="flex: 1">
|
||||
<div class="x-friend-list" ref="list" style="color: #aaa">
|
||||
<template v-if="config && config.minimalFeed">
|
||||
@@ -1283,9 +1280,9 @@
|
||||
<span style="display: inline-block">{{ t('vr.status.online') }} {{ onlineFriendCount }}</span>
|
||||
<span style="display: inline-block; margin-left: 5px">{{ customInfo }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- HMD Overlay -->
|
||||
<template v-else-if="appType === 'hmd' && !vrState.isHmdDisabled">
|
||||
<div class="hmd">
|
||||
<svg class="np-progress-circle">
|
||||
<circle
|
||||
class="np-progress-circle-stroke"
|
||||
@@ -1406,7 +1403,7 @@
|
||||
d="M102.9,75l11.3-11.3c10.3-10.3,11.5-26.1,3.8-37.8l17.4-17.4L126.9,0l-17.4,17.4C97.9,9.7,82,11,71.8,21.2L60.5,32.5C102,74,60.8,32.9,102.9,75z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1437,7 +1434,6 @@
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
const vrState = reactive({
|
||||
appType: new URLSearchParams(window.location.search).has('wrist') ? 'wrist' : 'hmd',
|
||||
appLanguage: 'en',
|
||||
currentCulture: 'en-gb',
|
||||
currentTime: new Date().toJSON(),
|
||||
@@ -1513,10 +1509,8 @@
|
||||
if (LINUX) {
|
||||
updateVrElectronLoop();
|
||||
}
|
||||
if (vrState.appType === 'wrist') {
|
||||
refreshCustomScript();
|
||||
updateStatsLoop();
|
||||
}
|
||||
refreshCustomScript();
|
||||
updateStatsLoop();
|
||||
setDatetimeFormat();
|
||||
|
||||
nextTick(() => {
|
||||
@@ -1614,25 +1608,17 @@
|
||||
|
||||
function nowPlayingUpdate(json) {
|
||||
vrState.nowPlaying = JSON.parse(json);
|
||||
if (vrState.appType === 'hmd') {
|
||||
const circle = /** @type {SVGCircleElement | null} */ (
|
||||
document.querySelector('.np-progress-circle-stroke')
|
||||
);
|
||||
const circle = /** @type {SVGCircleElement} */ (document.querySelector('.np-progress-circle-stroke'));
|
||||
|
||||
if (!circle) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vrState.lastLocation.progressPie && vrState.nowPlaying.percentage !== 0) {
|
||||
circle.style.opacity = (0.5).toString();
|
||||
const circumference = circle.getTotalLength();
|
||||
circle.style.strokeDashoffset = (
|
||||
circumference -
|
||||
(vrState.nowPlaying.percentage / 100) * circumference
|
||||
).toString();
|
||||
} else {
|
||||
circle.style.opacity = '0';
|
||||
}
|
||||
if (vrState.lastLocation.progressPie && vrState.nowPlaying.percentage !== 0) {
|
||||
circle.style.opacity = (0.5).toString();
|
||||
const circumference = circle.getTotalLength();
|
||||
circle.style.strokeDashoffset = (
|
||||
circumference -
|
||||
(vrState.nowPlaying.percentage / 100) * circumference
|
||||
).toString();
|
||||
} else {
|
||||
circle.style.opacity = '0';
|
||||
}
|
||||
updateFeedLength();
|
||||
}
|
||||
@@ -1647,7 +1633,7 @@
|
||||
}
|
||||
|
||||
function updateFeedLength() {
|
||||
if (vrState.appType === 'hmd' || vrState.wristFeed.length === 0) {
|
||||
if (vrState.wristFeed.length === 0) {
|
||||
return;
|
||||
}
|
||||
let length = 16;
|
||||
@@ -1787,36 +1773,19 @@
|
||||
|
||||
async function updateVrElectronLoop() {
|
||||
try {
|
||||
if (vrState.appType === 'wrist') {
|
||||
const wristOverlayQueue = await AppApiVr.GetExecuteVrFeedFunctionQueue();
|
||||
if (wristOverlayQueue) {
|
||||
wristOverlayQueue.forEach((item) => {
|
||||
// item[0] is the function name, item[1] is already an object
|
||||
const fullFunctionName = item[0];
|
||||
const jsonArg = item[1];
|
||||
const overlayQueue = await AppApiVr.GetExecuteVrOverlayFunctionQueue();
|
||||
if (overlayQueue) {
|
||||
overlayQueue.forEach((item) => {
|
||||
// item[0] is the function name, item[1] is already an object
|
||||
const fullFunctionName = item[0];
|
||||
const jsonArg = item[1];
|
||||
|
||||
if (typeof window.$vr === 'object' && typeof window.$vr[fullFunctionName] === 'function') {
|
||||
window.$vr[fullFunctionName](jsonArg);
|
||||
} else {
|
||||
console.error(`$vr.${fullFunctionName} is not defined or is not a function`);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const hmdOverlayQueue = await AppApiVr.GetExecuteVrOverlayFunctionQueue();
|
||||
if (hmdOverlayQueue) {
|
||||
hmdOverlayQueue.forEach((item) => {
|
||||
// item[0] is the function name, item[1] is already an object
|
||||
const fullFunctionName = item[0];
|
||||
const jsonArg = item[1];
|
||||
|
||||
if (typeof window.$vr === 'object' && typeof window.$vr[fullFunctionName] === 'function') {
|
||||
window.$vr[fullFunctionName](jsonArg);
|
||||
} else {
|
||||
console.error(`$vr.${fullFunctionName} is not defined or is not a function`);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (typeof window.$vr === 'object' && typeof window.$vr[fullFunctionName] === 'function') {
|
||||
window.$vr[fullFunctionName](jsonArg);
|
||||
} else {
|
||||
console.error(`$vr.${fullFunctionName} is not defined or is not a function`);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -2126,7 +2095,6 @@
|
||||
}
|
||||
|
||||
const {
|
||||
appType,
|
||||
config,
|
||||
wristFeed,
|
||||
devices,
|
||||
|
||||
@@ -240,6 +240,30 @@ button {
|
||||
|
||||
.x-app-type {
|
||||
color: #fff;
|
||||
width: 1024px;
|
||||
height: 1536px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wrist {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0;
|
||||
width: 512px;
|
||||
height: 512px;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hmd {
|
||||
position: absolute;
|
||||
top: 512px;
|
||||
left: 0;
|
||||
width: 1024px;
|
||||
height: 1024px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.background {
|
||||
|
||||
Reference in New Issue
Block a user