From 7f75328845c6ea2c16addbdee70f2e00bdec61a2 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/confighttp.cpp | 10 +++ src/platform/common.h | 9 +++ src/platform/linux/cuda.cpp | 32 ++++++++++ src/platform/linux/kmsgrab.cpp | 79 ++++++++++++++++++++++++ src/platform/linux/misc.cpp | 17 +++++ src/platform/linux/wlgrab.cpp | 42 +++++++++++++ src/platform/linux/x11grab.cpp | 36 +++++++++++ src/platform/macos/display.mm | 24 +++++++ src/platform/windows/display_base.cpp | 47 ++++++++++++++ src_assets/common/assets/web/config.html | 23 +++++-- 10 files changed, 315 insertions(+), 4 deletions(-) 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..dbebfffdf0c 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -504,6 +504,12 @@ namespace platf { int width, height; + struct info { + int id; + std::string name; + bool is_primary_display; + }; + protected: // collect capture timing data (at loglevel debug) stat_trackers::min_max_avg_tracker sleep_overshoot_tracker; @@ -578,6 +584,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..60b6c2a1d83 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 = display_t::info { + 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..e4e7234ca3c 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -1747,4 +1747,83 @@ 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]; + std::string option_name; + if (monitor == nullptr) { + option_name = "w: " + std::to_string((int) (crtc->x + crtc->width)) + + " h: " + std::to_string((int) (crtc->y + crtc->height)); + } else { + option_name = monitor.index; + } + + auto option = display_t::info { + count++, + std::to_string(count) + ": " + option_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..75531a72f36 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -840,6 +840,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..c37afab5559 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 = display_t::info { + 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..bbd57d2bae7 100644 --- a/src/platform/linux/x11grab.cpp +++ b/src/platform/linux/x11grab.cpp @@ -836,6 +836,42 @@ 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 = XRRGetOutputPrimary(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) { + auto option = display_t::info { + monitor, + out_info->name + " connected: " + std::to_string(out_info->connection == RR_Connected), + 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..4115763b0b5 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 = display_t::info { + [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..7cec0be3cc7 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 = display_t::info { + 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) => {