@next2d/builder 0.4.0 → 0.4.2

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/docs/steam.md CHANGED
@@ -101,8 +101,12 @@ Steamは複数Depotに対応。builderは各OSに1 IDまでで、一部OSだけ
101
101
  await window.next2dWindow?.setIgnoreMouseEvents(false); // キャラクター上では操作を受け付ける
102
102
  const { x, y, width, height, scaleFactor } = await window.next2dWindow.getBounds();
103
103
  await window.next2dWindow.quit();
104
+ // スリープ復帰・画面ロック解除のあとに呼ばれる(重ね表示の状態は再適用済み。ページ側の状態を合わせ直す)
105
+ const off = window.next2dWindow.onResume(() => { /* 状態の再送など */ });
104
106
  ```
105
107
 
108
+ 重ね表示が有効なときは、アプリは**1つだけ**起動します(2つ目の起動はすぐに終了します。macOSではDockにアイコンも出しません)。開発中に複数起動したいときは `--next2d-allow-multiple` を付けて起動します。
109
+
106
110
  Linuxの透明表示はコンポジタが必要です。
107
111
 
108
112
  ### STEP4:認証を用意する
@@ -317,8 +321,12 @@ Only when enabled, the renderer gets `window.next2dWindow` (app main frame only)
317
321
  await window.next2dWindow?.setIgnoreMouseEvents(false); // accept input while over the character
318
322
  const { x, y, width, height, scaleFactor } = await window.next2dWindow.getBounds();
319
323
  await window.next2dWindow.quit();
324
+ // Called after the PC wakes from sleep or the screen is unlocked (the overlay state was applied again; resync the page)
325
+ const off = window.next2dWindow.onResume(() => { /* resend state, etc. */ });
320
326
  ```
321
327
 
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.
329
+
322
330
  Transparency on Linux requires a compositor.
323
331
 
324
332
  ### STEP4: Prepare credentials
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@next2d/builder",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
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",
@@ -1,4 +1,4 @@
1
- const { app, BrowserWindow, Menu, net, protocol, screen, session, ipcMain } = require("electron");
1
+ const { app, BrowserWindow, Menu, net, powerMonitor, protocol, screen, session, ipcMain } = require("electron");
2
2
  const path = require("node:path");
3
3
  const fs = require("node:fs");
4
4
  const { pathToFileURL } = require("node:url");
@@ -13,6 +13,15 @@ app.on("before-quit", closeNativeBridge);
13
13
  app.setName(config.appName);
14
14
  const userDataOverride = app.commandLine.getSwitchValue("user-data-dir");
15
15
  app.setPath("userData", userDataOverride ? path.resolve(userDataOverride) : path.join(app.getPath("appData"), config.appId));
16
+
17
+ // A desktop overlay (a pet that stays on screen) must run once. A second launch quits at once (macOS: before
18
+ // it can show a Dock icon). The lock is tied to the userData path set above, so it needs to come after it.
19
+ const singleInstance = !app.commandLine.hasSwitch("next2d-allow-multiple") && Boolean(config.window.overlay?.enabled);
20
+ const hasLock = !singleInstance || app.requestSingleInstanceLock();
21
+ if (!hasLock) {
22
+ if (process.platform === "darwin") app.dock?.hide();
23
+ app.exit(0);
24
+ }
16
25
  const assetRoot = app.isPackaged
17
26
  ? path.join(process.resourcesPath, "resources")
18
27
  : path.join(__dirname, "resources");
@@ -26,6 +35,8 @@ protocol.registerSchemesAsPrivileged([{
26
35
  const startURL = "next2d://game/index.html";
27
36
  const overlay = config.window.overlay?.enabled ? config.window.overlay : null;
28
37
  let mainWindow;
38
+ // The last click-through state asked for, so it can be applied again after sleep/lock.
39
+ let overlayIgnore = Boolean(config.window.overlay?.enabled && config.window.overlay.clickThrough);
29
40
  // Bottom anchor: flush with the primary display's work area (above the taskbar/Dock), centred.
30
41
  const overlayBounds = () => {
31
42
  const { workArea } = screen.getPrimaryDisplay();
@@ -59,7 +70,9 @@ const createWindow = async () => {
59
70
  show: false,
60
71
  ...(overlay ? {
61
72
  ...overlayBounds(), transparent: true, frame: false, resizable: false, hasShadow: false,
62
- fullscreenable: false, skipTaskbar: false, backgroundColor: "#00000000"
73
+ fullscreenable: false, skipTaskbar: false, backgroundColor: "#00000000",
74
+ // The overlay must never take keyboard focus or activation from the apps under it (Word etc.).
75
+ focusable: false
63
76
  } : {}),
64
77
  webPreferences: {
65
78
  nodeIntegration: false, contextIsolation: true, sandbox: true,
@@ -104,11 +117,12 @@ const createWindow = async () => {
104
117
  closeNativeBridge();
105
118
  app.exit(1);
106
119
  });
107
- mainWindow.once("ready-to-show", () => mainWindow.show());
120
+ mainWindow.once("ready-to-show", () => (overlay ? mainWindow.showInactive() : mainWindow.show()));
108
121
  await mainWindow.loadURL(startURL);
109
122
  };
110
123
 
111
124
  app.whenReady().then(async () => {
125
+ if (!hasLock) return;
112
126
  Menu.setApplicationMenu(process.platform === "darwin" ? Menu.buildFromTemplate([
113
127
  { role: "appMenu" }, { role: "editMenu" }, { role: "windowMenu" }
114
128
  ]) : null);
@@ -154,6 +168,7 @@ app.whenReady().then(async () => {
154
168
  ipcMain.handle("next2d:window:set-ignore-mouse-events", (event, ignore) => {
155
169
  trusted(event);
156
170
  if (typeof ignore !== "boolean") throw new TypeError("ignore must be a boolean");
171
+ overlayIgnore = ignore;
157
172
  mainWindow.setIgnoreMouseEvents(ignore, ignore ? { forward: true } : undefined);
158
173
  });
159
174
  ipcMain.handle("next2d:window:get-bounds", (event) => {
@@ -161,6 +176,17 @@ app.whenReady().then(async () => {
161
176
  const bounds = mainWindow.getBounds();
162
177
  return { ...bounds, scaleFactor: screen.getDisplayMatching(bounds).scaleFactor };
163
178
  });
179
+ // After sleep or screen lock the OS can drop the mouse-forwarding hook and reset the window's
180
+ // z-order/bounds. Apply the overlay state again and tell the page so it can recover too.
181
+ const recoverOverlay = () => {
182
+ if (!mainWindow || mainWindow.isDestroyed()) return;
183
+ mainWindow.setAlwaysOnTop(true, "floating");
184
+ placeOverlay();
185
+ mainWindow.setIgnoreMouseEvents(false);
186
+ mainWindow.setIgnoreMouseEvents(overlayIgnore, overlayIgnore ? { forward: true } : undefined);
187
+ mainWindow.webContents.send("next2d:window:resume");
188
+ };
189
+ for (const name of ["resume", "unlock-screen"]) powerMonitor.on(name, () => setTimeout(recoverOverlay, 1000));
164
190
  ipcMain.handle("next2d:window:quit", (event) => {
165
191
  trusted(event);
166
192
  // Reply before the window is torn down.
@@ -173,5 +199,15 @@ app.whenReady().then(async () => {
173
199
  app.exit(1);
174
200
  });
175
201
 
202
+ if (singleInstance && hasLock) {
203
+ // Launching again does nothing visible; just make sure the overlay is still shown on top.
204
+ app.on("second-instance", () => {
205
+ if (mainWindow && !mainWindow.isDestroyed()) {
206
+ mainWindow.setAlwaysOnTop(true, "floating");
207
+ if (!mainWindow.isVisible()) mainWindow.showInactive();
208
+ }
209
+ });
210
+ }
211
+
176
212
  // Closing the game must stop Steam's "Running" status, including on macOS.
177
213
  app.on("window-all-closed", () => app.quit());
@@ -21,6 +21,13 @@ if (flags.has("--next2d-overlay")) {
21
21
  await ipcRenderer.invoke("next2d:window:set-ignore-mouse-events", ignore);
22
22
  },
23
23
  getBounds: () => ipcRenderer.invoke("next2d:window:get-bounds"),
24
- quit: async () => { await ipcRenderer.invoke("next2d:window:quit"); }
24
+ quit: async () => { await ipcRenderer.invoke("next2d:window:quit"); },
25
+ // Fires after the PC wakes from sleep or the screen is unlocked (the overlay state was applied again).
26
+ onResume: (listener) => {
27
+ if (typeof listener !== "function") throw new TypeError("Expected resume listener");
28
+ const handler = () => listener();
29
+ ipcRenderer.on("next2d:window:resume", handler);
30
+ return () => ipcRenderer.removeListener("next2d:window:resume", handler);
31
+ }
25
32
  });
26
33
  }