Skip to content

Commit

Permalink
feat: make desktopCapturer main-process-only (#30720)
Browse files Browse the repository at this point in the history
* feat: make desktopCapturer main-process-only

* remove --enable-api-filtering-logging

* remove test

* merge lib/browser/api/desktop-capturer.ts with lib/browser/desktop-capturer.ts

* remove desktop-capturer-get-sources event

* fix specs

* getSources needs to be async

Co-authored-by: Milan Burda <milan.burda@gmail.com>
  • Loading branch information
nornagon and miniak committed Oct 4, 2021
1 parent 6db8d79 commit 4fd7c2a
Show file tree
Hide file tree
Showing 17 changed files with 149 additions and 312 deletions.
10 changes: 0 additions & 10 deletions docs/api/app.md
Original file line number Diff line number Diff line change
Expand Up @@ -500,16 +500,6 @@ gets emitted.
**Note:** Extra command line arguments might be added by Chromium,
such as `--original-process-start-time`.

### Event: 'desktop-capturer-get-sources'

Returns:

* `event` Event
* `webContents` [WebContents](web-contents.md)

Emitted when `desktopCapturer.getSources()` is called in the renderer process of `webContents`.
Calling `event.preventDefault()` will make it return empty sources.

## Methods

The `app` object has the following methods:
Expand Down
6 changes: 0 additions & 6 deletions docs/api/command-line-switches.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,6 @@ throttling in one window, you can take the hack of

Forces the maximum disk space to be used by the disk cache, in bytes.

### --enable-api-filtering-logging

Enables caller stack logging for the following APIs (filtering events):

* `desktopCapturer.getSources()` / `desktop-capturer-get-sources`

### --enable-logging[=file]

Prints Chromium's logging to stderr (or a log file).
Expand Down
49 changes: 29 additions & 20 deletions docs/api/desktop-capturer.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,49 @@
> Access information about media sources that can be used to capture audio and
> video from the desktop using the [`navigator.mediaDevices.getUserMedia`] API.
Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process)
Process: [Main](../glossary.md#main-process)

The following example shows how to capture video from a desktop window whose
title is `Electron`:

```javascript
// In the renderer process.
// In the main process.
const { desktopCapturer } = require('electron')

desktopCapturer.getSources({ types: ['window', 'screen'] }).then(async sources => {
for (const source of sources) {
if (source.name === 'Electron') {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: source.id,
minWidth: 1280,
maxWidth: 1280,
minHeight: 720,
maxHeight: 720
}
}
})
handleStream(stream)
} catch (e) {
handleError(e)
}
mainWindow.webContents.send('SET_SOURCE', source.id)
return
}
}
})
```

```javascript
// In the preload script.
const { ipcRenderer } = require('electron')

ipcRenderer.on('SET_SOURCE', async (event, sourceId) => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: sourceId,
minWidth: 1280,
maxWidth: 1280,
minHeight: 720,
maxHeight: 720
}
}
})
handleStream(stream)
} catch (e) {
handleError(e)
}
})

function handleStream (stream) {
const video = document.querySelector('video')
Expand Down
9 changes: 0 additions & 9 deletions docs/api/web-contents.md
Original file line number Diff line number Diff line change
Expand Up @@ -856,15 +856,6 @@ Returns:

Emitted when the renderer process sends a synchronous message via `ipcRenderer.sendSync()`.

#### Event: 'desktop-capturer-get-sources'

Returns:

* `event` Event

Emitted when `desktopCapturer.getSources()` is called in the renderer process.
Calling `event.preventDefault()` will make it return empty sources.

#### Event: 'preferred-size-changed'

Returns:
Expand Down
4 changes: 0 additions & 4 deletions filenames.auto.gni
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ auto_filenames = {
"lib/common/web-view-methods.ts",
"lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/desktop-capturer.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/web-frame.ts",
"lib/renderer/inspector.ts",
Expand Down Expand Up @@ -224,7 +223,6 @@ auto_filenames = {
"lib/browser/api/web-contents.ts",
"lib/browser/api/web-frame-main.ts",
"lib/browser/default-menu.ts",
"lib/browser/desktop-capturer.ts",
"lib/browser/devtools.ts",
"lib/browser/guest-view-manager.ts",
"lib/browser/guest-window-manager.ts",
Expand Down Expand Up @@ -271,7 +269,6 @@ auto_filenames = {
"lib/common/webpack-provider.ts",
"lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/desktop-capturer.ts",
"lib/renderer/api/exports/electron.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.ts",
Expand Down Expand Up @@ -309,7 +306,6 @@ auto_filenames = {
"lib/common/webpack-provider.ts",
"lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/desktop-capturer.ts",
"lib/renderer/api/exports/electron.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.ts",
Expand Down
73 changes: 70 additions & 3 deletions lib/browser/api/desktop-capturer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,72 @@
import { getSourcesImpl } from '@electron/internal/browser/desktop-capturer';
const { createDesktopCapturer } = process._linkedBinding('electron_browser_desktop_capturer');

export async function getSources (options: Electron.SourcesOptions) {
return getSourcesImpl(null, options);
const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b);

let currentlyRunning: {
options: ElectronInternal.GetSourcesOptions;
getSources: Promise<ElectronInternal.GetSourcesResult[]>;
}[] = [];

// |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 async function getSources (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
// return that promise
return running.getSources;
}
}

const getSources = new Promise<ElectronInternal.GetSourcesResult[]>((resolve, reject) => {
let capturer: ElectronInternal.DesktopCapturer | null = createDesktopCapturer();

const stopRunning = () => {
if (capturer) {
delete capturer._onerror;
delete capturer._onfinished;
capturer = null;
}
// Remove from currentlyRunning once we resolve or reject
currentlyRunning = currentlyRunning.filter(running => running.options !== options);
};

capturer._onerror = (error: string) => {
stopRunning();
reject(error);
};

capturer._onfinished = (sources: Electron.DesktopCapturerSource[]) => {
stopRunning();
resolve(sources);
};

capturer.startHandling(captureWindow, captureScreen, thumbnailSize, fetchWindowIcons);
});

currentlyRunning.push({
options,
getSources
});

return getSources;
}
82 changes: 0 additions & 82 deletions lib/browser/desktop-capturer.ts

This file was deleted.

37 changes: 0 additions & 37 deletions lib/browser/rpc-server.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,9 @@
import { app } from 'electron/main';
import type { WebContents } from 'electron/main';
import { clipboard } from 'electron/common';
import * as fs from 'fs';
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';

import type * as desktopCapturerModule from '@electron/internal/browser/desktop-capturer';

const eventBinding = process._linkedBinding('electron_browser_event');

const emitCustomEvent = function (contents: WebContents, eventName: string, ...args: any[]) {
const event = eventBinding.createWithSender(contents);

app.emit(eventName, event, contents, ...args);
contents.emit(eventName, event, ...args);

return event;
};

const logStack = function (contents: WebContents, code: string, stack: string) {
if (stack) {
console.warn(`WebContents (${contents.id}): ${code}`, stack);
}
};

// Implements window.close()
ipcMainInternal.on(IPC_MESSAGES.BROWSER_WINDOW_CLOSE, function (event) {
const window = event.sender.getOwnerBrowserWindow();
Expand Down Expand Up @@ -58,22 +37,6 @@ ipcMainUtils.handleSync(IPC_MESSAGES.BROWSER_CLIPBOARD_SYNC, function (event, me
return (clipboard as any)[method](...args);
});

if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) {
const desktopCapturer = require('@electron/internal/browser/desktop-capturer') as typeof desktopCapturerModule;

ipcMainInternal.handle(IPC_MESSAGES.DESKTOP_CAPTURER_GET_SOURCES, async function (event, options: Electron.SourcesOptions, stack: string) {
logStack(event.sender, 'desktopCapturer.getSources()', stack);
const customEvent = emitCustomEvent(event.sender, 'desktop-capturer-get-sources');

if (customEvent.defaultPrevented) {
console.error('Blocked desktopCapturer.getSources()');
return [];
}

return await desktopCapturer.getSourcesImpl(event.sender, options);
});
}

const getPreloadScript = async function (preloadPath: string) {
let preloadSrc = null;
let preloadError = null;
Expand Down
2 changes: 0 additions & 2 deletions lib/common/ipc-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,4 @@ export const enum IPC_MESSAGES {
INSPECTOR_CONFIRM = 'INSPECTOR_CONFIRM',
INSPECTOR_CONTEXT_MENU = 'INSPECTOR_CONTEXT_MENU',
INSPECTOR_SELECT_FILE = 'INSPECTOR_SELECT_FILE',

DESKTOP_CAPTURER_GET_SOURCES = 'DESKTOP_CAPTURER_GET_SOURCES',
}
24 changes: 0 additions & 24 deletions lib/renderer/api/desktop-capturer.ts

This file was deleted.

1 comment on commit 4fd7c2a

@ldenoue
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nornagon would it be possible that getSources returns also the position and size of each window when window is chosen?
I currently rely on an external package node-window-manager to match window ids and find the position and size of each window, but it doesn't exist on Linux and it would be cleaner to not have to depend on another package.

Please sign in to comment.