mirror of
https://github.com/LizardByte/Sunshine.git
synced 2026-08-07 10:20:46 +00:00
feat(macOS): libdispalydevice integration (#5338)
Co-authored-by: Dave Lane <42013603+ReenigneArcher@users.noreply.github.com>
This commit is contained in:
+123
-7
@@ -5,6 +5,13 @@
|
||||
// header include
|
||||
#include "display_device.h"
|
||||
|
||||
// standard includes
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
#include <string_view>
|
||||
|
||||
// lib includes
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <display_device/audio_context_interface.h>
|
||||
@@ -12,8 +19,6 @@
|
||||
#include <display_device/json.h>
|
||||
#include <display_device/retry_scheduler.h>
|
||||
#include <display_device/settings_manager_interface.h>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
|
||||
// local includes
|
||||
#include "audio.h"
|
||||
@@ -27,6 +32,13 @@
|
||||
#include <display_device/windows/win_display_device.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <display_device/macos/display_power.h>
|
||||
#include <display_device/macos/mac_api_layer.h>
|
||||
#include <display_device/macos/mac_display_device.h>
|
||||
#include <display_device/macos/settings_manager.h>
|
||||
#endif
|
||||
|
||||
namespace display_device {
|
||||
namespace {
|
||||
constexpr std::chrono::milliseconds DEFAULT_RETRY_INTERVAL {5000};
|
||||
@@ -129,6 +141,37 @@ namespace display_device {
|
||||
return (int) result;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
bool is_unsigned_integer(std::string_view value) {
|
||||
return !value.empty() && std::ranges::all_of(value, [](unsigned char character) {
|
||||
return std::isdigit(character);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string_view apply_result_name(SettingsManagerInterface::ApplyResult result) {
|
||||
using enum SettingsManagerInterface::ApplyResult;
|
||||
|
||||
switch (result) {
|
||||
case Ok:
|
||||
return "Ok";
|
||||
case ApiTemporarilyUnavailable:
|
||||
return "ApiTemporarilyUnavailable";
|
||||
case DevicePrepFailed:
|
||||
return "DevicePrepFailed";
|
||||
case PrimaryDevicePrepFailed:
|
||||
return "PrimaryDevicePrepFailed";
|
||||
case DisplayModePrepFailed:
|
||||
return "DisplayModePrepFailed";
|
||||
case HdrStatePrepFailed:
|
||||
return "HdrStatePrepFailed";
|
||||
case PersistenceSaveFailed:
|
||||
return "PersistenceSaveFailed";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse resolution value from the string.
|
||||
* @param input String to be parsed.
|
||||
@@ -624,6 +667,23 @@ namespace display_device {
|
||||
.m_hdr_blank_delay = video_config.dd.wa.hdr_toggle_delay != std::chrono::milliseconds::zero() ? std::make_optional(video_config.dd.wa.hdr_toggle_delay) : std::nullopt
|
||||
}
|
||||
);
|
||||
#elif defined(__APPLE__)
|
||||
return std::make_unique<MacSettingsManager>(
|
||||
std::make_shared<MacDisplayDevice>(std::make_shared<MacApiLayer>()),
|
||||
std::make_shared<sunshine_audio_context_t>(),
|
||||
std::make_unique<MacPersistentState>(
|
||||
std::make_shared<FileSettingsPersistence>(persistence_filepath)
|
||||
),
|
||||
MacWorkarounds {}
|
||||
);
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<DisplayPowerInterface> make_display_power() {
|
||||
#ifdef __APPLE__
|
||||
return std::make_unique<MacDisplayPower>(std::make_shared<MacApiLayer>());
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
@@ -745,6 +805,24 @@ namespace display_device {
|
||||
return std::make_unique<deinit_t>();
|
||||
}
|
||||
|
||||
bool wake_display(const std::string &display_name, std::chrono::milliseconds timeout) {
|
||||
const auto display_power {make_display_power()};
|
||||
if (!display_power) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return display_power->wakeDisplay(display_name, timeout);
|
||||
}
|
||||
|
||||
std::unique_ptr<DisplayPowerGuardInterface> keep_display_awake(const std::string &reason) {
|
||||
const auto display_power {make_display_power()};
|
||||
if (!display_power) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return display_power->keepDisplayAwake(reason);
|
||||
}
|
||||
|
||||
std::string map_output_name(const std::string &output_name) {
|
||||
std::lock_guard lock {DD_DATA.mutex};
|
||||
if (!DD_DATA.sm_instance) {
|
||||
@@ -752,9 +830,17 @@ namespace display_device {
|
||||
return output_name;
|
||||
}
|
||||
|
||||
return DD_DATA.sm_instance->execute([&output_name](auto &settings_iface) {
|
||||
const auto mapped_name {DD_DATA.sm_instance->execute([&output_name](auto &settings_iface) {
|
||||
return settings_iface.getDisplayName(output_name);
|
||||
});
|
||||
})};
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (mapped_name.empty() && is_unsigned_integer(output_name)) {
|
||||
return output_name;
|
||||
}
|
||||
#endif
|
||||
|
||||
return mapped_name;
|
||||
}
|
||||
|
||||
void configure_display(const config::video_t &video_config, const rtsp_stream::launch_session_t &session) {
|
||||
@@ -765,11 +851,13 @@ namespace display_device {
|
||||
}
|
||||
|
||||
if (const auto *disabled {std::get_if<configuration_disabled_tag_t>(&result)}; disabled) {
|
||||
BOOST_LOG(info) << "Display device configuration is disabled. Reverting any active display device configuration.";
|
||||
revert_configuration();
|
||||
return;
|
||||
}
|
||||
|
||||
// Error already logged for failed_to_parse_tag_t case, and we also don't
|
||||
BOOST_LOG(error) << "Failed to parse display device configuration. Display settings will not be changed.";
|
||||
// Error details should already be logged for failed_to_parse_tag_t case, and we also don't
|
||||
// want to revert active configuration in case we have any
|
||||
}
|
||||
|
||||
@@ -780,10 +868,24 @@ namespace display_device {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG(info) << "Scheduling display device configuration:\n"
|
||||
<< toJson(config);
|
||||
|
||||
DD_DATA.sm_instance->schedule([config](auto &settings_iface, auto &stop_token) {
|
||||
using enum SettingsManagerInterface::ApplyResult;
|
||||
|
||||
// We only want to keep retrying in case of a transient errors.
|
||||
// In other cases, when we either fail or succeed we just want to stop...
|
||||
if (settings_iface.applySettings(config) != SettingsManagerInterface::ApplyResult::ApiTemporarilyUnavailable) {
|
||||
const auto result {settings_iface.applySettings(config)};
|
||||
if (result == Ok) {
|
||||
BOOST_LOG(info) << "Display device configuration applied successfully.";
|
||||
} else if (result == ApiTemporarilyUnavailable) {
|
||||
BOOST_LOG(warning) << "Display device configuration API is temporarily unavailable. Will retry.";
|
||||
} else {
|
||||
BOOST_LOG(error) << "Display device configuration failed with result: " << apply_result_name(result);
|
||||
}
|
||||
|
||||
if (result != ApiTemporarilyUnavailable) {
|
||||
stop_token.requestStop();
|
||||
}
|
||||
},
|
||||
@@ -831,7 +933,21 @@ namespace display_device {
|
||||
SingleDisplayConfiguration config;
|
||||
config.m_device_id = video_config.output_name;
|
||||
config.m_device_prep = *device_prep;
|
||||
config.m_hdr_state = parse_hdr_option(video_config, session);
|
||||
|
||||
const auto hdr_state {parse_hdr_option(video_config, session)};
|
||||
#ifdef __APPLE__
|
||||
if (hdr_state) {
|
||||
BOOST_LOG(info) << "Ignoring HDR display device request on macOS because macOS HDR changes are not supported by libdisplaydevice.";
|
||||
}
|
||||
#else
|
||||
config.m_hdr_state = hdr_state;
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (config.m_device_prep != SingleDisplayConfiguration::DevicePreparation::VerifyOnly) {
|
||||
BOOST_LOG(warning) << "macOS libdisplaydevice currently supports only VerifyOnly display preparation. The requested preparation mode will fail.";
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!parse_resolution_option(video_config, session, config)) {
|
||||
// Error already logged
|
||||
|
||||
@@ -5,10 +5,13 @@
|
||||
#pragma once
|
||||
|
||||
// standard includes
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
// lib includes
|
||||
#include <display_device/display_power_interface.h>
|
||||
#include <display_device/types.h>
|
||||
|
||||
// forward declarations
|
||||
@@ -50,6 +53,21 @@ namespace display_device {
|
||||
*/
|
||||
[[nodiscard]] std::string map_output_name(const std::string &output_name);
|
||||
|
||||
/**
|
||||
* @brief Ask the platform to wake displays before detection or capture.
|
||||
* @param display_name Platform capture selector.
|
||||
* @param timeout Maximum time to wait for platform-specific wake detection.
|
||||
* @returns True if the display was already available or the wake request succeeded.
|
||||
*/
|
||||
[[nodiscard]] bool wake_display(const std::string &display_name, std::chrono::milliseconds timeout);
|
||||
|
||||
/**
|
||||
* @brief Keep displays awake until the returned guard is destroyed.
|
||||
* @param reason Short human-readable reason for the power assertion.
|
||||
* @returns A guard owning the platform assertion, or nullptr if unsupported or unavailable.
|
||||
*/
|
||||
[[nodiscard]] std::unique_ptr<DisplayPowerGuardInterface> keep_display_awake(const std::string &reason);
|
||||
|
||||
/**
|
||||
* @brief Configure the display device based on the user configuration and the session information.
|
||||
* @note This is a convenience method for calling similar method of a different signature.
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
// platform includes
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
/**
|
||||
* @brief macOS capture session and video output handles.
|
||||
@@ -16,8 +16,6 @@ struct CaptureSession {
|
||||
NSCondition *captureStopped; ///< Capture stopped.
|
||||
};
|
||||
|
||||
static const int kMaxDisplays = 32;
|
||||
|
||||
/**
|
||||
* @brief AVFoundation video capture controller used by the macOS backend.
|
||||
*/
|
||||
@@ -66,20 +64,6 @@ typedef bool (^FrameCallbackBlock)(CMSampleBufferRef);
|
||||
*/
|
||||
@property (nonatomic, assign) NSMapTable<AVCaptureConnection *, dispatch_semaphore_t> *captureSignals;
|
||||
|
||||
/**
|
||||
* @brief List display names accepted by the selected capture backend.
|
||||
*
|
||||
* @return Display names accepted by the selected capture backend.
|
||||
*/
|
||||
+ (NSArray<NSDictionary *> *)displayNames;
|
||||
/**
|
||||
* @brief Return the user-visible name for a CoreGraphics display.
|
||||
*
|
||||
* @param displayID Display ID.
|
||||
* @return Display name for the supplied CoreGraphics display ID.
|
||||
*/
|
||||
+ (NSString *)getDisplayName:(CGDirectDisplayID)displayID;
|
||||
|
||||
/**
|
||||
* @brief Initialize AVFoundation capture for a display and frame rate.
|
||||
*
|
||||
|
||||
@@ -7,44 +7,14 @@
|
||||
|
||||
@implementation AVVideo
|
||||
|
||||
// XXX: Currently, this function only returns the screen IDs as names,
|
||||
// which is not very helpful to the user. The API to retrieve names
|
||||
// was deprecated with 10.9+.
|
||||
// However, there is a solution with little external code that can be used:
|
||||
// https://stackoverflow.com/questions/20025868/cgdisplayioserviceport-is-deprecated-in-os-x-10-9-how-to-replace
|
||||
+ (NSArray<NSDictionary *> *)displayNames {
|
||||
CGDirectDisplayID displays[kMaxDisplays];
|
||||
uint32_t count;
|
||||
if (CGGetActiveDisplayList(kMaxDisplays, displays, &count) != kCGErrorSuccess) {
|
||||
return [NSArray array];
|
||||
}
|
||||
|
||||
NSMutableArray *result = [NSMutableArray array];
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
[result addObject:@{
|
||||
@"id": [NSNumber numberWithUnsignedInt:displays[i]],
|
||||
@"name": [NSString stringWithFormat:@"%d", displays[i]],
|
||||
@"displayName": [self getDisplayName:displays[i]],
|
||||
}];
|
||||
}
|
||||
|
||||
return [NSArray arrayWithArray:result];
|
||||
}
|
||||
|
||||
+ (NSString *)getDisplayName:(CGDirectDisplayID)displayID {
|
||||
for (NSScreen *screen in [NSScreen screens]) {
|
||||
if ([screen.deviceDescription[@"NSScreenNumber"] isEqualToNumber:[NSNumber numberWithUnsignedInt:displayID]]) {
|
||||
return screen.localizedName;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)initWithDisplay:(CGDirectDisplayID)displayID frameRate:(int)frameRate {
|
||||
self = [super init];
|
||||
|
||||
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displayID);
|
||||
if (!mode) {
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.displayID = displayID;
|
||||
self.pixelFormat = kCVPixelFormatType_32BGRA;
|
||||
|
||||
+62
-240
@@ -4,15 +4,14 @@
|
||||
*/
|
||||
|
||||
// standard includes
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
// platform includes
|
||||
#include <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#include <charconv>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
// local includes
|
||||
#include "src/config.h"
|
||||
#include "src/display_device.h"
|
||||
#include "src/logging.h"
|
||||
#include "src/platform/common.h"
|
||||
#include "src/platform/macos/av_img_t.h"
|
||||
@@ -29,188 +28,29 @@
|
||||
#include "src/video.h"
|
||||
#undef AVMediaType
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::optional<CGDirectDisplayID> parse_display_id(std::string_view display_name) {
|
||||
if (display_name.empty()) {
|
||||
return display_count > 0;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
char *end = nullptr;
|
||||
const auto selected_display_id = std::strtoul(display_name.c_str(), &end, 10);
|
||||
if (!end || *end != '\0') {
|
||||
return display_count > 0;
|
||||
CGDirectDisplayID display_id {};
|
||||
const auto *const begin {display_name.data()};
|
||||
const auto *const end {display_name.data() + display_name.size()};
|
||||
const auto [ptr, ec] {std::from_chars(begin, end, display_id)};
|
||||
if (ec != std::errc {} || ptr != end) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < display_count; ++i) {
|
||||
if (displays[i] == selected_display_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return display_id;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
OSType videotoolbox_pixel_format(const video::config_t &config) {
|
||||
const auto colorspace {video::colorspace_from_client_config(config, false)};
|
||||
return colorspace.bit_depth == 10 ? kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange : kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -220,41 +60,10 @@ namespace platf {
|
||||
struct av_display_t: public display_t {
|
||||
AVVideo *av_capture {}; ///< AV capture.
|
||||
CGDirectDisplayID display_id {}; ///< Display ID.
|
||||
IOPMAssertionID display_sleep_assertion {kIOPMNullAssertionID}; ///< Display sleep assertion.
|
||||
std::unique_ptr<display_device::DisplayPowerGuardInterface> display_power_guard; ///< Display power guard.
|
||||
|
||||
~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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prevent macOS from sleeping the captured display while streaming.
|
||||
*/
|
||||
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 {
|
||||
@@ -412,37 +221,41 @@ namespace platf {
|
||||
}
|
||||
|
||||
auto display = std::make_shared<av_display_t>();
|
||||
display->prevent_display_sleep();
|
||||
wake_displays_for_detection(display_name);
|
||||
|
||||
BOOST_LOG(debug) << "Waking display for capture selector ["sv << display_name << ']';
|
||||
if (!display_device::wake_display(display_name, 1s)) {
|
||||
BOOST_LOG(debug) << "Display wake attempt did not expose the requested display ["sv << display_name << ']';
|
||||
}
|
||||
|
||||
display->display_power_guard = display_device::keep_display_awake("Sunshine display capture");
|
||||
if (display->display_power_guard) {
|
||||
BOOST_LOG(debug) << "Keeping display awake for capture"sv;
|
||||
} else {
|
||||
BOOST_LOG(debug) << "Unable to create display sleep prevention assertion"sv;
|
||||
}
|
||||
|
||||
// Default to main display
|
||||
display->display_id = CGMainDisplayID();
|
||||
|
||||
// Print all displays available with it's name and id
|
||||
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
|
||||
NSString *name = item[@"displayName"];
|
||||
// We are using CGGetActiveDisplayList that only returns active displays so hardcoded connected value in log to true
|
||||
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) {
|
||||
if (const auto configured_display_id {parse_display_id(display_name)}) {
|
||||
display->display_id = *configured_display_id;
|
||||
} else if (!display_name.empty()) {
|
||||
BOOST_LOG(warning) << "Configured display ["sv << display_name
|
||||
<< "] was not found in the active display list. Falling back to main display ["sv
|
||||
<< "] is not a valid macOS capture display id. Falling back to main display ["sv
|
||||
<< display->display_id << "]."sv;
|
||||
}
|
||||
|
||||
log_display_diagnostic(display->display_id, "selected for AVFoundation capture");
|
||||
// Print all displays available with their names and ids
|
||||
BOOST_LOG(debug) << "Detecting displays"sv;
|
||||
for (const auto &device : display_device::enumerate_devices()) {
|
||||
if (device.m_display_name.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BOOST_LOG(debug) << "Detected display: "sv << device.m_friendly_name
|
||||
<< " (id: "sv << device.m_display_name << ") connected: true"sv;
|
||||
}
|
||||
|
||||
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];
|
||||
@@ -458,19 +271,28 @@ namespace platf {
|
||||
display->env_width = display->width;
|
||||
display->env_height = display->height;
|
||||
|
||||
if (hwdevice_type == platf::mem_type_e::videotoolbox) {
|
||||
const auto pixel_format {videotoolbox_pixel_format(config)};
|
||||
[display->av_capture setFrameWidth:config.width frameHeight:config.height];
|
||||
display->av_capture.pixelFormat = pixel_format;
|
||||
}
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
std::vector<std::string> display_names(mem_type_e hwdevice_type) {
|
||||
__block std::vector<std::string> display_names;
|
||||
std::vector<std::string> display_names;
|
||||
if (hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::videotoolbox) {
|
||||
return display_names;
|
||||
}
|
||||
|
||||
auto display_array = [AVVideo displayNames];
|
||||
|
||||
display_names.reserve([display_array count]);
|
||||
[display_array enumerateObjectsUsingBlock:^(NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
|
||||
NSString *name = obj[@"name"];
|
||||
display_names.emplace_back(name.UTF8String);
|
||||
}];
|
||||
const auto devices {display_device::enumerate_devices()};
|
||||
display_names.reserve(devices.size());
|
||||
for (const auto &device : devices) {
|
||||
if (!device.m_display_name.empty()) {
|
||||
display_names.emplace_back(device.m_display_name);
|
||||
}
|
||||
}
|
||||
|
||||
return display_names;
|
||||
}
|
||||
|
||||
@@ -102,8 +102,13 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
testing::Values(
|
||||
std::make_pair(std::make_pair(hdr_option_e::disabled, client_wants_hdr_t {true}), std::nullopt),
|
||||
std::make_pair(std::make_pair(hdr_option_e::disabled, client_wants_hdr_t {false}), std::nullopt),
|
||||
#ifdef __APPLE__
|
||||
std::make_pair(std::make_pair(hdr_option_e::automatic, client_wants_hdr_t {true}), std::nullopt),
|
||||
std::make_pair(std::make_pair(hdr_option_e::automatic, client_wants_hdr_t {false}), std::nullopt)
|
||||
#else
|
||||
std::make_pair(std::make_pair(hdr_option_e::automatic, client_wants_hdr_t {true}), hdr_state_e::Enabled),
|
||||
std::make_pair(std::make_pair(hdr_option_e::automatic, client_wants_hdr_t {false}), hdr_state_e::Disabled)
|
||||
#endif
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
Vendored
+1
-1
Submodule third-party/libdisplaydevice updated: 1b24e6ad91...6e9722f891
Reference in New Issue
Block a user