using ElectronNET.API.Entities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace ElectronNET.API
{
///
///
///
public sealed class WindowManager
{
private static WindowManager _windowManager;
private static readonly object _syncRoot = new();
internal WindowManager() { }
internal static WindowManager Instance
{
get
{
if (_windowManager == null)
{
lock (_syncRoot)
{
if (_windowManager == null)
{
_windowManager = new WindowManager();
}
}
}
return _windowManager;
}
}
///
/// Quit when all windows are closed. (Default is true)
///
///
/// true if [quit window all closed]; otherwise, false.
///
public bool IsQuitOnWindowAllClosed
{
get { return _isQuitOnWindowAllClosed; }
set
{
BridgeConnector.Emit("quit-app-window-all-closed-event", value);
_isQuitOnWindowAllClosed = value;
}
}
private bool _isQuitOnWindowAllClosed = true;
///
/// Gets the browser windows.
///
///
/// The browser windows.
///
public IReadOnlyCollection BrowserWindows { get { return _browserWindows.Values.ToList().AsReadOnly(); } }
///
/// Get a browser window using the ID
///
/// The id of the browser window
/// The window, if any
/// True if it found the window
public bool TryGetBrowserWindows(int id, out BrowserWindow window) => _browserWindows.TryGetValue(id, out window);
private readonly ConcurrentDictionary _browserWindows = new ();
///
/// Gets the browser views.
///
///
/// The browser view.
///
public IReadOnlyCollection BrowserViews { get { return _browserViews.Values.ToList().AsReadOnly(); } }
private readonly ConcurrentDictionary _browserViews = new ();
///
/// Get a browser view using the ID
///
/// The id of the browser view
/// The view, if any
/// True if it found the view
public bool TryGetBrowserViews(int id, out BrowserView view) => _browserViews.TryGetValue(id, out view);
///
/// Creates the window asynchronous.
///
/// The load URL.
///
public async Task CreateWindowAsync(string loadUrl = "http://localhost")
{
return await CreateWindowAsync(new BrowserWindowOptions(), loadUrl);
}
///
/// Creates the window asynchronous.
///
/// The options.
/// The load URL.
///
public async Task CreateWindowAsync(BrowserWindowOptions options, string loadUrl = "http://localhost")
{
BootstrapUpdateOpenIDsEvent();
var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var guid = Guid.NewGuid().ToString();
BridgeConnector.Once("BrowserWindowCreated" + guid, (id) =>
{
var browserWindow = new BrowserWindow(id);
_browserWindows[id] = browserWindow;
taskCompletionSource.SetResult(browserWindow);
});
if (string.Equals(loadUrl, "HTTP://LOCALHOST", StringComparison.InvariantCultureIgnoreCase))
{
loadUrl = $"{loadUrl}:{BridgeSettings.WebPort}";
}
// Workaround Windows 10 / Electron Bug
// https://github.com/electron/electron/issues/4045
if (IsWindows10())
{
options.Width += 14;
options.Height += 7;
}
if (options.X == -1 && options.Y == -1)
{
options.X = 0; //This is manually removed by the browserWindows.js code before creating the window
options.Y = 0;
BridgeConnector.Emit("createBrowserWindow", guid, JObject.FromObject(options, _keepDefaultValuesSerializer), loadUrl);
}
else
{
// Workaround Windows 10 / Electron Bug
// https://github.com/electron/electron/issues/4045
if (IsWindows10())
{
options.X -= 7;
}
BridgeConnector.Emit("createBrowserWindow", guid, JObject.FromObject(options, _keepDefaultValuesSerializer), loadUrl);
}
return await taskCompletionSource.Task;
}
private bool _hasClosedEvent = false;
private readonly object _hasClosedEventLock = new();
private void BootstrapUpdateOpenIDsEvent()
{
if (!_hasClosedEvent)
{
lock(_hasClosedEventLock)
{
if(!_hasClosedEvent)
{
BridgeConnector.On("BrowserWindowUpdateOpenIDs", (browserWindowIdsStillOpen) =>
{
if (browserWindowIdsStillOpen.Any())
{
foreach (var id in _browserWindows.Keys.ToArray())
{
if (!browserWindowIdsStillOpen.Contains(id)) _browserWindows.TryRemove(id, out _);
}
}
else
{
_browserWindows.Clear();
}
});
_hasClosedEvent = true;
}
}
}
}
private bool IsWindows10()
{
return OperatingSystem.IsWindowsVersionAtLeast(10);
}
///
/// A BrowserView can be used to embed additional web content into a BrowserWindow.
/// It is like a child window, except that it is positioned relative to its owning window.
/// It is meant to be an alternative to the webview tag.
///
///
public Task CreateBrowserViewAsync()
{
return CreateBrowserViewAsync(new BrowserViewConstructorOptions());
}
///
/// A BrowserView can be used to embed additional web content into a BrowserWindow.
/// It is like a child window, except that it is positioned relative to its owning window.
/// It is meant to be an alternative to the webview tag.
///
///
///
public async Task CreateBrowserViewAsync(BrowserViewConstructorOptions options)
{
var guid = Guid.NewGuid().ToString();
var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
BridgeConnector.Once("BrowserViewCreated" + guid, (id) =>
{
var browserView = new BrowserView(id);
_browserViews[id] = browserView;
taskCompletionSource.SetResult(browserView);
});
BridgeConnector.Emit("createBrowserView", guid, JObject.FromObject(options, _keepDefaultValuesSerializer));
return await taskCompletionSource.Task;
}
///
/// Destroy all windows.
///
/// Number of windows destroyed
public async Task DestroyAllWindows()
{
var destroyed = await BridgeConnector.OnResult("browserWindowDestroyAll", "browserWindowDestroyAll-completed");
_browserViews.Clear();
_browserWindows.Clear();
return destroyed;
}
private static readonly JsonSerializer _keepDefaultValuesSerializer = new()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore
};
}
}