mirror of
https://github.com/LizardByte/Sunshine.git
synced 2026-08-07 10:20:46 +00:00
fix(macos): wake sleeping display at the beginning of capture session (#5291)
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user