825160ee46
Chronological port of every Tauri release commit (v0.11.4 -> v0.15.2)
into the Electron rebuild, plus Discord-style audio handling that goes
beyond the Tauri original.
Highlights:
- All 17 Tauri release commits ported (audio fixes, custom notification
sound, Discord-style chat UX, profile banner, changelog page,
Discord-parity call UX, screen-share echo + re-watch UX, audio-loop
fix).
- Native napi-rs audio-loopback addon with WASAPI process-loopback:
* EXCLUDE_TARGET_PROCESS_TREE for full-screen shares -> peers
never hear themselves echoed back through the capture.
* INCLUDE_TARGET_PROCESS_TREE for window shares -> only the
picked window's audio is captured, not the whole OS mixer
(Discord parity).
* HWND -> PID resolution via Win32 GetWindowThreadProcessId.
- Discord-style screen-source picker (thumbnail grid, screens vs
apps tabs, live-refreshing thumbnails).
- Hash routing fix for packaged builds (file:// can't resolve
BrowserRouter paths).
- Tauri sources removed (apps/desktop/src-tauri).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
92 lines
2.7 KiB
TypeScript
92 lines
2.7 KiB
TypeScript
// Persists BrowserWindow bounds + maximized state to userData/<filename>.
|
|
// Saves are debounced 500ms on move/resize, and fired synchronously on close.
|
|
// Validates loaded bounds against the current display layout to avoid
|
|
// restoring a window onto a display that no longer exists.
|
|
|
|
import { app, BrowserWindow, screen } from 'electron';
|
|
import { promises as fs } from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
interface State {
|
|
x?: number;
|
|
y?: number;
|
|
width: number;
|
|
height: number;
|
|
maximized?: boolean;
|
|
}
|
|
|
|
const DEFAULT: State = { width: 1200, height: 800 };
|
|
|
|
function isOnAnyDisplay(bounds: { x: number; y: number; width: number; height: number }): boolean {
|
|
for (const d of screen.getAllDisplays()) {
|
|
const wa = d.workArea;
|
|
if (
|
|
bounds.x >= wa.x &&
|
|
bounds.y >= wa.y &&
|
|
bounds.x + bounds.width <= wa.x + wa.width + 8 &&
|
|
bounds.y + bounds.height <= wa.y + wa.height + 8
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export async function loadState(filename: string): Promise<State> {
|
|
const filePath = path.join(app.getPath('userData'), filename);
|
|
try {
|
|
const raw = await fs.readFile(filePath, 'utf8');
|
|
const parsed = JSON.parse(raw) as Partial<State>;
|
|
const width = typeof parsed.width === 'number' ? parsed.width : DEFAULT.width;
|
|
const height = typeof parsed.height === 'number' ? parsed.height : DEFAULT.height;
|
|
const state: State = { width, height };
|
|
if (typeof parsed.x === 'number' && typeof parsed.y === 'number') {
|
|
if (isOnAnyDisplay({ x: parsed.x, y: parsed.y, width, height })) {
|
|
state.x = parsed.x;
|
|
state.y = parsed.y;
|
|
}
|
|
}
|
|
if (parsed.maximized) state.maximized = true;
|
|
return state;
|
|
} catch {
|
|
return { ...DEFAULT };
|
|
}
|
|
}
|
|
|
|
export function attach(win: BrowserWindow, filename: string): void {
|
|
const filePath = path.join(app.getPath('userData'), filename);
|
|
let saveTimer: NodeJS.Timeout | null = null;
|
|
|
|
const writeNow = (): void => {
|
|
if (win.isDestroyed()) return;
|
|
const bounds = win.getNormalBounds();
|
|
const state: State = {
|
|
x: bounds.x,
|
|
y: bounds.y,
|
|
width: bounds.width,
|
|
height: bounds.height,
|
|
maximized: win.isMaximized(),
|
|
};
|
|
// Fire-and-forget; exceptions are logged but non-fatal.
|
|
void fs
|
|
.writeFile(filePath, JSON.stringify(state), 'utf8')
|
|
.catch((err: unknown) => {
|
|
console.warn('[window-state] write failed', err);
|
|
});
|
|
};
|
|
|
|
const schedule = (): void => {
|
|
if (saveTimer) clearTimeout(saveTimer);
|
|
saveTimer = setTimeout(writeNow, 500);
|
|
};
|
|
|
|
win.on('move', schedule);
|
|
win.on('resize', schedule);
|
|
win.on('maximize', schedule);
|
|
win.on('unmaximize', schedule);
|
|
win.on('close', () => {
|
|
if (saveTimer) clearTimeout(saveTimer);
|
|
writeNow();
|
|
});
|
|
}
|