electron_api_desktop_capturer.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. // Copyright (c) 2015 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/api/electron_api_desktop_capturer.h"
  5. #include <map>
  6. #include <memory>
  7. #include <utility>
  8. #include <vector>
  9. #include "base/strings/string_number_conversions.h"
  10. #include "base/strings/utf_string_conversions.h"
  11. #include "base/threading/thread_restrictions.h"
  12. #include "chrome/browser/media/webrtc/desktop_media_list.h"
  13. #include "chrome/browser/media/webrtc/window_icon_util.h"
  14. #include "content/public/browser/desktop_capture.h"
  15. #include "gin/object_template_builder.h"
  16. #include "shell/browser/javascript_environment.h"
  17. #include "shell/common/api/electron_api_native_image.h"
  18. #include "shell/common/gin_converters/gfx_converter.h"
  19. #include "shell/common/gin_helper/dictionary.h"
  20. #include "shell/common/gin_helper/event_emitter_caller.h"
  21. #include "shell/common/node_includes.h"
  22. #include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h"
  23. #include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
  24. #if defined(USE_OZONE)
  25. #include "ui/ozone/buildflags.h"
  26. #if BUILDFLAG(OZONE_PLATFORM_X11)
  27. #define USE_OZONE_PLATFORM_X11
  28. #endif
  29. #endif
  30. #if BUILDFLAG(IS_WIN)
  31. #include "third_party/webrtc/modules/desktop_capture/win/dxgi_duplicator_controller.h"
  32. #include "third_party/webrtc/modules/desktop_capture/win/screen_capturer_win_directx.h"
  33. #include "ui/display/win/display_info.h"
  34. #elif BUILDFLAG(IS_LINUX)
  35. #if defined(USE_OZONE_PLATFORM_X11)
  36. #include "base/logging.h"
  37. #include "ui/base/x/x11_display_util.h"
  38. #include "ui/base/x/x11_util.h"
  39. #include "ui/display/util/edid_parser.h" // nogncheck
  40. #include "ui/gfx/x/randr.h"
  41. #include "ui/gfx/x/x11_atom_cache.h"
  42. #include "ui/gfx/x/xproto_util.h"
  43. #endif // defined(USE_OZONE_PLATFORM_X11)
  44. #endif // BUILDFLAG(IS_WIN)
  45. #if BUILDFLAG(IS_LINUX)
  46. // Private function in ui/base/x/x11_display_util.cc
  47. std::map<x11::RandR::Output, int> GetMonitors(int version,
  48. x11::RandR* randr,
  49. x11::Window window) {
  50. std::map<x11::RandR::Output, int> output_to_monitor;
  51. if (version >= 105) {
  52. if (auto reply = randr->GetMonitors({window}).Sync()) {
  53. for (size_t monitor = 0; monitor < reply->monitors.size(); monitor++) {
  54. for (x11::RandR::Output output : reply->monitors[monitor].outputs)
  55. output_to_monitor[output] = monitor;
  56. }
  57. }
  58. }
  59. return output_to_monitor;
  60. }
  61. // Get the EDID data from the |output| and stores to |edid|.
  62. // Private function in ui/base/x/x11_display_util.cc
  63. std::vector<uint8_t> GetEDIDProperty(x11::RandR* randr,
  64. x11::RandR::Output output) {
  65. constexpr const char kRandrEdidProperty[] = "EDID";
  66. auto future = randr->GetOutputProperty(x11::RandR::GetOutputPropertyRequest{
  67. .output = output,
  68. .property = x11::GetAtom(kRandrEdidProperty),
  69. .long_length = 128});
  70. auto response = future.Sync();
  71. std::vector<uint8_t> edid;
  72. if (response && response->format == 8 && response->type != x11::Atom::None)
  73. edid = std::move(response->data);
  74. return edid;
  75. }
  76. // Find the mapping from monitor name atom to the display identifier
  77. // that the screen API uses. Based on the logic in BuildDisplaysFromXRandRInfo
  78. // in ui/base/x/x11_display_util.cc
  79. std::map<int32_t, uint32_t> MonitorAtomIdToDisplayId() {
  80. auto* connection = x11::Connection::Get();
  81. auto& randr = connection->randr();
  82. auto x_root_window = ui::GetX11RootWindow();
  83. int version = ui::GetXrandrVersion();
  84. std::map<int32_t, uint32_t> monitor_atom_to_display;
  85. auto resources = randr.GetScreenResourcesCurrent({x_root_window}).Sync();
  86. if (!resources) {
  87. LOG(ERROR) << "XRandR returned no displays; don't know how to map ids";
  88. return monitor_atom_to_display;
  89. }
  90. std::map<x11::RandR::Output, int> output_to_monitor =
  91. GetMonitors(version, &randr, x_root_window);
  92. auto monitors_reply = randr.GetMonitors({x_root_window}).Sync();
  93. for (size_t i = 0; i < resources->outputs.size(); i++) {
  94. x11::RandR::Output output_id = resources->outputs[i];
  95. auto output_info =
  96. randr.GetOutputInfo({output_id, resources->config_timestamp}).Sync();
  97. if (!output_info)
  98. continue;
  99. if (output_info->connection != x11::RandR::RandRConnection::Connected)
  100. continue;
  101. if (output_info->crtc == static_cast<x11::RandR::Crtc>(0))
  102. continue;
  103. auto crtc =
  104. randr.GetCrtcInfo({output_info->crtc, resources->config_timestamp})
  105. .Sync();
  106. if (!crtc)
  107. continue;
  108. display::EdidParser edid_parser(
  109. GetEDIDProperty(&randr, static_cast<x11::RandR::Output>(output_id)));
  110. auto output_32 = static_cast<uint32_t>(output_id);
  111. int64_t display_id =
  112. output_32 > 0xff ? 0 : edid_parser.GetIndexBasedDisplayId(output_32);
  113. // It isn't ideal, but if we can't parse the EDID data, fall back on the
  114. // display number.
  115. if (!display_id)
  116. display_id = i;
  117. // Find the mapping between output identifier and the monitor name atom
  118. // Note this isn't the atom string, but the numeric atom identifier,
  119. // since this is what the WebRTC system uses as the display identifier
  120. auto output_monitor_iter = output_to_monitor.find(output_id);
  121. if (output_monitor_iter != output_to_monitor.end()) {
  122. x11::Atom atom =
  123. monitors_reply->monitors[output_monitor_iter->second].name;
  124. monitor_atom_to_display[static_cast<int32_t>(atom)] = display_id;
  125. }
  126. }
  127. return monitor_atom_to_display;
  128. }
  129. #endif
  130. namespace gin {
  131. template <>
  132. struct Converter<electron::api::DesktopCapturer::Source> {
  133. static v8::Local<v8::Value> ToV8(
  134. v8::Isolate* isolate,
  135. const electron::api::DesktopCapturer::Source& source) {
  136. gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
  137. content::DesktopMediaID id = source.media_list_source.id;
  138. dict.Set("name", base::UTF16ToUTF8(source.media_list_source.name));
  139. dict.Set("id", id.ToString());
  140. dict.Set("thumbnail",
  141. electron::api::NativeImage::Create(
  142. isolate, gfx::Image(source.media_list_source.thumbnail)));
  143. dict.Set("display_id", source.display_id);
  144. if (source.fetch_icon) {
  145. dict.Set(
  146. "appIcon",
  147. electron::api::NativeImage::Create(
  148. isolate, gfx::Image(GetWindowIcon(source.media_list_source.id))));
  149. } else {
  150. dict.Set("appIcon", nullptr);
  151. }
  152. return ConvertToV8(isolate, dict);
  153. }
  154. };
  155. } // namespace gin
  156. namespace electron::api {
  157. gin::WrapperInfo DesktopCapturer::kWrapperInfo = {gin::kEmbedderNativeGin};
  158. DesktopCapturer::DesktopCapturer(v8::Isolate* isolate) {}
  159. DesktopCapturer::~DesktopCapturer() = default;
  160. void DesktopCapturer::StartHandling(bool capture_window,
  161. bool capture_screen,
  162. const gfx::Size& thumbnail_size,
  163. bool fetch_window_icons) {
  164. fetch_window_icons_ = fetch_window_icons;
  165. #if BUILDFLAG(IS_WIN)
  166. if (content::desktop_capture::CreateDesktopCaptureOptions()
  167. .allow_directx_capturer()) {
  168. // DxgiDuplicatorController should be alive in this scope according to
  169. // screen_capturer_win.cc.
  170. auto duplicator = webrtc::DxgiDuplicatorController::Instance();
  171. using_directx_capturer_ = webrtc::ScreenCapturerWinDirectx::IsSupported();
  172. }
  173. #endif // BUILDFLAG(IS_WIN)
  174. // clear any existing captured sources.
  175. captured_sources_.clear();
  176. // Start listening for captured sources.
  177. capture_window_ = capture_window;
  178. capture_screen_ = capture_screen;
  179. {
  180. // Initialize the source list.
  181. // Apply the new thumbnail size and restart capture.
  182. if (capture_window) {
  183. if (auto capturer = content::desktop_capture::CreateWindowCapturer();
  184. capturer) {
  185. window_capturer_ = std::make_unique<NativeDesktopMediaList>(
  186. DesktopMediaList::Type::kWindow, std::move(capturer));
  187. window_capturer_->SetThumbnailSize(thumbnail_size);
  188. window_capturer_->Update(
  189. base::BindOnce(&DesktopCapturer::UpdateSourcesList,
  190. weak_ptr_factory_.GetWeakPtr(),
  191. window_capturer_.get()),
  192. /* refresh_thumbnails = */ true);
  193. }
  194. }
  195. if (capture_screen) {
  196. if (auto capturer = content::desktop_capture::CreateScreenCapturer();
  197. capturer) {
  198. screen_capturer_ = std::make_unique<NativeDesktopMediaList>(
  199. DesktopMediaList::Type::kScreen, std::move(capturer));
  200. screen_capturer_->SetThumbnailSize(thumbnail_size);
  201. screen_capturer_->Update(
  202. base::BindOnce(&DesktopCapturer::UpdateSourcesList,
  203. weak_ptr_factory_.GetWeakPtr(),
  204. screen_capturer_.get()),
  205. /* refresh_thumbnails = */ true);
  206. }
  207. }
  208. }
  209. }
  210. void DesktopCapturer::UpdateSourcesList(DesktopMediaList* list) {
  211. if (capture_window_ &&
  212. list->GetMediaListType() == DesktopMediaList::Type::kWindow) {
  213. capture_window_ = false;
  214. std::vector<DesktopCapturer::Source> window_sources;
  215. window_sources.reserve(list->GetSourceCount());
  216. for (int i = 0; i < list->GetSourceCount(); i++) {
  217. window_sources.emplace_back(list->GetSource(i), std::string(),
  218. fetch_window_icons_);
  219. }
  220. std::move(window_sources.begin(), window_sources.end(),
  221. std::back_inserter(captured_sources_));
  222. }
  223. if (capture_screen_ &&
  224. list->GetMediaListType() == DesktopMediaList::Type::kScreen) {
  225. capture_screen_ = false;
  226. std::vector<DesktopCapturer::Source> screen_sources;
  227. screen_sources.reserve(list->GetSourceCount());
  228. for (int i = 0; i < list->GetSourceCount(); i++) {
  229. screen_sources.emplace_back(list->GetSource(i), std::string());
  230. }
  231. #if BUILDFLAG(IS_WIN)
  232. // Gather the same unique screen IDs used by the electron.screen API in
  233. // order to provide an association between it and
  234. // desktopCapturer/getUserMedia. This is only required when using the
  235. // DirectX capturer, otherwise the IDs across the APIs already match.
  236. if (using_directx_capturer_) {
  237. std::vector<std::string> device_names;
  238. // Crucially, this list of device names will be in the same order as
  239. // |media_list_sources|.
  240. if (!webrtc::DxgiDuplicatorController::Instance()->GetDeviceNames(
  241. &device_names)) {
  242. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  243. v8::HandleScope scope(isolate);
  244. gin_helper::CallMethod(this, "_onerror", "Failed to get sources.");
  245. Unpin();
  246. return;
  247. }
  248. int device_name_index = 0;
  249. for (auto& source : screen_sources) {
  250. const auto& device_name = device_names[device_name_index++];
  251. std::wstring wide_device_name;
  252. base::UTF8ToWide(device_name.c_str(), device_name.size(),
  253. &wide_device_name);
  254. const int64_t device_id =
  255. display::win::internal::DisplayInfo::DeviceIdFromDeviceName(
  256. wide_device_name.c_str());
  257. source.display_id = base::NumberToString(device_id);
  258. }
  259. }
  260. #elif BUILDFLAG(IS_MAC)
  261. // On Mac, the IDs across the APIs match.
  262. for (auto& source : screen_sources) {
  263. source.display_id = base::NumberToString(source.media_list_source.id.id);
  264. }
  265. #elif BUILDFLAG(IS_LINUX)
  266. #if defined(USE_OZONE_PLATFORM_X11)
  267. // On Linux, with X11, the source id is the numeric value of the
  268. // display name atom and the display id is either the EDID or the
  269. // loop index when that display was found (see
  270. // BuildDisplaysFromXRandRInfo in ui/base/x/x11_display_util.cc)
  271. std::map<int32_t, uint32_t> monitor_atom_to_display_id =
  272. MonitorAtomIdToDisplayId();
  273. for (auto& source : screen_sources) {
  274. auto display_id_iter =
  275. monitor_atom_to_display_id.find(source.media_list_source.id.id);
  276. if (display_id_iter != monitor_atom_to_display_id.end())
  277. source.display_id = base::NumberToString(display_id_iter->second);
  278. }
  279. #endif // defined(USE_OZONE_PLATFORM_X11)
  280. #endif // BUILDFLAG(IS_WIN)
  281. std::move(screen_sources.begin(), screen_sources.end(),
  282. std::back_inserter(captured_sources_));
  283. }
  284. if (!capture_window_ && !capture_screen_) {
  285. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  286. v8::HandleScope scope(isolate);
  287. gin_helper::CallMethod(this, "_onfinished", captured_sources_);
  288. Unpin();
  289. }
  290. }
  291. // static
  292. gin::Handle<DesktopCapturer> DesktopCapturer::Create(v8::Isolate* isolate) {
  293. auto handle = gin::CreateHandle(isolate, new DesktopCapturer(isolate));
  294. // Keep reference alive until capturing has finished.
  295. handle->Pin(isolate);
  296. return handle;
  297. }
  298. gin::ObjectTemplateBuilder DesktopCapturer::GetObjectTemplateBuilder(
  299. v8::Isolate* isolate) {
  300. return gin::Wrappable<DesktopCapturer>::GetObjectTemplateBuilder(isolate)
  301. .SetMethod("startHandling", &DesktopCapturer::StartHandling);
  302. }
  303. const char* DesktopCapturer::GetTypeName() {
  304. return "DesktopCapturer";
  305. }
  306. } // namespace electron::api
  307. namespace {
  308. void Initialize(v8::Local<v8::Object> exports,
  309. v8::Local<v8::Value> unused,
  310. v8::Local<v8::Context> context,
  311. void* priv) {
  312. gin_helper::Dictionary dict(context->GetIsolate(), exports);
  313. dict.SetMethod("createDesktopCapturer",
  314. &electron::api::DesktopCapturer::Create);
  315. }
  316. } // namespace
  317. NODE_LINKED_BINDING_CONTEXT_AWARE(electron_browser_desktop_capturer, Initialize)