From a1d24dcd21ea99f73d44c380143bc5736e6c3c1c Mon Sep 17 00:00:00 2001 From: "James N. V. Cash" Date: Wed, 20 Apr 2022 14:20:13 -0400 Subject: [PATCH] fix: set display_id in desktop capturer on Linux Previously, display_id was an empty string, pending Chrome support for sharing individual screens. Now that this has been added, it is desirable to have this property set correctly. notes: Set display_id for desktopCapturer on Linux --- .../api/electron_api_desktop_capturer.cc | 117 +++++++++++++++++- 1 file changed, 114 insertions(+), 3 deletions(-) diff --git a/shell/browser/api/electron_api_desktop_capturer.cc b/shell/browser/api/electron_api_desktop_capturer.cc index 00721e52bfcf8..972b2210343db 100644 --- a/shell/browser/api/electron_api_desktop_capturer.cc +++ b/shell/browser/api/electron_api_desktop_capturer.cc @@ -4,6 +4,7 @@ #include "shell/browser/api/electron_api_desktop_capturer.h" +#include #include #include #include @@ -28,8 +29,108 @@ #include "third_party/webrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h" #include "third_party/webrtc/modules/desktop_capture/win/screen_capturer_win_directx.h" #include "ui/display/win/display_info.h" +#elif BUILDFLAG(IS_LINUX) +#include "base/logging.h" +#include "ui/base/x/x11_display_util.h" +#include "ui/base/x/x11_util.h" +#include "ui/display/util/display_util.h" +#include "ui/display/util/edid_parser.h" +#include "ui/gfx/x/randr.h" +#include "ui/gfx/x/x11_atom_cache.h" +#include "ui/gfx/x/xproto_util.h" #endif // BUILDFLAG(IS_WIN) +#if BUILDFLAG(IS_LINUX) +// Find the mapping between output identifier and the monitor name atom +// Note this isn't the atom string, but the numeric atom identifier, +// since this is what the WebRTC system uses as the display identifier +// Based on `GetMonitors` in ui/base/x/x11_display_util.cc +std::map GetMonitorsNameAtom(int version, + x11::RandR* randr, + x11::Window window) { + std::map output_to_monitor; + if (version >= 105) { + if (auto reply = randr->GetMonitors({window}).Sync()) { + for (size_t monitor = 0; monitor < reply->monitors.size(); monitor++) { + for (x11::RandR::Output output : reply->monitors[monitor].outputs) + output_to_monitor[output] = + static_cast(reply->monitors[monitor].name); + } + } + } + return output_to_monitor; +} +// Get the EDID data from the |output| and stores to |edid|. +// Private function in ui/base/x/x11_display_util.cc +std::vector GetEDIDProperty(x11::RandR* randr, + x11::RandR::Output output) { + constexpr const char kRandrEdidProperty[] = "EDID"; + auto future = randr->GetOutputProperty(x11::RandR::GetOutputPropertyRequest{ + .output = output, + .property = x11::GetAtom(kRandrEdidProperty), + .long_length = 128}); + auto response = future.Sync(); + std::vector edid; + if (response && response->format == 8 && response->type != x11::Atom::None) + edid = std::move(response->data); + return edid; +} + +// Find the mapping from monitor name atom to the display identifier +// that the screen API uses. Based on the logic in BuildDisplaysFromXRandRInfo +// in ui/base/x/x11_display_util.cc +std::map MonitorAtomIdToDisplayId() { + auto* connection = x11::Connection::Get(); + auto& randr = connection->randr(); + auto x_root_window = ui::GetX11RootWindow(); + int version = ui::GetXrandrVersion(); + + std::map monitor_atom_to_display; + + auto resources = randr.GetScreenResourcesCurrent({x_root_window}).Sync(); + if (!resources) { + LOG(ERROR) << "XRandR returned no displays; don't know how to map ids"; + return monitor_atom_to_display; + } + + std::map output_to_atom = + GetMonitorsNameAtom(version, &randr, x_root_window); + + for (size_t i = 0; i < resources->outputs.size(); i++) { + x11::RandR::Output output_id = resources->outputs[i]; + auto output_info = + randr.GetOutputInfo({output_id, resources->config_timestamp}).Sync(); + if (!output_info) + continue; + + if (output_info->connection != x11::RandR::RandRConnection::Connected) + continue; + + if (output_info->crtc == static_cast(0)) + continue; + + auto crtc = + randr.GetCrtcInfo({output_info->crtc, resources->config_timestamp}) + .Sync(); + if (!crtc) + continue; + display::EdidParser edid_parser( + GetEDIDProperty(&randr, static_cast(output_id))); + auto output_32 = static_cast(output_id); + int64_t display_id = + output_32 > 0xff ? 0 : edid_parser.GetIndexBasedDisplayId(output_32); + if (!display_id) + display_id = i; + + auto output_atom_iter = output_to_atom.find(output_id); + if (output_atom_iter != output_to_atom.end()) + monitor_atom_to_display[output_atom_iter->second] = display_id; + } + + return monitor_atom_to_display; +} +#endif + namespace gin { template <> @@ -181,10 +282,20 @@ void DesktopCapturer::UpdateSourcesList(DesktopMediaList* list) { for (auto& source : screen_sources) { source.display_id = base::NumberToString(source.media_list_source.id.id); } +#elif BUILDFLAG(IS_LINUX) + // On Linux, with X11, the source id is the numeric value of the + // display name atom and the display id is either the EDID or the + // loop index when that display was found (see + // BuildDisplaysFromXRandRInfo in ui/base/x/x11_display_util.cc) + std::map monitor_atom_to_display_id = + MonitorAtomIdToDisplayId(); + for (auto& source : screen_sources) { + auto display_id_iter = + monitor_atom_to_display_id.find(source.media_list_source.id.id); + if (display_id_iter != monitor_atom_to_display_id.end()) + source.display_id = base::NumberToString(display_id_iter->second); + } #endif // BUILDFLAG(IS_WIN) - // TODO(ajmacd): Add Linux support. The IDs across APIs differ but Chrome - // only supports capturing the entire desktop on Linux. Revisit this if - // individual screen support is added. std::move(screen_sources.begin(), screen_sources.end(), std::back_inserter(captured_sources_)); }