-
-
Notifications
You must be signed in to change notification settings - Fork 2k
feat(macOS): libdispalydevice integration #5338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,15 +5,20 @@ | |||||||||||||||||
| // 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> | ||||||||||||||||||
| #include <display_device/file_settings_persistence.h> | ||||||||||||||||||
| #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,35 @@ | |||||||||||||||||
| return (int) result; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| bool is_unsigned_integer(std::string_view value) { | ||||||||||||||||||
|
Check failure on line 144 in src/display_device.cpp
|
||||||||||||||||||
| return !value.empty() && std::ranges::all_of(value, [](unsigned char character) { | ||||||||||||||||||
| return std::isdigit(character); | ||||||||||||||||||
| }); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| 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 +665,23 @@ | |||||||||||||||||
| .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() { | ||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wasn't this added for Windows as well? Also, found this which should maybe replaced with the new library code? Sunshine/src/platform/windows/display_base.cpp Lines 241 to 248 in 9f645a9
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that's what I meant in part when I said I could make a power management pass after the interface-factories. Technically it could have been a part of this PR, but I did not want it out of control, touching multiple OSes. A note regarding it is also in the body text of the PR: it's definitely not forgotten about, it just felt out of scope.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Obviously, I have a reading problem today. Thanks for the clarification!
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe just throw a couple TODO comments in? (Sonar will complain about the TODOs, but I'll bypass it) |
||||||||||||||||||
| #ifdef __APPLE__ | ||||||||||||||||||
| return std::make_unique<MacDisplayPower>(std::make_shared<MacApiLayer>()); | ||||||||||||||||||
| #else | ||||||||||||||||||
| return nullptr; | ||||||||||||||||||
| #endif | ||||||||||||||||||
|
|
@@ -745,16 +803,42 @@ | |||||||||||||||||
| 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) { | ||||||||||||||||||
| // Fallback to giving back the output name if the platform is not supported. | ||||||||||||||||||
| 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 +849,13 @@ | |||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| 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 +866,24 @@ | |||||||||||||||||
| 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 +931,21 @@ | |||||||||||||||||
| 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 | ||||||||||||||||||
|
|
||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.