Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next2d/builder",
"version": "0.4.2",
"version": "0.4.3",
"description": "Multi-platform builder for Next2d Framework, supporting export to various platforms such as macOS, Windows, iOS, Android and Web(HTML)",
"author": "Toshiyuki Ienaga <ienaga@next2d.app>",
"license": "MIT",
Expand Down
27 changes: 24 additions & 3 deletions templates/electron/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ const overlayBounds = () => {
const placeOverlay = () => {
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.setBounds(overlayBounds());
};
// The overlay never takes focus, so Windows does not raise it when another topmost window (the taskbar, another
// always-on-top app) is activated, nor after the display is turned off and on. Put it back on top.
const keepOnTop = () => {
if (!mainWindow || mainWindow.isDestroyed()) return;
mainWindow.setAlwaysOnTop(true, "floating");
mainWindow.moveTop();
};
// Windows forwards mouse moves to a click-through window with a system mouse hook, and Electron installs it only
// when forwarding switches on. After sleep (closing the lid) the hook can be gone while Electron still thinks it is
// installed, so the page never sees the pointer. Switching forwarding off and on again installs a fresh hook.
const refreshForwarding = () => {
if (!overlayIgnore || !mainWindow || mainWindow.isDestroyed()) return;
mainWindow.setIgnoreMouseEvents(false);
mainWindow.setIgnoreMouseEvents(true, { forward: true });
};
const createWindow = async () => {
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
// The preload reads these flags; each API is exposed only when its feature is configured.
Expand Down Expand Up @@ -89,10 +104,16 @@ const createWindow = async () => {
mainWindow.setIgnoreMouseEvents(true, { forward: true });
}
const events = ["display-metrics-changed", "display-added", "display-removed"];
for (const name of events) screen.on(name, placeOverlay);
const onDisplayChange = () => { placeOverlay(); keepOnTop(); };
for (const name of events) screen.on(name, onDisplayChange);
mainWindow.once("closed", () => {
for (const name of events) screen.removeListener(name, placeOverlay);
for (const name of events) screen.removeListener(name, onDisplayChange);
});
if (process.platform === "win32") {
const timer = setInterval(() => { keepOnTop(); refreshForwarding(); }, 3000);
timer.unref?.();
mainWindow.once("closed", () => clearInterval(timer));
}
}
mainWindow.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
mainWindow.webContents.on("will-navigate", (event, url) => {
Expand Down Expand Up @@ -180,7 +201,7 @@ app.whenReady().then(async () => {
// z-order/bounds. Apply the overlay state again and tell the page so it can recover too.
const recoverOverlay = () => {
if (!mainWindow || mainWindow.isDestroyed()) return;
mainWindow.setAlwaysOnTop(true, "floating");
keepOnTop();
placeOverlay();
mainWindow.setIgnoreMouseEvents(false);
mainWindow.setIgnoreMouseEvents(overlayIgnore, overlayIgnore ? { forward: true } : undefined);
Expand Down
33 changes: 32 additions & 1 deletion tests/electron-overlay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ const fakeElectron = (options = {}) => {
isFullScreen() { return false; }
isDestroyed() { return false; }
isVisible() { return true; }
moveTop() { calls.push(["moveTop"]); }
showInactive() { calls.push(["showInactive"]); }
once() {}
once(name, cb) { this.onceListeners = { ...this.onceListeners, [name]: cb }; }
async loadURL() {}
}
const electron = {
Expand Down Expand Up @@ -244,3 +245,33 @@ test("a game without the overlay is not limited to one instance", async (t) => {
assert.equal(calls.some(([name]) => name === "requestSingleInstanceLock"), false);
assert.equal(appListeners.has("second-instance"), false);
});

test("on Windows the overlay is put back on top every few seconds and when the displays change", async (t) => {
t.mock.timers.enable({ apis: ["setInterval", "setTimeout"] });
const platform = Object.getOwnPropertyDescriptor(process, "platform");
Object.defineProperty(process, "platform", { value: "win32" });
t.after(() => Object.defineProperty(process, "platform", platform));
const { calls, listeners, handlers, windows } = await startHost(t, { window: { overlay: { enabled: true, clickThrough: true } } });
calls.length = 0;
t.mock.timers.tick(3000);
assert.deepEqual(calls.filter(([name]) => /^(setAlwaysOnTop|moveTop)$/.test(name)), [["setAlwaysOnTop", true, "floating"], ["moveTop"]]);
// Click-through is on: the mouse-forwarding hook is installed again (off, then on).
assert.deepEqual(calls.filter(([name]) => name === "setIgnoreMouseEvents"), [["setIgnoreMouseEvents", false], ["setIgnoreMouseEvents", true, { forward: true }]]);
// While the page captures the mouse (pointer over the pet) nothing is toggled under it.
handlers.get("next2d:window:set-ignore-mouse-events")({ sender: windows[0].webContents, senderFrame: Object.assign(windows[0].webContents.mainFrame, { url: "next2d://game/index.html" }) }, false);
calls.length = 0;
t.mock.timers.tick(3000);
assert.equal(calls.some(([name]) => name === "setIgnoreMouseEvents"), false);
calls.length = 0;
listeners.get("display-added")();
assert.deepEqual(calls.filter(([name]) => /^(setAlwaysOnTop|moveTop)$/.test(name)), [["setAlwaysOnTop", true, "floating"], ["moveTop"]]);
});

test("on other platforms there is no periodic raise", async (t) => {
t.mock.timers.enable({ apis: ["setInterval", "setTimeout"] });
if (process.platform === "win32") return;
const { calls } = await startHost(t, { window: { overlay: { enabled: true, clickThrough: true } } });
calls.length = 0;
t.mock.timers.tick(10000);
assert.equal(calls.some(([name]) => name === "moveTop"), false);
});
Loading