112 lines
3.4 KiB
JavaScript
112 lines
3.4 KiB
JavaScript
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');
|
|
|
|
function createWindow() {
|
|
const win = new BrowserWindow({
|
|
width: 1000,
|
|
height: 800,
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
nodeIntegration: false,
|
|
contextIsolation: true,
|
|
}
|
|
});
|
|
|
|
win.loadFile('index.html');
|
|
}
|
|
|
|
app.whenReady().then(async () => {
|
|
// Setup the virtual microphone as soon as the app starts
|
|
await PipewireHelper.createVirtualMic();
|
|
|
|
createWindow();
|
|
|
|
app.on('activate', () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
createWindow();
|
|
}
|
|
});
|
|
});
|
|
|
|
app.on('will-quit', async () => {
|
|
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
|
|
});
|
|
|
|
// Wayland Workaround: If we only see generic "WebRTC PipeWire capturer" windows,
|
|
// try to fetch real window titles via our python helper
|
|
try {
|
|
const genericNames = ['webrtc pipewire capturer', 'screen 1', 'screen 2'];
|
|
const hasGeneric = inputSources.some(s => genericNames.includes(s.name.toLowerCase()));
|
|
|
|
if (hasGeneric || inputSources.length === 1) {
|
|
const { execSync } = require('child_process');
|
|
const pyPath = path.join(__dirname, 'wayland-helper.py');
|
|
const out = execSync(`python3 ${pyPath}`, { timeout: 2000 }).toString();
|
|
const waylandWindows = JSON.parse(out);
|
|
|
|
if (waylandWindows && waylandWindows.length > 0) {
|
|
// If we only have 1 capturer source (common on Wayland compositors),
|
|
// rename it to the first active window title we found to be helpful.
|
|
if (inputSources.length === 1 && waylandWindows[0].title) {
|
|
inputSources[0].name = waylandWindows[0].title;
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error("Wayland helper failed:", e.message);
|
|
}
|
|
|
|
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');
|
|
});
|