Compare commits

...

3 commits

4 changed files with 136 additions and 19 deletions

View file

@ -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;

View file

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

View file

@ -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);
}

View file

@ -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 {