From 097c6b796d1b7544e501e910c73ec0365e6b15dc Mon Sep 17 00:00:00 2001 From: "trop[bot]" <37223003+trop[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 09:56:47 -0700 Subject: [PATCH] feat: add support for system picker in setDisplayMediaRequestHandler (#43679) * tmp Co-authored-by: Samuel Attard * feat: add support for system picker in setDisplayMediaRequestHandler Co-authored-by: Samuel Attard * oops Co-authored-by: Samuel Attard * Apply suggestions from code review Co-authored-by: Erick Zhao Co-authored-by: Samuel Attard * stuff Co-authored-by: Samuel Attard * well... Co-authored-by: Samuel Attard * seems legit Co-authored-by: Samuel Attard * chore: update patch to handle screenCapturer Co-authored-by: Keeley Hammond * feat: modify API to use useSystemPicker Co-authored-by: Keeley Hammond * fix: gate ScreenCaptureKitPicker to macos 15 or higher Co-authored-by: Keeley Hammond * fix: don't use native picker with legacy media selection Co-authored-by: Keeley Hammond * chore: code review, boolean set & docs update Co-authored-by: Keeley Hammond * fix: add cancelCallback Co-authored-by: Keeley Hammond * docs: clarify session & desktopCapturer docs Co-authored-by: Keeley Hammond * chore: remove incorrect backport patches * chore: update SCKP patch --------- Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Samuel Attard Co-authored-by: Samuel Attard Co-authored-by: Keeley Hammond --- docs/api/desktop-capturer.md | 6 +- docs/api/session.md | 14 +- filenames.gni | 1 + lib/browser/api/desktop-capturer.ts | 4 +- lib/browser/api/session.ts | 26 ++ patches/chromium/.patches | 1 + ...sharingpicker_on_supported_platforms.patch | 308 ++++++++++++++++++ .../api/electron_api_desktop_capturer.cc | 10 + .../api/electron_api_desktop_capturer.h | 2 + .../api/electron_api_desktop_capturer_mac.mm | 17 + shell/browser/api/electron_api_session.cc | 2 +- shell/browser/feature_list.cc | 8 + shell/browser/feature_list.h | 1 + shell/browser/feature_list_mac.mm | 9 + typings/internal-ambient.d.ts | 2 +- typings/internal-electron.d.ts | 4 + 16 files changed, 409 insertions(+), 6 deletions(-) create mode 100644 patches/chromium/feat_allow_usage_of_sccontentsharingpicker_on_supported_platforms.patch create mode 100644 shell/browser/api/electron_api_desktop_capturer_mac.mm diff --git a/docs/api/desktop-capturer.md b/docs/api/desktop-capturer.md index f415e1481929..89f6f120062f 100644 --- a/docs/api/desktop-capturer.md +++ b/docs/api/desktop-capturer.md @@ -20,7 +20,11 @@ app.whenReady().then(() => { // Grant access to the first screen found. callback({ video: sources[0], audio: 'loopback' }) }) - }) + // If true, use the system picker if available. + // Note: this is currently experimental. If the system picker + // is available, it will be used and the media request handler + // will not be invoked. + }, { useSystemPicker: true }) mainWindow.loadFile('index.html') }) diff --git a/docs/api/session.md b/docs/api/session.md index ad3c10515f41..c0312b47dfd1 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -953,7 +953,7 @@ session.fromPartition('some-partition').setPermissionCheckHandler((webContents, }) ``` -#### `ses.setDisplayMediaRequestHandler(handler)` +#### `ses.setDisplayMediaRequestHandler(handler[, opts])` * `handler` Function | null * `request` Object @@ -980,12 +980,18 @@ session.fromPartition('some-partition').setPermissionCheckHandler((webContents, and this is set to `true`, then local playback of audio will not be muted (e.g. using `MediaRecorder` to record `WebFrameMain` with this flag set to `true` will allow audio to pass through to the speakers while recording). Default is `false`. +* `opts` Object (optional) _macOS_ _Experimental_ + * `useSystemPicker` Boolean - true if the available native system picker should be used. Default is `false`. _macOS_ _Experimental_ This handler will be called when web content requests access to display media via the `navigator.mediaDevices.getDisplayMedia` API. Use the [desktopCapturer](desktop-capturer.md) API to choose which stream(s) to grant access to. +`useSystemPicker` allows an application to use the system picker instead of providing a specific video source from `getSources`. +This option is experimental, and currently available for MacOS 15+ only. If the system picker is available and `useSystemPicker` +is set to `true`, the handler will not be invoked. + ```js const { session, desktopCapturer } = require('electron') @@ -994,7 +1000,11 @@ session.defaultSession.setDisplayMediaRequestHandler((request, callback) => { // Grant access to the first screen found. callback({ video: sources[0] }) }) -}) + // Use the system picker if available. + // Note: this is currently experimental. If the system picker + // is available, it will be used and the media request handler + // will not be invoked. +}, { useSystemPicker: true }) ``` Passing a [WebFrameMain](web-frame-main.md) object as a video or audio stream diff --git a/filenames.gni b/filenames.gni index d51b3c8ce8e0..ca034c58d33e 100644 --- a/filenames.gni +++ b/filenames.gni @@ -270,6 +270,7 @@ filenames = { "shell/browser/api/electron_api_debugger.h", "shell/browser/api/electron_api_desktop_capturer.cc", "shell/browser/api/electron_api_desktop_capturer.h", + "shell/browser/api/electron_api_desktop_capturer_mac.mm", "shell/browser/api/electron_api_dialog.cc", "shell/browser/api/electron_api_download_item.cc", "shell/browser/api/electron_api_download_item.h", diff --git a/lib/browser/api/desktop-capturer.ts b/lib/browser/api/desktop-capturer.ts index 6b9720510f52..3ea4d6198c4a 100644 --- a/lib/browser/api/desktop-capturer.ts +++ b/lib/browser/api/desktop-capturer.ts @@ -1,5 +1,5 @@ import { BrowserWindow } from 'electron/main'; -const { createDesktopCapturer } = process._linkedBinding('electron_browser_desktop_capturer'); +const { createDesktopCapturer, isDisplayMediaSystemPickerAvailable } = process._linkedBinding('electron_browser_desktop_capturer'); const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b); @@ -13,6 +13,8 @@ function isValid (options: Electron.SourcesOptions) { return Array.isArray(options?.types); } +export { isDisplayMediaSystemPickerAvailable }; + export async function getSources (args: Electron.SourcesOptions) { if (!isValid(args)) throw new Error('Invalid options'); diff --git a/lib/browser/api/session.ts b/lib/browser/api/session.ts index 5f9343c1bd30..c47c33170501 100644 --- a/lib/browser/api/session.ts +++ b/lib/browser/api/session.ts @@ -1,11 +1,37 @@ import { fetchWithSession } from '@electron/internal/browser/api/net-fetch'; import { net } from 'electron/main'; const { fromPartition, fromPath, Session } = process._linkedBinding('electron_browser_session'); +const { isDisplayMediaSystemPickerAvailable } = process._linkedBinding('electron_browser_desktop_capturer'); + +// Fake video source that activates the native system picker +// This is used to get around the need for a screen/window +// id in Chrome's desktopCapturer. +let fakeVideoSourceId = -1; +const systemPickerVideoSource = Object.create(null); +Object.defineProperty(systemPickerVideoSource, 'id', { + get () { + return `window:${fakeVideoSourceId--}:0`; + } +}); +systemPickerVideoSource.name = ''; +Object.freeze(systemPickerVideoSource); Session.prototype.fetch = function (input: RequestInfo, init?: RequestInit) { return fetchWithSession(input, init, this, net.request); }; +Session.prototype.setDisplayMediaRequestHandler = function (handler, opts) { + if (!handler) return this._setDisplayMediaRequestHandler(handler, opts); + + this._setDisplayMediaRequestHandler(async (req, callback) => { + if (opts && opts.useSystemPicker && isDisplayMediaSystemPickerAvailable()) { + return callback({ video: systemPickerVideoSource }); + } + + return handler(req, callback); + }, opts); +}; + export default { fromPartition, fromPath, diff --git a/patches/chromium/.patches b/patches/chromium/.patches index 3924ba4a93b5..4b7bc4a0a9c1 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -131,3 +131,4 @@ feat_enable_passing_exit_code_on_service_process_crash.patch chore_remove_reference_to_chrome_browser_themes.patch feat_enable_customizing_symbol_color_in_framecaptionbutton.patch fix_potential_draggable_region_crash_when_no_mainframeimpl.patch +feat_allow_usage_of_sccontentsharingpicker_on_supported_platforms.patch diff --git a/patches/chromium/feat_allow_usage_of_sccontentsharingpicker_on_supported_platforms.patch b/patches/chromium/feat_allow_usage_of_sccontentsharingpicker_on_supported_platforms.patch new file mode 100644 index 000000000000..6683d712fdd9 --- /dev/null +++ b/patches/chromium/feat_allow_usage_of_sccontentsharingpicker_on_supported_platforms.patch @@ -0,0 +1,308 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samuel Attard +Date: Thu, 8 Aug 2024 08:39:10 -0700 +Subject: feat: allow usage of SCContentSharingPicker on supported platforms + +This is implemented as a magic "window id" that instead of pulling an SCStream manually +instead farms out to the screen picker. + +diff --git a/content/browser/media/capture/desktop_capture_device_mac.cc b/content/browser/media/capture/desktop_capture_device_mac.cc +index 88c56f4dfcc1f8517ef1e8b6f1d37f5ba4d0b2c7..a75493a6d4d8ce8340a2d820eff5eed4e6a95109 100644 +--- a/content/browser/media/capture/desktop_capture_device_mac.cc ++++ b/content/browser/media/capture/desktop_capture_device_mac.cc +@@ -28,7 +28,7 @@ class DesktopCaptureDeviceMac : public IOSurfaceCaptureDeviceBase { + ~DesktopCaptureDeviceMac() override = default; + + // IOSurfaceCaptureDeviceBase: +- void OnStart() override { ++ void OnStart(std::optional use_native_picker) override { + requested_format_ = capture_params().requested_format; + requested_format_.pixel_format = media::PIXEL_FORMAT_NV12; + DCHECK_GT(requested_format_.frame_size.GetArea(), 0); +diff --git a/content/browser/media/capture/io_surface_capture_device_base_mac.cc b/content/browser/media/capture/io_surface_capture_device_base_mac.cc +index 8a774911ce0f610b2c993976d108f840696c1d02..5ead7287e2d765d043f8b9c0229a2ee825d9f544 100644 +--- a/content/browser/media/capture/io_surface_capture_device_base_mac.cc ++++ b/content/browser/media/capture/io_surface_capture_device_base_mac.cc +@@ -20,7 +20,7 @@ void IOSurfaceCaptureDeviceBase::AllocateAndStart( + client_ = std::move(client); + capture_params_ = params; + +- OnStart(); ++ OnStart(params.use_native_picker); + } + + void IOSurfaceCaptureDeviceBase::StopAndDeAllocate() { +diff --git a/content/browser/media/capture/io_surface_capture_device_base_mac.h b/content/browser/media/capture/io_surface_capture_device_base_mac.h +index 8ac12480f663a74dfbdcf7128a582a81b4474d25..db6802a2603e1d3c3039e49737438124bf2ee1f1 100644 +--- a/content/browser/media/capture/io_surface_capture_device_base_mac.h ++++ b/content/browser/media/capture/io_surface_capture_device_base_mac.h +@@ -25,7 +25,7 @@ class CONTENT_EXPORT IOSurfaceCaptureDeviceBase + ~IOSurfaceCaptureDeviceBase() override; + + // OnStart is called by AllocateAndStart. +- virtual void OnStart() = 0; ++ virtual void OnStart(std::optional use_native_picker) = 0; + + // OnStop is called by StopAndDeAllocate. + virtual void OnStop() = 0; +diff --git a/content/browser/media/capture/screen_capture_kit_device_mac.mm b/content/browser/media/capture/screen_capture_kit_device_mac.mm +index 5c09b98b0c0ade9197a73186809ae4da28a12506..27b7edd2e99f36ebf3381781f2d2b3e7aff3eca1 100644 +--- a/content/browser/media/capture/screen_capture_kit_device_mac.mm ++++ b/content/browser/media/capture/screen_capture_kit_device_mac.mm +@@ -24,24 +24,83 @@ + std::optional, + std::optional)>; + using ErrorCallback = base::RepeatingClosure; ++using CancelCallback = base::RepeatingClosure; ++ ++API_AVAILABLE(macos(15.0)) ++@interface ScreenCaptureKitPickerHelper ++ : NSObject ++ ++- (void)contentSharingPicker:(SCContentSharingPicker *)picker ++ didCancelForStream:(SCStream *)stream; ++ ++- (void)contentSharingPicker:(SCContentSharingPicker *)picker ++ didUpdateWithFilter:(SCContentFilter *)filter ++ forStream:(SCStream *)stream; ++ ++- (void)contentSharingPickerStartDidFailWithError:(NSError *)error; ++ ++@end ++ ++@implementation ScreenCaptureKitPickerHelper { ++ base::RepeatingCallback _pickerCallback; ++ ErrorCallback _errorCallback; ++ CancelCallback _cancelCallback; ++} ++ ++- (void)contentSharingPicker:(SCContentSharingPicker *)picker ++ didCancelForStream:(SCStream *)stream { ++ // TODO: This doesn't appear to be called on Apple's side; ++ // implement this logic ++ _cancelCallback.Run(); ++} ++ ++- (void)contentSharingPicker:(SCContentSharingPicker *)picker ++ didUpdateWithFilter:(SCContentFilter *)filter ++ forStream:(SCStream *)stream { ++ if (stream == nil) { ++ _pickerCallback.Run(filter); ++ [picker removeObserver:self]; ++ } ++} ++ ++- (void)contentSharingPickerStartDidFailWithError:(NSError *)error { ++ _errorCallback.Run(); ++} ++ ++- (instancetype)initWithStreamPickCallback:(base::RepeatingCallback)pickerCallback ++ cancelCallback:(CancelCallback)cancelCallback ++ errorCallback:(ErrorCallback)errorCallback { ++ if (self = [super init]) { ++ _pickerCallback = pickerCallback; ++ _cancelCallback = cancelCallback; ++ _errorCallback = errorCallback; ++ } ++ return self; ++} ++ ++@end + + API_AVAILABLE(macos(12.3)) + @interface ScreenCaptureKitDeviceHelper + : NSObject + + - (instancetype)initWithSampleCallback:(SampleCallback)sampleCallback ++ cancelCallback:(CancelCallback)cancelCallback + errorCallback:(ErrorCallback)errorCallback; + @end + + @implementation ScreenCaptureKitDeviceHelper { + SampleCallback _sampleCallback; ++ CancelCallback _cancelCallback; + ErrorCallback _errorCallback; + } + + - (instancetype)initWithSampleCallback:(SampleCallback)sampleCallback ++ cancelCallback:(CancelCallback)cancelCallback + errorCallback:(ErrorCallback)errorCallback { + if (self = [super init]) { + _sampleCallback = sampleCallback; ++ _cancelCallback = cancelCallback; + _errorCallback = errorCallback; + } + return self; +@@ -141,7 +200,8 @@ + (SCStreamConfiguration*)streamConfigurationWithFrameSize:(gfx::Size)frameSize + + class API_AVAILABLE(macos(12.3)) ScreenCaptureKitDeviceMac + : public IOSurfaceCaptureDeviceBase, +- public ScreenCaptureKitResetStreamInterface { ++ public ScreenCaptureKitResetStreamInterface ++ { + public: + explicit ScreenCaptureKitDeviceMac(const DesktopMediaID& source) + : source_(source), +@@ -150,18 +210,41 @@ explicit ScreenCaptureKitDeviceMac(const DesktopMediaID& source) + device_task_runner_, + base::BindRepeating(&ScreenCaptureKitDeviceMac::OnStreamSample, + weak_factory_.GetWeakPtr())); ++ CancelCallback cancel_callback = base::BindPostTask( ++ device_task_runner_, ++ base::BindRepeating(&ScreenCaptureKitDeviceMac::OnStreamError, ++ weak_factory_.GetWeakPtr())); + ErrorCallback error_callback = base::BindPostTask( + device_task_runner_, + base::BindRepeating(&ScreenCaptureKitDeviceMac::OnStreamError, + weak_factory_.GetWeakPtr())); + helper_ = [[ScreenCaptureKitDeviceHelper alloc] + initWithSampleCallback:sample_callback ++ cancelCallback:cancel_callback + errorCallback:error_callback]; ++ ++ if (@available(macOS 15.0, *)) { ++ auto picker_callback = base::BindPostTask( ++ device_task_runner_, ++ base::BindRepeating(&ScreenCaptureKitDeviceMac::OnContentFilterReady, weak_factory_.GetWeakPtr()) ++ ); ++ auto* picker_observer = [[ScreenCaptureKitPickerHelper alloc] initWithStreamPickCallback:picker_callback cancelCallback:cancel_callback errorCallback:error_callback]; ++ [[SCContentSharingPicker sharedPicker] addObserver:picker_observer]; ++ } + } + ScreenCaptureKitDeviceMac(const ScreenCaptureKitDeviceMac&) = delete; + ScreenCaptureKitDeviceMac& operator=(const ScreenCaptureKitDeviceMac&) = + delete; +- ~ScreenCaptureKitDeviceMac() override = default; ++ ~ScreenCaptureKitDeviceMac() override { ++ if (@available(macOS 15.0, *)) { ++ auto* picker = [SCContentSharingPicker sharedPicker]; ++ ScreenCaptureKitDeviceMac::active_streams_--; ++ picker.maximumStreamCount = @(ScreenCaptureKitDeviceMac::active_streams_); ++ if (ScreenCaptureKitDeviceMac::active_streams_ == 0 && picker.active) { ++ picker.active = false; ++ } ++ } ++ } + + void OnShareableContentCreated(SCShareableContent* content) { + DCHECK(device_task_runner_->RunsTasksInCurrentSequence()); +@@ -225,6 +308,18 @@ void OnShareableContentCreated(SCShareableContent* content) { + return; + } + ++ if (@available(macOS 15.0, *)) { ++ // Update the content size. This step is neccessary when used together ++ // with SCContentSharingPicker. If the Chrome picker is used, it will ++ // change to retina resolution if applicable. ++ stream_config_content_size_ = ++ gfx::Size(filter.contentRect.size.width * filter.pointPixelScale, ++ filter.contentRect.size.height * filter.pointPixelScale); ++ } ++ ++ OnContentFilterReady(filter); ++ } ++ void OnContentFilterReady(SCContentFilter* filter) { + gfx::RectF dest_rect_in_frame; + actual_capture_format_ = capture_params().requested_format; + actual_capture_format_.pixel_format = media::PIXEL_FORMAT_NV12; +@@ -238,6 +333,7 @@ void OnShareableContentCreated(SCShareableContent* content) { + stream_ = [[SCStream alloc] initWithFilter:filter + configuration:config + delegate:helper_]; ++ + { + NSError* error = nil; + bool add_stream_output_result = +@@ -379,7 +475,7 @@ void OnStreamError() { + if (fullscreen_module_) { + fullscreen_module_->Reset(); + } +- OnStart(); ++ OnStart(std::nullopt); + } else { + client()->OnError(media::VideoCaptureError::kScreenCaptureKitStreamError, + FROM_HERE, "Stream delegate called didStopWithError"); +@@ -402,9 +498,30 @@ void OnUpdateConfigurationError() { + } + + // IOSurfaceCaptureDeviceBase: +- void OnStart() override { ++ void OnStart(std::optional use_native_picker) override { + DCHECK(device_task_runner_->RunsTasksInCurrentSequence()); + ++ if (@available(macOS 15.0, *)) { ++ constexpr bool DefaultUseNativePicker = true; ++ if (use_native_picker.value_or(DefaultUseNativePicker) && source_.id < 0 && source_.window_id == 0) { ++ auto* picker = [SCContentSharingPicker sharedPicker]; ++ ScreenCaptureKitDeviceMac::active_streams_++; ++ picker.maximumStreamCount = @(ScreenCaptureKitDeviceMac::active_streams_); ++ if (!picker.active) { ++ picker.active = true; ++ } ++ NSMutableArray* exclude_ns_windows = [NSMutableArray array]; ++ [[[[NSApplication sharedApplication] windows] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSWindow* win, NSDictionary *bindings) { ++ return [win sharingType] == NSWindowSharingNone; ++ }]] enumerateObjectsUsingBlock:^(NSWindow* win, NSUInteger idx, BOOL *stop) { ++ [exclude_ns_windows addObject:@([win windowNumber])]; ++ }]; ++ picker.defaultConfiguration.excludedWindowIDs = exclude_ns_windows; ++ [picker present]; ++ return; ++ } ++ } ++ + auto content_callback = base::BindPostTask( + device_task_runner_, + base::BindRepeating( +@@ -470,6 +587,8 @@ void ResetStreamTo(SCWindow* window) override { + } + + private: ++ static int active_streams_; ++ + const DesktopMediaID source_; + const scoped_refptr device_task_runner_; + +@@ -498,6 +617,8 @@ void ResetStreamTo(SCWindow* window) override { + base::WeakPtrFactory weak_factory_{this}; + }; + ++int ScreenCaptureKitDeviceMac::active_streams_ = 0; ++ + } // namespace + + std::unique_ptr CreateScreenCaptureKitDeviceMac( +diff --git a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc +index 6aa143bb74789d205f17014f511b7900001237b1..f38ea5df3b6c694aed3a54486733130a2bec606b 100644 +--- a/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc ++++ b/content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc +@@ -344,13 +344,15 @@ void InProcessVideoCaptureDeviceLauncher::LaunchDeviceAsync( + std::move(after_start_capture_callback)); + break; + #else ++ media::VideoCaptureParams updated_params = params; ++ updated_params.use_native_picker = stream_type != blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE; + // All cases other than tab capture or Aura desktop/window capture. + TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"), + "UsingDesktopCapturer", TRACE_EVENT_SCOPE_THREAD); + start_capture_closure = base::BindOnce( + &InProcessVideoCaptureDeviceLauncher:: + DoStartDesktopCaptureOnDeviceThread, +- base::Unretained(this), desktop_id, params, ++ base::Unretained(this), desktop_id, updated_params, + CreateDeviceClient(media::VideoCaptureBufferType::kSharedMemory, + kMaxNumberOfBuffers, std::move(receiver), + std::move(receiver_on_io_thread)), +diff --git a/media/capture/video_capture_types.h b/media/capture/video_capture_types.h +index f2b75f5b2f547ad135c1288bf3639b26dedc8053..ef18724d9f2ea68a47b66fc3981f58a73ac1b51d 100644 +--- a/media/capture/video_capture_types.h ++++ b/media/capture/video_capture_types.h +@@ -355,6 +355,8 @@ struct CAPTURE_EXPORT VideoCaptureParams { + // Flag indicating whether HiDPI mode should be enabled for tab capture + // sessions. + bool is_high_dpi_enabled = true; ++ ++ std::optional use_native_picker; + }; + + CAPTURE_EXPORT std::ostream& operator<<( diff --git a/shell/browser/api/electron_api_desktop_capturer.cc b/shell/browser/api/electron_api_desktop_capturer.cc index 823e72d584a1..82de0b974272 100644 --- a/shell/browser/api/electron_api_desktop_capturer.cc +++ b/shell/browser/api/electron_api_desktop_capturer.cc @@ -502,6 +502,13 @@ gin::Handle DesktopCapturer::Create(v8::Isolate* isolate) { return handle; } +// static +#if !BUILDFLAG(IS_MAC) +bool DesktopCapturer::IsDisplayMediaSystemPickerAvailable() { + return false; +} +#endif + gin::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder( v8::Isolate* isolate) { return gin::Wrappable::GetObjectTemplateBuilder(isolate) @@ -523,6 +530,9 @@ void Initialize(v8::Local exports, gin_helper::Dictionary dict(context->GetIsolate(), exports); dict.SetMethod("createDesktopCapturer", &electron::api::DesktopCapturer::Create); + dict.SetMethod( + "isDisplayMediaSystemPickerAvailable", + &electron::api::DesktopCapturer::IsDisplayMediaSystemPickerAvailable); } } // namespace diff --git a/shell/browser/api/electron_api_desktop_capturer.h b/shell/browser/api/electron_api_desktop_capturer.h index d21f782a8ae1..01f896c1ffd2 100644 --- a/shell/browser/api/electron_api_desktop_capturer.h +++ b/shell/browser/api/electron_api_desktop_capturer.h @@ -32,6 +32,8 @@ class DesktopCapturer final : public gin::Wrappable, static gin::Handle Create(v8::Isolate* isolate); + static bool IsDisplayMediaSystemPickerAvailable(); + void StartHandling(bool capture_window, bool capture_screen, const gfx::Size& thumbnail_size, diff --git a/shell/browser/api/electron_api_desktop_capturer_mac.mm b/shell/browser/api/electron_api_desktop_capturer_mac.mm new file mode 100644 index 000000000000..1ca3093c3f49 --- /dev/null +++ b/shell/browser/api/electron_api_desktop_capturer_mac.mm @@ -0,0 +1,17 @@ +// Copyright (c) 2024 Salesforce, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/browser/api/electron_api_desktop_capturer.h" + +namespace electron::api { + +// static +bool DesktopCapturer::IsDisplayMediaSystemPickerAvailable() { + if (@available(macOS 15.0, *)) { + return true; + } + return false; +} + +} // namespace electron::api diff --git a/shell/browser/api/electron_api_session.cc b/shell/browser/api/electron_api_session.cc index 9a8445b4f7c0..75f6218ef778 100644 --- a/shell/browser/api/electron_api_session.cc +++ b/shell/browser/api/electron_api_session.cc @@ -1607,7 +1607,7 @@ void Session::FillObjectTemplate(v8::Isolate* isolate, &Session::SetPermissionRequestHandler) .SetMethod("setPermissionCheckHandler", &Session::SetPermissionCheckHandler) - .SetMethod("setDisplayMediaRequestHandler", + .SetMethod("_setDisplayMediaRequestHandler", &Session::SetDisplayMediaRequestHandler) .SetMethod("setDevicePermissionHandler", &Session::SetDevicePermissionHandler) diff --git a/shell/browser/feature_list.cc b/shell/browser/feature_list.cc index 53e5dcd4fa84..ed2f12e43b87 100644 --- a/shell/browser/feature_list.cc +++ b/shell/browser/feature_list.cc @@ -58,6 +58,11 @@ void InitializeFeatureList() { if (platform_specific_enable_features.size() > 0) { enable_features += std::string(",") + platform_specific_enable_features; } + std::string platform_specific_disable_features = + DisablePlatformSpecificFeatures(); + if (platform_specific_disable_features.size() > 0) { + disable_features += std::string(",") + platform_specific_disable_features; + } base::FeatureList::InitInstance(enable_features, disable_features); } @@ -73,6 +78,9 @@ void InitializeFieldTrials() { std::string EnablePlatformSpecificFeatures() { return ""; } +std::string DisablePlatformSpecificFeatures() { + return ""; +} #endif } // namespace electron diff --git a/shell/browser/feature_list.h b/shell/browser/feature_list.h index 34fdeb5850b9..c3be0ed97ad8 100644 --- a/shell/browser/feature_list.h +++ b/shell/browser/feature_list.h @@ -11,6 +11,7 @@ namespace electron { void InitializeFeatureList(); void InitializeFieldTrials(); std::string EnablePlatformSpecificFeatures(); +std::string DisablePlatformSpecificFeatures(); } // namespace electron #endif // ELECTRON_SHELL_BROWSER_FEATURE_LIST_H_ diff --git a/shell/browser/feature_list_mac.mm b/shell/browser/feature_list_mac.mm index cde16007e951..6c5720939973 100644 --- a/shell/browser/feature_list_mac.mm +++ b/shell/browser/feature_list_mac.mm @@ -31,4 +31,13 @@ std::string EnablePlatformSpecificFeatures() { return ""; } +std::string DisablePlatformSpecificFeatures() { + if (@available(macOS 14.4, *)) { + // Required to stop timing out getDisplayMedia while waiting for + // the user to select a window with the picker + return "TimeoutHangingVideoCaptureStarts"; + } + return ""; +} + } // namespace electron diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts index 2ac2f55868ac..27c0b9f32eb0 100644 --- a/typings/internal-ambient.d.ts +++ b/typings/internal-ambient.d.ts @@ -213,7 +213,7 @@ declare namespace NodeJS { _linkedBinding(name: 'electron_browser_app'): { app: Electron.App, App: Function }; _linkedBinding(name: 'electron_browser_auto_updater'): { autoUpdater: Electron.AutoUpdater }; _linkedBinding(name: 'electron_browser_crash_reporter'): CrashReporterBinding; - _linkedBinding(name: 'electron_browser_desktop_capturer'): { createDesktopCapturer(): ElectronInternal.DesktopCapturer; }; + _linkedBinding(name: 'electron_browser_desktop_capturer'): { createDesktopCapturer(): ElectronInternal.DesktopCapturer; isDisplayMediaSystemPickerAvailable(): boolean; }; _linkedBinding(name: 'electron_browser_event_emitter'): { setEventEmitterPrototype(prototype: Object): void; }; _linkedBinding(name: 'electron_browser_global_shortcut'): { globalShortcut: Electron.GlobalShortcut }; _linkedBinding(name: 'electron_browser_image_view'): { ImageView: any }; diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index 2c99c4869ef2..9814355df688 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -123,6 +123,10 @@ declare namespace Electron { type?: 'backgroundPage' | 'window' | 'browserView' | 'remote' | 'webview' | 'offscreen'; } + interface Session { + _setDisplayMediaRequestHandler: Electron.Session['setDisplayMediaRequestHandler']; + } + type CreateWindowFunction = (options: BrowserWindowConstructorOptions) => WebContents; interface Menu {