fix: event based communication #39 #48 #55

This commit is contained in:
leukipp
2025-08-25 18:52:25 +02:00
parent 7e5e669b09
commit 619179c3c8
4 changed files with 78 additions and 87 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ app.whenReady().then(async () => {
global.ARGS = args; global.ARGS = args;
// Show used arguments // 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 // Chained init functions
const chained = [webview.init, hardware.init, integration.init]; const chained = [webview.init, hardware.init, integration.init];
+43 -27
View File
@@ -3,10 +3,11 @@ const fs = require("fs");
const path = require("path"); const path = require("path");
const fsp = require("fs/promises"); const fsp = require("fs/promises");
const cpr = require("child_process"); const cpr = require("child_process");
const Events = require("events");
global.HARDWARE = global.HARDWARE || { global.HARDWARE = global.HARDWARE || {
initialized: false, initialized: false,
status: null, events: new Events(),
session: {}, session: {},
support: {}, support: {},
battery: { battery: {
@@ -24,11 +25,9 @@ global.HARDWARE = global.HARDWARE || {
value: null, value: null,
max: null, max: null,
}, },
notifiers: [],
}, },
keyboard: { keyboard: {
visible: null, visible: null,
notifiers: [],
}, },
}; };
@@ -39,7 +38,11 @@ global.HARDWARE = global.HARDWARE || {
*/ */
const init = async () => { const init = async () => {
if (!compatibleDevice()) { 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; return false;
} }
@@ -55,7 +58,6 @@ const init = async () => {
HARDWARE.display.brightness.max = getDisplayBrightnessMax(); HARDWARE.display.brightness.max = getDisplayBrightnessMax();
HARDWARE.support.keyboardVisibility = processRuns("squeekboard"); HARDWARE.support.keyboardVisibility = processRuns("squeekboard");
HARDWARE.initialized = true; HARDWARE.initialized = true;
HARDWARE.status = "Supported";
// Show supported features // Show supported features
console.log(`Supported: ${JSON.stringify(HARDWARE.support, null, 2)}`); console.log(`Supported: ${JSON.stringify(HARDWARE.support, null, 2)}`);
@@ -92,9 +94,7 @@ const init = async () => {
return; return;
} }
HARDWARE.keyboard.visibility = property.Visible === "true"; HARDWARE.keyboard.visibility = property.Visible === "true";
HARDWARE.keyboard.notifiers.forEach((notifier) => { HARDWARE.events.emit("updateKeyboard");
notifier();
});
}); });
}); });
@@ -118,9 +118,7 @@ const update = async () => {
if (status !== HARDWARE.display.status.value) { if (status !== HARDWARE.display.status.value) {
console.log("Update Display Status:", getDisplayStatus()); console.log("Update Display Status:", getDisplayStatus());
HARDWARE.display.status.value = status; HARDWARE.display.status.value = status;
HARDWARE.display.notifiers.forEach((notifier) => { HARDWARE.events.emit("updateDisplay");
notifier();
});
} }
} }
@@ -130,9 +128,7 @@ const update = async () => {
if (brightness !== HARDWARE.display.brightness.value) { if (brightness !== HARDWARE.display.brightness.value) {
console.log("Update Display Brightness:", getDisplayBrightness()); console.log("Update Display Brightness:", getDisplayBrightness());
HARDWARE.display.brightness.value = brightness; HARDWARE.display.brightness.value = brightness;
HARDWARE.display.notifiers.forEach((notifier) => { HARDWARE.events.emit("updateDisplay");
notifier();
});
} }
} }
}; };
@@ -150,6 +146,19 @@ const compatibleDevice = () => {
return paths.every((path) => fs.existsSync(path)); 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()`. * 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`. * Gets the model name using `/sys/firmware/devicetree/base/model` or `/sys/class/dmi/id/product_name`.
* *
@@ -379,19 +379,26 @@ const getDisplayStatus = () => {
if (!HARDWARE.support.displayStatus) { if (!HARDWARE.support.displayStatus) {
return null; return null;
} }
if (session("wayland") && commandExists("wlopm")) { switch (HARDWARE.session.type) {
case "wayland":
if (commandExists("wlopm")) {
const status = execSyncCommand("wlopm", []); const status = execSyncCommand("wlopm", []);
if (status !== null) { if (status !== null) {
const out = status.split("\n")[0].split(" "); const out = status.split("\n")[0].split(" ");
return out.pop().toUpperCase(); return out.pop().toUpperCase();
} }
} else if (session("x11") && commandExists("xset")) { }
break;
case "x11":
if (commandExists("xset")) {
const status = execSyncCommand("xset", ["-q"]); const status = execSyncCommand("xset", ["-q"]);
if (status !== null) { if (status !== null) {
const on = /Monitor is On/.test(status); const on = /Monitor is On/.test(status);
return on ? "ON" : "OFF"; return on ? "ON" : "OFF";
} }
} }
break;
}
return null; return null;
}; };
@@ -414,11 +421,20 @@ const setDisplayStatus = (status, callback = null) => {
if (typeof callback === "function") callback(null, "Invalid status"); if (typeof callback === "function") callback(null, "Invalid status");
return; return;
} }
if (session("wayland") && commandExists("wlopm")) { switch (HARDWARE.session.type) {
case "wayland":
if (commandExists("wlopm")) {
execAsyncCommand("wlopm", [`--${status.toLowerCase()}`, "*"], callback); execAsyncCommand("wlopm", [`--${status.toLowerCase()}`, "*"], callback);
} else if (session("x11") && commandExists("xset")) { }
break;
case "x11":
if (commandExists("xset")) {
execAsyncCommand("xset", ["dpms", "force", status.toLowerCase()], callback); execAsyncCommand("xset", ["dpms", "force", status.toLowerCase()], callback);
} }
break;
default:
if (typeof callback === "function") callback(null, "Invalid session type");
}
}; };
/** /**
+12 -15
View File
@@ -1,10 +1,11 @@
const mqtt = require("mqtt"); const mqtt = require("mqtt");
const hardware = require("./hardware"); const hardware = require("./hardware");
const { app } = require("electron"); const { app } = require("electron");
const Events = require("events");
global.INTEGRATION = global.INTEGRATION || { global.INTEGRATION = global.INTEGRATION || {
initialized: false, initialized: false,
status: null, events: new Events(),
}; };
/** /**
@@ -86,20 +87,14 @@ const init = async () => {
// Integration initialized // Integration initialized
INTEGRATION.initialized = true; INTEGRATION.initialized = true;
INTEGRATION.status = "Online";
}) })
.on("error", (error) => { .on("error", (error) => {
console.error("MQTT:", error); console.error("MQTT:", error);
INTEGRATION.status = "Offline";
}); });
// Update sensor states from notifiers // Update sensor states from events
HARDWARE.display.notifiers.push(() => { HARDWARE.events.on("updateDisplay", updateDisplay);
updateDisplay(); HARDWARE.events.on("updateKeyboard", updateKeyboard);
});
HARDWARE.keyboard.notifiers.push(() => {
updateKeyboard();
});
// Update time sensors periodically (30s) // Update time sensors periodically (30s)
setInterval(() => { setInterval(() => {
@@ -250,7 +245,7 @@ const initRefresh = () => {
if (topic === config.command_topic) { if (topic === config.command_topic) {
console.log("Refreshing webview..."); console.log("Refreshing webview...");
hardware.setDisplayStatus("ON"); hardware.setDisplayStatus("ON");
WEBVIEW.views[WEBVIEW.viewActive].webContents.reloadIgnoringCache(); WEBVIEW.events.emit("reloadView");
} }
}) })
.subscribe(config.command_topic); .subscribe(config.command_topic);
@@ -441,6 +436,7 @@ const initPageNumber = () => {
const number = parseInt(message, 10); const number = parseInt(message, 10);
console.log("Set Page Number:", number); console.log("Set Page Number:", number);
WEBVIEW.viewActive = number; WEBVIEW.viewActive = number;
WEBVIEW.events.emit("updateView");
} }
}) })
.subscribe(config.command_topic); .subscribe(config.command_topic);
@@ -467,8 +463,8 @@ const initPageZoom = () => {
state_topic: `${root}/state`, state_topic: `${root}/state`,
value_template: "{{ value | int }}", value_template: "{{ value | int }}",
mode: "slider", mode: "slider",
min: 10, min: 5,
max: 400, max: 500,
unit_of_measurement: "%", unit_of_measurement: "%",
icon: "mdi:magnify-plus-outline", icon: "mdi:magnify-plus-outline",
device: INTEGRATION.device, device: INTEGRATION.device,
@@ -478,7 +474,8 @@ const initPageZoom = () => {
if (topic === config.command_topic) { if (topic === config.command_topic) {
const zoom = parseInt(message, 10); const zoom = parseInt(message, 10);
console.log("Set Page Zoom:", zoom); 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); .subscribe(config.command_topic);
@@ -489,7 +486,7 @@ const initPageZoom = () => {
* Updates the page zoom via the mqtt connection. * Updates the page zoom via the mqtt connection.
*/ */
const updatePageZoom = () => { 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); publishState("page_zoom", pageZoom);
}; };
+10 -32
View File
@@ -2,9 +2,11 @@ const path = require("path");
const hardware = require("./hardware"); const hardware = require("./hardware");
const integration = require("./integration"); const integration = require("./integration");
const { app, screen, nativeTheme, ipcMain, session, BaseWindow, WebContentsView } = require("electron"); const { app, screen, nativeTheme, ipcMain, session, BaseWindow, WebContentsView } = require("electron");
const Events = require("events");
global.WEBVIEW = global.WEBVIEW || { global.WEBVIEW = global.WEBVIEW || {
initialized: false, initialized: false,
events: new Events(),
status: null, status: null,
locked: false, locked: false,
pointer: { pointer: {
@@ -37,31 +39,17 @@ const init = async () => {
const urls = [loaderHtml(40, 1.0, theme)].concat(ARGS.web_url); const urls = [loaderHtml(40, 1.0, theme)].concat(ARGS.web_url);
// Init global properties // Init global properties
WEBVIEW.viewActive = 0;
WEBVIEW.viewUrls = urls; WEBVIEW.viewUrls = urls;
Object.defineProperty(WEBVIEW, "viewZoom", { WEBVIEW.viewZoom = zoom;
get() {
return this._viewZoom || zoom;
},
set(v) {
this._viewZoom = Math.max(0, v);
updateZoom();
},
});
WEBVIEW.viewTheme = theme; WEBVIEW.viewTheme = theme;
WEBVIEW.widgetTheme = theme; WEBVIEW.widgetTheme = theme;
WEBVIEW.widgetEnabled = widget; WEBVIEW.widgetEnabled = widget;
WEBVIEW.navigationTheme = theme; WEBVIEW.navigationTheme = theme;
WEBVIEW.navigationEnabled = widget; WEBVIEW.navigationEnabled = widget;
nativeTheme.themeSource = WEBVIEW.viewTheme; nativeTheme.themeSource = WEBVIEW.viewTheme;
Object.defineProperty(WEBVIEW, "viewActive", { WEBVIEW.events.on("reloadView", reloadView);
get() { WEBVIEW.events.on("updateView", updateView);
return this._viewActive || 0;
},
set(v) {
this._viewActive = Math.max(0, Math.min(v, WEBVIEW.viewUrls.length - 1));
updateView();
},
});
// Init global root window // Init global root window
WEBVIEW.window = new BaseWindow({ WEBVIEW.window = new BaseWindow({
@@ -263,6 +251,7 @@ const historyForward = () => {
*/ */
const nextView = () => { const nextView = () => {
WEBVIEW.viewActive = (WEBVIEW.viewActive + 1) % WEBVIEW.views.length || 1; WEBVIEW.viewActive = (WEBVIEW.viewActive + 1) % WEBVIEW.views.length || 1;
updateView();
}; };
/** /**
@@ -297,7 +286,6 @@ const resizeView = () => {
width: window.width, width: window.width,
height: window.height - navigation.height, height: window.height - navigation.height,
}); });
view.webContents.setZoomFactor(WEBVIEW.viewZoom);
}); });
// Update widget size // 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. * Register window events and handler.
*/ */
@@ -351,7 +328,7 @@ const windowEvents = () => {
} }
WEBVIEW.locked = false; WEBVIEW.locked = false;
}); });
HARDWARE.display.notifiers.push(() => { HARDWARE.events.on("updateDisplay", () => {
WEBVIEW.locked = hardware.getDisplayStatus() === "OFF"; WEBVIEW.locked = hardware.getDisplayStatus() === "OFF";
if (WEBVIEW.locked) { if (WEBVIEW.locked) {
WEBVIEW.window.blur(); WEBVIEW.window.blur();
@@ -577,8 +554,9 @@ const domEvents = () => {
return { action: "deny" }; return { action: "deny" };
}); });
// Remove webview scrollbar // Update webview layout
view.webContents.on("dom-ready", () => { view.webContents.on("dom-ready", () => {
view.webContents.setZoomFactor(WEBVIEW.viewZoom);
view.webContents.insertCSS("::-webkit-scrollbar { display: none; }"); view.webContents.insertCSS("::-webkit-scrollbar { display: none; }");
}); });