mirror of
https://github.com/ElectronNET/Electron.NET.git
synced 2026-09-22 23:15:25 +00:00
Compare commits
16 Commits
0.5.2-pre.
...
0.5.2-pre.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
195a29052e | ||
|
|
f577a53227 | ||
|
|
382d544468 | ||
|
|
18f08f9aff | ||
|
|
def289ab54 | ||
|
|
4011f3d30b | ||
|
|
3b57de976a | ||
|
|
d9e9b3d2e6 | ||
|
|
e5ccf75c9f | ||
|
|
819e643ab8 | ||
|
|
7fe3f9b5b6 | ||
|
|
16cbc5f2a4 | ||
|
|
3a2c997e6a | ||
|
|
f88f5f8b18 | ||
|
|
4e39f5f377 | ||
|
|
7fa5ec5b0b |
@@ -3,7 +3,13 @@
|
||||
## ElectronNET.Core
|
||||
|
||||
- Fixed token param being appended to external URLs (#1075)
|
||||
- Fixed startup mode discriminator in case of existing `wwwroot` (#1050)
|
||||
- Fixed CA1416 on ASP.NET project (#1091) @epsnm
|
||||
- Fixed loading issue in plain Visual Studio (#1084) @epsnm
|
||||
- Fixed bloated ASAR file (#1080) @epsnm
|
||||
- Improved selection of runtime identifier (#1081) @epsnm
|
||||
- Added more variants for `UseElectron` (#1076) @AeonSake
|
||||
- Added support for modern MacOS app icon (#1047)
|
||||
|
||||
# 0.5.1
|
||||
|
||||
|
||||
@@ -70,6 +70,12 @@ Since electron builder still expects a `package.json` file to exist, ElectronNET
|
||||
}
|
||||
```
|
||||
|
||||
### App Icon Path
|
||||
|
||||
The `ElectronIcon` property supports classic icon files (such as `.ico` and `.icns`) and modern macOS `.icon` app icon packages.
|
||||
|
||||
For `.icon`, provide the folder path (for example `Assets/MyApp.icon`). During build/publish, Electron.NET copies the full directory into the Electron output so `electron-builder.json` can reference it (for example `"mac": { "icon": "MyApp.icon" }`).
|
||||
|
||||
### Node.js Integration
|
||||
|
||||
Electron.NET requires Node.js integration to be enabled for IPC to function. If you are not using the IPC functionality you can disable Node.js integration like so:
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace ElectronNET.API
|
||||
|
||||
internal WindowManager()
|
||||
{
|
||||
EnsureBrowserWindowClosedSubscription();
|
||||
}
|
||||
|
||||
internal static WindowManager Instance
|
||||
@@ -106,17 +107,6 @@ namespace ElectronNET.API
|
||||
tcs.SetResult(browserWindow);
|
||||
});
|
||||
|
||||
BridgeConnector.Socket.Once<int[]>("BrowserWindowClosed", (ids) =>
|
||||
{
|
||||
for (int index = 0; index < _browserWindows.Count; index++)
|
||||
{
|
||||
if (!ids.Contains(_browserWindows[index].Id))
|
||||
{
|
||||
_browserWindows.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (loadUrl.Equals("http://localhost", StringComparison.OrdinalIgnoreCase) && ElectronNetRuntime.AspNetWebPort.HasValue)
|
||||
{
|
||||
loadUrl = $"{loadUrl}:{ElectronNetRuntime.AspNetWebPort}";
|
||||
@@ -149,6 +139,40 @@ namespace ElectronNET.API
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private readonly object _browserWindowSubscriptionSync = new();
|
||||
private bool _browserWindowClosedSubscribed;
|
||||
|
||||
private void EnsureBrowserWindowClosedSubscription()
|
||||
{
|
||||
if (_browserWindowClosedSubscribed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_browserWindowSubscriptionSync)
|
||||
{
|
||||
if (_browserWindowClosedSubscribed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BridgeConnector.Socket.On<int[]>("BrowserWindowClosed", HandleBrowserWindowClosed);
|
||||
_browserWindowClosedSubscribed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleBrowserWindowClosed(int[] ids)
|
||||
{
|
||||
if (ids == null || ids.Length == 0)
|
||||
{
|
||||
_browserWindows.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
var existingIds = ids.ToHashSet();
|
||||
_browserWindows.RemoveAll(window => !existingIds.Contains(window.Id));
|
||||
}
|
||||
|
||||
private bool IsWindows10()
|
||||
{
|
||||
return RuntimeInformation.OSDescription.Contains("Windows 10");
|
||||
|
||||
@@ -216,14 +216,16 @@
|
||||
|
||||
var webPort = ElectronNetRuntime.AspNetWebPort ?? 0;
|
||||
|
||||
// check for the content folder if its exists in base director otherwise no need to include
|
||||
// It was used before because we are publishing the project which copies everything to bin folder and contentroot wwwroot was folder there.
|
||||
// now we have implemented the live reload if app is run using /watch then we need to use the default project path.
|
||||
// In packaged mode, static content is deployed alongside the app binaries, so we must
|
||||
// point content root to the process base directory. In unpackaged/watch scenarios we
|
||||
// keep the default project content root to preserve live reload behavior.
|
||||
var isPackagedStartup = ElectronNetRuntime.StartupMethod == StartupMethod.PackagedElectronFirst ||
|
||||
ElectronNetRuntime.StartupMethod == StartupMethod.PackagedDotnetFirst;
|
||||
|
||||
// For port 0 (dynamic port assignment), Kestrel requires binding to specific IP (127.0.0.1) not localhost
|
||||
var host = webPort == 0 ? "127.0.0.1" : "localhost";
|
||||
|
||||
if (Directory.Exists($"{AppDomain.CurrentDomain.BaseDirectory}\\wwwroot"))
|
||||
if (isPackagedStartup)
|
||||
{
|
||||
builder = builder.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
|
||||
.UseUrls($"http://{host}:{webPort}");
|
||||
|
||||
@@ -247,9 +247,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.6",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz",
|
||||
"integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==",
|
||||
"version": "4.2.7",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz",
|
||||
"integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
|
||||
@@ -230,7 +230,10 @@ module.exports = (socket, app) => {
|
||||
window = app["mainWindow"];
|
||||
if (window) {
|
||||
window.reload();
|
||||
windows.push(window);
|
||||
synchronizeWindowRegistry();
|
||||
if (!windows.some((entry) => tryGetWindowId(entry) === window.id)) {
|
||||
windows.push(window);
|
||||
}
|
||||
electronSocket.emit("BrowserWindowCreated", window.id);
|
||||
return;
|
||||
}
|
||||
@@ -253,21 +256,9 @@ module.exports = (socket, app) => {
|
||||
}
|
||||
});
|
||||
lastOptions = options;
|
||||
window.on("closed", (sender) => {
|
||||
for (let index = 0; index < windows.length; index++) {
|
||||
const windowItem = windows[index];
|
||||
try {
|
||||
windowItem.id;
|
||||
}
|
||||
catch (error) {
|
||||
if (error.message === "Object has been destroyed") {
|
||||
windows.splice(index, 1);
|
||||
const ids = [];
|
||||
windows.forEach((x) => ids.push(x.id));
|
||||
electronSocket.emit("BrowserWindowClosed", ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
window.on("closed", () => {
|
||||
synchronizeWindowRegistry();
|
||||
emitBrowserWindowClosed();
|
||||
});
|
||||
app.on("activate", () => {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
@@ -703,12 +694,47 @@ module.exports = (socket, app) => {
|
||||
getWindowById(id).setBrowserView((0, browserView_1.browserViewMediateService)(browserViewId));
|
||||
});
|
||||
function getWindowById(id) {
|
||||
const runtimeWindow = electron_1.BrowserWindow.fromId(id);
|
||||
if (runtimeWindow) {
|
||||
return runtimeWindow;
|
||||
}
|
||||
synchronizeWindowRegistry();
|
||||
for (let index = 0; index < windows.length; index++) {
|
||||
const element = windows[index];
|
||||
if (element.id === id) {
|
||||
if (tryGetWindowId(element) === id) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
throw new Error(`BrowserWindow with id '${id}' was not found.`);
|
||||
}
|
||||
function tryGetWindowId(element) {
|
||||
try {
|
||||
return element.id;
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function synchronizeWindowRegistry() {
|
||||
const runtimeWindows = electron_1.BrowserWindow.getAllWindows();
|
||||
const runtimeWindowIds = new Set(runtimeWindows.map((entry) => entry.id));
|
||||
for (let index = windows.length - 1; index >= 0; index--) {
|
||||
const windowId = tryGetWindowId(windows[index]);
|
||||
if (windowId === null || !runtimeWindowIds.has(windowId)) {
|
||||
windows.splice(index, 1);
|
||||
}
|
||||
}
|
||||
readyToShowWindowsIds = readyToShowWindowsIds.filter((entryId) => runtimeWindowIds.has(entryId));
|
||||
}
|
||||
function emitBrowserWindowClosed() {
|
||||
const ids = [];
|
||||
for (const entry of windows) {
|
||||
const windowId = tryGetWindowId(entry);
|
||||
if (windowId !== null) {
|
||||
ids.push(windowId);
|
||||
}
|
||||
}
|
||||
electronSocket.emit("BrowserWindowClosed", ids);
|
||||
}
|
||||
};
|
||||
//# sourceMappingURL=browserWindows.js.map
|
||||
@@ -255,7 +255,10 @@ export = (socket: Socket, app: Electron.App) => {
|
||||
window = app["mainWindow"];
|
||||
if (window) {
|
||||
window.reload();
|
||||
windows.push(window);
|
||||
synchronizeWindowRegistry();
|
||||
if (!windows.some((entry) => tryGetWindowId(entry) === window.id)) {
|
||||
windows.push(window);
|
||||
}
|
||||
electronSocket.emit("BrowserWindowCreated", window.id);
|
||||
return;
|
||||
}
|
||||
@@ -283,21 +286,9 @@ export = (socket: Socket, app: Electron.App) => {
|
||||
|
||||
lastOptions = options;
|
||||
|
||||
window.on("closed", (sender) => {
|
||||
for (let index = 0; index < windows.length; index++) {
|
||||
const windowItem = windows[index];
|
||||
try {
|
||||
windowItem.id;
|
||||
} catch (error) {
|
||||
if (error.message === "Object has been destroyed") {
|
||||
windows.splice(index, 1);
|
||||
|
||||
const ids = [];
|
||||
windows.forEach((x) => ids.push(x.id));
|
||||
electronSocket.emit("BrowserWindowClosed", ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
window.on("closed", () => {
|
||||
synchronizeWindowRegistry();
|
||||
emitBrowserWindowClosed();
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
@@ -907,11 +898,57 @@ export = (socket: Socket, app: Electron.App) => {
|
||||
});
|
||||
|
||||
function getWindowById(id: number): Electron.BrowserWindow {
|
||||
const runtimeWindow = BrowserWindow.fromId(id);
|
||||
if (runtimeWindow) {
|
||||
return runtimeWindow;
|
||||
}
|
||||
|
||||
synchronizeWindowRegistry();
|
||||
|
||||
for (let index = 0; index < windows.length; index++) {
|
||||
const element = windows[index];
|
||||
if (element.id === id) {
|
||||
if (tryGetWindowId(element) === id) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`BrowserWindow with id '${id}' was not found.`);
|
||||
}
|
||||
|
||||
function tryGetWindowId(element: Electron.BrowserWindow): number | null {
|
||||
try {
|
||||
return element.id;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function synchronizeWindowRegistry(): void {
|
||||
const runtimeWindows = BrowserWindow.getAllWindows();
|
||||
const runtimeWindowIds = new Set(runtimeWindows.map((entry) => entry.id));
|
||||
|
||||
for (let index = windows.length - 1; index >= 0; index--) {
|
||||
const windowId = tryGetWindowId(windows[index]);
|
||||
if (windowId === null || !runtimeWindowIds.has(windowId)) {
|
||||
windows.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
readyToShowWindowsIds = readyToShowWindowsIds.filter((entryId) =>
|
||||
runtimeWindowIds.has(entryId),
|
||||
);
|
||||
}
|
||||
|
||||
function emitBrowserWindowClosed(): void {
|
||||
const ids: number[] = [];
|
||||
|
||||
for (const entry of windows) {
|
||||
const windowId = tryGetWindowId(entry);
|
||||
if (windowId !== null) {
|
||||
ids.push(windowId);
|
||||
}
|
||||
}
|
||||
|
||||
electronSocket.emit("BrowserWindowClosed", ids);
|
||||
}
|
||||
};
|
||||
|
||||
20
src/ElectronNET.Host/package-lock.json
generated
20
src/ElectronNET.Host/package-lock.json
generated
@@ -17,7 +17,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.18",
|
||||
"electron": "^30.0.3",
|
||||
"electron": "^39.8.10",
|
||||
"eslint": "^9.39.1",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
@@ -762,15 +762,15 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "30.5.1",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-30.5.1.tgz",
|
||||
"integrity": "sha512-AhL7+mZ8Lg14iaNfoYTkXQ2qee8mmsQyllKdqxlpv/zrKgfxz6jNVtcRRbQtLxtF8yzcImWdfTQROpYiPumdbw==",
|
||||
"version": "39.8.10",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-39.8.10.tgz",
|
||||
"integrity": "sha512-zbYtGPYUI7PzqLAzkk21Rk6j67WN0hxn0Mq/njErZo1d0HSf33is4f8ICI5fMLy5vYe0JtCtM5sYunNOaochSQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
"@types/node": "^20.9.0",
|
||||
"@types/node": "^22.7.7",
|
||||
"extract-zip": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
@@ -847,16 +847,6 @@
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron/node_modules/@types/node": {
|
||||
"version": "20.19.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz",
|
||||
"integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.18",
|
||||
"electron": "^30.0.3",
|
||||
"electron": "^39.8.10",
|
||||
"eslint": "^9.39.1",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace ElectronNET.IntegrationTests.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for ElectronNET.Core.targets icon copy behavior.
|
||||
/// Covers GitHub issue #1047: modern macOS .icon app-icon packages are folders
|
||||
/// and must be copied recursively, not treated as a single file.
|
||||
/// </summary>
|
||||
public class ElectronIconTargetsTests
|
||||
{
|
||||
private static readonly string CorePropsPath = FindBuildFile("src/ElectronNET/build/ElectronNET.Core.props", "ElectronNET/build/ElectronNET.Core.props");
|
||||
private static readonly string CoreTargetsPath = FindBuildFile("src/ElectronNET/build/ElectronNET.Core.targets", "ElectronNET/build/ElectronNET.Core.targets");
|
||||
|
||||
[Fact]
|
||||
public async Task ElectronCoreTargets_ElectronIconDirectory_ShouldBeMappedIntoElectronOutput()
|
||||
{
|
||||
var tempDir = CreateTempProjectDirectory();
|
||||
try
|
||||
{
|
||||
var iconPackageDir = Path.Combine(tempDir, "Assets", "MyApp.icon");
|
||||
Directory.CreateDirectory(Path.Combine(iconPackageDir, "layers"));
|
||||
|
||||
await File.WriteAllTextAsync(Path.Combine(iconPackageDir, "manifest.json"), "{}");
|
||||
await File.WriteAllTextAsync(Path.Combine(iconPackageDir, "layers", "foreground.png"), "not-a-real-png");
|
||||
|
||||
await WriteMinimalCsprojAsync(tempDir);
|
||||
|
||||
var (exitCode, output) = await RunDotnetMsBuildAsync(tempDir, "DumpElectronIconCopyItems");
|
||||
var normalizedOutput = NormalizePathSeparators(output);
|
||||
|
||||
exitCode.Should().Be(0,
|
||||
$"MSBuild target evaluation must succeed. Full output:\n{output}");
|
||||
|
||||
normalizedOutput.Should().Contain(
|
||||
".electron/MyApp.icon/manifest.json",
|
||||
$"the icon package root file must be mapped into .electron/MyApp.icon. Full output:\n{output}");
|
||||
|
||||
normalizedOutput.Should().Contain(
|
||||
".electron/MyApp.icon/layers/foreground.png",
|
||||
$"nested files in .icon package must preserve structure in .electron output. Full output:\n{output}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FindBuildFile(string relativeFromRepoRoot, string relativeFromSrc)
|
||||
{
|
||||
var dir = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (dir != null)
|
||||
{
|
||||
var fromRepoRoot = Path.Combine(dir.FullName, relativeFromRepoRoot);
|
||||
if (File.Exists(fromRepoRoot))
|
||||
{
|
||||
return Path.GetFullPath(fromRepoRoot);
|
||||
}
|
||||
|
||||
var fromSrc = Path.Combine(dir.FullName, relativeFromSrc);
|
||||
if (File.Exists(fromSrc))
|
||||
{
|
||||
return Path.GetFullPath(fromSrc);
|
||||
}
|
||||
|
||||
dir = dir.Parent;
|
||||
}
|
||||
|
||||
throw new FileNotFoundException(
|
||||
$"Could not locate '{relativeFromRepoRoot}' by walking up from '{AppContext.BaseDirectory}'.");
|
||||
}
|
||||
|
||||
private static string CreateTempProjectDirectory()
|
||||
{
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), $"electron-net-icon-test-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempDir);
|
||||
Directory.CreateDirectory(Path.Combine(tempDir, "Properties"));
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
private static Task WriteMinimalCsprojAsync(string tempDir)
|
||||
{
|
||||
var propsPathEscaped = CorePropsPath.Replace("'", "'");
|
||||
var targetsPathEscaped = CoreTargetsPath.Replace("'", "'");
|
||||
|
||||
return File.WriteAllTextAsync(
|
||||
Path.Combine(tempDir, "TestApp.csproj"),
|
||||
$$"""
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="{{propsPathEscaped}}" />
|
||||
|
||||
<PropertyGroup Label="ElectronNetCommon">
|
||||
<ElectronIcon>Assets/MyApp.icon</ElectronIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="{{targetsPathEscaped}}" />
|
||||
|
||||
<Target Name="DumpElectronIconCopyItems"
|
||||
DependsOnTargets="ElectronResolvePaths;ElectronGetCopyToOutputDirectoryItems">
|
||||
<Message Importance="High"
|
||||
Text="ELECTRON_COPY_ITEMS: @(_ElectronFilesToCopyWithTargetPath->'%(TargetPath)')" />
|
||||
</Target>
|
||||
</Project>
|
||||
""");
|
||||
}
|
||||
|
||||
private static async Task<(int ExitCode, string Output)> RunDotnetMsBuildAsync(string workingDirectory, string target)
|
||||
{
|
||||
var psi = new ProcessStartInfo("dotnet", $"msbuild TestApp.csproj --nologo -v:minimal /restore /t:{target}")
|
||||
{
|
||||
WorkingDirectory = workingDirectory,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
};
|
||||
|
||||
using var process = Process.Start(psi)!;
|
||||
var stdOut = await process.StandardOutput.ReadToEndAsync();
|
||||
var stdErr = await process.StandardError.ReadToEndAsync();
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
return (process.ExitCode, stdOut + stdErr);
|
||||
}
|
||||
|
||||
private static string NormalizePathSeparators(string value)
|
||||
{
|
||||
return value.Replace('\\', '/');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
namespace ElectronNET.IntegrationTests.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Regression checks for BrowserWindow lifecycle cleanup in WindowManager.
|
||||
/// Covers GitHub issue #1008.
|
||||
/// </summary>
|
||||
public class WindowManagerLifecycleTests
|
||||
{
|
||||
private static readonly string WindowManagerFilePath = FindWindowManagerFile();
|
||||
|
||||
[Fact]
|
||||
public void WindowManager_ShouldUsePersistentBrowserWindowClosedSubscription()
|
||||
{
|
||||
File.Exists(WindowManagerFilePath).Should().BeTrue(
|
||||
$"WindowManager source must exist at '{WindowManagerFilePath}'.");
|
||||
|
||||
var content = File.ReadAllText(WindowManagerFilePath);
|
||||
|
||||
content.Should().Contain(
|
||||
"Socket.On<int[]>(\"BrowserWindowClosed\"",
|
||||
"closed-window cleanup must be wired for every close event, not just the first one.");
|
||||
|
||||
content.Should().NotContain(
|
||||
"Socket.Once<int[]>(\"BrowserWindowClosed\"",
|
||||
"a one-shot subscription causes stale BrowserWindow references after subsequent closes (issue #1008).");
|
||||
}
|
||||
|
||||
private static string FindWindowManagerFile()
|
||||
{
|
||||
const string RelativeFromRepoRoot = "src/ElectronNET.API/API/WindowManager.cs";
|
||||
const string RelativeFromSrc = "ElectronNET.API/API/WindowManager.cs";
|
||||
|
||||
var dir = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (dir != null)
|
||||
{
|
||||
var fromRepoRoot = Path.Combine(dir.FullName, RelativeFromRepoRoot);
|
||||
if (File.Exists(fromRepoRoot))
|
||||
{
|
||||
return Path.GetFullPath(fromRepoRoot);
|
||||
}
|
||||
|
||||
var fromSrc = Path.Combine(dir.FullName, RelativeFromSrc);
|
||||
if (File.Exists(fromSrc))
|
||||
{
|
||||
return Path.GetFullPath(fromSrc);
|
||||
}
|
||||
|
||||
dir = dir.Parent;
|
||||
}
|
||||
|
||||
throw new FileNotFoundException(
|
||||
"Could not locate WindowManager.cs by walking up from " +
|
||||
$"'{AppContext.BaseDirectory}'.");
|
||||
}
|
||||
}
|
||||
@@ -526,9 +526,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"version": "1.1.18",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
@@ -1337,9 +1337,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
|
||||
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
|
||||
"integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1797,9 +1797,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/readdir-glob/node_modules/brace-expansion": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
|
||||
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
|
||||
@@ -313,7 +313,10 @@
|
||||
<ItemGroup>
|
||||
<!--<_ElectronFiles Include="$(ElectronIntermediatePackageJson)" />-->
|
||||
<_ElectronFiles Include="$(ElectronSplashScreen)" Condition="'$(ElectronSplashScreen)'!=''" />
|
||||
<_ElectronFiles Include="$(ElectronIcon)" Condition="'$(ElectronIcon)'!=''" />
|
||||
<_ElectronFiles Include="$(ElectronIcon)"
|
||||
Condition="'$(ElectronIcon)'!='' AND ( !Exists('$(ElectronIcon)') OR !$([System.IO.Directory]::Exists('$(ElectronIcon)') ) )" />
|
||||
<_ElectronIconDirectoryFiles Include="$(ElectronIcon)\**\*"
|
||||
Condition="'$(ElectronIcon)'!='' AND Exists('$(ElectronIcon)') AND $([System.IO.Directory]::Exists('$(ElectronIcon)') )" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -329,11 +332,16 @@
|
||||
</ItemGroup>
|
||||
|
||||
<Message Importance="High" Text="_ElectronFilesToCopy: @(_ElectronFilesToCopy)" />
|
||||
<Message Importance="High" Text="_ElectronIconDirectoryFiles: @(_ElectronIconDirectoryFiles)" />
|
||||
|
||||
<ItemGroup>
|
||||
<_ElectronFilesToCopyWithTargetPath Include="@(_ElectronFilesToCopy)">
|
||||
<TargetPath>$(ElectronDirName)\%(FileName)%(Extension)</TargetPath>
|
||||
</_ElectronFilesToCopyWithTargetPath>
|
||||
|
||||
<_ElectronFilesToCopyWithTargetPath Include="@(_ElectronIconDirectoryFiles)">
|
||||
<TargetPath>$(ElectronDirName)\$(ElectronIconFileName)\%(RecursiveDir)%(FileName)%(Extension)</TargetPath>
|
||||
</_ElectronFilesToCopyWithTargetPath>
|
||||
</ItemGroup>
|
||||
|
||||
<Message Text="_ElectronFilesToCopyWithTargetPath: @(_ElectronFilesToCopyWithTargetPath)" />
|
||||
@@ -645,10 +653,7 @@ For more information, see: https://github.com/ElectronNET/Electron.NET/wiki/Migr
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<_NpxCmd>npx electron-builder --config=./$(ElectronBuilderJson)</_NpxCmd>
|
||||
<_NpxCmd Condition="'$(IsLinuxWsl)' == 'true'">$(_NpxCmd) --$(ElectronPlatform)</_NpxCmd>
|
||||
<_NpxCmd Condition="'$(IsLinuxWsl)' == 'true'">$(_NpxCmd) --$(ElectronArch)</_NpxCmd>
|
||||
<_NpxCmd>$(_NpxCmd) -c.electronVersion=$(ElectronVersion) -c.directories.output "$(ElectronPublishUrlFullPath)" $(ElectronPaParams)</_NpxCmd>
|
||||
<_NpxCmd>npx electron-builder -c.$(ElectronPlatform).defaultArch=$(ElectronArch) --config=./$(ElectronBuilderJson) -c.electronVersion=$(ElectronVersion) --$(ElectronPlatform) --$(ElectronArch) -c.directories.output "$(ElectronPublishUrlFullPath)" $(ElectronPaParams)</_NpxCmd>
|
||||
<_NpxCmd Condition="'$(IsLinuxWsl)' == 'true'">wsl bash -ic '$(_NpxCmd)'</_NpxCmd>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -572,7 +572,7 @@
|
||||
|
||||
<StringProperty Name="ElectronIcon"
|
||||
DisplayName="App Icon"
|
||||
Description="Choose a ICO file to be used as application icon"
|
||||
Description="Choose an icon path for the app (e.g. .ico, .icns, or modern macOS .icon folder)"
|
||||
Subtype="File"
|
||||
Category="General">
|
||||
<StringProperty.DataSource>
|
||||
@@ -581,7 +581,7 @@
|
||||
<StringProperty.ValueEditors>
|
||||
<ValueEditor EditorType="FilePath">
|
||||
<ValueEditor.Metadata>
|
||||
<NameValuePair Name="FileTypeFilter" Value="Icon files (*.ico)|*.ico|All files (*.*)|*.*" />
|
||||
<NameValuePair Name="FileTypeFilter" Value="Icon files (*.ico,*.icns)|*.ico;*.icns|All files (*.*)|*.*" />
|
||||
</ValueEditor.Metadata>
|
||||
</ValueEditor>
|
||||
</StringProperty.ValueEditors>
|
||||
|
||||
Reference in New Issue
Block a user