@next2d/builder 0.4.8 → 0.4.9
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.d.ts +1 -0
- package/dist/electron-config.js +5 -2
- package/docs/steam.md +46 -2
- package/package.json +4 -4
- package/templates/electron/index.js +111 -7
- package/templates/electron/preload.cjs +11 -0
package/dist/electron-config.js
CHANGED
|
@@ -80,8 +80,8 @@ export const readElectronConfig = (root) => {
|
|
|
80
80
|
if (!mini || typeof mini !== "object" || Array.isArray(mini)) {
|
|
81
81
|
throw new Error("electron.config.json window.mini must be an object.");
|
|
82
82
|
}
|
|
83
|
-
config.window.mini = { "width": 720, "height": 180, "anchor": "bottom-right", "margin": 16, "alwaysOnTop": true, ...mini };
|
|
84
|
-
const { width, height, anchor, margin, alwaysOnTop } = config.window.mini;
|
|
83
|
+
config.window.mini = { "width": 720, "height": 180, "anchor": "bottom-right", "margin": 16, "alwaysOnTop": true, "transparent": false, ...mini };
|
|
84
|
+
const { width, height, anchor, margin, alwaysOnTop, transparent } = config.window.mini;
|
|
85
85
|
for (const dimension of [width, height]) {
|
|
86
86
|
if (!Number.isInteger(dimension) || dimension < 1 || dimension > 16384) {
|
|
87
87
|
throw new Error("electron.config.json window.mini.width/height must be integers between 1 and 16384.");
|
|
@@ -96,6 +96,9 @@ export const readElectronConfig = (root) => {
|
|
|
96
96
|
if (typeof alwaysOnTop !== "boolean") {
|
|
97
97
|
throw new Error("electron.config.json window.mini.alwaysOnTop must be a boolean.");
|
|
98
98
|
}
|
|
99
|
+
if (typeof transparent !== "boolean") {
|
|
100
|
+
throw new Error("electron.config.json window.mini.transparent must be a boolean.");
|
|
101
|
+
}
|
|
99
102
|
// The overlay already is the small always-on-top window, and it never takes keyboard focus.
|
|
100
103
|
if (config.window.overlay?.enabled) {
|
|
101
104
|
throw new Error("electron.config.json window.mini cannot be combined with window.overlay.enabled.");
|
package/docs/steam.md
CHANGED
|
@@ -118,7 +118,7 @@ Linuxの透明表示はコンポジタが必要です。
|
|
|
118
118
|
```json
|
|
119
119
|
"window": {
|
|
120
120
|
"width": 1920, "height": 1080, "fullscreen": true,
|
|
121
|
-
"mini": { "width": 720, "height": 180, "anchor": "bottom-right", "margin": 16, "alwaysOnTop": true }
|
|
121
|
+
"mini": { "width": 720, "height": 180, "anchor": "bottom-right", "margin": 16, "alwaysOnTop": true, "transparent": false }
|
|
122
122
|
}
|
|
123
123
|
```
|
|
124
124
|
|
|
@@ -128,6 +128,7 @@ Linuxの透明表示はコンポジタが必要です。
|
|
|
128
128
|
| `anchor` | 初めて小窓にしたときの位置。`"bottom-right"`(既定)/ `"bottom-left"` / `"top-right"` / `"top-left"`。窓のある画面の作業領域(タスクバー・Dockを除く)の隅 |
|
|
129
129
|
| `margin` | 隅からの間隔(px)。既定 `16` |
|
|
130
130
|
| `alwaysOnTop` | `true`(既定)で小窓を最前面に置く(macOSは全ワークスペース・全画面アプリ上にも表示) |
|
|
131
|
+
| `transparent` | `true` で背景が透明・枠なしの小窓にする(既定 `false`。下の「透明な小窓」) |
|
|
131
132
|
|
|
132
133
|
小窓は通常の窓のまま(タイトルバーあり。タイトルバーで動かせる)で、キー入力も受け取ります。リサイズ・最大化・全画面はできません。動かした位置は、アプリを終了するまで次の小窓で使います。通常の窓に戻すと、小窓にする前の位置・大きさ・最大化・全画面に戻ります。小窓の間は F11/Alt+Enter/Esc で全画面を切り替えません(Esc はゲームに届きます)。
|
|
133
134
|
|
|
@@ -143,6 +144,27 @@ await window.next2dWindow.quit();
|
|
|
143
144
|
|
|
144
145
|
`width` / `height` は窓の中身の大きさ(CSS ピクセル)です。Next2D のステージは縦横比を保ったまま窓に合わせて縮むため、1920×1080 のステージを 720×180 の小窓で表示すると、ステージの範囲は高さ 180・幅 320 になります(`options.fullScreen: true` なら、左右のステージの外も描かれます)。小窓用の表示は、この大きさに合わせてゲーム側で配置してください。
|
|
145
146
|
|
|
147
|
+
##### 透明な小窓(`transparent: true`)
|
|
148
|
+
|
|
149
|
+
`window.mini.transparent: true`(既定 `false`。`false` なら上の動作のまま)にすると、小窓が「背景が透明・枠なし・影なし」の窓になり、船などの絵だけがデスクトップに浮かびます。透明は窓の作成時にしか決められず(Windows では透明な窓を最大化・全画面にできません)、同じ窓の切り替えでは実現できないため、`setMode` は**窓を作り直します**。
|
|
150
|
+
|
|
151
|
+
- `setMode("mini")`:透明・枠なし・リサイズ不可の新しい窓を `width`×`height` で `anchor`/`margin`(前回動かした位置があればそこ)に作って同じ URL を読み込み、通常の窓の状態(位置・大きさ・最大化・全画面)を記憶して閉じます。最前面(`alwaysOnTop: true`)、macOS は全ワークスペースに表示、キー入力は受け取ります(F10 などで戻せます)。
|
|
152
|
+
- `setMode("full")`:記憶した状態(全画面を含む)の通常の窓を作り直し、小窓を閉じます。
|
|
153
|
+
- ページは作り直しで破棄されます。**ゲームは切り替える前に自分で保存してください**(localStorage は同じ origin のまま引き継がれます)。新しいページは起動時に `getMode()` で今のモードを読んで表示を決めます(アプリ起動時は常に `"full"`)。
|
|
154
|
+
- `setMode` の Promise は、新しい窓の読み込みが終わってから `{ mode, width, height }` で解決します。
|
|
155
|
+
|
|
156
|
+
透明な小窓(`mode === "mini"`)の間だけ、次の API が `window.next2dWindow` に加わります(`transparent: false` では公開されません)。
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// 作成直後は ignore=true:透明な部分は下のアプリへクリックを通し、マウスの移動だけページに届く。
|
|
160
|
+
// 船の上にポインタがあるときだけ false にして、クリック・ドラッグを受け取る。
|
|
161
|
+
await window.next2dWindow.setIgnoreMouseEvents(false);
|
|
162
|
+
// 枠がないので、ゲーム側のドラッグで窓を動かす。作業領域の外には出ない。位置は次の小窓にも使う。
|
|
163
|
+
await window.next2dWindow.moveBy(dx, dy);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Windows でスリープから復帰してもマウス転送が切れないよう、重ね表示と同様に転送フックを張り直します。Linux の透明表示にはコンポジタが必要です。
|
|
167
|
+
|
|
146
168
|
### STEP4:認証を用意する
|
|
147
169
|
|
|
148
170
|
#### macOSの署名・公証
|
|
@@ -372,7 +394,7 @@ At run time the game can switch between its normal window (fullscreen included)
|
|
|
372
394
|
```json
|
|
373
395
|
"window": {
|
|
374
396
|
"width": 1920, "height": 1080, "fullscreen": true,
|
|
375
|
-
"mini": { "width": 720, "height": 180, "anchor": "bottom-right", "margin": 16, "alwaysOnTop": true }
|
|
397
|
+
"mini": { "width": 720, "height": 180, "anchor": "bottom-right", "margin": 16, "alwaysOnTop": true, "transparent": false }
|
|
376
398
|
}
|
|
377
399
|
```
|
|
378
400
|
|
|
@@ -382,6 +404,7 @@ At run time the game can switch between its normal window (fullscreen included)
|
|
|
382
404
|
| `anchor` | Where the small window first appears: `"bottom-right"` (default), `"bottom-left"`, `"top-right"` or `"top-left"` corner of the work area (taskbar/Dock excluded) of the display the window is on. |
|
|
383
405
|
| `margin` | Distance from that corner in px. Default `16`. |
|
|
384
406
|
| `alwaysOnTop` | `true` (default) keeps the small window on top (on macOS also on every workspace and above fullscreen apps). |
|
|
407
|
+
| `transparent` | `true` makes it a transparent, frameless window (default `false`; see "Transparent small window" below). |
|
|
385
408
|
|
|
386
409
|
The small window stays a normal window: it has a title bar (drag it to move the window) and takes keyboard input. It cannot be resized, maximized or made fullscreen. Where the player moves it is used for the next small window until the app quits. Switching back restores the position, size, maximized and fullscreen state from before. In the small window F11 / Alt+Enter / Escape do not toggle fullscreen (Escape reaches the game).
|
|
387
410
|
|
|
@@ -397,6 +420,27 @@ await window.next2dWindow.quit();
|
|
|
397
420
|
|
|
398
421
|
`width` / `height` are the content size in CSS pixels. The Next2D stage keeps its aspect ratio when it is fitted into the window, so a 1920×1080 stage shown in a 720×180 window covers 320×180 (with `options.fullScreen: true` the area left and right of the stage is drawn too). Lay out the small-window view for that size in the game.
|
|
399
422
|
|
|
423
|
+
##### Transparent small window (`transparent: true`)
|
|
424
|
+
|
|
425
|
+
With `window.mini.transparent: true` (default `false`; `false` keeps the behaviour above) the small window becomes transparent, frameless and shadowless, so only what the game draws (a ship, say) floats on the desktop. Transparency can only be chosen when a window is created (and on Windows a transparent window cannot be maximized or made fullscreen), so one window cannot be reshaped into it: `setMode` **rebuilds the window**.
|
|
426
|
+
|
|
427
|
+
- `setMode("mini")`: builds a new transparent, frameless, non-resizable window of `width`×`height` at `anchor`/`margin` (or where it was last moved), loads the same URL, remembers the state of the normal window (position, size, maximized, fullscreen) and closes it. It is always on top (`alwaysOnTop: true`), on every workspace on macOS, and takes keys (so a key such as F10 can switch back).
|
|
428
|
+
- `setMode("full")`: rebuilds the normal window in the remembered state (fullscreen included) and closes the small one.
|
|
429
|
+
- The page is destroyed by the rebuild, so **the game must save before switching** (localStorage carries over: same origin). The new page reads the current mode with `getMode()` at startup (the app always starts in `"full"`).
|
|
430
|
+
- The promise of `setMode` resolves with `{ mode, width, height }` after the new window has finished loading.
|
|
431
|
+
|
|
432
|
+
Only while the transparent small window is shown (`mode === "mini"`), `window.next2dWindow` also has these (not exposed when `transparent` is `false`):
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
// ignore=true right after creation: transparent parts let clicks fall through to the app below, and only mouse
|
|
436
|
+
// moves reach the page. Set it to false while the pointer is over the ship to receive clicks and drags.
|
|
437
|
+
await window.next2dWindow.setIgnoreMouseEvents(false);
|
|
438
|
+
// There is no frame, so the game drags the window itself. It stays inside the work area; the position is reused by the next small window.
|
|
439
|
+
await window.next2dWindow.moveBy(dx, dy);
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Like the overlay, the mouse-forwarding hook is installed again after Windows resumes from sleep. Transparency on Linux requires a compositor.
|
|
443
|
+
|
|
400
444
|
### STEP4: Prepare credentials
|
|
401
445
|
|
|
402
446
|
#### macOS signing and notarization
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@next2d/builder",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
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",
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
"@eslint/eslintrc": "^3.3.7",
|
|
44
44
|
"@eslint/js": "^10.0.1",
|
|
45
45
|
"@types/node": "^26.6.6",
|
|
46
|
-
"@typescript-eslint/eslint-plugin": "^8.72.
|
|
47
|
-
"@typescript-eslint/parser": "^8.72.
|
|
46
|
+
"@typescript-eslint/eslint-plugin": "^8.72.2",
|
|
47
|
+
"@typescript-eslint/parser": "^8.72.2",
|
|
48
48
|
"eslint": "^10.14.0",
|
|
49
49
|
"eslint-plugin-unused-imports": "^4.4.1",
|
|
50
50
|
"globals": "^17.14.0",
|
|
51
51
|
"typescript": "^6.0.3",
|
|
52
|
-
"vite": "^8.3.
|
|
52
|
+
"vite": "^8.3.8"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -36,12 +36,17 @@ const startURL = "next2d://game/index.html";
|
|
|
36
36
|
const overlay = config.window.overlay?.enabled ? config.window.overlay : null;
|
|
37
37
|
// The game can switch between its normal window ("full") and a small always-on-top window ("mini") at run time.
|
|
38
38
|
const mini = !overlay && config.window.mini ? config.window.mini : null;
|
|
39
|
+
// mini.transparent: the small window is a different window (transparent, frameless), so a mode change builds a new
|
|
40
|
+
// one instead of reshaping the same one. The page is gone after a change; the game saves before it switches.
|
|
41
|
+
const miniTransparent = Boolean(mini?.transparent);
|
|
39
42
|
let mainWindow;
|
|
40
43
|
let windowMode = "full";
|
|
41
44
|
// How the window looked before it became small, to put it back exactly (position, size, maximized, fullscreen).
|
|
42
45
|
let fullState = null;
|
|
43
46
|
// Where the player last left the small window (it has a title bar and can be moved).
|
|
44
47
|
let miniPosition = null;
|
|
48
|
+
// Transparent mini only: true while the window lets clicks fall through (mouse moves are still forwarded to the page).
|
|
49
|
+
let miniIgnore = true;
|
|
45
50
|
// Mode changes are applied one after another (leaving fullscreen is animated on macOS).
|
|
46
51
|
let modeQueue = Promise.resolve();
|
|
47
52
|
const alive = () => Boolean(mainWindow && !mainWindow.isDestroyed());
|
|
@@ -65,9 +70,7 @@ const setFullScreenAndWait = (win, value) => new Promise((resolve) => {
|
|
|
65
70
|
win.once(value ? "enter-full-screen" : "leave-full-screen", finish);
|
|
66
71
|
win.setFullScreen(value);
|
|
67
72
|
});
|
|
68
|
-
const
|
|
69
|
-
const { width, height } = win.getBounds();
|
|
70
|
-
const { workArea } = screen.getDisplayMatching(win.getBounds());
|
|
73
|
+
const cornerIn = (workArea, width, height) => {
|
|
71
74
|
const right = mini.anchor.endsWith("right");
|
|
72
75
|
const bottom = mini.anchor.startsWith("bottom");
|
|
73
76
|
return {
|
|
@@ -75,6 +78,24 @@ const cornerOf = (win) => {
|
|
|
75
78
|
y: bottom ? workArea.y + workArea.height - height - mini.margin : workArea.y + mini.margin
|
|
76
79
|
};
|
|
77
80
|
};
|
|
81
|
+
const cornerOf = (win) => {
|
|
82
|
+
const { width, height } = win.getBounds();
|
|
83
|
+
return cornerIn(screen.getDisplayMatching(win.getBounds()).workArea, width, height);
|
|
84
|
+
};
|
|
85
|
+
// Transparent mini: a frameless window has no title bar, so its outer size is mini.width x mini.height.
|
|
86
|
+
const miniWindowBounds = (display) => {
|
|
87
|
+
const { x, y } = miniPosition ?? cornerIn(display.workArea, mini.width, mini.height);
|
|
88
|
+
return { x, y, width: mini.width, height: mini.height };
|
|
89
|
+
};
|
|
90
|
+
// Keep the small window inside the work area of the display it is on.
|
|
91
|
+
const clampToWorkArea = (bounds) => {
|
|
92
|
+
const { workArea } = screen.getDisplayMatching(bounds);
|
|
93
|
+
return {
|
|
94
|
+
...bounds,
|
|
95
|
+
x: Math.round(Math.max(workArea.x, Math.min(bounds.x, workArea.x + workArea.width - bounds.width))),
|
|
96
|
+
y: Math.round(Math.max(workArea.y, Math.min(bounds.y, workArea.y + workArea.height - bounds.height)))
|
|
97
|
+
};
|
|
98
|
+
};
|
|
78
99
|
const toMini = async () => {
|
|
79
100
|
const win = mainWindow;
|
|
80
101
|
fullState = { bounds: win.getNormalBounds(), maximized: win.isMaximized(), fullScreen: win.isFullScreen() };
|
|
@@ -112,10 +133,42 @@ const toFull = async () => {
|
|
|
112
133
|
win.focus();
|
|
113
134
|
windowMode = "full";
|
|
114
135
|
};
|
|
136
|
+
// Transparent mini: build the other kind of window first (so the app never has zero windows), then close the old one.
|
|
137
|
+
const swapWindow = async (mode) => {
|
|
138
|
+
const old = mainWindow;
|
|
139
|
+
const previous = windowMode;
|
|
140
|
+
if (mode === "mini") {
|
|
141
|
+
fullState = { bounds: old.getNormalBounds(), maximized: old.isMaximized(), fullScreen: old.isFullScreen() };
|
|
142
|
+
// Do not close a window that is in a fullscreen Space on macOS.
|
|
143
|
+
if (old.isFullScreen()) await setFullScreenAndWait(old, false);
|
|
144
|
+
miniIgnore = true;
|
|
145
|
+
} else {
|
|
146
|
+
const [x, y] = old.getPosition();
|
|
147
|
+
miniPosition = { x, y };
|
|
148
|
+
}
|
|
149
|
+
// The new page reads the mode with getMode() when it starts.
|
|
150
|
+
windowMode = mode;
|
|
151
|
+
const retire = (win) => {
|
|
152
|
+
if (win.isDestroyed()) return;
|
|
153
|
+
// Closing on purpose: do not treat the page going away as a crash.
|
|
154
|
+
win.webContents.removeAllListeners("render-process-gone");
|
|
155
|
+
win.destroy();
|
|
156
|
+
};
|
|
157
|
+
try {
|
|
158
|
+
await createWindow();
|
|
159
|
+
} catch (error) {
|
|
160
|
+
windowMode = previous;
|
|
161
|
+
const failed = mainWindow;
|
|
162
|
+
mainWindow = old;
|
|
163
|
+
if (failed && failed !== old) retire(failed);
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
retire(old);
|
|
167
|
+
};
|
|
115
168
|
const setWindowMode = (mode) => {
|
|
116
169
|
modeQueue = modeQueue.then(async () => {
|
|
117
170
|
if (!alive() || mode === windowMode) return;
|
|
118
|
-
await (mode === "mini" ? toMini() : toFull());
|
|
171
|
+
await (miniTransparent ? swapWindow(mode) : mode === "mini" ? toMini() : toFull());
|
|
119
172
|
mainWindow.webContents.send("next2d:window:mode", modeInfo());
|
|
120
173
|
}).catch((error) => console.error("Window mode change failed:", error));
|
|
121
174
|
return modeQueue.then(() => (alive() ? modeInfo() : { mode: windowMode, width: 0, height: 0 }));
|
|
@@ -147,7 +200,8 @@ const keepOnTop = () => {
|
|
|
147
200
|
// when forwarding switches on. After sleep (closing the lid) the hook can be gone while Electron still thinks it is
|
|
148
201
|
// installed, so the page never sees the pointer. Switching forwarding off and on again installs a fresh hook.
|
|
149
202
|
const refreshForwarding = () => {
|
|
150
|
-
|
|
203
|
+
const forwarding = overlay ? overlayIgnore : miniTransparent && windowMode === "mini" && miniIgnore;
|
|
204
|
+
if (!forwarding || !mainWindow || mainWindow.isDestroyed()) return;
|
|
151
205
|
mainWindow.setIgnoreMouseEvents(false);
|
|
152
206
|
mainWindow.setIgnoreMouseEvents(true, { forward: true });
|
|
153
207
|
};
|
|
@@ -211,17 +265,21 @@ const recreateWindow = async () => {
|
|
|
211
265
|
};
|
|
212
266
|
const createWindow = async () => {
|
|
213
267
|
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
|
|
268
|
+
const miniWindow = miniTransparent && windowMode === "mini";
|
|
269
|
+
// Coming back to the normal window after a transparent mini: put it back as it was.
|
|
270
|
+
const restore = miniTransparent && windowMode === "full" ? fullState : null;
|
|
214
271
|
// The preload reads these flags; each API is exposed only when its feature is configured.
|
|
215
272
|
const preloadFlags = [
|
|
216
273
|
...(config.nativeBridge ? ["--next2d-native-bridge"] : []),
|
|
217
274
|
...(overlay ? ["--next2d-overlay"] : []),
|
|
218
|
-
...(mini ? ["--next2d-window-mode"] : [])
|
|
275
|
+
...(mini ? ["--next2d-window-mode"] : []),
|
|
276
|
+
...(miniWindow ? ["--next2d-mini-transparent"] : [])
|
|
219
277
|
];
|
|
220
278
|
mainWindow = new BrowserWindow({
|
|
221
279
|
width: Math.min(config.window.width, width),
|
|
222
280
|
height: Math.min(config.window.height, height),
|
|
223
281
|
title: config.appName,
|
|
224
|
-
fullscreen: overlay ? false : config.window.fullscreen,
|
|
282
|
+
fullscreen: overlay || miniWindow ? false : restore ? restore.fullScreen : config.window.fullscreen,
|
|
225
283
|
...(config.icon ? { icon: path.join(assetRoot, config.icon) } : {}),
|
|
226
284
|
useContentSize: true,
|
|
227
285
|
backgroundColor: "#000000",
|
|
@@ -233,12 +291,35 @@ const createWindow = async () => {
|
|
|
233
291
|
// The overlay must never take keyboard focus or activation from the apps under it (Word etc.).
|
|
234
292
|
focusable: false
|
|
235
293
|
} : {}),
|
|
294
|
+
...(miniWindow ? {
|
|
295
|
+
...miniWindowBounds(screen.getDisplayMatching(mainWindow && !mainWindow.isDestroyed() ? mainWindow.getBounds() : screen.getPrimaryDisplay().bounds)),
|
|
296
|
+
transparent: true, frame: false, resizable: false, maximizable: false, hasShadow: false,
|
|
297
|
+
fullscreenable: false, backgroundColor: "#00000000",
|
|
298
|
+
// Unlike the overlay it takes keys (the game's key to go back to the normal window).
|
|
299
|
+
focusable: true
|
|
300
|
+
} : {}),
|
|
236
301
|
webPreferences: {
|
|
237
302
|
nodeIntegration: false, contextIsolation: true, sandbox: true,
|
|
238
303
|
...(preloadFlags.length ? { preload: path.join(__dirname, "preload.cjs"), additionalArguments: preloadFlags } : {}),
|
|
239
304
|
...(config.nativeBridge ? { backgroundThrottling: config.nativeBridge.backgroundThrottling ?? false } : {})
|
|
240
305
|
}
|
|
241
306
|
});
|
|
307
|
+
if (restore) {
|
|
308
|
+
mainWindow.setBounds(restore.bounds);
|
|
309
|
+
if (restore.maximized) mainWindow.maximize();
|
|
310
|
+
}
|
|
311
|
+
if (miniWindow) {
|
|
312
|
+
if (mini.alwaysOnTop) {
|
|
313
|
+
mainWindow.setAlwaysOnTop(true, "floating");
|
|
314
|
+
if (process.platform === "darwin") mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
|
|
315
|
+
}
|
|
316
|
+
mainWindow.setIgnoreMouseEvents(true, { forward: true });
|
|
317
|
+
if (process.platform === "win32") {
|
|
318
|
+
const timer = setInterval(() => { if (mini.alwaysOnTop) keepOnTop(); refreshForwarding(); }, 3000);
|
|
319
|
+
timer.unref?.();
|
|
320
|
+
mainWindow.once("closed", () => clearInterval(timer));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
242
323
|
if (overlay) {
|
|
243
324
|
mainWindow.setAlwaysOnTop(true, "floating");
|
|
244
325
|
if (process.platform === "darwin") {
|
|
@@ -375,6 +456,29 @@ app.whenReady().then(async () => {
|
|
|
375
456
|
return setWindowMode(mode);
|
|
376
457
|
});
|
|
377
458
|
}
|
|
459
|
+
if (miniTransparent) {
|
|
460
|
+
const inTransparentMini = () => {
|
|
461
|
+
if (windowMode !== "mini") throw new Error("Only available in the transparent mini window");
|
|
462
|
+
};
|
|
463
|
+
ipcMain.handle("next2d:window:set-ignore-mouse-events", (event, ignore) => {
|
|
464
|
+
trusted(event);
|
|
465
|
+
inTransparentMini();
|
|
466
|
+
if (typeof ignore !== "boolean") throw new TypeError("ignore must be a boolean");
|
|
467
|
+
miniIgnore = ignore;
|
|
468
|
+
mainWindow.setIgnoreMouseEvents(ignore, ignore ? { forward: true } : undefined);
|
|
469
|
+
});
|
|
470
|
+
ipcMain.handle("next2d:window:move-by", (event, dx, dy) => {
|
|
471
|
+
trusted(event);
|
|
472
|
+
inTransparentMini();
|
|
473
|
+
if (!Number.isFinite(dx) || !Number.isFinite(dy)) throw new TypeError("dx and dy must be finite numbers");
|
|
474
|
+
const bounds = mainWindow.getBounds();
|
|
475
|
+
const next = clampToWorkArea({ ...bounds, x: bounds.x + Math.round(dx), y: bounds.y + Math.round(dy) });
|
|
476
|
+
mainWindow.setBounds(next);
|
|
477
|
+
miniPosition = { x: next.x, y: next.y };
|
|
478
|
+
});
|
|
479
|
+
// After sleep the forwarding hook can be gone (Windows); install it again.
|
|
480
|
+
for (const name of ["resume", "unlock-screen"]) powerMonitor.on(name, () => setTimeout(refreshForwarding, 1000));
|
|
481
|
+
}
|
|
378
482
|
if (overlay) {
|
|
379
483
|
ipcMain.handle("next2d:window:set-ignore-mouse-events", (event, ignore) => {
|
|
380
484
|
trusted(event);
|
|
@@ -36,6 +36,17 @@ if (flags.has("--next2d-overlay")) {
|
|
|
36
36
|
// Never set together with the overlay (the config is rejected), so the two never expose next2dWindow twice.
|
|
37
37
|
if (flags.has("--next2d-window-mode")) {
|
|
38
38
|
contextBridge.exposeInMainWorld("next2dWindow", {
|
|
39
|
+
// Only in the transparent mini window (window.mini.transparent): click-through and moving the frameless window.
|
|
40
|
+
...(flags.has("--next2d-mini-transparent") ? {
|
|
41
|
+
setIgnoreMouseEvents: async (ignore) => {
|
|
42
|
+
if (typeof ignore !== "boolean") throw new TypeError("Expected boolean");
|
|
43
|
+
await ipcRenderer.invoke("next2d:window:set-ignore-mouse-events", ignore);
|
|
44
|
+
},
|
|
45
|
+
moveBy: async (dx, dy) => {
|
|
46
|
+
if (!Number.isFinite(dx) || !Number.isFinite(dy)) throw new TypeError("Expected finite numbers");
|
|
47
|
+
await ipcRenderer.invoke("next2d:window:move-by", dx, dy);
|
|
48
|
+
}
|
|
49
|
+
} : {}),
|
|
39
50
|
getMode: () => ipcRenderer.invoke("next2d:window:get-mode"),
|
|
40
51
|
setMode: async (mode) => {
|
|
41
52
|
if (mode !== "full" && mode !== "mini") throw new TypeError("Expected \"full\" or \"mini\"");
|