From 607eae178d94328f7dda4090931a20cd07d38c01 Mon Sep 17 00:00:00 2001 From: Vithorio Polten Date: Tue, 30 Apr 2024 08:44:19 -0300 Subject: [PATCH] feat(ui): list available displays with `select` --- src/config.h | 6 ++ src/confighttp.cpp | 10 ++++ src/platform/common.h | 3 + src/platform/linux/cuda.cpp | 32 +++++++++++ src/platform/linux/kmsgrab.cpp | 72 ++++++++++++++++++++++++ src/platform/linux/misc.cpp | 25 ++++++++ src/platform/linux/wlgrab.cpp | 42 ++++++++++++++ src/platform/linux/x11grab.cpp | 40 +++++++++++++ src/platform/macos/display.mm | 24 ++++++++ src/platform/windows/display_base.cpp | 47 ++++++++++++++++ src_assets/common/assets/web/config.html | 23 ++++++-- 11 files changed, 320 insertions(+), 4 deletions(-) diff --git a/src/config.h b/src/config.h index 55098c0c5a1..9261e8a129d 100644 --- a/src/config.h +++ b/src/config.h @@ -82,6 +82,12 @@ namespace config { bool install_steam_drivers; }; + struct display_options_t { + int id; + std::string name; + bool is_primary_display; + }; + constexpr int ENCRYPTION_MODE_NEVER = 0; // Never use video encryption, even if the client supports it constexpr int ENCRYPTION_MODE_OPPORTUNISTIC = 1; // Use video encryption if available, but stream without it if not supported constexpr int ENCRYPTION_MODE_MANDATORY = 2; // Always use video encryption and refuse clients that can't encrypt diff --git a/src/confighttp.cpp b/src/confighttp.cpp index de25bf0e7cf..f0a53becded 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -543,6 +543,16 @@ namespace confighttp { outputTree.put("platform", SUNSHINE_PLATFORM); outputTree.put("version", PROJECT_VER); + pt::ptree displays; + for (const auto &[id, name, is_primary_display] : platf::display_options()) { + pt::ptree display_value; + display_value.put("id", id); + display_value.put("name", name); + display_value.put("is_primary", is_primary_display); + displays.push_front(std::make_pair(std::to_string(id), display_value)); + } + outputTree.push_back(std::make_pair("displays", displays)); + auto vars = config::parse_config(file_handler::read_file(config::sunshine.config_file.c_str())); for (auto &[name, value] : vars) { diff --git a/src/platform/common.h b/src/platform/common.h index 007f7ece61b..7f8b2ff1b6f 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -578,6 +578,9 @@ namespace platf { std::vector display_names(mem_type_e hwdevice_type); + std::vector + display_options(); + /** * @brief Returns if GPUs/drivers have changed since the last call to this function. * @return `true` if a change has occurred or if it is unknown whether a change occurred. diff --git a/src/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp index 129a23e6bd5..9359dc87c92 100644 --- a/src/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -1049,4 +1049,36 @@ namespace platf { return display_names; } + + std::vector + nvfbc_display_options() { + if (cuda::init() || cuda::nvfbc::init()) { + return {}; + } + + std::vector display_options; + + auto handle = cuda::nvfbc::handle_t::make(); + if (!handle) { + return {}; + } + + auto status_params = handle->status(); + if (!status_params) { + return {}; + } + + for (auto x = 0; x < status_params->dwOutputNum; ++x) { + auto &output = status_params->outputs[x]; + std::string name = output.name; + auto option = config::display_options_t { + x, + name + ", dwID: " + std::to_string(output.dwId), + false // TODO: Find proper way of doing it, found no way of checking for primary display myself + }; + display_options.emplace_back(option); + } + + return display_options; + } } // namespace platf diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index ad97c9b07d3..a235b0d9aeb 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -1747,4 +1747,76 @@ namespace platf { return display_names; } + std::vector + kms_display_options() { + int count = 0; + + if (!fs::exists("/dev/dri")) { + return {}; + } + + if (!gbm::create_device) { + return {}; + } + + std::vector display_options; + + kms::conn_type_count_t conn_type_count; + + fs::path card_dir { "/dev/dri"sv }; + for (auto &entry : fs::directory_iterator { card_dir }) { + auto file = entry.path().filename(); + + auto filestring = file.generic_string(); + if (std::string_view { filestring }.substr(0, 4) != "card"sv) { + continue; + } + + kms::card_t card; + if (card.init(entry.path().c_str())) { + continue; + } + + auto crtc_to_monitor = kms::map_crtc_to_monitor(card.monitors(conn_type_count)); + + auto end = std::end(card); + for (auto plane = std::begin(card); plane != end; ++plane) { + // Skip unused planes + if (!plane->fb_id) { + continue; + } + + if (card.is_cursor(plane->plane_id)) { + continue; + } + + auto fb = card.fb(plane.get()); + if (!fb) { + continue; + } + + if (!fb->handles[0]) { + break; + } + + // This appears to return the offset of the monitor + auto crtc = card.crtc(plane->crtc_id); + if (!crtc) { + continue; + } + + auto monitor = crtc_to_monitor[plane->crtc_id]; + + auto option = config::display_options_t { + count++, + std::to_string(count) + ", type: " + std::to_string(monitor.index), // TODO: Get real display name + false // TODO: Dunno how to find if it is primary + }; + display_options.emplace_back(option); + } + } + + return display_options; + } + } // namespace platf diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 980c0804858..97efe06598f 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -777,6 +777,8 @@ namespace platf { #ifdef SUNSHINE_BUILD_CUDA std::vector nvfbc_display_names(); + std::vector + nvfbc_display_options(); std::shared_ptr nvfbc_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config); @@ -789,6 +791,8 @@ namespace platf { #ifdef SUNSHINE_BUILD_WAYLAND std::vector wl_display_names(); + std::vector + wl_display_options(); std::shared_ptr wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config); @@ -801,6 +805,8 @@ namespace platf { #ifdef SUNSHINE_BUILD_DRM std::vector kms_display_names(mem_type_e hwdevice_type); + std::vector + kms_display_options(); std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config); @@ -813,6 +819,8 @@ namespace platf { #ifdef SUNSHINE_BUILD_X11 std::vector x11_display_names(); + std::vector + x11_display_options(); std::shared_ptr x11_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config); @@ -840,6 +848,23 @@ namespace platf { return {}; } + std::vector + display_options() { + #ifdef SUNSHINE_BUILD_CUDA + if (sources[source::NVFBC]) return nvfbc_display_options(); + #endif + #ifdef SUNSHINE_BUILD_WAYLAND + if (sources[source::WAYLAND]) return wl_display_options(); + #endif + #ifdef SUNSHINE_BUILD_DRM + if (sources[source::KMS]) return kms_display_options(); + #endif + #ifdef SUNSHINE_BUILD_X11 + if (sources[source::X11]) return x11_display_options(); + #endif + return {}; + } + /** * @brief Returns if GPUs/drivers have changed since the last call to this function. * @return `true` if a change has occurred or if it is unknown whether a change occurred. diff --git a/src/platform/linux/wlgrab.cpp b/src/platform/linux/wlgrab.cpp index a6ac4adbb96..1633fae8f8f 100644 --- a/src/platform/linux/wlgrab.cpp +++ b/src/platform/linux/wlgrab.cpp @@ -454,4 +454,46 @@ namespace platf { return display_names; } + std::vector + wl_display_options() { + wl::display_t display; + if (display.init()) { + return {}; + } + + wl::interface_t interface; + interface.listen(display.registry()); + + display.roundtrip(); + + if (!interface[wl::interface_t::XDG_OUTPUT]) { + return {}; + } + + if (!interface[wl::interface_t::WLR_EXPORT_DMABUF]) { + return {}; + } + + for (auto &monitor : interface.monitors) { + monitor->listen(interface.output_manager); + } + + display.roundtrip(); + + std::vector display_options; + + for (int x = 0; x < interface.monitors.size(); ++x) { + auto monitor = interface.monitors[x].get(); + + auto option = config::display_options_t { + x, + monitor->name + ": " + monitor->description, + false // TODO: Find proper way of doing it, found no way of checking for primary display myself + }; + display_options.emplace_back(option); + } + + return display_options; + } + } // namespace platf diff --git a/src/platform/linux/x11grab.cpp b/src/platform/linux/x11grab.cpp index c3b23a4c97e..181a9383ae0 100644 --- a/src/platform/linux/x11grab.cpp +++ b/src/platform/linux/x11grab.cpp @@ -64,6 +64,7 @@ namespace platf { namespace rr { _FN(GetScreenResources, XRRScreenResources *, (Display * dpy, Window window)); + _FN(GetOutputPrimary, RROutput, (Display * dpy, Window window)); _FN(GetOutputInfo, XRROutputInfo *, (Display * dpy, XRRScreenResources *resources, RROutput output)); _FN(GetCrtcInfo, XRRCrtcInfo *, (Display * dpy, XRRScreenResources *resources, RRCrtc crtc)); _FN(FreeScreenResources, void, (XRRScreenResources * resources)); @@ -86,6 +87,7 @@ namespace platf { std::vector> funcs { { (dyn::apiproc *) &GetScreenResources, "XRRGetScreenResources" }, + { (dyn::apiproc *) &GetOutputPrimary, "XRRGetOutputPrimary" }, { (dyn::apiproc *) &GetOutputInfo, "XRRGetOutputInfo" }, { (dyn::apiproc *) &GetCrtcInfo, "XRRGetCrtcInfo" }, { (dyn::apiproc *) &FreeScreenResources, "XRRFreeScreenResources" }, @@ -836,6 +838,44 @@ namespace platf { return names; } + std::vector + x11_display_options() { + if (load_x11() || load_xcb()) { + return {}; + } + + x11::xdisplay_t xdisplay { x11::OpenDisplay(nullptr) }; + if (!xdisplay) { + return {}; + } + + auto xwindow = DefaultRootWindow(xdisplay.get()); + screen_res_t screenr { x11::rr::GetScreenResources(xdisplay.get(), xwindow) }; + int output = screenr->noutput; + + auto main_display = x11::rr::GetOutputPrimary(xdisplay.get(), xwindow); + + std::vector display_options; + + int monitor = 0; + for (int x = 0; x < output; ++x) { + output_info_t out_info { x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) }; + if (out_info) { + std::string name = out_info->name; + auto is_connected_value = (out_info->connection == RR_Connected) ? "true" : "false"; + auto option = config::display_options_t { + monitor, + name + " connected: " + is_connected_value, + main_display == screenr->outputs[x] + }; + display_options.emplace_back(option); + ++monitor; + } + } + + return display_options; + } + void freeImage(XImage *p) { XDestroyImage(p); diff --git a/src/platform/macos/display.mm b/src/platform/macos/display.mm index 3468d46f55e..3f1fbd1b722 100644 --- a/src/platform/macos/display.mm +++ b/src/platform/macos/display.mm @@ -188,6 +188,30 @@ return display_names; } + std::vector + display_options() { + __block std::vector display_options; + + auto display_array = [AVVideo displayNames]; + + auto main_display_id = CGMainDisplayID(); + display_options.reserve([display_array count]); + + [display_array enumerateObjectsUsingBlock:^(NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { + NSNumber *display_id = obj[@"id"]; + NSString *name = obj[@"displayName"]; + + auto option = config::display_options_t { + [display_id intValue], + name.UTF8String, + main_display_id == [display_id unsignedIntValue] + }; + display_options.emplace_back(option); + }]; + + return display_options; + } + /** * @brief Returns if GPUs/drivers have changed since the last call to this function. * @return `true` if a change has occurred or if it is unknown whether a change occurred. diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 227efe7628f..84a6e7e6014 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -1138,6 +1138,53 @@ namespace platf { return display_names; } + std::vector + display_options() { + // We sync the thread desktop once before we start the enumeration process + // to ensure test_dxgi_duplication() returns consistent results for all GPUs + // even if the current desktop changes during our enumeration process. + // It is critical that we either fully succeed in enumeration or fully fail, + // otherwise it can lead to the capture code switching monitors unexpectedly. + syncThreadDesktop(); + + dxgi::factory1_t factory; + auto status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **) &factory); + if (FAILED(status)) { + return {}; + } + + std::vector display_options; + + dxgi::adapter_t adapter; + int monitorIndex = 0; + for (int x = 0; factory->EnumAdapters1(x, &adapter) != DXGI_ERROR_NOT_FOUND; ++x) { + DXGI_ADAPTER_DESC1 adapter_desc; + adapter->GetDesc1(&adapter_desc); + + dxgi::output_t::pointer output_p {}; + for (int y = 0; adapter->EnumOutputs(y, &output_p) != DXGI_ERROR_NOT_FOUND; ++y) { + dxgi::output_t output { output_p }; + + DXGI_OUTPUT_DESC desc; + output->GetDesc(&desc); + + auto device_name = to_utf8(desc.DeviceName); + + // Don't include the display in the list if we can't actually capture it + if (desc.AttachedToDesktop && dxgi::test_dxgi_duplication(adapter, output, true)) { + auto option = config::display_options_t { + monitorIndex++, + std::move(device_name), + false // TODO: Correclty check if this is the primary display for windows, idk how + }; + display_options.emplace_back(option); + } + } + } + + return display_options; + } + /** * @brief Returns if GPUs/drivers have changed since the last call to this function. * @return `true` if a change has occurred or if it is unknown whether a change occurred. diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index babb1ad4c46..34f20d8acae 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -386,7 +386,13 @@

+ v-model="config.output_name" v-if="!displays"> +
{{ $t('config.output_name_desc_win') }}
tools\dxgi-info.exe
@@ -394,11 +400,17 @@

- + +
{{ $t('config.output_name_desc_unix') }}

-
+            
               Info: Detecting displays
               Info: Detected display: DVI-D-0 (id: 0) connected: false
               Info: Detected display: HDMI-0 (id: 1) connected: true
@@ -406,7 +418,7 @@ 

Info: Detected display: DP-1 (id: 3) connected: false Info: Detected display: DVI-D-1 (id: 4) connected: false

-
+            
               Info: Detecting displays
               Info: Detected display: Monitor-0 (id: 3) connected: true
               Info: Detected display: Monitor-1 (id: 2) connected: true
@@ -1080,6 +1092,7 @@ 

data() { return { platform: "", + displays: [], saved: false, restarted: false, config: null, @@ -1237,6 +1250,8 @@

this.config = r; this.platform = this.config.platform; + this.displays = this.config.displays; + var app = document.getElementById("app"); if (this.platform === "windows") { this.tabs = this.tabs.filter((el) => {