fix(macos): wake sleeping display at the beginning of capture session (#5291)

This commit is contained in:
martona
2026-06-17 11:28:07 -04:00
committed by GitHub
parent a103db8590
commit c9863ebee9
3 changed files with 235 additions and 1 deletions
+1
View File
@@ -35,6 +35,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${CORE_MEDIA_LIBRARY}
${CORE_VIDEO_LIBRARY}
${FOUNDATION_LIBRARY}
${IOKIT_LIBRARY}
${VIDEO_TOOLBOX_LIBRARY})
set(APPLE_PLIST_TEMPLATE "${SUNSHINE_SOURCE_ASSETS_DIR}/macos/build/Info.plist.in")
+1
View File
@@ -9,6 +9,7 @@ FIND_LIBRARY(CORE_AUDIO_LIBRARY CoreAudio)
FIND_LIBRARY(CORE_MEDIA_LIBRARY CoreMedia)
FIND_LIBRARY(CORE_VIDEO_LIBRARY CoreVideo)
FIND_LIBRARY(FOUNDATION_LIBRARY Foundation)
FIND_LIBRARY(IOKIT_LIBRARY IOKit)
FIND_LIBRARY(VIDEO_TOOLBOX_LIBRARY VideoToolbox)
if(SUNSHINE_ENABLE_TRAY)
+233 -1
View File
@@ -2,6 +2,15 @@
* @file src/platform/macos/display.mm
* @brief Definitions for display capture on macOS.
*/
// standard includes
#include <cstdlib>
#include <sstream>
#include <thread>
// platform includes
#include <IOKit/pwr_mgt/IOPMLib.h>
// local includes
#include "src/config.h"
#include "src/logging.h"
@@ -21,12 +30,221 @@ namespace fs = std::filesystem;
namespace platf {
using namespace std::literals;
namespace {
const char *cg_error_name(CGError error) {
switch (error) {
case kCGErrorSuccess:
return "success";
case kCGErrorFailure:
return "failure";
case kCGErrorIllegalArgument:
return "illegal argument";
case kCGErrorInvalidConnection:
return "invalid connection";
case kCGErrorInvalidContext:
return "invalid context";
case kCGErrorCannotComplete:
return "cannot complete";
case kCGErrorNotImplemented:
return "not implemented";
case kCGErrorRangeCheck:
return "range check";
case kCGErrorTypeCheck:
return "type check";
case kCGErrorInvalidOperation:
return "invalid operation";
case kCGErrorNoneAvailable:
return "none available";
default:
return "unknown";
}
}
std::string format_rect(CGRect rect) {
std::ostringstream formatted;
formatted << '(' << rect.origin.x << ',' << rect.origin.y << ") "
<< rect.size.width << 'x' << rect.size.height;
return formatted.str();
}
std::string display_mode_summary(CGDisplayModeRef mode) {
if (!mode) {
return "<none>";
}
std::ostringstream formatted;
formatted << CGDisplayModeGetWidth(mode) << 'x' << CGDisplayModeGetHeight(mode)
<< " points, " << CGDisplayModeGetPixelWidth(mode) << 'x'
<< CGDisplayModeGetPixelHeight(mode) << " pixels";
const auto refresh_rate = CGDisplayModeGetRefreshRate(mode);
if (refresh_rate > 0) {
formatted << ", " << refresh_rate << " Hz";
}
return formatted.str();
}
void log_display_diagnostic(CGDirectDisplayID display_id, const char *source) {
NSString *display_name = [AVVideo getDisplayName:display_id];
const char *display_name_utf8 = display_name ? display_name.UTF8String : "<unknown>";
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display_id);
BOOST_LOG(info) << "Display diagnostic ["sv << source << "]: id: "sv << display_id
<< ", name: "sv << display_name_utf8
<< ", main: "sv << (display_id == CGMainDisplayID())
<< ", active: "sv << CGDisplayIsActive(display_id)
<< ", online: "sv << CGDisplayIsOnline(display_id)
<< ", asleep: "sv << CGDisplayIsAsleep(display_id)
<< ", built-in: "sv << CGDisplayIsBuiltin(display_id)
<< ", bounds: "sv << format_rect(CGDisplayBounds(display_id))
<< ", framebuffer pixels: "sv << CGDisplayPixelsWide(display_id) << 'x' << CGDisplayPixelsHigh(display_id)
<< ", mode: "sv << display_mode_summary(mode);
if (mode) {
CFRelease(mode);
}
}
void log_nsscreen_diagnostics() {
NSArray<NSScreen *> *screens = [NSScreen screens];
BOOST_LOG(info) << "NSScreen diagnostics: count: "sv << [screens count];
for (NSScreen *screen in screens) {
NSNumber *display_id = screen.deviceDescription[@"NSScreenNumber"];
NSString *screen_name = screen.localizedName;
BOOST_LOG(info) << "NSScreen diagnostic: id: "sv << (display_id ? [display_id unsignedIntValue] : 0)
<< ", name: "sv << (screen_name ? screen_name.UTF8String : "<unknown>")
<< ", frame: "sv << format_rect(screen.frame)
<< ", backing scale: "sv << screen.backingScaleFactor;
}
}
void log_display_list_diagnostics(const char *list_name, CGError error, const CGDirectDisplayID *displays, uint32_t count) {
BOOST_LOG(info) << list_name << ": status: "sv << cg_error_name(error)
<< " ("sv << error << "), count: "sv << count;
if (error != kCGErrorSuccess) {
return;
}
for (uint32_t i = 0; i < count; ++i) {
log_display_diagnostic(displays[i], list_name);
}
}
void log_display_environment_diagnostics() {
CGDirectDisplayID active_displays[kMaxDisplays];
CGDirectDisplayID online_displays[kMaxDisplays];
uint32_t active_display_count = 0;
uint32_t online_display_count = 0;
const auto active_error = CGGetActiveDisplayList(kMaxDisplays, active_displays, &active_display_count);
const auto online_error = CGGetOnlineDisplayList(kMaxDisplays, online_displays, &online_display_count);
BOOST_LOG(info) << "Main display diagnostic: id: "sv << CGMainDisplayID();
log_display_list_diagnostics("CGGetActiveDisplayList", active_error, active_displays, active_display_count);
log_display_list_diagnostics("CGGetOnlineDisplayList", online_error, online_displays, online_display_count);
log_nsscreen_diagnostics();
}
bool has_required_active_display(const std::string &display_name) {
CGDirectDisplayID displays[kMaxDisplays];
uint32_t display_count = 0;
if (CGGetActiveDisplayList(kMaxDisplays, displays, &display_count) != kCGErrorSuccess) {
return false;
}
if (display_name.empty()) {
return display_count > 0;
}
char *end = nullptr;
const auto selected_display_id = std::strtoul(display_name.c_str(), &end, 10);
if (!end || *end != '\0') {
return display_count > 0;
}
for (uint32_t i = 0; i < display_count; ++i) {
if (displays[i] == selected_display_id) {
return true;
}
}
return false;
}
void wake_displays_for_detection(const std::string &display_name) {
IOPMAssertionID wake_assertion = kIOPMNullAssertionID;
const auto result = IOPMAssertionDeclareUserActivity(
CFSTR("Sunshine display detection"),
kIOPMUserActiveRemote,
&wake_assertion
);
if (result != kIOReturnSuccess) {
BOOST_LOG(warning) << "Unable to declare remote user activity to wake displays, IOReturn: "sv << result;
return;
}
BOOST_LOG(info) << "Declared remote user activity to wake displays, assertion id: "sv << wake_assertion;
for (int attempt = 0; attempt < 10 && !has_required_active_display(display_name); ++attempt) {
std::this_thread::sleep_for(100ms);
}
if (!has_required_active_display(display_name)) {
BOOST_LOG(warning) << "Display wake attempt did not expose the requested display ["sv
<< display_name << "] in the active display list."sv;
}
if (wake_assertion != kIOPMNullAssertionID) {
const auto release_result = IOPMAssertionRelease(wake_assertion);
if (release_result != kIOReturnSuccess) {
BOOST_LOG(warning) << "Unable to release display wake assertion, IOReturn: "sv << release_result;
}
}
}
} // namespace
struct av_display_t: public display_t {
AVVideo *av_capture {};
CGDirectDisplayID display_id {};
IOPMAssertionID display_sleep_assertion {kIOPMNullAssertionID};
~av_display_t() override {
[av_capture release];
if (display_sleep_assertion != kIOPMNullAssertionID) {
const auto result = IOPMAssertionRelease(display_sleep_assertion);
if (result != kIOReturnSuccess) {
BOOST_LOG(warning) << "Unable to release display sleep assertion, IOReturn: "sv << result;
}
}
}
void prevent_display_sleep() {
if (display_sleep_assertion != kIOPMNullAssertionID) {
return;
}
const auto result = IOPMAssertionCreateWithName(
kIOPMAssertPreventUserIdleDisplaySleep,
kIOPMAssertionLevelOn,
CFSTR("Sunshine display capture"),
&display_sleep_assertion
);
if (result == kIOReturnSuccess) {
BOOST_LOG(info) << "Created display sleep prevention assertion, assertion id: "sv << display_sleep_assertion;
return;
}
display_sleep_assertion = kIOPMNullAssertionID;
BOOST_LOG(warning) << "Unable to create display sleep prevention assertion, IOReturn: "sv << result;
}
capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
@@ -158,13 +376,18 @@ namespace platf {
}
auto display = std::make_shared<av_display_t>();
display->prevent_display_sleep();
wake_displays_for_detection(display_name);
// Default to main display
display->display_id = CGMainDisplayID();
// Print all displays available with it's name and id
auto display_array = [AVVideo displayNames];
BOOST_LOG(info) << "Detecting displays"sv;
log_display_environment_diagnostics();
auto display_array = [AVVideo displayNames];
bool matched_configured_display = display_name.empty();
for (NSDictionary *item in display_array) {
NSNumber *display_id = item[@"id"];
// We need show display's product name and corresponding display number given by user
@@ -173,8 +396,17 @@ namespace platf {
BOOST_LOG(info) << "Detected display: "sv << name.UTF8String << " (id: "sv << [NSString stringWithFormat:@"%@", display_id].UTF8String << ") connected: true"sv;
if (!display_name.empty() && std::atoi(display_name.c_str()) == [display_id unsignedIntValue]) {
display->display_id = [display_id unsignedIntValue];
matched_configured_display = true;
}
}
if (!matched_configured_display) {
BOOST_LOG(warning) << "Configured display ["sv << display_name
<< "] was not found in the active display list. Falling back to main display ["sv
<< display->display_id << "]."sv;
}
log_display_diagnostic(display->display_id, "selected for AVFoundation capture");
BOOST_LOG(info) << "Configuring selected display ("sv << display->display_id << ") to stream"sv;
display->av_capture = [[AVVideo alloc] initWithDisplay:display->display_id frameRate:config.framerate];