implement Demo App sections: Dialogs, Menu, Tray, Shell, CrashHang, Notification, Shortcuts etc. Fix some API bugs and implement GlobalShortcut-, Shell- and WebContents-API.

This commit is contained in:
Gregor Biswanger
2017-10-21 04:37:01 +02:00
parent 9046f6ca25
commit 248ddde82b
61 changed files with 2505 additions and 57 deletions

View File

@@ -156,7 +156,7 @@ namespace ElectronNET.API
{
if (_unresponsive == null)
{
BridgeConnector.Socket.On("browserWindow-unresponsive", () =>
BridgeConnector.Socket.On("browserWindow-unresponsive" + Id, () =>
{
_unresponsive();
});
@@ -182,7 +182,7 @@ namespace ElectronNET.API
{
if (_responsive == null)
{
BridgeConnector.Socket.On("browserWindow-responsive", () =>
BridgeConnector.Socket.On("browserWindow-responsive" + Id, () =>
{
_responsive();
});
@@ -807,6 +807,7 @@ namespace ElectronNET.API
internal BrowserWindow(int id) {
Id = id;
WebContents = new WebContents(id);
}
/// <summary>
@@ -2148,6 +2149,11 @@ namespace ElectronNET.API
BridgeConnector.Socket.Emit("browserWindowSetVibrancy", Id, type.GetDescription());
}
/// <summary>
/// Render and control web pages.
/// </summary>
public WebContents WebContents { get; internal set; }
private JsonSerializer _jsonSerializer = new JsonSerializer()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),

View File

@@ -36,5 +36,15 @@
/// Add icons and context menus to the systems notification area.
/// </summary>
public static Tray Tray { get { return Tray.Instance; } }
/// <summary>
/// Detect keyboard events when the application does not have keyboard focus.
/// </summary>
public static GlobalShortcut GlobalShortcut { get { return GlobalShortcut.Instance; } }
/// <summary>
/// Manage files and URLs using their default applications.
/// </summary>
public static Shell Shell { get { return Shell.Instance; } }
}
}

View File

@@ -0,0 +1,15 @@
namespace ElectronNET.API.Entities
{
/// <summary>
/// Opens the devtools with specified dock state, can be right, bottom, undocked,
/// detach.Defaults to last used dock state.In undocked mode it's possible to dock
/// back.In detach mode it's not.
/// </summary>
public enum DevToolsMode
{
right,
bottom,
undocked,
detach
}
}

View File

@@ -0,0 +1,7 @@
namespace ElectronNET.API.Entities
{
public class Error
{
public string Stack { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace ElectronNET.API.Entities
{
public class OpenDevToolsOptions
{
/// <summary>
/// Opens the devtools with specified dock state, can be right, bottom, undocked,
/// detach.Defaults to last used dock state.In undocked mode it's possible to dock
/// back.In detach mode it's not.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public DevToolsMode Mode { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using System.ComponentModel;
namespace ElectronNET.API.Entities
{
public class OpenExternalOptions
{
/// <summary>
/// true to bring the opened application to the foreground. The default is true.
/// </summary>
[DefaultValue(true)]
public bool Activate { get; set; } = true;
}
}

View File

@@ -0,0 +1,41 @@
namespace ElectronNET.API
{
public class ShortcutDetails
{
/// <summary>
/// The Application User Model ID. Default is empty.
/// </summary>
public string AppUserModelId { get; set; }
/// <summary>
/// The arguments to be applied to target when launching from this shortcut. Default is empty.
/// </summary>
public string Args { get; set; }
/// <summary>
/// The working directory. Default is empty.
/// </summary>
public string Cwd { get; set; }
/// <summary>
/// The description of the shortcut. Default is empty.
/// </summary>
public string Description { get; set; }
/// <summary>
/// The path to the icon, can be a DLL or EXE. icon and iconIndex have to be set
/// together.Default is empty, which uses the target's icon.
/// </summary>
public string Icon { get; set; }
/// <summary>
/// The resource ID of icon when icon is a DLL or EXE. Default is 0.
/// </summary>
public int IconIndex { get; set; }
/// <summary>
/// The target to launch from this shortcut.
/// </summary>
public string Target { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
namespace ElectronNET.API
{
public enum ShortcutLinkOperation
{
/// <summary>
/// Creates a new shortcut, overwriting if necessary.
/// </summary>
create,
/// <summary>
/// Updates specified properties only on an existing shortcut.
/// </summary>
update,
/// <summary>
/// Overwrites an existing shortcut, fails if the shortcut doesnt exist.
/// </summary>
replace
}
}

View File

@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ElectronNET.API
{
/// <summary>
/// Detect keyboard events when the application does not have keyboard focus.
/// </summary>
public sealed class GlobalShortcut
{
private static GlobalShortcut _globalShortcut;
internal GlobalShortcut() { }
internal static GlobalShortcut Instance
{
get
{
if (_globalShortcut == null)
{
_globalShortcut = new GlobalShortcut();
}
return _globalShortcut;
}
}
private Dictionary<string, Action> _shortcuts = new Dictionary<string, Action>();
/// <summary>
/// Registers a global shortcut of accelerator.
/// The callback is called when the registered shortcut is pressed by the user.
///
/// When the accelerator is already taken by other applications, this call will
/// silently fail.This behavior is intended by operating systems, since they dont
/// want applications to fight for global shortcuts.
/// </summary>
public void Register(string accelerator, Action function)
{
if (!_shortcuts.ContainsKey(accelerator))
{
_shortcuts.Add(accelerator, function);
BridgeConnector.Socket.Off("globalShortcut-pressed");
BridgeConnector.Socket.On("globalShortcut-pressed", (shortcut) =>
{
if (_shortcuts.ContainsKey(shortcut.ToString()))
{
_shortcuts[shortcut.ToString()]();
}
});
BridgeConnector.Socket.Emit("globalShortcut-register", accelerator);
}
}
/// <summary>
/// When the accelerator is already taken by other applications,
/// this call will still return false. This behavior is intended by operating systems,
/// since they dont want applications to fight for global shortcuts.
/// </summary>
/// <returns>Whether this application has registered accelerator.</returns>
public Task<bool> IsRegisteredAsync(string accelerator)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
BridgeConnector.Socket.On("globalShortcut-isRegisteredCompleted", (isRegistered) =>
{
BridgeConnector.Socket.Off("globalShortcut-isRegisteredCompleted");
taskCompletionSource.SetResult((bool)isRegistered);
});
BridgeConnector.Socket.Emit("globalShortcut-isRegistered", accelerator);
return taskCompletionSource.Task;
}
/// <summary>
/// Unregisters the global shortcut of accelerator.
/// </summary>
public void Unregister(string accelerator)
{
_shortcuts.Remove(accelerator);
BridgeConnector.Socket.Emit("globalShortcut-unregister", accelerator);
}
/// <summary>
/// Unregisters all of the global shortcuts.
/// </summary>
public void UnregisterAll()
{
_shortcuts.Clear();
BridgeConnector.Socket.Emit("globalShortcut-unregisterAll");
}
}
}

View File

@@ -39,6 +39,25 @@ namespace ElectronNET.API
BridgeConnector.Socket.On(channel, listener);
}
/// <summary>
/// Send a message to the renderer process synchronously via channel,
/// you can also send arbitrary arguments.
///
/// Note: Sending a synchronous message will block the whole renderer process,
/// unless you know what you are doing you should never use it.
/// </summary>
/// <param name="channel"></param>
/// <param name="listener"></param>
public void OnSync(string channel, Func<object, object> listener)
{
BridgeConnector.Socket.Emit("registerSyncIpcMainChannel", channel);
BridgeConnector.Socket.On(channel, (args) => {
var result = listener(args);
BridgeConnector.Socket.Emit(channel + "Sync", result);
});
}
/// <summary>
/// Adds a one time listener method for the event. This listener is invoked only
/// the next time a message is sent to channel, after which it is removed.

View File

@@ -4,6 +4,9 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Collections.Generic;
using ElectronNET.API.Extensions;
using System.Linq;
using System.Collections.ObjectModel;
using System;
namespace ElectronNET.API
{
@@ -26,22 +29,55 @@ namespace ElectronNET.API
}
}
public IReadOnlyCollection<MenuItem> Items { get { return _items.AsReadOnly(); } }
private List<MenuItem> _items = new List<MenuItem>();
public IReadOnlyCollection<MenuItem> MenuItems { get { return _menuItems.AsReadOnly(); } }
private List<MenuItem> _menuItems = new List<MenuItem>();
public void SetApplicationMenu(MenuItem[] menuItems)
{
_menuItems.Clear();
menuItems.AddMenuItemsId();
BridgeConnector.Socket.Emit("menu-setApplicationMenu", JArray.FromObject(menuItems, _jsonSerializer));
_items.AddRange(menuItems);
_menuItems.AddRange(menuItems);
BridgeConnector.Socket.Off("menuItemClicked");
BridgeConnector.Socket.On("menuItemClicked", (id) => {
MenuItem menuItem = _items.GetMenuItem(id.ToString());
menuItem?.Click();
MenuItem menuItem = _menuItems.GetMenuItem(id.ToString());
menuItem.Click?.Invoke();
});
}
public IReadOnlyDictionary<int, ReadOnlyCollection<MenuItem>> ContextMenuItems { get; internal set; }
private Dictionary<int, List<MenuItem>> _contextMenuItems = new Dictionary<int, List<MenuItem>>();
public void SetContextMenu(BrowserWindow browserWindow, MenuItem[] menuItems)
{
if (!_contextMenuItems.ContainsKey(browserWindow.Id))
{
menuItems.AddMenuItemsId();
BridgeConnector.Socket.Emit("menu-setContextMenu", browserWindow.Id, JArray.FromObject(menuItems, _jsonSerializer));
_contextMenuItems.Add(browserWindow.Id, menuItems.ToList());
var x = _contextMenuItems.ToDictionary(kv => kv.Key, kv => kv.Value.AsReadOnly());
ContextMenuItems = new ReadOnlyDictionary<int, ReadOnlyCollection<MenuItem>>(x);
BridgeConnector.Socket.Off("contextMenuItemClicked");
BridgeConnector.Socket.On("contextMenuItemClicked", (results) =>
{
var id = ((JArray)results).First.ToString();
var browserWindowId = (int)((JArray)results).Last;
MenuItem menuItem = _contextMenuItems[browserWindowId].GetMenuItem(id);
menuItem.Click?.Invoke();
});
}
}
public void ContextMenuPopup(BrowserWindow browserWindow)
{
BridgeConnector.Socket.Emit("menu-contextMenuPopup", browserWindow.Id);
}
private JsonSerializer _jsonSerializer = new JsonSerializer()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),

244
ElectronNET.API/Shell.cs Normal file
View File

@@ -0,0 +1,244 @@
using ElectronNET.API.Entities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ElectronNET.API
{
/// <summary>
/// Manage files and URLs using their default applications.
/// </summary>
public sealed class Shell
{
private static Shell _shell;
internal Shell() { }
internal static Shell Instance
{
get
{
if (_shell == null)
{
_shell = new Shell();
}
return _shell;
}
}
/// <summary>
/// Show the given file in a file manager. If possible, select the file.
/// </summary>
/// <param name="fullPath"></param>
/// <returns>Whether the item was successfully shown.</returns>
public Task<bool> ShowItemInFolderAsync(string fullPath)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
BridgeConnector.Socket.On("shell-showItemInFolderCompleted", (success) =>
{
BridgeConnector.Socket.Off("shell-showItemInFolderCompleted");
taskCompletionSource.SetResult((bool)success);
});
BridgeConnector.Socket.Emit("shell-showItemInFolder", fullPath);
return taskCompletionSource.Task;
}
/// <summary>
/// Open the given file in the desktops default manner.
/// </summary>
/// <param name="fullPath"></param>
/// <returns>Whether the item was successfully opened.</returns>
public Task<bool> OpenItemAsync(string fullPath)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
BridgeConnector.Socket.On("shell-openItemCompleted", (success) =>
{
BridgeConnector.Socket.Off("shell-openItemCompleted");
taskCompletionSource.SetResult((bool)success);
});
BridgeConnector.Socket.Emit("shell-openItem", fullPath);
return taskCompletionSource.Task;
}
/// <summary>
/// Open the given external protocol URL in the desktops default manner.
/// (For example, mailto: URLs in the users default mail agent).
/// </summary>
/// <param name="url"></param>
/// <returns>Whether an application was available to open the URL.
/// If callback is specified, always returns true.</returns>
public Task<bool> OpenExternalAsync(string url)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
BridgeConnector.Socket.On("shell-openExternalCompleted", (success) =>
{
BridgeConnector.Socket.Off("shell-openExternalCompleted");
taskCompletionSource.SetResult((bool)success);
});
BridgeConnector.Socket.Emit("shell-openExternal", url);
return taskCompletionSource.Task;
}
/// <summary>
/// Open the given external protocol URL in the desktops default manner.
/// (For example, mailto: URLs in the users default mail agent).
/// </summary>
/// <param name="url"></param>
/// <param name="options">macOS only</param>
/// <returns>Whether an application was available to open the URL.
/// If callback is specified, always returns true.</returns>
public Task<bool> OpenExternalAsync(string url, OpenExternalOptions options)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
BridgeConnector.Socket.On("shell-openExternalCompleted", (success) =>
{
BridgeConnector.Socket.Off("shell-openExternalCompleted");
taskCompletionSource.SetResult((bool)success);
});
BridgeConnector.Socket.Emit("shell-openExternal", url, JObject.FromObject(options, _jsonSerializer));
return taskCompletionSource.Task;
}
/// <summary>
/// Open the given external protocol URL in the desktops default manner.
/// (For example, mailto: URLs in the users default mail agent).
/// </summary>
/// <param name="url"></param>
/// <param name="options">macOS only</param>
/// <param name="action">macOS only</param>
/// <returns>Whether an application was available to open the URL.
/// If callback is specified, always returns true.</returns>
public Task<bool> OpenExternalAsync(string url, OpenExternalOptions options, Action<Error> action)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
BridgeConnector.Socket.On("shell-openExternalCompleted", (success) =>
{
BridgeConnector.Socket.Off("shell-openExternalCompleted");
taskCompletionSource.SetResult((bool)success);
});
BridgeConnector.Socket.Off("shell-openExternalCallback");
BridgeConnector.Socket.On("shell-openExternalCallback", (args) => {
var urlKey = ((JArray)args).First.ToString();
var error = ((JArray)args).Last.ToObject<Error>();
if(_openExternalCallbacks.ContainsKey(urlKey))
{
_openExternalCallbacks[urlKey](error);
}
});
_openExternalCallbacks.Add(url, action);
BridgeConnector.Socket.Emit("shell-openExternal", url, JObject.FromObject(options, _jsonSerializer), true);
return taskCompletionSource.Task;
}
private Dictionary<string, Action<Error>> _openExternalCallbacks = new Dictionary<string, Action<Error>>();
/// <summary>
/// Move the given file to trash and returns a boolean status for the operation.
/// </summary>
/// <param name="fullPath"></param>
/// <returns> Whether the item was successfully moved to the trash.</returns>
public Task<bool> MoveItemToTrashAsync(string fullPath)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
BridgeConnector.Socket.On("shell-moveItemToTrashCompleted", (success) =>
{
BridgeConnector.Socket.Off("shell-moveItemToTrashCompleted");
taskCompletionSource.SetResult((bool)success);
});
BridgeConnector.Socket.Emit("shell-moveItemToTrash", fullPath);
return taskCompletionSource.Task;
}
/// <summary>
/// Play the beep sound.
/// </summary>
public void Beep()
{
BridgeConnector.Socket.Emit("shell-beep");
}
/// <summary>
/// Creates or updates a shortcut link at shortcutPath.
/// </summary>
/// <param name="shortcutPath"></param>
/// <param name="operation"></param>
/// <param name="options"></param>
/// <returns>Whether the shortcut was created successfully.</returns>
public Task<bool> WriteShortcutLinkAsync(string shortcutPath, ShortcutLinkOperation operation, ShortcutDetails options)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
BridgeConnector.Socket.On("shell-writeShortcutLinkCompleted", (success) =>
{
BridgeConnector.Socket.Off("shell-writeShortcutLinkCompleted");
taskCompletionSource.SetResult((bool)success);
});
BridgeConnector.Socket.Emit("shell-writeShortcutLink", shortcutPath, operation.ToString(), JObject.FromObject(options, _jsonSerializer));
return taskCompletionSource.Task;
}
/// <summary>
/// Resolves the shortcut link at shortcutPath.
///
/// An exception will be thrown when any error happens.
/// </summary>
/// <param name="shortcutPath"></param>
/// <returns></returns>
public Task<ShortcutDetails> ReadShortcutLinkAsync(string shortcutPath)
{
var taskCompletionSource = new TaskCompletionSource<ShortcutDetails>();
BridgeConnector.Socket.On("shell-readShortcutLinkCompleted", (shortcutDetails) =>
{
BridgeConnector.Socket.Off("shell-readShortcutLinkCompleted");
taskCompletionSource.SetResult((ShortcutDetails)shortcutDetails);
});
BridgeConnector.Socket.Emit("shell-readShortcutLink", shortcutPath);
return taskCompletionSource.Task;
}
private JsonSerializer _jsonSerializer = new JsonSerializer()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore
};
}
}

View File

@@ -199,6 +199,11 @@ namespace ElectronNET.API
public IReadOnlyCollection<MenuItem> Items { get { return _items.AsReadOnly(); } }
private List<MenuItem> _items = new List<MenuItem>();
public void Show(string image, MenuItem menuItem)
{
Show(image, new MenuItem[] { menuItem });
}
public void Show(string image, MenuItem[] menuItems)
{
menuItems.AddMenuItemsId();
@@ -218,6 +223,7 @@ namespace ElectronNET.API
public void Destroy()
{
BridgeConnector.Socket.Emit("tray-destroy");
_items.Clear();
}
/// <summary>

View File

@@ -0,0 +1,99 @@
using ElectronNET.API.Entities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Diagnostics;
namespace ElectronNET.API
{
/// <summary>
/// Render and control web pages.
/// </summary>
public class WebContents
{
public int Id { get; private set; }
/// <summary>
/// Emitted when the renderer process crashes or is killed.
/// </summary>
public event Action<bool> OnCrashed
{
add
{
if (_crashed == null)
{
BridgeConnector.Socket.On("webContents-crashed" + Id, (killed) =>
{
_crashed((bool)killed);
});
BridgeConnector.Socket.Emit("register-webContents-crashed", Id);
}
_crashed += value;
}
remove
{
_crashed -= value;
}
}
private event Action<bool> _crashed;
/// <summary>
/// Emitted when the navigation is done, i.e. the spinner of the tab has
/// stopped spinning, and the onload event was dispatched.
/// </summary>
public event Action OnDidFinishLoad
{
add
{
if (_didFinishLoad == null)
{
BridgeConnector.Socket.On("webContents-didFinishLoad" + Id, () =>
{
_didFinishLoad();
});
BridgeConnector.Socket.Emit("register-webContents-didFinishLoad", Id);
}
_didFinishLoad += value;
}
remove
{
_didFinishLoad -= value;
}
}
private event Action _didFinishLoad;
internal WebContents(int id)
{
Id = id;
}
/// <summary>
/// Opens the devtools.
/// </summary>
public void OpenDevTools()
{
BridgeConnector.Socket.Emit("webContentsOpenDevTools", Id);
}
/// <summary>
/// Opens the devtools.
/// </summary>
/// <param name="openDevToolsOptions"></param>
public void OpenDevTools(OpenDevToolsOptions openDevToolsOptions)
{
BridgeConnector.Socket.Emit("webContentsOpenDevTools", Id, JObject.FromObject(openDevToolsOptions, _jsonSerializer));
}
private JsonSerializer _jsonSerializer = new JsonSerializer()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore
};
}
}