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:
byGalax
2026-05-06 23:35:01 +02:00
parent 72d02e385f
commit 825160ee46
504 changed files with 14217 additions and 14366 deletions
+91
View File
@@ -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();
});
}