feat(desktop): port v0.11.4-v0.15.2 from Tauri to Electron + Discord-parity audio (v0.16.0)
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>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
// 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();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user