1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
|
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/cocoa/permissions_utils.h"
#include <CoreGraphics/CoreGraphics.h>
#include <Foundation/Foundation.h>
#import <ScreenCaptureKit/ScreenCaptureKit.h>
#include "base/apple/bridging.h"
#include "base/apple/foundation_util.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/mac/mac_util.h"
#include "base/task/thread_pool.h"
namespace ui {
namespace {
BASE_FEATURE(kWarmScreenCaptureSonoma,
"WarmScreenCaptureSonoma",
base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kWarmScreenCaptureSequoia,
"WarmScreenCaptureSequoia",
base::FEATURE_DISABLED_BY_DEFAULT);
bool ShouldWarmScreenCapture() {
const int macos_version = base::mac::MacOSVersion();
// On macOS < 14, we're using CGWindowListCreateImage to capture a screenshot.
// Starting in macOS 14, CGWindowListCreateImage causes a "your screen is
// being captured" chip to show in the menu bar while an app is capturing the
// screen, and if it's a one-time image capture, it shows for ten seconds.
if (macos_version < 14'00'00) {
return true;
}
// Kill switch, Sonoma.
if (macos_version < 15'00'00 &&
!base::FeatureList::IsEnabled(kWarmScreenCaptureSonoma)) {
return false;
}
// Feature disabled by default for Sequoia unless explicitly enabled.
if (macos_version >= 15'00'00 &&
!base::FeatureList::IsEnabled(kWarmScreenCaptureSequoia)) {
return false;
}
// On macOS >= 14, Apple introduced SCScreenshotManager that can be used to
// capture a screenshot without any notification shown to the user. There's a
// bug in this API that was fixed in 14.4.
if (macos_version >= 14'04'00) {
return true;
}
// macOS 14-14.3.
return false;
}
// Capture a screenshot and throw away the result.
void CaptureScreenshot() {
if (@available(macOS 14.0, *)) {
CHECK(base::FeatureList::IsEnabled(kWarmScreenCaptureSonoma));
// Capturing a screenshot using SCK involves three asynchronous steps:
// 1. Request shareable contents (getShareableContent...),
// 2. Instruct SCScreenshotManager to take a screenshot (captureImage...),
// 3. Receive a callback with the actual image.
auto shareable_content_handler = ^(SCShareableContent* content,
NSError* error) {
if (!content.displays.count || error) {
LOG(WARNING)
<< "Failed to get shareable content during WarmScreenCapture.";
return;
}
SCDisplay* first_display = content.displays.firstObject;
SCStreamConfiguration* config = [[SCStreamConfiguration alloc] init];
// Set the size to something small to make it clear to anyone reading the
// code that the screenshot contains no real information.
config.width = 16;
config.height = 10;
auto screenshot_handler = ^(CGImageRef sampleBuffer, NSError* sc_error) {
// Do nothing.
};
SCContentFilter* filter =
[[SCContentFilter alloc] initWithDisplay:first_display
excludingWindows:@[]];
[SCScreenshotManager captureImageWithFilter:filter
configuration:config
completionHandler:screenshot_handler];
};
[SCShareableContent
getShareableContentExcludingDesktopWindows:true
onScreenWindowsOnly:true
completionHandler:shareable_content_handler];
} else {
base::apple::ScopedCFTypeRef<CGImageRef>(
CGWindowListCreateImage(CGRectInfinite, kCGWindowListOptionOnScreenOnly,
kCGNullWindowID, kCGWindowImageDefault));
}
}
} // namespace
bool IsScreenCaptureAllowed() {
return CGPreflightScreenCaptureAccess();
}
bool TryPromptUserForScreenCapture() {
return CGRequestScreenCaptureAccess();
}
void WarmScreenCapture() {
if (!ShouldWarmScreenCapture()) {
return;
}
// WarmScreenCapture() is meant to be called during early startup. Since the
// calls to warm the cache may block, execute them off the main thread so we
// don't hold up startup. To be effective these calls need to run before
// Chrome is updated. Running them off the main thread technically opens us
// to a race condition, however updating happens way later so this is not a
// concern.
base::ThreadPool::PostTask(
FROM_HERE,
// Checking screen capture access hits the TCC.db and reads Chrome's
// code signature from disk, marking as MayBlock.
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce([] {
if (IsScreenCaptureAllowed()) {
CaptureScreenshot();
}
}));
}
} // namespace ui
|