Compare commits

..

3 commits

4 changed files with 136 additions and 19 deletions

View file

@ -85,6 +85,17 @@ export const config: Configuration = {
electronDownload: { electronDownload: {
cache: ".cache", cache: ".cache",
}, },
electronFuses: {
runAsNode: false,
enableCookieEncryption: false,
enableNodeOptionsEnvironmentVariable: false,
enableNodeCliInspectArguments: false,
enableEmbeddedAsarIntegrityValidation: false,
onlyLoadAppFromAsar: true,
loadBrowserProcessSpecificV8Snapshot: false,
grantFileProtocolExtraPrivileges: false,
},
}; };
export default config; export default config;

View file

@ -4,4 +4,6 @@ export interface WindowState {
x: number; x: number;
y: number; y: number;
isMaximized: boolean; isMaximized: boolean;
displayId?: number;
displayScaleFactor?: number;
} }

View file

@ -8,6 +8,7 @@ import {
dialog, dialog,
type MessageBoxOptions, type MessageBoxOptions,
nativeImage, nativeImage,
screen,
shell, shell,
} from "electron"; } from "electron";
import contextMenu from "electron-context-menu"; import contextMenu from "electron-context-menu";
@ -29,6 +30,51 @@ import { registerVenmicIpc } from "./venmic.js";
export let mainWindows: BrowserWindow[] = []; export let mainWindows: BrowserWindow[] = [];
export let inviteWindow: 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({ contextMenu({
showSaveImageAs: true, showSaveImageAs: true,
showCopyImageAddress: true, showCopyImageAddress: true,
@ -291,6 +337,10 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void {
passedWindow.destroy(); passedWindow.destroy();
} }
if (getConfig("minimizeToTray") && !forceQuit) { if (getConfig("minimizeToTray") && !forceQuit) {
// Save state when hiding to tray so we persist display metadata
try {
saveWindowState(passedWindow);
} catch {}
e.preventDefault(); e.preventDefault();
passedWindow.hide(); passedWindow.hide();
} else if (!getConfig("minimizeToTray")) { } else if (!getConfig("minimizeToTray")) {
@ -299,17 +349,22 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void {
}); });
app.on("before-quit", () => { app.on("before-quit", () => {
stopRPC(); stopRPC();
const [width, height] = passedWindow.getSize(); try {
setWindowState({ // Ensure current window state is saved with display info
width, if (passedWindow && !passedWindow.isDestroyed()) saveWindowState(passedWindow);
height, } catch (e) {
isMaximized: passedWindow.isMaximized(), console.log("[Window] before-quit save failed:", e);
x: passedWindow.getPosition()[0], }
y: passedWindow.getPosition()[1],
});
setForceQuit(true); setForceQuit(true);
}); });
// also save on minimize in case of session shutdowns
passedWindow.on("minimize", () => {
try {
saveWindowState(passedWindow);
} catch {}
});
passedWindow.on("focus", () => { passedWindow.on("focus", () => {
void passedWindow.webContents.executeJavaScript(`document.body.removeAttribute("unFocused");`); void passedWindow.webContents.executeJavaScript(`document.body.removeAttribute("unFocused");`);
}); });
@ -331,6 +386,48 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void {
} }
registerGlobalKeybinds(); 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")) { switch (getConfig("channel")) {
case "stable": case "stable":
void passedWindow.loadURL("https://discord.com/app"); void passedWindow.loadURL("https://discord.com/app");
@ -352,11 +449,11 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void {
} }
export function createWindow() { export function createWindow() {
const storedBounds = getStoredWindowBounds();
const browserWindowOptions: BrowserWindowConstructorOptions = { const browserWindowOptions: BrowserWindowConstructorOptions = {
width: getWindowState("width") ?? 835, // Use safe defaults for constructor; actual bounds applied via setBounds() below
height: getWindowState("height") ?? 600, width: 835,
x: getWindowState("x"), height: 600,
y: getWindowState("y"),
title: "Legcord", title: "Legcord",
show: false, show: false,
darkTheme: true, darkTheme: true,
@ -417,6 +514,13 @@ export function createWindow() {
break; break;
} }
const mainWindow = new BrowserWindow(browserWindowOptions); 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); mainWindows.push(mainWindow);
doAfterDefiningTheWindow(mainWindow); doAfterDefiningTheWindow(mainWindow);
} }

View file

@ -9,14 +9,16 @@
background-color 0.15s ease, background-color 0.15s ease,
transform 0.1s ease, transform 0.1s ease,
border-color 0.25s ease; border-color 0.25s ease;
border: 2px solid transparent; border: 2px solid var(--background-modifier-accent);
position: relative; position: relative;
user-select: none; user-select: none;
overflow: visible; overflow: visible;
background: var(--background-secondary);
} }
.card:hover { .card:hover {
background-color: var(--background-modifier-hover); background-color: var(--background-modifier-hover);
border-color: var(--background-modifier-selected);
} }
.card:active { .card:active {
@ -25,11 +27,11 @@
.cardSelected { .cardSelected {
border-color: var(--brand-experiment-560); 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 { .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 { .checkBadge {
@ -89,8 +91,7 @@
transition: transition:
opacity 0.2s ease, opacity 0.2s ease,
filter 0.2s ease; filter 0.2s ease;
opacity: 0.55; opacity: 0.85;
filter: brightness(0.85);
} }
.thumbnailSelected { .thumbnailSelected {
@ -105,8 +106,7 @@
} }
.card:hover .thumbnailUnselected { .card:hover .thumbnailUnselected {
opacity: 0.75; opacity: 1;
filter: brightness(0.95);
} }
.name { .name {