Search code examples
c#monogameawesomium

How do i implement Awesomium 1.7.4.2 in a Monogame project?


I'm trying to render render a browser in side my monogame project, for drawing some interface & stuff. I've done this in the past with older versions of awesomium with no problems. But I can't figure out how to properly initialize awesomium in this new version, I get an error no matter how I try to go about it.

As I understand it I need to call WebCore.Run() once, instead of WebCore.Update(), but I get various exceptions from that method.

Here are the steps that I've followed so far:

  1. Install Awesomium 1.7.4.2
  2. Refrenced \1.7.4.2\wrappers\Awesomium.NET\Assemblies\Packed\Awesomium.Core.dll in my project

Here is some of my attempts:

    WebCore.Initialize(new WebConfig());
    WebCore.Run();
    //Error: Starting an update loop on a thread with an existing message loop, is not supported.

    WebCore.Initialized += (sender, e) =>
    {
        WebCore.Run();
    };
    WebCore.Initialize(new WebConfig());
    WebView WebView = WebCore.CreateWebView(500, 400);
    //Error: Starting an update loop on a thread with an existing message loop, is not supported.

    WebCore.Initialize(new WebConfig());
    WebView WebView = WebCore.CreateWebView(500, 400);
    WebView.Source = new Uri("http://www.google.com");
    WebView.DocumentReady += (sender, e) =>
    {
        JSObject js = WebView.CreateGlobalJavascriptObject("w");
    };
    // No errors, but DocumentReady is never fired..

I have also managed to get NullRefrence errors, and if I wait Thread.Sleep(400) before calling WebCore.Run(), it just enters the WebCore.Run() and never completes that line.

How do I set this up? Can't find any examples anywhere. All the examples online still tell you to use Update which is Obsolete.


Solution

  • I have just got this working, you have to create a new thread, then call Run, then listen for an event to be raised by WebCore which will have created a new SynchronizationContext by then. You then want to keep a reference to that context on your main thread...

    Thread awesomiumThread = new System.Threading.Thread(new System.Threading.ThreadStart(() =>
    {
         WebCore.Started += (s, e) => {
             awesomiumContext = SynchronizationContext.Current;
         };
    
         WebCore.Run();
    }));
    
    awesomiumThread.Start();
    
    WebCore.Initialize(new WebConfig() { });
    

    ... you can then call all your WebView methods using that SynchronizationContext ...

    awesomiumContext.Post(state =>
    {
        this.WebView.Source = "http://www.google.com";
    }, null);
    

    I will tidy this up with a future edit, but to get you guys started, here is my component...

    using Awesomium.Core;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace AwesomiumComponent
    {
        public class BasicAwesomiumComponent : DrawableGameComponent
        {
            private Byte[] imageBytes;
            private Rectangle area;
            private Rectangle? newArea;
            private Boolean resizing;
            private SpriteBatch spriteBatch;
            private Texture2D WebViewTexture { get; set; }
            private SynchronizationContext awesomiumContext = null;
            private WebView WebView { get; set; }
            private BitmapSurface Surface { get; set; }
            private MouseState lastMouseState;
            private MouseState currentMouseState;
            private Keys[] lastPressedKeys;
            private Keys[] currentPressedKeys = new Keys[0];
            private static ManualResetEvent awesomiumReady = new ManualResetEvent(false);
    
            public Rectangle Area
            {
                get { return this.area; }
                set
                {
                    this.newArea = value;
                }
            }
    
            public BasicAwesomiumComponent(Game game, Rectangle area)
                : base(game)
            {
                this.area = area;
    
                this.spriteBatch = new SpriteBatch(game.GraphicsDevice);
    
    
                Thread awesomiumThread = new System.Threading.Thread(new System.Threading.ThreadStart(() =>
                {
                    WebCore.Started += (s, e) => {
                        awesomiumContext = SynchronizationContext.Current;
                        awesomiumReady.Set();
                    };
    
                    WebCore.Run();
                }));
    
                awesomiumThread.Start();
    
                WebCore.Initialize(new WebConfig() { });
    
                awesomiumReady.WaitOne();
    
                awesomiumContext.Post(state =>
                {
                    this.WebView = WebCore.CreateWebView(this.area.Width, this.area.Height, WebViewType.Offscreen);
    
                    this.WebView.IsTransparent = true;
                    this.WebView.CreateSurface += (s, e) =>
                    {
                        this.Surface = new BitmapSurface(this.area.Width, this.area.Height);
                        e.Surface = this.Surface;
                    };
                }, null);
            }
    
            public void SetResourceInterceptor(IResourceInterceptor interceptor)
            {
                awesomiumContext.Post(state =>
                {
                    WebCore.ResourceInterceptor = interceptor;
                }, null);
            }
    
            public void Execute(string method, params object[] args)
            {
                string script = string.Format("viewModel.{0}({1})", method, string.Join(",", args.Select(x => "\"" + x.ToString() + "\"")));
                this.WebView.ExecuteJavascript(script);
            }
    
            public void RegisterFunction(string methodName, Action<object, CancelEventArgs> handler)
            {
                // Create and acquire a Global Javascript object.
                // These object persist for the lifetime of the web-view.
                using (JSObject myGlobalObject = this.WebView.CreateGlobalJavascriptObject("game"))
                {
                    // The handler is of type JavascriptMethodEventHandler. Here we define it
                    // using a lambda expression.
    
                    myGlobalObject.Bind(methodName, true, (s, e) =>
                    {
                        handler(s, e);
                        // Provide a response.
                        e.Result = "My response";
                    });
                }
            }
    
            public void Load()
            {
                LoadContent();
            }
    
            protected override void LoadContent()
            {
                if (this.area.IsEmpty)
                {
                    this.area = this.GraphicsDevice.Viewport.Bounds;
                    this.newArea = this.GraphicsDevice.Viewport.Bounds;
                }
                this.WebViewTexture = new Texture2D(this.Game.GraphicsDevice, this.area.Width, this.area.Height, false, SurfaceFormat.Color);
    
                this.imageBytes = new Byte[this.area.Width * 4 * this.area.Height];
            }
    
            public override void Update(GameTime gameTime)
            {
                awesomiumContext.Post(state =>
                {
                    if (this.newArea.HasValue && !this.resizing && gameTime.TotalGameTime.TotalSeconds > 0.10f)
                    {
                        this.area = this.newArea.Value;
                        if (this.area.IsEmpty)
                            this.area = this.GraphicsDevice.Viewport.Bounds;
    
                        this.WebView.Resize(this.area.Width, this.area.Height);
                        this.WebViewTexture = new Texture2D(this.Game.GraphicsDevice, this.area.Width, this.area.Height, false, SurfaceFormat.Color);
                        this.imageBytes = new Byte[this.area.Width * 4 * this.area.Height];
                        this.resizing = true;
    
                        this.newArea = null;
                    }
    
                    lastMouseState = currentMouseState;
                    currentMouseState = Mouse.GetState();
    
                    this.WebView.InjectMouseMove(currentMouseState.X - this.area.X, currentMouseState.Y - this.area.Y);
    
                    if (currentMouseState.LeftButton == ButtonState.Pressed && lastMouseState.LeftButton == ButtonState.Released)
                    {
                        this.WebView.InjectMouseDown(MouseButton.Left);
                    }
                    if (currentMouseState.LeftButton == ButtonState.Released && lastMouseState.LeftButton == ButtonState.Pressed)
                    {
                        this.WebView.InjectMouseUp(MouseButton.Left);
                    }
                    if (currentMouseState.RightButton == ButtonState.Pressed && lastMouseState.RightButton == ButtonState.Released)
                    {
                        this.WebView.InjectMouseDown(MouseButton.Right);
                    }
                    if (currentMouseState.RightButton == ButtonState.Released && lastMouseState.RightButton == ButtonState.Pressed)
                    {
                        this.WebView.InjectMouseUp(MouseButton.Right);
                    }
                    if (currentMouseState.MiddleButton == ButtonState.Pressed && lastMouseState.MiddleButton == ButtonState.Released)
                    {
                        this.WebView.InjectMouseDown(MouseButton.Middle);
                    }
                    if (currentMouseState.MiddleButton == ButtonState.Released && lastMouseState.MiddleButton == ButtonState.Pressed)
                    {
                        this.WebView.InjectMouseUp(MouseButton.Middle);
                    }
    
                    if (currentMouseState.ScrollWheelValue != lastMouseState.ScrollWheelValue)
                    {
                        this.WebView.InjectMouseWheel((currentMouseState.ScrollWheelValue - lastMouseState.ScrollWheelValue), 0);
                    }
    
                    lastPressedKeys = currentPressedKeys;
                    currentPressedKeys = Keyboard.GetState().GetPressedKeys();
    
                    // Key Down
                    foreach (var key in currentPressedKeys)
                    {
                        if (!lastPressedKeys.Contains(key))
                        {
                            this.WebView.InjectKeyboardEvent(new WebKeyboardEvent()
                            {
                                Type = WebKeyboardEventType.KeyDown,
                                VirtualKeyCode = (VirtualKey)(int)key,
                                NativeKeyCode = (int)key
                            });
    
                            if ((int)key >= 65 && (int)key <= 90)
                            {
                                this.WebView.InjectKeyboardEvent(new WebKeyboardEvent()
                                {
                                    Type = WebKeyboardEventType.Char,
                                    Text = key.ToString().ToLower()
                                });
                            }
                            else if (key == Keys.Space)
                            {
                                this.WebView.InjectKeyboardEvent(new WebKeyboardEvent()
                                {
                                    Type = WebKeyboardEventType.Char,
                                    Text = " "
                                });
                            }
                        }
                    }
    
                    // Key Up
                    foreach (var key in lastPressedKeys)
                    {
                        if (!currentPressedKeys.Contains(key))
                        {
                            this.WebView.InjectKeyboardEvent(new WebKeyboardEvent()
                            {
                                Type = WebKeyboardEventType.KeyUp,
                                VirtualKeyCode = (VirtualKey)(int)key,
                                NativeKeyCode = (int)key
                            });
                        }
                    }
    
                }, null);
    
                base.Update(gameTime);
            }
    
            public override void Draw(GameTime gameTime)
            {
                awesomiumContext.Post(state =>
                {
                    if (Surface != null && Surface.IsDirty && !resizing)
                    {
                        unsafe
                        {
                            // This part saves us from double copying everything.
                            fixed (Byte* imagePtr = this.imageBytes)
                            {
                                Surface.CopyTo((IntPtr)imagePtr, Surface.Width * 4, 4, true, false);
                            }
                        }
                        this.WebViewTexture.SetData(this.imageBytes);
                    }
                }, null);
    
                Vector2 pos = new Vector2(0, 0);
                spriteBatch.Begin();
                spriteBatch.Draw(this.WebViewTexture, pos, Color.White);
                spriteBatch.End();
                GraphicsDevice.Textures[0] = null;
    
                base.Draw(gameTime);
            }
    
            public Uri Source
            {
                get
                {
                    return this.WebView.Source;
                }
                set
                {
                    awesomiumContext.Post(state =>
                    {
                        this.WebView.Source = value;
                    }, null);
                }
            }
    
            public void Resize(int width, int height)
            {
                this.newArea = new Rectangle(0, 0, width, height); ;
            }
        }
    }