using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO.MemoryMappedFiles;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Threading;
using System.Threading.Tasks;
using CefSharp;
using CefSharp.OffScreen;
using Microsoft.Win32.SafeHandles;
namespace RageCoop.Client.CefHost
{
public enum BufferMode
{
Full = 1,
Dirty = 2
}
public enum MouseButton
{
Left,
Middle,
Right
}
///
/// Hosted by CefHost for managing cef instances.
///
public class CefController : MarshalByRefObject, IDisposable
{
private static Process _host;
private static ActivatedClientTypeEntry _controllerEntry;
private static IpcChannel _adapterChannel;
public static Action OnCefMessage;
private ChromiumWebBrowser _browser;
private MemoryMappedFile _mmf;
private string _mmfName;
private CefProcessor _processor;
private SafeMemoryMappedViewHandle _mmfView;
private BufferMode _mode;
public IntPtr PtrBuffer { get; private set; }
public int FrameRate
{
get => _browser.GetBrowserHost().WindowlessFrameRate;
set => _browser.GetBrowserHost().WindowlessFrameRate = value;
}
public void Dispose()
{
_browser?.Dispose();
_mmf?.Dispose();
if (PtrBuffer != IntPtr.Zero) _mmfView?.ReleasePointer();
_mmfView?.Dispose();
PtrBuffer = IntPtr.Zero;
_mmf = null;
_mmfView = null;
}
public static void Initialize(string fileName = "RageCoop.Client.CefHost.exe")
{
_host = new Process();
_host.StartInfo = new ProcessStartInfo
{
FileName = fileName,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
_host.EnableRaisingEvents = true;
_host.Start();
RegisterChannels(_host.StandardOutput.ReadLine());
Task.Run(() =>
{
while (_host?.HasExited == false) OnCefMessage?.Invoke("[CEF]: " + _host.StandardOutput.ReadLine());
});
Task.Run(() =>
{
while (_host?.HasExited == false)
OnCefMessage?.Invoke("[CEF][ERROR]: " + _host.StandardError.ReadLine());
});
}
public static void ShutDown()
{
if (_host == null) return;
_host.StandardInput.WriteLine("exit");
_host.WaitForExit(1000);
_host.Kill();
_host = null;
}
private static void RegisterChannels(string hostChannel)
{
var service = Guid.NewGuid().ToString();
Console.WriteLine("Registering adapter channel: " + service);
_adapterChannel = new IpcChannel(service);
ChannelServices.RegisterChannel(_adapterChannel, false);
_controllerEntry = new ActivatedClientTypeEntry(typeof(CefController), "ipc://" + hostChannel);
RemotingConfiguration.RegisterActivatedClientType(_controllerEntry);
Console.WriteLine("Registered controller entry: " + "ipc://" + hostChannel);
RemotingConfiguration.RegisterActivatedServiceType(typeof(CefAdapter));
Console.WriteLine("Registered service: " + nameof(CefAdapter));
_host.StandardInput.WriteLine("ipc://" + service);
}
///
/// Called inside client process
///
///
///
///
///
///
///
public static CefController Create(int id, Size size, out CefAdapter adapter, BufferMode bufferMode,
long bufferSize = 1024 * 1024 * 16)
{
if (RemotingConfiguration.IsRemotelyActivatedClientType(typeof(CefController)) == null)
throw new RemotingException();
var controller = new CefController();
controller.Activate(id, size, bufferMode, bufferSize);
adapter = CefAdapter.Adapters[id];
controller.Ping();
return controller;
}
public unsafe void Activate(int id, Size size, BufferMode mode = BufferMode.Dirty,
long sharedMemorySize = 1024 * 1024 * 16)
{
_mode = mode;
_mmfName = Guid.NewGuid().ToString();
// Set up shared memory
_mmf = MemoryMappedFile.CreateNew(_mmfName, sharedMemorySize);
_mmfView = _mmf.CreateViewAccessor().SafeMemoryMappedViewHandle;
byte* pBuf = null;
try
{
_mmfView.AcquirePointer(ref pBuf);
PtrBuffer = (IntPtr)pBuf;
}
catch
{
Dispose();
throw;
}
var adapter = new CefAdapter();
adapter.Register(id, mode, _mmfName);
_browser = new ChromiumWebBrowser();
_browser.RenderHandler = _processor = new CefProcessor(size, adapter, PtrBuffer, mode);
while (_browser.GetBrowserHost() == null) Thread.Sleep(20); // Wait till the browser is actually created
Console.WriteLine("CefController created: " + size);
}
public void LoadUrl(string url)
{
_browser.LoadUrl(url);
}
public void Resize(Size size)
{
_browser.Size = size;
_processor.Size = size;
}
public void SendMouseClick(int x, int y, int modifiers, MouseButton button, bool mouseUp, int clicks)
{
var e = new MouseEvent(x, y, (CefEventFlags)modifiers);
_browser.GetBrowserHost()
?.SendMouseClickEvent(e, (MouseButtonType)button, mouseUp, clicks);
}
public void SendMouseMove(int x, int y,bool leave=false)
{
var e = new MouseEvent(x, y, 0);
_browser.GetBrowserHost()?.SendMouseMoveEvent(e,leave);
}
public DateTime Ping()
{
return DateTime.UtcNow;
;
}
public override object InitializeLifetimeService()
{
return null;
}
}
///
/// Hosted by client for receiving rendering data
///
public class CefAdapter : MarshalByRefObject, IDisposable
{
public delegate void PaintDelegate(int bufferSize, Rectangle dirtyRect);
public delegate void ResizeDelegate(Size newSize);
public static Dictionary Adapters = new Dictionary();
private MemoryMappedFile _mmf;
private SafeMemoryMappedViewHandle _mmfView;
public int Id;
public Size Size { get; private set; }
public CefAdapter()
{
Console.WriteLine("Adapter created");
}
public IntPtr PtrBuffer { get; private set; }
///
/// Maximum buffer size for a paint event, use this property to allocate memory.
///
/// Value is equal to *4, therefore will change upon resize
public int MaxBufferSize => Size.Height * Size.Width * 4;
public BufferMode BufferMode { get; private set; }
public void Dispose()
{
_mmf?.Dispose();
if (PtrBuffer != IntPtr.Zero) _mmfView?.ReleasePointer();
_mmfView?.Dispose();
PtrBuffer = IntPtr.Zero;
_mmf = null;
_mmfView = null;
lock (Adapters)
{
if (Adapters.ContainsKey(Id)) Adapters.Remove(Id);
}
}
public event PaintDelegate OnPaint;
public event ResizeDelegate OnResize;
public void Resized(Size newSize)
{
Size = newSize;
OnResize?.Invoke(newSize);
}
public void Paint(Rectangle dirtyRect)
{
var size = BufferMode == BufferMode.Dirty
? dirtyRect.Width * dirtyRect.Height * 4
: Size.Width * Size.Height * 4;
OnPaint?.Invoke(size, dirtyRect);
}
public override object InitializeLifetimeService()
{
return null;
}
public unsafe void Register(int id, BufferMode mode, string mmfName)
{
lock (Adapters)
{
if (Adapters.ContainsKey(id)) throw new ArgumentException("Specified id is already used", nameof(id));
// Set up shared memory
_mmf = MemoryMappedFile.OpenExisting(mmfName);
_mmfView = _mmf.CreateViewAccessor().SafeMemoryMappedViewHandle;
byte* pBuf = null;
try
{
_mmfView.AcquirePointer(ref pBuf);
PtrBuffer = (IntPtr)pBuf;
}
catch
{
Dispose();
throw;
}
Id = id;
BufferMode = mode;
Adapters.Add(id, this);
}
}
///
/// Ensure ipc connection
///
///
public DateTime Ping()
{
return DateTime.UtcNow;
}
}
}