33 * @brief Definitions for display capture on macOS.
44 */
55// standard includes
6+ #include < charconv>
67#include < cstring>
78#include < mutex>
9+ #include < thread>
10+
11+ // platform includes
12+ #include < IOKit/pwr_mgt/IOPMLib.h>
813
914// local includes
1015#include " src/config.h"
2530namespace platf {
2631 using namespace std ::literals;
2732
33+ namespace {
34+ bool display_selector_is_active (std::string_view selector) {
35+ CGDirectDisplayID active_displays[kMaxDisplays ] {};
36+ uint32_t display_count {};
37+ if (CGGetActiveDisplayList (kMaxDisplays , active_displays, &display_count) != kCGErrorSuccess ) {
38+ return false ;
39+ }
40+
41+ if (selector.empty ()) {
42+ return display_count > 0 ;
43+ }
44+
45+ CGDirectDisplayID numeric_display_id {};
46+ const auto numeric_result = std::from_chars (
47+ selector.data (),
48+ selector.data () + selector.size (),
49+ numeric_display_id
50+ );
51+ const bool is_numeric_selector =
52+ numeric_result.ec == std::errc {} && numeric_result.ptr == selector.data () + selector.size ();
53+
54+ for (uint32_t index = 0 ; index < display_count; ++index) {
55+ const auto display_id = active_displays[index];
56+ if ((is_numeric_selector && display_id == numeric_display_id) ||
57+ (!is_numeric_selector && selector == display_uuid_selector (display_id))) {
58+ return true ;
59+ }
60+ }
61+
62+ return false ;
63+ }
64+
65+ void wake_display_for_capture (std::string_view selector) {
66+ if (display_selector_is_active (selector)) {
67+ return ;
68+ }
69+
70+ IOPMAssertionID wake_assertion = kIOPMNullAssertionID ;
71+ const auto result = IOPMAssertionDeclareUserActivity (
72+ CFSTR (" Sunshine display detection" ),
73+ kIOPMUserActiveRemote ,
74+ &wake_assertion
75+ );
76+
77+ if (result != kIOReturnSuccess ) {
78+ BOOST_LOG (warning) << " Unable to declare remote user activity to wake displays, IOReturn: " sv << result;
79+ return ;
80+ }
81+
82+ BOOST_LOG (info) << " Declared remote user activity to wake displays" sv;
83+
84+ for (int attempt = 0 ; attempt < 30 && !display_selector_is_active (selector); ++attempt) {
85+ std::this_thread::sleep_for (100ms);
86+ }
87+
88+ if (!display_selector_is_active (selector)) {
89+ BOOST_LOG (warning) << " Display wake attempt did not expose the requested display [" sv
90+ << selector << " ] in the active display list." sv;
91+ }
92+
93+ if (wake_assertion != kIOPMNullAssertionID ) {
94+ const auto release_result = IOPMAssertionRelease (wake_assertion);
95+ if (release_result != kIOReturnSuccess ) {
96+ BOOST_LOG (warning) << " Unable to release display wake assertion, IOReturn: " sv << release_result;
97+ }
98+ }
99+ }
100+ } // namespace
101+
28102 struct capture_callback_context_t {
29103 capture_callback_context_t (
30104 const display_t ::push_captured_image_cb_t &push_callback,
40114 display_t ::pull_free_image_cb_t pull_callback;
41115 };
42116
117+ struct dummy_capture_context_t {
118+ explicit dummy_capture_context_t (img_t *image):
119+ image {image} {
120+ }
121+
122+ std::mutex mutex;
123+ bool active {true };
124+ img_t *image;
125+ };
126+
43127 struct av_display_t : public display_t {
44128 AVVideo *av_capture {};
45129 CGDirectDisplayID display_id {};
130+ IOPMAssertionID display_sleep_assertion {kIOPMNullAssertionID };
46131
47132 ~av_display_t () override {
48133 [av_capture release ];
134+
135+ if (display_sleep_assertion != kIOPMNullAssertionID ) {
136+ const auto result = IOPMAssertionRelease (display_sleep_assertion);
137+ if (result != kIOReturnSuccess ) {
138+ BOOST_LOG (warning) << " Unable to release display sleep assertion, IOReturn: " sv << result;
139+ }
140+ }
141+ }
142+
143+ void prevent_display_sleep () {
144+ if (display_sleep_assertion != kIOPMNullAssertionID ) {
145+ return ;
146+ }
147+
148+ const auto result = IOPMAssertionCreateWithName (
149+ kIOPMAssertPreventUserIdleDisplaySleep ,
150+ kIOPMAssertionLevelOn ,
151+ CFSTR (" Sunshine display capture" ),
152+ &display_sleep_assertion
153+ );
154+
155+ if (result == kIOReturnSuccess ) {
156+ BOOST_LOG (debug) << " Keeping display awake for capture" sv;
157+ return ;
158+ }
159+
160+ display_sleep_assertion = kIOPMNullAssertionID ;
161+ BOOST_LOG (warning) << " Unable to create display sleep prevention assertion, IOReturn: " sv << result;
49162 }
50163
51164 void stop_capture () override {
@@ -151,26 +264,33 @@ int dummy_img(img_t *img) override {
151264 return 1 ;
152265 }
153266
267+ auto capture_context = std::make_shared<dummy_capture_context_t >(img);
154268 auto signal = [av_capture capture: ^(CMSampleBufferRef sampleBuffer) {
269+ std::lock_guard<std::mutex> lock {capture_context->mutex };
270+ if (!capture_context->active || !capture_context->image ) {
271+ return false ;
272+ }
273+
155274 auto new_sample_buffer = std::make_shared<av_sample_buf_t >(sampleBuffer);
156275 auto new_pixel_buffer = std::make_shared<av_pixel_buf_t >(new_sample_buffer->buf );
157276
158- auto av_img = (av_img_t *) img;
277+ auto image = capture_context->image ;
278+ auto av_img = (av_img_t *) image;
159279
160280 auto old_data_retainer = std::make_shared<temp_retain_av_img_t >(
161281 av_img->sample_buffer ,
162282 av_img->pixel_buffer ,
163- img ->data
283+ image ->data
164284 );
165285
166286 av_img->sample_buffer = new_sample_buffer;
167287 av_img->pixel_buffer = new_pixel_buffer;
168- img ->data = new_pixel_buffer->data ();
288+ image ->data = new_pixel_buffer->data ();
169289
170- img ->width = (int ) CVPixelBufferGetWidth (new_pixel_buffer->buf );
171- img ->height = (int ) CVPixelBufferGetHeight (new_pixel_buffer->buf );
172- img ->row_pitch = (int ) CVPixelBufferGetBytesPerRow (new_pixel_buffer->buf );
173- img ->pixel_pitch = img ->row_pitch / img ->width ;
290+ image ->width = (int ) CVPixelBufferGetWidth (new_pixel_buffer->buf );
291+ image ->height = (int ) CVPixelBufferGetHeight (new_pixel_buffer->buf );
292+ image ->row_pitch = (int ) CVPixelBufferGetBytesPerRow (new_pixel_buffer->buf );
293+ image ->pixel_pitch = image ->row_pitch / image ->width ;
174294
175295 old_data_retainer = nullptr ;
176296
@@ -182,7 +302,24 @@ int dummy_img(img_t *img) override {
182302 return 1 ;
183303 }
184304
185- dispatch_semaphore_wait (signal, DISPATCH_TIME_FOREVER );
305+ constexpr auto first_frame_timeout = 5s;
306+ const auto timeout = dispatch_time (
307+ DISPATCH_TIME_NOW ,
308+ std::chrono::duration_cast<std::chrono::nanoseconds>(first_frame_timeout).count ()
309+ );
310+ const bool timed_out = dispatch_semaphore_wait (signal, timeout) != 0 ;
311+
312+ {
313+ std::lock_guard<std::mutex> lock {capture_context->mutex };
314+ capture_context->active = false ;
315+ capture_context->image = nullptr ;
316+ }
317+
318+ if (timed_out) {
319+ BOOST_LOG (error) << " Timed out waiting for the first frame from macOS display capture" sv;
320+ [av_capture stopCapture ];
321+ return 1 ;
322+ }
186323
187324 return av_capture.captureFailed ? 1 : 0 ;
188325 }
@@ -242,6 +379,12 @@ bool get_hdr_metadata(SS_HDR_METADATA &metadata) override {
242379
243380 auto display = std::make_shared<av_display_t >();
244381
382+ // AVFoundation and ScreenCaptureKit do not deliver frames for sleeping
383+ // displays. Hold the display awake for this capture object's lifetime and
384+ // wake it before resolving the configured stable selector.
385+ display->prevent_display_sleep ();
386+ wake_display_for_capture (display_name);
387+
245388 // Print all displays available with it's name and id
246389 auto display_array = [AVVideo displayNames ];
247390 BOOST_LOG (info) << " Detecting displays" sv;
0 commit comments