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; } } }