jitsi-meet-electron/main.js

406 lines
11 KiB
JavaScript

/* global __dirname, process */
const {
BrowserWindow,
Menu,
app,
ipcMain
} = require('electron');
const contextMenu = require('electron-context-menu');
const debug = require('electron-debug');
const isDev = require('electron-is-dev');
const { autoUpdater } = require('electron-updater');
const windowStateKeeper = require('electron-window-state');
const {
initPopupsConfigurationMain,
getPopupTarget,
RemoteControlMain,
setupAlwaysOnTopMain,
setupPowerMonitorMain,
setupScreenSharingMain
} = require('jitsi-meet-electron-utils');
const path = require('path');
const URL = require('url');
const config = require('./app/features/config');
const { openExternalLink } = require('./app/features/utils/openExternalLink');
const pkgJson = require('./package.json');
const { existsSync } = require('fs');
const showDevTools = Boolean(process.env.SHOW_DEV_TOOLS) || (process.argv.indexOf('--show-dev-tools') > -1);
const ENABLE_REMOTE_CONTROL = false;
// We need this because of https://github.com/electron/electron/issues/18214
app.commandLine.appendSwitch('disable-site-isolation-trials');
// This allows BrowserWindow.setContentProtection(true) to work on macOS.
// https://github.com/electron/electron/issues/19880
app.commandLine.appendSwitch('disable-features', 'IOSurfaceCapturer');
// Enable Opus RED field trial.
app.commandLine.appendSwitch('force-fieldtrials', 'WebRTC-Audio-Red-For-Opus/Enabled/');
// Enable optional PipeWire support.
if (!app.commandLine.hasSwitch('enable-features')) {
app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');
}
autoUpdater.logger = require('electron-log');
autoUpdater.logger.transports.file.level = 'info';
// Enable context menu so things like copy and paste work in input fields.
contextMenu({
showLookUpSelection: false,
showSearchWithGoogle: false,
showCopyImage: false,
showCopyImageAddress: false,
showSaveImage: false,
showSaveImageAs: false,
showInspectElement: true,
showServices: false
});
// Enable DevTools also on release builds to help troubleshoot issues. Don't
// show them automatically though.
debug({
isEnabled: true,
showDevTools
});
/**
* When in development mode:
* - Enable automatic reloads
*/
if (isDev) {
require('electron-reload')(path.join(__dirname, 'build'));
}
/**
* The window object that will load the iframe with Jitsi Meet.
* IMPORTANT: Must be defined as global in order to not be garbage collected
* acidentally.
*/
let mainWindow = null;
let webrtcInternalsWindow = null;
/**
* Add protocol data
*/
const appProtocolSurplus = `${config.default.appProtocolPrefix}://`;
let rendererReady = false;
let protocolDataForFrontApp = null;
/**
* Sets the application menu. It is hidden on all platforms except macOS because
* otherwise copy and paste functionality is not available.
*/
function setApplicationMenu() {
if (process.platform === 'darwin') {
const template = [ {
label: app.name,
submenu: [
{
role: 'services',
submenu: []
},
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
}, {
label: 'Edit',
submenu: [ {
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
selector: 'undo:'
},
{
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
selector: 'redo:'
},
{
type: 'separator'
},
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
selector: 'cut:'
},
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
selector: 'copy:'
},
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
selector: 'paste:'
},
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
selector: 'selectAll:'
} ]
}, {
label: '&Window',
role: 'window',
submenu: [
{ role: 'minimize' },
{ role: 'close' }
]
} ];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
} else {
Menu.setApplicationMenu(null);
}
}
/**
* Opens new window with index.html(Jitsi Meet is loaded in iframe there).
*/
function createJitsiMeetWindow() {
// Application menu.
setApplicationMenu();
// Check for Updates.
if (!process.mas) {
autoUpdater.checkForUpdatesAndNotify();
}
// Load the previous window state with fallback to defaults.
const windowState = windowStateKeeper({
defaultWidth: 800,
defaultHeight: 600
});
// Path to root directory.
let basePath = isDev ? __dirname : app.getAppPath();
// runtime detection on mac if this is a universal build with app-arm64.asar'
// as prepared in https://github.com/electron/universal/blob/master/src/index.ts
// if universal build, load the arch-specific real asar as the app does not load otherwise
if (process.platform === 'darwin' && existsSync(path.join(app.getAppPath(), '..', 'app-arm64.asar'))) {
if (process.arch === 'arm64') {
basePath = app.getAppPath().replace('app.asar', 'app-arm64.asar');
} else if (process.arch === 'x64') {
basePath = app.getAppPath().replace('app.asar', 'app-x64.asar');
}
}
// URL for index.html which will be our entry point.
const indexURL = URL.format({
pathname: path.resolve(basePath, './build/index.html'),
protocol: 'file:',
slashes: true
});
// Options used when creating the main Jitsi Meet window.
// Use a preload script in order to provide node specific functionality
// to a isolated BrowserWindow in accordance with electron security
// guideline.
const options = {
x: windowState.x,
y: windowState.y,
width: windowState.width,
height: windowState.height,
icon: path.resolve(basePath, './resources/icon.png'),
minWidth: 800,
minHeight: 600,
show: false,
webPreferences: {
enableBlinkFeatures: 'RTCInsertableStreams,WebAssemblySimd,WebAssemblyCSP',
enableRemoteModule: true,
contextIsolation: false,
nativeWindowOpen: true,
nodeIntegration: false,
preload: path.resolve(basePath, './build/preload.js')
}
};
mainWindow = new BrowserWindow(options);
windowState.manage(mainWindow);
mainWindow.loadURL(indexURL);
initPopupsConfigurationMain(mainWindow);
setupAlwaysOnTopMain(mainWindow);
setupPowerMonitorMain(mainWindow);
setupScreenSharingMain(mainWindow, config.default.appName, pkgJson.build.appId);
if (ENABLE_REMOTE_CONTROL) {
new RemoteControlMain(mainWindow); // eslint-disable-line no-new
}
mainWindow.webContents.on('new-window', (event, url, frameName) => {
const target = getPopupTarget(url, frameName);
if (!target || target === 'browser') {
event.preventDefault();
openExternalLink(url);
}
});
mainWindow.on('closed', () => {
mainWindow = null;
});
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
/**
* When someone tries to enter something like jitsi-meet://test
* while app is closed
* it will trigger this event below
*/
handleProtocolCall(process.argv.pop());
}
/**
* Opens new window with WebRTC internals.
*/
function createWebRTCInternalsWindow() {
const options = {
minWidth: 800,
minHeight: 600,
show: true
};
webrtcInternalsWindow = new BrowserWindow(options);
webrtcInternalsWindow.loadURL('chrome://webrtc-internals');
}
/**
* Handler for application protocol links to initiate a conference.
*/
function handleProtocolCall(fullProtocolCall) {
// don't touch when something is bad
if (
!fullProtocolCall
|| fullProtocolCall.trim() === ''
|| fullProtocolCall.indexOf(appProtocolSurplus) !== 0
) {
return;
}
const inputURL = fullProtocolCall.replace(appProtocolSurplus, '');
if (app.isReady() && mainWindow === null) {
createJitsiMeetWindow();
}
protocolDataForFrontApp = inputURL;
if (rendererReady) {
mainWindow
.webContents
.send('protocol-data-msg', inputURL);
}
}
/**
* Force Single Instance Application.
* Handle this on darwin via LSMultipleInstancesProhibited in Info.plist as below does not work on MAS
*/
const gotInstanceLock = process.platform === 'darwin' ? true : app.requestSingleInstanceLock();
if (!gotInstanceLock) {
app.quit();
process.exit(0);
}
/**
* Run the application.
*/
app.on('activate', () => {
if (mainWindow === null) {
createJitsiMeetWindow();
}
});
app.on('certificate-error',
// eslint-disable-next-line max-params
(event, webContents, url, error, certificate, callback) => {
if (isDev) {
event.preventDefault();
callback(true);
} else {
callback(false);
}
}
);
app.on('ready', createJitsiMeetWindow);
if (isDev) {
app.on('ready', createWebRTCInternalsWindow);
}
app.on('second-instance', (event, commandLine) => {
/**
* If someone creates second instance of the application, set focus on
* existing window.
*/
if (mainWindow) {
mainWindow.isMinimized() && mainWindow.restore();
mainWindow.focus();
/**
* This is for windows [win32]
* so when someone tries to enter something like jitsi-meet://test
* while app is opened it will trigger protocol handler.
*/
handleProtocolCall(commandLine.pop());
}
});
app.on('window-all-closed', () => {
app.quit();
});
// remove so we can register each time as we run the app.
app.removeAsDefaultProtocolClient(config.default.appProtocolPrefix);
// If we are running a non-packaged version of the app && on windows
if (isDev && process.platform === 'win32') {
// Set the path of electron.exe and your app.
// These two additional parameters are only available on windows.
app.setAsDefaultProtocolClient(
config.default.appProtocolPrefix,
process.execPath,
[ path.resolve(process.argv[1]) ]
);
} else {
app.setAsDefaultProtocolClient(config.default.appProtocolPrefix);
}
/**
* This is for mac [darwin]
* so when someone tries to enter something like jitsi-meet://test
* it will trigger this event below
*/
app.on('open-url', (event, data) => {
event.preventDefault();
handleProtocolCall(data);
});
/**
* This is to notify main.js [this] that front app is ready to receive messages.
*/
ipcMain.on('renderer-ready', () => {
rendererReady = true;
if (protocolDataForFrontApp) {
mainWindow
.webContents
.send('protocol-data-msg', protocolDataForFrontApp);
}
});
ipcMain.on('electron-store-exists', event => {
event.returnValue = existsSync(path.join(app.getPath('userData'), 'config.json'));
});