Compare commits

...

5 Commits

Author SHA1 Message Date
Keeley Hammond
d1671d3112 feat: add getNativePickerSource() API option 2024-08-27 11:37:44 -04:00
Keeley Hammond
2ff8439001 fix: make capturer work on both screens and windows 2024-08-27 11:36:43 -04:00
Keeley Hammond
d525cd5c19 fix: make window capturer work, still issue with first refresh 2024-08-26 11:33:47 -07:00
Keeley Hammond
7e33202df7 feat: use upstream MacOS SCContentSharingPicker implementation pt. 1 2024-08-21 19:00:33 -07:00
Keeley Hammond
d8d9c2cd21 feat: partially implement IsDisplayMediaSystemPickerAvailable 2024-08-21 18:44:25 -07:00
9 changed files with 261 additions and 5 deletions

View File

@@ -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",

View File

@@ -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,29 @@ function isValid (options: Electron.SourcesOptions) {
return Array.isArray(options?.types);
}
export { isDisplayMediaSystemPickerAvailable };
export async function getNativePickerSource () {
if (process.platform !== 'darwin') {
console.error('Native system picker option is currently only supported on MacOS');
}
if (!isDisplayMediaSystemPickerAvailable) {
console.error(`Native system picker unavailable.
Note: This is an experimental API; please check the API documentation for updated restrictions`);
}
// Pass in the needed options for a more native experience
// screen & windows by default, no thumbnails, since the native picker doesn't return them
const options: Electron.SourcesOptions = {
types: ['screen', 'window'],
thumbnailSize: { width: 0, height: 0 },
fetchWindowIcons: false
};
return await getSources(options);
}
export async function getSources (args: Electron.SourcesOptions) {
if (!isValid(args)) throw new Error('Invalid options');

View File

@@ -129,3 +129,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
build_expose_webplugininfo_interface_to_electron.patch
feat_make_macos_sccontentsharingpicker_work_in_electron.patch

View File

@@ -0,0 +1,155 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Keeley Hammond <khammond@slack-corp.com>
Date: Wed, 21 Aug 2024 19:00:00 -0700
Subject: feat: make MacOS SCContentSharingPicker work in Electron
This patch is a work in progress that contains assorted changes to make the MacOS SCContentSharingPicker upstream implementation work within Electron. If this comment is still in this patch during PR review, it is not ready for prime time
This patch can be removed after our desktopCapturer is refactored.
diff --git a/chrome/browser/media/webrtc/desktop_media_list_base.cc b/chrome/browser/media/webrtc/desktop_media_list_base.cc
index 6599311831b638f49658e768fe35e19e9961ef1d..f49519a6cc52d6e90ff07b64e5a71010094f9c5d 100644
--- a/chrome/browser/media/webrtc/desktop_media_list_base.cc
+++ b/chrome/browser/media/webrtc/desktop_media_list_base.cc
@@ -77,7 +77,7 @@ void DesktopMediaListBase::StartUpdating(DesktopMediaListObserver* observer) {
void DesktopMediaListBase::Update(UpdateCallback callback, bool refresh_thumbnails) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(sources_.empty());
- DCHECK(!refresh_callback_);
+ // DCHECK(!refresh_callback_);
refresh_callback_ = std::move(callback);
Refresh(refresh_thumbnails);
}
diff --git a/chrome/browser/media/webrtc/native_desktop_media_list.cc b/chrome/browser/media/webrtc/native_desktop_media_list.cc
index 95a1c18438619c19a1dd71ca3e6e23af5e0ebacb..68ece50018124992f951557e817a12aa45d65956 100644
--- a/chrome/browser/media/webrtc/native_desktop_media_list.cc
+++ b/chrome/browser/media/webrtc/native_desktop_media_list.cc
@@ -46,6 +46,7 @@
#endif
#if BUILDFLAG(IS_MAC)
+#include "chrome/browser/media/webrtc/thumbnail_capturer_mac.h"
#include "components/remote_cocoa/browser/scoped_cg_window_id.h"
#endif
@@ -545,11 +546,23 @@ NativeDesktopMediaList::Worker::FormatSources(
break;
case DesktopMediaID::Type::TYPE_WINDOW:
+#if BUILDFLAG(IS_MAC)
+ // If using NativeScreenCapturePickerMac,
+ // skipping the picker will skip the first window selection.
+ if (ShouldUseSCContentSharingPicker()) {
+ title = base::UTF8ToUTF16(sources[i].title);
+ } else if (sources[i].id == excluded_window_id) {
+ // Skip the picker dialog window.
+ continue;
+ }
+ title = base::UTF8ToUTF16(sources[i].title);
+ #else
// Skip the picker dialog window.
if (sources[i].id == excluded_window_id) {
continue;
}
title = base::UTF8ToUTF16(sources[i].title);
+#endif
break;
default:
diff --git a/chrome/browser/media/webrtc/thumbnail_capturer_mac.h b/chrome/browser/media/webrtc/thumbnail_capturer_mac.h
index 12a74f8f32cc00a7f3d7802865ae4b309961341d..acbcfb08ae8c44e24a04b326096289428bc6ff60 100644
--- a/chrome/browser/media/webrtc/thumbnail_capturer_mac.h
+++ b/chrome/browser/media/webrtc/thumbnail_capturer_mac.h
@@ -8,6 +8,9 @@
#include "chrome/browser/media/webrtc/desktop_media_list.h"
#include "chrome/browser/media/webrtc/thumbnail_capturer.h"
+// Returns true if the SCK sharing picker is available and enabled.
+bool ShouldUseSCContentSharingPicker();
+
// Returns true if the SCK thumbnail capturer is available and enabled.
bool ShouldUseThumbnailCapturerMac(DesktopMediaList::Type type);
diff --git a/chrome/browser/media/webrtc/thumbnail_capturer_mac.mm b/chrome/browser/media/webrtc/thumbnail_capturer_mac.mm
index 2215bf4589342fa4619fb58ec3e21ff5ef3ed3b4..3e52ce331b80cf97fd7b9bcbf7dd4311bacf07f2 100644
--- a/chrome/browser/media/webrtc/thumbnail_capturer_mac.mm
+++ b/chrome/browser/media/webrtc/thumbnail_capturer_mac.mm
@@ -40,14 +40,14 @@
// is required to avoid recurring permission dialogs.
BASE_FEATURE(kUseSCContentSharingPicker,
"UseSCContentSharingPicker",
- base::FEATURE_DISABLED_BY_DEFAULT);
+ base::FEATURE_ENABLED_BY_DEFAULT);
// Use the built-in MacOS screen-sharing picker (SCContentSharingPicker) on
// MacOS 14 Sonoma and later. This flag will use the built-in picker for all
// MacOS versions where it is supported.
BASE_FEATURE(kUseSCContentSharingPickerSonoma,
"UseSCContentSharingPickerSonoma",
- base::FEATURE_DISABLED_BY_DEFAULT);
+ base::FEATURE_ENABLED_BY_DEFAULT);
#endif
using SampleCallback =
@@ -1006,6 +1006,8 @@ void OnCapturedFrame(base::apple::ScopedCFTypeRef<CGImageRef> image,
source_id);
}
+} // namespace
+
bool ShouldUseSCContentSharingPicker() {
if (@available(macOS 15.0, *)) {
if (base::FeatureList::IsEnabled(kUseSCContentSharingPicker)) {
@@ -1020,8 +1022,6 @@ bool ShouldUseSCContentSharingPicker() {
return false;
}
-} // namespace
-
bool ShouldUseThumbnailCapturerMac(DesktopMediaList::Type type) {
// There was a bug in ScreenCaptureKit that was fixed in 14.4,
// see b/40076027.
diff --git a/content/browser/media/capture/native_screen_capture_picker_mac.mm b/content/browser/media/capture/native_screen_capture_picker_mac.mm
index b5a776f37b4bb667bc1aa62a08102b67a12f5b64..36b1508f0a8bd17bec0e49bf797a64c2fcc38bc2 100644
--- a/content/browser/media/capture/native_screen_capture_picker_mac.mm
+++ b/content/browser/media/capture/native_screen_capture_picker_mac.mm
@@ -117,8 +117,11 @@ void Open(DesktopMediaID::Type type,
base::OnceCallback<void()> cancel_callback,
base::OnceCallback<void()> error_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Chrome doesn't allow both screens & windows in their picker,
+ // but Electron does - add a check for TYPE_NONE.
CHECK(type == DesktopMediaID::Type::TYPE_SCREEN ||
- type == DesktopMediaID::Type::TYPE_WINDOW);
+ type == DesktopMediaID::Type::TYPE_WINDOW ||
+ type == DesktopMediaID::Type::TYPE_NONE);
if (@available(macOS 14.0, *)) {
NSNumber* source_id = @(next_id_);
auto picker_observer = [[PickerObserver alloc]
@@ -135,20 +138,14 @@ void Open(DesktopMediaID::Type type,
// TODO(https://crbug.com/360781940): Add support for changing selected
// content. The problem to solve is how this should interact with stream
// restart.
- config.allowsChangingSelectedContent = false;
+ config.allowsChangingSelectedContent = true;
// Limits the maximum number of screen/window capture to 5.
NSNumber* max_stream_count = @5;
- if (type == DesktopMediaID::Type::TYPE_SCREEN) {
- config.allowedPickerModes = SCContentSharingPickerModeSingleDisplay;
- picker.defaultConfiguration = config;
- picker.maximumStreamCount = max_stream_count;
- [picker presentPickerUsingContentStyle:SCShareableContentStyleDisplay];
- } else {
- config.allowedPickerModes = SCContentSharingPickerModeSingleWindow;
- picker.defaultConfiguration = config;
- picker.maximumStreamCount = max_stream_count;
- [picker presentPickerUsingContentStyle:SCShareableContentStyleWindow];
- }
+ // Chrome doesn't allow both screens & windows in their picker,
+ // but Electron does; we patch out the MediaID::Type conditional here
+ picker.defaultConfiguration = config;
+ picker.maximumStreamCount = max_stream_count;
+ [picker present];
} else {
NOTREACHED();
}

View File

@@ -9,6 +9,7 @@
#include <vector>
#include "base/containers/flat_map.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_restrictions.h"
@@ -239,6 +240,12 @@ void DesktopCapturer::DesktopListListener::OnDelegatedSourceListSelection() {
}
}
void DesktopCapturer::DesktopListListener::OnSourceAdded(int index) {
// TODO: Implement the wrapped DesktopCapturer::RequestUpdate callback
// here for native pickers, instead of in the screen/window implementation
// below
}
void DesktopCapturer::DesktopListListener::OnSourceThumbnailChanged(int index) {
if (have_selection_) {
// This is called every time a thumbnail is refreshed. Reset variable to
@@ -325,6 +332,7 @@ void DesktopCapturer::StartHandling(bool capture_window,
window_capturer_ = std::make_unique<NativeDesktopMediaList>(
DesktopMediaList::Type::kWindow, std::move(capturer));
window_capturer_->SetThumbnailSize(thumbnail_size);
window_capturer_->ShowDelegatedList();
#if BUILDFLAG(IS_MAC)
window_capturer_->skip_next_refresh_ =
ShouldUseThumbnailCapturerMac(DesktopMediaList::Type::kWindow) ? 2
@@ -335,11 +343,16 @@ void DesktopCapturer::StartHandling(bool capture_window,
&DesktopCapturer::UpdateSourcesList, weak_ptr_factory_.GetWeakPtr(),
window_capturer_.get());
// Needed to force a refresh for the native MacOS Picker
OnceCallback wrapped_update_callback = base::BindOnce(
&DesktopCapturer::RequestUpdate, weak_ptr_factory_.GetWeakPtr(),
window_capturer_.get(), std::move(update_callback));
if (window_capturer_->IsSourceListDelegated()) {
OnceCallback failure_callback = base::BindOnce(
&DesktopCapturer::HandleFailure, weak_ptr_factory_.GetWeakPtr());
window_listener_ = std::make_unique<DesktopListListener>(
std::move(update_callback), std::move(failure_callback),
std::move(wrapped_update_callback), std::move(failure_callback),
thumbnail_size.IsEmpty());
window_capturer_->StartUpdating(window_listener_.get());
} else {
@@ -355,6 +368,7 @@ void DesktopCapturer::StartHandling(bool capture_window,
screen_capturer_ = std::make_unique<NativeDesktopMediaList>(
DesktopMediaList::Type::kScreen, std::move(capturer));
screen_capturer_->SetThumbnailSize(thumbnail_size);
screen_capturer_->ShowDelegatedList();
#if BUILDFLAG(IS_MAC)
screen_capturer_->skip_next_refresh_ =
ShouldUseThumbnailCapturerMac(DesktopMediaList::Type::kScreen) ? 2
@@ -365,11 +379,16 @@ void DesktopCapturer::StartHandling(bool capture_window,
&DesktopCapturer::UpdateSourcesList, weak_ptr_factory_.GetWeakPtr(),
screen_capturer_.get());
// Needed to force a refresh for the native MacOS Picker
OnceCallback wrapped_update_callback = base::BindOnce(
&DesktopCapturer::RequestUpdate, weak_ptr_factory_.GetWeakPtr(),
screen_capturer_.get(), std::move(update_callback));
if (screen_capturer_->IsSourceListDelegated()) {
OnceCallback failure_callback = base::BindOnce(
&DesktopCapturer::HandleFailure, weak_ptr_factory_.GetWeakPtr());
screen_listener_ = std::make_unique<DesktopListListener>(
std::move(update_callback), std::move(failure_callback),
std::move(wrapped_update_callback), std::move(failure_callback),
thumbnail_size.IsEmpty());
screen_capturer_->StartUpdating(screen_listener_.get());
} else {
@@ -381,6 +400,11 @@ void DesktopCapturer::StartHandling(bool capture_window,
}
}
void DesktopCapturer::RequestUpdate(DesktopMediaList* list,
OnceCallback update_callback) {
list->Update(std::move(update_callback));
}
void DesktopCapturer::UpdateSourcesList(DesktopMediaList* list) {
if (capture_window_ &&
list->GetMediaListType() == DesktopMediaList::Type::kWindow) {
@@ -398,6 +422,7 @@ void DesktopCapturer::UpdateSourcesList(DesktopMediaList* list) {
if (capture_screen_ &&
list->GetMediaListType() == DesktopMediaList::Type::kScreen) {
capture_screen_ = false;
base::debug::StackTrace().Print();
std::vector<DesktopCapturer::Source> screen_sources;
screen_sources.reserve(list->GetSourceCount());
for (int i = 0; i < list->GetSourceCount(); i++) {
@@ -504,6 +529,13 @@ gin::Handle<DesktopCapturer> 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<DesktopCapturer>::GetObjectTemplateBuilder(isolate)
@@ -525,6 +557,9 @@ void Initialize(v8::Local<v8::Object> exports,
gin_helper::Dictionary dict(context->GetIsolate(), exports);
dict.SetMethod("createDesktopCapturer",
&electron::api::DesktopCapturer::Create);
dict.SetMethod(
"isDisplayMediaSystemPickerAvailable",
&electron::api::DesktopCapturer::IsDisplayMediaSystemPickerAvailable);
}
} // namespace

View File

@@ -36,6 +36,8 @@ class DesktopCapturer : public gin::Wrappable<DesktopCapturer>,
static gin::Handle<DesktopCapturer> Create(v8::Isolate* isolate);
static bool IsDisplayMediaSystemPickerAvailable();
void StartHandling(bool capture_window,
bool capture_screen,
const gfx::Size& thumbnail_size,
@@ -76,7 +78,7 @@ class DesktopCapturer : public gin::Wrappable<DesktopCapturer>,
~DesktopListListener() override;
protected:
void OnSourceAdded(int index) override {}
void OnSourceAdded(int index) override;
void OnSourceRemoved(int index) override {}
void OnSourceMoved(int old_index, int new_index) override {}
void OnSourceNameChanged(int index) override {}
@@ -92,6 +94,7 @@ class DesktopCapturer : public gin::Wrappable<DesktopCapturer>,
bool have_thumbnail_ = false;
};
void RequestUpdate(DesktopMediaList* list, OnceCallback update_callback);
void UpdateSourcesList(DesktopMediaList* list);
void HandleFailure();
void HandleSuccess();

View File

@@ -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 14.4, *)) {
return true;
}
return false;
}
} // namespace electron::api

View File

@@ -26,6 +26,27 @@ std::string EnablePlatformSpecificFeatures() {
#else
return "ScreenCaptureKitPickerScreen,ScreenCaptureKitStreamPickerSonoma,"
"ThumbnailCapturerMac:capture_mode/sc_screenshot_manager";
#endif
}
if (@available(macOS 15.0, *)) {
// These flags aren't exported so reference them by name directly, they are
// used to ensure that screen and window capture exclusive use
// ScreenCaptureKit APIs to avoid warning dialogs on macOS 15.0 and higher.
// kScreenCaptureKitPickerScreen,
// chrome/browser/media/webrtc/thumbnail_capturer_mac.mm
// kScreenCaptureKitStreamPickerSonoma,
// chrome/browser/media/webrtc/thumbnail_capturer_mac.mm
// kThumbnailCapturerMac,
// chrome/browser/media/webrtc/thumbnail_capturer_mac.mm
// kUseSCContentSharingPicker,
// chrome/browser/media/webrtc/thumbnail_capturer_mac.mm
#if DCHECK_IS_ON()
return "ScreenCaptureKitPickerScreen,ScreenCaptureKitStreamPickerSonoma,"
"UseSCContentSharingPicker";
#else
return "ScreenCaptureKitPickerScreen,ScreenCaptureKitStreamPickerSonoma,"
"UseSCContentSharingPicker,"
"ThumbnailCapturerMac:capture_mode/sc_screenshot_manager";
#endif
}
return "";

View File

@@ -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 };