diff --git a/electron-builder.ts b/electron-builder.ts index bd784de..9a6ec11 100644 --- a/electron-builder.ts +++ b/electron-builder.ts @@ -85,6 +85,17 @@ export const config: Configuration = { electronDownload: { cache: ".cache", }, + + electronFuses: { + runAsNode: false, + enableCookieEncryption: false, + enableNodeOptionsEnvironmentVariable: false, + enableNodeCliInspectArguments: false, + enableEmbeddedAsarIntegrityValidation: false, + onlyLoadAppFromAsar: true, + loadBrowserProcessSpecificV8Snapshot: false, + grantFileProtocolExtraPrivileges: false, + }, }; export default config; diff --git a/src/@types/windowState.d.ts b/src/@types/windowState.d.ts index 93eeb3f..6a82527 100644 --- a/src/@types/windowState.d.ts +++ b/src/@types/windowState.d.ts @@ -4,4 +4,6 @@ export interface WindowState { x: number; y: number; isMaximized: boolean; + displayId?: number; + displayScaleFactor?: number; } diff --git a/src/discord/window.ts b/src/discord/window.ts index 339e9f0..c969714 100644 --- a/src/discord/window.ts +++ b/src/discord/window.ts @@ -8,6 +8,7 @@ import { dialog, type MessageBoxOptions, nativeImage, + screen, shell, } from "electron"; import contextMenu from "electron-context-menu"; @@ -29,6 +30,51 @@ import { registerVenmicIpc } from "./venmic.js"; export let mainWindows: BrowserWindow[] = []; export let inviteWindow: BrowserWindow; +function getStoredWindowBounds(): BrowserWindowConstructorOptions { + const width = getWindowState("width") ?? 835; + const height = getWindowState("height") ?? 600; + const x = getWindowState("x"); + const y = getWindowState("y"); + + if (x === undefined || y === undefined) { + return { + width, + height, + }; + } + + // Return the stored window coordinates as-is. + // Restore uses setPosition/setSize for a direct API roundtrip. + return { + width, + height, + x, + y, + }; +} + +// Save window bounds using the same API family we restore with. +// This avoids coordinate-space mismatches caused by getNormalBounds()/setBounds() across DPI setups. +function saveWindowState(win: BrowserWindow): void { + try { + const [x, y] = win.getPosition(); + const [width, height] = win.getSize(); + const display = screen.getDisplayNearestPoint({ x, y }); + + setWindowState({ + width, + height, + isMaximized: win.isMaximized(), + x, + y, + displayId: display.id, + displayScaleFactor: display.scaleFactor, + }); + } catch (e) { + console.log("[Window] Failed to save window state:", e); + } +} + contextMenu({ showSaveImageAs: true, showCopyImageAddress: true, @@ -291,6 +337,10 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { passedWindow.destroy(); } if (getConfig("minimizeToTray") && !forceQuit) { + // Save state when hiding to tray so we persist display metadata + try { + saveWindowState(passedWindow); + } catch {} e.preventDefault(); passedWindow.hide(); } else if (!getConfig("minimizeToTray")) { @@ -299,17 +349,22 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { }); app.on("before-quit", () => { stopRPC(); - const [width, height] = passedWindow.getSize(); - setWindowState({ - width, - height, - isMaximized: passedWindow.isMaximized(), - x: passedWindow.getPosition()[0], - y: passedWindow.getPosition()[1], - }); + try { + // Ensure current window state is saved with display info + if (passedWindow && !passedWindow.isDestroyed()) saveWindowState(passedWindow); + } catch (e) { + console.log("[Window] before-quit save failed:", e); + } setForceQuit(true); }); + // also save on minimize in case of session shutdowns + passedWindow.on("minimize", () => { + try { + saveWindowState(passedWindow); + } catch {} + }); + passedWindow.on("focus", () => { void passedWindow.webContents.executeJavaScript(`document.body.removeAttribute("unFocused");`); }); @@ -331,6 +386,48 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { } registerGlobalKeybinds(); + // Persist bounds on move/resize with debounce to avoid frequent writes + let _saveTimer: NodeJS.Timeout | null = null; + const queueSave = () => { + if (_saveTimer) clearTimeout(_saveTimer); + _saveTimer = setTimeout(() => { + try { + saveWindowState(passedWindow); + } catch (e) { + console.log("[Window] queueSave failed:", e); + } + _saveTimer = null; + }, 500); + }; + passedWindow.on("move", queueSave); + passedWindow.on("resize", queueSave); + + // Fallback: periodic poll to detect bounds changes. + // Compare raw getNormalBounds() values to detect movement; + // saveWindowState normalizes to DIP where needed. + let lastPolledBounds: { x: number; y: number; width: number; height: number } | null = null; + const pollInterval = setInterval(() => { + try { + if (passedWindow.isDestroyed()) { + clearInterval(pollInterval); + return; + } + const { x, y, width, height } = passedWindow.getNormalBounds(); + if ( + !lastPolledBounds || + lastPolledBounds.x !== x || + lastPolledBounds.y !== y || + lastPolledBounds.width !== width || + lastPolledBounds.height !== height + ) { + lastPolledBounds = { x, y, width, height }; + saveWindowState(passedWindow); + } + } catch (e) { + // ignore transient errors + } + }, 1000); + passedWindow.on("closed", () => clearInterval(pollInterval)); switch (getConfig("channel")) { case "stable": void passedWindow.loadURL("https://discord.com/app"); @@ -352,11 +449,11 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void { } export function createWindow() { + const storedBounds = getStoredWindowBounds(); const browserWindowOptions: BrowserWindowConstructorOptions = { - width: getWindowState("width") ?? 835, - height: getWindowState("height") ?? 600, - x: getWindowState("x"), - y: getWindowState("y"), + // Use safe defaults for constructor; actual bounds applied via setBounds() below + width: 835, + height: 600, title: "Legcord", show: false, darkTheme: true, @@ -417,6 +514,13 @@ export function createWindow() { break; } const mainWindow = new BrowserWindow(browserWindowOptions); + + // Restore by position + size directly to match saveWindowState roundtrip. + if (storedBounds.x !== undefined && storedBounds.y !== undefined) { + mainWindow.setPosition(storedBounds.x, storedBounds.y); + mainWindow.setSize(storedBounds.width, storedBounds.height); + } + mainWindows.push(mainWindow); doAfterDefiningTheWindow(mainWindow); } diff --git a/src/shelter/screenshare/components/SourceCard.module.css b/src/shelter/screenshare/components/SourceCard.module.css index c615cd5..21e0f57 100644 --- a/src/shelter/screenshare/components/SourceCard.module.css +++ b/src/shelter/screenshare/components/SourceCard.module.css @@ -9,14 +9,16 @@ background-color 0.15s ease, transform 0.1s ease, border-color 0.25s ease; - border: 2px solid transparent; + border: 2px solid var(--background-modifier-accent); position: relative; user-select: none; overflow: visible; + background: var(--background-secondary); } .card:hover { background-color: var(--background-modifier-hover); + border-color: var(--background-modifier-selected); } .card:active { @@ -25,11 +27,11 @@ .cardSelected { border-color: var(--brand-experiment-560); - background-color: color-mix(in srgb, var(--brand-experiment-560) 10%, transparent); + background-color: color-mix(in srgb, var(--brand-experiment-560) 12%, var(--background-secondary)); } .cardSelected:hover { - background-color: color-mix(in srgb, var(--brand-experiment-560) 16%, transparent); + background-color: color-mix(in srgb, var(--brand-experiment-560) 18%, var(--background-secondary)); } .checkBadge { @@ -89,8 +91,7 @@ transition: opacity 0.2s ease, filter 0.2s ease; - opacity: 0.55; - filter: brightness(0.85); + opacity: 0.85; } .thumbnailSelected { @@ -105,8 +106,7 @@ } .card:hover .thumbnailUnselected { - opacity: 0.75; - filter: brightness(0.95); + opacity: 1; } .name {