Skip to content

Commit

Permalink
feat: Add libdisplaydevice dependency and output name mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
FrogTheFrog committed Nov 16, 2024
1 parent d5854ae commit fbb45a8
Show file tree
Hide file tree
Showing 18 changed files with 309 additions and 101 deletions.
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
path = third-party/inputtino
url = https://github.com/games-on-whales/inputtino.git
branch = stable
[submodule "third-party/libdisplaydevice"]
path = third-party/libdisplaydevice
url = https://github.com/LizardByte/libdisplaydevice.git
branch = master
[submodule "third-party/moonlight-common-c"]
path = third-party/moonlight-common-c
url = https://github.com/moonlight-stream/moonlight-common-c.git
Expand Down
3 changes: 3 additions & 0 deletions cmake/compile_definitions/common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ set(SUNSHINE_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/uuid.h"
"${CMAKE_SOURCE_DIR}/src/config.h"
"${CMAKE_SOURCE_DIR}/src/config.cpp"
"${CMAKE_SOURCE_DIR}/src/display_device.h"
"${CMAKE_SOURCE_DIR}/src/display_device.cpp"
"${CMAKE_SOURCE_DIR}/src/entry_handler.cpp"
"${CMAKE_SOURCE_DIR}/src/entry_handler.h"
"${CMAKE_SOURCE_DIR}/src/file_handler.cpp"
Expand Down Expand Up @@ -146,6 +148,7 @@ list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${MINIUPNP_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
enet
libdisplaydevice::display_device
opus
${FFMPEG_LIBRARIES}
${Boost_LIBRARIES}
Expand Down
3 changes: 3 additions & 0 deletions cmake/dependencies/common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/moonlight-common-c/enet")
# web server
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/Simple-Web-Server")

# libdisplaydevice
add_subdirectory("${CMAKE_SOURCE_DIR}/third-party/libdisplaydevice")

# common dependencies
find_package(OpenSSL REQUIRED)
find_package(PkgConfig REQUIRED)
Expand Down
52 changes: 49 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -865,10 +865,56 @@ editing the `conf` file in a text editor. Use the examples as reference.
<br>
**Windows:**
<br>
Enter the following command in command prompt or PowerShell.
During Sunshine startup, you should see the list of detected displays:
@code{}
%ProgramFiles%\Sunshine\tools\dxgi-info.exe
Info: Currently available display devices:
[
{
"device_id": "{64243705-4020-5895-b923-adc862c3457e}",
"display_name": "",
"friendly_name": "IDD HDR",
"info": null
},
{
"device_id": "{77f67f3e-754f-5d31-af64-ee037e18100a}",
"display_name": "",
"friendly_name": "SunshineHDR",
"info": null
},
{
"device_id": "{daeac860-f4db-5208-b1f5-cf59444fb768}",
"display_name": "\\\\.\\DISPLAY1",
"friendly_name": "ROG PG279Q",
"info": {
"hdr_state": null,
"origin_point": {
"x": 0,
"y": 0
},
"primary": true,
"refresh_rate": {
"type": "rational",
"value": {
"denominator": 1000,
"numerator": 119998
}
},
"resolution": {
"height": 1440,
"width": 2560
},
"resolution_scale": {
"type": "rational",
"value": {
"denominator": 100,
"numerator": 100
}
}
}
}
]
@endcode
You need to use the `device_id` value.
}
</td>
</tr>
Expand All @@ -891,7 +937,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
<tr>
<td>Example (Windows)</td>
<td colspan="2">@code{}
output_name = \\.\DISPLAY1
output_name = {daeac860-f4db-5208-b1f5-cf59444fb768}
@endcode</td>
</tr>
</table>
Expand Down
85 changes: 85 additions & 0 deletions src/display_device.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* @file src/display_device.cpp
* @brief Definitions for display device handling.
*/
// header include
#include "display_device.h"

// lib includes
#include <display_device/json.h>
#include <display_device/retry_scheduler.h>
#include <display_device/settings_manager_interface.h>

// local includes
#include "platform/common.h"

// platform-specific includes
#ifdef _WIN32
#include <display_device/windows/settings_manager.h>
#include <display_device/windows/win_api_layer.h>
#include <display_device/windows/win_display_device.h>
#endif

namespace display_device {
namespace {
/**
* @brief A global for the settings manager interface whose lifetime is managed by `display_device::init()`.
*/
std::unique_ptr<RetryScheduler<SettingsManagerInterface>> SM_INSTANCE;

/**
* @brief Construct a settings manager interface to manage display device settings.
* @return An interface or nullptr if the OS does not support the interface.
*/
std::unique_ptr<SettingsManagerInterface>
make_settings_manager() {

Check warning on line 35 in src/display_device.cpp

View check run for this annotation

Codecov / codecov/patch

src/display_device.cpp#L35

Added line #L35 was not covered by tests
#ifdef _WIN32
// TODO: In the upcoming PR, add audio context capture and settings persistence
return std::make_unique<SettingsManager>(
std::make_shared<WinDisplayDevice>(std::make_shared<WinApiLayer>()),
nullptr,
std::make_unique<PersistentState>(nullptr),
WinWorkarounds {});

Check warning on line 42 in src/display_device.cpp

View check run for this annotation

Codecov / codecov/patch

src/display_device.cpp#L41-L42

Added lines #L41 - L42 were not covered by tests
#else
return nullptr;

Check warning on line 44 in src/display_device.cpp

View check run for this annotation

Codecov / codecov/patch

src/display_device.cpp#L44

Added line #L44 was not covered by tests
#endif
}
} // namespace

std::unique_ptr<platf::deinit_t>
init() {

Check warning on line 50 in src/display_device.cpp

View check run for this annotation

Codecov / codecov/patch

src/display_device.cpp#L50

Added line #L50 was not covered by tests
// We can support re-init without any issues, however we should make sure to cleanup first!
SM_INSTANCE = nullptr;

// If we fail to create settings manager, this means platform is not supported and
// we will need to provided error-free passtrough in other methods
if (auto settings_manager { make_settings_manager() }) {
SM_INSTANCE = std::make_unique<RetryScheduler<SettingsManagerInterface>>(std::move(settings_manager));

const auto available_devices { SM_INSTANCE->execute([](auto &settings_iface) { return settings_iface.enumAvailableDevices(); }) };
BOOST_LOG(info) << "Currently available display devices:\n"
<< toJson(available_devices);

// TODO: In the upcoming PR, schedule recovery here
}

class deinit_t: public platf::deinit_t {

Check warning on line 66 in src/display_device.cpp

View check run for this annotation

Codecov / codecov/patch

src/display_device.cpp#L66

Added line #L66 was not covered by tests
public:
~deinit_t() override {

Check warning on line 68 in src/display_device.cpp

View check run for this annotation

Codecov / codecov/patch

src/display_device.cpp#L68

Added line #L68 was not covered by tests
// TODO: In the upcoming PR, execute recovery once here
SM_INSTANCE = nullptr;
}
};
return std::make_unique<deinit_t>();

Check warning on line 73 in src/display_device.cpp

View check run for this annotation

Codecov / codecov/patch

src/display_device.cpp#L73

Added line #L73 was not covered by tests
}

std::string
map_output_name(const std::string &output_name) {
if (!SM_INSTANCE) {
// Fallback to giving back the output name if the platform is not supported.
return output_name;
}

return SM_INSTANCE->execute([&output_name](auto &settings_iface) { return settings_iface.getDisplayName(output_name); });
}
} // namespace display_device
39 changes: 39 additions & 0 deletions src/display_device.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @file src/display_device.h
* @brief Declarations for display device handling.
*/
#pragma once

// lib includes
#include <memory>

// forward declarations
namespace platf {
class deinit_t;
} // namespace platf

namespace display_device {
/**
* @brief Initialize the implementation and perform the initial state recovery (if needed).
* @returns A deinit_t instance that performs cleanup when destroyed.
*
* @examples
* const auto init_guard { display_device::init() };
* @examples_end
*/
std::unique_ptr<platf::deinit_t>
init();

/**
* @brief Map the output name to a specific display.
* @param output_name The user-configurable output name.
* @returns Mapped display name or empty string if the output name could not be mapped.
*
* @examples
* const auto mapped_name_config { map_output_name(config::video.output_name) };
* const auto mapped_name_custom { map_output_name("{some-device-id}") };
* @examples_end
*/
std::string
map_output_name(const std::string &output_name);
} // namespace display_device
33 changes: 33 additions & 0 deletions src/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <boost/log/expressions.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <display_device/logging.h>

// local includes
#include "logging.h"
Expand Down Expand Up @@ -106,6 +107,7 @@ namespace logging {
}

setup_av_logging(min_log_level);
setup_libdisplaydevice_logging(min_log_level);

sink = boost::make_shared<text_sink>();

Expand Down Expand Up @@ -159,6 +161,37 @@ namespace logging {
});
}

void
setup_libdisplaydevice_logging(int min_log_level) {
constexpr int min_level { static_cast<int>(display_device::Logger::LogLevel::verbose) };
constexpr int max_level { static_cast<int>(display_device::Logger::LogLevel::fatal) };
const auto log_level { static_cast<display_device::Logger::LogLevel>(std::min(std::max(min_level, min_log_level), max_level)) };

display_device::Logger::get().setLogLevel(log_level);
display_device::Logger::get().setCustomCallback([](const display_device::Logger::LogLevel level, const std::string &message) {
switch (level) {
case display_device::Logger::LogLevel::verbose:

Check warning on line 173 in src/logging.cpp

View check run for this annotation

Codecov / codecov/patch

src/logging.cpp#L173

Added line #L173 was not covered by tests
BOOST_LOG(verbose) << message;
break;
case display_device::Logger::LogLevel::debug:

Check warning on line 176 in src/logging.cpp

View check run for this annotation

Codecov / codecov/patch

src/logging.cpp#L175-L176

Added lines #L175 - L176 were not covered by tests
BOOST_LOG(debug) << message;
break;
case display_device::Logger::LogLevel::info:

Check warning on line 179 in src/logging.cpp

View check run for this annotation

Codecov / codecov/patch

src/logging.cpp#L178-L179

Added lines #L178 - L179 were not covered by tests
BOOST_LOG(info) << message;
break;
case display_device::Logger::LogLevel::warning:

Check warning on line 182 in src/logging.cpp

View check run for this annotation

Codecov / codecov/patch

src/logging.cpp#L181-L182

Added lines #L181 - L182 were not covered by tests
BOOST_LOG(warning) << message;
break;
case display_device::Logger::LogLevel::error:

Check warning on line 185 in src/logging.cpp

View check run for this annotation

Codecov / codecov/patch

src/logging.cpp#L184-L185

Added lines #L184 - L185 were not covered by tests
BOOST_LOG(error) << message;
break;
case display_device::Logger::LogLevel::fatal:

Check warning on line 188 in src/logging.cpp

View check run for this annotation

Codecov / codecov/patch

src/logging.cpp#L187-L188

Added lines #L187 - L188 were not covered by tests
BOOST_LOG(fatal) << message;
break;

Check warning on line 190 in src/logging.cpp

View check run for this annotation

Codecov / codecov/patch

src/logging.cpp#L190

Added line #L190 was not covered by tests
}
});

Check warning on line 192 in src/logging.cpp

View check run for this annotation

Codecov / codecov/patch

src/logging.cpp#L192

Added line #L192 was not covered by tests
}

void
log_flush() {
if (sink) {
Expand Down
7 changes: 7 additions & 0 deletions src/logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ namespace logging {
void
setup_av_logging(int min_log_level);

/**
* @brief Setup logging for libdisplaydevice.
* @param min_log_level The log level.
*/
void
setup_libdisplaydevice_logging(int min_log_level);

/**
* @brief Flush the log.
* @examples
Expand Down
15 changes: 13 additions & 2 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

// local includes
#include "confighttp.h"
#include "display_device.h"
#include "entry_handler.h"
#include "globals.h"
#include "httpcommon.h"
Expand Down Expand Up @@ -133,6 +134,14 @@ main(int argc, char *argv[]) {
return fn->second(argv[0], config::sunshine.cmd.argc, config::sunshine.cmd.argv);
}

// Adding guard here first as it also performs recovery after crash,
// otherwise people could theoretically end up without display output.
// It also should be destroyed before forced shutdown to expedite the cleanup.
auto display_device_deinit_guard = display_device::init();
if (!display_device_deinit_guard) {
BOOST_LOG(error) << "Display device session failed to initialize"sv;
}

#ifdef WIN32
// Modify relevant NVIDIA control panel settings if the system has corresponding gpu
if (nvprefs_instance.load()) {
Expand Down Expand Up @@ -230,7 +239,7 @@ main(int argc, char *argv[]) {

// Create signal handler after logging has been initialized
auto shutdown_event = mail::man->event<bool>(mail::shutdown);
on_signal(SIGINT, [&force_shutdown, shutdown_event]() {
on_signal(SIGINT, [&force_shutdown, &display_device_deinit_guard, shutdown_event]() {
BOOST_LOG(info) << "Interrupt handler called"sv;

auto task = []() {
Expand All @@ -241,9 +250,10 @@ main(int argc, char *argv[]) {
force_shutdown = task_pool.pushDelayed(task, 10s).task_id;

shutdown_event->raise(true);
display_device_deinit_guard = nullptr;
});

on_signal(SIGTERM, [&force_shutdown, shutdown_event]() {
on_signal(SIGTERM, [&force_shutdown, &display_device_deinit_guard, shutdown_event]() {
BOOST_LOG(info) << "Terminate handler called"sv;

auto task = []() {
Expand All @@ -254,6 +264,7 @@ main(int argc, char *argv[]) {
force_shutdown = task_pool.pushDelayed(task, 10s).task_id;

shutdown_event->raise(true);
display_device_deinit_guard = nullptr;
});

#ifdef _WIN32
Expand Down
3 changes: 2 additions & 1 deletion src/platform/macos/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <chrono>
#include <mach/mach.h>

#include "src/display_device.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/utility.h"
Expand Down Expand Up @@ -541,7 +542,7 @@ const KeyCodeMap kKeyCodesMap[] = {
// Default to main display
macos_input->display = CGMainDisplayID();

auto output_name = config::video.output_name;
auto output_name = display_device::map_output_name(config::video.output_name);
// If output_name is set, try to find the display with that display id
if (!output_name.empty()) {
uint32_t max_display = 32;
Expand Down
4 changes: 3 additions & 1 deletion src/platform/windows/display_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ typedef long NTSTATUS;
#include "display.h"
#include "misc.h"
#include "src/config.h"
#include "src/display_device.h"
#include "src/logging.h"
#include "src/platform/common.h"
#include "src/video.h"
Expand Down Expand Up @@ -1101,7 +1102,8 @@ namespace platf {
BOOST_LOG(debug) << "Detecting monitors..."sv;

// We must set the GPU preference before calling any DXGI APIs!
if (!dxgi::probe_for_gpu_preference(config::video.output_name)) {
const auto output_name { display_device::map_output_name(config::video.output_name) };
if (!dxgi::probe_for_gpu_preference(output_name)) {
BOOST_LOG(warning) << "Failed to set GPU preference. Capture may not work!"sv;
}

Expand Down
Loading

0 comments on commit fbb45a8

Please sign in to comment.