Skip to content
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

feat(display)!: Add libdisplaydevice dependency and output name mapping #2894

Merged
merged 1 commit into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
3 changes: 3 additions & 0 deletions packaging/linux/flatpak/dev.lizardbyte.app.Sunshine.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ modules:
# Test dependencies
- "modules/xvfb/xvfb.json"

# Build dependencies
- "modules/nlohmann_json.json"
ReenigneArcher marked this conversation as resolved.
Show resolved Hide resolved

# Runtime dependencies
- shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json
- "modules/avahi.json"
Expand Down
15 changes: 15 additions & 0 deletions packaging/linux/flatpak/modules/nlohmann_json.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "nlohmann_json",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DJSON_MultipleHeaders=OFF",
"-DJSON_BuildTests=OFF"
],
"sources": [
{
"type": "archive",
"url": "https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz",
"sha256": "d6c65aca6b1ed68e7a182f4757257b107ae403032760ed6ef121c9d55e81757d"
}
]
}
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 @@
}

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 @@
});
}

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
Loading
Loading