[fix] fix crash when screen recording permission is not granted on macOS, refs #29

This commit is contained in:
dijunkun
2025-11-27 03:32:16 +08:00
parent f14bdb7fe8
commit 3c3c7b9ae0

View File

@@ -196,14 +196,26 @@ ScreenCapturerSckImpl::~ScreenCapturerSckImpl() {
int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) { int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
_on_data = cb; _on_data = cb;
fps_ = fps;
if (@available(macOS 10.15, *)) {
bool has_permission = CGPreflightScreenCaptureAccess();
if (!has_permission) {
LOG_ERROR("Screen recording permission not granted");
return -1;
}
}
dispatch_semaphore_t sema = dispatch_semaphore_create(0); dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block SCShareableContent *content = nil; __block SCShareableContent *content = nil;
__block NSError *capture_error = nil;
[SCShareableContent [SCShareableContent
getShareableContentWithCompletionHandler:^(SCShareableContent *result, NSError *error) { getShareableContentWithCompletionHandler:^(SCShareableContent *result, NSError *error) {
if (error) { if (error) {
NSLog(@"Failed to get shareable content: %@", error); capture_error = error;
LOG_ERROR("Failed to get shareable content: {}",
std::string([error.localizedDescription UTF8String]));
} else { } else {
content = result; content = result;
} }
@@ -211,9 +223,10 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
}]; }];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
if (!content || content.displays.count == 0) { if (capture_error || !content || content.displays.count == 0) {
LOG_ERROR("Failed to get display info"); LOG_ERROR("Failed to get display info, error: {}",
return 0; std::string([capture_error.localizedDescription UTF8String]));
return -1;
} }
CGDirectDisplayID displays[10]; CGDirectDisplayID displays[10];
@@ -252,6 +265,16 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
} }
int ScreenCapturerSckImpl::Start(bool show_cursor) { int ScreenCapturerSckImpl::Start(bool show_cursor) {
if (permanent_error_) {
LOG_ERROR("Cannot start capturer: permanent error occurred");
return -1;
}
if (display_info_list_.empty()) {
LOG_ERROR("Cannot start capturer: display info not initialized");
return -1;
}
show_cursor_ = show_cursor; show_cursor_ = show_cursor;
StartOrReconfigureCapturer(); StartOrReconfigureCapturer();
return 0; return 0;
@@ -307,17 +330,17 @@ void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *conten
return; return;
} }
if (!content.displays.count) { if (!content.displays || content.displays.count == 0) {
LOG_ERROR("getShareableContent returned no displays"); LOG_ERROR("getShareableContent returned no displays");
permanent_error_ = true; permanent_error_ = true;
return; return;
} }
SCDisplay *captured_display; SCDisplay *captured_display = nil;
{ {
std::lock_guard<std::mutex> lock(lock_); std::lock_guard<std::mutex> lock(lock_);
for (SCDisplay *display in content.displays) { for (SCDisplay *display in content.displays) {
if (current_display_ == display.displayID) { if (current_display_ != 0 && current_display_ == display.displayID) {
LOG_WARN("current display: {}, name: {}", current_display_, LOG_WARN("current display: {}, name: {}", current_display_,
display_id_name_map_[current_display_]); display_id_name_map_[current_display_]);
captured_display = display; captured_display = display;
@@ -326,13 +349,33 @@ void ScreenCapturerSckImpl::OnShareableContentCreated(SCShareableContent *conten
} }
if (!captured_display) { if (!captured_display) {
captured_display = content.displays.firstObject; captured_display = content.displays.firstObject;
if (captured_display) {
current_display_ = captured_display.displayID; current_display_ = captured_display.displayID;
} }
} }
}
if (!captured_display) {
LOG_ERROR("Failed to find valid display");
permanent_error_ = true;
return;
}
SCContentFilter *filter = [[SCContentFilter alloc] initWithDisplay:captured_display SCContentFilter *filter = [[SCContentFilter alloc] initWithDisplay:captured_display
excludingWindows:@[]]; excludingWindows:@[]];
if (!filter) {
LOG_ERROR("Failed to create SCContentFilter");
permanent_error_ = true;
return;
}
SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init]; SCStreamConfiguration *config = [[SCStreamConfiguration alloc] init];
if (!config) {
LOG_ERROR("Failed to create SCStreamConfiguration");
permanent_error_ = true;
return;
}
config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; config.pixelFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
config.showsCursor = show_cursor_; config.showsCursor = show_cursor_;
config.width = filter.contentRect.size.width * filter.pointPixelScale; config.width = filter.contentRect.size.width * filter.pointPixelScale;
@@ -423,11 +466,28 @@ void ScreenCapturerSckImpl::OnNewCVPixelBuffer(CVPixelBufferRef pixelBuffer,
} }
void ScreenCapturerSckImpl::StartOrReconfigureCapturer() { void ScreenCapturerSckImpl::StartOrReconfigureCapturer() {
// The copy is needed to avoid capturing `this` in the Objective-C block. Accessing `helper_` if (permanent_error_) {
// inside the block is equivalent to `this->helper_` and would crash (UAF) if `this` is LOG_ERROR("Cannot reconfigure capturer: permanent error occurred");
// deleted before the block is executed. return;
}
if (@available(macOS 10.15, *)) {
bool has_permission = CGPreflightScreenCaptureAccess();
if (!has_permission) {
LOG_ERROR("Screen recording permission not granted");
permanent_error_ = true;
return;
}
}
SckHelper *local_helper = helper_; SckHelper *local_helper = helper_;
auto handler = ^(SCShareableContent *content, NSError *error) { auto handler = ^(SCShareableContent *content, NSError *error) {
if (error) {
LOG_ERROR("getShareableContent failed: {}",
std::string([error.localizedDescription UTF8String]));
[local_helper onShareableContentCreated:nil];
return;
}
[local_helper onShareableContentCreated:content]; [local_helper onShareableContentCreated:content];
}; };
[SCShareableContent getShareableContentWithCompletionHandler:handler]; [SCShareableContent getShareableContentWithCompletionHandler:handler];