fix: window race conditions #102

This commit is contained in:
leukipp
2025-10-27 16:26:20 +01:00
parent e6d8c537f2
commit 81b4c30aa2
2 changed files with 137 additions and 59 deletions
+16 -10
View File
@@ -102,14 +102,15 @@ const init = async () => {
INTEGRATION.initialized = true; INTEGRATION.initialized = true;
// Register global events // Register global events
EVENTS.on("updateDisplay", () => { EVENTS.on("updateApp", updateApp);
updateDisplay();
updateLastActive();
});
EVENTS.on("updateStatus", updateKiosk); EVENTS.on("updateStatus", updateKiosk);
EVENTS.on("updateTheme", updateTheme); EVENTS.on("updateTheme", updateTheme);
EVENTS.on("updateVolume", updateVolume); EVENTS.on("updateVolume", updateVolume);
EVENTS.on("updateKeyboard", updateKeyboard); EVENTS.on("updateKeyboard", updateKeyboard);
EVENTS.on("updateDisplay", () => {
updateDisplay();
updateLastActive();
});
EVENTS.on("consoleLog", updateErrors); EVENTS.on("consoleLog", updateErrors);
}) })
.on("error", (error) => { .on("error", (error) => {
@@ -139,7 +140,6 @@ const init = async () => {
if (APP.exiting) { if (APP.exiting) {
return; return;
} }
updateApp();
updatePackageUpgrades(); updatePackageUpgrades();
}, 3600 * 1000); }, 3600 * 1000);
@@ -499,8 +499,8 @@ const initDisplay = () => {
const updateDisplay = async () => { const updateDisplay = async () => {
const status = hardware.getDisplayStatus(); const status = hardware.getDisplayStatus();
const brightness = hardware.getDisplayBrightness(); const brightness = hardware.getDisplayBrightness();
publishState("display/power", status);
publishState("display/brightness", brightness); publishState("display/brightness", brightness);
publishState("display/power", status);
}; };
/** /**
@@ -610,10 +610,12 @@ const initPageNumber = () => {
.on("message", (topic, message) => { .on("message", (topic, message) => {
if (topic === config.command_topic) { if (topic === config.command_topic) {
const number = parseInt(message, 10); const number = parseInt(message, 10);
if (WEBVIEW.viewActive && number) {
console.verbose("Set Page Number:", number); console.verbose("Set Page Number:", number);
WEBVIEW.viewActive = number || 1; WEBVIEW.viewActive = number;
EVENTS.emit("updateView"); EVENTS.emit("updateView");
} }
}
}) })
.subscribe(config.command_topic); .subscribe(config.command_topic);
updatePageNumber(); updatePageNumber();
@@ -650,10 +652,12 @@ const initPageZoom = () => {
.on("message", (topic, message) => { .on("message", (topic, message) => {
if (topic === config.command_topic) { if (topic === config.command_topic) {
const zoom = parseInt(message, 10); const zoom = parseInt(message, 10);
if (WEBVIEW.viewActive && zoom) {
console.verbose("Set Page Zoom:", zoom); console.verbose("Set Page Zoom:", zoom);
WEBVIEW.views[WEBVIEW.viewActive || 1].webContents.setZoomFactor(zoom / 100.0); WEBVIEW.views[WEBVIEW.viewActive].webContents.setZoomFactor(zoom / 100.0);
EVENTS.emit("updateView"); EVENTS.emit("updateView");
} }
}
}) })
.subscribe(config.command_topic); .subscribe(config.command_topic);
updatePageZoom(); updatePageZoom();
@@ -686,8 +690,10 @@ const initPageUrl = () => {
.on("message", (topic, message) => { .on("message", (topic, message) => {
if (topic === config.command_topic) { if (topic === config.command_topic) {
const url = message.toString(); const url = message.toString();
if (WEBVIEW.viewActive && url) {
console.verbose("Set Page Url:", url); console.verbose("Set Page Url:", url);
WEBVIEW.views[WEBVIEW.viewActive || 1].webContents.loadURL(url); WEBVIEW.views[WEBVIEW.viewActive].webContents.loadURL(url);
}
} }
}) })
.subscribe(config.command_topic); .subscribe(config.command_topic);
@@ -701,7 +707,7 @@ const updatePageUrl = async () => {
const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive || 1]; const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive || 1];
const currentUrl = WEBVIEW.views[WEBVIEW.viewActive || 1].webContents.getURL(); const currentUrl = WEBVIEW.views[WEBVIEW.viewActive || 1].webContents.getURL();
const pageUrl = !currentUrl || currentUrl.startsWith("data:") ? defaultUrl : currentUrl; const pageUrl = !currentUrl || currentUrl.startsWith("data:") ? defaultUrl : currentUrl;
publishState("page_url", pageUrl); publishState("page_url", pageUrl.length < 255 ? pageUrl : null);
}; };
/** /**
+114 -42
View File
@@ -59,11 +59,18 @@ const init = async () => {
WEBVIEW.viewActive = 0; WEBVIEW.viewActive = 0;
WEBVIEW.viewUrls = urls; WEBVIEW.viewUrls = urls;
WEBVIEW.viewZoom = zoom; WEBVIEW.viewZoom = zoom;
WEBVIEW.statusEnabled = true; WEBVIEW.statusEnabled = !debug;
WEBVIEW.pagerEnabled = widget; WEBVIEW.pagerEnabled = widget;
WEBVIEW.widgetEnabled = widget; WEBVIEW.widgetEnabled = widget;
WEBVIEW.navigationEnabled = widget; WEBVIEW.navigationEnabled = widget;
// Init global display
const display = screen.getPrimaryDisplay();
WEBVIEW.display = display ? display.workAreaSize : {};
if (!WEBVIEW.display.width || !WEBVIEW.display.height) {
WEBVIEW.display = { width: 800, height: 600 };
}
// Init global theme // Init global theme
WEBVIEW.theme = { WEBVIEW.theme = {
set: (name) => { set: (name) => {
@@ -83,19 +90,19 @@ const init = async () => {
WEBVIEW.theme.set(theme); WEBVIEW.theme.set(theme);
// Init global root window // Init global root window
WEBVIEW.display = screen.getPrimaryDisplay().workAreaSize;
WEBVIEW.window = new BaseWindow({ WEBVIEW.window = new BaseWindow({
title: APP.title, title: APP.title,
icon: APP.icon, icon: APP.icon,
fullscreen: !debug,
autoHideMenuBar: true, autoHideMenuBar: true,
frame: debug || !WEBVIEW.statusEnabled, frame: !WEBVIEW.statusEnabled,
width: Math.floor(WEBVIEW.display.width * 0.9), width: Math.floor(WEBVIEW.display.width * 0.85),
height: Math.floor(WEBVIEW.display.height * 0.9), height: Math.floor(WEBVIEW.display.height * 0.75),
minWidth: 136, minWidth: 136,
minHeight: 136, minHeight: 136,
}); });
WEBVIEW.window.setMenuBarVisibility(false); const size = WEBVIEW.window.getBounds();
console.info(`Open Window: ${size.width}x${size.height}+${size.x}+${size.y}`);
updateTheme();
// Init global webview // Init global webview
WEBVIEW.views = []; WEBVIEW.views = [];
@@ -186,6 +193,9 @@ const update = async () => {
// Update window status // Update window status
updateStatus(); updateStatus();
// Update pager status
updatePager();
// Update widget status // Update widget status
updateWidget(); updateWidget();
@@ -200,6 +210,9 @@ const update = async () => {
* Updates the application theme. * Updates the application theme.
*/ */
const updateTheme = () => { const updateTheme = () => {
if (APP.exiting) {
return;
}
const previous = WEBVIEW.statusTheme; const previous = WEBVIEW.statusTheme;
const theme = WEBVIEW.theme.get(); const theme = WEBVIEW.theme.get();
@@ -208,10 +221,12 @@ const updateTheme = () => {
console.info("Update Application Theme:", theme); console.info("Update Application Theme:", theme);
} }
WEBVIEW.statusTheme = theme; WEBVIEW.statusTheme = theme;
WEBVIEW.pagerTheme = theme;
WEBVIEW.widgetTheme = theme; WEBVIEW.widgetTheme = theme;
WEBVIEW.navigationTheme = theme; WEBVIEW.navigationTheme = theme;
// Update controls theme // Update controls theme
WEBVIEW.window.setMenuBarVisibility(false);
if (WEBVIEW.initialized) { if (WEBVIEW.initialized) {
resizeView(); resizeView();
} }
@@ -268,16 +283,25 @@ const updateStatus = () => {
EVENTS.emit("updateStatus"); EVENTS.emit("updateStatus");
}; };
/**
* Updates the pager control.
*/
const updatePager = () => {
// Disable pager buttons
WEBVIEW.pager.webContents.send("button-disabled", {
id: "previous",
disabled: WEBVIEW.viewActive <= 1,
});
WEBVIEW.pager.webContents.send("button-disabled", {
id: "next",
disabled: WEBVIEW.viewActive >= WEBVIEW.viewUrls.length - 1,
});
};
/** /**
* Updates the widget control. * Updates the widget control.
*/ */
const updateWidget = () => { const updateWidget = () => {
// Hide status button
WEBVIEW.widget.webContents.send("button-hidden", {
id: "fullscreen",
hidden: !WEBVIEW.statusEnabled,
});
// Hide keyboard button // Hide keyboard button
WEBVIEW.widget.webContents.send("button-hidden", { WEBVIEW.widget.webContents.send("button-hidden", {
id: "keyboard", id: "keyboard",
@@ -295,6 +319,9 @@ const updateWidget = () => {
* Updates the navigation control. * Updates the navigation control.
*/ */
const updateNavigation = () => { const updateNavigation = () => {
if (!WEBVIEW.viewActive) {
return;
}
const view = WEBVIEW.views[WEBVIEW.viewActive]; const view = WEBVIEW.views[WEBVIEW.viewActive];
const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive]; const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive];
const currentUrl = view.webContents.getURL(); const currentUrl = view.webContents.getURL();
@@ -416,6 +443,9 @@ const toggleStatus = (force = null) => {
* Decreases page zoom on the active webview. * Decreases page zoom on the active webview.
*/ */
const zoomMinus = () => { const zoomMinus = () => {
if (!WEBVIEW.viewActive) {
return;
}
const view = WEBVIEW.views[WEBVIEW.viewActive]; const view = WEBVIEW.views[WEBVIEW.viewActive];
const zoom = view.webContents.getZoomFactor() - 0.1; const zoom = view.webContents.getZoomFactor() - 0.1;
view.webContents.setZoomFactor(Math.max(0.25, Math.round(zoom * 10) / 10)); view.webContents.setZoomFactor(Math.max(0.25, Math.round(zoom * 10) / 10));
@@ -426,6 +456,9 @@ const zoomMinus = () => {
* Increases page zoom on the active webview. * Increases page zoom on the active webview.
*/ */
const zoomPlus = () => { const zoomPlus = () => {
if (!WEBVIEW.viewActive) {
return;
}
const view = WEBVIEW.views[WEBVIEW.viewActive]; const view = WEBVIEW.views[WEBVIEW.viewActive];
const zoom = view.webContents.getZoomFactor() + 0.1; const zoom = view.webContents.getZoomFactor() + 0.1;
view.webContents.setZoomFactor(Math.min(4.0, Math.round(zoom * 10) / 10)); view.webContents.setZoomFactor(Math.min(4.0, Math.round(zoom * 10) / 10));
@@ -436,6 +469,9 @@ const zoomPlus = () => {
* Navigates backward in the history of the active webview. * Navigates backward in the history of the active webview.
*/ */
const historyBackward = () => { const historyBackward = () => {
if (!WEBVIEW.viewActive) {
return;
}
const view = WEBVIEW.views[WEBVIEW.viewActive]; const view = WEBVIEW.views[WEBVIEW.viewActive];
if (view.webContents.navigationHistory.canGoBack()) { if (view.webContents.navigationHistory.canGoBack()) {
view.webContents.navigationHistory.goBack(); view.webContents.navigationHistory.goBack();
@@ -446,6 +482,9 @@ const historyBackward = () => {
* Navigates forward in the history of the active webview. * Navigates forward in the history of the active webview.
*/ */
const historyForward = () => { const historyForward = () => {
if (!WEBVIEW.viewActive) {
return;
}
const view = WEBVIEW.views[WEBVIEW.viewActive]; const view = WEBVIEW.views[WEBVIEW.viewActive];
if (view.webContents.navigationHistory.canGoForward()) { if (view.webContents.navigationHistory.canGoForward()) {
view.webContents.navigationHistory.goForward(); view.webContents.navigationHistory.goForward();
@@ -456,7 +495,12 @@ const historyForward = () => {
* Activates the previous webview page. * Activates the previous webview page.
*/ */
const previousView = () => { const previousView = () => {
if (WEBVIEW.viewActive > 1) WEBVIEW.viewActive--; if (!WEBVIEW.viewActive) {
return;
}
if (WEBVIEW.viewActive > 1) {
WEBVIEW.viewActive--;
}
updateView(); updateView();
}; };
@@ -464,7 +508,12 @@ const previousView = () => {
* Activates the next webview page. * Activates the next webview page.
*/ */
const nextView = () => { const nextView = () => {
if (WEBVIEW.viewActive < WEBVIEW.views.length - 1) WEBVIEW.viewActive++; if (!WEBVIEW.viewActive) {
return;
}
if (WEBVIEW.viewActive < WEBVIEW.views.length - 1) {
WEBVIEW.viewActive++;
}
updateView(); updateView();
}; };
@@ -472,6 +521,9 @@ const nextView = () => {
* Reloads the default url and settings on the active webview. * Reloads the default url and settings on the active webview.
*/ */
const homeView = () => { const homeView = () => {
if (!WEBVIEW.viewActive) {
return;
}
const view = WEBVIEW.views[WEBVIEW.viewActive]; const view = WEBVIEW.views[WEBVIEW.viewActive];
const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive]; const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive];
const currentUrl = view.webContents.getURL(); const currentUrl = view.webContents.getURL();
@@ -498,6 +550,9 @@ const homeView = () => {
* Reloads the current url on the active webview. * Reloads the current url on the active webview.
*/ */
const reloadView = () => { const reloadView = () => {
if (!WEBVIEW.viewActive) {
return;
}
const view = WEBVIEW.views[WEBVIEW.viewActive]; const view = WEBVIEW.views[WEBVIEW.viewActive];
const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive]; const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive];
const currentUrl = view.webContents.getURL(); const currentUrl = view.webContents.getURL();
@@ -544,7 +599,7 @@ const resizeView = () => {
height: pager.height - status.height - navigation.height, height: pager.height - status.height - navigation.height,
}); });
WEBVIEW.pager.webContents.send("data-theme", { WEBVIEW.pager.webContents.send("data-theme", {
theme: "hidden", theme: WEBVIEW.viewUrls.length > 2 ? WEBVIEW.pagerTheme : "hidden",
}); });
} }
@@ -594,11 +649,6 @@ const resizeView = () => {
const windowEvents = async () => { const windowEvents = async () => {
console.debug("webview.js: windowEvents()"); console.debug("webview.js: windowEvents()");
// Handle window resize events
WEBVIEW.window.on("ready-to-show", resizeView);
WEBVIEW.window.on("resize", resizeView);
resizeView();
// Handle window status updates // Handle window status updates
WEBVIEW.window.setStatus = async (status) => { WEBVIEW.window.setStatus = async (status) => {
const apply = (func, ...args) => { const apply = (func, ...args) => {
@@ -657,6 +707,9 @@ const windowEvents = async () => {
WEBVIEW.window.on("enter-full-screen", WEBVIEW.window.onStatus); WEBVIEW.window.on("enter-full-screen", WEBVIEW.window.onStatus);
WEBVIEW.window.on("leave-full-screen", WEBVIEW.window.onStatus); WEBVIEW.window.on("leave-full-screen", WEBVIEW.window.onStatus);
// Handle window resize events
WEBVIEW.window.on("resize", resizeView);
// Handle global shortcut events // Handle global shortcut events
globalShortcut.register("Control+Left", () => { globalShortcut.register("Control+Left", () => {
previousView(); previousView();
@@ -749,6 +802,9 @@ const widgetEvents = async () => {
console.debug(`webview.js: widgetEvents(button-click-${button.id})`); console.debug(`webview.js: widgetEvents(button-click-${button.id})`);
WEBVIEW.tracker.pointer.time = new Date(); WEBVIEW.tracker.pointer.time = new Date();
switch (button.id) { switch (button.id) {
case "fullscreen":
WEBVIEW.window.setStatus(WEBVIEW.tracker.window.status === "Fullscreen" ? "Framed" : "Fullscreen");
break;
case "theme": case "theme":
WEBVIEW.theme.toggle(); WEBVIEW.theme.toggle();
break; break;
@@ -776,9 +832,6 @@ const statusEvents = async () => {
console.debug(`webview.js: statusEvents(button-click-${button.id})`); console.debug(`webview.js: statusEvents(button-click-${button.id})`);
WEBVIEW.tracker.pointer.time = new Date(); WEBVIEW.tracker.pointer.time = new Date();
switch (button.id) { switch (button.id) {
case "fullscreen":
WEBVIEW.window.setStatus(WEBVIEW.tracker.window.status === "Fullscreen" ? "Framed" : "Fullscreen");
break;
case "minimize": case "minimize":
WEBVIEW.window.setStatus("Minimized"); WEBVIEW.window.setStatus("Minimized");
break; break;
@@ -906,9 +959,31 @@ const navigationEvents = async () => {
*/ */
const viewEvents = async () => { const viewEvents = async () => {
const ready = []; const ready = [];
WEBVIEW.views.forEach((view, i) => { const debug = "app_debug" in ARGS;
console.debug(`webview.js: viewEvents(${i})`);
const loaded = (i) => {
if (WEBVIEW.viewActive !== 0) {
return true;
}
// Set window status to fullscreen
if (i === 0 && !debug) {
WEBVIEW.window.setStatus("Fullscreen");
}
// Hide loader and show first view
const done = ready.length >= WEBVIEW.views.length;
if (done) {
WEBVIEW.viewActive = 1;
updateView();
}
console.debug(`webview.js: viewEvents(${i},loaded-${ready.length}/${WEBVIEW.views.length})`);
return done;
};
// Handle events per webview
WEBVIEW.views.forEach((view, i) => {
// Enable webview touch emulation // Enable webview touch emulation
view.webContents.debugger.attach("1.1"); view.webContents.debugger.attach("1.1");
view.webContents.debugger.sendCommand("Emulation.setEmitTouchEventsForMouse", { view.webContents.debugger.sendCommand("Emulation.setEmitTouchEventsForMouse", {
@@ -926,32 +1001,27 @@ const viewEvents = async () => {
view.webContents.on("dom-ready", () => { view.webContents.on("dom-ready", () => {
console.debug(`webview.js: viewEvents(${i},dom-ready)`); console.debug(`webview.js: viewEvents(${i},dom-ready)`);
view.webContents.insertCSS("::-webkit-scrollbar { display: none; }"); view.webContents.insertCSS("::-webkit-scrollbar { display: none; }");
if (ready.length < WEBVIEW.views.length) { });
view.webContents.once("dom-ready", () => {
view.webContents.setZoomFactor(WEBVIEW.viewZoom); view.webContents.setZoomFactor(WEBVIEW.viewZoom);
ready.push(i); ready.push(i);
}
}); });
// Webview fully loaded // Webview fully loaded
view.webContents.on("did-finish-load", () => { view.webContents.on("did-finish-load", () => {
console.debug(`webview.js: viewEvents(${i},did-finish-load)`); console.debug(`webview.js: viewEvents(${i},did-finish-load)`);
if (WEBVIEW.viewActive === 0 && ready.length === WEBVIEW.views.length) { if (debug) {
nextView();
}
if ("app_debug" in ARGS) {
setTimeout(() => { setTimeout(() => {
view.webContents.openDevTools(); view.webContents.openDevTools();
}, 2000); }, 2000);
} }
loaded(i);
}); });
// Webview not loaded // Webview not loaded
view.webContents.on("did-fail-load", (e, code, text, url, mainframe) => { view.webContents.on("did-fail-load", (e, code, text, url, mainframe) => {
if (mainframe) { if (mainframe) {
console.debug(`webview.js: viewEvents(${i},did-fail-load)`); console.debug(`webview.js: viewEvents(${i},did-fail-load)`);
if (WEBVIEW.viewActive === 0 && ready.length === WEBVIEW.views.length) {
nextView();
}
switch (code) { switch (code) {
case -3: case -3:
console.warn(`Load Warning: ${url}, ERR_ABORTED (${code})`); console.warn(`Load Warning: ${url}, ERR_ABORTED (${code})`);
@@ -960,6 +1030,7 @@ const viewEvents = async () => {
console.error(`Load Error: ${url}, ${text} (${code})`); console.error(`Load Error: ${url}, ${text} (${code})`);
view.webContents.loadURL(errorHtml(code, text, url, WEBVIEW.theme.get())); view.webContents.loadURL(errorHtml(code, text, url, WEBVIEW.theme.get()));
} }
loaded(i);
} }
}); });
@@ -1090,17 +1161,17 @@ const appEvents = async () => {
}); });
process.on("SIGINT", app.quit); process.on("SIGINT", app.quit);
// Webview initialized // Check latest release (2h)
WEBVIEW.initialized = true;
// Check for latest release infos (2h)
setInterval(() => { setInterval(() => {
if (APP.exiting) { if (APP.exiting) {
return; return;
} }
latestRelease(); latestRelease();
}, 7200 * 1000); }, 7200 * 1000);
await latestRelease(); latestRelease();
// Webview initialized
WEBVIEW.initialized = true;
}; };
/** /**
@@ -1108,7 +1179,7 @@ const appEvents = async () => {
*/ */
const latestRelease = async () => { const latestRelease = async () => {
try { try {
const response = await axios.get(APP.releases.url, { timeout: 10000 }); const response = await axios.get(APP.releases.url, { timeout: 20000 });
const release = response?.data?.find((item) => { const release = response?.data?.find((item) => {
return !item.draft && (!item.prerelease || "app_early" in ARGS); return !item.draft && (!item.prerelease || "app_early" in ARGS);
}); });
@@ -1119,6 +1190,7 @@ const latestRelease = async () => {
summary: release.body || " ", summary: release.body || " ",
url: release.html_url || " ", url: release.html_url || " ",
}; };
EVENTS.emit("updateApp");
} }
} catch (error) { } catch (error) {
console.warn("Github Error:", error.message); console.warn("Github Error:", error.message);
@@ -1141,7 +1213,7 @@ const onlineStatus = (url, interval = 1000, timeout = 60000) => {
try { try {
if (!url.startsWith("data:")) { if (!url.startsWith("data:")) {
const agent = new https.Agent({ rejectUnauthorized: !("ignore_certificate_errors" in ARGS) }); const agent = new https.Agent({ rejectUnauthorized: !("ignore_certificate_errors" in ARGS) });
await axios.get(url, { httpsAgent: agent, timeout: 10000 }); await axios.get(url, { httpsAgent: agent, timeout: 20000 });
} }
resolve(true); resolve(true);
} catch (error) { } catch (error) {