@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.
@@ -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 starts as an agent app (no Dock icon) so that a second launch, which quits at once,
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つ目の起動はすぐに終了します。macOSではDockにアイコンも出しません)。開発中に複数起動したいときは `--next2d-allow-multiple` を付けて起動します。
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 (and, on macOS, never shows a Dock icon). Start with `--next2d-allow-multiple` to run several copies while developing.
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.5",
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) so a second launch never shows a Dock icon;
148
- // the instance that keeps running shows it.
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)) throw new Error("Window API access denied");
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 ["resume", "unlock-screen"]) powerMonitor.on(name, () => setTimeout(recoverOverlay, 1000));
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.