diff --git a/Robust.Client.WebView/BeforeBrowseContext.cs b/Robust.Client.WebView/Cef/CefBeforeBrowseContext.cs similarity index 81% rename from Robust.Client.WebView/BeforeBrowseContext.cs rename to Robust.Client.WebView/Cef/CefBeforeBrowseContext.cs index a50b1c110..c2d5fb575 100644 --- a/Robust.Client.WebView/BeforeBrowseContext.cs +++ b/Robust.Client.WebView/Cef/CefBeforeBrowseContext.cs @@ -1,8 +1,8 @@ using Xilium.CefGlue; -namespace Robust.Client.WebView +namespace Robust.Client.WebView.Cef { - public sealed class BeforeBrowseContext + internal sealed class CefBeforeBrowseContext : IBeforeBrowseContext { internal readonly CefRequest CefRequest; @@ -14,7 +14,7 @@ namespace Robust.Client.WebView public bool IsCancelled { get; private set; } - internal BeforeBrowseContext( + internal CefBeforeBrowseContext( bool isRedirect, bool userGesture, CefRequest cefRequest) diff --git a/Robust.Client.WebView/CefKeyCodes.cs b/Robust.Client.WebView/Cef/CefKeyCodes.cs similarity index 99% rename from Robust.Client.WebView/CefKeyCodes.cs rename to Robust.Client.WebView/Cef/CefKeyCodes.cs index f70d64641..5484124c4 100644 --- a/Robust.Client.WebView/CefKeyCodes.cs +++ b/Robust.Client.WebView/Cef/CefKeyCodes.cs @@ -1,7 +1,7 @@  using System.Diagnostics.CodeAnalysis; -namespace Robust.Client.WebView +namespace Robust.Client.WebView.Cef { [SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "UnusedMember.Global")] diff --git a/Robust.Client.WebView/RequestHandlerContext.cs b/Robust.Client.WebView/Cef/CefRequestHandlerContext.cs similarity index 89% rename from Robust.Client.WebView/RequestHandlerContext.cs rename to Robust.Client.WebView/Cef/CefRequestHandlerContext.cs index f987dce9a..8db5fbc2b 100644 --- a/Robust.Client.WebView/RequestHandlerContext.cs +++ b/Robust.Client.WebView/Cef/CefRequestHandlerContext.cs @@ -3,9 +3,9 @@ using System.IO; using System.Net; using Xilium.CefGlue; -namespace Robust.Client.WebView +namespace Robust.Client.WebView.Cef { - public sealed class RequestHandlerContext + internal sealed class CefRequestHandlerContext : IRequestHandlerContext { internal readonly CefRequest CefRequest; @@ -22,7 +22,7 @@ namespace Robust.Client.WebView internal IRequestResult? Result { get; private set; } - internal RequestHandlerContext( + internal CefRequestHandlerContext( bool isNavigation, bool isDownload, string requestInitiator, diff --git a/Robust.Client.WebView/ImageBuffer.cs b/Robust.Client.WebView/Cef/ImageBuffer.cs similarity index 82% rename from Robust.Client.WebView/ImageBuffer.cs rename to Robust.Client.WebView/Cef/ImageBuffer.cs index 924afbea6..08e776d34 100644 --- a/Robust.Client.WebView/ImageBuffer.cs +++ b/Robust.Client.WebView/Cef/ImageBuffer.cs @@ -1,22 +1,14 @@ using System; -using Robust.Client.UserInterface; using Robust.Client.Utility; using Robust.Shared.Maths; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using Xilium.CefGlue; -namespace Robust.Client.WebView +namespace Robust.Client.WebView.Cef { internal sealed class ImageBuffer { - private readonly Control _control; - - public ImageBuffer(Control control) - { - _control = control; - } - public Image Buffer { get; private set; } = new(1, 1); public unsafe void UpdateBuffer(int width, int height, IntPtr buffer, CefRectangle dirtyRect) diff --git a/Robust.Client.WebView/Program.cs b/Robust.Client.WebView/Cef/Program.cs similarity index 96% rename from Robust.Client.WebView/Program.cs rename to Robust.Client.WebView/Cef/Program.cs index b5e72e59d..0160cdd18 100644 --- a/Robust.Client.WebView/Program.cs +++ b/Robust.Client.WebView/Cef/Program.cs @@ -1,7 +1,7 @@ using System; using Xilium.CefGlue; -namespace Robust.Client.WebView +namespace Robust.Client.WebView.Cef { public static class Program { diff --git a/Robust.Client.WebView/RobustCefApp.cs b/Robust.Client.WebView/Cef/RobustCefApp.cs similarity index 93% rename from Robust.Client.WebView/RobustCefApp.cs rename to Robust.Client.WebView/Cef/RobustCefApp.cs index 74144ec55..84554d735 100644 --- a/Robust.Client.WebView/RobustCefApp.cs +++ b/Robust.Client.WebView/Cef/RobustCefApp.cs @@ -1,9 +1,8 @@ -using System; using Robust.Shared.IoC; using Robust.Shared.Log; using Xilium.CefGlue; -namespace Robust.Client.WebView +namespace Robust.Client.WebView.Cef { internal class RobustCefApp : CefApp { @@ -24,6 +23,7 @@ namespace Robust.Client.WebView { // Disable zygote on Linux. commandLine.AppendSwitch("--no-zygote"); + // commandLine.AppendSwitch("--single-process"); //commandLine.AppendSwitch("--disable-gpu"); //commandLine.AppendSwitch("--disable-gpu-compositing"); diff --git a/Robust.Client.WebView/RobustCefClient.cs b/Robust.Client.WebView/Cef/RobustCefClient.cs similarity index 95% rename from Robust.Client.WebView/RobustCefClient.cs rename to Robust.Client.WebView/Cef/RobustCefClient.cs index 623f0f336..ff09f53d8 100644 --- a/Robust.Client.WebView/RobustCefClient.cs +++ b/Robust.Client.WebView/Cef/RobustCefClient.cs @@ -1,6 +1,6 @@ using Xilium.CefGlue; -namespace Robust.Client.WebView +namespace Robust.Client.WebView.Cef { // Simple CEF client. internal class RobustCefClient : CefClient diff --git a/Robust.Client.WebView/RobustLoadHandler.cs b/Robust.Client.WebView/Cef/RobustLoadHandler.cs similarity index 92% rename from Robust.Client.WebView/RobustLoadHandler.cs rename to Robust.Client.WebView/Cef/RobustLoadHandler.cs index a0345b975..aec8c70b1 100644 --- a/Robust.Client.WebView/RobustLoadHandler.cs +++ b/Robust.Client.WebView/Cef/RobustLoadHandler.cs @@ -1,6 +1,6 @@ using Xilium.CefGlue; -namespace Robust.Client.WebView +namespace Robust.Client.WebView.Cef { public sealed class RobustLoadHandler : CefLoadHandler { diff --git a/Robust.Client.WebView/RobustRequestHandler.cs b/Robust.Client.WebView/Cef/RobustRequestHandler.cs similarity index 80% rename from Robust.Client.WebView/RobustRequestHandler.cs rename to Robust.Client.WebView/Cef/RobustRequestHandler.cs index de4d42ad2..4475d4ca0 100644 --- a/Robust.Client.WebView/RobustRequestHandler.cs +++ b/Robust.Client.WebView/Cef/RobustRequestHandler.cs @@ -3,20 +3,20 @@ using System.Collections.Generic; using Robust.Shared.Log; using Xilium.CefGlue; -namespace Robust.Client.WebView +namespace Robust.Client.WebView.Cef { internal sealed class RobustRequestHandler : CefRequestHandler { private readonly ISawmill _sawmill; - private readonly List> _resourceRequestHandlers = new(); - private readonly List> _beforeBrowseHandlers = new(); + private readonly List> _resourceRequestHandlers = new(); + private readonly List> _beforeBrowseHandlers = new(); public RobustRequestHandler(ISawmill sawmill) { _sawmill = sawmill; } - public void AddResourceRequestHandler(Action handler) + public void AddResourceRequestHandler(Action handler) { lock (_resourceRequestHandlers) { @@ -24,7 +24,7 @@ namespace Robust.Client.WebView } } - public void RemoveResourceRequestHandler(Action handler) + public void RemoveResourceRequestHandler(Action handler) { lock (_resourceRequestHandlers) { @@ -32,7 +32,7 @@ namespace Robust.Client.WebView } } - public void AddBeforeBrowseHandler(Action handler) + public void AddBeforeBrowseHandler(Action handler) { lock (_beforeBrowseHandlers) { @@ -40,7 +40,7 @@ namespace Robust.Client.WebView } } - public void RemoveBeforeBrowseHandler(Action handler) + public void RemoveBeforeBrowseHandler(Action handler) { lock (_beforeBrowseHandlers) { @@ -61,7 +61,7 @@ namespace Robust.Client.WebView { _sawmill.Debug($"HANDLING REQUEST: {request.Url}"); - var context = new RequestHandlerContext(isNavigation, isDownload, requestInitiator, request); + var context = new CefRequestHandlerContext(isNavigation, isDownload, requestInitiator, request); foreach (var handler in _resourceRequestHandlers) { @@ -85,7 +85,7 @@ namespace Robust.Client.WebView { lock (_beforeBrowseHandlers) { - var context = new BeforeBrowseContext(isRedirect, userGesture, request); + var context = new CefBeforeBrowseContext(isRedirect, userGesture, request); foreach (var handler in _beforeBrowseHandlers) { diff --git a/Robust.Client.WebView/CefManager.BrowserWindow.cs b/Robust.Client.WebView/Cef/WebViewManagerCef.BrowserWindow.cs similarity index 91% rename from Robust.Client.WebView/CefManager.BrowserWindow.cs rename to Robust.Client.WebView/Cef/WebViewManagerCef.BrowserWindow.cs index 311e3244a..049bf5ac0 100644 --- a/Robust.Client.WebView/CefManager.BrowserWindow.cs +++ b/Robust.Client.WebView/Cef/WebViewManagerCef.BrowserWindow.cs @@ -6,9 +6,9 @@ using Robust.Shared.Log; using Robust.Shared.ViewVariables; using Xilium.CefGlue; -namespace Robust.Client.WebView +namespace Robust.Client.WebView.Cef { - internal partial class CefManager + internal partial class WebViewManagerCef { [Dependency] private readonly IClydeInternal _clyde = default!; @@ -41,11 +41,11 @@ namespace Robust.Client.WebView private sealed class WebViewWindowImpl : IWebViewWindow { - private readonly CefManager _manager; + private readonly WebViewManagerCef _manager; internal CefBrowser Browser = default!; internal RobustRequestHandler RequestHandler = default!; - public Action? OnResourceRequest { get; set; } + public Action? OnResourceRequest { get; set; } [ViewVariables(VVAccess.ReadWrite)] public string Url @@ -72,7 +72,7 @@ namespace Robust.Client.WebView } } - public WebViewWindowImpl(CefManager manager) + public WebViewWindowImpl(WebViewManagerCef manager) { _manager = manager; } @@ -115,12 +115,12 @@ namespace Robust.Client.WebView Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1); } - public void AddResourceRequestHandler(Action handler) + public void AddResourceRequestHandler(Action handler) { RequestHandler.AddResourceRequestHandler(handler); } - public void RemoveResourceRequestHandler(Action handler) + public void RemoveResourceRequestHandler(Action handler) { RequestHandler.RemoveResourceRequestHandler(handler); } diff --git a/Robust.Client.WebView/Cef/WebViewManagerCef.Control.cs b/Robust.Client.WebView/Cef/WebViewManagerCef.Control.cs new file mode 100644 index 000000000..d4f9a0ddf --- /dev/null +++ b/Robust.Client.WebView/Cef/WebViewManagerCef.Control.cs @@ -0,0 +1,580 @@ +using System; +using System.Collections.Generic; +using Robust.Client.Graphics; +using Robust.Client.Input; +using Robust.Client.UserInterface; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Maths; +using Robust.Shared.Utility; +using SixLabors.ImageSharp.PixelFormats; +using Xilium.CefGlue; +using static Robust.Client.WebView.Cef.CefKeyCodes.ChromiumKeyboardCode; +using static Robust.Client.Input.Keyboard; + +namespace Robust.Client.WebView.Cef +{ + internal partial class WebViewManagerCef + { + public IWebViewControlImpl MakeControlImpl(WebViewControl owner) + { + var impl = new ControlImpl(owner); + _dependencyCollection.InjectDependencies(impl); + return impl; + } + + private sealed class ControlImpl : IWebViewControlImpl + { + private static readonly Dictionary KeyMap = new() + { + [Key.A] = VKEY_A, + [Key.B] = VKEY_B, + [Key.C] = VKEY_C, + [Key.D] = VKEY_D, + [Key.E] = VKEY_E, + [Key.F] = VKEY_F, + [Key.G] = VKEY_G, + [Key.H] = VKEY_H, + [Key.I] = VKEY_I, + [Key.J] = VKEY_J, + [Key.K] = VKEY_K, + [Key.L] = VKEY_L, + [Key.M] = VKEY_M, + [Key.N] = VKEY_N, + [Key.O] = VKEY_O, + [Key.P] = VKEY_P, + [Key.Q] = VKEY_Q, + [Key.R] = VKEY_R, + [Key.S] = VKEY_S, + [Key.T] = VKEY_T, + [Key.U] = VKEY_U, + [Key.V] = VKEY_V, + [Key.W] = VKEY_W, + [Key.X] = VKEY_X, + [Key.Y] = VKEY_Y, + [Key.Z] = VKEY_Z, + [Key.Num0] = VKEY_0, + [Key.Num1] = VKEY_1, + [Key.Num2] = VKEY_2, + [Key.Num3] = VKEY_3, + [Key.Num4] = VKEY_4, + [Key.Num5] = VKEY_5, + [Key.Num6] = VKEY_6, + [Key.Num7] = VKEY_7, + [Key.Num8] = VKEY_8, + [Key.Num9] = VKEY_9, + [Key.NumpadNum0] = VKEY_NUMPAD0, + [Key.NumpadNum1] = VKEY_NUMPAD1, + [Key.NumpadNum2] = VKEY_NUMPAD2, + [Key.NumpadNum3] = VKEY_NUMPAD3, + [Key.NumpadNum4] = VKEY_NUMPAD4, + [Key.NumpadNum5] = VKEY_NUMPAD5, + [Key.NumpadNum6] = VKEY_NUMPAD6, + [Key.NumpadNum7] = VKEY_NUMPAD7, + [Key.NumpadNum8] = VKEY_NUMPAD8, + [Key.NumpadNum9] = VKEY_NUMPAD9, + [Key.Escape] = VKEY_ESCAPE, + [Key.Control] = VKEY_CONTROL, + [Key.Shift] = VKEY_SHIFT, + [Key.Alt] = VKEY_MENU, + [Key.LSystem] = VKEY_LWIN, + [Key.RSystem] = VKEY_RWIN, + [Key.LBracket] = VKEY_OEM_4, + [Key.RBracket] = VKEY_OEM_6, + [Key.SemiColon] = VKEY_OEM_1, + [Key.Comma] = VKEY_OEM_COMMA, + [Key.Period] = VKEY_OEM_PERIOD, + [Key.Apostrophe] = VKEY_OEM_7, + [Key.Slash] = VKEY_OEM_2, + [Key.BackSlash] = VKEY_OEM_5, + [Key.Tilde] = VKEY_OEM_3, + [Key.Equal] = VKEY_OEM_PLUS, + [Key.Space] = VKEY_SPACE, + [Key.Return] = VKEY_RETURN, + [Key.BackSpace] = VKEY_BACK, + [Key.Tab] = VKEY_TAB, + [Key.PageUp] = VKEY_PRIOR, + [Key.PageDown] = VKEY_NEXT, + [Key.End] = VKEY_END, + [Key.Home] = VKEY_HOME, + [Key.Insert] = VKEY_INSERT, + [Key.Delete] = VKEY_DELETE, + [Key.Minus] = VKEY_OEM_MINUS, + [Key.NumpadAdd] = VKEY_ADD, + [Key.NumpadSubtract] = VKEY_SUBTRACT, + [Key.NumpadDivide] = VKEY_DIVIDE, + [Key.NumpadMultiply] = VKEY_MULTIPLY, + [Key.NumpadDecimal] = VKEY_DECIMAL, + [Key.Left] = VKEY_LEFT, + [Key.Right] = VKEY_RIGHT, + [Key.Up] = VKEY_UP, + [Key.Down] = VKEY_DOWN, + [Key.F1] = VKEY_F1, + [Key.F2] = VKEY_F2, + [Key.F3] = VKEY_F3, + [Key.F4] = VKEY_F4, + [Key.F5] = VKEY_F5, + [Key.F6] = VKEY_F6, + [Key.F7] = VKEY_F7, + [Key.F8] = VKEY_F8, + [Key.F9] = VKEY_F9, + [Key.F10] = VKEY_F10, + [Key.F11] = VKEY_F11, + [Key.F12] = VKEY_F12, + [Key.F13] = VKEY_F13, + [Key.F14] = VKEY_F14, + [Key.F15] = VKEY_F15, + [Key.Pause] = VKEY_PAUSE, + }; + + [Dependency] private readonly IClyde _clyde = default!; + [Dependency] private readonly IInputManager _inputMgr = default!; + + public readonly WebViewControl Owner; + + public ControlImpl(WebViewControl owner) + { + Owner = owner; + } + + private const int ScrollSpeed = 50; + + private readonly RobustRequestHandler _requestHandler = new(Logger.GetSawmill("root")); + private LiveData? _data; + private string _startUrl = "about:blank"; + + public string Url + { + get => _data == null ? _startUrl : _data.Browser.GetMainFrame().Url; + set + { + if (_data == null) + _startUrl = value; + else + _data.Browser.GetMainFrame().LoadUrl(value); + } + } + + public bool IsLoading => _data?.Browser.IsLoading ?? false; + + public void EnteredTree() + { + DebugTools.AssertNull(_data); + + // A funny render handler that will allow us to render to the control. + var renderer = new ControlRenderHandler(this); + + // A funny web cef client. This can actually be shared by multiple browsers, but I'm not sure how the + // rendering would work in that case? TODO CEF: Investigate a way to share the web client? + var client = new RobustCefClient(renderer, _requestHandler, new RobustLoadHandler()); + + var info = CefWindowInfo.Create(); + + // FUNFACT: If you DO NOT set these below and set info.Width/info.Height instead, you get an external window + // Good to know, huh? Setup is the same, except you can pass a dummy render handler to the CEF client. + info.SetAsWindowless(IntPtr.Zero, false); // TODO CEF: Pass parent handle? + info.WindowlessRenderingEnabled = true; + + var settings = new CefBrowserSettings() + { + WindowlessFrameRate = 60 + }; + + // Create the web browser! And by default, we go to about:blank. + var browser = CefBrowserHost.CreateBrowserSync(info, client, settings, _startUrl); + + var texture = _clyde.CreateBlankTexture(Vector2i.One); + + _data = new LiveData(texture, client, browser, renderer); + } + + public void ExitedTree() + { + DebugTools.AssertNotNull(_data); + + _data!.Texture.Dispose(); + _data.Browser.GetHost().CloseBrowser(true); + _data = null; + } + + public void MouseMove(GUIMouseMoveEventArgs args) + { + if (_data == null) + return; + + // Logger.Debug(); + var modifiers = CalcMouseModifiers(); + var mouseEvent = new CefMouseEvent( + (int)args.RelativePosition.X, (int)args.RelativePosition.Y, + modifiers); + + _data.Browser.GetHost().SendMouseMoveEvent(mouseEvent, false); + } + + public void MouseExited() + { + if (_data == null) + return; + + var modifiers = CalcMouseModifiers(); + + _data.Browser.GetHost().SendMouseMoveEvent(new CefMouseEvent(0, 0, modifiers), true); + } + + public void MouseWheel(GUIMouseWheelEventArgs args) + { + if (_data == null) + return; + + var modifiers = CalcMouseModifiers(); + var mouseEvent = new CefMouseEvent( + (int)args.RelativePosition.X, (int)args.RelativePosition.Y, + modifiers); + + _data.Browser.GetHost().SendMouseWheelEvent( + mouseEvent, + (int)args.Delta.X * ScrollSpeed, + (int)args.Delta.Y * ScrollSpeed); + } + + public bool RawKeyEvent(in GuiRawKeyEvent guiRawEvent) + { + if (_data == null) + return false; + + var host = _data.Browser.GetHost(); + + if (guiRawEvent.Key is Key.MouseLeft or Key.MouseMiddle or Key.MouseRight) + { + var key = guiRawEvent.Key switch + { + Key.MouseLeft => CefMouseButtonType.Left, + Key.MouseMiddle => CefMouseButtonType.Middle, + Key.MouseRight => CefMouseButtonType.Right, + _ => default // not possible + }; + + var mouseEvent = new CefMouseEvent( + guiRawEvent.MouseRelative.X, guiRawEvent.MouseRelative.Y, + CefEventFlags.None); + + // Logger.Debug($"MOUSE: {guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {key}"); + + // TODO: double click support? + host.SendMouseClickEvent(mouseEvent, key, guiRawEvent.Action == RawKeyAction.Up, 1); + } + else + { + // TODO: Handle left/right modifier keys?? + if (!KeyMap.TryGetValue(guiRawEvent.Key, out var vkKey)) + vkKey = default; + + // Logger.Debug($"{guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {vkKey}"); + + var lParam = 0; + lParam |= (guiRawEvent.ScanCode & 0xFF) << 16; + if (guiRawEvent.Action != RawKeyAction.Down) + lParam |= 1 << 30; + + if (guiRawEvent.Action == RawKeyAction.Up) + lParam |= 1 << 31; + + var modifiers = CalcModifiers(guiRawEvent.Key); + + host.SendKeyEvent(new CefKeyEvent + { + // Repeats are sent as key downs, I guess? + EventType = guiRawEvent.Action == RawKeyAction.Up + ? CefKeyEventType.KeyUp + : CefKeyEventType.RawKeyDown, + NativeKeyCode = lParam, + // NativeKeyCode = guiRawEvent.ScanCode, + WindowsKeyCode = (int)vkKey, + IsSystemKey = false, // TODO + Modifiers = modifiers + }); + + if (guiRawEvent.Action != RawKeyAction.Up && guiRawEvent.Key == Key.Return) + { + host.SendKeyEvent(new CefKeyEvent + { + EventType = CefKeyEventType.Char, + WindowsKeyCode = '\r', + NativeKeyCode = lParam, + Modifiers = modifiers + }); + } + } + + return true; + } + + private CefEventFlags CalcModifiers(Key key) + { + CefEventFlags modifiers = default; + + if (_inputMgr.IsKeyDown(Key.Control)) + modifiers |= CefEventFlags.ControlDown; + + if (_inputMgr.IsKeyDown(Key.Alt)) + modifiers |= CefEventFlags.AltDown; + + if (_inputMgr.IsKeyDown(Key.Shift)) + modifiers |= CefEventFlags.ShiftDown; + + if (_inputMgr.IsKeyDown(Key.Shift)) + modifiers |= CefEventFlags.ShiftDown; + + return modifiers; + } + + private CefEventFlags CalcMouseModifiers() + { + CefEventFlags modifiers = default; + + if (_inputMgr.IsKeyDown(Key.Control)) + modifiers |= CefEventFlags.ControlDown; + + if (_inputMgr.IsKeyDown(Key.Alt)) + modifiers |= CefEventFlags.AltDown; + + if (_inputMgr.IsKeyDown(Key.Shift)) + modifiers |= CefEventFlags.ShiftDown; + + if (_inputMgr.IsKeyDown(Key.Shift)) + modifiers |= CefEventFlags.ShiftDown; + + if (_inputMgr.IsKeyDown(Key.MouseLeft)) + modifiers |= CefEventFlags.LeftMouseButton; + + if (_inputMgr.IsKeyDown(Key.MouseMiddle)) + modifiers |= CefEventFlags.MiddleMouseButton; + + if (_inputMgr.IsKeyDown(Key.MouseRight)) + modifiers |= CefEventFlags.RightMouseButton; + + return modifiers; + } + + public void TextEntered(GUITextEventArgs args) + { + if (_data == null) + return; + + var host = _data.Browser.GetHost(); + + Span buf = stackalloc char[2]; + var written = args.AsRune.EncodeToUtf16(buf); + + for (var i = 0; i < written; i++) + { + host.SendKeyEvent(new CefKeyEvent + { + EventType = CefKeyEventType.Char, + WindowsKeyCode = buf[i], + Character = buf[i], + UnmodifiedCharacter = buf[i] + }); + } + } + + public void Resized() + { + if (_data == null) + return; + + _data.Browser.GetHost().NotifyMoveOrResizeStarted(); + _data.Browser.GetHost().WasResized(); + _data.Texture.Dispose(); + _data.Texture = _clyde.CreateBlankTexture((Owner.PixelWidth, Owner.PixelHeight)); + } + + public void Draw(DrawingHandleScreen handle) + { + if (_data == null) + return; + + var bufImg = _data.Renderer.Buffer.Buffer; + + _data.Texture.SetSubImage( + Vector2i.Zero, + bufImg, + new UIBox2i( + 0, 0, + Math.Min(Owner.PixelWidth, bufImg.Width), + Math.Min(Owner.PixelHeight, bufImg.Height))); + + handle.DrawTexture(_data.Texture, Vector2.Zero); + } + + public void StopLoad() + { + if (_data == null) + throw new InvalidOperationException(); + + _data.Browser.StopLoad(); + } + + public void Reload() + { + if (_data == null) + throw new InvalidOperationException(); + + _data.Browser.Reload(); + } + + public bool GoBack() + { + if (_data == null) + throw new InvalidOperationException(); + + if (!_data.Browser.CanGoBack) + return false; + + _data.Browser.GoBack(); + return true; + } + + public bool GoForward() + { + if (_data == null) + throw new InvalidOperationException(); + + if (!_data.Browser.CanGoForward) + return false; + + _data.Browser.GoForward(); + return true; + } + + public void ExecuteJavaScript(string code) + { + if (_data == null) + throw new InvalidOperationException(); + + // TODO: this should not run until the browser is done loading seriously does this even work? + _data.Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1); + } + + public void AddResourceRequestHandler(Action handler) + { + _requestHandler.AddResourceRequestHandler(handler); + } + + public void RemoveResourceRequestHandler(Action handler) + { + _requestHandler.RemoveResourceRequestHandler(handler); + } + + public void AddBeforeBrowseHandler(Action handler) + { + _requestHandler.AddBeforeBrowseHandler(handler); + } + + public void RemoveBeforeBrowseHandler(Action handler) + { + _requestHandler.RemoveBeforeBrowseHandler(handler); + } + + private sealed class LiveData + { + public OwnedTexture Texture; + public readonly RobustCefClient Client; + public readonly CefBrowser Browser; + public readonly ControlRenderHandler Renderer; + + public LiveData( + OwnedTexture texture, + RobustCefClient client, + CefBrowser browser, + ControlRenderHandler renderer) + { + Texture = texture; + Client = client; + Browser = browser; + Renderer = renderer; + } + } + } + + private sealed class ControlRenderHandler : CefRenderHandler + { + public ImageBuffer Buffer { get; } + private ControlImpl _control; + + internal ControlRenderHandler(ControlImpl control) + { + Buffer = new ImageBuffer(); + _control = control; + } + + protected override CefAccessibilityHandler? GetAccessibilityHandler() => null; + + protected override void GetViewRect(CefBrowser browser, out CefRectangle rect) + { + if (_control.Owner.Disposed) + { + rect = new CefRectangle(); + return; + } + + // TODO CEF: Do we need to pass real screen coords? Cause what we do already works... + //var screenCoords = _control.ScreenCoordinates; + //rect = new CefRectangle((int) screenCoords.X, (int) screenCoords.Y, (int)Math.Max(_control.Size.X, 1), (int)Math.Max(_control.Size.Y, 1)); + + // We do the max between size and 1 because it will LITERALLY CRASH WITHOUT AN ERROR otherwise. + rect = new CefRectangle( + 0, 0, + (int)Math.Max(_control.Owner.Size.X, 1), (int)Math.Max(_control.Owner.Size.Y, 1)); + } + + protected override bool GetScreenInfo(CefBrowser browser, CefScreenInfo screenInfo) + { + if (_control.Owner.Disposed) + return false; + + // TODO CEF: Get actual scale factor? + screenInfo.DeviceScaleFactor = 1.0f; + + return true; + } + + protected override void OnPopupSize(CefBrowser browser, CefRectangle rect) + { + if (_control.Owner.Disposed) + return; + } + + protected override void OnPaint(CefBrowser browser, CefPaintElementType type, CefRectangle[] dirtyRects, + IntPtr buffer, int width, int height) + { + if (_control.Owner.Disposed) + return; + + foreach (var dirtyRect in dirtyRects) + { + Buffer.UpdateBuffer(width, height, buffer, dirtyRect); + } + } + + protected override void OnAcceleratedPaint(CefBrowser browser, CefPaintElementType type, + CefRectangle[] dirtyRects, IntPtr sharedHandle) + { + // Unused, but we're forced to implement it so.. NOOP. + } + + protected override void OnScrollOffsetChanged(CefBrowser browser, double x, double y) + { + if (_control.Owner.Disposed) + return; + } + + protected override void OnImeCompositionRangeChanged(CefBrowser browser, CefRange selectedRange, + CefRectangle[] characterBounds) + { + if (_control.Owner.Disposed) + return; + } + } + } +} diff --git a/Robust.Client.WebView/CefManager.cs b/Robust.Client.WebView/Cef/WebViewManagerCef.cs similarity index 74% rename from Robust.Client.WebView/CefManager.cs rename to Robust.Client.WebView/Cef/WebViewManagerCef.cs index fcc40e8e5..75c4e3256 100644 --- a/Robust.Client.WebView/CefManager.cs +++ b/Robust.Client.WebView/Cef/WebViewManagerCef.cs @@ -1,31 +1,21 @@ using System; using System.IO; -using Robust.Client.WebView; -using Robust.Client.WebViewHook; using Robust.Shared.ContentPack; using Robust.Shared.IoC; using Robust.Shared.Log; -using Robust.Shared.Utility; - -// The library we're using right now. -// TODO CEF: Do we want to use something else? We will need to ship it ourselves if so. using Xilium.CefGlue; -[assembly: WebViewManagerImpl(typeof(CefManager))] - -namespace Robust.Client.WebView +namespace Robust.Client.WebView.Cef { - internal partial class CefManager : IWebViewManagerHook, IWebViewManager + internal partial class WebViewManagerCef : IWebViewManagerImpl { private CefApp _app = default!; - private bool _initialized = false; + + [Dependency] private readonly IDependencyCollection _dependencyCollection = default!; public void Initialize() { - IoCManager.RegisterInstance(this); - IoCManager.RegisterInstance(this); - - DebugTools.Assert(!_initialized); + IoCManager.Instance!.InjectDependencies(this, oneOff: true); string subProcessName; if (OperatingSystem.IsWindows()) @@ -52,7 +42,7 @@ namespace Robust.Client.WebView // --------------------------- README -------------------------------------------------- // By the way! You're gonna need the CEF binaries in your client's bin folder. - // More specifically, version cef_binary_95.7.14+g9f72f35+chromium-95.0.4638.69 + // More specifically, version cef_binary_94.4.1+g4b61a8c+chromium-94.0.4606.54 // Windows: https://cef-builds.spotifycdn.com/cef_binary_94.4.1%2Bg4b61a8c%2Bchromium-94.0.4606.54_windows64_minimal.tar.bz2 // Linux: https://cef-builds.spotifycdn.com/cef_binary_94.4.1%2Bg4b61a8c%2Bchromium-94.0.4606.54_linux64_minimal.tar.bz2 // Here's how to get it to work: @@ -68,28 +58,16 @@ namespace Robust.Client.WebView // TODO CEF: After this point, debugging breaks. No, literally. My client crashes but ONLY with the debugger. // I have tried using the DEBUG and RELEASE versions of libcef.so, stripped or non-stripped... // And nothing seemed to work. Odd. - - _initialized = true; - } - - public void CheckInitialized() - { - if (!_initialized) - throw new InvalidOperationException("CefManager has not been initialized!"); } public void Update() { - DebugTools.Assert(_initialized); - // Calling this makes CEF do its work, without using its own update loop. CefRuntime.DoMessageLoopWork(); } public void Shutdown() { - DebugTools.Assert(_initialized); - CefRuntime.Shutdown(); } } diff --git a/Robust.Client.WebView/IBeforeBrowseContext.cs b/Robust.Client.WebView/IBeforeBrowseContext.cs new file mode 100644 index 000000000..c65414403 --- /dev/null +++ b/Robust.Client.WebView/IBeforeBrowseContext.cs @@ -0,0 +1,12 @@ +namespace Robust.Client.WebView +{ + public interface IBeforeBrowseContext + { + string Url { get; } + string Method { get; } + bool IsRedirect { get; } + bool UserGesture { get; } + bool IsCancelled { get; } + void DoCancel(); + } +} diff --git a/Robust.Client.WebView/IRequestHandlerContext.cs b/Robust.Client.WebView/IRequestHandlerContext.cs new file mode 100644 index 000000000..ebfa9f610 --- /dev/null +++ b/Robust.Client.WebView/IRequestHandlerContext.cs @@ -0,0 +1,18 @@ +using System.IO; +using System.Net; + +namespace Robust.Client.WebView +{ + public interface IRequestHandlerContext + { + bool IsNavigation { get; } + bool IsDownload { get; } + string RequestInitiator { get; } + string Url { get; } + string Method { get; } + bool IsHandled { get; } + bool IsCancelled { get; } + void DoCancel(); + void DoRespondStream(Stream stream, string contentType, HttpStatusCode code = HttpStatusCode.OK); + } +} diff --git a/Robust.Client.WebView/IWebViewControl.cs b/Robust.Client.WebView/IWebViewControl.cs index 2010434b3..d856bbefa 100644 --- a/Robust.Client.WebView/IWebViewControl.cs +++ b/Robust.Client.WebView/IWebViewControl.cs @@ -1,4 +1,5 @@ using System; +using Robust.Client.WebView.Cef; namespace Robust.Client.WebView { @@ -42,7 +43,7 @@ namespace Robust.Client.WebView /// JavaScript code. void ExecuteJavaScript(string code); - void AddResourceRequestHandler(Action handler); - void RemoveResourceRequestHandler(Action handler); + void AddResourceRequestHandler(Action handler); + void RemoveResourceRequestHandler(Action handler); } } diff --git a/Robust.Client.WebView/IWebViewControlImpl.cs b/Robust.Client.WebView/IWebViewControlImpl.cs new file mode 100644 index 000000000..308c56953 --- /dev/null +++ b/Robust.Client.WebView/IWebViewControlImpl.cs @@ -0,0 +1,24 @@ +using System; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; + +namespace Robust.Client.WebView +{ + /// + /// Internal swappable implementation of . + /// + internal interface IWebViewControlImpl : IWebViewControl + { + void EnteredTree(); + void ExitedTree(); + void MouseMove(GUIMouseMoveEventArgs args); + void MouseExited(); + void MouseWheel(GUIMouseWheelEventArgs args); + bool RawKeyEvent(in GuiRawKeyEvent guiRawEvent); + void TextEntered(GUITextEventArgs args); + void Resized(); + void Draw(DrawingHandleScreen handle); + void AddBeforeBrowseHandler(Action handler); + void RemoveBeforeBrowseHandler(Action handler); + } +} diff --git a/Robust.Client.WebView/IWebViewManagerImpl.cs b/Robust.Client.WebView/IWebViewManagerImpl.cs new file mode 100644 index 000000000..498f6a4ee --- /dev/null +++ b/Robust.Client.WebView/IWebViewManagerImpl.cs @@ -0,0 +1,12 @@ +using Robust.Client.WebViewHook; + +namespace Robust.Client.WebView +{ + /// + /// Internal implementation of WebViewManager that is switched out by . + /// + internal interface IWebViewManagerImpl : IWebViewManagerInternal, IWebViewManagerHook + { + + } +} diff --git a/Robust.Client.WebView/IWebViewManagerInternal.cs b/Robust.Client.WebView/IWebViewManagerInternal.cs new file mode 100644 index 000000000..e10a7018d --- /dev/null +++ b/Robust.Client.WebView/IWebViewManagerInternal.cs @@ -0,0 +1,7 @@ +namespace Robust.Client.WebView +{ + internal interface IWebViewManagerInternal : IWebViewManager + { + IWebViewControlImpl MakeControlImpl(WebViewControl owner); + } +} diff --git a/Robust.Client.WebView/WebViewControl.cs b/Robust.Client.WebView/WebViewControl.cs index f4603d389..67e2cdf03 100644 --- a/Robust.Client.WebView/WebViewControl.cs +++ b/Robust.Client.WebView/WebViewControl.cs @@ -1,18 +1,9 @@ using System; -using System.Collections.Generic; using Robust.Client.Graphics; -using Robust.Client.Input; using Robust.Client.UserInterface; +using Robust.Client.WebView.Cef; using Robust.Shared.IoC; -using Robust.Shared.Log; -using Robust.Shared.Maths; -using Robust.Shared.Utility; using Robust.Shared.ViewVariables; -using SixLabors.ImageSharp.PixelFormats; -using Xilium.CefGlue; -using static Robust.Client.WebView.CefKeyCodes; -using static Robust.Client.WebView.CefKeyCodes.ChromiumKeyboardCode; -using static Robust.Client.Input.Keyboard; namespace Robust.Client.WebView { @@ -21,132 +12,18 @@ namespace Robust.Client.WebView /// public sealed class WebViewControl : Control, IWebViewControl, IRawInputControl { - private const int ScrollSpeed = 50; + [Dependency] private readonly IWebViewManagerInternal _webViewManager = default!; - [Dependency] private readonly IClyde _clyde = default!; - [Dependency] private readonly IInputManager _inputMgr = default!; - [Dependency] private readonly CefManager _cef = default!; - - private RobustRequestHandler _requestHandler = new RobustRequestHandler(Logger.GetSawmill("root")); - private LiveData? _data; - private string _startUrl = "about:blank"; + private readonly IWebViewControlImpl _controlImpl; [ViewVariables(VVAccess.ReadWrite)] public string Url { - get => _data == null ? _startUrl : _data.Browser.GetMainFrame().Url; - set - { - if (_data == null) - _startUrl = value; - else - _data.Browser.GetMainFrame().LoadUrl(value); - } + get => _controlImpl.Url; + set => _controlImpl.Url = value; } - [ViewVariables] public bool IsLoading => _data?.Browser.IsLoading ?? false; - - private readonly Dictionary _keyMap = new() - { - [Key.A] = VKEY_A, - [Key.B] = VKEY_B, - [Key.C] = VKEY_C, - [Key.D] = VKEY_D, - [Key.E] = VKEY_E, - [Key.F] = VKEY_F, - [Key.G] = VKEY_G, - [Key.H] = VKEY_H, - [Key.I] = VKEY_I, - [Key.J] = VKEY_J, - [Key.K] = VKEY_K, - [Key.L] = VKEY_L, - [Key.M] = VKEY_M, - [Key.N] = VKEY_N, - [Key.O] = VKEY_O, - [Key.P] = VKEY_P, - [Key.Q] = VKEY_Q, - [Key.R] = VKEY_R, - [Key.S] = VKEY_S, - [Key.T] = VKEY_T, - [Key.U] = VKEY_U, - [Key.V] = VKEY_V, - [Key.W] = VKEY_W, - [Key.X] = VKEY_X, - [Key.Y] = VKEY_Y, - [Key.Z] = VKEY_Z, - [Key.Num0] = VKEY_0, - [Key.Num1] = VKEY_1, - [Key.Num2] = VKEY_2, - [Key.Num3] = VKEY_3, - [Key.Num4] = VKEY_4, - [Key.Num5] = VKEY_5, - [Key.Num6] = VKEY_6, - [Key.Num7] = VKEY_7, - [Key.Num8] = VKEY_8, - [Key.Num9] = VKEY_9, - [Key.NumpadNum0] = VKEY_NUMPAD0, - [Key.NumpadNum1] = VKEY_NUMPAD1, - [Key.NumpadNum2] = VKEY_NUMPAD2, - [Key.NumpadNum3] = VKEY_NUMPAD3, - [Key.NumpadNum4] = VKEY_NUMPAD4, - [Key.NumpadNum5] = VKEY_NUMPAD5, - [Key.NumpadNum6] = VKEY_NUMPAD6, - [Key.NumpadNum7] = VKEY_NUMPAD7, - [Key.NumpadNum8] = VKEY_NUMPAD8, - [Key.NumpadNum9] = VKEY_NUMPAD9, - [Key.Escape] = VKEY_ESCAPE, - [Key.Control] = VKEY_CONTROL, - [Key.Shift] = VKEY_SHIFT, - [Key.Alt] = VKEY_MENU, - [Key.LSystem] = VKEY_LWIN, - [Key.RSystem] = VKEY_RWIN, - [Key.LBracket] = VKEY_OEM_4, - [Key.RBracket] = VKEY_OEM_6, - [Key.SemiColon] = VKEY_OEM_1, - [Key.Comma] = VKEY_OEM_COMMA, - [Key.Period] = VKEY_OEM_PERIOD, - [Key.Apostrophe] = VKEY_OEM_7, - [Key.Slash] = VKEY_OEM_2, - [Key.BackSlash] = VKEY_OEM_5, - [Key.Tilde] = VKEY_OEM_3, - [Key.Equal] = VKEY_OEM_PLUS, - [Key.Space] = VKEY_SPACE, - [Key.Return] = VKEY_RETURN, - [Key.BackSpace] = VKEY_BACK, - [Key.Tab] = VKEY_TAB, - [Key.PageUp] = VKEY_PRIOR, - [Key.PageDown] = VKEY_NEXT, - [Key.End] = VKEY_END, - [Key.Home] = VKEY_HOME, - [Key.Insert] = VKEY_INSERT, - [Key.Delete] = VKEY_DELETE, - [Key.Minus] = VKEY_OEM_MINUS, - [Key.NumpadAdd] = VKEY_ADD, - [Key.NumpadSubtract] = VKEY_SUBTRACT, - [Key.NumpadDivide] = VKEY_DIVIDE, - [Key.NumpadMultiply] = VKEY_MULTIPLY, - [Key.NumpadDecimal] = VKEY_DECIMAL, - [Key.Left] = VKEY_LEFT, - [Key.Right] = VKEY_RIGHT, - [Key.Up] = VKEY_UP, - [Key.Down] = VKEY_DOWN, - [Key.F1] = VKEY_F1, - [Key.F2] = VKEY_F2, - [Key.F3] = VKEY_F3, - [Key.F4] = VKEY_F4, - [Key.F5] = VKEY_F5, - [Key.F6] = VKEY_F6, - [Key.F7] = VKEY_F7, - [Key.F8] = VKEY_F8, - [Key.F9] = VKEY_F9, - [Key.F10] = VKEY_F10, - [Key.F11] = VKEY_F11, - [Key.F12] = VKEY_F12, - [Key.F13] = VKEY_F13, - [Key.F14] = VKEY_F14, - [Key.F15] = VKEY_F15, - [Key.Pause] = VKEY_PAUSE, - }; + [ViewVariables] public bool IsLoading => _controlImpl.IsLoading; public WebViewControl() { @@ -155,441 +32,114 @@ namespace Robust.Client.WebView MouseFilter = MouseFilterMode.Stop; IoCManager.InjectDependencies(this); + + _controlImpl = _webViewManager.MakeControlImpl(this); } protected override void EnteredTree() { base.EnteredTree(); - _cef.CheckInitialized(); - - DebugTools.AssertNull(_data); - - // A funny render handler that will allow us to render to the control. - var renderer = new ControlRenderHandler(this); - - // A funny web cef client. This can actually be shared by multiple browsers, but I'm not sure how the - // rendering would work in that case? TODO CEF: Investigate a way to share the web client? - var client = new RobustCefClient(renderer, _requestHandler, new RobustLoadHandler()); - - var info = CefWindowInfo.Create(); - - // FUNFACT: If you DO NOT set these below and set info.Width/info.Height instead, you get an external window - // Good to know, huh? Setup is the same, except you can pass a dummy render handler to the CEF client. - info.SetAsWindowless(IntPtr.Zero, false); // TODO CEF: Pass parent handle? - info.WindowlessRenderingEnabled = true; - - var settings = new CefBrowserSettings() - { - WindowlessFrameRate = 60 - }; - - // Create the web browser! And by default, we go to about:blank. - var browser = CefBrowserHost.CreateBrowserSync(info, client, settings, _startUrl); - - var texture = _clyde.CreateBlankTexture(Vector2i.One); - - _data = new LiveData(texture, client, browser, renderer); + _controlImpl.EnteredTree(); } protected override void ExitedTree() { base.ExitedTree(); - DebugTools.AssertNotNull(_data); - - _data!.Texture.Dispose(); - _data.Browser.GetHost().CloseBrowser(true); - _data = null; + _controlImpl.ExitedTree(); } protected internal override void MouseMove(GUIMouseMoveEventArgs args) { base.MouseMove(args); - if (_data == null) - return; - - // Logger.Debug(); - var modifiers = CalcMouseModifiers(); - var mouseEvent = new CefMouseEvent( - (int) args.RelativePosition.X, (int) args.RelativePosition.Y, - modifiers); - - _data.Browser.GetHost().SendMouseMoveEvent(mouseEvent, false); + _controlImpl.MouseMove(args); } protected internal override void MouseExited() { base.MouseExited(); - if (_data == null) - return; - - var modifiers = CalcMouseModifiers(); - - _data.Browser.GetHost().SendMouseMoveEvent(new CefMouseEvent(0, 0, modifiers), true); + _controlImpl.MouseExited(); } protected internal override void MouseWheel(GUIMouseWheelEventArgs args) { base.MouseWheel(args); - if (_data == null) - return; - - var modifiers = CalcMouseModifiers(); - var mouseEvent = new CefMouseEvent( - (int) args.RelativePosition.X, (int) args.RelativePosition.Y, - modifiers); - - _data.Browser.GetHost().SendMouseWheelEvent( - mouseEvent, - (int) args.Delta.X * ScrollSpeed, - (int) args.Delta.Y * ScrollSpeed); + _controlImpl.MouseWheel(args); } bool IRawInputControl.RawKeyEvent(in GuiRawKeyEvent guiRawEvent) { - if (_data == null) - return false; - - var host = _data.Browser.GetHost(); - - if (guiRawEvent.Key is Key.MouseLeft or Key.MouseMiddle or Key.MouseRight) - { - var key = guiRawEvent.Key switch - { - Key.MouseLeft => CefMouseButtonType.Left, - Key.MouseMiddle => CefMouseButtonType.Middle, - Key.MouseRight => CefMouseButtonType.Right, - _ => default // not possible - }; - - var mouseEvent = new CefMouseEvent( - guiRawEvent.MouseRelative.X, guiRawEvent.MouseRelative.Y, - CefEventFlags.None); - - // Logger.Debug($"MOUSE: {guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {key}"); - - // TODO: double click support? - host.SendMouseClickEvent(mouseEvent, key, guiRawEvent.Action == RawKeyAction.Up, 1); - } - else - { - // TODO: Handle left/right modifier keys?? - if (!_keyMap.TryGetValue(guiRawEvent.Key, out var vkKey)) - vkKey = default; - - // Logger.Debug($"{guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {vkKey}"); - - var lParam = 0; - lParam |= (guiRawEvent.ScanCode & 0xFF) << 16; - if (guiRawEvent.Action != RawKeyAction.Down) - lParam |= 1 << 30; - - if (guiRawEvent.Action == RawKeyAction.Up) - lParam |= 1 << 31; - - var modifiers = CalcModifiers(guiRawEvent.Key); - - host.SendKeyEvent(new CefKeyEvent - { - // Repeats are sent as key downs, I guess? - EventType = guiRawEvent.Action == RawKeyAction.Up - ? CefKeyEventType.KeyUp - : CefKeyEventType.RawKeyDown, - NativeKeyCode = lParam, - // NativeKeyCode = guiRawEvent.ScanCode, - WindowsKeyCode = (int) vkKey, - IsSystemKey = false, // TODO - Modifiers = modifiers - }); - - if (guiRawEvent.Action != RawKeyAction.Up && guiRawEvent.Key == Key.Return) - { - host.SendKeyEvent(new CefKeyEvent - { - EventType = CefKeyEventType.Char, - WindowsKeyCode = '\r', - NativeKeyCode = lParam, - Modifiers = modifiers - }); - } - } - - return true; - } - - private CefEventFlags CalcModifiers(Key key) - { - CefEventFlags modifiers = default; - - if (_inputMgr.IsKeyDown(Key.Control)) - modifiers |= CefEventFlags.ControlDown; - - if (_inputMgr.IsKeyDown(Key.Alt)) - modifiers |= CefEventFlags.AltDown; - - if (_inputMgr.IsKeyDown(Key.Shift)) - modifiers |= CefEventFlags.ShiftDown; - - if (_inputMgr.IsKeyDown(Key.Shift)) - modifiers |= CefEventFlags.ShiftDown; - - return modifiers; - } - - private CefEventFlags CalcMouseModifiers() - { - CefEventFlags modifiers = default; - - if (_inputMgr.IsKeyDown(Key.Control)) - modifiers |= CefEventFlags.ControlDown; - - if (_inputMgr.IsKeyDown(Key.Alt)) - modifiers |= CefEventFlags.AltDown; - - if (_inputMgr.IsKeyDown(Key.Shift)) - modifiers |= CefEventFlags.ShiftDown; - - if (_inputMgr.IsKeyDown(Key.Shift)) - modifiers |= CefEventFlags.ShiftDown; - - if (_inputMgr.IsKeyDown(Key.MouseLeft)) - modifiers |= CefEventFlags.LeftMouseButton; - - if (_inputMgr.IsKeyDown(Key.MouseMiddle)) - modifiers |= CefEventFlags.MiddleMouseButton; - - if (_inputMgr.IsKeyDown(Key.MouseRight)) - modifiers |= CefEventFlags.RightMouseButton; - - return modifiers; + return _controlImpl.RawKeyEvent(guiRawEvent); } protected internal override void TextEntered(GUITextEventArgs args) { base.TextEntered(args); - if (_data == null) - return; - - var host = _data.Browser.GetHost(); - - Span buf = stackalloc char[2]; - var written = args.AsRune.EncodeToUtf16(buf); - - for (var i = 0; i < written; i++) - { - host.SendKeyEvent(new CefKeyEvent - { - EventType = CefKeyEventType.Char, - WindowsKeyCode = buf[i], - Character = buf[i], - UnmodifiedCharacter = buf[i] - }); - } + _controlImpl.TextEntered(args); } protected override void Resized() { base.Resized(); - if (_data == null) - return; - - _data.Browser.GetHost().NotifyMoveOrResizeStarted(); - _data.Browser.GetHost().WasResized(); - _data.Texture.Dispose(); - _data.Texture = _clyde.CreateBlankTexture((PixelWidth, PixelHeight)); + _controlImpl.Resized(); } protected internal override void Draw(DrawingHandleScreen handle) { base.Draw(handle); - if (_data == null) - return; - - var bufImg = _data.Renderer.Buffer.Buffer; - - _data.Texture.SetSubImage( - Vector2i.Zero, - bufImg, - new UIBox2i( - 0, 0, - Math.Min(PixelWidth, bufImg.Width), - Math.Min(PixelHeight, bufImg.Height))); - - handle.DrawTexture(_data.Texture, Vector2.Zero); + _controlImpl.Draw(handle); } public void StopLoad() { - if (_data == null) - throw new InvalidOperationException(); - - _data.Browser.StopLoad(); + _controlImpl.StopLoad(); } public void Reload() { - if (_data == null) - throw new InvalidOperationException(); - - _data.Browser.Reload(); + _controlImpl.Reload(); } public bool GoBack() { - if (_data == null) - throw new InvalidOperationException(); - - if (!_data.Browser.CanGoBack) - return false; - - _data.Browser.GoBack(); - return true; + return _controlImpl.GoBack(); } public bool GoForward() { - if (_data == null) - throw new InvalidOperationException(); - - if (!_data.Browser.CanGoForward) - return false; - - _data.Browser.GoForward(); - return true; + return _controlImpl.GoForward(); } public void ExecuteJavaScript(string code) { - if (_data == null) - throw new InvalidOperationException(); - - // TODO: this should not run until the browser is done loading seriously does this even work? - _data.Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1); + _controlImpl.ExecuteJavaScript(code); } - public void AddResourceRequestHandler(Action handler) + public void AddResourceRequestHandler(Action handler) { - _requestHandler.AddResourceRequestHandler(handler); + _controlImpl.AddResourceRequestHandler(handler); } - public void RemoveResourceRequestHandler(Action handler) + public void RemoveResourceRequestHandler(Action handler) { - _requestHandler.RemoveResourceRequestHandler(handler); + _controlImpl.RemoveResourceRequestHandler(handler); } - public void AddBeforeBrowseHandler(Action handler) + public void AddBeforeBrowseHandler(Action handler) { - _requestHandler.AddBeforeBrowseHandler(handler); + _controlImpl.AddBeforeBrowseHandler(handler); } - public void RemoveBeforeBrowseHandler(Action handler) + public void RemoveBeforeBrowseHandler(Action handler) { - _requestHandler.RemoveBeforeBrowseHandler(handler); - } - - private sealed class LiveData - { - public OwnedTexture Texture; - public readonly RobustCefClient Client; - public readonly CefBrowser Browser; - public readonly ControlRenderHandler Renderer; - - public LiveData( - OwnedTexture texture, - RobustCefClient client, - CefBrowser browser, - ControlRenderHandler renderer) - { - Texture = texture; - Client = client; - Browser = browser; - Renderer = renderer; - } - } - } - - internal class ControlRenderHandler : CefRenderHandler - { - public ImageBuffer Buffer { get; } - private Control _control; - - internal ControlRenderHandler(Control control) - { - Buffer = new ImageBuffer(control); - _control = control; - } - - protected override CefAccessibilityHandler? GetAccessibilityHandler() => null; - - protected override void GetViewRect(CefBrowser browser, out CefRectangle rect) - { - if (_control.Disposed) - { - rect = new CefRectangle(); - return; - } - - // TODO CEF: Do we need to pass real screen coords? Cause what we do already works... - //var screenCoords = _control.ScreenCoordinates; - //rect = new CefRectangle((int) screenCoords.X, (int) screenCoords.Y, (int)Math.Max(_control.Size.X, 1), (int)Math.Max(_control.Size.Y, 1)); - - // We do the max between size and 1 because it will LITERALLY CRASH WITHOUT AN ERROR otherwise. - rect = new CefRectangle(0, 0, (int) Math.Max(_control.Size.X, 1), (int) Math.Max(_control.Size.Y, 1)); - } - - protected override bool GetScreenInfo(CefBrowser browser, CefScreenInfo screenInfo) - { - if (_control.Disposed) - return false; - - // TODO CEF: Get actual scale factor? - screenInfo.DeviceScaleFactor = 1.0f; - - return true; - } - - protected override void OnPopupSize(CefBrowser browser, CefRectangle rect) - { - if (_control.Disposed) - return; - } - - protected override void OnPaint(CefBrowser browser, CefPaintElementType type, CefRectangle[] dirtyRects, - IntPtr buffer, int width, int height) - { - if (_control.Disposed) - return; - - foreach (var dirtyRect in dirtyRects) - { - Buffer.UpdateBuffer(width, height, buffer, dirtyRect); - } - } - - protected override void OnAcceleratedPaint(CefBrowser browser, CefPaintElementType type, - CefRectangle[] dirtyRects, IntPtr sharedHandle) - { - // Unused, but we're forced to implement it so.. NOOP. - } - - protected override void OnScrollOffsetChanged(CefBrowser browser, double x, double y) - { - if (_control.Disposed) - return; - } - - protected override void OnImeCompositionRangeChanged(CefBrowser browser, CefRange selectedRange, - CefRectangle[] characterBounds) - { - if (_control.Disposed) - return; + _controlImpl.RemoveBeforeBrowseHandler(handler); } } } diff --git a/Robust.Client.WebView/WebViewManager.cs b/Robust.Client.WebView/WebViewManager.cs new file mode 100644 index 000000000..799a3d1fa --- /dev/null +++ b/Robust.Client.WebView/WebViewManager.cs @@ -0,0 +1,54 @@ +using Robust.Client.WebView; +using Robust.Client.WebView.Cef; +using Robust.Client.WebViewHook; +using Robust.Shared.IoC; +using Robust.Shared.Utility; + +[assembly: WebViewManagerImpl(typeof(WebViewManager))] + +namespace Robust.Client.WebView +{ + internal sealed class WebViewManager : IWebViewManagerInternal, IWebViewManagerHook + { + private IWebViewManagerImpl? _impl; + + public void Initialize() + { + DebugTools.Assert(_impl == null, "WebViewManager has already been initialized!"); + + IoCManager.RegisterInstance(this); + IoCManager.RegisterInstance(this); + + _impl = new WebViewManagerCef(); + _impl.Initialize(); + } + + public void Update() + { + DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!"); + + _impl!.Update(); + } + + public void Shutdown() + { + DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!"); + + _impl!.Shutdown(); + } + + public IWebViewWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams) + { + DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!"); + + return _impl!.CreateBrowserWindow(createParams); + } + + public IWebViewControlImpl MakeControlImpl(WebViewControl owner) + { + DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!"); + + return _impl!.MakeControlImpl(owner); + } + } +}