const { app, BrowserWindow, desktopCapturer, ipcMain } = require('electron'); const path = require('path'); const PipewireHelper = require('./pipewire'); // Ensure Chromium tries to use PipeWire for screen sharing on Linux app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer'); let mainWindow = null; function createWindow() { mainWindow = new BrowserWindow({ width: 1000, height: 800, icon: path.join(__dirname, 'icon.png'), webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: false, contextIsolation: true, } }); mainWindow.loadFile('index.html'); mainWindow.on('closed', () => { mainWindow = null; }); } app.whenReady().then(async () => { // Setup the virtual microphone as soon as the app starts await PipewireHelper.createVirtualMic(); createWindow(); // Monitor PipeWire graph for new/removed audio streams PipewireHelper.startMonitoring(async () => { try { const apps = await PipewireHelper.getAudioApplications(); if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send('audio-apps-updated', apps); } } catch (e) { console.error('Failed to refresh audio apps:', e); } }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); app.on('will-quit', async () => { PipewireHelper.stopMonitoring(); await PipewireHelper.destroyVirtualMic(); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); // Handle IPC request from renderer to get screen/audio sources ipcMain.handle('get-sources', async () => { let inputSources = await desktopCapturer.getSources({ types: ['window', 'screen'], fetchWindowIcons: true }); // Note: On Wayland, source names come from the ScreenCast portal directly. // The wayland-helper override was removed because it returned stale/incorrect window titles. return inputSources.map(source => ({ id: source.id, name: source.name, thumbnail: source.thumbnail.toDataURL() })); }); // Handle Pipewire specific audio isolation requests ipcMain.handle('get-audio-apps', async () => { return await PipewireHelper.getAudioApplications(); }); ipcMain.handle('link-app-audio', async (event, appName) => { return await PipewireHelper.linkApplicationToMic(appName); }); ipcMain.handle('link-monitor-audio', async () => { return await PipewireHelper.linkMonitorToMic(); }); // Handle saving and loading the config.json profile const fs = require('fs'); const configPath = path.join(__dirname, 'config.json'); ipcMain.handle('get-config', () => { try { if (fs.existsSync(configPath)) { return JSON.parse(fs.readFileSync(configPath, 'utf8')); } } catch (e) { console.error("Could not read config", e); } return { serverUrl: 'http://localhost:3000', serverPassword: '' }; }); ipcMain.handle('save-config', (event, newConfig) => { fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2), 'utf8'); });