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