feat: expose the desktopCapturer module in the main process (#23548)
This commit is contained in:
parent
4b23a85475
commit
df53816eea
10 changed files with 95 additions and 68 deletions
|
@ -46,6 +46,7 @@ const ignoredModules = []
|
|||
if (defines['ENABLE_DESKTOP_CAPTURER'] === 'false') {
|
||||
ignoredModules.push(
|
||||
'@electron/internal/browser/desktop-capturer',
|
||||
'@electron/internal/browser/api/desktop-capturer',
|
||||
'@electron/internal/renderer/api/desktop-capturer'
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
> Access information about media sources that can be used to capture audio and
|
||||
> video from the desktop using the [`navigator.mediaDevices.getUserMedia`] API.
|
||||
|
||||
Process: [Renderer](../glossary.md#renderer-process)
|
||||
Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process)
|
||||
|
||||
The following example shows how to capture video from a desktop window whose
|
||||
title is `Electron`:
|
||||
|
|
|
@ -193,6 +193,7 @@ auto_filenames = {
|
|||
"lib/browser/api/browser-window.js",
|
||||
"lib/browser/api/content-tracing.js",
|
||||
"lib/browser/api/crash-reporter.ts",
|
||||
"lib/browser/api/desktop-capturer.ts",
|
||||
"lib/browser/api/dialog.js",
|
||||
"lib/browser/api/exports/electron.ts",
|
||||
"lib/browser/api/global-shortcut.js",
|
||||
|
|
5
lib/browser/api/desktop-capturer.ts
Normal file
5
lib/browser/api/desktop-capturer.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { getSourcesImpl } from '@electron/internal/browser/desktop-capturer';
|
||||
|
||||
export async function getSources (options: Electron.SourcesOptions) {
|
||||
return getSourcesImpl(null, options);
|
||||
}
|
|
@ -33,6 +33,12 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
|
|||
{ name: 'WebContentsView', loader: () => require('./web-contents-view') }
|
||||
];
|
||||
|
||||
if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) {
|
||||
browserModuleList.push(
|
||||
{ name: 'desktopCapturer', loader: () => require('./desktop-capturer') }
|
||||
);
|
||||
}
|
||||
|
||||
if (BUILDFLAG(ENABLE_VIEWS_API)) {
|
||||
browserModuleList.push(
|
||||
{ name: 'ImageView', loader: () => require('./views/image-view') }
|
||||
|
|
|
@ -36,6 +36,10 @@ export const browserModuleNames = [
|
|||
'WebContentsView'
|
||||
];
|
||||
|
||||
if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) {
|
||||
browserModuleNames.push('desktopCapturer');
|
||||
}
|
||||
|
||||
if (BUILDFLAG(ENABLE_VIEWS_API)) {
|
||||
browserModuleNames.push(
|
||||
'ImageView'
|
||||
|
|
|
@ -7,7 +7,28 @@ let currentlyRunning: {
|
|||
getSources: Promise<ElectronInternal.GetSourcesResult[]>;
|
||||
}[] = [];
|
||||
|
||||
export const getSources = (event: Electron.IpcMainEvent, options: ElectronInternal.GetSourcesOptions) => {
|
||||
// |options.types| can't be empty and must be an array
|
||||
function isValid (options: Electron.SourcesOptions) {
|
||||
const types = options ? options.types : undefined;
|
||||
return Array.isArray(types);
|
||||
}
|
||||
|
||||
export const getSourcesImpl = (event: Electron.IpcMainEvent | null, args: Electron.SourcesOptions) => {
|
||||
if (!isValid(args)) throw new Error('Invalid options');
|
||||
|
||||
const captureWindow = args.types.includes('window');
|
||||
const captureScreen = args.types.includes('screen');
|
||||
|
||||
const { thumbnailSize = { width: 150, height: 150 } } = args;
|
||||
const { fetchWindowIcons = false } = args;
|
||||
|
||||
const options = {
|
||||
captureWindow,
|
||||
captureScreen,
|
||||
thumbnailSize,
|
||||
fetchWindowIcons
|
||||
};
|
||||
|
||||
for (const running of currentlyRunning) {
|
||||
if (deepEqual(running.options, options)) {
|
||||
// If a request is currently running for the same options
|
||||
|
@ -39,12 +60,14 @@ export const getSources = (event: Electron.IpcMainEvent, options: ElectronIntern
|
|||
resolve(sources);
|
||||
};
|
||||
|
||||
capturer.startHandling(options.captureWindow, options.captureScreen, options.thumbnailSize, options.fetchWindowIcons);
|
||||
capturer.startHandling(captureWindow, captureScreen, thumbnailSize, fetchWindowIcons);
|
||||
|
||||
// If the WebContents is destroyed before receiving result, just remove the
|
||||
// reference to emit and the capturer itself so that it never dispatches
|
||||
// back to the renderer
|
||||
event.sender.once('destroyed', () => stopRunning());
|
||||
if (event) {
|
||||
event.sender.once('destroyed', () => stopRunning());
|
||||
}
|
||||
});
|
||||
|
||||
currentlyRunning.push({
|
||||
|
|
|
@ -71,7 +71,7 @@ if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) {
|
|||
return [];
|
||||
}
|
||||
|
||||
return typeUtils.serialize(await desktopCapturer.getSources(event, options));
|
||||
return typeUtils.serialize(await desktopCapturer.getSourcesImpl(event, options));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,6 @@ import { deserialize } from '@electron/internal/common/type-utils';
|
|||
|
||||
const { hasSwitch } = process.electronBinding('command_line');
|
||||
|
||||
// |options.types| can't be empty and must be an array
|
||||
function isValid (options: Electron.SourcesOptions) {
|
||||
const types = options ? options.types : undefined;
|
||||
return Array.isArray(types);
|
||||
}
|
||||
|
||||
const enableStacks = hasSwitch('enable-api-filtering-logging');
|
||||
|
||||
function getCurrentStack () {
|
||||
|
@ -20,20 +14,5 @@ function getCurrentStack () {
|
|||
}
|
||||
|
||||
export async function getSources (options: Electron.SourcesOptions) {
|
||||
if (!isValid(options)) throw new Error('Invalid options');
|
||||
|
||||
const captureWindow = options.types.includes('window');
|
||||
const captureScreen = options.types.includes('screen');
|
||||
|
||||
const { thumbnailSize = { width: 150, height: 150 } } = options;
|
||||
const { fetchWindowIcons = false } = options;
|
||||
|
||||
const sources = await ipcRendererInternal.invoke<ElectronInternal.GetSourcesResult[]>('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', {
|
||||
captureWindow,
|
||||
captureScreen,
|
||||
thumbnailSize,
|
||||
fetchWindowIcons
|
||||
} as ElectronInternal.GetSourcesOptions, getCurrentStack());
|
||||
|
||||
return deserialize(sources);
|
||||
return deserialize(await ipcRendererInternal.invoke('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options, getCurrentStack()));
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { expect } from 'chai';
|
||||
import { screen, BrowserWindow, SourcesOptions } from 'electron/main';
|
||||
import { desktopCapturer } from 'electron/renderer';
|
||||
import { desktopCapturer } from 'electron/common';
|
||||
import { emittedOnce } from './events-helpers';
|
||||
import { ifdescribe, ifit } from './spec-helpers';
|
||||
import { closeAllWindows } from './window-helpers';
|
||||
|
@ -21,55 +21,63 @@ ifdescribe(features.isDesktopCapturerEnabled() && !process.arch.includes('arm')
|
|||
`);
|
||||
};
|
||||
|
||||
// TODO(nornagon): figure out why this test is failing on Linux and re-enable it.
|
||||
ifit(process.platform !== 'linux')('should return a non-empty array of sources', async () => {
|
||||
const sources = await getSources({ types: ['window', 'screen'] });
|
||||
expect(sources).to.be.an('array').that.is.not.empty();
|
||||
});
|
||||
const generateSpecs = (description: string, getSources: typeof desktopCapturer.getSources) => {
|
||||
describe(description, () => {
|
||||
// TODO(nornagon): figure out why this test is failing on Linux and re-enable it.
|
||||
ifit(process.platform !== 'linux')('should return a non-empty array of sources', async () => {
|
||||
const sources = await getSources({ types: ['window', 'screen'] });
|
||||
expect(sources).to.be.an('array').that.is.not.empty();
|
||||
});
|
||||
|
||||
it('throws an error for invalid options', async () => {
|
||||
const promise = getSources(['window', 'screen'] as any);
|
||||
await expect(promise).to.be.eventually.rejectedWith(Error, 'Invalid options');
|
||||
});
|
||||
it('throws an error for invalid options', async () => {
|
||||
const promise = getSources(['window', 'screen'] as any);
|
||||
await expect(promise).to.be.eventually.rejectedWith(Error, 'Invalid options');
|
||||
});
|
||||
|
||||
// TODO(nornagon): figure out why this test is failing on Linux and re-enable it.
|
||||
ifit(process.platform !== 'linux')('does not throw an error when called more than once (regression)', async () => {
|
||||
const sources1 = await getSources({ types: ['window', 'screen'] });
|
||||
expect(sources1).to.be.an('array').that.is.not.empty();
|
||||
// TODO(nornagon): figure out why this test is failing on Linux and re-enable it.
|
||||
ifit(process.platform !== 'linux')('does not throw an error when called more than once (regression)', async () => {
|
||||
const sources1 = await getSources({ types: ['window', 'screen'] });
|
||||
expect(sources1).to.be.an('array').that.is.not.empty();
|
||||
|
||||
const sources2 = await getSources({ types: ['window', 'screen'] });
|
||||
expect(sources2).to.be.an('array').that.is.not.empty();
|
||||
});
|
||||
const sources2 = await getSources({ types: ['window', 'screen'] });
|
||||
expect(sources2).to.be.an('array').that.is.not.empty();
|
||||
});
|
||||
|
||||
ifit(process.platform !== 'linux')('responds to subsequent calls of different options', async () => {
|
||||
const promise1 = getSources({ types: ['window'] });
|
||||
await expect(promise1).to.eventually.be.fulfilled();
|
||||
ifit(process.platform !== 'linux')('responds to subsequent calls of different options', async () => {
|
||||
const promise1 = getSources({ types: ['window'] });
|
||||
await expect(promise1).to.eventually.be.fulfilled();
|
||||
|
||||
const promise2 = getSources({ types: ['screen'] });
|
||||
await expect(promise2).to.eventually.be.fulfilled();
|
||||
});
|
||||
const promise2 = getSources({ types: ['screen'] });
|
||||
await expect(promise2).to.eventually.be.fulfilled();
|
||||
});
|
||||
|
||||
// Linux doesn't return any window sources.
|
||||
ifit(process.platform !== 'linux')('returns an empty display_id for window sources on Windows and Mac', async () => {
|
||||
const w = new BrowserWindow({ width: 200, height: 200 });
|
||||
// Linux doesn't return any window sources.
|
||||
ifit(process.platform !== 'linux')('returns an empty display_id for window sources on Windows and Mac', async () => {
|
||||
const w = new BrowserWindow({ width: 200, height: 200 });
|
||||
await w.loadURL('about:blank');
|
||||
|
||||
const sources = await getSources({ types: ['window'] });
|
||||
w.destroy();
|
||||
expect(sources).to.be.an('array').that.is.not.empty();
|
||||
for (const { display_id: displayId } of sources) {
|
||||
expect(displayId).to.be.a('string').and.be.empty();
|
||||
}
|
||||
});
|
||||
const sources = await getSources({ types: ['window'] });
|
||||
w.destroy();
|
||||
expect(sources).to.be.an('array').that.is.not.empty();
|
||||
for (const { display_id: displayId } of sources) {
|
||||
expect(displayId).to.be.a('string').and.be.empty();
|
||||
}
|
||||
});
|
||||
|
||||
ifit(process.platform !== 'linux')('returns display_ids matching the Screen API on Windows and Mac', async () => {
|
||||
const displays = screen.getAllDisplays();
|
||||
const sources = await getSources({ types: ['screen'] });
|
||||
expect(sources).to.be.an('array').of.length(displays.length);
|
||||
ifit(process.platform !== 'linux')('returns display_ids matching the Screen API on Windows and Mac', async () => {
|
||||
const displays = screen.getAllDisplays();
|
||||
const sources = await getSources({ types: ['screen'] });
|
||||
expect(sources).to.be.an('array').of.length(displays.length);
|
||||
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
expect(sources[i].display_id).to.equal(displays[i].id.toString());
|
||||
}
|
||||
});
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
expect(sources[i].display_id).to.equal(displays[i].id.toString());
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
generateSpecs('in renderer process', getSources);
|
||||
generateSpecs('in main process', desktopCapturer.getSources);
|
||||
|
||||
ifit(process.platform !== 'linux')('returns an empty source list if blocked by the main process', async () => {
|
||||
w.webContents.once('desktop-capturer-get-sources', (event) => {
|
||||
|
|
Loading…
Reference in a new issue