diff --git a/index.js b/index.js index 17709cb..581fd79 100644 --- a/index.js +++ b/index.js @@ -59,7 +59,7 @@ app.whenReady().then(async () => { global.ARGS = args; // Show used arguments - console.log(`Arguments: ${JSON.stringify(global.ARGS, null, 2)}`); + console.log(`Arguments: ${JSON.stringify(global.ARGS, null, 2)}\n`); // Chained init functions const chained = [webview.init, hardware.init, integration.init]; diff --git a/js/hardware.js b/js/hardware.js index c1abca4..7715547 100644 --- a/js/hardware.js +++ b/js/hardware.js @@ -3,10 +3,11 @@ const fs = require("fs"); const path = require("path"); const fsp = require("fs/promises"); const cpr = require("child_process"); +const Events = require("events"); global.HARDWARE = global.HARDWARE || { initialized: false, - status: null, + events: new Events(), session: {}, support: {}, battery: { @@ -24,11 +25,9 @@ global.HARDWARE = global.HARDWARE || { value: null, max: null, }, - notifiers: [], }, keyboard: { visible: null, - notifiers: [], }, }; @@ -39,7 +38,11 @@ global.HARDWARE = global.HARDWARE || { */ const init = async () => { if (!compatibleDevice()) { - console.warn("Device not supported"); + console.warn("Operating system is not supported\n"); + return false; + } + if (!sessionRights()) { + console.warn(`User "${sessionUser()}" is missing password-less sudo rights\n`); return false; } @@ -55,7 +58,6 @@ const init = async () => { HARDWARE.display.brightness.max = getDisplayBrightnessMax(); HARDWARE.support.keyboardVisibility = processRuns("squeekboard"); HARDWARE.initialized = true; - HARDWARE.status = "Supported"; // Show supported features console.log(`Supported: ${JSON.stringify(HARDWARE.support, null, 2)}`); @@ -92,9 +94,7 @@ const init = async () => { return; } HARDWARE.keyboard.visibility = property.Visible === "true"; - HARDWARE.keyboard.notifiers.forEach((notifier) => { - notifier(); - }); + HARDWARE.events.emit("updateKeyboard"); }); }); @@ -118,9 +118,7 @@ const update = async () => { if (status !== HARDWARE.display.status.value) { console.log("Update Display Status:", getDisplayStatus()); HARDWARE.display.status.value = status; - HARDWARE.display.notifiers.forEach((notifier) => { - notifier(); - }); + HARDWARE.events.emit("updateDisplay"); } } @@ -130,9 +128,7 @@ const update = async () => { if (brightness !== HARDWARE.display.brightness.value) { console.log("Update Display Brightness:", getDisplayBrightness()); HARDWARE.display.brightness.value = brightness; - HARDWARE.display.notifiers.forEach((notifier) => { - notifier(); - }); + HARDWARE.events.emit("updateDisplay"); } } }; @@ -150,6 +146,19 @@ const compatibleDevice = () => { return paths.every((path) => fs.existsSync(path)); }; +/** + * Verifies user rights to run sudo commands without a password. + * + * @returns {bool} Returns true if password-less sudo rights exists. + */ +const sessionRights = () => { + if (!commandExists("sudo")) { + return false; + } + const result = execSyncCommand("sudo", ["-n", "true"]); + return result !== null && !result.includes("password is required"); +}; + /** * Gets the session user name using `os.userInfo()`. * @@ -178,15 +187,6 @@ const sessionType = () => { ]); }; -/** - * Checks the session type for the logged in user. - * - * @returns {bool} Returns true if the session matches the type. - */ -const session = (type) => { - return HARDWARE.session.type === type; -}; - /** * Gets the model name using `/sys/firmware/devicetree/base/model` or `/sys/class/dmi/id/product_name`. * @@ -379,18 +379,25 @@ const getDisplayStatus = () => { if (!HARDWARE.support.displayStatus) { return null; } - if (session("wayland") && commandExists("wlopm")) { - const status = execSyncCommand("wlopm", []); - if (status !== null) { - const out = status.split("\n")[0].split(" "); - return out.pop().toUpperCase(); - } - } else if (session("x11") && commandExists("xset")) { - const status = execSyncCommand("xset", ["-q"]); - if (status !== null) { - const on = /Monitor is On/.test(status); - return on ? "ON" : "OFF"; - } + switch (HARDWARE.session.type) { + case "wayland": + if (commandExists("wlopm")) { + const status = execSyncCommand("wlopm", []); + if (status !== null) { + const out = status.split("\n")[0].split(" "); + return out.pop().toUpperCase(); + } + } + break; + case "x11": + if (commandExists("xset")) { + const status = execSyncCommand("xset", ["-q"]); + if (status !== null) { + const on = /Monitor is On/.test(status); + return on ? "ON" : "OFF"; + } + } + break; } return null; }; @@ -414,10 +421,19 @@ const setDisplayStatus = (status, callback = null) => { if (typeof callback === "function") callback(null, "Invalid status"); return; } - if (session("wayland") && commandExists("wlopm")) { - execAsyncCommand("wlopm", [`--${status.toLowerCase()}`, "*"], callback); - } else if (session("x11") && commandExists("xset")) { - execAsyncCommand("xset", ["dpms", "force", status.toLowerCase()], callback); + switch (HARDWARE.session.type) { + case "wayland": + if (commandExists("wlopm")) { + execAsyncCommand("wlopm", [`--${status.toLowerCase()}`, "*"], callback); + } + break; + case "x11": + if (commandExists("xset")) { + execAsyncCommand("xset", ["dpms", "force", status.toLowerCase()], callback); + } + break; + default: + if (typeof callback === "function") callback(null, "Invalid session type"); } }; diff --git a/js/integration.js b/js/integration.js index 3fc56e0..1ed958c 100644 --- a/js/integration.js +++ b/js/integration.js @@ -1,10 +1,11 @@ const mqtt = require("mqtt"); const hardware = require("./hardware"); const { app } = require("electron"); +const Events = require("events"); global.INTEGRATION = global.INTEGRATION || { initialized: false, - status: null, + events: new Events(), }; /** @@ -86,20 +87,14 @@ const init = async () => { // Integration initialized INTEGRATION.initialized = true; - INTEGRATION.status = "Online"; }) .on("error", (error) => { console.error("MQTT:", error); - INTEGRATION.status = "Offline"; }); - // Update sensor states from notifiers - HARDWARE.display.notifiers.push(() => { - updateDisplay(); - }); - HARDWARE.keyboard.notifiers.push(() => { - updateKeyboard(); - }); + // Update sensor states from events + HARDWARE.events.on("updateDisplay", updateDisplay); + HARDWARE.events.on("updateKeyboard", updateKeyboard); // Update time sensors periodically (30s) setInterval(() => { @@ -250,7 +245,7 @@ const initRefresh = () => { if (topic === config.command_topic) { console.log("Refreshing webview..."); hardware.setDisplayStatus("ON"); - WEBVIEW.views[WEBVIEW.viewActive].webContents.reloadIgnoringCache(); + WEBVIEW.events.emit("reloadView"); } }) .subscribe(config.command_topic); @@ -441,6 +436,7 @@ const initPageNumber = () => { const number = parseInt(message, 10); console.log("Set Page Number:", number); WEBVIEW.viewActive = number; + WEBVIEW.events.emit("updateView"); } }) .subscribe(config.command_topic); @@ -467,8 +463,8 @@ const initPageZoom = () => { state_topic: `${root}/state`, value_template: "{{ value | int }}", mode: "slider", - min: 10, - max: 400, + min: 5, + max: 500, unit_of_measurement: "%", icon: "mdi:magnify-plus-outline", device: INTEGRATION.device, @@ -478,7 +474,8 @@ const initPageZoom = () => { if (topic === config.command_topic) { const zoom = parseInt(message, 10); console.log("Set Page Zoom:", zoom); - WEBVIEW.viewZoom = zoom / 100.0; + WEBVIEW.views[WEBVIEW.viewActive].webContents.setZoomFactor(zoom / 100.0); + WEBVIEW.events.emit("updateView"); } }) .subscribe(config.command_topic); @@ -489,7 +486,7 @@ const initPageZoom = () => { * Updates the page zoom via the mqtt connection. */ const updatePageZoom = () => { - const pageZoom = Math.round(WEBVIEW.viewZoom * 100.0); + const pageZoom = Math.round(WEBVIEW.views[WEBVIEW.viewActive].webContents.getZoomFactor() * 100.0); publishState("page_zoom", pageZoom); }; diff --git a/js/webview.js b/js/webview.js index 4accc57..ef8c8e9 100644 --- a/js/webview.js +++ b/js/webview.js @@ -2,9 +2,11 @@ const path = require("path"); const hardware = require("./hardware"); const integration = require("./integration"); const { app, screen, nativeTheme, ipcMain, session, BaseWindow, WebContentsView } = require("electron"); +const Events = require("events"); global.WEBVIEW = global.WEBVIEW || { initialized: false, + events: new Events(), status: null, locked: false, pointer: { @@ -37,31 +39,17 @@ const init = async () => { const urls = [loaderHtml(40, 1.0, theme)].concat(ARGS.web_url); // Init global properties + WEBVIEW.viewActive = 0; WEBVIEW.viewUrls = urls; - Object.defineProperty(WEBVIEW, "viewZoom", { - get() { - return this._viewZoom || zoom; - }, - set(v) { - this._viewZoom = Math.max(0, v); - updateZoom(); - }, - }); + WEBVIEW.viewZoom = zoom; WEBVIEW.viewTheme = theme; WEBVIEW.widgetTheme = theme; WEBVIEW.widgetEnabled = widget; WEBVIEW.navigationTheme = theme; WEBVIEW.navigationEnabled = widget; nativeTheme.themeSource = WEBVIEW.viewTheme; - Object.defineProperty(WEBVIEW, "viewActive", { - get() { - return this._viewActive || 0; - }, - set(v) { - this._viewActive = Math.max(0, Math.min(v, WEBVIEW.viewUrls.length - 1)); - updateView(); - }, - }); + WEBVIEW.events.on("reloadView", reloadView); + WEBVIEW.events.on("updateView", updateView); // Init global root window WEBVIEW.window = new BaseWindow({ @@ -263,6 +251,7 @@ const historyForward = () => { */ const nextView = () => { WEBVIEW.viewActive = (WEBVIEW.viewActive + 1) % WEBVIEW.views.length || 1; + updateView(); }; /** @@ -297,7 +286,6 @@ const resizeView = () => { width: window.width, height: window.height - navigation.height, }); - view.webContents.setZoomFactor(WEBVIEW.viewZoom); }); // Update widget size @@ -323,17 +311,6 @@ const resizeView = () => { } }; -/** - * Updates zoom level for all webviews. - */ -const updateZoom = () => { - // And apply to all views - WEBVIEW.views.forEach((view) => { - view.webContents.setZoomFactor(WEBVIEW.viewZoom); - }); - -}; - /** * Register window events and handler. */ @@ -351,7 +328,7 @@ const windowEvents = () => { } WEBVIEW.locked = false; }); - HARDWARE.display.notifiers.push(() => { + HARDWARE.events.on("updateDisplay", () => { WEBVIEW.locked = hardware.getDisplayStatus() === "OFF"; if (WEBVIEW.locked) { WEBVIEW.window.blur(); @@ -577,8 +554,9 @@ const domEvents = () => { return { action: "deny" }; }); - // Remove webview scrollbar + // Update webview layout view.webContents.on("dom-ready", () => { + view.webContents.setZoomFactor(WEBVIEW.viewZoom); view.webContents.insertCSS("::-webkit-scrollbar { display: none; }"); });