diff --git a/index.js b/index.js index 964c304..f4cf7e4 100644 --- a/index.js +++ b/index.js @@ -93,6 +93,11 @@ const promptArgs = async (proc) => { question: "Enter WEB zoom level", fallback: "1.25", }, + { + key: "web_widget", + question: "Enter WEB widget enabled", + fallback: "true", + }, { key: "mqtt", question: "\nConnect to MQTT Broker?", diff --git a/src/webview.js b/src/webview.js index 7ed9737..67d79d9 100644 --- a/src/webview.js +++ b/src/webview.js @@ -1,7 +1,7 @@ const path = require("path"); const hardware = require("./hardware"); const integration = require("./integration"); -const { app, screen, nativeTheme, BaseWindow, WebContentsView } = require("electron"); +const { app, screen, nativeTheme, ipcMain, BaseWindow, WebContentsView } = require("electron"); global.WEBVIEW = global.WEBVIEW || { initialized: false, @@ -24,12 +24,23 @@ const init = async (args) => { console.error("Please provide the '--web-url' parameter"); return app.quit(); } + if (!/^https?:\/\//.test(args.web_url)) { + console.error("Please provide the '--web-url' parameter with http(s)"); + return app.quit(); + } const url = new URL(args.web_url); - const theme = args.web_theme ? args.web_theme : "dark"; - const zoom = args.web_zoom ? parseFloat(args.web_zoom) : 1.25; + const theme = ["light", "dark"].includes(args.web_theme) ? args.web_theme : "dark"; + const zoom = !isNaN(parseFloat(args.web_zoom)) ? parseFloat(args.web_zoom) : 1.25; + const widget = args.web_widget ? args.web_widget === "true" : true; + + // Init global layout + WEBVIEW.viewZoom = zoom; + WEBVIEW.viewTheme = theme; + WEBVIEW.widgetTheme = theme; + WEBVIEW.widgetEnabled = widget; + nativeTheme.themeSource = WEBVIEW.viewTheme; // Init global root window - nativeTheme.themeSource = theme; WEBVIEW.window = new BaseWindow({ title: `TouchKio - ${url.host}`, icon: path.join(__dirname, "..", "img", "icon.png"), @@ -37,15 +48,34 @@ const init = async (args) => { WEBVIEW.window.setMenuBarVisibility(false); WEBVIEW.window.setFullScreen(true); - // Init global main webview - WEBVIEW.view = new WebContentsView(); + // Init global webview + WEBVIEW.view = new WebContentsView({ + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + }, + }); WEBVIEW.window.contentView.addChildView(WEBVIEW.view); WEBVIEW.view.webContents.loadURL(url.toString()); + WEBVIEW.view.setBackgroundColor("#FFFFFFFF"); + + // Init global widget + WEBVIEW.widget = new WebContentsView({ + transparent: true, + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + }, + }); + WEBVIEW.window.contentView.addChildView(WEBVIEW.widget); + WEBVIEW.widget.webContents.loadFile(path.join(__dirname, "widget.html")); + WEBVIEW.widget.setBackgroundColor("#00000000"); // Register events - windowEvents(WEBVIEW.window, zoom); - touchEvents(WEBVIEW.window); - viewEvents(WEBVIEW.view); + windowEvents(); + widgetEvents(); + domEvents(); + appEvents(); return true; }; @@ -69,6 +99,11 @@ const update = () => { WEBVIEW.status = "Framed"; } + // Update widget status + if (!HARDWARE.support.keyboardVisibility) { + WEBVIEW.widget.webContents.send("button-hide", { id: "keyboard" }); + } + // Update integration sensor console.log("Update Kiosk Status:", WEBVIEW.status); integration.update(); @@ -76,67 +111,62 @@ const update = () => { /** * Register window events and handler. - * - * @param {Object} window - The root window object. - * @param {number} zoom - Zoom level used for the webview. */ -const windowEvents = (window, zoom) => { - // Handle window resize for webview bounds +const windowEvents = () => { + // Handle window resize events const resize = () => { - const { width, height } = window.getBounds(); - WEBVIEW.view.setBounds({ x: 0, y: 0, width: width, height: height }); - WEBVIEW.view.webContents.setZoomFactor(zoom); + const window = WEBVIEW.window.getBounds(); + const widget = { width: 60, height: 200 }; + + // Update view size + WEBVIEW.view.setBounds({ + x: 0, + y: 0, + width: window.width, + height: window.height, + }); + WEBVIEW.view.webContents.setZoomFactor(WEBVIEW.viewZoom); + WEBVIEW.view.webContents.focus(); + + // Update widget size + if (WEBVIEW.widgetEnabled) { + WEBVIEW.widget.setBounds({ + x: window.width - 15, + y: parseInt(window.height / 2 - widget.height / 2), + width: widget.width, + height: widget.height, + }); + WEBVIEW.widget.webContents.send("data-theme", { theme: "hidden" }); + } }; - window.on("ready-to-show", resize); - window.on("resize", resize); + WEBVIEW.window.on("ready-to-show", resize); + WEBVIEW.window.on("resize", resize); resize(); - // Handle window status updates - window.on("minimize", update); - window.on("restore", update); - window.on("maximize", update); - window.on("unmaximize", update); - window.on("enter-full-screen", update); - window.on("leave-full-screen", update); - - // Handle signal and exit events - process.on("SIGINT", app.quit); - app.on("before-quit", () => { - WEBVIEW.status = "Terminated"; - integration.update(); - }); - - // Handle multiple instances - app.on("second-instance", () => { - if (window.isMinimized()) { - window.restore(); - } - window.focus(); - }); -}; - -/** - * Register touch events and handler. - * - * @param {Object} window - The root window object. - */ -const touchEvents = (window) => { - // Handle focus change for webview lock - window.on("focus", () => { + // Handle window focus events + WEBVIEW.window.on("focus", () => { if (WEBVIEW.locked) { hardware.setDisplayStatus("ON"); - window.blur(); + WEBVIEW.window.blur(); } WEBVIEW.locked = false; }); HARDWARE.display.notifiers.push(() => { WEBVIEW.locked = hardware.getDisplayStatus() === "OFF"; if (WEBVIEW.locked) { - window.blur(); + WEBVIEW.window.blur(); } }); - // Handle touch events for activity tracking + // Handle window status updates + WEBVIEW.window.on("minimize", update); + WEBVIEW.window.on("restore", update); + WEBVIEW.window.on("maximize", update); + WEBVIEW.window.on("unmaximize", update); + WEBVIEW.window.on("enter-full-screen", update); + WEBVIEW.window.on("leave-full-screen", update); + + // Handle window touch events for activity tracking setInterval(() => { const posOld = WEBVIEW.pointer.position; const posNew = screen.getCursorScreenPoint(); @@ -156,35 +186,134 @@ const touchEvents = (window) => { }; /** - * Register webview events and handler. - * - * @param {Object} view - The main webview object. + * Register widget events and handler. */ -const viewEvents = (view) => { +const widgetEvents = () => { + if (!WEBVIEW.widgetEnabled) { + return; + } + + // Handle widget focus events + WEBVIEW.widget.webContents.on("focus", () => { + const window = WEBVIEW.window.getBounds(); + const widget = WEBVIEW.widget.getBounds(); + + // Show widget + WEBVIEW.widget.setBounds({ + x: window.width - 60, + y: widget.y, + width: widget.width, + height: widget.height, + }); + WEBVIEW.widget.webContents.send("data-theme", { theme: WEBVIEW.widgetTheme }); + }); + + // Handle widget blur events + WEBVIEW.widget.webContents.on("blur", () => { + const window = WEBVIEW.window.getBounds(); + const widget = WEBVIEW.widget.getBounds(); + + // Hide widget + WEBVIEW.widget.setBounds({ + x: window.width - 15, + y: widget.y, + width: widget.width, + height: widget.height, + }); + WEBVIEW.widget.webContents.send("data-theme", { theme: "hidden" }); + }); + + // Handle widget button click events + ipcMain.on("button-click", (e, button) => { + switch (button.id) { + case "refresh": + WEBVIEW.view.webContents.reloadIgnoringCache(); + break; + case "fullscreen": + if (WEBVIEW.window.isFullScreen()) { + WEBVIEW.window.restore(); + WEBVIEW.window.unmaximize(); + WEBVIEW.window.setFullScreen(false); + } else { + WEBVIEW.window.restore(); + WEBVIEW.window.unmaximize(); + WEBVIEW.window.setFullScreen(true); + } + break; + case "minimize": + WEBVIEW.window.restore(); + WEBVIEW.window.setFullScreen(false); + WEBVIEW.window.minimize(); + break; + case "keyboard": + const toggle = hardware.getKeyboardVisibility() === "ON" ? "OFF" : "ON"; + hardware.setKeyboardVisibility(toggle); + switch (toggle) { + case "OFF": + WEBVIEW.window.restore(); + WEBVIEW.window.unmaximize(); + WEBVIEW.window.setFullScreen(true); + break; + case "ON": + WEBVIEW.window.restore(); + WEBVIEW.window.setFullScreen(false); + WEBVIEW.window.maximize(); + break; + } + break; + } + }); +}; + +/** + * Register dom events and handler. + */ +const domEvents = () => { // Enable webview touch emulation - view.webContents.debugger.attach("1.1"); - view.webContents.debugger.sendCommand("Emulation.setEmitTouchEventsForMouse", { + WEBVIEW.view.webContents.debugger.attach("1.1"); + WEBVIEW.view.webContents.debugger.sendCommand("Emulation.setEmitTouchEventsForMouse", { configuration: "mobile", enabled: true, }); // Disable webview hyperlinks - view.webContents.setWindowOpenHandler(() => { + WEBVIEW.view.webContents.setWindowOpenHandler(() => { return { action: "deny" }; }); // Remove webview scrollbar - view.webContents.on("dom-ready", () => { - view.webContents.insertCSS("::-webkit-scrollbar { display: none; }"); + WEBVIEW.view.webContents.on("dom-ready", () => { + WEBVIEW.view.webContents.insertCSS("::-webkit-scrollbar { display: none; }"); }); // Webview fully loaded - view.webContents.on("did-finish-load", () => { + WEBVIEW.view.webContents.on("did-finish-load", () => { + // view.webContents.openDevTools(); WEBVIEW.initialized = true; update(); }); }; +/** + * Register app events and handler. + */ +const appEvents = () => { + // Handle signal and exit events + process.on("SIGINT", app.quit); + app.on("before-quit", () => { + WEBVIEW.status = "Terminated"; + integration.update(); + }); + + // Handle multiple instances + app.on("second-instance", () => { + if (WEBVIEW.window.isMinimized()) { + WEBVIEW.window.restore(); + } + WEBVIEW.window.focus(); + }); +}; + module.exports = { init, update, diff --git a/src/widget.html b/src/widget.html new file mode 100644 index 0000000..bd52174 --- /dev/null +++ b/src/widget.html @@ -0,0 +1,125 @@ + + + + + + + +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
    + + +