diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index 2e0ef601ff..c68b078a29 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -101,6 +101,8 @@ changes: - pr-url: https://github.com/electron/electron/pull/16427 description: "This method now returns a Promise instead of using a callback function." breaking-changes-header: api-changed-callback-based-versions-of-promisified-apis + - pr-url: https://github.com/electron/electron/pull/54550 + description: "Added the `persistMode` and `restoreToken` options." ``` --> @@ -114,6 +116,18 @@ changes: * `fetchWindowIcons` boolean (optional) - Set to true to enable fetching window icons. The default value is false. When false the appIcon property of the sources return null. Same if a source has the type screen. + * `persistMode` string (optional) _Linux_ _Experimental_ - Can be `transient` or `persistent`. + How long the source the user picks in the system picker can be reopened without showing the + picker again. `transient`, the default, lasts at most until the app exits. `persistent` lasts + across launches until the user revokes it; store the returned source's `restoreToken` to use + it on a later launch. See [Restoring a source on Wayland](#restoring-a-source-on-wayland). + * `restoreToken` string (optional) _Linux_ _Experimental_ - A `restoreToken` from an earlier + call; `persistMode` must be passed with it. If the system still honors the token, the promise + resolves with that source and no picker is shown. A token that was revoked or names a source + that no longer exists is ignored and the picker is shown as if no token was passed; a value + that is not a token the system issued makes the promise reject. Pass `persistMode: + 'persistent'` with a persistent token every time: restoring it as `transient` ends the + persistent grant. Returns `Promise` - Resolves with an array of [`DesktopCapturerSource`](structures/desktop-capturer-source.md) objects, each `DesktopCapturerSource` represents a screen or an individual window that can be captured. @@ -122,6 +136,25 @@ Returns `Promise` - Resolves with an array of [`Desktop > * Capturing audio requires `NSAudioCaptureUsageDescription` Info.plist key on macOS 14.2 Sonoma and higher - [read more](#macos-versions-142-or-higher). > * Capturing the screen contents requires user consent on macOS 10.15 Catalina or higher, which can detected by [`systemPreferences.getMediaAccessStatus`][]. +### `desktopCapturer.getRestoreToken(sourceId)` _Linux_ _Experimental_ + + + +* `sourceId` string - The `id` of a [`DesktopCapturerSource`](structures/desktop-capturer-source.md). + +Returns `string` - The current restore token for the source, or an empty string if it has none. + +The system may replace the token each time the source is opened, which happens once in +`desktopCapturer.getSources` and again when a stream is started from the source, and then only +the newest token works on the next launch. Read it with this method once the stream is running, +or before the app quits, and store that value rather than the one `getSources` returned. See +[Restoring a source on Wayland](#restoring-a-source-on-wayland). + [`navigator.mediaDevices.getUserMedia`]: https://developer.mozilla.org/en/docs/Web/API/MediaDevices/getUserMedia [`systemPreferences.getMediaAccessStatus`]: system-preferences.md#systempreferencesgetmediaaccessstatusmediatype-windows-macos @@ -133,6 +166,84 @@ Returns `Promise` - Resolves with an array of [`Desktop PipeWire supports a single capture for both screens and windows. If you request the window and screen type, the selected source will be returned as a window capture. +#### Restoring a source on Wayland + +On Wayland the source is chosen in the system's own picker, which `desktopCapturer.getSources` +shows on every call. To let the user pick once and capture that choice again on later launches, +pass `persistMode: 'persistent'`, store the source's restore token, and pass it back as +`restoreToken` next time. Electron does not store tokens itself. A token names a screen or +window the user already agreed to share, so keep it somewhere private to the app, for example +encrypted with [`safeStorage`](safe-storage.md). If the user has revoked the grant or the screen or +window no longer exists, the token is ignored and the picker is shown again. If the stored value is +not a token the system issued, `getSources` rejects, so drop it and ask again without one. + +How a token is matched to a source is up to the desktop environment. GNOME and KDE remember a +window by its app and title, and restore the open window of that app whose title is closest, so a +token can resolve to a different window of the same app. If no title is close enough the picker is +shown. + +With a valid token nothing asks the user before capture starts. Show the user what is being +shared, and let them choose something else by calling `desktopCapturer.getSources` again without +`restoreToken`. + +This needs `xdg-desktop-portal` with version 4 or later of the ScreenCast interface. In an X11 +session and on other platforms the options are ignored and `restoreToken` is always empty. In a +Wayland session they also apply when the app runs under XWayland. + +```js +const { app, desktopCapturer, safeStorage, session } = require('electron') + +const fs = require('node:fs/promises') +const path = require('node:path') + +const tokenFile = path.join(app.getPath('userData'), 'screencast-token') + +async function readToken() { + try { + const { result } = await safeStorage.decryptStringAsync(await fs.readFile(tokenFile)) + return result + } catch { + return undefined + } +} + +async function writeToken(token) { + if (token) await fs.writeFile(tokenFile, await safeStorage.encryptStringAsync(token)) +} + +app.whenReady().then(() => { + let sourceId + + session.defaultSession.setDisplayMediaRequestHandler(async (request, callback) => { + // There is no source if the user closes the picker, and the call rejects if + // the stored value is not a token the system issued; forget it either way. + const [source] = await desktopCapturer + .getSources({ + types: ['screen', 'window'], + persistMode: 'persistent', + restoreToken: (sourceId && desktopCapturer.getRestoreToken(sourceId)) || (await readToken()) + }) + .catch(() => fs.rm(tokenFile, { force: true }).then(() => [])) + if (!source) { + callback(null) + return + } + sourceId = source.id + await writeToken(source.restoreToken) + callback({ video: source }) + }) + + app.on('will-quit', (event) => { + if (!sourceId) return + event.preventDefault() + writeToken(desktopCapturer.getRestoreToken(sourceId)).finally(() => { + sourceId = undefined + app.quit() + }) + }) +}) +``` + ### macOS versions 14.2 or higher `NSAudioCaptureUsageDescription` Info.plist key must be added in order for audio to be captured by diff --git a/docs/api/structures/desktop-capturer-source.md b/docs/api/structures/desktop-capturer-source.md index e898ebd523..c5c03a48c4 100644 --- a/docs/api/structures/desktop-capturer-source.md +++ b/docs/api/structures/desktop-capturer-source.md @@ -24,3 +24,7 @@ application that owns the window or null if the source has a type screen. The size of the icon is not known in advance and depends on what the application provides. +* `restoreToken` string _Linux_ _Experimental_ - A token that lets a later + `desktopCapturer.getSources` call reopen this source without showing the + system picker, or an empty string if the system did not issue one. See + [Restoring a source on Wayland](../desktop-capturer.md#restoring-a-source-on-wayland). diff --git a/lib/browser/api/desktop-capturer.ts b/lib/browser/api/desktop-capturer.ts index 53d2f93f6a..34e3b6b205 100644 --- a/lib/browser/api/desktop-capturer.ts +++ b/lib/browser/api/desktop-capturer.ts @@ -1,6 +1,6 @@ import { BaseWindow } from 'electron/main'; -const { createDesktopCapturer, isDisplayMediaSystemPickerAvailable } = process._linkedBinding( +const { createDesktopCapturer, isDisplayMediaSystemPickerAvailable, getRestoreToken } = process._linkedBinding( 'electron_browser_desktop_capturer' ); @@ -12,12 +12,16 @@ let currentlyRunning: { getSources: Promise; }[] = []; -// |options.types| can't be empty and must be an array +const persistModes = new Set(['transient', 'persistent']); + function isValid(options: Electron.SourcesOptions) { - return Array.isArray(options?.types); + if (!Array.isArray(options?.types)) return false; + if (options.persistMode != null && !persistModes.has(options.persistMode)) return false; + if (options.restoreToken != null && typeof options.restoreToken !== 'string') return false; + return !options.restoreToken || options.persistMode != null; } -export { isDisplayMediaSystemPickerAvailable }; +export { isDisplayMediaSystemPickerAvailable, getRestoreToken }; export async function getSources(args: Electron.SourcesOptions) { if (!isValid(args)) throw new Error('Invalid options'); @@ -37,12 +41,16 @@ export async function getSources(args: Electron.SourcesOptions) { const { thumbnailSize = { width: 150, height: 150 } } = args; const { fetchWindowIcons = false } = args; + const persistent = args.persistMode === 'persistent'; + const restoreToken = args.restoreToken ?? ''; const options = { captureWindow, captureScreen, thumbnailSize, - fetchWindowIcons + fetchWindowIcons, + persistent, + restoreToken }; for (const running of currentlyRunning) { @@ -96,7 +104,7 @@ export async function getSources(args: Electron.SourcesOptions) { resolveGetSources(sources); }; - capturer.startHandling(captureWindow, captureScreen, thumbnailSize, fetchWindowIcons); + capturer.startHandling(captureWindow, captureScreen, thumbnailSize, fetchWindowIcons, persistent, restoreToken); return getSources; } diff --git a/shell/browser/api/electron_api_desktop_capturer.cc b/shell/browser/api/electron_api_desktop_capturer.cc index 3b03db80cc..898d87667a 100644 --- a/shell/browser/api/electron_api_desktop_capturer.cc +++ b/shell/browser/api/electron_api_desktop_capturer.cc @@ -20,6 +20,7 @@ #include "chrome/browser/media/webrtc/thumbnail_capturer_mac.h" #include "chrome/browser/media/webrtc/window_icon_util.h" #include "content/public/browser/desktop_capture.h" +#include "content/public/browser/desktop_media_id.h" #include "gin/object_template_builder.h" #include "gin/persistent.h" #include "shell/browser/javascript_environment.h" @@ -54,6 +55,11 @@ #include "ui/base/cocoa/permissions_utils.h" #endif +#if defined(WEBRTC_USE_PIPEWIRE) +#include "third_party/webrtc/modules/desktop_capture/linux/wayland/restore_token_manager.h" +#include "third_party/webrtc/modules/portal/screencast_persist_mode.h" +#endif + namespace { #if BUILDFLAG(IS_LINUX) // Private function in ui/base/x/x11_display_util.cc @@ -151,7 +157,33 @@ base::flat_map MonitorAtomIdToDisplayId() { } #endif -std::unique_ptr MakeWindowCapturer() { +// A PipeWire capturer's portal session asks for `persistent` and tries +// `restore_token`; the issued token is kept under the capturer's source id. +std::unique_ptr WrapCapturer( + std::unique_ptr capturer, + bool persistent, + const std::string& restore_token) { + if (!capturer) + return nullptr; +#if defined(WEBRTC_USE_PIPEWIRE) + webrtc::DesktopCapturer::SourceList sources; + if (capturer->GetDelegatedSourceListController() && + capturer->GetSourceList(&sources) && sources.size() == 1) { + if (persistent || !restore_token.empty()) { + webrtc::RestoreTokenManager::GetInstance().AddToken( + sources[0].id, restore_token, + persistent ? webrtc::xdg_portal::ScreenCastPersistMode::kPersistent + : webrtc::xdg_portal::ScreenCastPersistMode::kTransient); + } + capturer->SelectSource(sources[0].id); + } +#endif + return std::make_unique(std::move(capturer)); +} + +std::unique_ptr MakeWindowCapturer( + bool persistent, + const std::string& restore_token) { #if BUILDFLAG(IS_MAC) if (ShouldUseThumbnailCapturerMac(DesktopMediaList::Type::kWindow)) { return CreateThumbnailCapturerMac(DesktopMediaList::Type::kWindow, @@ -159,15 +191,15 @@ std::unique_ptr MakeWindowCapturer() { } #endif // BUILDFLAG(IS_MAC) - std::unique_ptr window_capturer = + return WrapCapturer( content::desktop_capture::CreateWindowCapturer( - content::desktop_capture::CreateDesktopCaptureOptions()); - return window_capturer ? std::make_unique( - std::move(window_capturer)) - : nullptr; + content::desktop_capture::CreateDesktopCaptureOptions()), + persistent, restore_token); } -std::unique_ptr MakeScreenCapturer() { +std::unique_ptr MakeScreenCapturer( + bool persistent, + const std::string& restore_token) { #if BUILDFLAG(IS_MAC) if (ShouldUseThumbnailCapturerMac(DesktopMediaList::Type::kScreen)) { return CreateThumbnailCapturerMac(DesktopMediaList::Type::kScreen, @@ -175,13 +207,11 @@ std::unique_ptr MakeScreenCapturer() { } #endif // BUILDFLAG(IS_MAC) - std::unique_ptr screen_capturer = + return WrapCapturer( content::desktop_capture::CreateScreenCapturer( content::desktop_capture::CreateDesktopCaptureOptions(), - /*for_snapshot=*/false); - return screen_capturer ? std::make_unique( - std::move(screen_capturer)) - : nullptr; + /*for_snapshot=*/false), + persistent, restore_token); } #if BUILDFLAG(IS_WIN) @@ -217,6 +247,7 @@ struct Converter { electron::api::NativeImage::Create( isolate, gfx::Image(source.media_list_source.thumbnail))); dict.Set("display_id", source.display_id); + dict.Set("restoreToken", source.restore_token); if (source.fetch_icon) { dict.Set( "appIcon", @@ -339,7 +370,9 @@ void DesktopCapturer::FinalizeList(std::unique_ptr& observer, void DesktopCapturer::StartHandling(bool capture_window, bool capture_screen, const gfx::Size& thumbnail_size, - bool fetch_window_icons) { + bool fetch_window_icons, + bool persistent, + const std::string& restore_token) { fetch_window_icons_ = fetch_window_icons; #if BUILDFLAG(IS_WIN) if (content::desktop_capture::CreateDesktopCaptureOptions() @@ -373,21 +406,19 @@ void DesktopCapturer::StartHandling(bool capture_window, std::unique_ptr capturer; bool use_generic = false; if (capture_screen) { - auto desktop_capturer = webrtc::DesktopCapturer::CreateGenericCapturer( - content::desktop_capture::CreateDesktopCaptureOptions()); - auto wrapper = desktop_capturer - ? std::make_unique( - std::move(desktop_capturer)) - : nullptr; - if (wrapper && wrapper->GetDelegatedSourceListController()) { - capturer = std::move(wrapper); + auto generic = WrapCapturer( + webrtc::DesktopCapturer::CreateGenericCapturer( + content::desktop_capture::CreateDesktopCaptureOptions()), + persistent, restore_token); + if (generic && generic->GetDelegatedSourceListController()) { + capturer = std::move(generic); use_generic = true; // Generic capturer handles both types as window capture. need_screen = false; } } if (!capturer) - capturer = MakeWindowCapturer(); + capturer = MakeWindowCapturer(persistent, restore_token); if (capturer) { window_capturer_ = std::make_unique( @@ -403,7 +434,7 @@ void DesktopCapturer::StartHandling(bool capture_window, } if (need_screen) { - auto capturer = MakeScreenCapturer(); + auto capturer = MakeScreenCapturer(persistent, restore_token); if (capturer) { screen_capturer_ = std::make_unique( DesktopMediaList::Type::kScreen, std::move(capturer)); @@ -467,25 +498,17 @@ void DesktopCapturer::OnReadyTimeout() { } void DesktopCapturer::CollectSourcesFrom(DesktopMediaList* list) { - const auto type = list->GetMediaListType(); + const bool is_screen_list = + list->GetMediaListType() == DesktopMediaList::Type::kScreen; - if (type == DesktopMediaList::Type::kWindow) { - std::vector window_sources; - window_sources.reserve(list->GetSourceCount()); - for (int i = 0; i < list->GetSourceCount(); i++) { - window_sources.emplace_back(list->GetSource(i), std::string(), - fetch_window_icons_); - } - std::move(window_sources.begin(), window_sources.end(), - std::back_inserter(captured_sources_)); + std::vector sources; + sources.reserve(list->GetSourceCount()); + for (int i = 0; i < list->GetSourceCount(); i++) { + sources.emplace_back(list->GetSource(i), std::string(), + !is_screen_list && fetch_window_icons_); } - if (type == DesktopMediaList::Type::kScreen) { - std::vector screen_sources; - screen_sources.reserve(list->GetSourceCount()); - for (int i = 0; i < list->GetSourceCount(); i++) { - screen_sources.emplace_back(list->GetSource(i), std::string()); - } + if (is_screen_list) { #if BUILDFLAG(IS_WIN) // Gather the same unique screen IDs used by the electron.screen API in // order to provide an association between it and @@ -494,13 +517,13 @@ void DesktopCapturer::CollectSourcesFrom(DesktopMediaList* list) { if (using_directx_capturer_) { std::vector device_names; // Crucially, this list of device names will be in the same order as - // |screen_sources|. + // |sources|. if (!webrtc::DxgiDuplicatorController::Instance()->GetDeviceNames( &device_names)) { HandleFailure(); return; } - DCHECK_EQ(device_names.size(), screen_sources.size()); + DCHECK_EQ(device_names.size(), sources.size()); std::vector monitors; EnumDisplayMonitors(nullptr, nullptr, EnumDisplayMonitorsCallback, @@ -520,7 +543,7 @@ void DesktopCapturer::CollectSourcesFrom(DesktopMediaList* list) { } int device_name_index = 0; - for (auto& source : screen_sources) { + for (auto& source : sources) { const auto& device_name = device_names[device_name_index++]; if (auto id_iter = device_name_to_id.find(device_name); id_iter != device_name_to_id.end()) { @@ -530,7 +553,7 @@ void DesktopCapturer::CollectSourcesFrom(DesktopMediaList* list) { } #elif BUILDFLAG(IS_MAC) // On Mac, the IDs across the APIs match. - for (auto& source : screen_sources) { + for (auto& source : sources) { source.display_id = base::NumberToString(source.media_list_source.id.id); } #elif BUILDFLAG(SUPPORTS_OZONE_X11) @@ -539,16 +562,27 @@ void DesktopCapturer::CollectSourcesFrom(DesktopMediaList* list) { // loop index when that display was found (see // BuildDisplaysFromXRandRInfo in ui/base/x/x11_display_util.cc) const auto monitor_atom_to_display_id = MonitorAtomIdToDisplayId(); - for (auto& source : screen_sources) { + for (auto& source : sources) { auto display_id_iter = monitor_atom_to_display_id.find(source.media_list_source.id.id); if (display_id_iter != monitor_atom_to_display_id.end()) source.display_id = base::NumberToString(display_id_iter->second); } #endif - std::move(screen_sources.begin(), screen_sources.end(), - std::back_inserter(captured_sources_)); } + +#if defined(WEBRTC_USE_PIPEWIRE) + if (list->IsSourceListDelegated()) { + for (auto& source : sources) { + source.restore_token = webrtc::RestoreTokenManager::GetInstance() + .GetEntry(source.media_list_source.id.id) + .token; + } + } +#endif + + std::move(sources.begin(), sources.end(), + std::back_inserter(captured_sources_)); } void DesktopCapturer::HandleSuccess() { @@ -601,6 +635,19 @@ bool DesktopCapturer::IsDisplayMediaSystemPickerAvailable() { } #endif +// static +std::string DesktopCapturer::GetRestoreToken(const std::string& source_id) { +#if defined(WEBRTC_USE_PIPEWIRE) + const content::DesktopMediaID id = content::DesktopMediaID::Parse(source_id); + if (id.id_type == content::DesktopMediaID::IdType::kNativePickerSession && + (id.type == content::DesktopMediaID::TYPE_SCREEN || + id.type == content::DesktopMediaID::TYPE_WINDOW)) { + return webrtc::RestoreTokenManager::GetInstance().GetEntry(id.id).token; + } +#endif + return {}; +} + gin::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder( v8::Isolate* isolate) { return gin::Wrappable::GetObjectTemplateBuilder(isolate) @@ -635,6 +682,8 @@ void Initialize(v8::Local exports, dict.SetMethod< &electron::api::DesktopCapturer::IsDisplayMediaSystemPickerAvailable>( "isDisplayMediaSystemPickerAvailable"); + dict.SetMethod<&electron::api::DesktopCapturer::GetRestoreToken>( + "getRestoreToken"); } } // namespace diff --git a/shell/browser/api/electron_api_desktop_capturer.h b/shell/browser/api/electron_api_desktop_capturer.h index d1101e4449..4ec673ade1 100644 --- a/shell/browser/api/electron_api_desktop_capturer.h +++ b/shell/browser/api/electron_api_desktop_capturer.h @@ -26,16 +26,24 @@ class DesktopCapturer final : public gin::Wrappable { // Whether or not this source should provide an icon. bool fetch_icon = false; + + // ScreenCast portal restore token; empty when the source has none. + std::string restore_token; }; static DesktopCapturer* Create(v8::Isolate* isolate); static bool IsDisplayMediaSystemPickerAvailable(); + [[nodiscard]] static std::string GetRestoreToken( + const std::string& source_id); + void StartHandling(bool capture_window, bool capture_screen, const gfx::Size& thumbnail_size, - bool fetch_window_icons); + bool fetch_window_icons, + bool persistent, + const std::string& restore_token); // gin::Wrappable static const gin::WrapperInfo kWrapperInfo; diff --git a/spec/api-desktop-capturer.spec.ts b/spec/api-desktop-capturer.spec.ts index a386697ee3..b1a56e6a11 100644 --- a/spec/api-desktop-capturer.spec.ts +++ b/spec/api-desktop-capturer.spec.ts @@ -5,7 +5,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { once } from 'node:events'; import { setTimeout } from 'node:timers/promises'; -import { ifdescribe, ifit } from './lib/spec-helpers.ts'; +import { ifdescribe, ifit, isWayland } from './lib/spec-helpers.ts'; import { closeAllWindows } from './lib/window-helpers.ts'; function getSourceTypes(): ('window' | 'screen')[] { @@ -27,6 +27,51 @@ describe('desktopCapturer', { tags: ['serial'] }, () => { await expect(promise).rejects.toThrow('Invalid options'); }); + it('throws an error for an unknown persistMode', async () => { + const promise = desktopCapturer.getSources({ types: getSourceTypes(), persistMode: 'forever' as any }); + await expect(promise).to.be.eventually.rejectedWith(Error, 'Invalid options'); + }); + + it('throws an error for a non-string restoreToken', async () => { + const promise = desktopCapturer.getSources({ types: getSourceTypes(), restoreToken: 42 as any }); + await expect(promise).to.be.eventually.rejectedWith(Error, 'Invalid options'); + }); + + it('accepts persistMode and restoreToken and returns a restoreToken for each source', async () => { + const sources = await desktopCapturer.getSources({ + types: getSourceTypes(), + thumbnailSize: { width: 0, height: 0 }, + persistMode: 'transient', + restoreToken: '' + }); + expect(sources).to.be.an('array').that.is.not.empty(); + for (const source of sources) { + expect(source.restoreToken).to.be.a('string'); + if (!isWayland) expect(source.restoreToken).to.be.empty(); + } + }); + + it('throws when restoreToken is passed without persistMode', async () => { + const promise = desktopCapturer.getSources({ types: getSourceTypes(), restoreToken: 'token' }); + await expect(promise).to.be.eventually.rejectedWith(Error, 'Invalid options'); + }); + + it('getRestoreToken returns an empty string for a source that has no token', () => { + expect(desktopCapturer.getRestoreToken('window:0:0')).to.equal(''); + expect(desktopCapturer.getRestoreToken('not-a-source-id')).to.equal(''); + }); + + it('getRestoreToken returns the restoreToken of a returned source', async () => { + const sources = await desktopCapturer.getSources({ + types: getSourceTypes(), + thumbnailSize: { width: 0, height: 0 } + }); + expect(sources).to.be.an('array').that.is.not.empty(); + for (const source of sources) { + expect(desktopCapturer.getRestoreToken(source.id)).to.equal(source.restoreToken); + } + }); + it('does not throw an error when called more than once (regression)', async () => { const sources1 = await desktopCapturer.getSources({ types: getSourceTypes() }); expect(sources1).toBeInstanceOf(Array); diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts index 7a1069dfa0..67bcdf6bfa 100644 --- a/typings/internal-ambient.d.ts +++ b/typings/internal-ambient.d.ts @@ -258,6 +258,7 @@ declare namespace NodeJS { _linkedBinding(name: 'electron_browser_desktop_capturer'): { createDesktopCapturer(): ElectronInternal.DesktopCapturer; isDisplayMediaSystemPickerAvailable(): boolean; + getRestoreToken(sourceId: string): string; }; _linkedBinding(name: 'electron_browser_event_emitter'): { setEventEmitterPrototype(prototype: Object): void }; _linkedBinding(name: 'electron_browser_ipc_dispatch'): { diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index 96a8adfe85..367a65b5d4 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -256,7 +256,9 @@ declare namespace ElectronInternal { captureWindow: boolean, captureScreen: boolean, thumbnailSize: Electron.Size, - fetchWindowIcons: boolean + fetchWindowIcons: boolean, + persistent: boolean, + restoreToken: string ): void; _onerror?: (error: string) => void; _onfinished?: (sources: Electron.DesktopCapturerSource[], fetchWindowIcons: boolean) => void; @@ -267,6 +269,8 @@ declare namespace ElectronInternal { captureScreen: boolean; thumbnailSize: Electron.Size; fetchWindowIcons: boolean; + persistent: boolean; + restoreToken: string; } interface GetSourcesResult { @@ -274,6 +278,7 @@ declare namespace ElectronInternal { name: string; thumbnail: Electron.NativeImage; display_id: string; + restoreToken: string; appIcon: Electron.NativeImage | null; }