@next2d/builder 0.4.1 → 0.4.3
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.
|
|
3
|
+
"version": "0.4.3",
|
|
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();
|
|
@@ -40,6 +51,21 @@ const overlayBounds = () => {
|
|
|
40
51
|
const placeOverlay = () => {
|
|
41
52
|
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.setBounds(overlayBounds());
|
|
42
53
|
};
|
|
54
|
+
// The overlay never takes focus, so Windows does not raise it when another topmost window (the taskbar, another
|
|
55
|
+
// always-on-top app) is activated, nor after the display is turned off and on. Put it back on top.
|
|
56
|
+
const keepOnTop = () => {
|
|
57
|
+
if (!mainWindow || mainWindow.isDestroyed()) return;
|
|
58
|
+
mainWindow.setAlwaysOnTop(true, "floating");
|
|
59
|
+
mainWindow.moveTop();
|
|
60
|
+
};
|
|
61
|
+
// Windows forwards mouse moves to a click-through window with a system mouse hook, and Electron installs it only
|
|
62
|
+
// when forwarding switches on. After sleep (closing the lid) the hook can be gone while Electron still thinks it is
|
|
63
|
+
// installed, so the page never sees the pointer. Switching forwarding off and on again installs a fresh hook.
|
|
64
|
+
const refreshForwarding = () => {
|
|
65
|
+
if (!overlayIgnore || !mainWindow || mainWindow.isDestroyed()) return;
|
|
66
|
+
mainWindow.setIgnoreMouseEvents(false);
|
|
67
|
+
mainWindow.setIgnoreMouseEvents(true, { forward: true });
|
|
68
|
+
};
|
|
43
69
|
const createWindow = async () => {
|
|
44
70
|
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
|
|
45
71
|
// The preload reads these flags; each API is exposed only when its feature is configured.
|
|
@@ -78,10 +104,16 @@ const createWindow = async () => {
|
|
|
78
104
|
mainWindow.setIgnoreMouseEvents(true, { forward: true });
|
|
79
105
|
}
|
|
80
106
|
const events = ["display-metrics-changed", "display-added", "display-removed"];
|
|
81
|
-
|
|
107
|
+
const onDisplayChange = () => { placeOverlay(); keepOnTop(); };
|
|
108
|
+
for (const name of events) screen.on(name, onDisplayChange);
|
|
82
109
|
mainWindow.once("closed", () => {
|
|
83
|
-
for (const name of events) screen.removeListener(name,
|
|
110
|
+
for (const name of events) screen.removeListener(name, onDisplayChange);
|
|
84
111
|
});
|
|
112
|
+
if (process.platform === "win32") {
|
|
113
|
+
const timer = setInterval(() => { keepOnTop(); refreshForwarding(); }, 3000);
|
|
114
|
+
timer.unref?.();
|
|
115
|
+
mainWindow.once("closed", () => clearInterval(timer));
|
|
116
|
+
}
|
|
85
117
|
}
|
|
86
118
|
mainWindow.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
|
|
87
119
|
mainWindow.webContents.on("will-navigate", (event, url) => {
|
|
@@ -111,6 +143,7 @@ const createWindow = async () => {
|
|
|
111
143
|
};
|
|
112
144
|
|
|
113
145
|
app.whenReady().then(async () => {
|
|
146
|
+
if (!hasLock) return;
|
|
114
147
|
Menu.setApplicationMenu(process.platform === "darwin" ? Menu.buildFromTemplate([
|
|
115
148
|
{ role: "appMenu" }, { role: "editMenu" }, { role: "windowMenu" }
|
|
116
149
|
]) : null);
|
|
@@ -156,6 +189,7 @@ app.whenReady().then(async () => {
|
|
|
156
189
|
ipcMain.handle("next2d:window:set-ignore-mouse-events", (event, ignore) => {
|
|
157
190
|
trusted(event);
|
|
158
191
|
if (typeof ignore !== "boolean") throw new TypeError("ignore must be a boolean");
|
|
192
|
+
overlayIgnore = ignore;
|
|
159
193
|
mainWindow.setIgnoreMouseEvents(ignore, ignore ? { forward: true } : undefined);
|
|
160
194
|
});
|
|
161
195
|
ipcMain.handle("next2d:window:get-bounds", (event) => {
|
|
@@ -163,6 +197,17 @@ app.whenReady().then(async () => {
|
|
|
163
197
|
const bounds = mainWindow.getBounds();
|
|
164
198
|
return { ...bounds, scaleFactor: screen.getDisplayMatching(bounds).scaleFactor };
|
|
165
199
|
});
|
|
200
|
+
// After sleep or screen lock the OS can drop the mouse-forwarding hook and reset the window's
|
|
201
|
+
// z-order/bounds. Apply the overlay state again and tell the page so it can recover too.
|
|
202
|
+
const recoverOverlay = () => {
|
|
203
|
+
if (!mainWindow || mainWindow.isDestroyed()) return;
|
|
204
|
+
keepOnTop();
|
|
205
|
+
placeOverlay();
|
|
206
|
+
mainWindow.setIgnoreMouseEvents(false);
|
|
207
|
+
mainWindow.setIgnoreMouseEvents(overlayIgnore, overlayIgnore ? { forward: true } : undefined);
|
|
208
|
+
mainWindow.webContents.send("next2d:window:resume");
|
|
209
|
+
};
|
|
210
|
+
for (const name of ["resume", "unlock-screen"]) powerMonitor.on(name, () => setTimeout(recoverOverlay, 1000));
|
|
166
211
|
ipcMain.handle("next2d:window:quit", (event) => {
|
|
167
212
|
trusted(event);
|
|
168
213
|
// Reply before the window is torn down.
|
|
@@ -175,5 +220,15 @@ app.whenReady().then(async () => {
|
|
|
175
220
|
app.exit(1);
|
|
176
221
|
});
|
|
177
222
|
|
|
223
|
+
if (singleInstance && hasLock) {
|
|
224
|
+
// Launching again does nothing visible; just make sure the overlay is still shown on top.
|
|
225
|
+
app.on("second-instance", () => {
|
|
226
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
227
|
+
mainWindow.setAlwaysOnTop(true, "floating");
|
|
228
|
+
if (!mainWindow.isVisible()) mainWindow.showInactive();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
178
233
|
// Closing the game must stop Steam's "Running" status, including on macOS.
|
|
179
234
|
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
|
}
|