blob: 799f6f13a2429f0d7966272180cc64c306a232ec [file] [log] [blame]
/*
* Copyright (C) 2021 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "DisplayCaptureSessionManager.h"
#if PLATFORM(COCOA) && ENABLE(MEDIA_STREAM)
#import "Logging.h"
#import "MediaPermissionUtilities.h"
#import "WKWebViewInternal.h"
#import "WebPageProxy.h"
#import <WebCore/CaptureDeviceManager.h>
#import <WebCore/LocalizedStrings.h>
#import <WebCore/MockRealtimeMediaSourceCenter.h>
#import <WebCore/ScreenCaptureKitCaptureSource.h>
#import <WebCore/SecurityOriginData.h>
#import <wtf/BlockPtr.h>
#import <wtf/MainThread.h>
#import <wtf/NeverDestroyed.h>
#import <wtf/URLHelpers.h>
#import <wtf/cocoa/TypeCastsCocoa.h>
#import <wtf/text/StringToIntegerConversion.h>
namespace WebKit {
#if HAVE(SCREEN_CAPTURE_KIT)
static void alertForWindowSelection(WebPageProxy& page, const WebCore::SecurityOriginData& origin, CompletionHandler<void(std::optional<String>, std::optional<String>)>&& completionHandler)
{
auto webView = page.cocoaView();
if (!webView) {
completionHandler(std::nullopt, std::nullopt);
return;
}
Vector<DisplayCaptureManager::WindowCaptureDevice> windowInfo;
RealtimeMediaSourceCenter::singleton().displayCaptureFactory().displayCaptureDeviceManager().windowDevices(windowInfo);
if (windowInfo.isEmpty()) {
completionHandler(std::nullopt, std::nullopt);
return;
}
std::sort(windowInfo.begin(), windowInfo.end(), [](auto& a, auto& b) {
if (a.m_application != b.m_application)
return codePointCompareLessThan(a.m_application, b.m_application);
return codePointCompareLessThan(a.m_device.label(), b.m_device.label());
});
auto alert = adoptNS([[NSAlert alloc] init]);
[alert setMessageText:WEB_UI_NSSTRING(@"Choose a window to share", Message for window sharing prompt)];
auto popupButton = adoptNS([[NSPopUpButton alloc] initWithFrame:NSMakeRect(10, 0, 290, 36) pullsDown:NO]);
auto menu = [popupButton menu];
menu.autoenablesItems = NO;
String currentApplication;
unsigned infoIndex = 0;
for (auto& info : windowInfo) {
if (info.m_application != currentApplication) {
auto applicationItem = adoptNS([[NSMenuItem alloc] initWithTitle:info.m_application action:nil keyEquivalent:@""]);
[applicationItem setEnabled:NO];
[menu addItem:applicationItem.get()];
currentApplication = info.m_application;
}
auto title = info.m_device.label();
auto windowItem = adoptNS([[NSMenuItem alloc] initWithTitle:(!title.isEmpty() ? title : info.m_application) action:nil keyEquivalent:@""]);
[windowItem setIndentationLevel:1];
[windowItem setRepresentedObject:@(infoIndex++)];
[menu addItem:windowItem.get()];
}
[popupButton selectItem:nil];
auto menuLabel = adoptNS([[NSTextView alloc] init]);
[menuLabel setString:WEB_UI_NSSTRING(@"Window: ", "Label for window sharing menu")];
[menuLabel setDrawsBackground:NO];
[menuLabel setSelectable:NO];
[menuLabel setEditable:NO];
auto accessoryView = adoptNS([[NSView alloc] initWithFrame:NSMakeRect(0, 0, 300, 40)]);
[accessoryView addSubview:popupButton.get()];
[accessoryView addSubview:menuLabel.get()];
[alert setAccessoryView:accessoryView.get()];
NSButton *button = [alert addButtonWithTitle:WEB_UI_NSSTRING(@"Allow (window sharing)", "Allow button title in window sharing prompt")];
button.keyEquivalent = @"";
button = [alert addButtonWithTitle:WEB_UI_NSSTRING(@"Don’t Allow (window sharing)", "Disallow button title in window sharing prompt")];
button.keyEquivalent = @"\E";
[alert beginSheetModalForWindow:[webView window] completionHandler:[popupButton = WTFMove(popupButton), windowInfo, completionBlock = makeBlockPtr(WTFMove(completionHandler))](NSModalResponse returnCode) {
if (returnCode != NSAlertFirstButtonReturn) {
completionBlock(std::nullopt, std::nullopt);
return;
}
NSNumber *infoIndex = [[popupButton selectedItem] representedObject];
if (!infoIndex || [infoIndex unsignedIntegerValue] > windowInfo.size()) {
completionBlock(std::nullopt, std::nullopt);
return;
}
auto info = windowInfo[[infoIndex unsignedIntegerValue]];
completionBlock(info.m_device.persistentId(), info.m_device.label());
}];
}
void DisplayCaptureSessionManager::alertForGetDisplayMedia(WebPageProxy& page, const WebCore::SecurityOriginData& origin, CompletionHandler<void(DisplayCaptureSessionManager::CaptureSessionType)>&& completionHandler)
{
auto webView = page.cocoaView();
if (!webView) {
completionHandler(DisplayCaptureSessionManager::CaptureSessionType::None);
return;
}
NSString *visibleOrigin = applicationVisibleNameFromOrigin(origin);
if (!visibleOrigin)
visibleOrigin = applicationVisibleName();
NSString *alertTitle = [NSString stringWithFormat:WEB_UI_NSSTRING(@"Allow “%@” to observe one of your windows or screens?", "Message for window and screen sharing prompt"), visibleOrigin];
auto *allowWindowButtonString = WEB_UI_NSSTRING(@"Allow Observing a Window", "Allow window button title in window and screen sharing prompt");
auto *allowScreenButtonString = WEB_UI_NSSTRING(@"Allow Observing a Screen", "Allow screen button title in window and screen sharing prompt");
auto *doNotAllowButtonString = WEB_UI_NSSTRING(@"Don’t Allow (window and screen sharing)", "Disallow button title in window and screen sharing prompt");
auto alert = adoptNS([[NSAlert alloc] init]);
[alert setMessageText:alertTitle];
auto *button = [alert addButtonWithTitle:allowWindowButtonString];
button.keyEquivalent = @"";
button = [alert addButtonWithTitle:allowScreenButtonString];
button.keyEquivalent = @"";
button = [alert addButtonWithTitle:doNotAllowButtonString];
button.keyEquivalent = @"\E";
[alert beginSheetModalForWindow:[webView window] completionHandler:[completionBlock = makeBlockPtr(WTFMove(completionHandler))](NSModalResponse returnCode) {
DisplayCaptureSessionManager::CaptureSessionType result = DisplayCaptureSessionManager::CaptureSessionType::None;
switch (returnCode) {
case NSAlertFirstButtonReturn:
result = DisplayCaptureSessionManager::CaptureSessionType::Window;
break;
case NSAlertSecondButtonReturn:
result = DisplayCaptureSessionManager::CaptureSessionType::Screen;
break;
case NSAlertThirdButtonReturn:
result = DisplayCaptureSessionManager::CaptureSessionType::None;
break;
}
completionBlock(result);
}];
}
std::optional<CaptureDevice> DisplayCaptureSessionManager::deviceSelectedForTesting(CaptureDevice::DeviceType deviceType)
{
ASSERT(m_indexOfDeviceSelectedForTesting);
unsigned index = 0;
for (auto& device : RealtimeMediaSourceCenter::singleton().displayCaptureFactory().displayCaptureDeviceManager().captureDevices()) {
if (device.enabled() && device.type() == deviceType) {
if (index == m_indexOfDeviceSelectedForTesting.value())
return { device };
++index;
}
}
return std::nullopt;
}
void DisplayCaptureSessionManager::showWindowPicker(WebPageProxy& page, const WebCore::SecurityOriginData& origin, CompletionHandler<void(std::optional<CaptureDevice>)>&& completionHandler)
{
if (m_indexOfDeviceSelectedForTesting) {
completionHandler(deviceSelectedForTesting(CaptureDevice::DeviceType::Window));
return;
}
alertForWindowSelection(page, origin, [completionHandler = WTFMove(completionHandler)] (std::optional<String> windowID, std::optional<String> windowTitle) mutable {
if (!windowID || !windowTitle) {
completionHandler(std::nullopt);
return;
}
CaptureDevice device = { windowID.value(), CaptureDevice::DeviceType::Window, windowTitle.value(), emptyString(), true };
completionHandler({ device });
});
}
void DisplayCaptureSessionManager::showScreenPicker(WebPageProxy&, const WebCore::SecurityOriginData&, CompletionHandler<void(std::optional<CaptureDevice>)>&& completionHandler)
{
if (m_indexOfDeviceSelectedForTesting) {
completionHandler(deviceSelectedForTesting(CaptureDevice::DeviceType::Screen));
return;
}
callOnMainRunLoop([completionHandler = WTFMove(completionHandler)] () mutable {
for (auto& device : RealtimeMediaSourceCenter::singleton().displayCaptureFactory().displayCaptureDeviceManager().captureDevices()) {
if (device.enabled() && device.type() == CaptureDevice::DeviceType::Screen) {
completionHandler({ device });
return;
}
}
completionHandler(std::nullopt);
});
}
#endif
bool DisplayCaptureSessionManager::isAvailable()
{
#if HAVE(SCREEN_CAPTURE_KIT)
return ScreenCaptureKitCaptureSource::isAvailable();
#else
return false;
#endif
}
DisplayCaptureSessionManager& DisplayCaptureSessionManager::singleton()
{
ASSERT(isMainRunLoop());
static NeverDestroyed<DisplayCaptureSessionManager> manager;
return manager;
}
DisplayCaptureSessionManager::DisplayCaptureSessionManager()
{
}
DisplayCaptureSessionManager::~DisplayCaptureSessionManager()
{
}
void DisplayCaptureSessionManager::promptForGetDisplayMedia(UserMediaPermissionRequestProxy::UserMediaDisplayCapturePromptType promptType, WebPageProxy& page, const WebCore::SecurityOriginData& origin, CompletionHandler<void(std::optional<CaptureDevice>)>&& completionHandler)
{
ASSERT(isAvailable());
#if HAVE(SCREEN_CAPTURE_KIT)
if (!isAvailable() || !completionHandler) {
completionHandler(std::nullopt);
return;
}
if (promptType == UserMediaPermissionRequestProxy::UserMediaDisplayCapturePromptType::Screen) {
showScreenPicker(page, origin, WTFMove(completionHandler));
return;
}
if (promptType == UserMediaPermissionRequestProxy::UserMediaDisplayCapturePromptType::Window) {
showWindowPicker(page, origin, WTFMove(completionHandler));
return;
}
alertForGetDisplayMedia(page, origin, [this, page = Ref { page }, origin, completionHandler = WTFMove(completionHandler)] (DisplayCaptureSessionManager::CaptureSessionType sessionType) mutable {
if (sessionType == CaptureSessionType::None) {
completionHandler(std::nullopt);
return;
}
if (sessionType == CaptureSessionType::Screen)
showScreenPicker(page, origin, WTFMove(completionHandler));
else
showWindowPicker(page, origin, WTFMove(completionHandler));
});
#else
completionHandler(std::nullopt);
#endif
}
} // namespace WebKit
#endif // PLATFORM(COCOA) && ENABLE(MEDIA_STREAM)