@next2d/builder 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/electron-config.js +1 -2
- package/docs/steam.md +6 -2
- package/package.json +1 -1
- package/templates/electron/index.js +95 -6
package/dist/electron-config.js
CHANGED
|
@@ -132,8 +132,7 @@ export const createElectronPackagerOptions = (root, config, os, resources) => {
|
|
|
132
132
|
...os === "macos" && (config.macos.localNetworkUsageDescription || config.window.overlay?.enabled) ? {
|
|
133
133
|
"extendInfo": {
|
|
134
134
|
...config.macos.localNetworkUsageDescription ? { "NSLocalNetworkUsageDescription": config.macos.localNetworkUsageDescription } : {},
|
|
135
|
-
// An overlay
|
|
136
|
-
// never shows one. The first instance shows its Dock icon itself (templates/electron/index.js).
|
|
135
|
+
// An overlay is an agent app: no Dock icon, also for a second launch that quits at once.
|
|
137
136
|
...config.window.overlay?.enabled ? { "LSUIElement": true } : {}
|
|
138
137
|
},
|
|
139
138
|
...config.macos.localNetworkUsageDescription ? {
|
package/docs/steam.md
CHANGED
|
@@ -105,7 +105,9 @@ await window.next2dWindow.quit();
|
|
|
105
105
|
const off = window.next2dWindow.onResume(() => { /* 状態の再送など */ });
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
重ね表示が有効なときは、アプリは**1つだけ**起動します(2
|
|
108
|
+
重ね表示が有効なときは、アプリは**1つだけ**起動します(2つ目の起動はすぐに終了します)。macOSではDockにアイコンを出しません(エージェントアプリ。終了はゲーム内のボタンで行います)。開発中に複数起動したいときは `--next2d-allow-multiple` を付けて起動します。
|
|
109
|
+
|
|
110
|
+
Windowsでスリープ(ノートPCのふたを閉じる等)から復帰すると、重ね表示の窓がマウスを受け取れなくなることがあるため、窓を作り直します(ゲームは自動保存を前提にしてください)。電源・ディスプレイ・GPUのイベントは `userData/diagnostics.log`(Windowsは `%APPDATA%\<appId>\diagnostics.log`)に記録されます。
|
|
109
111
|
|
|
110
112
|
Linuxの透明表示はコンポジタが必要です。
|
|
111
113
|
|
|
@@ -325,7 +327,9 @@ await window.next2dWindow.quit();
|
|
|
325
327
|
const off = window.next2dWindow.onResume(() => { /* resend state, etc. */ });
|
|
326
328
|
```
|
|
327
329
|
|
|
328
|
-
With the overlay enabled the app runs **once**: a second launch quits at once
|
|
330
|
+
With the overlay enabled the app runs **once**: a second launch quits at once. On macOS it is an agent app with no Dock icon (quit from inside the game). Start with `--next2d-allow-multiple` to run several copies while developing.
|
|
331
|
+
|
|
332
|
+
On Windows, after sleep (e.g. closing a laptop lid) the overlay window can stop receiving the mouse, so a fresh window replaces it; the game should save automatically. Power, display and GPU events are logged to `userData/diagnostics.log` (Windows: `%APPDATA%\<appId>\diagnostics.log`).
|
|
329
333
|
|
|
330
334
|
Transparency on Linux requires a compositor.
|
|
331
335
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@next2d/builder",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "Multi-platform builder for Next2d Framework, supporting export to various platforms such as macOS, Windows, iOS, Android and Web(HTML)",
|
|
5
5
|
"author": "Toshiyuki Ienaga <ienaga@next2d.app>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -66,6 +66,64 @@ const refreshForwarding = () => {
|
|
|
66
66
|
mainWindow.setIgnoreMouseEvents(false);
|
|
67
67
|
mainWindow.setIgnoreMouseEvents(true, { forward: true });
|
|
68
68
|
};
|
|
69
|
+
// A small log of power/display/GPU events (userData/diagnostics.log) to find out what happened after sleep.
|
|
70
|
+
const diagnosticsFile = () => path.join(app.getPath("userData"), "diagnostics.log");
|
|
71
|
+
const diag = (message) => {
|
|
72
|
+
if (!overlay) return;
|
|
73
|
+
try {
|
|
74
|
+
const file = diagnosticsFile();
|
|
75
|
+
if (fs.existsSync(file) && fs.statSync(file).size > 200000) fs.writeFileSync(file, "");
|
|
76
|
+
fs.appendFileSync(file, `${new Date().toISOString()} ${message}\n`);
|
|
77
|
+
} catch { /* diagnostics must never break the game */ }
|
|
78
|
+
};
|
|
79
|
+
// Counts of mouse events the page really receives (moves are forwarded while click-through; down/up only arrive
|
|
80
|
+
// while the window accepts clicks). Logged when they change, to tell "clicks never reach the page" from other causes.
|
|
81
|
+
const DIAG_SCRIPT = `(() => {
|
|
82
|
+
if (window.__n2dDiag) return;
|
|
83
|
+
const d = window.__n2dDiag = { moves: 0, downs: 0, ups: 0, keys: 0, last: null };
|
|
84
|
+
const mark = (name) => (event) => { d[name]++; d.last = [event.clientX, event.clientY]; };
|
|
85
|
+
addEventListener("mousemove", mark("moves"), true);
|
|
86
|
+
addEventListener("pointerdown", mark("downs"), true);
|
|
87
|
+
addEventListener("pointerup", mark("ups"), true);
|
|
88
|
+
addEventListener("keydown", () => { d.keys++; }, true);
|
|
89
|
+
})()`;
|
|
90
|
+
let lastSnapshot = "";
|
|
91
|
+
const snapshotRenderer = async () => {
|
|
92
|
+
if (!mainWindow || mainWindow.isDestroyed()) return;
|
|
93
|
+
try {
|
|
94
|
+
const raw = await mainWindow.webContents.executeJavaScript("JSON.stringify(window.__n2dDiag || null)");
|
|
95
|
+
if (raw !== lastSnapshot) {
|
|
96
|
+
lastSnapshot = raw;
|
|
97
|
+
diag(`renderer ${raw} ignore=${overlayIgnore} onTop=${mainWindow.isAlwaysOnTop()} bounds=${JSON.stringify(mainWindow.getBounds())}`);
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
diag(`renderer snapshot failed: ${error?.message ?? error}`);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
// After sleep (closing a laptop lid) Windows can leave the overlay window itself broken: mouse moves are no longer
|
|
104
|
+
// forwarded to it, or clicks fall through even where the pet is drawn. Reloading the page does not help (the
|
|
105
|
+
// window is the same), so build a new window and close the old one. The game saves automatically, so progress is
|
|
106
|
+
// kept. The new window is created first so the app never has zero windows (which would quit it).
|
|
107
|
+
let recreating = false;
|
|
108
|
+
const recreateWindow = async () => {
|
|
109
|
+
if (recreating || !mainWindow || mainWindow.isDestroyed()) return;
|
|
110
|
+
recreating = true;
|
|
111
|
+
diag("recreate window (resume)");
|
|
112
|
+
const old = mainWindow;
|
|
113
|
+
overlayIgnore = Boolean(overlay?.clickThrough);
|
|
114
|
+
try {
|
|
115
|
+
await createWindow();
|
|
116
|
+
} catch (error) {
|
|
117
|
+
diag(`recreate window failed: ${error?.message ?? error}`);
|
|
118
|
+
} finally {
|
|
119
|
+
recreating = false;
|
|
120
|
+
if (old !== mainWindow && !old.isDestroyed()) {
|
|
121
|
+
// Closing on purpose: do not treat the old page going away as a crash.
|
|
122
|
+
old.webContents.removeAllListeners("render-process-gone");
|
|
123
|
+
old.destroy();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
69
127
|
const createWindow = async () => {
|
|
70
128
|
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
|
|
71
129
|
// The preload reads these flags; each API is exposed only when its feature is configured.
|
|
@@ -103,8 +161,25 @@ const createWindow = async () => {
|
|
|
103
161
|
if (overlay.clickThrough) {
|
|
104
162
|
mainWindow.setIgnoreMouseEvents(true, { forward: true });
|
|
105
163
|
}
|
|
164
|
+
// Errors the game prints (a failed window request, for example) are otherwise invisible in a packaged app.
|
|
165
|
+
let consoleLogged = 0;
|
|
166
|
+
mainWindow.webContents.on("console-message", (...args) => {
|
|
167
|
+
const first = args[0];
|
|
168
|
+
const detail = first && typeof first === "object" && "message" in first ? first : { level: args[1], message: args[2] };
|
|
169
|
+
if (!["error", "warning", 2, 3].includes(detail.level) || ++consoleLogged > 200) return;
|
|
170
|
+
diag(`console ${detail.level}: ${String(detail.message).slice(0, 300)}`);
|
|
171
|
+
});
|
|
172
|
+
mainWindow.webContents.on("did-finish-load", () => {
|
|
173
|
+
diag("renderer loaded");
|
|
174
|
+
mainWindow.webContents.executeJavaScript(DIAG_SCRIPT).catch(() => {});
|
|
175
|
+
});
|
|
176
|
+
const snapshotTimer = setInterval(snapshotRenderer, 5000);
|
|
177
|
+
snapshotTimer.unref?.();
|
|
178
|
+
mainWindow.once("closed", () => clearInterval(snapshotTimer));
|
|
179
|
+
mainWindow.webContents.on("unresponsive", () => diag("renderer unresponsive"));
|
|
180
|
+
mainWindow.webContents.on("responsive", () => diag("renderer responsive"));
|
|
106
181
|
const events = ["display-metrics-changed", "display-added", "display-removed"];
|
|
107
|
-
const onDisplayChange = () => { placeOverlay(); keepOnTop(); };
|
|
182
|
+
const onDisplayChange = () => { diag("display changed"); placeOverlay(); keepOnTop(); };
|
|
108
183
|
for (const name of events) screen.on(name, onDisplayChange);
|
|
109
184
|
mainWindow.once("closed", () => {
|
|
110
185
|
for (const name of events) screen.removeListener(name, onDisplayChange);
|
|
@@ -144,9 +219,8 @@ const createWindow = async () => {
|
|
|
144
219
|
|
|
145
220
|
app.whenReady().then(async () => {
|
|
146
221
|
if (!hasLock) return;
|
|
147
|
-
// The overlay app is packaged as an agent app (LSUIElement)
|
|
148
|
-
// the
|
|
149
|
-
if (process.platform === "darwin" && overlay) app.dock?.show();
|
|
222
|
+
// The overlay app is packaged as an agent app (LSUIElement): it never shows a Dock icon (a desktop pet stays out
|
|
223
|
+
// of the Dock and the app switcher; the game offers its own "quit").
|
|
150
224
|
Menu.setApplicationMenu(process.platform === "darwin" ? Menu.buildFromTemplate([
|
|
151
225
|
{ role: "appMenu" }, { role: "editMenu" }, { role: "windowMenu" }
|
|
152
226
|
]) : null);
|
|
@@ -187,7 +261,10 @@ app.whenReady().then(async () => {
|
|
|
187
261
|
}
|
|
188
262
|
if (overlay) {
|
|
189
263
|
const trusted = (event) => {
|
|
190
|
-
if (!isTrustedSender(event, mainWindow))
|
|
264
|
+
if (!isTrustedSender(event, mainWindow)) {
|
|
265
|
+
diag(`window API access denied (frame url: ${event?.senderFrame?.url})`);
|
|
266
|
+
throw new Error("Window API access denied");
|
|
267
|
+
}
|
|
191
268
|
};
|
|
192
269
|
ipcMain.handle("next2d:window:set-ignore-mouse-events", (event, ignore) => {
|
|
193
270
|
trusted(event);
|
|
@@ -210,7 +287,19 @@ app.whenReady().then(async () => {
|
|
|
210
287
|
mainWindow.setIgnoreMouseEvents(overlayIgnore, overlayIgnore ? { forward: true } : undefined);
|
|
211
288
|
mainWindow.webContents.send("next2d:window:resume");
|
|
212
289
|
};
|
|
213
|
-
for (const name of ["
|
|
290
|
+
for (const name of ["suspend", "lock-screen"]) powerMonitor.on(name, () => diag(name));
|
|
291
|
+
for (const name of ["resume", "unlock-screen"]) {
|
|
292
|
+
powerMonitor.on(name, () => {
|
|
293
|
+
diag(name);
|
|
294
|
+
if (name === "resume" && process.platform === "win32") {
|
|
295
|
+
// Give the GPU/display time to come back, then start over with a fresh window.
|
|
296
|
+
setTimeout(() => { void recreateWindow(); }, 3000);
|
|
297
|
+
} else {
|
|
298
|
+
setTimeout(recoverOverlay, 1000);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
app.on("child-process-gone", (_event, details) => diag(`child-process-gone ${details.type} ${details.reason}`));
|
|
214
303
|
ipcMain.handle("next2d:window:quit", (event) => {
|
|
215
304
|
trusted(event);
|
|
216
305
|
// Reply before the window is torn down.
|