mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-21 15:53:50 +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:
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; }
|
||||
}
|
||||
Reference in New Issue
Block a user