V2
Some checks failed
Overlay Installer / build-overlay (push) Has been cancelled

This commit is contained in:
2026-05-11 21:24:15 +02:00
parent 1a9185f88f
commit b8a6fdfcc2
12 changed files with 5580 additions and 0 deletions

167
overlay/src/main.ts Normal file
View File

@@ -0,0 +1,167 @@
import { app, BrowserWindow, ipcMain, Menu, nativeImage, screen, Tray } from "electron";
import path from "node:path";
const OVERLAY_WIDTH = 820;
const OVERLAY_HEIGHT = 150;
const SCREEN_EDGE_MARGIN = 24;
const TASKBAR_GAP = 14;
const APP_USER_MODEL_ID = "com.customstreamdeck.overlay";
const TRAY_ICON_DATA_URL =
"data:image/png;base64," +
"iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAkUlEQVR4nGNgwAMEJdT/UwPjs4NmlpLlGFpbjtcR9LIcqyPobTmGI0a2AwbKcrgjiFGkdDSOLEwVB5BrObGOGPwOGPAoGBSJcNQBFnt/kIWp4gByLSfWEYPfAQMeBYMiEY464GexGF5MUwcQspxSRwy8A4hpFdHU8gFvEw4KBwx4x2RQdM3o5Qi8ltPKMfjsAABiL54YN/wECQAAAABJRU5ErkJggg==";
let overlayWindow: BrowserWindow | null = null;
let tray: Tray | null = null;
let isQuitting = false;
const hasSingleInstanceLock = app.requestSingleInstanceLock();
if (!hasSingleInstanceLock) {
app.quit();
}
function createOverlayWindow(): void {
const display = screen.getPrimaryDisplay();
const width = Math.min(OVERLAY_WIDTH, display.workArea.width - SCREEN_EDGE_MARGIN * 2);
const height = OVERLAY_HEIGHT;
const { x, y } = overlayPosition(display, width, height);
overlayWindow = new BrowserWindow({
width,
height,
x,
y,
frame: false,
transparent: true,
resizable: false,
movable: false,
minimizable: false,
maximizable: false,
fullscreenable: false,
alwaysOnTop: true,
skipTaskbar: true,
focusable: false,
show: false,
hasShadow: false,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
backgroundThrottling: false
}
});
overlayWindow.setIgnoreMouseEvents(true);
overlayWindow.setAlwaysOnTop(true, "screen-saver");
overlayWindow.loadFile(path.join(__dirname, "..", "index.html"));
overlayWindow.once("ready-to-show", () => {
console.log("[overlay:main] window ready; keeping transparent click-through overlay alive");
overlayWindow?.showInactive();
overlayWindow?.setIgnoreMouseEvents(true);
});
overlayWindow.webContents.on("console-message", details => {
console.log(`[overlay:renderer:${details.level}] ${details.message} (${details.sourceId}:${details.lineNumber})`);
});
overlayWindow.on("closed", () => {
console.log("[overlay:main] window closed");
overlayWindow = null;
});
}
function createTray(): void {
if (tray) {
return;
}
const icon = nativeImage.createFromDataURL(TRAY_ICON_DATA_URL);
tray = new Tray(icon);
tray.setToolTip("Custom Streamdeck Overlay");
tray.setContextMenu(
Menu.buildFromTemplate([
{
label: "Show overlay",
click: revealOverlayFromTray
},
{
label: "Quit",
click: () => {
isQuitting = true;
app.quit();
}
}
])
);
tray.on("click", revealOverlayFromTray);
}
function showOverlay(): void {
if (!overlayWindow) {
console.log("[overlay:main] show requested before window exists");
return;
}
const display = screen.getDisplayNearestPoint(screen.getCursorScreenPoint());
const [width, height] = overlayWindow.getSize();
console.log(`[overlay:main] showing overlay at ${width}x${height} on display ${display.id}`);
overlayWindow.setBounds({
width,
height,
...overlayPosition(display, width, height)
});
overlayWindow.showInactive();
overlayWindow.setAlwaysOnTop(true, "screen-saver");
overlayWindow.setIgnoreMouseEvents(true);
}
function hideOverlay(): void {
// Keep the BrowserWindow alive so the hidden renderer continues receiving websocket events.
console.log("[overlay:main] overlay faded; leaving window alive for websocket events");
overlayWindow?.setIgnoreMouseEvents(true);
}
function revealOverlayFromTray(): void {
if (!overlayWindow) {
createOverlayWindow();
}
overlayWindow?.webContents.send("overlay:reveal");
showOverlay();
}
if (hasSingleInstanceLock) {
app.on("second-instance", revealOverlayFromTray);
}
app.whenReady().then(() => {
app.setAppUserModelId(APP_USER_MODEL_ID);
console.log("[overlay:main] app ready");
createTray();
createOverlayWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createOverlayWindow();
}
});
});
app.on("before-quit", () => {
isQuitting = true;
});
app.on("window-all-closed", () => {
if (isQuitting) {
app.quit();
}
});
ipcMain.on("overlay:show", showOverlay);
ipcMain.on("overlay:hide", hideOverlay);
function overlayPosition(display: Electron.Display, width: number, height: number): { x: number; y: number } {
const x = Math.round(display.workArea.x + (display.workArea.width - width) / 2);
const bottomAlignedY = display.workArea.y + display.workArea.height - height - TASKBAR_GAP;
const y = Math.round(Math.max(display.workArea.y + SCREEN_EDGE_MARGIN, bottomAlignedY));
return { x, y };
}