This commit is contained in:
167
overlay/src/main.ts
Normal file
167
overlay/src/main.ts
Normal 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 };
|
||||
}
|
||||
Reference in New Issue
Block a user